Updated on 2026-08-14
This commit is contained in:
parent
68f800a495
commit
2883c49098
32 changed files with 578 additions and 634 deletions
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}")
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ class YieldSupplyEstimateEnterFeeUseCase(
|
|||
if (estimatedFees.estimatedGasList.isEmpty()) return null
|
||||
|
||||
val fee = feeRepository.getEthereumFeeWithoutGas(
|
||||
userWallet = userWallet,
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue