Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-27 19:53:49 +05:00
parent 68f800a495
commit 2883c49098
32 changed files with 578 additions and 634 deletions

View file

@ -43,6 +43,7 @@ import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Par
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.wallet.WalletEntryComponent
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent
import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent
import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent
import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent
@ -116,6 +117,7 @@ internal class ChildFactory @Inject constructor(
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
private val kycComponentFactory: KycComponent.Factory,
private val yieldSupplyPromoComponentFactory: YieldSupplyPromoComponent.Factory,
private val yieldSupplyActiveComponentFactory: YieldSupplyActiveComponent.Factory,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
) {
@ -681,6 +683,16 @@ internal class ChildFactory @Inject constructor(
componentFactory = yieldSupplyPromoComponentFactory,
)
}
is AppRoute.YieldSupplyActive -> {
createComponentChild(
context = context,
params = YieldSupplyActiveComponent.Params(
userWalletId = route.userWalletId,
cryptoCurrency = route.cryptoCurrency,
),
componentFactory = yieldSupplyActiveComponentFactory,
)
}
}
}
}

View file

@ -446,4 +446,11 @@ sealed class AppRoute(val path: String) : Route {
val cryptoCurrency: CryptoCurrency,
val apy: String,
) : AppRoute(path = "/yield_supply_promo/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
@Serializable
data class YieldSupplyActive(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
val apy: String,
) : AppRoute(path = "/yield_supply_active/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
}

View file

@ -16,6 +16,7 @@ import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import java.math.BigDecimal
@ -31,11 +32,11 @@ internal class DefaultFeeRepository(
}
override suspend fun getEthereumFeeWithoutGas(
userWallet: UserWallet,
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Fee.Ethereum {
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWallet.walletId,
userWalletId = userWalletId,
network = cryptoCurrency.network,
)

View file

@ -7,6 +7,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
interface FeeRepository {
@ -20,5 +21,5 @@ interface FeeRepository {
transactionData: TransactionData,
): TransactionFee
suspend fun getEthereumFeeWithoutGas(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): Fee.Ethereum
suspend fun getEthereumFeeWithoutGas(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Fee.Ethereum
}

View file

@ -70,7 +70,7 @@ class YieldSupplyEstimateEnterFeeUseCase(
if (estimatedFees.estimatedGasList.isEmpty()) return null
val fee = feeRepository.getEthereumFeeWithoutGas(
userWallet = userWallet,
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrency,
)

View file

@ -2,14 +2,14 @@ package com.tangem.domain.yield.supply.usecase
import arrow.core.Either
import arrow.core.Either.Companion.catch
import com.tangem.domain.yield.supply.fixFee
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.yield.supply.YieldSupplyConst.YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT
import com.tangem.domain.yield.supply.fixFee
import java.math.BigDecimal
import java.math.RoundingMode
@ -23,16 +23,16 @@ class YieldSupplyGetCurrentFeeUseCase(
) {
suspend operator fun invoke(
userWallet: UserWallet,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): Either<Throwable, BigDecimal> = catch {
val feeWithoutGas = feeRepository.getEthereumFeeWithoutGas(userWallet, cryptoCurrencyStatus.currency)
val feeWithoutGas = feeRepository.getEthereumFeeWithoutGas(userWalletId, cryptoCurrencyStatus.currency)
val fiatRate = cryptoCurrencyStatus.value.fiatRate ?: error("Fiat rate is missing")
require(fiatRate > BigDecimal.ZERO) { "Fiat rate for token must be > 0" }
val nativeCryptoCurrency = currenciesRepository.getNetworkCoin(
userWalletId = userWallet.walletId,
userWalletId = userWalletId,
networkId = cryptoCurrencyStatus.currency.network.id,
derivationPath = cryptoCurrencyStatus.currency.network.derivationPath,
)

View file

@ -6,7 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.currency.yieldSupplyKey
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.yield.supply.YieldSupplyRepository
@ -32,7 +32,7 @@ class YieldSupplyGetMaxFeeUseCase(
) {
suspend operator fun invoke(
userWallet: UserWallet,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): Either<Throwable, YieldSupplyMaxFee> = catch {
val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token
@ -42,7 +42,7 @@ class YieldSupplyGetMaxFeeUseCase(
require(fiatRate > BigDecimal.ZERO) { "Fiat rate for token must be > 0" }
val nativeCryptoCurrency = currenciesRepository.getNetworkCoin(
userWalletId = userWallet.walletId,
userWalletId = userWalletId,
networkId = cryptoCurrencyStatus.currency.network.id,
derivationPath = cryptoCurrencyStatus.currency.network.derivationPath,
)

View file

@ -2,14 +2,14 @@ package com.tangem.domain.yield.supply.usecase
import arrow.core.Either
import arrow.core.Either.Companion.catch
import com.tangem.domain.yield.supply.fixFee
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.quote.QuoteStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.quotes.QuotesRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.yield.supply.YieldSupplyConst.YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT
import com.tangem.domain.yield.supply.fixFee
import java.math.BigDecimal
import java.math.RoundingMode
@ -20,16 +20,16 @@ class YieldSupplyMinAmountUseCase(
) {
suspend operator fun invoke(
userWallet: UserWallet,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): Either<Throwable, BigDecimal> = catch {
val feeWithoutGas = feeRepository.getEthereumFeeWithoutGas(userWallet, cryptoCurrencyStatus.currency)
val feeWithoutGas = feeRepository.getEthereumFeeWithoutGas(userWalletId, cryptoCurrencyStatus.currency)
val fiatRate = cryptoCurrencyStatus.value.fiatRate ?: error("Fiat rate is missing")
require(fiatRate > BigDecimal.ZERO) { "Fiat rate for token must be > 0" }
val nativeCryptoCurrency = currenciesRepository.getNetworkCoin(
userWalletId = userWallet.walletId,
userWalletId = userWalletId,
networkId = cryptoCurrencyStatus.currency.network.id,
derivationPath = cryptoCurrencyStatus.currency.network.derivationPath,
)

View file

@ -34,7 +34,9 @@ class YieldSupplyEstimateEnterFeeUseCaseTest {
private val blockAidGasEstimate: BlockAidGasEstimate = mockk()
private val useCase = YieldSupplyEstimateEnterFeeUseCase(feeRepository, feeErrorResolver, blockAidGasEstimate)
private val userWallet: UserWallet = mockk()
private val userWallet: UserWallet = mockk {
every { walletId } returns mockk()
}
private val cryptoCurrency: CryptoCurrency = mockk(relaxed = true) {
every { decimals } returns 18
}

View file

@ -65,7 +65,7 @@ class YieldSupplyMinAmountUseCaseTest {
val maxFeePerGas = BigInteger("158320679232")
val fee = createEip1559Fee(maxFeePerGas)
coEvery { feeRepository.getEthereumFeeWithoutGas(userWallet, token) } returns fee
coEvery { feeRepository.getEthereumFeeWithoutGas(userWallet.walletId, token) } returns fee
coEvery {
currenciesRepository.getNetworkCoin(
userWalletId = userWallet.walletId,
@ -88,7 +88,7 @@ class YieldSupplyMinAmountUseCaseTest {
),
)
val expected = expectedMinAmount(maxFeePerGas, nativeFiatRate, tokenFiatRate, token.decimals)
val result = useCase(userWallet, tokenStatus).getOrNull()
val result = useCase(userWallet.walletId, tokenStatus).getOrNull()
Truth.assertThat(result).isEqualTo(expected)
}
@ -114,7 +114,7 @@ class YieldSupplyMinAmountUseCaseTest {
),
)
val userWallet = createUserWallet()
val result = useCase(userWallet, tokenStatus)
val result = useCase(userWallet.walletId, tokenStatus)
Truth.assertThat(result.isLeft()).isTrue()
Truth.assertThat(result.leftOrNull()?.message).isEqualTo("Fiat rate is missing")
}
@ -145,7 +145,7 @@ class YieldSupplyMinAmountUseCaseTest {
val maxFeePerGas = BigInteger("158320679232")
val fee = createEip1559Fee(maxFeePerGas)
coEvery { feeRepository.getEthereumFeeWithoutGas(userWallet, token) } returns fee
coEvery { feeRepository.getEthereumFeeWithoutGas(userWallet.walletId, token) } returns fee
coEvery {
currenciesRepository.getNetworkCoin(
userWalletId = userWallet.walletId,
@ -157,7 +157,7 @@ class YieldSupplyMinAmountUseCaseTest {
coEvery {
quotesRepository.getMultiQuoteSyncOrNull(setOf(nativeCoin.id.rawCurrencyId!!))
} returns null
val result = useCase(userWallet, tokenStatus)
val result = useCase(userWallet.walletId, tokenStatus)
Truth.assertThat(result.isLeft()).isTrue()
Truth.assertThat(result.leftOrNull()?.message).isEqualTo("Quotes for native coin are unavailable")
}

View file

@ -0,0 +1,16 @@
package com.tangem.features.yield.supply.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
interface YieldSupplyActiveComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
)
interface Factory : ComponentFactory<Params, YieldSupplyActiveComponent>
}

View file

@ -0,0 +1,140 @@
package com.tangem.features.yield.supply.impl.active
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.Fade
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.active.model.YieldSupplyActiveModel
import com.tangem.features.yield.supply.impl.active.model.YieldSupplyActiveRoute
import com.tangem.features.yield.supply.impl.active.ui.YieldSupplyActiveContent
import com.tangem.features.yield.supply.impl.active.ui.YieldSupplyActiveTitle
import com.tangem.features.yield.supply.impl.chart.DefaultYieldSupplyChartComponent
import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultYieldSupplyActiveComponent @AssistedInject constructor(
@Assisted private val appComponentContext: AppComponentContext,
@Assisted private val params: YieldSupplyActiveComponent.Params,
private val appRouter: AppRouter,
) : YieldSupplyActiveComponent, AppComponentContext by appComponentContext {
private val model: YieldSupplyActiveModel = getOrCreateModel(params = params)
private val chartComponent = DefaultYieldSupplyChartComponent(
appComponentContext = child("chartComponent"),
params = DefaultYieldSupplyChartComponent.Params(
cryptoCurrency = model.cryptoCurrencyStatusFlow.value.currency as CryptoCurrency.Token,
),
)
private val bottomSheetSlot = childSlot(
key = "yieldSupplyActiveStack",
source = model.slotNavigation,
serializer = null,
handleBackButton = true,
childFactory = { configuration, factoryContext ->
createChild(
configuration,
childByContext(
componentContext = factoryContext,
),
)
},
)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
val isBalanceHidden by model.balanceHiddenFlow.collectAsStateWithLifecycle()
val slotState by bottomSheetSlot.subscribeAsState()
Column(
modifier = Modifier
.background(TangemTheme.colors.background.secondary)
.fillMaxSize()
.systemBarsPadding(),
) {
YieldSupplyActiveTitle(onCloseClick = appRouter::pop)
Box(modifier = Modifier.weight(1f)) {
YieldSupplyActiveContent(
state = state,
isBalanceHidden = isBalanceHidden,
chartComponent = chartComponent,
onReadMoreClick = model::onReadMoreClick,
)
Fade(
backgroundColor = TangemTheme.colors.background.tertiary,
modifier = Modifier.align(Alignment.BottomCenter),
)
}
SecondaryButton(
text = stringResourceSafe(R.string.yield_module_stop_earning),
onClick = model::onStopEarning,
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
}
slotState.child?.instance?.BottomSheet()
}
private fun createChild(
route: YieldSupplyActiveRoute,
factoryContext: AppComponentContext,
): ComposableBottomSheetComponent = when (route) {
YieldSupplyActiveRoute.Exit -> YieldSupplyStopEarningComponent(
appComponentContext = factoryContext,
params = YieldSupplyStopEarningComponent.Params(
userWallet = model.userWallet,
cryptoCurrencyStatusFlow = model.cryptoCurrencyStatusFlow,
callback = model,
),
)
YieldSupplyActiveRoute.Approve -> YieldSupplyApproveComponent(
appComponentContext = factoryContext,
params = YieldSupplyApproveComponent.Params(
userWallet = model.userWallet,
cryptoCurrencyStatusFlow = model.cryptoCurrencyStatusFlow,
callback = model,
),
)
}
@AssistedFactory
interface Factory : YieldSupplyActiveComponent.Factory {
override fun create(
context: AppComponentContext,
params: YieldSupplyActiveComponent.Params,
): DefaultYieldSupplyActiveComponent
}
}

View file

@ -0,0 +1,34 @@
package com.tangem.features.yield.supply.impl.active.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent
import com.tangem.features.yield.supply.impl.active.DefaultYieldSupplyActiveComponent
import com.tangem.features.yield.supply.impl.active.model.YieldSupplyActiveModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface YieldSupplyActiveBindsModule {
@Binds
@Singleton
fun provideYieldSupplyActiveComponentFactory(
impl: DefaultYieldSupplyActiveComponent.Factory,
): YieldSupplyActiveComponent.Factory
}
@Module
@InstallIn(ModelComponent::class)
internal interface YieldSupplyActiveModule {
@Binds
@IntoMap
@ClassKey(YieldSupplyActiveModel::class)
fun provideYieldSupplyActiveModel(impl: YieldSupplyActiveModel): Model
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.entity
package com.tangem.features.yield.supply.impl.active.entity
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.extensions.TextReference

View file

@ -1,10 +1,15 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.model
package com.tangem.features.yield.supply.impl.active.model
import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
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.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
@ -14,18 +19,21 @@ 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.domain.yield.supply.usecase.YieldSupplyGetCurrentFeeUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetMaxFeeUseCase
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.yield.supply.usecase.*
import com.tangem.features.yield.supply.api.YieldSupplyActiveComponent
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveComponent
import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM
import com.tangem.features.yield.supply.impl.subcomponents.active.model.transformers.YieldSupplyActiveMinAmountTransformer
import com.tangem.features.yield.supply.impl.subcomponents.active.model.transformers.YieldSupplyActiveFeeContentTransformer
import com.tangem.features.yield.supply.impl.active.entity.YieldSupplyActiveContentUM
import com.tangem.features.yield.supply.impl.active.model.transformers.YieldSupplyActiveFeeContentTransformer
import com.tangem.features.yield.supply.impl.active.model.transformers.YieldSupplyActiveMinAmountTransformer
import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.TangemBlogUrlBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.transformer.update
import kotlinx.collections.immutable.persistentListOf
@ -34,7 +42,7 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass")
@ModelScoped
internal class YieldSupplyActiveModel @Inject constructor(
paramsContainer: ParamsContainer,
@ -46,11 +54,31 @@ internal class YieldSupplyActiveModel @Inject constructor(
private val yieldSupplyGetCurrentFeeUseCase: YieldSupplyGetCurrentFeeUseCase,
private val yieldSupplyGetMaxFeeUseCase: YieldSupplyGetMaxFeeUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
) : Model() {
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val urlOpener: UrlOpener,
private val appRouter: AppRouter,
) : Model(), YieldSupplyStopEarningComponent.ModelCallback,
YieldSupplyApproveComponent.ModelCallback {
private val params: YieldSupplyActiveComponent.Params = paramsContainer.require()
private val cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow
val slotNavigation = SlotNavigation<YieldSupplyActiveRoute>()
lateinit var userWallet: UserWallet
val cryptoCurrencyStatusFlow = MutableStateFlow(
CryptoCurrencyStatus(
value = CryptoCurrencyStatus.Loading,
currency = params.cryptoCurrency,
),
)
val balanceHiddenFlow = MutableStateFlow(false)
val transactionInProgressFlow: StateFlow<Boolean>
field = MutableStateFlow(false)
private val userWalletId = params.userWalletId
private val cryptoCurrency = cryptoCurrencyStatusFlow.value.currency
private var appCurrency = AppCurrency.Default
@ -81,12 +109,12 @@ internal class YieldSupplyActiveModel @Inject constructor(
blockchain = cryptoCurrency.network.name,
),
)
subscribeOnCurrencyUpdates()
subscribeOnCurrencyStatusUpdates()
modelScope.launch(dispatchers.default) {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
val protocolBalance = yieldSupplyGetProtocolBalanceUseCase(
userWalletId = params.userWallet.walletId,
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
).getOrNull()
@ -101,32 +129,90 @@ internal class YieldSupplyActiveModel @Inject constructor(
}
}
private fun subscribeOnCurrencyUpdates() {
cryptoCurrencyStatusFlow.onEach { cryptoCurrencyStatus ->
val protocolBalance = cryptoCurrencyStatus.value.yieldSupplyStatus?.effectiveProtocolBalance
?: yieldSupplyGetProtocolBalanceUseCase(
userWalletId = params.userWallet.walletId,
cryptoCurrency = cryptoCurrency,
).getOrNull()
override fun onDismissClick() {
if (!transactionInProgressFlow.value) {
slotNavigation.dismiss()
}
}
loadApy()
loadMinAmount()
loadFees()
override fun onTransactionProgress(inProgress: Boolean) {
transactionInProgressFlow.update { inProgress }
}
uiState.update {
it.copy(
availableBalance = stringReference(
protocolBalance.format {
crypto(
symbol = AAVEV3_PREFIX + cryptoCurrency.symbol,
decimals = cryptoCurrency.decimals,
)
},
),
)
}
}.flowOn(dispatchers.default)
.launchIn(modelScope)
override fun onStopEarningTransactionSent() {
transactionInProgressFlow.update { false }
appRouter.pop()
}
override fun onTransactionSent() {
transactionInProgressFlow.update { false }
appRouter.pop()
}
fun onApprove() {
slotNavigation.activate(YieldSupplyActiveRoute.Approve)
}
fun onStopEarning() {
slotNavigation.activate(YieldSupplyActiveRoute.Exit)
}
fun onReadMoreClick() {
urlOpener.openUrl(TangemBlogUrlBuilder.YIELD_SUPPLY_HOW_IT_WORKS_URL)
}
private fun subscribeOnCurrencyStatusUpdates() {
modelScope.launch {
getUserWalletUseCase(params.userWalletId).fold(
ifRight = { wallet ->
userWallet = wallet
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = params.userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = false,
).onEach { maybeCryptoCurrency ->
maybeCryptoCurrency.fold(
ifRight = { cryptoCurrencyStatus ->
cryptoCurrencyStatusFlow.update { cryptoCurrencyStatus }
val protocolBalance =
cryptoCurrencyStatus.value.yieldSupplyStatus?.effectiveProtocolBalance
?: yieldSupplyGetProtocolBalanceUseCase(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
).getOrNull()
loadApy()
loadMinAmount()
loadFees()
uiState.update {
it.copy(
availableBalance = stringReference(
protocolBalance.format {
crypto(
symbol = AAVEV3_PREFIX + cryptoCurrency.symbol,
decimals = cryptoCurrency.decimals,
)
},
),
)
}
},
ifLeft = {
Timber.w(it.toString())
},
)
}.flowOn(dispatchers.default)
.launchIn(modelScope)
},
ifLeft = { error ->
Timber.w(error.toString())
return@launch
},
)
}
}
private fun loadApy() {
@ -152,7 +238,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
private fun loadMinAmount() {
modelScope.launch(dispatchers.default) {
yieldSupplyMinAmountUseCase(
params.userWallet,
userWalletId,
cryptoCurrencyStatusFlow.value,
).onRight { minAmount ->
uiState.update(
@ -161,7 +247,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
appCurrency = appCurrency,
minAmount = minAmount,
analyticsHandler = analyticsHandler,
onApprove = params.callback::onApprove,
onApprove = ::onApprove,
),
)
}.onLeft {
@ -180,12 +266,12 @@ internal class YieldSupplyActiveModel @Inject constructor(
val cryptoStatus = cryptoCurrencyStatusFlow.value
val currentFee = yieldSupplyGetCurrentFeeUseCase(
userWallet = params.userWallet,
userWalletId = userWalletId,
cryptoCurrencyStatus = cryptoStatus,
).getOrNull()
val maxFee = yieldSupplyGetMaxFeeUseCase(
userWallet = params.userWallet,
userWalletId = userWalletId,
cryptoCurrencyStatus = cryptoStatus,
).getOrNull()

View file

@ -1,9 +1,8 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.model
package com.tangem.features.yield.supply.impl.active.model
import com.tangem.core.decompose.navigation.Route
internal sealed class YieldSupplyActiveRoute : Route {
data object Info : YieldSupplyActiveRoute()
data object Exit : YieldSupplyActiveRoute()
data object Approve : YieldSupplyActiveRoute()
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.model.transformers
package com.tangem.features.yield.supply.impl.active.model.transformers
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.extensions.resourceReference
@ -13,7 +13,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.yield.supply.models.YieldSupplyMaxFee
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM
import com.tangem.features.yield.supply.impl.active.entity.YieldSupplyActiveContentUM
import com.tangem.utils.transformer.Transformer
import java.math.BigDecimal

View file

@ -1,4 +1,4 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.model.transformers
package com.tangem.features.yield.supply.impl.active.model.transformers
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -15,7 +15,7 @@ import com.tangem.domain.models.currency.notSuppliedAmountOrNull
import com.tangem.domain.models.currency.shouldShowNotSuppliedInfoIcon
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM
import com.tangem.features.yield.supply.impl.active.entity.YieldSupplyActiveContentUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList

View file

@ -1,4 +1,4 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.ui
package com.tangem.features.yield.supply.impl.active.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
@ -6,7 +6,9 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@ -26,6 +28,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH4
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.SpacerWMax
@ -37,7 +40,7 @@ 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.subcomponents.active.entity.YieldSupplyActiveContentUM
import com.tangem.features.yield.supply.impl.active.entity.YieldSupplyActiveContentUM
import kotlinx.collections.immutable.persistentListOf
@Composable
@ -50,10 +53,12 @@ internal fun YieldSupplyActiveContent(
) {
Column(
verticalArrangement = Arrangement.spacedBy(14.dp),
modifier = modifier.padding(
vertical = 8.dp,
horizontal = 16.dp,
),
modifier = modifier
.verticalScroll(rememberScrollState())
.padding(
vertical = 8.dp,
horizontal = 16.dp,
),
) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
@ -90,6 +95,10 @@ internal fun YieldSupplyActiveContent(
onReadMoreClick = onReadMoreClick,
)
YieldSupplyActiveTopUp(
state = state,
)
AnimatedVisibility(state.feeDescription != null) {
Text(
modifier = Modifier.padding(horizontal = 12.dp),
@ -107,6 +116,8 @@ internal fun YieldSupplyActiveContent(
color = TangemTheme.colors.text.tertiary,
)
}
SpacerH(16.dp)
}
}
@ -198,20 +209,28 @@ private fun YieldSupplyActiveMyFunds(
thickness = 0.5.dp,
color = TangemTheme.colors.stroke.primary,
)
InfoRow(
title = resourceReference(R.string.yield_module_earn_sheet_transfers_title),
info = resourceReference(R.string.yield_module_transfer_mode_automatic),
isBalanceHidden = false,
)
HorizontalDivider(
thickness = 0.5.dp,
color = TangemTheme.colors.stroke.primary,
)
InfoRow(
title = resourceReference(R.string.yield_module_earn_sheet_available_title),
info = state.availableBalance,
isBalanceHidden = isBalanceHidden,
)
}
}
@Composable
private fun YieldSupplyActiveTopUp(state: YieldSupplyActiveContentUM) {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.action)
.fillMaxWidth()
.padding(horizontal = 12.dp),
) {
InfoRow(
title = resourceReference(R.string.yield_module_earn_sheet_transfers_title),
info = resourceReference(R.string.yield_module_transfer_mode_automatic),
isBalanceHidden = false,
)
HorizontalDivider(
thickness = 0.5.dp,
color = TangemTheme.colors.stroke.primary,

View file

@ -1,24 +1,40 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.ui
package com.tangem.features.yield.supply.impl.active.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.buttons.small.TangemIconButton
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.yield.supply.impl.R
@Composable
internal fun YieldSupplyActiveTitle(onCloseClick: () -> Unit) {
Box(modifier = Modifier.fillMaxWidth()) {
Row(modifier = Modifier.fillMaxWidth()) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_close_24),
contentDescription = null,
modifier = Modifier
.padding(16.dp)
.clickable(
indication = ripple(false),
interactionSource = remember { MutableInteractionSource() },
onClick = onCloseClick,
),
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.align(Alignment.Center),
modifier = Modifier.padding(vertical = 8.dp),
) {
Text(
text = stringResourceSafe(R.string.yield_module_earn_sheet_title),
@ -30,25 +46,17 @@ internal fun YieldSupplyActiveTitle(onCloseClick: () -> Unit) {
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResourceSafe(R.string.yield_module_status_active),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
Box(
modifier = Modifier
.size(8.dp)
.background(TangemTheme.colors.icon.accent, CircleShape),
)
Text(
text = stringResourceSafe(R.string.yield_module_status_active),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier,
)
}
}
TangemIconButton(
iconRes = R.drawable.ic_close_24,
onClick = onCloseClick,
modifier = Modifier
.padding(16.dp)
.align(Alignment.CenterEnd),
)
}
}

View file

@ -4,18 +4,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
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.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.impl.main.model.YieldSupplyModel
import com.tangem.features.yield.supply.impl.main.ui.YieldSupplyBlockContent
import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveEntryComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -27,33 +20,11 @@ internal class DefaultYieldSupplyComponent @AssistedInject constructor(
private val model: YieldSupplyModel = getOrCreateModel(params = params)
private val bottomSheetSlot = childSlot(
source = model.bottomSheetNavigation,
serializer = null,
handleBackButton = false,
childFactory = { _, context -> bottomSheetChild(context) },
)
@Composable
override fun Content(modifier: Modifier) {
val yieldSupplyUM by model.uiState.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState()
YieldSupplyBlockContent(yieldSupplyUM = yieldSupplyUM, modifier = modifier)
bottomSheet.child?.instance?.BottomSheet()
}
private fun bottomSheetChild(componentContext: ComponentContext): ComposableBottomSheetComponent {
return YieldSupplyActiveEntryComponent(
appComponentContext = childByContext(componentContext),
params = YieldSupplyActiveEntryComponent.Params(
userWallet = model.userWallet,
cryptoCurrencyStatusFlow = model.cryptoCurrencyStatusFlow,
isBalanceHiddenFlow = model.isBalanceHiddenFlow,
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
}
@AssistedFactory

View file

@ -1,8 +1,7 @@
package com.tangem.features.yield.supply.impl.main.model
import com.tangem.common.routing.AppRoute
import android.os.SystemClock
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
@ -13,7 +12,6 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -60,7 +58,6 @@ internal class YieldSupplyModel @Inject constructor(
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
@DelayedWork private val coroutineScope: CoroutineScope,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase,
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
@ -74,23 +71,9 @@ internal class YieldSupplyModel @Inject constructor(
val uiState: StateFlow<YieldSupplyUM>
field = MutableStateFlow<YieldSupplyUM>(YieldSupplyUM.Initial)
val bottomSheetNavigation: SlotNavigation<Unit> = SlotNavigation()
private val handleNavigation = params.handleNavigation
private val cryptoCurrency = params.cryptoCurrency
var userWallet: UserWallet by Delegates.notNull()
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
field = MutableStateFlow(
CryptoCurrencyStatus(
currency = params.cryptoCurrency,
value = CryptoCurrencyStatus.Loading,
),
)
val isBalanceHiddenFlow: StateFlow<Boolean>
field = MutableStateFlow(false)
private var lastYieldSupplyStatus: YieldSupplyStatus? = null
private val fetchCurrencyJobHolder = JobHolder()
@ -98,32 +81,6 @@ internal class YieldSupplyModel @Inject constructor(
init {
checkIfYieldSupplyIsAvailable()
val protocolStatus = yieldSupplyRepository.getTokenProtocolStatus(
userWalletId = params.userWalletId,
cryptoCurrency = cryptoCurrency,
)
if (handleNavigation != null && protocolStatus == null) {
if (handleNavigation) {
modelScope.launch {
delay(timeMillis = 1000)
bottomSheetNavigation.activate(Unit)
}
} else {
uiState
.filterIsInstance<YieldSupplyUM.Available>()
.take(1)
.onEach { state ->
appRouter.push(
YieldSupplyPromo(
userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency,
apy = state.apy,
),
)
}.launchIn(modelScope)
}
}
}
private fun checkIfYieldSupplyIsAvailable() {
@ -131,7 +88,6 @@ internal class YieldSupplyModel @Inject constructor(
val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency)
if (isAvailable) {
subscribeOnCurrencyStatusUpdates()
subscribeOnBalanceHidden()
}
}
}
@ -149,7 +105,6 @@ internal class YieldSupplyModel @Inject constructor(
).onEach { maybeCryptoCurrency ->
maybeCryptoCurrency.fold(
ifRight = { cryptoCurrencyStatus ->
cryptoCurrencyStatusFlow.update { cryptoCurrencyStatus }
onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus)
},
ifLeft = {
@ -185,8 +140,7 @@ internal class YieldSupplyModel @Inject constructor(
}
override fun onStartEarningClick() {
val yieldSupplyUM = uiState.value
val apy = when (yieldSupplyUM) {
val apy = when (val yieldSupplyUM = uiState.value) {
is YieldSupplyUM.Available -> yieldSupplyUM.apy
is YieldSupplyUM.Content -> yieldSupplyUM.apy
else -> ""
@ -201,18 +155,18 @@ internal class YieldSupplyModel @Inject constructor(
}
override fun onActiveClick() {
bottomSheetNavigation.activate(Unit)
}
private fun subscribeOnBalanceHidden() {
getBalanceHidingSettingsUseCase()
.conflate()
.distinctUntilChanged()
.onEach {
isBalanceHiddenFlow.value = it.isBalanceHidden
}
.flowOn(dispatchers.default)
.launchIn(modelScope)
val apy = when (val yieldSupplyUM = uiState.value) {
is YieldSupplyUM.Available -> yieldSupplyUM.apy
is YieldSupplyUM.Content -> yieldSupplyUM.apy
else -> ""
}
appRouter.push(
AppRoute.YieldSupplyActive(
userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency,
apy = apy,
),
)
}
@Suppress("MaximumLineLength")
@ -379,7 +333,8 @@ internal class YieldSupplyModel @Inject constructor(
private fun computeAndApplyShowInfoIcon(cryptoCurrencyStatus: CryptoCurrencyStatus) {
modelScope.launch(dispatchers.default) {
val isShowInfoIcon = if (cryptoCurrencyStatus.hasNotSuppliedAmount()) {
val minAmount = yieldSupplyMinAmountUseCase(userWallet, cryptoCurrencyStatus).getOrNull()
val minAmount = yieldSupplyMinAmountUseCase(userWalletId = userWallet.walletId, cryptoCurrencyStatus)
.getOrNull()
if (minAmount != null) {
cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)
} else {

View file

@ -1,82 +0,0 @@
package com.tangem.features.yield.supply.impl.subcomponents.active
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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(
appComponentContext: AppComponentContext,
private val params: Params,
) : 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() {
YieldSupplyActiveTitle(onCloseClick = params.callback::onBackClick)
}
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
val isBalanceHidden by params.isBalanceHiddenFlow.collectAsStateWithLifecycle()
YieldSupplyActiveContent(
state = state,
isBalanceHidden = isBalanceHidden,
chartComponent = chartComponent,
onReadMoreClick = params.callback::onReadMoreClick,
modifier = Modifier,
)
}
@Composable
override fun Footer() {
SecondaryButton(
text = stringResourceSafe(R.string.yield_module_disable_button),
onClick = params.callback::onStopEarning,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
)
}
data class Params(
val userWallet: UserWallet,
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val isBalanceHiddenFlow: StateFlow<Boolean>,
val callback: ModelCallback,
)
interface ModelCallback {
fun onBackClick()
fun onStopEarning()
fun onApprove()
fun onReadMoreClick()
}
}

View file

@ -1,116 +0,0 @@
package com.tangem.features.yield.supply.impl.subcomponents.active
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.features.yield.supply.impl.subcomponents.active.model.YieldSupplyActiveEntryModel
import com.tangem.features.yield.supply.impl.subcomponents.active.model.YieldSupplyActiveRoute
import com.tangem.features.yield.supply.impl.subcomponents.active.ui.YieldSupplyActiveEntryBottomSheet
import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent
import kotlinx.coroutines.flow.StateFlow
internal class YieldSupplyActiveEntryComponent(
private val appComponentContext: AppComponentContext,
private val params: Params,
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<YieldSupplyActiveRoute>()
private val innerRouter = InnerRouter<YieldSupplyActiveRoute>(
stackNavigation = stackNavigation,
popCallback = { onChildBack() },
)
private val model: YieldSupplyActiveEntryModel = getOrCreateModel(params = params, router = innerRouter)
private val innerStack = childStack(
key = "yieldSupplyActiveStack",
source = stackNavigation,
serializer = null,
initialConfiguration = YieldSupplyActiveRoute.Info,
handleBackButton = true,
childFactory = { configuration, factoryContext ->
createChild(
configuration,
childByContext(
componentContext = factoryContext,
router = innerRouter,
),
)
},
)
override fun dismiss() {
params.onDismiss()
}
@Composable
override fun BottomSheet() {
val stackState by innerStack.subscribeAsState()
val isTransactionInProgress by model.isTransactionInProgressFlow.collectAsStateWithLifecycle()
YieldSupplyActiveEntryBottomSheet(
stackState = stackState,
dismissOnClickOutside = { !isTransactionInProgress },
onDismiss = ::dismiss,
)
}
private fun createChild(
route: YieldSupplyActiveRoute,
factoryContext: AppComponentContext,
): ComposableModularContentComponent = when (route) {
YieldSupplyActiveRoute.Info -> YieldSupplyActiveComponent(
appComponentContext = factoryContext,
params = YieldSupplyActiveComponent.Params(
userWallet = params.userWallet,
cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow,
isBalanceHiddenFlow = params.isBalanceHiddenFlow,
callback = model,
),
)
YieldSupplyActiveRoute.Exit -> YieldSupplyStopEarningComponent(
appComponentContext = factoryContext,
params = YieldSupplyStopEarningComponent.Params(
userWallet = params.userWallet,
cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow,
callback = model,
),
)
YieldSupplyActiveRoute.Approve -> YieldSupplyApproveComponent(
appComponentContext = factoryContext,
params = YieldSupplyApproveComponent.Params(
userWallet = params.userWallet,
cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow,
callback = model,
),
)
}
private fun onChildBack() {
if (innerStack.value.backStack.isEmpty()) {
dismiss()
} else {
stackNavigation.pop()
}
}
data class Params(
val userWallet: UserWallet,
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val isBalanceHiddenFlow: StateFlow<Boolean>,
val onDismiss: () -> Unit,
)
}

View file

@ -1,26 +0,0 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.yield.supply.impl.subcomponents.active.model.YieldSupplyActiveEntryModel
import com.tangem.features.yield.supply.impl.subcomponents.active.model.YieldSupplyActiveModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(ModelComponent::class)
internal interface YieldSupplyActiveModule {
@Binds
@IntoMap
@ClassKey(YieldSupplyActiveModel::class)
fun provideYieldSupplyActiveModel(impl: YieldSupplyActiveModel): Model
@Binds
@IntoMap
@ClassKey(YieldSupplyActiveEntryModel::class)
fun provideYieldSupplyActiveEntryModel(impl: YieldSupplyActiveEntryModel): Model
}

View file

@ -1,60 +0,0 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.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.core.navigation.url.UrlOpener
import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveComponent
import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveEntryComponent
import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent
import com.tangem.utils.TangemBlogUrlBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import javax.inject.Inject
@ModelScoped
internal class YieldSupplyActiveEntryModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
private val router: Router,
private val urlOpener: UrlOpener,
) : Model(), YieldSupplyActiveComponent.ModelCallback,
YieldSupplyStopEarningComponent.ModelCallback,
YieldSupplyApproveComponent.ModelCallback {
private val params = paramsContainer.require<YieldSupplyActiveEntryComponent.Params>()
val isTransactionInProgressFlow: StateFlow<Boolean>
field = MutableStateFlow(false)
override fun onStopEarning() {
router.push(YieldSupplyActiveRoute.Exit)
}
override fun onBackClick() {
if (!isTransactionInProgressFlow.value) {
router.pop()
}
}
override fun onTransactionProgress(inProgress: Boolean) {
isTransactionInProgressFlow.update { inProgress }
}
override fun onTransactionSent() {
isTransactionInProgressFlow.update { false }
params.onDismiss()
}
override fun onApprove() {
router.push(YieldSupplyActiveRoute.Approve)
}
override fun onReadMoreClick() {
urlOpener.openUrl(TangemBlogUrlBuilder.YIELD_SUPPLY_HOW_IT_WORKS_URL)
}
}

View file

@ -1,50 +0,0 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.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.active.model.YieldSupplyActiveRoute
@Composable
internal fun YieldSupplyActiveEntryBottomSheet(
stackState: ChildStack<YieldSupplyActiveRoute, ComposableModularContentComponent>,
dismissOnClickOutside: () -> Boolean,
onDismiss: () -> Unit,
) {
TangemModalBottomSheetWithFooter<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onDismiss,
dismissOnClickOutside = dismissOnClickOutside,
content = TangemBottomSheetConfigContent.Empty,
),
containerColor = TangemTheme.colors.background.tertiary,
title = { state ->
AnimatedContent(
stackState.active.instance,
) { currentState ->
currentState.Title()
}
},
footer = { state ->
AnimatedContent(
stackState.active.instance,
) { currentState ->
currentState.Footer()
}
},
content = { state ->
AnimatedContent(
stackState.active.instance,
) { currentState ->
currentState.Content(modifier = Modifier)
}
},
)
}

View file

@ -19,8 +19,11 @@ 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.PrimaryButtonIconEnd
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.decompose.ComposableModularContentComponent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -35,7 +38,7 @@ import kotlinx.coroutines.flow.StateFlow
internal class YieldSupplyApproveComponent(
private val appComponentContext: AppComponentContext,
private val params: Params,
) : ComposableModularContentComponent, AppComponentContext by appComponentContext {
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
private val model: YieldSupplyApproveModel = getOrCreateModel(params = params)
@ -49,52 +52,60 @@ internal class YieldSupplyApproveComponent(
),
)
@Composable
override fun Title() {
TangemModalBottomSheetTitle(
startIconRes = R.drawable.ic_back_24,
onStartClick = params.callback::onBackClick,
)
override fun dismiss() {
params.callback.onDismissClick()
}
@Composable
override fun Content(modifier: Modifier) {
override fun BottomSheet() {
val state by model.uiState.collectAsStateWithLifecycle()
YieldSupplyActionContent(
yieldSupplyActionUM = state,
onFooterClick = model::onReadMoreClick,
yieldSupplyNotificationsComponent = yieldSupplyNotificationsComponent,
modifier = modifier,
) {
Box(
modifier = Modifier
.size(56.dp)
.background(TangemTheme.colors.icon.accent.copy(0.1f), CircleShape),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_check_circle_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
modifier = Modifier
.padding(12.dp)
.size(32.dp),
TangemModalBottomSheetWithFooter<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = params.callback::onDismissClick,
content = TangemBottomSheetConfigContent.Empty,
),
containerColor = TangemTheme.colors.background.tertiary,
title = {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = params.callback::onDismissClick,
)
}
}
}
@Composable
override fun Footer() {
val state by model.uiState.collectAsStateWithLifecycle()
PrimaryButtonIconEnd(
text = stringResourceSafe(R.string.common_confirm),
onClick = model::onClick,
iconResId = walletInterationIcon(params.userWallet),
enabled = state.isPrimaryButtonEnabled,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
},
footer = {
PrimaryButtonIconEnd(
text = stringResourceSafe(R.string.common_confirm),
onClick = model::onClick,
iconResId = walletInterationIcon(params.userWallet),
enabled = state.isPrimaryButtonEnabled,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
)
},
content = {
YieldSupplyActionContent(
yieldSupplyActionUM = state,
onFooterClick = model::onReadMoreClick,
yieldSupplyNotificationsComponent = yieldSupplyNotificationsComponent,
) {
Box(
modifier = Modifier
.size(56.dp)
.background(TangemTheme.colors.icon.accent.copy(0.1f), CircleShape),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_check_circle_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
modifier = Modifier
.padding(12.dp)
.size(32.dp),
)
}
}
},
)
}
@ -105,7 +116,7 @@ internal class YieldSupplyApproveComponent(
)
interface ModelCallback {
fun onBackClick()
fun onDismissClick()
fun onTransactionProgress(inProgress: Boolean)
fun onTransactionSent()
}

View file

@ -144,7 +144,7 @@ internal class YieldSupplyApproveModel @Inject constructor(
)
yieldSupplyAlertFactory.getSendTransactionErrorState(
error = error,
popBack = params.callback::onBackClick,
popBack = params.callback::onDismissClick,
onFailedTxEmailClick = { errorMessage ->
modelScope.launch(dispatchers.default) {
yieldSupplyAlertFactory.onFailedTxEmailClick(

View file

@ -70,6 +70,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require()
private val cryptoCurrency = params.cryptoCurrency
private val userWalletId = params.userWalletId
private var minAmount: BigDecimal by Delegates.notNull()
var userWallet: UserWallet by Delegates.notNull()
@ -126,8 +127,8 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
}
}
private suspend fun calculateMinAmount(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus) {
yieldSupplyMinAmountUseCase(userWallet, cryptoCurrencyStatus).onRight {
private suspend fun calculateMinAmount(cryptoCurrencyStatus: CryptoCurrencyStatus) {
yieldSupplyMinAmountUseCase(userWalletId, cryptoCurrencyStatus).onRight {
minAmount = it
}.onLeft {
minAmount = BigDecimal.ZERO
@ -141,11 +142,11 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
}
val maxFee = yieldSupplyGetMaxFeeUseCase(userWallet, cryptoCurrencyStatus).getOrNull() ?: return
val estimatedFee = yieldSupplyGetCurrentFeeUseCase(userWallet, cryptoCurrencyStatus).getOrNull() ?: return
val maxFee = yieldSupplyGetMaxFeeUseCase(userWalletId, cryptoCurrencyStatus).getOrNull() ?: return
val estimatedFee = yieldSupplyGetCurrentFeeUseCase(userWalletId, cryptoCurrencyStatus).getOrNull() ?: return
val transactionListData = yieldSupplyStartEarningUseCase(
userWalletId = userWallet.walletId,
userWalletId = userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
maxNetworkFee = maxFee.tokenMaxFee,
).getOrNull()
@ -255,7 +256,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
private suspend fun onStartEarningTransactionSuccess(yieldSupplyFeeUM: YieldSupplyFeeUM.Content) {
yieldSupplyRepository.saveTokenProtocolStatus(
userWallet.walletId,
userWalletId,
cryptoCurrency,
YieldSupplyEnterStatus.Enter,
)
@ -282,7 +283,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
val address = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
if (address != null) {
yieldSupplyActivateUseCase(
userWalletId = userWallet.walletId,
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
address = address,
)
@ -295,7 +296,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
private fun subscribeOnCurrencyStatusUpdates() {
modelScope.launch {
getUserWalletUseCase(params.userWalletId).fold(
getUserWalletUseCase(userWalletId).fold(
ifRight = { wallet ->
userWallet = wallet
getCurrenciesStatusUpdates()
@ -325,7 +326,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
private fun getCurrenciesStatusUpdates() {
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
userWalletId = params.userWalletId,
userWalletId = userWalletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = false,
).onEach { maybeCryptoCurrency ->
@ -334,7 +335,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
onDataLoaded(
currencyStatus = cryptoCurrencyStatus,
feeCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = params.userWalletId,
userWalletId = userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus,
)
@ -352,7 +353,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
feeCryptoCurrencyStatusFlow.update { feeCurrencyStatus }
modelScope.launch {
calculateMinAmount(userWallet, currencyStatus)
calculateMinAmount(currencyStatus)
onLoadFee()
}
}

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
@ -19,8 +20,11 @@ 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.PrimaryButtonIconEnd
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.decompose.ComposableModularContentComponent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -31,10 +35,11 @@ import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSu
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.model.YieldSupplyStopEarningModel
import kotlinx.coroutines.flow.StateFlow
@Suppress("MagicNumber")
internal class YieldSupplyStopEarningComponent(
private val appComponentContext: AppComponentContext,
private val params: Params,
) : ComposableModularContentComponent, AppComponentContext by appComponentContext {
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
private val model: YieldSupplyStopEarningModel = getOrCreateModel(params = params)
@ -48,54 +53,64 @@ internal class YieldSupplyStopEarningComponent(
),
)
@Composable
override fun Title() {
TangemModalBottomSheetTitle(
startIconRes = R.drawable.ic_back_24,
onStartClick = params.callback::onBackClick,
)
override fun dismiss() {
params.callback.onDismissClick()
}
@Suppress("MagicNumber")
@Composable
override fun Content(modifier: Modifier) {
override fun BottomSheet() {
val state by model.uiState.collectAsStateWithLifecycle()
YieldSupplyActionContent(
yieldSupplyActionUM = state,
onFooterClick = model::onReadMoreClick,
yieldSupplyNotificationsComponent = yieldSupplyNotificationsComponent,
modifier = modifier,
) {
Box(
modifier = Modifier
.size(56.dp)
.background(TangemTheme.colors.icon.attention.copy(0.1f), CircleShape),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_alert_triangle_20),
contentDescription = null,
tint = TangemTheme.colors.icon.attention,
modifier = Modifier
.padding(12.dp)
.size(32.dp),
)
}
val config = remember {
TangemBottomSheetConfig(
isShown = true,
onDismissRequest = params.callback::onDismissClick,
content = TangemBottomSheetConfigContent.Empty,
)
}
}
@Composable
override fun Footer() {
val state by model.uiState.collectAsStateWithLifecycle()
PrimaryButtonIconEnd(
text = stringResourceSafe(R.string.common_confirm),
onClick = model::onClick,
iconResId = walletInterationIcon(params.userWallet),
enabled = state.isPrimaryButtonEnabled,
showProgress = state.isTransactionSending,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
TangemModalBottomSheetWithFooter<TangemBottomSheetConfigContent.Empty>(
config = config,
containerColor = TangemTheme.colors.background.tertiary,
title = {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = params.callback::onDismissClick,
)
},
footer = {
PrimaryButtonIconEnd(
text = stringResourceSafe(R.string.common_confirm),
onClick = model::onClick,
iconResId = walletInterationIcon(params.userWallet),
enabled = state.isPrimaryButtonEnabled,
showProgress = state.isTransactionSending,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
)
},
content = {
YieldSupplyActionContent(
yieldSupplyActionUM = state,
onFooterClick = model::onReadMoreClick,
yieldSupplyNotificationsComponent = yieldSupplyNotificationsComponent,
) {
Box(
modifier = Modifier
.size(56.dp)
.background(TangemTheme.colors.icon.attention.copy(0.1f), CircleShape),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_alert_triangle_20),
contentDescription = null,
tint = TangemTheme.colors.icon.attention,
modifier = Modifier
.padding(12.dp)
.size(32.dp),
)
}
}
},
)
}
@ -106,8 +121,8 @@ internal class YieldSupplyStopEarningComponent(
)
interface ModelCallback {
fun onBackClick()
fun onDismissClick()
fun onTransactionProgress(inProgress: Boolean)
fun onTransactionSent()
fun onStopEarningTransactionSent()
}
}

View file

@ -154,7 +154,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
)
yieldSupplyAlertFactory.getSendTransactionErrorState(
error = error,
popBack = params.callback::onBackClick,
popBack = params.callback::onDismissClick,
onFailedTxEmailClick = { errorMessage ->
modelScope.launch(dispatchers.default) {
yieldSupplyAlertFactory.onFailedTxEmailClick(
@ -203,7 +203,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
}
modelScope.launch {
params.callback.onTransactionSent()
params.callback.onStopEarningTransactionSent()
}
}