Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-17 13:01:11 +05:00
parent 396e2cf3e1
commit dcbe3fefb3
17 changed files with 588 additions and 5 deletions

View file

@ -102,6 +102,7 @@ dependencies {
implementation(projects.features.txhistory.api)
implementation(projects.features.sendV2.api)
implementation(projects.features.tokenRecieve.api)
implementation(projects.features.yieldSupply.api)
implementation(deps.decompose.ext.compose)

View file

@ -5,8 +5,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.core.decompose.context.AppComponentContext
@ -23,6 +23,7 @@ import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.tokenreceive.TokenReceiveComponent
import com.tangem.features.txhistory.component.TxHistoryComponent
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -34,6 +35,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
tokenMarketBlockComponentFactory: TokenMarketBlockComponent.Factory,
txHistoryComponentFactory: TxHistoryComponent.Factory,
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
yieldSupplyComponentFactory: YieldSupplyComponent.Factory,
) : TokenDetailsComponent, AppComponentContext by appComponentContext {
private val model: TokenDetailsModel = getOrCreateModel(params)
@ -67,6 +69,14 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
)
}
private val yieldSupplyComponent = yieldSupplyComponentFactory.create(
context = child("tokenYieldSupplyComponent"),
params = YieldSupplyComponent.Params(
userWalletId = params.userWalletId,
cryptoCurrency = params.currency,
),
)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
@ -76,6 +86,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
state = state,
tokenMarketBlockComponent = tokenMarketBlockComponent,
txHistoryComponent = txHistoryComponent,
yieldSupplyComponent = yieldSupplyComponent,
)
bottomSheet.child?.instance?.BottomSheet()
}

View file

@ -178,6 +178,7 @@ internal object TokenDetailsPreviewData {
bottomSheetConfig = null,
isBalanceHidden = false,
isMarketPriceAvailable = false,
isYieldSupplyFeatureEnabled = false,
)
val tokenDetailsState_2 = TokenDetailsState(
@ -203,6 +204,7 @@ internal object TokenDetailsPreviewData {
bottomSheetConfig = null,
isBalanceHidden = false,
isMarketPriceAvailable = true,
isYieldSupplyFeatureEnabled = true,
)
val tokenDetailsState_3 = tokenDetailsState_2.copy(stakingBlocksState = stakingBalanceBlock)

View file

@ -81,6 +81,7 @@ import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.tokendetails.impl.R
import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle
import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import com.tangem.utils.extensions.isZero
@ -137,6 +138,7 @@ internal class TokenDetailsModel @Inject constructor(
private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle,
private val getViewedTokenReceiveWarningUseCase: GetViewedTokenReceiveWarningUseCase,
private val getEnsNameUseCase: GetEnsNameUseCase,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
) : Model(), TokenDetailsClickIntents {
private val params = paramsContainer.require<TokenDetailsComponent.Params>()
@ -170,6 +172,7 @@ internal class TokenDetailsModel @Inject constructor(
networkHasDerivationUseCase = networkHasDerivationUseCase,
getUserWalletUseCase = getUserWalletUseCase,
userWalletId = userWalletId,
yieldSupplyFeatureToggles = yieldSupplyFeatureToggles,
)
private val expressStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {

View file

@ -23,4 +23,5 @@ internal data class TokenDetailsState(
val bottomSheetConfig: TangemBottomSheetConfig?,
val isBalanceHidden: Boolean,
val isMarketPriceAvailable: Boolean,
val isYieldSupplyFeatureEnabled: Boolean,
)

View file

@ -18,6 +18,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDeta
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton
import com.tangem.features.tokendetails.impl.R
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList
@ -29,6 +30,7 @@ internal class TokenDetailsSkeletonStateConverter(
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val userWalletId: UserWalletId,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
) : Converter<CryptoCurrency, TokenDetailsState> {
private val iconStateConverter by lazy { TokenDetailsIconStateConverter() }
@ -69,6 +71,7 @@ internal class TokenDetailsSkeletonStateConverter(
bottomSheetConfig = null,
isBalanceHidden = true,
isMarketPriceAvailable = value.id.rawCurrencyId != null,
isYieldSupplyFeatureEnabled = yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled,
)
}

View file

@ -31,6 +31,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig
import com.tangem.features.tokendetails.impl.R
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.utils.Provider
import kotlinx.collections.immutable.toImmutableList
@ -43,6 +44,7 @@ internal class TokenDetailsStateFactory(
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val userWalletId: UserWalletId,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
) {
private val skeletonStateConverter by lazy {
@ -51,6 +53,7 @@ internal class TokenDetailsStateFactory(
networkHasDerivationUseCase = networkHasDerivationUseCase,
getUserWalletUseCase = getUserWalletUseCase,
userWalletId = userWalletId,
yieldSupplyFeatureToggles = yieldSupplyFeatureToggles,
)
}

View file

@ -40,6 +40,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.s
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.txhistory.component.TxHistoryComponent
import com.tangem.features.txhistory.entity.TxHistoryUM
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -50,6 +51,7 @@ internal fun TokenDetailsScreen(
state: TokenDetailsState,
tokenMarketBlockComponent: TokenMarketBlockComponent?,
txHistoryComponent: TxHistoryComponent,
yieldSupplyComponent: YieldSupplyComponent,
) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
@ -145,6 +147,12 @@ internal fun TokenDetailsScreen(
)
}
if (state.isYieldSupplyFeatureEnabled) {
item {
yieldSupplyComponent.Content(modifier = itemModifier)
}
}
expressTransactionsItems(
expressTxs = state.expressTxsToDisplay,
modifier = itemModifier,
@ -190,6 +198,11 @@ private fun TokenDetailsScreenPreview(
override fun LazyListScope.txHistoryContent(listState: LazyListState, state: TxHistoryUM) = Unit
},
yieldSupplyComponent = object : YieldSupplyComponent {
@Composable
override fun Content(modifier: Modifier) {
}
},
)
}
}

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 YieldSupplyComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
)
interface Factory : ComponentFactory<Params, YieldSupplyComponent>
}

View file

@ -0,0 +1,65 @@
package com.tangem.features.yield.supply.impl.main
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
internal class DefaultYieldSupplyComponent @AssistedInject constructor(
@Assisted private val appComponentContext: AppComponentContext,
@Assisted private val params: YieldSupplyComponent.Params,
) : YieldSupplyComponent, AppComponentContext by appComponentContext {
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,
onDismiss = model.bottomSheetNavigation::dismiss,
),
)
}
@AssistedFactory
interface Factory : YieldSupplyComponent.Factory {
override fun create(
context: AppComponentContext,
params: YieldSupplyComponent.Params,
): DefaultYieldSupplyComponent
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.features.yield.supply.impl.main.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.impl.main.DefaultYieldSupplyComponent
import com.tangem.features.yield.supply.impl.main.model.YieldSupplyModel
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 YieldSupplyBindsModule {
@Binds
@Singleton
fun provideYieldSupplyComponentFactory(impl: DefaultYieldSupplyComponent.Factory): YieldSupplyComponent.Factory
}
@Module
@InstallIn(ModelComponent::class)
internal interface YieldSupplyModelModule {
@Binds
@IntoMap
@ClassKey(YieldSupplyModel::class)
fun provideYieldSupplyModel(impl: YieldSupplyModel): Model
}

View file

@ -0,0 +1,24 @@
package com.tangem.features.yield.supply.impl.main.entity
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
@Immutable
internal sealed class YieldSupplyUM {
data class Initial(
val title: TextReference,
val onClick: () -> Unit,
) : YieldSupplyUM()
data object Loading : YieldSupplyUM()
data class Content(
val rewardsBalance: TextReference,
val rewardsApy: TextReference,
val onClick: () -> Unit,
val isAllowedToSpend: Boolean,
) : YieldSupplyUM()
data object Processing : YieldSupplyUM()
}

View file

@ -0,0 +1,6 @@
package com.tangem.features.yield.supply.impl.main.model
interface YieldSupplyClickIntents {
fun onStartEarningClick()
fun onActiveClick()
}

View file

@ -0,0 +1,124 @@
package com.tangem.features.yield.supply.impl.main.model
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.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
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.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import kotlin.properties.Delegates
@ModelScoped
internal class YieldSupplyModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val appRouter: AppRouter,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
) : Model(), YieldSupplyClickIntents {
private val params = paramsContainer.require<YieldSupplyComponent.Params>()
val uiState: StateFlow<YieldSupplyUM>
field = MutableStateFlow<YieldSupplyUM>(YieldSupplyUM.Loading)
val bottomSheetNavigation: SlotNavigation<Unit> = SlotNavigation()
private val cryptoCurrency = params.cryptoCurrency
var userWallet: UserWallet by Delegates.notNull()
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
field = MutableStateFlow(
CryptoCurrencyStatus(
currency = params.cryptoCurrency,
value = CryptoCurrencyStatus.Loading,
),
)
init {
subscribeOnCurrencyStatusUpdates()
}
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 }
onDataLoaded(cryptoCurrencyStatus)
},
ifLeft = {
// todo error
},
)
}.launchIn(modelScope)
},
ifLeft = {
Timber.w(it.toString())
return@launch
},
)
}
}
override fun onStartEarningClick() {
appRouter.push(
YieldSupplyPromo(
userWalletId = params.userWalletId,
cryptoCurrency = params.cryptoCurrency,
),
)
}
override fun onActiveClick() {
bottomSheetNavigation.activate(Unit)
}
private fun onDataLoaded(cryptoCurrencyStatus: CryptoCurrencyStatus) {
val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
// todo yield supply add processing state
val yieldSupplyUM = when {
yieldSupplyStatus?.isActive == true ->
YieldSupplyUM.Content(
rewardsBalance = TextReference.EMPTY,
rewardsApy = TextReference.EMPTY,
onClick = ::onActiveClick,
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
)
else -> YieldSupplyUM.Initial(
title = resourceReference(
id = R.string.yield_module_token_details_earn_notification_title,
formatArgs = wrappedList("5.1"),
),
onClick = ::onStartEarningClick,
)
}
uiState.update { yieldSupplyUM }
}
}

View file

@ -0,0 +1,254 @@
package com.tangem.features.yield.supply.impl.main.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
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.main.entity.YieldSupplyUM
import com.tangem.utils.StringsSigns
@Composable
internal fun YieldSupplyBlockContent(yieldSupplyUM: YieldSupplyUM, modifier: Modifier = Modifier) {
AnimatedContent(
targetState = yieldSupplyUM,
modifier = modifier,
) { supplyUM ->
when (supplyUM) {
is YieldSupplyUM.Initial -> SupplyInitial(supplyUM)
YieldSupplyUM.Loading -> SupplyLoading()
is YieldSupplyUM.Content -> SupplyContent(supplyUM)
YieldSupplyUM.Processing -> SupplyProcessing()
}
}
}
@Composable
private fun SupplyInitial(supplyUM: YieldSupplyUM.Initial) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.padding(12.dp),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_analytics_up_24),
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
modifier = Modifier
.background(TangemTheme.colors.icon.accent.copy(alpha = 0.1f), CircleShape)
.padding(6.dp)
.size(24.dp),
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = supplyUM.title.resolveReference(),
style = TangemTheme.typography.button,
color = TangemTheme.colors.text.primary1,
)
Text(
text = stringResourceSafe(R.string.yield_module_token_details_earn_notification_description),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
SecondaryButton(
text = stringResourceSafe(R.string.common_learn_more),
onClick = supplyUM.onClick,
size = TangemButtonSize.WideAction,
modifier = Modifier.fillMaxWidth(),
)
}
}
@Composable
private fun SupplyContent(supplyUM: YieldSupplyUM.Content) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.clickable(onClick = supplyUM.onClick)
.padding(12.dp),
) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.weight(1f),
) {
Text(
text = stringResourceSafe(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = supplyUM.rewardsBalance.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
Text(
text = StringsSigns.DOT,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.tertiary,
)
Text(
text = supplyUM.rewardsApy.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.tertiary,
)
}
}
SpacerW8()
AnimatedVisibility(supplyUM.isAllowedToSpend.not()) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_alert_triangle_20),
contentDescription = null,
tint = TangemTheme.colors.icon.attention,
)
}
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
modifier = Modifier.size(20.dp),
)
}
}
@Composable
private fun SupplyProcessing() {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.padding(12.dp),
) {
Text(
text = stringResourceSafe(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = stringResourceSafe(
R.string.yield_module_token_details_earn_notification_processing,
),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary,
)
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
color = TangemTheme.colors.icon.accent,
strokeWidth = 2.dp,
)
}
}
}
@Composable
private fun SupplyLoading() {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.padding(12.dp),
) {
Text(
text = stringResourceSafe(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
TextShimmer(
style = TangemTheme.typography.subtitle2,
text = stringResourceSafe(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
)
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun YieldSupplyBlockContent_Preview(@PreviewParameter(PreviewProvider::class) params: YieldSupplyUM) {
TangemThemePreview {
YieldSupplyBlockContent(params)
}
}
private class PreviewProvider : PreviewParameterProvider<YieldSupplyUM> {
override val values: Sequence<YieldSupplyUM>
get() = sequenceOf(
YieldSupplyUM.Initial(
title = TextReference.Res(
R.string.yield_module_token_details_earn_notification_title,
wrappedList("5.1"),
),
onClick = {},
),
YieldSupplyUM.Content(
rewardsBalance = stringReference("1 USDT"),
rewardsApy = stringReference("5.1 % APY"),
onClick = {},
isAllowedToSpend = false,
),
YieldSupplyUM.Content(
rewardsBalance = stringReference("1 USDT"),
rewardsApy = stringReference("5.1 % APY"),
onClick = {},
isAllowedToSpend = true,
),
YieldSupplyUM.Loading,
YieldSupplyUM.Processing,
)
}
// endregion

View file

@ -3,15 +3,19 @@ package com.tangem.features.yield.supply.impl.promo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
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.core.ui.decompose.getEmptyComposableBottomSheetComponent
import com.tangem.core.ui.decompose.EmptyComposableBottomSheetComponent
import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent
import com.tangem.features.yield.supply.impl.promo.model.YieldSupplyPromoModel
import com.tangem.features.yield.supply.impl.promo.ui.YieldSupplyPromoContent
import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningEntryComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -27,7 +31,7 @@ internal class DefaultYieldSupplyPromoComponent @AssistedInject constructor(
source = model.bottomSheetNavigation,
serializer = null,
handleBackButton = false,
childFactory = { _, _ -> bottomSheetChild() },
childFactory = { config, componentContext -> bottomSheetChild(config, componentContext) },
)
@Composable
@ -44,7 +48,26 @@ internal class DefaultYieldSupplyPromoComponent @AssistedInject constructor(
bottomSheet.child?.instance?.BottomSheet()
}
private fun bottomSheetChild(): ComposableBottomSheetComponent = getEmptyComposableBottomSheetComponent()
private fun bottomSheetChild(
config: YieldSupplyPromoConfig,
componentContext: ComponentContext,
): ComposableBottomSheetComponent = when (config) {
YieldSupplyPromoConfig.Apy -> EmptyComposableBottomSheetComponent
YieldSupplyPromoConfig.Action -> YieldSupplyStartEarningEntryComponent(
appComponentContext = childByContext(componentContext),
params = YieldSupplyStartEarningEntryComponent.Params(
userWalletId = params.userWalletId,
cryptoCurrency = params.currency,
onDismiss = { isProcessing ->
if (isProcessing) {
router.pop()
} else {
model.bottomSheetNavigation.dismiss()
}
},
),
)
}
@AssistedFactory
interface Factory : YieldSupplyPromoComponent.Factory {

View file

@ -160,7 +160,9 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
uiState.update { it.copy(isPrimaryButtonEnabled = true) }
},
ifRight = {
params.callback.onTransactionSent()
modelScope.launch {
params.callback.onTransactionSent()
}
},
)
}