Updated on 2026-08-14
This commit is contained in:
commit
5ee5b4f61b
563 changed files with 13917 additions and 4692 deletions
|
|
@ -5,6 +5,7 @@ import com.tangem.blockchain.common.TransactionData
|
|||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Immutable
|
||||
internal sealed class YieldSupplyFeeUM {
|
||||
|
|
@ -15,6 +16,7 @@ internal sealed class YieldSupplyFeeUM {
|
|||
val feeValue: TextReference,
|
||||
val currentNetworkFeeValue: TextReference,
|
||||
val maxNetworkFeeValue: TextReference,
|
||||
val minAmountFeeValue: TextReference,
|
||||
) : YieldSupplyFeeUM()
|
||||
}
|
||||
|
||||
|
|
@ -27,4 +29,5 @@ internal data class YieldSupplyActionUM(
|
|||
val yieldSupplyFeeUM: YieldSupplyFeeUM,
|
||||
val isPrimaryButtonEnabled: Boolean,
|
||||
val isTransactionSending: Boolean,
|
||||
val maxFee: BigDecimal = BigDecimal.ZERO,
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.features.yield.supply.impl.common.formatter
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
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.utils.StringsSigns
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class YieldSupplyMinAmountFormatter(
|
||||
private val feeCryptoCurrency: CryptoCurrency,
|
||||
private val appCurrency: AppCurrency,
|
||||
) {
|
||||
|
||||
operator fun invoke(feeValue: BigDecimal, fiatRate: BigDecimal?): TextReference {
|
||||
val cryptoFee = feeValue.format { crypto(feeCryptoCurrency) }
|
||||
val fiatFeeValue = fiatRate?.let(feeValue::multiply)
|
||||
val fiatFee = fiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
|
||||
return stringReference(cryptoFee + " ${StringsSigns.DOT} " + fiatFee)
|
||||
}
|
||||
}
|
||||
|
|
@ -141,6 +141,7 @@ private class YieldSupplyActionContentPreviewProvider : PreviewParameterProvider
|
|||
feeValue = stringReference("0.00020 ETH • \$0.99"),
|
||||
currentNetworkFeeValue = stringReference("1.45 USDT • \$1.45"),
|
||||
maxNetworkFeeValue = stringReference("8.50 USDT • \$8.50"),
|
||||
minAmountFeeValue = stringReference("50 USDT • \$50"),
|
||||
),
|
||||
isPrimaryButtonEnabled = false,
|
||||
isTransactionSending = false,
|
||||
|
|
|
|||
|
|
@ -38,9 +38,8 @@ internal class DefaultYieldSupplyComponent @AssistedInject constructor(
|
|||
override fun Content(modifier: Modifier) {
|
||||
val yieldSupplyUM by model.uiState.collectAsStateWithLifecycle()
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
val isBalanceHidden by model.isBalanceHiddenFlow.collectAsStateWithLifecycle()
|
||||
|
||||
YieldSupplyBlockContent(yieldSupplyUM = yieldSupplyUM, isBalanceHidden = isBalanceHidden, modifier = modifier)
|
||||
YieldSupplyBlockContent(yieldSupplyUM = yieldSupplyUM, modifier = modifier)
|
||||
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
@Immutable
|
||||
internal sealed class YieldSupplyUM {
|
||||
|
||||
data class Initial(
|
||||
data object Initial : YieldSupplyUM()
|
||||
|
||||
data class Available(
|
||||
val title: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
) : YieldSupplyUM()
|
||||
|
|
@ -16,7 +18,8 @@ internal sealed class YieldSupplyUM {
|
|||
data object Unavailable : YieldSupplyUM()
|
||||
|
||||
data class Content(
|
||||
val rewardsBalance: TextReference,
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
val rewardsApy: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
val isAllowedToSpend: Boolean,
|
||||
|
|
|
|||
|
|
@ -4,24 +4,31 @@ import com.arkivanov.decompose.router.slot.SlotNavigation
|
|||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.common.routing.AppRoute.YieldSupplyPromo
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
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.wrappedList
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
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
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyComponent
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyIsAvailableUseCase
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyComponent
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
|
||||
import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.DelayedWork
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -29,6 +36,7 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import com.tangem.utils.transformer.update
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
|
@ -37,6 +45,7 @@ import kotlin.properties.Delegates
|
|||
internal class YieldSupplyModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
|
|
@ -44,12 +53,15 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
|
||||
private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase,
|
||||
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
|
||||
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
||||
) : Model(), YieldSupplyClickIntents {
|
||||
|
||||
private val params = paramsContainer.require<YieldSupplyComponent.Params>()
|
||||
|
||||
val uiState: StateFlow<YieldSupplyUM>
|
||||
field = MutableStateFlow<YieldSupplyUM>(YieldSupplyUM.Loading)
|
||||
field = MutableStateFlow<YieldSupplyUM>(YieldSupplyUM.Initial)
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<Unit> = SlotNavigation()
|
||||
|
||||
|
|
@ -67,9 +79,20 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
val isBalanceHiddenFlow: StateFlow<Boolean>
|
||||
field = MutableStateFlow(false)
|
||||
|
||||
private var lastYieldSupplyStatus: YieldSupplyStatus? = null
|
||||
|
||||
init {
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
subscribeOnBalanceHidden()
|
||||
checkIfYieldSupplyIsAvailable()
|
||||
}
|
||||
|
||||
private fun checkIfYieldSupplyIsAvailable() {
|
||||
modelScope.launch(dispatchers.io) {
|
||||
val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency)
|
||||
if (isAvailable) {
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
subscribeOnBalanceHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
|
|
@ -86,7 +109,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
maybeCryptoCurrency.fold(
|
||||
ifRight = { cryptoCurrencyStatus ->
|
||||
cryptoCurrencyStatusFlow.update { cryptoCurrencyStatus }
|
||||
onDataLoaded(cryptoCurrencyStatus)
|
||||
onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus)
|
||||
},
|
||||
ifLeft = {
|
||||
Timber.w(it.toString())
|
||||
|
|
@ -102,23 +125,19 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadTokenStatus(cryptoCurrency: CryptoCurrency.Token) {
|
||||
private fun loadTokenStatus() {
|
||||
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
|
||||
modelScope.launch(dispatchers.default) {
|
||||
yieldSupplyGetTokenStatusUseCase(cryptoCurrency)
|
||||
yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken)
|
||||
.onRight { tokenStatus ->
|
||||
val newState = if (tokenStatus.isActive) {
|
||||
YieldSupplyUM.Initial(
|
||||
title = resourceReference(
|
||||
id = R.string.yield_module_token_details_earn_notification_title,
|
||||
formatArgs = wrappedList(tokenStatus.apy),
|
||||
),
|
||||
onClick = ::onStartEarningClick,
|
||||
)
|
||||
} else {
|
||||
YieldSupplyUM.Unavailable
|
||||
}
|
||||
uiState.update { newState }
|
||||
uiState.update(
|
||||
YieldSupplyTokenStatusSuccessTransformer(
|
||||
tokenStatus = tokenStatus,
|
||||
onStartEarningClick = ::onStartEarningClick,
|
||||
),
|
||||
)
|
||||
}.onLeft {
|
||||
Timber.e(it)
|
||||
uiState.update { YieldSupplyUM.Unavailable }
|
||||
}
|
||||
}
|
||||
|
|
@ -148,38 +167,85 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun onDataLoaded(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
@Suppress("MaximumLineLength")
|
||||
private fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
|
||||
val hasActiveTransaction = cryptoCurrencyStatus.value.hasCurrentNetworkTransactions
|
||||
val yieldTransaction = cryptoCurrencyStatus.value.pendingTransactions.firstOrNull {
|
||||
it.type is TxInfo.TransactionType.YieldSupply
|
||||
}?.type as? TxInfo.TransactionType.YieldSupply
|
||||
sendInfoAboutProtocolStatus(cryptoCurrencyStatus)
|
||||
|
||||
val yieldSupplyUM = when {
|
||||
when {
|
||||
hasActiveTransaction && yieldTransaction != null -> {
|
||||
coroutineScope.launch(dispatchers.io) {
|
||||
delay(PROCESSING_UPDATE_DELAY)
|
||||
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
|
||||
}
|
||||
when (yieldTransaction) {
|
||||
TxInfo.TransactionType.YieldSupply.Enter -> YieldSupplyUM.Processing.Enter
|
||||
TxInfo.TransactionType.YieldSupply.Exit -> YieldSupplyUM.Processing.Exit
|
||||
uiState.update {
|
||||
when (yieldTransaction) {
|
||||
TxInfo.TransactionType.YieldSupply.Enter -> YieldSupplyUM.Processing.Enter
|
||||
TxInfo.TransactionType.YieldSupply.Exit -> YieldSupplyUM.Processing.Exit
|
||||
}
|
||||
}
|
||||
}
|
||||
yieldSupplyStatus?.isActive == true ->
|
||||
YieldSupplyUM.Content(
|
||||
rewardsBalance = TextReference.EMPTY,
|
||||
rewardsApy = TextReference.EMPTY,
|
||||
onClick = ::onActiveClick,
|
||||
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
|
||||
)
|
||||
else -> YieldSupplyUM.Loading
|
||||
yieldSupplyStatus?.isActive == true -> {
|
||||
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
|
||||
if (!yieldSupplyStatus.isAllowedToSpend) {
|
||||
analyticsEventsHandler.send(
|
||||
YieldSupplyAnalytics.NoticeApproveNeeded(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
modelScope.launch(dispatchers.default) {
|
||||
yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken)
|
||||
.onRight { tokenStatus ->
|
||||
uiState.update {
|
||||
YieldSupplyUM.Content(
|
||||
title = resourceReference(
|
||||
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_subtitle,
|
||||
),
|
||||
rewardsApy = combinedReference(
|
||||
resourceReference(
|
||||
R.string.yield_module_token_details_earn_notification_apy,
|
||||
),
|
||||
stringReference(" ${tokenStatus.apy}%"),
|
||||
),
|
||||
onClick = ::onActiveClick,
|
||||
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
|
||||
)
|
||||
}
|
||||
}.onLeft {
|
||||
Timber.e(it)
|
||||
uiState.update { YieldSupplyUM.Loading }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
loadTokenStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiState.update { yieldSupplyUM }
|
||||
|
||||
if (yieldSupplyUM is YieldSupplyUM.Loading) {
|
||||
(cryptoCurrency as? CryptoCurrency.Token)?.let(::loadTokenStatus)
|
||||
private fun sendInfoAboutProtocolStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
if (lastYieldSupplyStatus == cryptoCurrencyStatus.value.yieldSupplyStatus) return
|
||||
val token = cryptoCurrency as? CryptoCurrency.Token ?: return
|
||||
modelScope.launch(dispatchers.default) {
|
||||
if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true) {
|
||||
yieldSupplyActivateUseCase(token).onRight {
|
||||
lastYieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
|
||||
}
|
||||
} else {
|
||||
yieldSupplyDeactivateUseCase(token).onRight {
|
||||
lastYieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.yield.supply.impl.main.model.transformers
|
||||
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class YieldSupplyTokenStatusSuccessTransformer(
|
||||
private val tokenStatus: YieldMarketToken,
|
||||
private val onStartEarningClick: () -> Unit,
|
||||
) : Transformer<YieldSupplyUM> {
|
||||
|
||||
override fun transform(prevState: YieldSupplyUM): YieldSupplyUM {
|
||||
if (!tokenStatus.isActive) return YieldSupplyUM.Unavailable
|
||||
|
||||
return YieldSupplyUM.Available(
|
||||
title = resourceReference(
|
||||
id = R.string.yield_module_token_details_earn_notification_title,
|
||||
formatArgs = wrappedList(tokenStatus.apy),
|
||||
),
|
||||
onClick = onStartEarningClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.yield.supply.impl.main.ui
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -17,12 +18,14 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
|
|
@ -34,19 +37,15 @@ import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
|
|||
import com.tangem.utils.StringsSigns
|
||||
|
||||
@Composable
|
||||
internal fun YieldSupplyBlockContent(
|
||||
yieldSupplyUM: YieldSupplyUM,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
internal fun YieldSupplyBlockContent(yieldSupplyUM: YieldSupplyUM, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(
|
||||
targetState = yieldSupplyUM,
|
||||
modifier = modifier,
|
||||
) { supplyUM ->
|
||||
when (supplyUM) {
|
||||
is YieldSupplyUM.Initial -> SupplyInitial(supplyUM)
|
||||
is YieldSupplyUM.Available -> SupplyAvailable(supplyUM)
|
||||
YieldSupplyUM.Loading -> SupplyLoading()
|
||||
is YieldSupplyUM.Content -> SupplyContent(supplyUM, isBalanceHidden)
|
||||
is YieldSupplyUM.Content -> SupplyContent(supplyUM)
|
||||
YieldSupplyUM.Processing.Enter -> SupplyProcessing(
|
||||
resourceReference(R.string.yield_module_token_details_earn_notification_processing),
|
||||
)
|
||||
|
|
@ -54,12 +53,13 @@ internal fun YieldSupplyBlockContent(
|
|||
resourceReference(R.string.yield_module_stop_earning),
|
||||
)
|
||||
YieldSupplyUM.Unavailable -> SupplyUnavailable()
|
||||
YieldSupplyUM.Initial -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SupplyInitial(supplyUM: YieldSupplyUM.Initial) {
|
||||
private fun SupplyAvailable(supplyUM: YieldSupplyUM.Available) {
|
||||
SupplyInfo(
|
||||
title = supplyUM.title,
|
||||
subtitle = resourceReference(R.string.yield_module_token_details_earn_notification_description),
|
||||
|
|
@ -86,7 +86,7 @@ private fun SupplyUnavailable() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun SupplyContent(supplyUM: YieldSupplyUM.Content, isBalanceHidden: Boolean) {
|
||||
private fun SupplyContent(supplyUM: YieldSupplyUM.Content) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
|
|
@ -95,22 +95,21 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, isBalanceHidden: Bool
|
|||
.clickable(onClick = supplyUM.onClick)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_aave_22),
|
||||
modifier = Modifier.size(36.dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerW12()
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(
|
||||
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
|
||||
),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = supplyUM.rewardsBalance.orMaskWithStars(isBalanceHidden).resolveReference(),
|
||||
text = supplyUM.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
|
@ -122,9 +121,15 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, isBalanceHidden: Bool
|
|||
Text(
|
||||
text = supplyUM.rewardsApy.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = supplyUM.subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
SpacerW8()
|
||||
AnimatedVisibility(supplyUM.isAllowedToSpend.not()) {
|
||||
|
|
@ -145,36 +150,42 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, isBalanceHidden: Bool
|
|||
|
||||
@Composable
|
||||
private fun SupplyProcessing(text: TextReference) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(
|
||||
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
|
||||
),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_aave_22),
|
||||
modifier = Modifier.size(36.dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
SpacerW12()
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography.body1,
|
||||
text = stringResourceSafe(
|
||||
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
|
||||
),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp),
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
strokeWidth = 2.dp,
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
SpacerW8()
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(20.dp),
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,14 +296,14 @@ private fun SupplyInfo(
|
|||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun YieldSupplyBlockContent_Preview(@PreviewParameter(PreviewProvider::class) params: YieldSupplyUM) {
|
||||
TangemThemePreview {
|
||||
YieldSupplyBlockContent(params, true)
|
||||
YieldSupplyBlockContent(yieldSupplyUM = params, modifier = Modifier)
|
||||
}
|
||||
}
|
||||
|
||||
private class PreviewProvider : PreviewParameterProvider<YieldSupplyUM> {
|
||||
override val values: Sequence<YieldSupplyUM>
|
||||
get() = sequenceOf(
|
||||
YieldSupplyUM.Initial(
|
||||
YieldSupplyUM.Available(
|
||||
title = TextReference.Res(
|
||||
R.string.yield_module_token_details_earn_notification_title,
|
||||
wrappedList("5.1"),
|
||||
|
|
@ -300,13 +311,15 @@ private class PreviewProvider : PreviewParameterProvider<YieldSupplyUM> {
|
|||
onClick = {},
|
||||
),
|
||||
YieldSupplyUM.Content(
|
||||
rewardsBalance = stringReference("1 USDT"),
|
||||
title = stringReference("Aave lending is active"),
|
||||
subtitle = stringReference("Interest accrues automatically"),
|
||||
rewardsApy = stringReference("5.1 % APY"),
|
||||
onClick = {},
|
||||
isAllowedToSpend = false,
|
||||
),
|
||||
YieldSupplyUM.Content(
|
||||
rewardsBalance = stringReference("1 USDT"),
|
||||
title = stringReference("Aave lending is active"),
|
||||
subtitle = stringReference("Interest accrues automatically"),
|
||||
rewardsApy = stringReference("5.1 % APY"),
|
||||
onClick = {},
|
||||
isAllowedToSpend = true,
|
||||
|
|
|
|||
|
|
@ -3,30 +3,48 @@ package com.tangem.features.yield.supply.impl.promo.model
|
|||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.promo.YieldSupplyPromoConfig
|
||||
import com.tangem.features.yield.supply.impl.promo.entity.YieldSupplyPromoUM
|
||||
import com.tangem.utils.TangemBlogUrlBuilder.YIELD_SUPPLY_HOW_IT_WORKS_URL
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class YieldSupplyPromoModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val appRouter: AppRouter,
|
||||
) : Model(), YieldSupplyPromoClickIntents {
|
||||
|
||||
val params: YieldSupplyPromoComponent.Params = paramsContainer.require()
|
||||
|
||||
val uiState: YieldSupplyPromoUM = YieldSupplyPromoUM(
|
||||
tosLink = "https://tangem.com/terms-of-service/", // TODO replace with real link
|
||||
policyLink = "https://tangem.com/privacy-policy/", // TODO replace with real link
|
||||
title = resourceReference(R.string.yield_module_promo_screen_title, wrappedList("5.3")),
|
||||
)
|
||||
|
||||
init {
|
||||
analytics.send(
|
||||
YieldSupplyAnalytics.EarningScreenInfoOpened(
|
||||
token = params.currency.symbol,
|
||||
blockchain = params.currency.network.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<YieldSupplyPromoConfig> = SlotNavigation()
|
||||
|
||||
override fun onBackClick() {
|
||||
|
|
@ -34,6 +52,7 @@ internal class YieldSupplyPromoModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onApyInfoClick() {
|
||||
analytics.send(YieldSupplyAnalytics.ApyChartViewed)
|
||||
bottomSheetNavigation.activate(YieldSupplyPromoConfig.Apy)
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +61,7 @@ internal class YieldSupplyPromoModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onHowItWorksClick() {
|
||||
urlOpener.openUrl("https://tangem.com/") // TODO replace with real link
|
||||
urlOpener.openUrl(YIELD_SUPPLY_HOW_IT_WORKS_URL)
|
||||
}
|
||||
|
||||
override fun onStartEarningClick() {
|
||||
|
|
|
|||
|
|
@ -8,16 +8,19 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.decompose.ComposableModularContentComponent
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.features.yield.supply.impl.subcomponents.active.model.YieldSupplyActiveModel
|
||||
import com.tangem.features.yield.supply.impl.subcomponents.active.ui.YieldSupplyActiveContent
|
||||
import com.tangem.features.yield.supply.impl.subcomponents.active.ui.YieldSupplyActiveTitle
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.impl.chart.DefaultYieldSupplyChartComponent
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal class YieldSupplyActiveComponent(
|
||||
|
|
@ -26,6 +29,12 @@ internal class YieldSupplyActiveComponent(
|
|||
) : ComposableModularContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: YieldSupplyActiveModel = getOrCreateModel(params = params)
|
||||
private val chartComponent = DefaultYieldSupplyChartComponent(
|
||||
appComponentContext = child("chartComponent"),
|
||||
params = DefaultYieldSupplyChartComponent.Params(
|
||||
cryptoCurrency = params.cryptoCurrencyStatusFlow.value.currency as CryptoCurrency.Token,
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Title() {
|
||||
|
|
@ -37,7 +46,12 @@ internal class YieldSupplyActiveComponent(
|
|||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
val isBalanceHidden by params.isBalanceHiddenFlow.collectAsStateWithLifecycle()
|
||||
|
||||
YieldSupplyActiveContent(state = state, isBalanceHidden = isBalanceHidden, modifier = Modifier)
|
||||
YieldSupplyActiveContent(
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
chartComponent = chartComponent,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -10,4 +10,6 @@ internal data class YieldSupplyActiveContentUM(
|
|||
val subtitle: TextReference,
|
||||
val subtitleLink: TextReference,
|
||||
val notificationUM: NotificationUM?,
|
||||
val apy: TextReference? = null,
|
||||
val minAmount: TextReference?,
|
||||
)
|
||||
|
|
@ -1,35 +1,53 @@
|
|||
package com.tangem.features.yield.supply.impl.subcomponents.active.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
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.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetProtocolBalanceUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.common.formatter.YieldSupplyMinAmountFormatter
|
||||
import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveComponent
|
||||
import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class YieldSupplyActiveModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
analyticsHandler: AnalyticsEventHandler,
|
||||
private val yieldSupplyGetProtocolBalanceUseCase: YieldSupplyGetProtocolBalanceUseCase,
|
||||
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
|
||||
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val params: YieldSupplyActiveComponent.Params = paramsContainer.require()
|
||||
|
||||
private val cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow
|
||||
private val cryptoCurrency = cryptoCurrencyStatusFlow.value.currency
|
||||
private var appCurrency = AppCurrency.Default
|
||||
|
||||
val uiState: StateFlow<YieldSupplyActiveContentUM>
|
||||
field = MutableStateFlow(
|
||||
|
|
@ -43,13 +61,21 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
),
|
||||
subtitleLink = resourceReference(R.string.common_read_more),
|
||||
notificationUM = null,
|
||||
minAmount = null,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
analyticsHandler.send(
|
||||
YieldSupplyAnalytics.EarningScreenInfoOpened(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
subscribeOnCurrencyUpdates()
|
||||
|
||||
modelScope.launch(dispatchers.default) {
|
||||
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||
val protocolBalance = yieldSupplyGetProtocolBalanceUseCase(
|
||||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
|
|
@ -87,6 +113,9 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
null
|
||||
}
|
||||
|
||||
loadApy()
|
||||
loadMinAmount()
|
||||
|
||||
uiState.update {
|
||||
it.copy(
|
||||
notificationUM = approvalNotification,
|
||||
|
|
@ -104,6 +133,42 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun loadApy() {
|
||||
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
|
||||
modelScope.launch(dispatchers.default) {
|
||||
yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken).onRight { tokenStatus ->
|
||||
uiState.update {
|
||||
it.copy(
|
||||
apy = TextReference.Str("${tokenStatus.apy}%"),
|
||||
)
|
||||
}
|
||||
}.onLeft {
|
||||
Timber.e("Error loading token status")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadMinAmount() {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
yieldSupplyMinAmountUseCase(
|
||||
params.userWallet,
|
||||
cryptoCurrencyStatusFlow.value,
|
||||
).onRight { minAmount ->
|
||||
val minAmountTextReference = YieldSupplyMinAmountFormatter(
|
||||
cryptoCurrencyStatusFlow.value.currency,
|
||||
appCurrency,
|
||||
).invoke(minAmount, cryptoCurrencyStatusFlow.value.value.fiatRate)
|
||||
uiState.update {
|
||||
it.copy(minAmount = minAmountTextReference)
|
||||
}
|
||||
}.onLeft {
|
||||
uiState.update {
|
||||
it.copy(minAmount = TextReference.Str(DASH_SIGN))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val AAVEV3_PREFIX = "a"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.withLink
|
||||
|
|
@ -25,6 +27,7 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -35,6 +38,7 @@ import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSu
|
|||
internal fun YieldSupplyActiveContent(
|
||||
state: YieldSupplyActiveContentUM,
|
||||
isBalanceHidden: Boolean,
|
||||
chartComponent: ComposableContentComponent,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -52,18 +56,9 @@ internal fun YieldSupplyActiveContent(
|
|||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.yield_module_earn_sheet_total_earnings_title),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
ResizableText(
|
||||
text = state.totalEarnings.orMaskWithStars(isBalanceHidden).resolveReference(),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
CurrentApy(state.apy)
|
||||
chartComponent.Content(Modifier.padding(bottom = 12.dp))
|
||||
}
|
||||
YieldSupplyActiveMyFunds(state = state, isBalanceHidden = isBalanceHidden)
|
||||
|
||||
AnimatedVisibility(state.notificationUM != null) {
|
||||
val wrappedNotification = remember(this) { requireNotNull(state.notificationUM) }
|
||||
|
|
@ -73,6 +68,48 @@ internal fun YieldSupplyActiveContent(
|
|||
containerColor = TangemTheme.colors.background.action,
|
||||
)
|
||||
}
|
||||
|
||||
YieldSupplyActiveMyFunds(state = state, isBalanceHidden = isBalanceHidden)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CurrentApy(apy: TextReference?, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.padding(vertical = 12.dp)) {
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
text = stringResourceSafe(R.string.yield_module_earn_sheet_current_apy_title),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
AnimatedContent(
|
||||
modifier = Modifier.height(32.dp),
|
||||
targetState = apy?.resolveReference(),
|
||||
label = "CurrentApy",
|
||||
) { apyText ->
|
||||
if (apyText == null) {
|
||||
TextShimmer(
|
||||
modifier = modifier.width(94.dp),
|
||||
text = "",
|
||||
style = TangemTheme.typography.head,
|
||||
)
|
||||
} else {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_arrow_up_8),
|
||||
tint = TangemTheme.colors.text.accent,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(end = 6.dp).size(12.dp),
|
||||
)
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = apyText,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +164,15 @@ private fun YieldSupplyActiveMyFunds(state: YieldSupplyActiveContentUM, isBalanc
|
|||
info = state.availableBalance,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = 0.5.dp,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
)
|
||||
InfoRow(
|
||||
title = resourceReference(R.string.yield_module_fee_policy_sheet_min_amount_title),
|
||||
info = state.minAmount,
|
||||
isBalanceHidden = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +239,11 @@ private fun YieldSupplyActiveBottomSheet_Preview(
|
|||
@PreviewParameter(YieldSupplyActiveBottomSheetPreviewProvider::class) params: YieldSupplyActiveContentUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
YieldSupplyActiveContent(params, true)
|
||||
YieldSupplyActiveContent(
|
||||
state = params,
|
||||
isBalanceHidden = true,
|
||||
chartComponent = ComposableContentComponent.EMPTY,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,6 +260,8 @@ private class YieldSupplyActiveBottomSheetPreviewProvider : PreviewParameterProv
|
|||
),
|
||||
subtitleLink = resourceReference(R.string.common_read_more),
|
||||
notificationUM = NotificationUM.Error.InvalidAmount,
|
||||
apy = stringReference("5,14%"),
|
||||
minAmount = stringReference("50 USDT"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.features.yield.supply.impl.subcomponents.approve.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -19,6 +22,7 @@ import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
|
|||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetContractAddressUseCase
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory
|
||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
|
||||
|
|
@ -44,6 +48,7 @@ import javax.inject.Inject
|
|||
internal class YieldSupplyApproveModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger,
|
||||
private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase,
|
||||
|
|
@ -138,6 +143,11 @@ internal class YieldSupplyApproveModel @Inject constructor(
|
|||
)
|
||||
},
|
||||
ifRight = {
|
||||
analyticsEventHandler.send(YieldSupplyAnalytics.ApprovalAction(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
action = YieldSupplyAnalytics.Action.Approve,
|
||||
))
|
||||
params.callback.onTransactionSent()
|
||||
},
|
||||
)
|
||||
|
|
@ -194,40 +204,47 @@ internal class YieldSupplyApproveModel @Inject constructor(
|
|||
}
|
||||
},
|
||||
ifRight = { fee ->
|
||||
val feeCryptoValue = fee.normal.amount.value
|
||||
applyFee(fee, approvalTransitionData)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val feeFiatValue = feeCryptoCurrencyStatus.value.fiatRate?.let { rate ->
|
||||
feeCryptoValue?.multiply(rate)
|
||||
}
|
||||
val cryptoFee = feeCryptoValue.format { crypto(feeCryptoCurrencyStatus.currency) }
|
||||
val fiatFee = feeFiatValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
private suspend fun applyFee(transactionFee: TransactionFee, approvalTransitionData: TransactionData.Uncompiled) {
|
||||
val feeCryptoValue = transactionFee.normal.amount.value
|
||||
|
||||
uiState.update {
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
||||
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
|
||||
} else {
|
||||
it.copy(
|
||||
isPrimaryButtonEnabled = true,
|
||||
yieldSupplyFeeUM = YieldSupplyFeeUM.Content(
|
||||
transactionDataList = persistentListOf(approvalTransitionData.copy(fee = fee.normal)),
|
||||
feeValue = combinedReference(
|
||||
stringReference(cryptoFee),
|
||||
stringReference(" $DOT "),
|
||||
stringReference(fiatFee),
|
||||
),
|
||||
currentNetworkFeeValue = TextReference.EMPTY,
|
||||
maxNetworkFeeValue = TextReference.EMPTY,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
yieldSupplyNotificationsUpdateTrigger.triggerUpdate(
|
||||
data = YieldSupplyNotificationData(
|
||||
feeValue = feeCryptoValue,
|
||||
feeError = null,
|
||||
val feeFiatValue = feeCryptoCurrencyStatus.value.fiatRate?.let { rate ->
|
||||
feeCryptoValue?.multiply(rate)
|
||||
}
|
||||
val cryptoFee = feeCryptoValue.format { crypto(feeCryptoCurrencyStatus.currency) }
|
||||
val fiatFee = feeFiatValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
|
||||
uiState.update {
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
||||
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
|
||||
} else {
|
||||
it.copy(
|
||||
isPrimaryButtonEnabled = true,
|
||||
yieldSupplyFeeUM = YieldSupplyFeeUM.Content(
|
||||
transactionDataList = persistentListOf(
|
||||
approvalTransitionData.copy(fee = transactionFee.normal),
|
||||
),
|
||||
feeValue = combinedReference(
|
||||
stringReference(cryptoFee),
|
||||
stringReference(" $DOT "),
|
||||
stringReference(fiatFee),
|
||||
),
|
||||
currentNetworkFeeValue = TextReference.EMPTY,
|
||||
maxNetworkFeeValue = TextReference.EMPTY,
|
||||
minAmountFeeValue = TextReference.EMPTY,
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
yieldSupplyNotificationsUpdateTrigger.triggerUpdate(
|
||||
data = YieldSupplyNotificationData(
|
||||
feeValue = feeCryptoValue,
|
||||
feeError = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.tangem.features.yield.supply.impl.common.ui.YieldSupplyFeeRow
|
|||
import com.tangem.utils.StringsSigns
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun YieldSupplyFeePolicyContent(
|
||||
yieldSupplyFeeUM: YieldSupplyFeeUM,
|
||||
|
|
@ -61,6 +62,28 @@ internal fun YieldSupplyFeePolicyContent(
|
|||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
SpacerH24()
|
||||
FooterContainer(
|
||||
footer = resourceReference(
|
||||
id = R.string.yield_module_fee_policy_sheet_min_amount_note,
|
||||
formatArgs = wrappedList(networkName),
|
||||
),
|
||||
paddingValues = PaddingValues(
|
||||
top = 8.dp,
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
),
|
||||
) {
|
||||
val minAmount = when (yieldSupplyFeeUM) {
|
||||
is YieldSupplyFeeUM.Content -> yieldSupplyFeeUM.minAmountFeeValue
|
||||
YieldSupplyFeeUM.Error -> stringReference(StringsSigns.DASH_SIGN)
|
||||
YieldSupplyFeeUM.Loading -> null
|
||||
}
|
||||
YieldSupplyFeeRow(
|
||||
title = resourceReference(R.string.yield_module_fee_policy_sheet_min_amount_title),
|
||||
value = minAmount,
|
||||
)
|
||||
}
|
||||
SpacerH16()
|
||||
FooterContainer(
|
||||
footer = resourceReference(
|
||||
id = R.string.yield_module_fee_policy_sheet_current_fee_note,
|
||||
|
|
@ -101,6 +124,18 @@ internal fun YieldSupplyFeePolicyContent(
|
|||
value = maxFee,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.yield_module_fee_policy_tangem_service_fee_title),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
top = 8.dp,
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,11 +151,11 @@ private fun YieldSupplyFeePolicyContent_Preview() {
|
|||
feeValue = stringReference("0.0001 ETH • \$1.45"),
|
||||
maxNetworkFeeValue = stringReference("8.50 USDT • \$8.50"),
|
||||
currentNetworkFeeValue = stringReference("1.45 USDT • \$1.45"),
|
||||
minAmountFeeValue = stringReference("50 USDT • \$50"),
|
||||
),
|
||||
tokenSymbol = "USDT",
|
||||
networkName = "Ethereum",
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import com.tangem.common.routing.AppRouter
|
|||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExceedsBalanceNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachableNotification
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent
|
||||
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateListener
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
|
|
@ -25,6 +27,7 @@ import javax.inject.Inject
|
|||
internal class YieldSupplyNotificationsModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
|
||||
private val yieldSupplyNotificationsUpdateListener: YieldSupplyNotificationsUpdateListener,
|
||||
|
|
@ -73,6 +76,15 @@ internal class YieldSupplyNotificationsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
if (notifications.any { it is NotificationUM.Error.TokenExceedsBalance }) {
|
||||
analyticsEventHandler.send(
|
||||
YieldSupplyAnalytics.NoticeNotEnoughFee(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
blockchain = cryptoCurrencyStatus.currency.network.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
uiState.update { notifications.toPersistentList() }
|
||||
|
||||
yieldSupplyNotificationsUpdateListener.callbackHasError(notifications.any())
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ internal class YieldSupplyStartEarningComponent(
|
|||
onClick = model::onClick,
|
||||
enabled = state.isPrimaryButtonEnabled,
|
||||
iconResId = icon,
|
||||
showProgress = state.isTransactionSending,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.yield.supply.impl.subcomponents.startearning.model
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -8,6 +9,7 @@ import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIco
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
|
||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
|
||||
import com.tangem.features.yield.supply.impl.subcomponents.feepolicy.YieldSupplyFeePolicyComponent
|
||||
|
|
@ -24,6 +26,7 @@ import javax.inject.Inject
|
|||
internal class YieldSupplyStartEarningEntryModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model(), YieldSupplyStartEarningComponent.ModelCallback, YieldSupplyFeePolicyComponent.ModelCallback {
|
||||
|
||||
|
|
@ -55,6 +58,9 @@ internal class YieldSupplyStartEarningEntryModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onFeePolicyClick() {
|
||||
analyticsEventHandler.send(
|
||||
YieldSupplyAnalytics.ButtonFeePolicy,
|
||||
)
|
||||
router.push(YieldSupplyStartEarningRoute.FeePolicy)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,24 +2,32 @@ package com.tangem.features.yield.supply.impl.subcomponents.startearning.model
|
|||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.TransactionSender
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
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.models.wallet.UserWallet
|
||||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory
|
||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
|
||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
|
||||
|
|
@ -33,6 +41,7 @@ import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.tr
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
|
@ -45,6 +54,7 @@ import kotlin.properties.Delegates
|
|||
internal class YieldSupplyStartEarningModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
|
|
@ -55,11 +65,15 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
|||
private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
|
||||
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
|
||||
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
|
||||
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
||||
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
|
||||
|
||||
private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require()
|
||||
|
||||
private val cryptoCurrency = params.cryptoCurrency
|
||||
private var minAmount: BigDecimal by Delegates.notNull()
|
||||
var userWallet: UserWallet by Delegates.notNull()
|
||||
|
||||
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
|
||||
|
|
@ -99,6 +113,12 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
|||
private var appCurrency = AppCurrency.Default
|
||||
|
||||
init {
|
||||
analytics.send(
|
||||
YieldSupplyAnalytics.StartEarningScreen(
|
||||
token = params.cryptoCurrency.symbol,
|
||||
blockchain = params.cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
modelScope.launch {
|
||||
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
|
|
@ -106,17 +126,51 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun calculateMinAmount(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
yieldSupplyMinAmountUseCase(userWallet, cryptoCurrencyStatus).onRight {
|
||||
minAmount = it
|
||||
}.onLeft {
|
||||
minAmount = BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMaxFee(): BigDecimal? {
|
||||
if (uiState.value.maxFee != BigDecimal.ZERO) return uiState.value.maxFee
|
||||
val yieldTokenStatus = yieldSupplyGetTokenStatusUseCase(cryptoCurrency as CryptoCurrency.Token)
|
||||
.getOrNull()
|
||||
return yieldTokenStatus?.maxFeeNative?.parseToBigDecimal(cryptoCurrency.decimals)
|
||||
}
|
||||
|
||||
private suspend fun onLoadFee() {
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading || uiState.value.isTransactionSending) return
|
||||
|
||||
uiState.update {
|
||||
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
|
||||
}
|
||||
|
||||
val maxFee = if (uiState.value.maxFee == BigDecimal.ZERO) {
|
||||
getMaxFee()
|
||||
} else {
|
||||
uiState.value.maxFee
|
||||
} ?: return
|
||||
|
||||
val transactionListData = yieldSupplyStartEarningUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
maxNetworkFee = MAX_NETWORK_FEE,
|
||||
).getOrNull() ?: return
|
||||
maxNetworkFee = maxFee,
|
||||
).getOrNull()
|
||||
|
||||
uiState.update {
|
||||
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
|
||||
if (transactionListData == null) {
|
||||
uiState.update {
|
||||
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Error)
|
||||
}
|
||||
yieldSupplyNotificationsUpdateTrigger.triggerUpdate(
|
||||
data = YieldSupplyNotificationData(
|
||||
feeValue = null,
|
||||
feeError = GetFeeError.UnknownError,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
yieldSupplyEstimateEnterFeeUseCase.invoke(
|
||||
|
|
@ -146,7 +200,8 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
|||
appCurrency = appCurrency,
|
||||
updatedTransactionList = updatedTransactionList,
|
||||
feeValue = feeSum,
|
||||
maxNetworkFee = MAX_NETWORK_FEE,
|
||||
maxNetworkFee = maxFee,
|
||||
minAmount = minAmount,
|
||||
),
|
||||
)
|
||||
yieldSupplyNotificationsUpdateTrigger.triggerUpdate(
|
||||
|
|
@ -161,6 +216,12 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
|||
|
||||
fun onClick() {
|
||||
val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return
|
||||
analytics.send(
|
||||
YieldSupplyAnalytics.ButtonStartEarning(
|
||||
token = params.cryptoCurrency.symbol,
|
||||
blockchain = params.cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
|
||||
uiState.update(YieldSupplyTransactionInProgressTransformer)
|
||||
modelScope.launch(dispatchers.default) {
|
||||
|
|
@ -173,6 +234,10 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
|||
ifLeft = { error ->
|
||||
Timber.e(error.toString())
|
||||
uiState.update(YieldSupplyTransactionReadyTransformer)
|
||||
analytics.send(YieldSupplyAnalytics.EarnErrors(
|
||||
action = YieldSupplyAnalytics.Action.Approve,
|
||||
errorDescription = error.getAnalyticsDescription(),
|
||||
))
|
||||
yieldSupplyAlertFactory.getSendTransactionErrorState(
|
||||
error = error,
|
||||
popBack = params.callback::onBackClick,
|
||||
|
|
@ -188,7 +253,14 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
|||
)
|
||||
},
|
||||
ifRight = {
|
||||
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
|
||||
yieldSupplyActivateUseCase(cryptoCurrency)
|
||||
modelScope.launch(NonCancellable) {
|
||||
fetchCurrencyStatusUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency.id,
|
||||
)
|
||||
}
|
||||
analytics.send(YieldSupplyAnalytics.FundsEarned)
|
||||
modelScope.launch {
|
||||
params.callback.onTransactionSent()
|
||||
}
|
||||
|
|
@ -256,6 +328,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
|||
feeCryptoCurrencyStatusFlow.update { feeCurrencyStatus }
|
||||
|
||||
modelScope.launch {
|
||||
calculateMinAmount(userWallet, currencyStatus)
|
||||
onLoadFee()
|
||||
}
|
||||
}
|
||||
|
|
@ -274,8 +347,4 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
|||
popBack = params.callback::onBackClick,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val MAX_NETWORK_FEE: BigDecimal = BigDecimal.TEN // TODO replace with value from api
|
||||
}
|
||||
}
|
||||
|
|
@ -10,12 +10,14 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
|
||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
|
||||
import com.tangem.features.yield.supply.impl.common.formatter.YieldSupplyMinAmountFormatter
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class YieldSupplyStartEarningFeeContentTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
|
|
@ -23,6 +25,7 @@ internal class YieldSupplyStartEarningFeeContentTransformer(
|
|||
private val updatedTransactionList: List<TransactionData.Uncompiled>,
|
||||
private val feeValue: BigDecimal,
|
||||
private val maxNetworkFee: BigDecimal,
|
||||
private val minAmount: BigDecimal,
|
||||
) : Transformer<YieldSupplyActionUM> {
|
||||
override fun transform(prevState: YieldSupplyActionUM): YieldSupplyActionUM {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
|
|
@ -46,6 +49,11 @@ internal class YieldSupplyStartEarningFeeContentTransformer(
|
|||
}
|
||||
val maxFiatFee = maxFiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||
|
||||
val minAmountTextReference = YieldSupplyMinAmountFormatter(
|
||||
cryptoCurrency,
|
||||
appCurrency,
|
||||
).invoke(minAmount, cryptoCurrencyStatus.value.fiatRate)
|
||||
|
||||
return if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
||||
prevState.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
|
||||
} else {
|
||||
|
|
@ -67,6 +75,7 @@ internal class YieldSupplyStartEarningFeeContentTransformer(
|
|||
stringReference(" $DOT "),
|
||||
stringReference(maxFiatFee),
|
||||
),
|
||||
minAmountFeeValue = minAmountTextReference,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ internal class YieldSupplyStopEarningComponent(
|
|||
onClick = model::onClick,
|
||||
iconResId = walletInterationIcon(params.userWallet),
|
||||
enabled = state.isPrimaryButtonEnabled,
|
||||
showProgress = state.isTransactionSending,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.yield.supply.impl.subcomponents.stopearning.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -12,11 +13,14 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
|||
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.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory
|
||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
|
||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
|
||||
|
|
@ -31,6 +35,7 @@ import com.tangem.utils.TangemBlogUrlBuilder
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
|
@ -41,6 +46,7 @@ import javax.inject.Inject
|
|||
internal class YieldSupplyStopEarningModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
|
|
@ -49,6 +55,8 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
|
|||
private val urlOpener: UrlOpener,
|
||||
private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger,
|
||||
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
|
||||
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
|
||||
|
||||
private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require()
|
||||
|
|
@ -108,6 +116,12 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
|
|||
|
||||
fun onClick() {
|
||||
val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return
|
||||
analytics.send(
|
||||
YieldSupplyAnalytics.ButtonStopEarning(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
uiState.update(YieldSupplyTransactionInProgressTransformer)
|
||||
|
||||
modelScope.launch(dispatchers.default) {
|
||||
|
|
@ -134,6 +148,16 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
|
|||
)
|
||||
},
|
||||
ifRight = {
|
||||
analytics.send(
|
||||
YieldSupplyAnalytics.FundsWithdrawn(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
yieldSupplyDeactivateUseCase(cryptoCurrency)
|
||||
modelScope.launch(NonCancellable) {
|
||||
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
|
||||
}
|
||||
params.callback.onTransactionSent()
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ internal class YieldSupplyStopEarningFeeContentTransformer(
|
|||
),
|
||||
currentNetworkFeeValue = TextReference.EMPTY,
|
||||
maxNetworkFeeValue = TextReference.EMPTY,
|
||||
minAmountFeeValue = TextReference.EMPTY,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue