Updated on 2026-08-14
This commit is contained in:
commit
d44fcb3218
61 changed files with 1333 additions and 232 deletions
|
|
@ -199,4 +199,30 @@ internal object YieldSupplyDomainModule {
|
|||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetShouldShowMainPromoUseCase(
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
): YieldSupplyGetShouldShowMainPromoUseCase {
|
||||
return YieldSupplyGetShouldShowMainPromoUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplySetShouldShowMainPromoUseCase(
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
): YieldSupplySetShouldShowMainPromoUseCase {
|
||||
return YieldSupplySetShouldShowMainPromoUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyGetDustMinAmountUseCase(): YieldSupplyGetDustMinAmountUseCase {
|
||||
return YieldSupplyGetDustMinAmountUseCase()
|
||||
}
|
||||
}
|
||||
|
|
@ -43,12 +43,14 @@ import java.math.BigDecimal
|
|||
*/
|
||||
class TokenItemStateConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val yieldModuleApyMap: Map<String, String> = emptyMap(),
|
||||
private val yieldModuleApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
private val stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
private val yieldSupplyPromoBannerKey: String? = null,
|
||||
private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = {
|
||||
CryptoCurrencyToIconStateConverter().convert(it)
|
||||
},
|
||||
private val onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)? = null,
|
||||
private val onYieldPromoCloseClick: (() -> Unit)? = null,
|
||||
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { currencyStatus ->
|
||||
createTitleState(
|
||||
currencyStatus = currencyStatus,
|
||||
|
|
@ -66,6 +68,15 @@ class TokenItemStateConverter(
|
|||
private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = {
|
||||
createFiatAmountState(status = it, appCurrency = appCurrency)
|
||||
},
|
||||
private val promoBannerProvider: (CryptoCurrencyStatus) -> TokenItemState.PromoBannerState = { status ->
|
||||
createPromoBannerState(
|
||||
status = status,
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKey,
|
||||
onApyLabelClick = onApyLabelClick,
|
||||
onYieldPromoCloseClick = onYieldPromoCloseClick,
|
||||
)
|
||||
},
|
||||
private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||
private val onItemLongClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||
|
|
@ -102,6 +113,7 @@ class TokenItemStateConverter(
|
|||
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||
fiatAmountState = requireNotNull(fiatAmountStateProvider(this)),
|
||||
subtitle2State = requireNotNull(subtitle2StateProvider(this)),
|
||||
promoBannerState = promoBannerProvider(this),
|
||||
onItemClick = onItemClick?.let { onItemClick ->
|
||||
{ onItemClick(it, this) }
|
||||
},
|
||||
|
|
@ -164,7 +176,7 @@ class TokenItemStateConverter(
|
|||
|
||||
private fun createTitleState(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
yieldModuleApyMap: Map<String, String>,
|
||||
yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?,
|
||||
): TokenItemState.TitleState {
|
||||
|
|
@ -204,7 +216,7 @@ class TokenItemStateConverter(
|
|||
// polygon-pos_0xc2132d05d31c914a87c6611c10748aeb04b58e8f
|
||||
private fun resolveEarnApy(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
yieldModuleApyMap: Map<String, String>,
|
||||
yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
): EarnApyInfo? {
|
||||
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token
|
||||
|
|
@ -223,7 +235,7 @@ class TokenItemStateConverter(
|
|||
wrappedList(yieldSupplyApy),
|
||||
),
|
||||
isActive = isActive,
|
||||
apy = yieldSupplyApy,
|
||||
apy = yieldSupplyApy.toString(),
|
||||
source = ApySource.YIELD_SUPPLY,
|
||||
)
|
||||
}
|
||||
|
|
@ -381,6 +393,36 @@ class TokenItemStateConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createPromoBannerState(
|
||||
status: CryptoCurrencyStatus,
|
||||
yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
yieldSupplyPromoBannerKey: String?,
|
||||
onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?,
|
||||
onYieldPromoCloseClick: (() -> Unit)?,
|
||||
): TokenItemState.PromoBannerState {
|
||||
val token = status.currency as? CryptoCurrency.Token ?: return TokenItemState.PromoBannerState.Empty
|
||||
if (yieldSupplyPromoBannerKey == null || yieldSupplyPromoBannerKey != token.yieldSupplyKey() ||
|
||||
yieldModuleApyMap[token.yieldSupplyKey()] == null
|
||||
) {
|
||||
return TokenItemState.PromoBannerState.Empty
|
||||
}
|
||||
val yieldSupplyApy =
|
||||
yieldModuleApyMap[token.yieldSupplyKey()] ?: return TokenItemState.PromoBannerState.Empty
|
||||
|
||||
return TokenItemState.PromoBannerState.Content(
|
||||
title = resourceReference(
|
||||
R.string.yield_module_main_screen_promo_banner_message,
|
||||
wrappedList(yieldSupplyApy),
|
||||
),
|
||||
onPromoBannerClick = {
|
||||
onApyLabelClick?.invoke(status, ApySource.YIELD_SUPPLY, yieldSupplyApy.toString())
|
||||
},
|
||||
onCloseClick = {
|
||||
onYieldPromoCloseClick?.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState {
|
||||
val fiatRate = value.fiatRate
|
||||
val priceChange = value.priceChange
|
||||
|
|
|
|||
|
|
@ -122,6 +122,10 @@ object PreferencesKeys {
|
|||
|
||||
val YIELD_SUPPLY_WARNINGS_STATES_KEY by lazy { stringPreferencesKey(name = "yieldSupplyWarningsStates") }
|
||||
|
||||
val YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY by lazy {
|
||||
booleanPreferencesKey(name = "yieldSupplyShouldShowMainPromo")
|
||||
}
|
||||
|
||||
val ACCESS_CODE_SKIPPED_STATES_KEY by lazy { stringPreferencesKey(name = "accessCodeSkippedStates") }
|
||||
|
||||
// region Notifications
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.core.ui.components.token
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.audits.AuditLabelUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
|
|
@ -28,6 +30,7 @@ import com.tangem.core.ui.components.token.internal.*
|
|||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.PromoBannerState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
|
|
@ -44,7 +47,7 @@ private const val TITLE_MIN_WIDTH_COEFFICIENT = 0.3
|
|||
private const val PRICE_MIN_WIDTH_COEFFICIENT = 0.32
|
||||
|
||||
private enum class LayoutId {
|
||||
ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT
|
||||
ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT, PROMO_BANNER
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -109,6 +112,14 @@ fun TokenItem(
|
|||
.testTag(TokenElementsTestTags.TOKEN_ICON),
|
||||
)
|
||||
|
||||
YieldSupplyPromoBanner(
|
||||
state = state.promoBannerState,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.PROMO_BANNER)
|
||||
.testTag(TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
TokenTitle(
|
||||
state = state.titleState,
|
||||
modifier = Modifier
|
||||
|
|
@ -220,6 +231,13 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
|
||||
val nonFiatContent = measurables.measure(layoutId = LayoutId.NON_FIAT_CONTENT, constraints = constraints)
|
||||
|
||||
val promoBanner = when (state.promoBannerState) {
|
||||
is PromoBannerState.Content -> measurables.measure(
|
||||
layoutId = LayoutId.PROMO_BANNER,
|
||||
constraints = constraints,
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
var firstRowRemainingFreeSpace: Int? = null
|
||||
var secondRowRemainingFreeSpace: Int? = null
|
||||
|
||||
|
|
@ -283,10 +301,18 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
)
|
||||
}
|
||||
|
||||
val promoBannerHeight = promoBanner?.height ?: 0
|
||||
val promoOffset = if (promoBannerHeight > 0) {
|
||||
promoBannerHeight - 8.dp.roundToPx()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
val layoutHeight = calculateLayoutHeight(
|
||||
state = state,
|
||||
minLayoutHeight = with(density) { dimens.size68.roundToPx() },
|
||||
layoutPadding = verticalPadding,
|
||||
promoOffset = promoOffset,
|
||||
title = title,
|
||||
fiatAmount = fiatAmount,
|
||||
cryptoAmount = cryptoAmount,
|
||||
|
|
@ -294,16 +320,22 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
)
|
||||
|
||||
layout(width = constraints.maxWidth, height = layoutHeight) {
|
||||
icon.placeRelative(x = 0, y = (layoutHeight - icon.height).div(other = 2))
|
||||
promoBanner?.placeRelative(x = 0, y = 0)
|
||||
|
||||
icon.placeRelative(
|
||||
x = 0,
|
||||
y = promoOffset + (layoutHeight - promoOffset - icon.height)
|
||||
.div(other = 2),
|
||||
)
|
||||
|
||||
title.placeRelative(
|
||||
x = icon.width,
|
||||
y = when (state) {
|
||||
y = promoOffset + when (state) {
|
||||
is TokenItemState.NoAddress,
|
||||
is TokenItemState.Unreachable,
|
||||
-> {
|
||||
if (state.subtitleState == null) {
|
||||
(layoutHeight - title.height).div(other = 2)
|
||||
(layoutHeight - promoOffset - title.height).div(other = 2)
|
||||
} else {
|
||||
verticalPadding
|
||||
}
|
||||
|
|
@ -314,8 +346,8 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
|
||||
fiatAmount?.placeRelative(
|
||||
x = layoutWidth - fiatAmount.width,
|
||||
y = when (state.subtitle2State) {
|
||||
null -> (layoutHeight - fiatAmount.height).div(other = 2)
|
||||
y = promoOffset + when (state.subtitle2State) {
|
||||
null -> (layoutHeight - promoOffset - fiatAmount.height).div(other = 2)
|
||||
else -> verticalPadding
|
||||
},
|
||||
)
|
||||
|
|
@ -335,7 +367,7 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
|
||||
nonFiatContent.placeRelative(
|
||||
x = layoutWidth - nonFiatContent.width,
|
||||
y = (layoutHeight - nonFiatContent.height).div(other = 2),
|
||||
y = promoOffset + (layoutHeight - promoOffset - nonFiatContent.height).div(other = 2),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -443,6 +475,7 @@ private fun calculateLayoutHeight(
|
|||
state: TokenItemState,
|
||||
minLayoutHeight: Int,
|
||||
layoutPadding: Int,
|
||||
promoOffset: Int,
|
||||
title: Placeable,
|
||||
fiatAmount: Placeable?,
|
||||
cryptoAmount: Placeable?,
|
||||
|
|
@ -468,7 +501,7 @@ private fun calculateLayoutHeight(
|
|||
}
|
||||
}
|
||||
|
||||
return max(firstColumnHeight, secondColumnHeight).coerceAtLeast(minLayoutHeight)
|
||||
return (promoOffset + max(firstColumnHeight, secondColumnHeight)).coerceAtLeast(promoOffset + minLayoutHeight)
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
|
|
@ -587,6 +620,11 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
promoBannerState = PromoBannerState.Content(
|
||||
title = TextReference.Str(value = "Trusted"),
|
||||
onPromoBannerClick = {},
|
||||
onCloseClick = {},
|
||||
),
|
||||
),
|
||||
TokenItemState.Loading(
|
||||
id = "Loading#1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
package com.tangem.core.ui.components.token.internal
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.ripple
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.PromoBannerState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
internal fun YieldSupplyPromoBanner(state: PromoBannerState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is PromoBannerState.Content -> YieldSupplyPromoBanner(state = state, modifier = modifier)
|
||||
is PromoBannerState.Empty -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun YieldSupplyPromoBanner(state: PromoBannerState.Content, modifier: Modifier = Modifier) {
|
||||
val bgColor = TangemTheme.colors.control.unchecked
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(color = bgColor, shape = TangemTheme.shapes.roundedCornersXMedium)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.clickable(onClick = state.onPromoBannerClick)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_analytics_up_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier
|
||||
.padding(end = TangemTheme.dimens.spacing8)
|
||||
.size(TangemTheme.dimens.size16),
|
||||
)
|
||||
Text(
|
||||
text = state.title.resolveReference(),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size16)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(bounded = false),
|
||||
onClick = { state.onCloseClick() },
|
||||
),
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_rectangle_bottom),
|
||||
contentDescription = null,
|
||||
tint = bgColor,
|
||||
modifier = Modifier
|
||||
.size(width = 12.dp, height = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_YieldSupplyPromoBanner() {
|
||||
TangemThemePreview {
|
||||
YieldSupplyPromoBanner(
|
||||
state = PromoBannerState.Content(
|
||||
title = TextReference.Str(value = "Earn up to 5% APY"),
|
||||
onPromoBannerClick = {},
|
||||
onCloseClick = {},
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,8 @@ sealed class TokenItemState {
|
|||
*/
|
||||
abstract val subtitle2State: Subtitle2State?
|
||||
|
||||
abstract val promoBannerState: PromoBannerState
|
||||
|
||||
/** Callback which will be called when an item is clicked */
|
||||
abstract val onItemClick: ((TokenItemState) -> Unit)?
|
||||
|
||||
|
|
@ -59,6 +61,7 @@ sealed class TokenItemState {
|
|||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Loading
|
||||
override val subtitle2State: Subtitle2State = Subtitle2State.Loading
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
|
|
@ -75,6 +78,7 @@ sealed class TokenItemState {
|
|||
override val subtitleState: SubtitleState = SubtitleState.Locked
|
||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Locked
|
||||
override val subtitle2State: Subtitle2State = Subtitle2State.Locked
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
|
|
@ -99,6 +103,7 @@ sealed class TokenItemState {
|
|||
override val subtitleState: SubtitleState,
|
||||
override val fiatAmountState: FiatAmountState?,
|
||||
override val subtitle2State: Subtitle2State?,
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty,
|
||||
override val onItemClick: ((TokenItemState) -> Unit)?,
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null,
|
||||
|
|
@ -120,6 +125,7 @@ sealed class TokenItemState {
|
|||
) : TokenItemState() {
|
||||
override val subtitleState: SubtitleState? = null
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
|
|
@ -146,6 +152,7 @@ sealed class TokenItemState {
|
|||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val subtitle2State: Subtitle2State? = null
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -168,6 +175,7 @@ sealed class TokenItemState {
|
|||
override val subtitle2State: Subtitle2State? = null
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
override val promoBannerState: PromoBannerState = PromoBannerState.Empty
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
@ -254,4 +262,15 @@ sealed class TokenItemState {
|
|||
|
||||
data object Locked : Subtitle2State()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class PromoBannerState {
|
||||
data class Content(
|
||||
val title: TextReference,
|
||||
val onPromoBannerClick: () -> Unit,
|
||||
val onCloseClick: () -> Unit,
|
||||
) : PromoBannerState()
|
||||
|
||||
data object Empty : PromoBannerState()
|
||||
}
|
||||
}
|
||||
|
|
@ -19,10 +19,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ChainStyle
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import androidx.constraintlayout.compose.Visibility
|
||||
import androidx.constraintlayout.compose.atLeast
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
|
|
@ -94,7 +96,7 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod
|
|||
bottom.linkTo(subtitleItem.top)
|
||||
start.linkTo(iconItem.end)
|
||||
end.linkTo(amountItem.start)
|
||||
width = Dimension.fillToConstraints
|
||||
width = Dimension.fillToConstraints.atLeast(50.dp)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -122,7 +124,6 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod
|
|||
visibility = state.isGoneIf { amount.isEmpty() }
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(timestampItem.top)
|
||||
start.linkTo(titleItem.end)
|
||||
end.linkTo(parent.end)
|
||||
width = Dimension.fillToConstraints
|
||||
},
|
||||
|
|
@ -436,7 +437,7 @@ private class TransactionItemStateProvider : CollectionPreviewParameterProvider<
|
|||
),
|
||||
TransactionState.Content(
|
||||
txHash = UUID.randomUUID().toString(),
|
||||
amount = "0.625 USDT",
|
||||
amount = "0.62521313 USDT",
|
||||
time = "€0.50",
|
||||
status = Status.Confirmed,
|
||||
direction = Direction.OUTGOING,
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ object TokenElementsTestTags {
|
|||
const val TOKEN_FIAT_AMOUNT = "TOKEN_FIAT_AMOUNT"
|
||||
const val TOKEN_CRYPTO_AMOUNT = "TOKEN_CRYPTO_AMOUNT"
|
||||
const val TOKEN_NON_FIAT_BLOCK = "TOKEN_NON_FIAT_BLOCK"
|
||||
const val TOKEN_YIELD_PROMO_BANNER = "TOKEN_YIELD_PROMO_BANNER"
|
||||
}
|
||||
9
core/ui/src/main/res/drawable/ic_connect_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_connect_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M11.799,8.899C12.047,8.469 12.597,8.322 13.028,8.57C14.276,9.291 15.268,10.385 15.863,11.698C16.458,13.011 16.627,14.478 16.345,15.892C16.064,17.306 15.347,18.597 14.295,19.582C13.242,20.567 11.908,21.199 10.478,21.387C9.049,21.575 7.596,21.31 6.325,20.631C5.054,19.951 4.027,18.891 3.389,17.598C2.752,16.305 2.535,14.844 2.77,13.422C3.005,12 3.68,10.686 4.699,9.667C5.05,9.316 5.62,9.316 5.971,9.667C6.323,10.019 6.323,10.588 5.971,10.939C5.218,11.693 4.719,12.664 4.546,13.715C4.372,14.766 4.532,15.845 5.004,16.801C5.475,17.756 6.234,18.541 7.173,19.043C8.113,19.545 9.187,19.741 10.244,19.602C11.3,19.462 12.286,18.996 13.064,18.268C13.842,17.539 14.372,16.585 14.58,15.54C14.788,14.495 14.663,13.411 14.223,12.441C13.783,11.47 13.05,10.662 12.128,10.129C11.697,9.88 11.55,9.33 11.799,8.899ZM13.766,2.615C15.196,2.427 16.648,2.692 17.92,3.371C19.191,4.051 20.218,5.111 20.856,6.404C21.494,7.697 21.71,9.158 21.475,10.58C21.24,12.002 20.566,13.316 19.546,14.335C19.195,14.686 18.625,14.686 18.273,14.335C17.922,13.984 17.922,13.414 18.273,13.063C19.027,12.309 19.525,11.338 19.699,10.287C19.872,9.236 19.712,8.156 19.241,7.2C18.77,6.245 18.011,5.46 17.071,4.958C16.131,4.456 15.058,4.26 14.002,4.399C12.945,4.539 11.959,5.005 11.181,5.733C10.404,6.462 9.874,7.416 9.666,8.461C9.458,9.506 9.582,10.59 10.022,11.561C10.462,12.531 11.194,13.34 12.117,13.873C12.547,14.122 12.695,14.672 12.447,15.103C12.198,15.533 11.648,15.68 11.217,15.432C9.969,14.711 8.977,13.617 8.382,12.304C7.788,10.991 7.619,9.524 7.9,8.11C8.181,6.696 8.899,5.406 9.951,4.42C11.003,3.435 12.337,2.804 13.766,2.615Z"
|
||||
android:fillColor="#1E1E1E"/>
|
||||
</vector>
|
||||
15
core/ui/src/main/res/drawable/ic_disconnect_24.xml
Normal file
15
core/ui/src/main/res/drawable/ic_disconnect_24.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M5.91,11.001C5.191,11.745 4.714,12.691 4.546,13.714C4.372,14.764 4.533,15.844 5.004,16.799C5.475,17.755 6.234,18.539 7.173,19.042C8.113,19.544 9.188,19.739 10.244,19.6C11.3,19.461 12.286,18.994 13.064,18.266C13.083,18.249 13.1,18.229 13.119,18.212L14.392,19.485C14.36,19.517 14.328,19.549 14.295,19.581C13.242,20.566 11.907,21.197 10.478,21.385C9.049,21.573 7.596,21.309 6.325,20.629C5.054,19.95 4.027,18.889 3.389,17.596C2.752,16.303 2.535,14.843 2.77,13.42C3,12.027 3.652,10.738 4.637,9.728L5.91,11.001Z"
|
||||
android:fillColor="#1E1E1E"/>
|
||||
<path
|
||||
android:pathData="M4.164,4.164C4.515,3.812 5.086,3.812 5.437,4.164L19.837,18.564C20.188,18.916 20.188,19.485 19.837,19.837C19.485,20.188 18.916,20.188 18.564,19.837L4.164,5.437C3.812,5.086 3.812,4.515 4.164,4.164Z"
|
||||
android:fillColor="#1E1E1E"/>
|
||||
<path
|
||||
android:pathData="M13.766,2.614C15.195,2.426 16.648,2.69 17.92,3.37C19.191,4.049 20.219,5.11 20.856,6.403C21.493,7.696 21.71,9.157 21.475,10.579C21.24,12.001 20.566,13.314 19.546,14.334C19.526,14.354 19.503,14.373 19.481,14.391L18.215,13.125C18.233,13.103 18.253,13.082 18.273,13.061C19.027,12.308 19.525,11.337 19.699,10.286C19.872,9.235 19.712,8.154 19.241,7.199C18.77,6.244 18.01,5.459 17.071,4.957C16.131,4.455 15.058,4.259 14.002,4.398C12.945,4.537 11.959,5.004 11.181,5.732C11.12,5.79 11.059,5.848 11.001,5.909L9.728,4.636C9.801,4.562 9.875,4.49 9.951,4.419C11.003,3.433 12.337,2.802 13.766,2.614Z"
|
||||
android:fillColor="#1E1E1E"/>
|
||||
</vector>
|
||||
14
core/ui/src/main/res/drawable/ic_gear_24.xml
Normal file
14
core/ui/src/main/res/drawable/ic_gear_24.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,7.6C14.43,7.6 16.4,9.57 16.4,12C16.4,14.43 14.43,16.4 12,16.4C9.57,16.4 7.6,14.43 7.6,12C7.6,9.57 9.57,7.6 12,7.6ZM12,9.4C10.564,9.4 9.4,10.564 9.4,12C9.4,13.436 10.564,14.6 12,14.6C13.436,14.6 14.6,13.436 14.6,12C14.6,10.564 13.436,9.4 12,9.4Z"
|
||||
android:fillColor="#1E1E1E"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M13.938,2.1C14.721,2.1 15.374,2.099 15.911,2.151C16.468,2.206 16.972,2.324 17.451,2.6C17.931,2.876 18.285,3.252 18.611,3.706C18.926,4.144 19.253,4.708 19.646,5.385L21.569,8.7C21.964,9.38 22.292,9.946 22.517,10.439C22.749,10.949 22.9,11.445 22.9,12C22.9,12.555 22.749,13.051 22.517,13.561C22.292,14.054 21.964,14.62 21.569,15.3L19.646,18.615C19.253,19.292 18.926,19.856 18.611,20.294C18.285,20.748 17.931,21.125 17.451,21.4C16.972,21.676 16.468,21.794 15.911,21.849C15.374,21.901 14.721,21.9 13.938,21.9H10.063C9.279,21.9 8.626,21.901 8.089,21.849C7.532,21.794 7.028,21.676 6.549,21.4C6.069,21.125 5.715,20.748 5.389,20.294C5.074,19.856 4.747,19.292 4.354,18.615L2.431,15.3C2.036,14.62 1.708,14.054 1.483,13.561C1.251,13.051 1.1,12.555 1.1,12C1.1,11.445 1.251,10.949 1.483,10.439C1.708,9.946 2.036,9.38 2.431,8.7L4.354,5.385C4.747,4.708 5.074,4.144 5.389,3.706C5.715,3.252 6.069,2.876 6.549,2.6C7.028,2.324 7.532,2.206 8.089,2.151C8.626,2.099 9.279,2.1 10.063,2.1H13.938ZM10.063,3.9C9.244,3.9 8.694,3.901 8.265,3.943C7.855,3.984 7.628,4.056 7.447,4.16C7.267,4.264 7.089,4.424 6.85,4.757C6.598,5.106 6.321,5.581 5.911,6.288L3.988,9.604C3.576,10.314 3.3,10.79 3.121,11.184C2.95,11.559 2.9,11.792 2.9,12C2.9,12.208 2.95,12.441 3.121,12.816C3.3,13.21 3.576,13.686 3.988,14.396L5.911,17.712C6.321,18.419 6.598,18.894 6.85,19.243C7.089,19.576 7.267,19.736 7.447,19.84C7.628,19.944 7.855,20.016 8.265,20.057C8.694,20.099 9.244,20.1 10.063,20.1H13.938C14.756,20.1 15.306,20.099 15.735,20.057C16.145,20.016 16.372,19.944 16.553,19.84C16.733,19.736 16.911,19.576 17.15,19.243C17.402,18.894 17.678,18.419 18.089,17.712L20.012,14.396C20.424,13.686 20.7,13.21 20.879,12.816C21.05,12.441 21.1,12.208 21.1,12C21.1,11.792 21.05,11.559 20.879,11.184C20.7,10.79 20.424,10.314 20.012,9.604L18.089,6.288C17.678,5.581 17.402,5.106 17.15,4.757C16.911,4.424 16.733,4.264 16.553,4.16C16.372,4.056 16.145,3.984 15.735,3.943C15.306,3.901 14.756,3.9 13.938,3.9H10.063Z"
|
||||
android:fillColor="#1E1E1E"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
9
core/ui/src/main/res/drawable/ic_rectangle_bottom.xml
Normal file
9
core/ui/src/main/res/drawable/ic_rectangle_bottom.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="12dp"
|
||||
android:height="8dp"
|
||||
android:viewportWidth="12"
|
||||
android:viewportHeight="8">
|
||||
<path
|
||||
android:pathData="M0.198,1.178C0.034,0.976 -0.028,0.695 0.012,0.433C0.058,0.135 0.373,0 0.675,0H6.183H11.325C11.627,0 11.943,0.135 11.988,0.433C12.028,0.695 11.966,0.976 11.802,1.178L6.477,7.756C6.214,8.081 5.786,8.081 5.523,7.756L0.198,1.178Z"
|
||||
android:fillColor="#EBEBEB"/>
|
||||
</vector>
|
||||
|
|
@ -69,15 +69,16 @@ fun CoroutineScope.launchOnCancellation(block: suspend () -> Unit) {
|
|||
}
|
||||
|
||||
@Suppress("LongParameterList", "MagicNumber")
|
||||
inline fun <T1, T2, T3, T4, T5, T6, R> combine6(
|
||||
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine7(
|
||||
flow1: Flow<T1>,
|
||||
flow2: Flow<T2>,
|
||||
flow3: Flow<T3>,
|
||||
flow4: Flow<T4>,
|
||||
flow5: Flow<T5>,
|
||||
flow6: Flow<T6>,
|
||||
crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R,
|
||||
): Flow<R> = combine(flow1, flow2, flow3, flow4, flow5, flow6) { arr ->
|
||||
flow7: Flow<T7>,
|
||||
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R,
|
||||
): Flow<R> = combine(flow1, flow2, flow3, flow4, flow5, flow6, flow7) { arr ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
transform(
|
||||
arr[0] as T1,
|
||||
|
|
@ -86,5 +87,6 @@ inline fun <T1, T2, T3, T4, T5, T6, R> combine6(
|
|||
arr[3] as T4,
|
||||
arr[4] as T5,
|
||||
arr[5] as T6,
|
||||
arr[6] as T7,
|
||||
)
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ internal class SdkTransactionHistoryItemConverter(
|
|||
SdkTransactionHistoryItem.TransactionStatus.Failed -> TxInfo.TransactionStatus.Failed
|
||||
SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
|
||||
},
|
||||
type = typeConverter.convert(value.type),
|
||||
type = typeConverter.convert(value.type to value.destinationType.toDomain()),
|
||||
amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" },
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
package com.tangem.data.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem.TransactionType
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyExitCallData
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyInitTokenCallData
|
||||
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyReactivateTokenCallData
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.walletmanager.model.SmartContractMethod
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class SdkTransactionTypeConverter(
|
||||
private val smartContractMethods: Map<String, SmartContractMethod>,
|
||||
) : Converter<TransactionType, TxInfo.TransactionType> {
|
||||
) : Converter<Pair<TransactionType, TxInfo.DestinationType>, TxInfo.TransactionType> {
|
||||
|
||||
override fun convert(value: TransactionType): TxInfo.TransactionType {
|
||||
return when (value) {
|
||||
override fun convert(value: Pair<TransactionType, TxInfo.DestinationType>): TxInfo.TransactionType {
|
||||
val (type, destination) = value
|
||||
|
||||
return when (type) {
|
||||
is TransactionType.ContractMethod -> {
|
||||
getTransactionType(methodName = smartContractMethods[value.id]?.name)
|
||||
getTransactionType(methodName = smartContractMethods[type.id]?.name, type.callData, destination)
|
||||
}
|
||||
is TransactionType.ContractMethodName -> {
|
||||
getTransactionType(methodName = value.name)
|
||||
getTransactionType(methodName = type.name, type.callData, destination)
|
||||
}
|
||||
is TransactionType.Transfer -> {
|
||||
TxInfo.TransactionType.Transfer
|
||||
|
|
@ -27,7 +33,7 @@ internal class SdkTransactionTypeConverter(
|
|||
TxInfo.TransactionType.Staking.Unstake
|
||||
}
|
||||
is TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
|
||||
TxInfo.TransactionType.Staking.Vote(value.validatorAddress)
|
||||
TxInfo.TransactionType.Staking.Vote(type.validatorAddress)
|
||||
}
|
||||
is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> {
|
||||
TxInfo.TransactionType.Staking.ClaimRewards
|
||||
|
|
@ -38,7 +44,12 @@ internal class SdkTransactionTypeConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getTransactionType(methodName: String?): TxInfo.TransactionType {
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun getTransactionType(
|
||||
methodName: String?,
|
||||
callData: String?,
|
||||
destination: TxInfo.DestinationType,
|
||||
): TxInfo.TransactionType {
|
||||
return when (methodName) {
|
||||
"transfer" -> TxInfo.TransactionType.Transfer
|
||||
"approve" -> TxInfo.TransactionType.Approve
|
||||
|
|
@ -59,11 +70,33 @@ internal class SdkTransactionTypeConverter(
|
|||
"withdrawRewardsPOL",
|
||||
-> TxInfo.TransactionType.Staking.ClaimRewards
|
||||
"redelegate" -> TxInfo.TransactionType.Staking.Restake
|
||||
"supplyEnter" -> TxInfo.TransactionType.YieldSupply.Enter
|
||||
"supplyExit" -> TxInfo.TransactionType.YieldSupply.Exit
|
||||
"yieldSend" -> TxInfo.TransactionType.YieldSupply.Send
|
||||
"enterProtocolByOwner" -> callData?.let { data ->
|
||||
TxInfo.TransactionType.YieldSupply.Enter(
|
||||
EthereumYieldSupplyEnterCallData.decode(data)?.tokenContractAddress.orEmpty(),
|
||||
)
|
||||
}
|
||||
"withdrawAndDeactivate" -> callData?.let { data ->
|
||||
TxInfo.TransactionType.YieldSupply.Exit(
|
||||
EthereumYieldSupplyExitCallData.decode(data)?.tokenContractAddress.orEmpty(),
|
||||
)
|
||||
}
|
||||
"deployYieldModule" -> TxInfo.TransactionType.YieldSupply.DeployContract(
|
||||
(destination as? TxInfo.DestinationType.Single)?.addressType?.address.orEmpty(),
|
||||
)
|
||||
"initYieldToken" -> callData?.let { data ->
|
||||
TxInfo.TransactionType.YieldSupply.InitializeToken(
|
||||
EthereumYieldSupplyInitTokenCallData.decode(data)?.tokenContractAddress.orEmpty(),
|
||||
)
|
||||
}
|
||||
"reactivateToken" -> callData?.let { data ->
|
||||
TxInfo.TransactionType.YieldSupply.ReactivateToken(
|
||||
EthereumYieldSupplyReactivateTokenCallData.decode(data)?.tokenContractAddress.orEmpty(),
|
||||
)
|
||||
}
|
||||
"supplyTopUp" -> TxInfo.TransactionType.YieldSupply.Topup
|
||||
null -> TxInfo.TransactionType.UnknownOperation
|
||||
else -> TxInfo.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() })
|
||||
}
|
||||
} ?: TxInfo.TransactionType.Operation(name = methodName?.replaceFirstChar { it.titlecase() }.orEmpty())
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ internal class TransactionDataToTxHistoryItemConverter(
|
|||
TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed
|
||||
TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
|
||||
},
|
||||
type = getTransactionType(value.extras),
|
||||
type = getTransactionType(value),
|
||||
amount = amount,
|
||||
)
|
||||
}
|
||||
|
|
@ -99,16 +99,25 @@ internal class TransactionDataToTxHistoryItemConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getTransactionType(extras: TransactionExtras?): TxInfo.TransactionType {
|
||||
return when (extras) {
|
||||
private fun getTransactionType(transactionData: TransactionData.Uncompiled?): TxInfo.TransactionType {
|
||||
return when (val extras = transactionData?.extras) {
|
||||
is EthereumTransactionExtras -> {
|
||||
when (extras.callData) {
|
||||
is EthereumYieldSupplyDeployCallData,
|
||||
is EthereumYieldSupplyReactivateTokenCallData,
|
||||
is EthereumYieldSupplyInitTokenCallData,
|
||||
is EthereumYieldSupplyEnterCallData,
|
||||
-> TxInfo.TransactionType.YieldSupply.Enter
|
||||
is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit
|
||||
when (val callData = extras.callData) {
|
||||
is EthereumYieldSupplyDeployCallData -> TxInfo.TransactionType.YieldSupply.DeployContract(
|
||||
transactionData.destinationAddress,
|
||||
)
|
||||
is EthereumYieldSupplyReactivateTokenCallData -> TxInfo.TransactionType.YieldSupply.ReactivateToken(
|
||||
callData.tokenContractAddress,
|
||||
)
|
||||
is EthereumYieldSupplyInitTokenCallData -> TxInfo.TransactionType.YieldSupply.InitializeToken(
|
||||
callData.tokenContractAddress,
|
||||
)
|
||||
is EthereumYieldSupplyEnterCallData -> TxInfo.TransactionType.YieldSupply.Enter(
|
||||
callData.tokenContractAddress,
|
||||
)
|
||||
is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit(
|
||||
callData.tokenContractAddress,
|
||||
)
|
||||
is ApprovalERC20TokenCallData -> TxInfo.TransactionType.Approve
|
||||
else -> TxInfo.TransactionType.Transfer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ dependencies {
|
|||
/** Tangem SDKs */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
// region AndroidX libraries
|
||||
implementation(deps.androidx.datastore)
|
||||
// endregion
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ import com.tangem.data.yield.supply.converters.YieldTokenChartConverter
|
|||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldSupplyChangeTokenStatusBody
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -35,6 +39,7 @@ internal class DefaultYieldSupplyRepository(
|
|||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : YieldSupplyRepository {
|
||||
|
||||
private val statusMap: MutableMap<String, YieldSupplyEnterStatus> = ConcurrentHashMap()
|
||||
|
|
@ -174,6 +179,14 @@ internal class DefaultYieldSupplyRepository(
|
|||
null
|
||||
}
|
||||
|
||||
override fun getShouldShowYieldPromoBanner(): Flow<Boolean> {
|
||||
return appPreferencesStore.get(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, true)
|
||||
}
|
||||
|
||||
override suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean) {
|
||||
appPreferencesStore.store(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, shouldShow)
|
||||
}
|
||||
|
||||
private fun Set<TxInfo>.hasYieldEnterTransactions(yieldAddress: String) = any {
|
||||
it.type == TxInfo.TransactionType.YieldSupply.Enter ||
|
||||
it.type == TxInfo.TransactionType.Approve &&
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.data.yield.supply.DefaultYieldSupplyRepository
|
|||
import com.tangem.data.yield.supply.DefaultYieldSupplyErrorResolver
|
||||
import com.tangem.data.yield.supply.DefaultYieldSupplyTransactionRepository
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
|
|
@ -41,6 +42,7 @@ internal object YieldSupplyDataModule {
|
|||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
): YieldSupplyRepository {
|
||||
return DefaultYieldSupplyRepository(
|
||||
yieldSupplyApi = yieldSupplyApi,
|
||||
|
|
@ -48,6 +50,7 @@ internal object YieldSupplyDataModule {
|
|||
dispatchers = dispatchers,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
analyticsExceptionHandler = analyticsExceptionHandler,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -205,32 +205,32 @@
|
|||
"0xcbeda14c": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supply"
|
||||
"name": "deployYieldModule"
|
||||
},
|
||||
"0x79be55f7": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supplyEnter"
|
||||
"name": "enterProtocolByOwner"
|
||||
},
|
||||
"0xc65e6dcf": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supplyExit"
|
||||
"name": "withdrawAndDeactivate"
|
||||
},
|
||||
"0xebd4b81c": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supply"
|
||||
"name": "initYieldToken"
|
||||
},
|
||||
"0xc478e956": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "supply"
|
||||
"name": "reactivateToken"
|
||||
},
|
||||
"0x0779afe6": {
|
||||
"info": "yieldModule",
|
||||
"source": "https://github.com/tangem/tangem-yield-module-contracts/",
|
||||
"name": "transfer"
|
||||
"name": "yieldSend"
|
||||
},
|
||||
"0xb9de6a93": {
|
||||
"info": "yieldModule",
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ fun CryptoCurrencyStatus.hasNotSuppliedAmount(): Boolean {
|
|||
return notSupplied > BigDecimal.ZERO
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount: BigDecimal): Boolean {
|
||||
fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount: BigDecimal): Boolean {
|
||||
val notSupplied = notSuppliedAmountOrNull() ?: return false
|
||||
return notSupplied >= minAmount
|
||||
return notSupplied >= dustAmount
|
||||
}
|
||||
|
||||
fun CryptoCurrencyStatus.notSuppliedAmountOrNull(): BigDecimal? {
|
||||
|
|
|
|||
|
|
@ -110,14 +110,32 @@ data class TxInfo(
|
|||
@Serializable
|
||||
sealed interface YieldSupply : TransactionType {
|
||||
|
||||
@Serializable
|
||||
data object Enter : YieldSupply
|
||||
val address: String?
|
||||
|
||||
@Serializable
|
||||
data object Exit : YieldSupply
|
||||
data class Enter(override val address: String) : YieldSupply
|
||||
|
||||
@Serializable
|
||||
data object Topup : YieldSupply
|
||||
data class Exit(override val address: String) : YieldSupply
|
||||
|
||||
@Serializable
|
||||
data object Topup : YieldSupply {
|
||||
override val address: String? = null
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object Send : YieldSupply {
|
||||
override val address: String? = null
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class DeployContract(override val address: String) : YieldSupply
|
||||
|
||||
@Serializable
|
||||
data class ReactivateToken(override val address: String) : YieldSupply
|
||||
|
||||
@Serializable
|
||||
data class InitializeToken(override val address: String) : YieldSupply
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.yield.supply.models
|
||||
|
||||
data class YieldSupplyRewardBalance(
|
||||
val fiatBalance: String?,
|
||||
val cryptoBalance: String?,
|
||||
) {
|
||||
companion object {
|
||||
fun empty() = YieldSupplyRewardBalance(null, null)
|
||||
}
|
||||
}
|
||||
|
|
@ -108,4 +108,8 @@ interface YieldSupplyRepository {
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): YieldSupplyEnterStatus?
|
||||
|
||||
fun getShouldShowYieldPromoBanner(): Flow<Boolean>
|
||||
|
||||
suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.domain.yield.supply.usecase
|
|||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Emits a map of APY values per token.
|
||||
|
|
@ -15,11 +16,11 @@ class YieldSupplyApyFlowUseCase(
|
|||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(): Flow<Map<String, String>> {
|
||||
operator fun invoke(): Flow<Map<String, BigDecimal>> {
|
||||
return yieldSupplyRepository.getMarketsFlow()
|
||||
.map { yieldMarketTokenList ->
|
||||
yieldMarketTokenList.filter { it.isActive }.associate { token ->
|
||||
token.yieldSupplyKey to token.apy.toString()
|
||||
token.yieldSupplyKey to token.apy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import java.math.BigDecimal
|
||||
|
||||
class YieldSupplyGetDustMinAmountUseCase {
|
||||
|
||||
operator fun invoke(minAmount: BigDecimal, appCurrency: AppCurrency): BigDecimal {
|
||||
return if (appCurrency.code in SUPPORTED_DUST_CURRENCIES) {
|
||||
DUST_MIN_AMOUNT
|
||||
} else {
|
||||
minAmount.stripTrailingZeros()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DUST_MIN_AMOUNT = BigDecimal("0.1")
|
||||
private val SUPPORTED_DUST_CURRENCIES = setOf("EUR", "USD", "AUD", "CAD", "GBP")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -22,7 +24,7 @@ class YieldSupplyGetRewardsBalanceUseCase(
|
|||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow<String> = flow {
|
||||
operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow<YieldSupplyRewardBalance> = flow {
|
||||
val cryptoAmount = status.value.amount
|
||||
val fiatRate = status.value.fiatRate
|
||||
|
||||
|
|
@ -30,11 +32,7 @@ class YieldSupplyGetRewardsBalanceUseCase(
|
|||
return@flow
|
||||
}
|
||||
|
||||
val amount = if (cryptoAmount != null && fiatRate != null) {
|
||||
cryptoAmount.multiply(fiatRate)
|
||||
} else {
|
||||
return@flow
|
||||
}
|
||||
if (cryptoAmount == null) return@flow
|
||||
|
||||
val tokenAddress = (status.currency as? CryptoCurrency.Token)?.contractAddress ?: return@flow
|
||||
val apy = try {
|
||||
|
|
@ -50,37 +48,57 @@ class YieldSupplyGetRewardsBalanceUseCase(
|
|||
return@flow
|
||||
}
|
||||
|
||||
val initialPerTickDelta = amount
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
.abs()
|
||||
val initialPerTickDeltaCrypto = perTickDelta(cryptoAmount, apyFraction).abs()
|
||||
|
||||
val minVisibleDecimals = calculateMinVisibleDecimals(initialPerTickDelta)
|
||||
val minVisibleDecimalsCrypto = calculateMinVisibleDecimals(
|
||||
perTickDeltaAbs = initialPerTickDeltaCrypto,
|
||||
maxDecimals = status.currency.decimals,
|
||||
)
|
||||
|
||||
var currentBalance: BigDecimal = amount
|
||||
val fiatAmountStart = fiatRate?.let { cryptoAmount.multiply(it) }
|
||||
val minVisibleDecimalsFiat = fiatAmountStart?.let { amount ->
|
||||
val initialPerTickDeltaFiat = perTickDelta(amount, apyFraction).abs()
|
||||
calculateMinVisibleDecimals(
|
||||
perTickDeltaAbs = initialPerTickDeltaFiat,
|
||||
maxDecimals = FIAT_MAX_DECIMALS,
|
||||
)
|
||||
}
|
||||
|
||||
var currentCryptoBalance: BigDecimal = cryptoAmount
|
||||
var currentFiatBalance: BigDecimal? = fiatAmountStart
|
||||
while (true) {
|
||||
val fiatBalanceFormatted: String? = currentFiatBalance?.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
).anyDecimals(decimals = minVisibleDecimalsFiat ?: FIAT_MIN_DECIMALS)
|
||||
}
|
||||
|
||||
val cryptoBalanceFormatted: String = currentCryptoBalance.format {
|
||||
crypto(status.currency).anyDecimals(
|
||||
maxDecimals = minVisibleDecimalsCrypto,
|
||||
minDecimals = minVisibleDecimalsCrypto,
|
||||
)
|
||||
}
|
||||
|
||||
emit(
|
||||
currentBalance.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
).anyDecimals(decimals = minVisibleDecimals)
|
||||
},
|
||||
YieldSupplyRewardBalance(fiatBalance = fiatBalanceFormatted, cryptoBalance = cryptoBalanceFormatted),
|
||||
)
|
||||
|
||||
val perTickDelta = currentBalance
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
val perTickDeltaCrypto = perTickDelta(currentCryptoBalance, apyFraction)
|
||||
|
||||
currentBalance = currentBalance.add(perTickDelta)
|
||||
currentCryptoBalance = currentCryptoBalance.add(perTickDeltaCrypto)
|
||||
|
||||
currentFiatBalance = currentFiatBalance?.let { current ->
|
||||
val perTickDeltaFiat = perTickDelta(current, apyFraction)
|
||||
current.add(perTickDeltaFiat)
|
||||
}
|
||||
|
||||
delay(TICK_MILLIS)
|
||||
}
|
||||
}.flowOn(dispatcherProvider.default)
|
||||
|
||||
private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal): Int {
|
||||
private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal, maxDecimals: Int): Int {
|
||||
if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS
|
||||
|
||||
val perTickAsDouble = perTickDeltaAbs.toDouble()
|
||||
|
|
@ -88,20 +106,28 @@ class YieldSupplyGetRewardsBalanceUseCase(
|
|||
|
||||
val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble
|
||||
val raw = ceil(-ln(safe) / LN_10)
|
||||
return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS)
|
||||
return raw.toInt().coerceIn(MIN_DECIMALS, maxDecimals)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TICK_MILLIS: Long = 300
|
||||
private val TICK_SECONDS_BD = BigDecimal("0.3")
|
||||
private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") // 365 * 24 * 60 * 60
|
||||
private val HUNDRED_BD = BigDecimal("100")
|
||||
private const val SCALE = 18
|
||||
private fun perTickDelta(amount: BigDecimal, apyFraction: BigDecimal): BigDecimal {
|
||||
return amount
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
}
|
||||
|
||||
private const val MIN_DECIMALS = 3
|
||||
private const val MAX_DECIMALS = 12
|
||||
companion object {
|
||||
internal const val TICK_MILLIS: Long = 800
|
||||
internal val TICK_SECONDS_BD: BigDecimal = BigDecimal("0.8")
|
||||
internal val SECONDS_PER_YEAR_BD: BigDecimal = BigDecimal("31536000") // 365 * 24 * 60 * 60
|
||||
internal val HUNDRED_BD: BigDecimal = BigDecimal("100")
|
||||
internal const val SCALE: Int = 18
|
||||
|
||||
private val LN_10 = ln(10.0)
|
||||
private const val EPSILON = 1e-18
|
||||
internal const val MIN_DECIMALS: Int = 3
|
||||
internal const val FIAT_MIN_DECIMALS: Int = 2
|
||||
internal const val FIAT_MAX_DECIMALS: Int = 12
|
||||
|
||||
internal val LN_10: Double = ln(10.0)
|
||||
internal const val EPSILON: Double = 1e-18
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class YieldSupplyGetShouldShowMainPromoUseCase(
|
||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(): Flow<Boolean> {
|
||||
return yieldSupplyRepository.getShouldShowYieldPromoBanner()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
|
||||
class YieldSupplySetShouldShowMainPromoUseCase(
|
||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(shouldShow: Boolean) {
|
||||
yieldSupplyRepository.setShouldShowYieldPromoBanner(shouldShow)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.domain.yield.supply.usecase
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class YieldSupplyGetDustMinAmountUseCaseTest {
|
||||
|
||||
private val useCase = YieldSupplyGetDustMinAmountUseCase()
|
||||
|
||||
@Test
|
||||
fun `GIVEN supported currency WHEN invoke THEN return dust min amount`() {
|
||||
val minAmount = BigDecimal("123.456")
|
||||
val appCurrency = AppCurrency(code = "EUR", name = "Euro", symbol = "€")
|
||||
|
||||
val result = useCase(minAmount, appCurrency)
|
||||
|
||||
assertThat(result).isEqualTo(BigDecimal("0.1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN unsupported currency WHEN invoke THEN return min amount stripped`() {
|
||||
val minAmount = BigDecimal("1.2300")
|
||||
val appCurrency = AppCurrency(code = "JPY", name = "Japanese Yen", symbol = "¥")
|
||||
|
||||
val result = useCase(minAmount, appCurrency)
|
||||
|
||||
assertThat(result).isEqualTo(BigDecimal("1.23"))
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.google.common.truth.Truth.assertThat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -11,6 +12,7 @@ import com.tangem.domain.models.network.Network
|
|||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase.Companion.TICK_MILLIS
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
|
|
@ -26,7 +28,6 @@ import org.junit.jupiter.api.Test
|
|||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.ln
|
||||
|
||||
class YieldSupplyGetRewardsBalanceUseCaseTest {
|
||||
|
||||
|
|
@ -165,9 +166,9 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
val deferred = async { useCase(status, appCurrency).take(3).toList() }
|
||||
|
||||
testScheduler.advanceUntilIdle()
|
||||
advanceTimeBy(300)
|
||||
advanceTimeBy(TICK_MILLIS)
|
||||
testScheduler.advanceUntilIdle()
|
||||
advanceTimeBy(300)
|
||||
advanceTimeBy(TICK_MILLIS)
|
||||
testScheduler.advanceUntilIdle()
|
||||
|
||||
val collected = deferred.await()
|
||||
|
|
@ -175,25 +176,147 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
|
||||
val expectedDecimals = calculateMinVisibleDecimalsForTest(initialPerTickDelta(amount, apy))
|
||||
|
||||
val firstExpected = amount.format { fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals) }
|
||||
assertThat(collected[0]).isEqualTo(firstExpected)
|
||||
val firstExpected = amount.format {
|
||||
fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals)
|
||||
}
|
||||
assertThat(collected[0].fiatBalance).isEqualTo(firstExpected)
|
||||
|
||||
val firstNext = nextBalance(amount, apy)
|
||||
val secondExpected = firstNext.format { fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals) }
|
||||
assertThat(collected[1]).isEqualTo(secondExpected)
|
||||
val secondExpected = firstNext.format {
|
||||
fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals)
|
||||
}
|
||||
assertThat(collected[1].fiatBalance).isEqualTo(secondExpected)
|
||||
|
||||
val secondNext = nextBalance(firstNext, apy)
|
||||
val thirdExpected = secondNext.format { fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals) }
|
||||
assertThat(collected[2]).isEqualTo(thirdExpected)
|
||||
val thirdExpected = secondNext.format {
|
||||
fiat(
|
||||
appCurrency.code,
|
||||
appCurrency.symbol,
|
||||
).anyDecimals(decimals = expectedDecimals)
|
||||
}
|
||||
assertThat(collected[2].fiatBalance).isEqualTo(thirdExpected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN polygon USDT0 Loaded status WHEN invoke THEN emit formatted balances`() = runTest {
|
||||
val network = Network(
|
||||
id = Network.ID(Network.RawID("POLYGON"), Network.DerivationPath.Card("m/44'/60'/0'/0/0")),
|
||||
backendId = "polygon-pos",
|
||||
name = "Polygon",
|
||||
currencySymbol = "POL",
|
||||
derivationPath = Network.DerivationPath.Card("m/44'/60'/0'/0/0"),
|
||||
isTestnet = false,
|
||||
standardType = Network.StandardType.Unspecified("Polygon"),
|
||||
hasFiatFeeRate = true,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
val tokenId = CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkId(network.rawId),
|
||||
suffix = CryptoCurrency.ID.Suffix.RawID("usdt0", "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"),
|
||||
)
|
||||
val currency = CryptoCurrency.Token(
|
||||
id = tokenId,
|
||||
network = network,
|
||||
name = "USDT0",
|
||||
symbol = "USDT0",
|
||||
decimals = 6,
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/usdt0.png",
|
||||
isCustom = false,
|
||||
contractAddress = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
|
||||
)
|
||||
|
||||
val amount = BigDecimal("9.241136")
|
||||
val fiatRate = BigDecimal("0.9999761277273864")
|
||||
val status = CryptoCurrencyStatus(
|
||||
currency = currency,
|
||||
value = CryptoCurrencyStatus.Loaded(
|
||||
amount = amount,
|
||||
fiatAmount = amount.multiply(fiatRate),
|
||||
fiatRate = fiatRate,
|
||||
priceChange = BigDecimal("-0.000058200000000008245"),
|
||||
yieldBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = NetworkAddress.Single(
|
||||
NetworkAddress.Address(
|
||||
value = "0xb71fa0E20ba8579B3ec51cC79aaa84Bf5982BB49",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
),
|
||||
)
|
||||
|
||||
val apy = BigDecimal("5.0")
|
||||
coEvery { repository.getCachedMarkets() } returns listOf(
|
||||
YieldMarketToken(
|
||||
tokenAddress = currency.contractAddress,
|
||||
chainId = 137,
|
||||
apy = apy,
|
||||
isActive = true,
|
||||
maxFeeNative = BigDecimal.ZERO,
|
||||
maxFeeUSD = BigDecimal.ZERO,
|
||||
backendId = "polygon-pos",
|
||||
),
|
||||
)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(this)
|
||||
val useCase = YieldSupplyGetRewardsBalanceUseCase(repository, dispatcherProvider)
|
||||
val appCurrency = AppCurrency.Default
|
||||
|
||||
val deferred = async { useCase(status, appCurrency).take(2).toList() }
|
||||
|
||||
testScheduler.advanceUntilIdle()
|
||||
advanceTimeBy(TICK_MILLIS)
|
||||
testScheduler.advanceUntilIdle()
|
||||
|
||||
val emissions = deferred.await()
|
||||
assertThat(emissions).hasSize(2)
|
||||
|
||||
val apyFraction = apy.divide(BigDecimal("100"), 18, RoundingMode.HALF_UP)
|
||||
val perTickCrypto = amount.multiply(apyFraction)
|
||||
.multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD)
|
||||
.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
.abs()
|
||||
val minCryptoDecimals = calculateMinVisibleDecimalsForTest(perTickCrypto).coerceAtMost(currency.decimals)
|
||||
|
||||
val fiatAmountStart = amount.multiply(fiatRate)
|
||||
val perTickFiat = fiatAmountStart.multiply(apyFraction)
|
||||
.multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD)
|
||||
.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
.abs()
|
||||
val minFiatDecimals = calculateMinVisibleDecimalsForTest(perTickFiat)
|
||||
|
||||
val expectedCrypto0 = amount.format {
|
||||
crypto(currency).anyDecimals(
|
||||
maxDecimals = minCryptoDecimals,
|
||||
minDecimals = minCryptoDecimals,
|
||||
)
|
||||
}
|
||||
val expectedFiat0 = fiatAmountStart.format {
|
||||
fiat(appCurrency.code, appCurrency.symbol).anyDecimals(decimals = minFiatDecimals)
|
||||
}
|
||||
|
||||
assertThat(emissions[0].cryptoBalance).isEqualTo(expectedCrypto0)
|
||||
assertThat(emissions[0].fiatBalance).isEqualTo(expectedFiat0)
|
||||
}
|
||||
|
||||
private fun testDispatcherProvider(scope: TestScope): CoroutineDispatcherProvider {
|
||||
|
|
@ -260,42 +383,48 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
}
|
||||
|
||||
private fun initialPerTickDelta(amount: BigDecimal, apy: BigDecimal): BigDecimal {
|
||||
val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP)
|
||||
val apyFraction = apy.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
return amount
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
.multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD)
|
||||
.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
.abs()
|
||||
}
|
||||
|
||||
private fun nextBalance(current: BigDecimal, apy: BigDecimal): BigDecimal {
|
||||
val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP)
|
||||
val apyFraction = apy.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
val perTickDelta = current
|
||||
.multiply(apyFraction)
|
||||
.multiply(TICK_SECONDS_BD)
|
||||
.divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP)
|
||||
.multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD)
|
||||
.divide(
|
||||
YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD,
|
||||
YieldSupplyGetRewardsBalanceUseCase.SCALE,
|
||||
RoundingMode.HALF_UP,
|
||||
)
|
||||
return current.add(perTickDelta)
|
||||
}
|
||||
|
||||
private fun calculateMinVisibleDecimalsForTest(perTickDeltaAbs: BigDecimal): Int {
|
||||
if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS
|
||||
if (perTickDeltaAbs <= BigDecimal.ZERO) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS
|
||||
val perTickAsDouble = perTickDeltaAbs.toDouble()
|
||||
if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return MIN_DECIMALS
|
||||
val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble
|
||||
val raw = ceil(-ln(safe) / LN_10)
|
||||
return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val SCALE = 18
|
||||
private val TICK_SECONDS_BD = BigDecimal("0.3")
|
||||
private val SECONDS_PER_YEAR_BD = BigDecimal("31536000")
|
||||
private val HUNDRED_BD = BigDecimal("100")
|
||||
|
||||
private const val MIN_DECIMALS = 3
|
||||
private const val MAX_DECIMALS = 8
|
||||
|
||||
private val LN_10 = ln(10.0)
|
||||
private const val EPSILON = 1e-18
|
||||
if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS
|
||||
val safe = if (perTickAsDouble <= 0.0) YieldSupplyGetRewardsBalanceUseCase.EPSILON else perTickAsDouble
|
||||
val raw = ceil(-kotlin.math.ln(safe) / YieldSupplyGetRewardsBalanceUseCase.LN_10)
|
||||
return raw.toInt().coerceIn(
|
||||
YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS,
|
||||
YieldSupplyGetRewardsBalanceUseCase.FIAT_MAX_DECIMALS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -88,6 +88,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.yieldSupply)
|
||||
implementation(projects.domain.yieldSupply.models)
|
||||
|
||||
/** Temp dependency to swap domain */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ internal object TokenDetailsPreviewData {
|
|||
selectedBalanceType = BalanceType.ALL,
|
||||
onBalanceSelect = {},
|
||||
displayCryptoBalance = "966,96 XLM",
|
||||
displayYeildSupplyCryptoBalance = null,
|
||||
displayYieldSupplyFiatBalance = null,
|
||||
displayFiatBalance = "91,50$",
|
||||
isBalanceSelectorEnabled = true,
|
||||
isBalanceFlickering = false,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
|||
import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase
|
||||
import com.tangem.feature.tokendetails.deeplink.TokenDetailsDeepLinkActionListener
|
||||
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
|
||||
|
|
@ -416,7 +417,9 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
.saveIn(yieldSupplyBalanceJobHolder)
|
||||
} else {
|
||||
yieldSupplyBalanceJobHolder.cancel()
|
||||
internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance(null)
|
||||
internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance(
|
||||
YieldSupplyRewardBalance.empty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1251,14 +1254,8 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleNavigationParam() {
|
||||
when (val action = params.navigationAction) {
|
||||
is NavigationAction.Staking -> openStaking()
|
||||
is NavigationAction.YieldSupply -> if (action.isActive) {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
fetchCurrencyStatusUseCase(userWalletId = userWalletId, id = cryptoCurrency.id)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
if (params.navigationAction is NavigationAction.Staking) {
|
||||
openStaking()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ internal sealed class TokenDetailsBalanceBlockState {
|
|||
val isBalanceSelectorEnabled: Boolean,
|
||||
val isBalanceFlickering: Boolean,
|
||||
val yieldSupplyState: TokenDetailsYieldSupplyState = TokenDetailsYieldSupplyState.Empty,
|
||||
val displayYeildSupplyCryptoBalance: String? = null,
|
||||
val displayYieldSupplyFiatBalance: String? = null,
|
||||
val displayYieldSupplyCryptoBalance: String? = null,
|
||||
) : TokenDetailsBalanceBlockState()
|
||||
|
||||
data class Error(
|
||||
|
|
|
|||
|
|
@ -98,8 +98,8 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
stakingCryptoAmount,
|
||||
currentState.selectedBalanceType,
|
||||
),
|
||||
displayYeildSupplyCryptoBalance = (currentState as? TokenDetailsBalanceBlockState.Content)
|
||||
?.displayYeildSupplyCryptoBalance,
|
||||
displayYieldSupplyFiatBalance = (currentState as? TokenDetailsBalanceBlockState.Content)
|
||||
?.displayYieldSupplyFiatBalance,
|
||||
balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig,
|
||||
onBalanceSelect = clickIntents::onBalanceSelect,
|
||||
selectedBalanceType = currentState.selectedBalanceType,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.tangem.domain.tokens.model.TokenActionsState
|
|||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsAppBarMenuConfig
|
||||
|
|
@ -315,13 +316,18 @@ internal class TokenDetailsStateFactory(
|
|||
return balanceSelectStateConverter.convert(buttonConfig)
|
||||
}
|
||||
|
||||
fun getStateWithUpdatedYieldSupplyDisplayBalance(displayBalance: String?): TokenDetailsState {
|
||||
fun getStateWithUpdatedYieldSupplyDisplayBalance(
|
||||
yieldSupplyRewardBalance: YieldSupplyRewardBalance,
|
||||
): TokenDetailsState {
|
||||
val state = currentStateProvider()
|
||||
val balanceState = state.tokenBalanceBlockState
|
||||
return state.copy(
|
||||
tokenBalanceBlockState = when (balanceState) {
|
||||
is TokenDetailsBalanceBlockState.Content ->
|
||||
balanceState.copy(displayYeildSupplyCryptoBalance = displayBalance)
|
||||
balanceState.copy(
|
||||
displayYieldSupplyFiatBalance = yieldSupplyRewardBalance.fiatBalance,
|
||||
displayYieldSupplyCryptoBalance = yieldSupplyRewardBalance.cryptoBalance,
|
||||
)
|
||||
is TokenDetailsBalanceBlockState.Error -> balanceState
|
||||
is TokenDetailsBalanceBlockState.Loading -> balanceState
|
||||
},
|
||||
|
|
|
|||
|
|
@ -122,12 +122,12 @@ private fun FiatBalance(
|
|||
height = TangemTheme.dimens.size32,
|
||||
),
|
||||
)
|
||||
is TokenDetailsBalanceBlockState.Content -> if (state.displayYeildSupplyCryptoBalance != null &&
|
||||
is TokenDetailsBalanceBlockState.Content -> if (state.displayYieldSupplyFiatBalance != null &&
|
||||
!isBalanceHidden
|
||||
) {
|
||||
TextAnimatedCounter(
|
||||
modifier = modifier,
|
||||
text = state.displayYeildSupplyCryptoBalance,
|
||||
text = state.displayYieldSupplyFiatBalance,
|
||||
style = TangemTheme.typography.h2.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
|
|
@ -136,7 +136,7 @@ private fun FiatBalance(
|
|||
} else {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = (state.displayYeildSupplyCryptoBalance ?: state.displayFiatBalance).orMaskWithStars(
|
||||
text = (state.displayYieldSupplyFiatBalance ?: state.displayFiatBalance).orMaskWithStars(
|
||||
isBalanceHidden,
|
||||
),
|
||||
style = TangemTheme.typography.h2.applyBladeBrush(
|
||||
|
|
@ -184,8 +184,9 @@ private fun CryptoBalance(
|
|||
tint = TangemTheme.colors.icon.inactive,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = state.displayCryptoBalance.orMaskWithStars(isBalanceHidden),
|
||||
TextAnimatedCounter(
|
||||
text = (state.displayYieldSupplyCryptoBalance ?: state.displayCryptoBalance)
|
||||
.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.caption2.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors.text.tertiary,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ internal class TxHistoryItemToTransactionStateConverter(
|
|||
R.drawable.ic_close_24
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.Approve -> R.drawable.ic_doc_24
|
||||
is TransactionType.YieldSupply.DeployContract,
|
||||
is TransactionType.Approve,
|
||||
-> R.drawable.ic_doc_24
|
||||
is TransactionType.Staking.Stake,
|
||||
is TransactionType.Staking.Vote,
|
||||
is TransactionType.Staking.Restake,
|
||||
|
|
@ -51,11 +53,16 @@ internal class TxHistoryItemToTransactionStateConverter(
|
|||
is TransactionType.Staking.Unstake,
|
||||
is TransactionType.Staking.Withdraw,
|
||||
-> R.drawable.ic_transaction_history_unstaking_24
|
||||
is TransactionType.YieldSupply.Enter -> R.drawable.ic_connect_24
|
||||
is TransactionType.YieldSupply.InitializeToken -> R.drawable.ic_gear_24
|
||||
is TransactionType.YieldSupply.ReactivateToken -> R.drawable.ic_refresh_24
|
||||
is TransactionType.YieldSupply.Exit -> R.drawable.ic_disconnect_24
|
||||
is TransactionType.Operation,
|
||||
is TransactionType.Swap,
|
||||
is TransactionType.Transfer,
|
||||
is TransactionType.YieldSupply,
|
||||
is TransactionType.UnknownOperation,
|
||||
TransactionType.YieldSupply.Send,
|
||||
TransactionType.YieldSupply.Topup,
|
||||
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
|
||||
}
|
||||
}
|
||||
|
|
@ -66,32 +73,76 @@ internal class TxHistoryItemToTransactionStateConverter(
|
|||
is TransactionType.Operation -> stringReference(type.name)
|
||||
is TransactionType.Swap -> resourceReference(R.string.common_swap)
|
||||
is TransactionType.Transfer -> resourceReference(R.string.common_transfer)
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit)
|
||||
is TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup)
|
||||
is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
|
||||
is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
|
||||
is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)
|
||||
is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
|
||||
is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw)
|
||||
is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake)
|
||||
is TransactionType.YieldSupply -> when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit)
|
||||
TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup)
|
||||
TransactionType.YieldSupply.Send -> if (isOutgoing) {
|
||||
resourceReference(R.string.common_transfer)
|
||||
} else {
|
||||
resourceReference(R.string.yield_module_transaction_withdraw)
|
||||
}
|
||||
is TransactionType.YieldSupply.DeployContract -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_deploy_contract,
|
||||
)
|
||||
is TransactionType.YieldSupply.InitializeToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_initialize,
|
||||
)
|
||||
is TransactionType.YieldSupply.ReactivateToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_reactivate,
|
||||
)
|
||||
}
|
||||
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
|
||||
}
|
||||
|
||||
private fun TxInfo.extractSubtitle(): TextReference {
|
||||
return when (this.type) {
|
||||
is TransactionType.YieldSupply.Enter -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Topup,
|
||||
-> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Exit -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount))
|
||||
return when (val type = this.type) {
|
||||
is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Coin) {
|
||||
if (type == TransactionType.YieldSupply.Send) {
|
||||
extractSubtitleByAddressType()
|
||||
} else {
|
||||
resourceReference(
|
||||
R.string.transaction_history_transaction_for_address,
|
||||
wrappedList(type.address?.toBriefAddressFormat().orEmpty()),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount))
|
||||
}
|
||||
TransactionType.YieldSupply.Topup -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Exit -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount))
|
||||
}
|
||||
TransactionType.YieldSupply.Send -> {
|
||||
if (isOutgoing) {
|
||||
extractSubtitleByAddressType()
|
||||
} else {
|
||||
val amount =
|
||||
amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_exit_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
|
|
@ -133,14 +184,21 @@ internal class TxHistoryItemToTransactionStateConverter(
|
|||
|
||||
@Suppress("ComplexCondition")
|
||||
private fun TxInfo.getAmount(): String {
|
||||
if (type is TransactionType.Staking.Vote ||
|
||||
type == TransactionType.Staking.ClaimRewards ||
|
||||
type == TransactionType.Staking.Withdraw ||
|
||||
type == TransactionType.YieldSupply.Enter ||
|
||||
type == TransactionType.YieldSupply.Exit ||
|
||||
type == TransactionType.YieldSupply.Topup
|
||||
) {
|
||||
return ""
|
||||
when (type) {
|
||||
is TransactionType.Staking.Vote,
|
||||
TransactionType.Staking.ClaimRewards,
|
||||
TransactionType.Staking.Withdraw,
|
||||
-> return ""
|
||||
|
||||
is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Token) {
|
||||
when (type) {
|
||||
TransactionType.YieldSupply.Send -> if (!isOutgoing) {
|
||||
return ""
|
||||
}
|
||||
else -> return ""
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
val prefix = when {
|
||||
status == TxInfo.TransactionStatus.Failed -> ""
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.domain.tokens.model.details.NavigationAction
|
|||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
|
|
@ -57,6 +58,8 @@ internal interface WalletContentClickIntents {
|
|||
apy: String,
|
||||
)
|
||||
|
||||
fun onYieldPromoCloseClick()
|
||||
|
||||
fun onAccountExpandClick(account: Account)
|
||||
|
||||
fun onAccountCollapseClick(account: Account)
|
||||
|
|
@ -94,6 +97,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
||||
private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase,
|
||||
) : BaseWalletClickIntents(), WalletContentClickIntents {
|
||||
|
||||
override fun onDetailsClick() {
|
||||
|
|
@ -193,6 +197,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onYieldPromoCloseClick() {
|
||||
modelScope.launch {
|
||||
yieldSupplySetShouldShowMainPromoUseCase(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccountExpandClick(account: Account) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
accountDependencies.expandedAccountsHolder.expandAccount(userWalletId, account.accountId)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -45,6 +46,7 @@ internal class MultiWalletContentLoader(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
|
||||
|
|
@ -63,6 +65,7 @@ internal class MultiWalletContentLoader(
|
|||
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
).let(::add)
|
||||
|
||||
WalletNFTListSubscriber(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -44,6 +45,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
|
||||
|
|
@ -72,6 +74,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
tangemPayFeatureToggles = tangemPayFeatureToggles,
|
||||
tangemPayMainSubscriberFactory = tangemPayMainSubscriberFactory,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
|
|||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -35,6 +36,7 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -49,6 +51,7 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
).let(::add)
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
|
|||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -36,6 +37,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
|
||||
|
|
@ -55,6 +57,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,14 +11,16 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.converte
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SetTokenListTransformer(
|
||||
private val params: TokenConverterParams,
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
private val yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
private val stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
private val shouldShowMainPromo: Boolean,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
|
|
@ -63,6 +65,7 @@ internal class SetTokenListTransformer(
|
|||
clickIntents = clickIntents,
|
||||
yieldModuleApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
).convert(value = this)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -17,6 +18,7 @@ internal class SetTxHistoryCountErrorTransformer(
|
|||
private val error: TxHistoryStateError,
|
||||
private val pendingTransactions: Set<TxInfo>,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val currency: CryptoCurrency,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
private val txHistoryItemConverter by lazy {
|
||||
|
|
@ -26,6 +28,7 @@ internal class SetTxHistoryCountErrorTransformer(
|
|||
}
|
||||
|
||||
TxHistoryItemStateConverter(
|
||||
currency = currency,
|
||||
symbol = blockchain.currency,
|
||||
decimals = blockchain.decimals(),
|
||||
clickIntents = clickIntents,
|
||||
|
|
|
|||
|
|
@ -28,17 +28,25 @@ import kotlinx.collections.immutable.PersistentList
|
|||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TokenListStateConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val params: TokenConverterParams,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val yieldModuleApyMap: Map<String, String>,
|
||||
private val yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
private val stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
private val shouldShowMainPromo: Boolean,
|
||||
) : Converter<WalletTokensListState, WalletTokensListState> {
|
||||
|
||||
private val yieldSupplyPromoBannerKeyConverter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap,
|
||||
shouldShowMainPromo,
|
||||
)
|
||||
|
||||
private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
|
||||
{ accountId, currencyStatus ->
|
||||
clickIntents.onTokenItemClick(selectedWallet.walletId, currencyStatus)
|
||||
|
|
@ -63,10 +71,12 @@ internal class TokenListStateConverter(
|
|||
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKeyConverter.convert(params),
|
||||
stakingApyMap = stakingApyMap,
|
||||
onItemClick = { _, status -> onTokenClick(accountId, status) },
|
||||
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
|
||||
onApyLabelClick = { status, apySource, apy -> onApyLabelClick(status, apySource, apy) },
|
||||
onYieldPromoCloseClick = clickIntents::onYieldPromoCloseClick,
|
||||
)
|
||||
|
||||
override fun convert(value: WalletTokensListState): WalletTokensListState {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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.utils.toTimeFormat
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.models.network.TxInfo.TransactionStatus
|
||||
import com.tangem.domain.models.network.TxInfo.TransactionType
|
||||
|
|
@ -20,6 +21,7 @@ import com.tangem.utils.converter.Converter
|
|||
import com.tangem.utils.toBriefAddressFormat
|
||||
|
||||
internal class TxHistoryItemStateConverter(
|
||||
private val currency: CryptoCurrency,
|
||||
private val symbol: String,
|
||||
private val decimals: Int,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
|
|
@ -49,7 +51,9 @@ internal class TxHistoryItemStateConverter(
|
|||
R.drawable.ic_close_24
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.Approve -> R.drawable.ic_doc_24
|
||||
is TransactionType.YieldSupply.DeployContract,
|
||||
is TransactionType.Approve,
|
||||
-> R.drawable.ic_doc_24
|
||||
is TransactionType.Staking.Stake,
|
||||
is TransactionType.Staking.Vote,
|
||||
is TransactionType.Staking.Restake,
|
||||
|
|
@ -59,11 +63,16 @@ internal class TxHistoryItemStateConverter(
|
|||
is TransactionType.Staking.Unstake,
|
||||
is TransactionType.Staking.Withdraw,
|
||||
-> R.drawable.ic_transaction_history_unstaking_24
|
||||
is TransactionType.YieldSupply.Enter -> R.drawable.ic_connect_24
|
||||
is TransactionType.YieldSupply.InitializeToken -> R.drawable.ic_gear_24
|
||||
is TransactionType.YieldSupply.ReactivateToken -> R.drawable.ic_refresh_24
|
||||
is TransactionType.YieldSupply.Exit -> R.drawable.ic_disconnect_24
|
||||
is TransactionType.Operation,
|
||||
is TransactionType.Swap,
|
||||
is TransactionType.Transfer,
|
||||
is TransactionType.YieldSupply,
|
||||
is TransactionType.UnknownOperation,
|
||||
TransactionType.YieldSupply.Send,
|
||||
TransactionType.YieldSupply.Topup,
|
||||
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
|
||||
}
|
||||
}
|
||||
|
|
@ -74,32 +83,81 @@ internal class TxHistoryItemStateConverter(
|
|||
is TransactionType.Operation -> stringReference(type.name)
|
||||
is TransactionType.Swap -> resourceReference(R.string.common_swap)
|
||||
is TransactionType.Transfer -> resourceReference(R.string.common_transfer)
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit)
|
||||
is TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup)
|
||||
is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
|
||||
is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
|
||||
is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)
|
||||
is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
|
||||
is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw)
|
||||
is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake)
|
||||
is TransactionType.YieldSupply -> when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit)
|
||||
TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup)
|
||||
TransactionType.YieldSupply.Send -> if (isOutgoing) {
|
||||
resourceReference(R.string.common_transfer)
|
||||
} else {
|
||||
resourceReference(R.string.yield_module_transaction_withdraw)
|
||||
}
|
||||
is TransactionType.YieldSupply.DeployContract -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_deploy_contract,
|
||||
)
|
||||
is TransactionType.YieldSupply.InitializeToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_initialize,
|
||||
)
|
||||
is TransactionType.YieldSupply.ReactivateToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_reactivate,
|
||||
)
|
||||
}
|
||||
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
|
||||
}
|
||||
|
||||
private fun TxInfo.extractSubtitle(): TextReference {
|
||||
return when (this.type) {
|
||||
is TransactionType.YieldSupply.Enter -> {
|
||||
val amount = amount.format { crypto(symbol = symbol, decimals = decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Topup,
|
||||
-> {
|
||||
val amount = amount.format { crypto(symbol = symbol, decimals = decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Exit -> {
|
||||
val amount = amount.format { crypto(symbol = symbol, decimals = decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount))
|
||||
return when (val type = this.type) {
|
||||
is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Coin) {
|
||||
resourceReference(
|
||||
R.string.transaction_history_transaction_for_address,
|
||||
wrappedList(type.address?.toBriefAddressFormat().orEmpty()),
|
||||
)
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_enter_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
TransactionType.YieldSupply.Topup -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_topup_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
TransactionType.YieldSupply.Send -> {
|
||||
if (isOutgoing) {
|
||||
extractSubtitleByAddressType()
|
||||
} else {
|
||||
val amount =
|
||||
amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_exit_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
}
|
||||
is TransactionType.YieldSupply.Exit -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_exit_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
|
|
@ -147,14 +205,18 @@ internal class TxHistoryItemStateConverter(
|
|||
|
||||
@Suppress("ComplexCondition")
|
||||
private fun TxInfo.getAmount(): String {
|
||||
if (type is TransactionType.Staking.Vote ||
|
||||
type == TransactionType.Staking.ClaimRewards ||
|
||||
type == TransactionType.Staking.Withdraw ||
|
||||
type == TransactionType.YieldSupply.Enter ||
|
||||
type == TransactionType.YieldSupply.Exit ||
|
||||
type == TransactionType.YieldSupply.Topup
|
||||
) {
|
||||
return ""
|
||||
when (type) {
|
||||
is TransactionType.Staking.Vote,
|
||||
TransactionType.Staking.ClaimRewards,
|
||||
TransactionType.Staking.Withdraw,
|
||||
-> return ""
|
||||
|
||||
is TransactionType.YieldSupply -> {
|
||||
if (currency is CryptoCurrency.Token && type == TransactionType.YieldSupply.Send && !isOutgoing) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
val prefix = when {
|
||||
status == TransactionStatus.Failed -> ""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class YieldSupplyPromoBannerKeyConverter(
|
||||
private val yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
private val shouldShowMainPromo: Boolean,
|
||||
) : Converter<TokenConverterParams, String?> {
|
||||
|
||||
override fun convert(value: TokenConverterParams): String? {
|
||||
if (!shouldShowMainPromo) return null
|
||||
|
||||
val currencies = when (value) {
|
||||
is TokenConverterParams.Wallet -> value.tokenList.flattenCurrencies()
|
||||
is TokenConverterParams.Account -> value.accountList.flattenCurrencies()
|
||||
}.filter { status ->
|
||||
status.value is CryptoCurrencyStatus.Loaded ||
|
||||
status.value is CryptoCurrencyStatus.Custom
|
||||
}
|
||||
|
||||
val tokens = currencies.filter { it.currency is CryptoCurrency.Token }
|
||||
|
||||
if (tokens.any { it.value.yieldSupplyStatus?.isActive == true }) return null
|
||||
if (yieldModuleApyMap.isEmpty()) return null
|
||||
|
||||
val max = tokens.asSequence()
|
||||
.mapNotNull { status ->
|
||||
val token = status.currency as? CryptoCurrency.Token ?: return@mapNotNull null
|
||||
val tokenKey = token.yieldSupplyKey()
|
||||
val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId)
|
||||
|
||||
val matchedKey = yieldModuleApyMap.keys.firstOrNull { mapKey ->
|
||||
mapKey.equals(tokenKey, shouldIgnoreCase)
|
||||
} ?: return@mapNotNull null
|
||||
|
||||
status to matchedKey
|
||||
}
|
||||
.maxByOrNull { (status, _) -> status.value.amount ?: BigDecimal.ZERO }
|
||||
|
||||
return max?.second
|
||||
}
|
||||
}
|
||||
|
|
@ -5,16 +5,18 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.utils.coroutines.combine6
|
||||
import com.tangem.utils.coroutines.combine7
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Subscriber that monitors account list related data and updates the wallet state accordingly.
|
||||
|
|
@ -30,19 +32,21 @@ internal class AccountListSubscriber @AssistedInject constructor(
|
|||
override val clickIntents: WalletClickIntents,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : BasicAccountListSubscriber() {
|
||||
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine6(
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine7(
|
||||
flow1 = getAccountStatusListFlow(),
|
||||
flow2 = getAppCurrencyFlow(),
|
||||
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
|
||||
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
|
||||
flow5 = yieldSupplyApyFlow(),
|
||||
flow6 = stakingApyFlow(),
|
||||
flow7 = yieldSupplyGetShouldShowMainPromoFlow(),
|
||||
transform = ::updateState,
|
||||
)
|
||||
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, String>> {
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, BigDecimal>> {
|
||||
return yieldSupplyApyFlowUseCase().distinctUntilChanged()
|
||||
}
|
||||
|
||||
|
|
@ -50,6 +54,10 @@ internal class AccountListSubscriber @AssistedInject constructor(
|
|||
return stakingApyFlowUseCase().distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow<Boolean> {
|
||||
return yieldSupplyGetShouldShowMainPromoUseCase().distinctUntilChanged()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(userWallet: UserWallet): AccountListSubscriber
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenCon
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Basic implementation of [WalletSubscriber] for wallet with accounts.
|
||||
|
|
@ -46,8 +47,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
appCurrency: AppCurrency,
|
||||
expandedAccounts: Set<AccountId>,
|
||||
isAccountMode: Boolean,
|
||||
yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
shouldShowMainPromo: Boolean = false,
|
||||
) {
|
||||
val accountFlattenCurrencies = accountList.flattenCurrencies()
|
||||
val mainAccount = accountList.mainAccount
|
||||
|
|
@ -67,6 +69,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
portfolioId = PortfolioId(mainAccount.accountId),
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
}
|
||||
isAccountMode -> {
|
||||
|
|
@ -82,7 +85,13 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
} else {
|
||||
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
|
||||
|
||||
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
|
||||
updateContent(
|
||||
params = convertParams,
|
||||
appCurrency = appCurrency,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,8 +101,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
appCurrency: AppCurrency,
|
||||
portfolioId: PortfolioId,
|
||||
yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
shouldShowMainPromo: Boolean,
|
||||
) {
|
||||
val tokenList = maybeTokenList.getOrElse(
|
||||
ifLoading = { maybeContent ->
|
||||
|
|
@ -123,14 +133,16 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
appCurrency = appCurrency,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateContent(
|
||||
params: TokenConverterParams,
|
||||
appCurrency: AppCurrency,
|
||||
yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
shouldShowMainPromo: Boolean,
|
||||
) {
|
||||
stateController.update(
|
||||
SetTokenListTransformer(
|
||||
|
|
@ -140,6 +152,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
clickIntents = clickIntents,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.domain.staking.model.stakekit.Yield
|
|||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
|
|
@ -28,6 +29,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Deprecated("Use AccountListSubscriber instead")
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -40,6 +42,7 @@ internal abstract class BasicTokenListSubscriber(
|
|||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : WalletSubscriber() {
|
||||
|
||||
private val sendAnalyticsJobHolder = JobHolder()
|
||||
|
|
@ -69,7 +72,8 @@ internal abstract class BasicTokenListSubscriber(
|
|||
flow2 = appCurrencyFlow(),
|
||||
flow3 = yieldSupplyApyFlow(),
|
||||
flow4 = stakingApyFlow(),
|
||||
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap ->
|
||||
flow5 = yieldSupplyGetShouldShowMainPromoFlow(),
|
||||
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap, shouldShowMainPromo ->
|
||||
val tokenList = maybeTokenList.getOrElse(
|
||||
ifLoading = { maybeContent ->
|
||||
val isRefreshing = stateHolder.getWalletState(userWallet.walletId)
|
||||
|
|
@ -98,6 +102,7 @@ internal abstract class BasicTokenListSubscriber(
|
|||
appCurrency = appCurrency,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
|
||||
walletWithFundsChecker.check(tokenList)
|
||||
|
|
@ -122,8 +127,9 @@ internal abstract class BasicTokenListSubscriber(
|
|||
private fun updateContent(
|
||||
params: TokenConverterParams,
|
||||
appCurrency: AppCurrency,
|
||||
yieldSupplyApyMap: Map<String, String>,
|
||||
yieldSupplyApyMap: Map<String, BigDecimal>,
|
||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
shouldShowMainPromo: Boolean,
|
||||
) {
|
||||
stateHolder.update(
|
||||
SetTokenListTransformer(
|
||||
|
|
@ -133,6 +139,7 @@ internal abstract class BasicTokenListSubscriber(
|
|||
clickIntents = clickIntents,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -146,9 +153,12 @@ internal abstract class BasicTokenListSubscriber(
|
|||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, String>> = yieldSupplyApyFlowUseCase()
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, BigDecimal>> = yieldSupplyApyFlowUseCase()
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun stakingApyFlow(): Flow<Map<String, List<Yield.Validator>>> = stakingApyFlowUseCase()
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow<Boolean> = yieldSupplyGetShouldShowMainPromoUseCase()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
|||
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
|
|
@ -32,6 +33,7 @@ internal class MultiWalletTokenListSubscriber(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : BasicTokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
|
|
@ -41,6 +43,7 @@ internal class MultiWalletTokenListSubscriber(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
|
|
@ -27,6 +28,7 @@ internal class SingleWalletWithTokenListSubscriber(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : BasicTokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
|
|
@ -36,6 +38,7 @@ internal class SingleWalletWithTokenListSubscriber(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.paging.cachedIn
|
|||
import androidx.paging.map
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -58,7 +59,7 @@ internal class TxHistorySubscriber(
|
|||
refresh = isRefresh,
|
||||
).map { it.cachedIn(coroutineScope) }
|
||||
|
||||
setLoadedTxHistoryState(maybeTxHistoryItems)
|
||||
setLoadedTxHistoryState(maybeTxHistoryItems, status.currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,18 +68,19 @@ internal class TxHistorySubscriber(
|
|||
private fun setLoadingTxHistoryState(maybeTxHistoryItemCount: MaybeTxHistoryCount, status: CryptoCurrencyStatus) {
|
||||
stateHolder.update(
|
||||
maybeTxHistoryItemCount.fold(
|
||||
ifLeft = {
|
||||
ifLeft = { error ->
|
||||
SetTxHistoryCountErrorTransformer(
|
||||
userWallet = userWallet,
|
||||
error = it,
|
||||
error = error,
|
||||
pendingTransactions = status.value.pendingTransactions,
|
||||
currency = status.currency,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
ifRight = { txCount ->
|
||||
SetTxHistoryCountTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
transactionsCount = it,
|
||||
transactionsCount = txCount,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
|
|
@ -86,7 +88,7 @@ internal class TxHistorySubscriber(
|
|||
)
|
||||
}
|
||||
|
||||
private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems) {
|
||||
private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems, currency: CryptoCurrency) {
|
||||
stateHolder.update(
|
||||
maybeTxHistoryItems.fold(
|
||||
ifLeft = {
|
||||
|
|
@ -102,6 +104,7 @@ internal class TxHistorySubscriber(
|
|||
symbol = blockchain.currency,
|
||||
decimals = blockchain.decimals(),
|
||||
clickIntents = clickIntents,
|
||||
currency = currency,
|
||||
)
|
||||
|
||||
SetTxHistoryItemsTransformer(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.paging.cachedIn
|
|||
import androidx.paging.map
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -51,7 +52,7 @@ internal class TxHistorySubscriberV2(
|
|||
refresh = isRefresh,
|
||||
).map { it.cachedIn(coroutineScope) }
|
||||
|
||||
setLoadedTxHistoryState(maybeTxHistoryItems)
|
||||
setLoadedTxHistoryState(maybeTxHistoryItems, currency = status.currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -60,18 +61,19 @@ internal class TxHistorySubscriberV2(
|
|||
private fun setLoadingTxHistoryState(maybeTxHistoryItemCount: MaybeTxHistoryCount, status: CryptoCurrencyStatus) {
|
||||
stateController.update(
|
||||
maybeTxHistoryItemCount.fold(
|
||||
ifLeft = {
|
||||
ifLeft = { error ->
|
||||
SetTxHistoryCountErrorTransformer(
|
||||
userWallet = userWallet,
|
||||
error = it,
|
||||
error = error,
|
||||
pendingTransactions = status.value.pendingTransactions,
|
||||
clickIntents = clickIntents,
|
||||
currency = status.currency,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
ifRight = { txCount ->
|
||||
SetTxHistoryCountTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
transactionsCount = it,
|
||||
transactionsCount = txCount,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
|
|
@ -79,7 +81,7 @@ internal class TxHistorySubscriberV2(
|
|||
)
|
||||
}
|
||||
|
||||
private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems) {
|
||||
private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems, currency: CryptoCurrency) {
|
||||
stateController.update(
|
||||
maybeTxHistoryItems.fold(
|
||||
ifLeft = {
|
||||
|
|
@ -92,6 +94,7 @@ internal class TxHistorySubscriberV2(
|
|||
ifRight = { itemsFlow ->
|
||||
val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
val itemConverter = TxHistoryItemStateConverter(
|
||||
currency = currency,
|
||||
symbol = blockchain.currency,
|
||||
decimals = blockchain.decimals(),
|
||||
clickIntents = clickIntents,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class YieldSupplyPromoBannerKeyConverterTest {
|
||||
|
||||
@Test
|
||||
fun `GIVEN promo disabled WHEN convert THEN return null`() {
|
||||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xABCDEF")
|
||||
val status = createStatus(token = token, amount = BigDecimal.ONE, isYieldActive = false)
|
||||
val tokenList = ungroupedTokenList(status)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = tokenList,
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.10")),
|
||||
shouldShowMainPromo = false,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty apy map WHEN convert THEN return null`() {
|
||||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xA1")
|
||||
val status = createStatus(token = token, amount = BigDecimal("2.0"), isYieldActive = false)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = emptyMap(),
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN active yield token present WHEN convert THEN return null`() {
|
||||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xAA")
|
||||
val statusActive = createStatus(token = token, amount = BigDecimal("5"), isYieldActive = true)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(statusActive),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.12")),
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multiple candidates EVM case insensitive WHEN convert THEN return key of max amount`() {
|
||||
val evmNetworkId = "ETH"
|
||||
val tokenSmall = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xAbCd")
|
||||
val tokenBig = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xBEEF")
|
||||
|
||||
val statusSmall = createStatus(token = tokenSmall, amount = BigDecimal("1.00"), isYieldActive = false)
|
||||
val statusBig = createStatus(token = tokenBig, amount = BigDecimal("10.00"), isYieldActive = false)
|
||||
|
||||
val apyMap = mapOf(
|
||||
"${tokenSmall.network.backendId}_${tokenSmall.contractAddress.lowercase()}" to BigDecimal("0.05"),
|
||||
"${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}" to BigDecimal("0.15"),
|
||||
)
|
||||
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(statusSmall, statusBig),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = apyMap,
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
val expectedKey = "${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}"
|
||||
assertThat(result).isEqualTo(expectedKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non evm case sensitive mismatch WHEN convert THEN return null`() {
|
||||
val nonEvmId = "xrp"
|
||||
val token = createToken(networkId = nonEvmId, backendId = nonEvmId, contract = "rAbC123")
|
||||
val status = createStatus(token = token, amount = BigDecimal("3"), isYieldActive = false)
|
||||
|
||||
val mismatchedKey = "${token.network.backendId}_${token.contractAddress.lowercase()}"
|
||||
val apyMap = mapOf(mismatchedKey to BigDecimal("0.07"))
|
||||
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = apyMap,
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
private fun ungroupedTokenList(vararg statuses: CryptoCurrencyStatus): TokenList.Ungrouped {
|
||||
return TokenList.Ungrouped(
|
||||
totalFiatBalance = com.tangem.domain.models.TotalFiatBalance.Loaded(
|
||||
amount = BigDecimal.ZERO,
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
sortedBy = com.tangem.domain.models.TokensSortType.NONE,
|
||||
currencies = statuses.toList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createStatus(
|
||||
token: CryptoCurrency.Token,
|
||||
amount: BigDecimal,
|
||||
isYieldActive: Boolean,
|
||||
): CryptoCurrencyStatus {
|
||||
val networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "addr",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
)
|
||||
val value = CryptoCurrencyStatus.Custom(
|
||||
amount = amount,
|
||||
fiatAmount = null,
|
||||
fiatRate = null,
|
||||
priceChange = null,
|
||||
yieldBalance = null,
|
||||
yieldSupplyStatus = if (isYieldActive) {
|
||||
YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = null,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = networkAddress,
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
)
|
||||
return CryptoCurrencyStatus(
|
||||
currency = token,
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createToken(networkId: String, backendId: String, contract: String): CryptoCurrency.Token {
|
||||
val network = Network(
|
||||
id = Network.ID(value = networkId, derivationPath = Network.DerivationPath.None),
|
||||
backendId = backendId,
|
||||
name = backendId,
|
||||
currencySymbol = "SYM",
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
isTestnet = false,
|
||||
standardType = when (backendId) {
|
||||
"ethereum" -> Network.StandardType.ERC20
|
||||
else -> Network.StandardType.Unspecified("UNSPEC")
|
||||
},
|
||||
hasFiatFeeRate = false,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
return CryptoCurrency.Token(
|
||||
id = CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkId(networkId),
|
||||
suffix = CryptoCurrency.ID.Suffix.ContractAddress(contract),
|
||||
),
|
||||
network = network,
|
||||
name = "Token",
|
||||
symbol = "TKN",
|
||||
decimals = 18,
|
||||
iconUrl = null,
|
||||
isCustom = false,
|
||||
contractAddress = contract,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val appRouter: AppRouter,
|
||||
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
|
||||
) : Model(), YieldSupplyStopEarningComponent.ModelCallback,
|
||||
YieldSupplyApproveComponent.ModelCallback {
|
||||
|
||||
|
|
@ -241,11 +242,13 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
userWalletId,
|
||||
cryptoCurrencyStatusFlow.value,
|
||||
).onRight { minAmount ->
|
||||
val dustAmount = yieldSupplyGetDustMinAmountUseCase(minAmount = minAmount, appCurrency = appCurrency)
|
||||
uiState.update(
|
||||
YieldSupplyActiveMinAmountTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value,
|
||||
appCurrency = appCurrency,
|
||||
minAmount = minAmount,
|
||||
dustMinAmount = dustAmount,
|
||||
analyticsHandler = analyticsHandler,
|
||||
onApprove = ::onApprove,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@ import java.math.BigDecimal
|
|||
* - Builds the fee policy note text using the minimum amount.
|
||||
* - Adds contextual notifications:
|
||||
* - Approval required notification when spending is not yet allowed (emits analytics on CTA).
|
||||
* - "Not all amount supplied" info when wallet balance exceeds the supplied balance by more than [minAmount].
|
||||
* - "Not all amount supplied" info when the not-supplied balance exceeds the dust threshold [dustMinAmount].
|
||||
*
|
||||
* @property cryptoCurrencyStatus Current currency status used to calculate values and flags.
|
||||
* @property appCurrency Preferred fiat currency for formatting.
|
||||
* @property minAmount Protocol-required minimal amount to deposit/supply (in crypto units).
|
||||
* @property dustMinAmount Threshold used to detect dust/not-supplied balance (in crypto units).
|
||||
* @property analyticsHandler Analytics reporter for user actions.
|
||||
* @property onApprove Action invoked when the "Approve" notification button is tapped.
|
||||
*/
|
||||
|
|
@ -40,6 +41,7 @@ internal class YieldSupplyActiveMinAmountTransformer(
|
|||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val minAmount: BigDecimal,
|
||||
private val dustMinAmount: BigDecimal,
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
private val onApprove: () -> Unit,
|
||||
) : Transformer<YieldSupplyActiveContentUM> {
|
||||
|
|
@ -89,7 +91,7 @@ internal class YieldSupplyActiveMinAmountTransformer(
|
|||
}
|
||||
|
||||
private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? {
|
||||
return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)) {
|
||||
return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustMinAmount)) {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val notDepositedAmount = cryptoCurrencyStatus.notSuppliedAmountOrNull()
|
||||
val formattedAmount =
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.yield.supply.impl.main.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import android.os.SystemClock
|
||||
import com.tangem.common.routing.AppRoute.YieldSupplyPromo
|
||||
|
|
@ -12,6 +13,8 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -51,6 +54,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
|
|
@ -61,6 +65,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
||||
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
|
||||
) : Model(), YieldSupplyClickIntents {
|
||||
|
||||
private val params = paramsContainer.require<YieldSupplyComponent.Params>()
|
||||
|
|
@ -69,6 +74,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
field = MutableStateFlow<YieldSupplyUM>(YieldSupplyUM.Initial)
|
||||
|
||||
private val cryptoCurrency = params.cryptoCurrency
|
||||
private var appCurrency: AppCurrency = AppCurrency.Default
|
||||
var userWallet: UserWallet by Delegates.notNull()
|
||||
|
||||
private val fetchCurrencyJobHolder = JobHolder()
|
||||
|
|
@ -81,10 +87,17 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun checkIfYieldSupplyIsAvailable() {
|
||||
modelScope.launch(dispatchers.io) {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||
val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency)
|
||||
if (isAvailable) {
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
singleNetworkStatusFetcher(
|
||||
params = SingleNetworkStatusFetcher.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -334,7 +347,11 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
val minAmount = yieldSupplyMinAmountUseCase(userWalletId = userWallet.walletId, cryptoCurrencyStatus)
|
||||
.getOrNull()
|
||||
if (minAmount != null) {
|
||||
cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)
|
||||
val dustAmount = yieldSupplyGetDustMinAmountUseCase(
|
||||
minAmount = minAmount,
|
||||
appCurrency = appCurrency,
|
||||
)
|
||||
cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
# https://github.com/tangem/tangem-sdk-android/
|
||||
# https://github.com/tangem/vico
|
||||
|
||||
tangemBlockchainSdk = "releases-5.31-1314"
|
||||
tangemBlockchainSdk = "releases-5.31.1-1316"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "releases-5.31-569"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue