Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-16 16:20:57 +05:00
parent fc93f3649c
commit 687d2e1423
12 changed files with 762 additions and 10 deletions

View file

@ -1,13 +1,16 @@
package com.tangem.domain.yield.supply.usecase
import arrow.core.Either
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.error.FeeErrorResolver
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.utils.extensions.isSingleItem
class YieldSupplyEstimateEnterFeeUseCase(
private val feeRepository: FeeRepository,
@ -18,19 +21,36 @@ class YieldSupplyEstimateEnterFeeUseCase(
cryptoCurrency: CryptoCurrency,
transactionDataList: List<TransactionData.Uncompiled>,
): Either<GetFeeError, List<TransactionData.Uncompiled>> = Either.catch {
transactionDataList.mapIndexed { index, transaction ->
val fee = feeRepository.calculateFee(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionData = transaction,
).normal
val withCalculatedFee = transactionDataList.filter {
(it.extras as? EthereumTransactionExtras)?.callData !is EthereumYieldSupplyEnterCallData
}.map { transaction ->
transaction.copy(
fee = feeRepository.calculateFee(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionData = transaction,
).normal,
)
}
if (index == transactionDataList.lastIndex) { // todo yield supply replace with check for contract
transaction.copy(fee = fee.fixFee(cryptoCurrency))
val withEstimatedCalculatedFee = transactionDataList.filter {
(it.extras as? EthereumTransactionExtras)?.callData is EthereumYieldSupplyEnterCallData
}.map { transaction ->
if (transactionDataList.isSingleItem()) {
transaction.copy(
fee = feeRepository.calculateFee(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionData = transaction,
).normal,
)
} else {
transaction.copy(fee = fee)
transaction.copy(fee = withCalculatedFee.first().fee?.fixFee(cryptoCurrency))
}
}
// Transactions order must be preserved
withCalculatedFee + withEstimatedCalculatedFee
}.mapLeft(feeErrorResolver::resolve)
private fun Fee.fixFee(cryptoCurrency: CryptoCurrency) = when (this) {

View file

@ -0,0 +1,26 @@
package com.tangem.features.yield.supply.impl.common.entity
import androidx.compose.runtime.Immutable
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
@Immutable
internal sealed class YieldSupplyFeeUM {
data object Loading : YieldSupplyFeeUM()
data object Error : YieldSupplyFeeUM()
data class Content(
val transactionDataList: ImmutableList<TransactionData.Uncompiled>,
val feeValue: TextReference,
) : YieldSupplyFeeUM()
}
internal data class YieldSupplyActionUM(
val title: TextReference,
val subtitle: TextReference,
val footer: TextReference,
val currencyIconState: CurrencyIconState,
val yieldSupplyFeeUM: YieldSupplyFeeUM,
val isPrimaryButtonEnabled: Boolean,
)

View file

@ -0,0 +1,147 @@
package com.tangem.features.yield.supply.impl.common.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
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.*
import com.tangem.core.ui.components.containers.FooterContainer
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
import com.tangem.utils.StringsSigns
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun YieldSupplyActionContent(
yieldSupplyActionUM: YieldSupplyActionUM,
modifier: Modifier = Modifier,
iconContent: @Composable ColumnScope.() -> Unit,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
) {
iconContent()
SpacerH24()
Text(
text = yieldSupplyActionUM.title.resolveReference(),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
)
SpacerH8()
Text(
text = yieldSupplyActionUM.subtitle.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 16.dp),
)
SpacerH24()
FooterContainer(
footer = yieldSupplyActionUM.footer,
paddingValues = PaddingValues(
start = 12.dp,
end = 12.dp,
top = 8.dp,
),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(14.dp))
.background(TangemTheme.colors.background.action)
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
Text(
text = stringResourceSafe(R.string.common_network_fee_title),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
SpacerWMax()
when (val fee = yieldSupplyActionUM.yieldSupplyFeeUM) {
is YieldSupplyFeeUM.Content -> Text(
text = fee.feeValue.resolveReference(),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary,
)
YieldSupplyFeeUM.Error -> Text(
text = stringResourceSafe(R.string.common_fee_error),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.attention,
)
YieldSupplyFeeUM.Loading -> {
TextShimmer(
style = TangemTheme.typography.body1,
text = stringResourceSafe(R.string.common_fee_error),
)
}
}
}
}
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 344)
@Preview(showBackground = true, widthDp = 344, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun YieldSupplyActionContent_Preview(
@PreviewParameter(YieldSupplyActionContentPreviewProvider::class) params: YieldSupplyActionUM,
) {
TangemThemePreview {
YieldSupplyActionContent(
yieldSupplyActionUM = params,
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
) {
CurrencyIcon(
state = CurrencyIconState.Loading,
shouldDisplayNetwork = false,
iconSize = 48.dp,
modifier = Modifier.size(48.dp),
)
}
}
}
private class YieldSupplyActionContentPreviewProvider : PreviewParameterProvider<YieldSupplyActionUM> {
override val values: Sequence<YieldSupplyActionUM>
get() = sequenceOf(
YieldSupplyActionUM(
title = resourceReference(R.string.yield_module_start_earning),
subtitle = resourceReference(
R.string.yield_module_start_earning_sheet_description,
wrappedList("USDT"),
),
footer = combinedReference(
resourceReference(R.string.yield_module_start_earning_sheet_next_deposits),
stringReference(StringsSigns.WHITE_SPACE),
resourceReference(R.string.yield_module_start_earning_sheet_fee_policy),
),
currencyIconState = CurrencyIconState.Loading,
yieldSupplyFeeUM = YieldSupplyFeeUM.Content(
transactionDataList = persistentListOf(),
feeValue = stringReference("0.00020 ETH • \$0.99"),
),
isPrimaryButtonEnabled = false,
),
)
}
// endregion

View file

@ -0,0 +1,107 @@
package com.tangem.features.yield.supply.impl.subcomponents.startearning
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.vectorResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.decompose.ComposableModularContentComponent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.common.ui.YieldSupplyActionContent
import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.YieldSupplyStartEarningModel
internal class YieldSupplyStartEarningComponent(
private val appComponentContext: AppComponentContext,
private val params: Params,
) : ComposableModularContentComponent, AppComponentContext by appComponentContext {
private val model: YieldSupplyStartEarningModel = getOrCreateModel(params = params)
@Composable
override fun Title() {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = params.callback::onBackClick,
)
}
@Suppress("MagicNumber")
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
YieldSupplyActionContent(
yieldSupplyActionUM = state,
modifier = modifier,
) {
Box(
modifier = Modifier
.width(80.dp)
.padding(vertical = 1.dp),
) {
CurrencyIcon(
state = state.currencyIconState,
shouldDisplayNetwork = false,
iconSize = 48.dp,
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(48.dp))
.align(Alignment.CenterStart),
)
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_aave_36),
contentDescription = null,
modifier = Modifier
.background(TangemTheme.colors.background.tertiary, RoundedCornerShape(51.dp))
.padding(3.dp)
.size(48.dp)
.align(Alignment.CenterEnd),
)
}
}
}
@Composable
override fun Footer() {
val state by model.uiState.collectAsStateWithLifecycle()
PrimaryButtonIconEnd(
text = stringResourceSafe(R.string.yield_module_start_earning),
onClick = model::onClick,
enabled = state.isPrimaryButtonEnabled,
iconResId = R.drawable.ic_tangem_24,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
)
}
data class Params(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
val callback: ModelCallback,
)
interface ModelCallback {
fun onBackClick()
fun onFeePolicyClick()
fun onTransactionSent()
}
}

View file

@ -0,0 +1,94 @@
package com.tangem.features.yield.supply.impl.subcomponents.startearning
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableModularContentComponent
import com.tangem.core.ui.decompose.getEmptyComposableModularContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.YieldSupplyStartEarningEntryModel
import com.tangem.features.yield.supply.impl.subcomponents.startearning.ui.YieldSupplyStartEarningBottomSheet
internal class YieldSupplyStartEarningEntryComponent(
private val appComponentContext: AppComponentContext,
private val params: Params,
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<YieldSupplyStartEarningRoute>()
private val innerRouter = InnerRouter<YieldSupplyStartEarningRoute>(
stackNavigation = stackNavigation,
popCallback = { onChildBack() },
)
private val model: YieldSupplyStartEarningEntryModel = getOrCreateModel(params = params, router = innerRouter)
private val innerStack = childStack(
key = "startEarningStack",
source = stackNavigation,
serializer = null,
initialConfiguration = YieldSupplyStartEarningRoute.Action,
handleBackButton = true,
childFactory = { configuration, factoryContext ->
createChild(
configuration,
childByContext(
componentContext = factoryContext,
router = innerRouter,
),
)
},
)
override fun dismiss() {
params.onDismiss(false)
}
@Composable
override fun BottomSheet() {
val stackState by innerStack.subscribeAsState()
YieldSupplyStartEarningBottomSheet(
stackState = stackState,
onDismiss = ::dismiss,
)
}
private fun createChild(
route: YieldSupplyStartEarningRoute,
factoryContext: AppComponentContext,
): ComposableModularContentComponent = when (route) {
YieldSupplyStartEarningRoute.FeePolicy -> getEmptyComposableModularContentComponent() // todo fee policy
YieldSupplyStartEarningRoute.Action -> YieldSupplyStartEarningComponent(
appComponentContext = factoryContext,
params = YieldSupplyStartEarningComponent.Params(
userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency,
callback = model,
),
)
}
private fun onChildBack() {
if (innerStack.value.backStack.isEmpty()) {
dismiss()
} else {
stackNavigation.pop()
}
}
data class Params(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
val onDismiss: (Boolean) -> Unit,
)
}

View file

@ -0,0 +1,8 @@
package com.tangem.features.yield.supply.impl.subcomponents.startearning
import com.tangem.core.decompose.navigation.Route
internal sealed class YieldSupplyStartEarningRoute : Route {
data object Action : YieldSupplyStartEarningRoute()
data object FeePolicy : YieldSupplyStartEarningRoute()
}

View file

@ -0,0 +1,26 @@
package com.tangem.features.yield.supply.impl.subcomponents.startearning.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.YieldSupplyStartEarningEntryModel
import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.YieldSupplyStartEarningModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(ModelComponent::class)
internal interface YieldSupplyStartEarningModule {
@Binds
@IntoMap
@ClassKey(YieldSupplyStartEarningModel::class)
fun provideYieldSupplyStartEarningModel(impl: YieldSupplyStartEarningModel): Model
@Binds
@IntoMap
@ClassKey(YieldSupplyStartEarningEntryModel::class)
fun provideYieldSupplyStartEarningEntryModel(impl: YieldSupplyStartEarningEntryModel): Model
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.yield.supply.impl.subcomponents.startearning.entity
import androidx.compose.runtime.Immutable
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
@Immutable
internal sealed class YieldSupplyStartEarningContentUM : TangemBottomSheetConfigContent {
data class Main(
val currencyIconState: CurrencyIconState,
val fee: Fee?,
) : YieldSupplyStartEarningContentUM()
data class FeePolicy(
val title: TextReference,
) : YieldSupplyStartEarningContentUM()
}

View file

@ -0,0 +1,33 @@
package com.tangem.features.yield.supply.impl.subcomponents.startearning.model
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.decompose.navigation.Router
import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningComponent
import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningEntryComponent
import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningRoute
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
@ModelScoped
internal class YieldSupplyStartEarningEntryModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
paramsContainer: ParamsContainer,
) : Model(), YieldSupplyStartEarningComponent.ModelCallback {
private val params = paramsContainer.require<YieldSupplyStartEarningEntryComponent.Params>()
override fun onBackClick() {
router.pop()
}
override fun onFeePolicyClick() {
router.push(YieldSupplyStartEarningRoute.FeePolicy)
}
override fun onTransactionSent() {
params.onDismiss(true)
}
}

View file

@ -0,0 +1,222 @@
package com.tangem.features.yield.supply.impl.subcomponents.startearning.model
import arrow.core.getOrElse
import com.tangem.blockchain.common.TransactionSender
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.combinedReference
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.fiat
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.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase
import com.tangem.features.yield.supply.impl.R
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.startearning.YieldSupplyStartEarningComponent
import com.tangem.utils.StringsSigns.DOT
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import java.math.BigDecimal
import javax.inject.Inject
import kotlin.properties.Delegates
@Suppress("LongParameterList")
@ModelScoped
internal class YieldSupplyStartEarningModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
private val sendTransactionUseCase: SendTransactionUseCase,
private val yieldSupplyStartEarningUseCase: YieldSupplyStartEarningUseCase,
private val yieldSupplyEstimateEnterFeeUseCase: YieldSupplyEstimateEnterFeeUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
) : Model() {
private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require()
private val cryptoCurrency = params.cryptoCurrency
private var userWallet: UserWallet by Delegates.notNull()
private val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
field = MutableStateFlow(
CryptoCurrencyStatus(
currency = cryptoCurrency,
value = CryptoCurrencyStatus.Loading,
),
)
private val feeCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
field = MutableStateFlow(
CryptoCurrencyStatus(
currency = cryptoCurrency,
value = CryptoCurrencyStatus.Loading,
),
)
val uiState: StateFlow<YieldSupplyActionUM>
field: MutableStateFlow<YieldSupplyActionUM> = MutableStateFlow(
YieldSupplyActionUM(
title = resourceReference(R.string.yield_module_start_earning),
subtitle = resourceReference(
R.string.yield_module_start_earning_sheet_description,
wrappedList(cryptoCurrency.symbol),
),
footer = combinedReference(
resourceReference(R.string.yield_module_start_earning_sheet_next_deposits),
resourceReference(R.string.yield_module_start_earning_sheet_fee_policy),
),
currencyIconState = CryptoCurrencyToIconStateConverter().convert(params.cryptoCurrency),
yieldSupplyFeeUM = YieldSupplyFeeUM.Loading,
isPrimaryButtonEnabled = false,
),
)
private val cryptoCurrencyStatus
get() = cryptoCurrencyStatusFlow.value
private var appCurrency = AppCurrency.Default
init {
modelScope.launch {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
subscribeOnCurrencyStatusUpdates()
}
}
private suspend fun onLoadFee() {
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) return
val transactionListData = yieldSupplyStartEarningUseCase(
userWalletId = userWallet.walletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: return
val updatedTransactionList = yieldSupplyEstimateEnterFeeUseCase.invoke(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionDataList = transactionListData,
).getOrNull() ?: return
val feeSum = updatedTransactionList.sumOf {
it.fee?.amount?.value ?: BigDecimal.ZERO
}
val crypto = feeSum.format { crypto(feeCryptoCurrencyStatusFlow.value.currency) }
val fiatFeeValue = cryptoCurrencyStatus.value.fiatRate?.let { rate ->
feeSum.multiply(rate)
}
val fiat = fiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) }
uiState.update {
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
} else {
it.copy(
isPrimaryButtonEnabled = true, // todo yield supply check for notifications
yieldSupplyFeeUM = YieldSupplyFeeUM.Content(
transactionDataList = updatedTransactionList.toPersistentList(),
feeValue = combinedReference(
stringReference(crypto),
stringReference(" $DOT "),
stringReference(fiat),
),
),
)
}
}
}
fun onClick() {
val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return
uiState.update { it.copy(isPrimaryButtonEnabled = false) }
modelScope.launch(dispatchers.default) {
sendTransactionUseCase.invoke(
txsData = yieldSupplyFeeUM.transactionDataList,
userWallet = userWallet,
network = cryptoCurrency.network,
sendMode = TransactionSender.MultipleTransactionSendMode.DEFAULT,
).fold(
ifLeft = {
Timber.e(it.toString())
uiState.update { it.copy(isPrimaryButtonEnabled = true) }
},
ifRight = {
params.callback.onTransactionSent()
},
)
}
}
private fun subscribeOnCurrencyStatusUpdates() {
modelScope.launch {
getUserWalletUseCase(params.userWalletId).fold(
ifRight = { wallet ->
userWallet = wallet
getCurrenciesStatusUpdates()
},
ifLeft = {
Timber.w(it.toString())
// showAlertError() todo yield supply error alert
return@launch
},
)
}
}
private fun getCurrenciesStatusUpdates() {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = params.userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = false,
).onEach { maybeCryptoCurrency ->
maybeCryptoCurrency.fold(
ifRight = { cryptoCurrencyStatus ->
onDataLoaded(
currencyStatus = cryptoCurrencyStatus,
feeCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = params.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus,
)
},
ifLeft = {
// todo yield supply error
// sendConfirmAlertFactory.getGenericErrorState(
// onFailedTxEmailClick = {
// onFailedTxEmailClick(it.toString())
// },
// popBack = router::pop,
// )
},
)
}.launchIn(modelScope)
}
private fun onDataLoaded(currencyStatus: CryptoCurrencyStatus, feeCurrencyStatus: CryptoCurrencyStatus) {
cryptoCurrencyStatusFlow.update { currencyStatus }
feeCryptoCurrencyStatusFlow.update { feeCurrencyStatus }
modelScope.launch {
onLoadFee()
}
}
}

View file

@ -0,0 +1,48 @@
package com.tangem.features.yield.supply.impl.subcomponents.startearning.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
import com.tangem.core.ui.decompose.ComposableModularContentComponent
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningRoute
@Composable
internal fun YieldSupplyStartEarningBottomSheet(
stackState: ChildStack<YieldSupplyStartEarningRoute, ComposableModularContentComponent>,
onDismiss: () -> Unit,
) {
TangemModalBottomSheetWithFooter<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onDismiss,
content = TangemBottomSheetConfigContent.Empty,
),
containerColor = TangemTheme.colors.background.tertiary,
title = {
AnimatedContent(
stackState.active.instance,
) { currentState ->
currentState.Title()
}
},
footer = {
AnimatedContent(
stackState.active.instance,
) { currentState ->
currentState.Footer()
}
},
content = {
AnimatedContent(
stackState.active.instance,
) { currentState ->
currentState.Content(modifier = Modifier)
}
},
)
}

View file

@ -5,7 +5,8 @@
# https://github.com/tangem/tangem-sdk-android/
# https://github.com/tangem/vico
tangemBlockchainSdk = "develop-1229"
tangemBlockchainSdk = "develop-1230"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-561"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
tangemVico = "2.0.0-alpha.25-tangem12"