Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-16 17:38:43 +05:00
parent 9d162d2092
commit 0da45924c6
14 changed files with 833 additions and 1 deletions

View file

@ -45,7 +45,7 @@ class YieldSupplyEstimateEnterFeeUseCase(
).normal, ).normal,
) )
} else { } else {
transaction.copy(fee = withCalculatedFee.first().fee?.fixFee(cryptoCurrency)) transaction.copy(fee = withCalculatedFee.firstOrNull()?.fee?.fixFee(cryptoCurrency))
} }
} }

View file

@ -0,0 +1,63 @@
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.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.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 kotlinx.coroutines.flow.StateFlow
internal class YieldSupplyActiveComponent(
appComponentContext: AppComponentContext,
private val params: Params,
) : ComposableModularContentComponent, AppComponentContext by appComponentContext {
private val model: YieldSupplyActiveModel = getOrCreateModel(params = params)
@Composable
override fun Title() {
YieldSupplyActiveTitle(onCloseClick = params.callback::onBackClick)
}
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
YieldSupplyActiveContent(state = state, modifier = Modifier)
}
@Composable
override fun Footer() {
SecondaryButton(
text = stringResourceSafe(R.string.yield_module_stop_earning),
onClick = params.callback::onStopEarning,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
)
}
data class Params(
val userWallet: UserWallet,
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val callback: ModelCallback,
)
interface ModelCallback {
fun onBackClick()
fun onStopEarning()
}
}

View file

@ -0,0 +1,103 @@
package com.tangem.features.yield.supply.impl.subcomponents.active
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableModularContentComponent
import com.tangem.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.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()
YieldSupplyActiveEntryBottomSheet(
stackState = stackState,
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,
callback = model,
),
)
YieldSupplyActiveRoute.Action -> YieldSupplyStopEarningComponent(
appComponentContext = factoryContext,
params = YieldSupplyStopEarningComponent.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 onDismiss: () -> Unit,
)
}

View file

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

View file

@ -0,0 +1,10 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.entity
import com.tangem.core.ui.extensions.TextReference
internal data class YieldSupplyActiveContentUM(
val totalEarnings: TextReference,
val availableBalance: TextReference,
val providerTitle: TextReference,
val subtitle: TextReference,
)

View file

@ -0,0 +1,33 @@
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.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.stopearning.YieldSupplyStopEarningComponent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
@ModelScoped
internal class YieldSupplyActiveEntryModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
private val router: Router,
) : Model(), YieldSupplyActiveComponent.ModelCallback, YieldSupplyStopEarningComponent.ModelCallback {
private val params = paramsContainer.require<YieldSupplyActiveEntryComponent.Params>()
override fun onStopEarning() {
router.push(YieldSupplyActiveRoute.Action)
}
override fun onBackClick() {
router.pop()
}
override fun onTransactionSent() {
params.onDismiss()
}
}

View file

@ -0,0 +1,50 @@
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.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
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.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ModelScoped
internal class YieldSupplyActiveModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
private val params: YieldSupplyActiveComponent.Params = paramsContainer.require()
private val cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow
private val cryptoCurrency = cryptoCurrencyStatusFlow.value.currency
val uiState: StateFlow<YieldSupplyActiveContentUM>
field = MutableStateFlow(
YieldSupplyActiveContentUM(
totalEarnings = stringReference("0"),
availableBalance = stringReference(
cryptoCurrencyStatusFlow.value.value.amount.format {
crypto(cryptoCurrency = cryptoCurrencyStatusFlow.value.currency)
},
),
providerTitle = resourceReference(R.string.yield_module_provider),
subtitle = combinedReference(
resourceReference(
id = R.string.yield_module_earn_sheet_provider_description,
formatArgs = wrappedList(cryptoCurrency.symbol, cryptoCurrency.symbol),
),
resourceReference(R.string.common_read_more),
),
),
)
}

View file

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

View file

@ -0,0 +1,164 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.ui
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.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.ResizableText
import com.tangem.core.ui.components.SpacerH4
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.SpacerWMax
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
@Composable
internal fun YieldSupplyActiveContent(state: YieldSupplyActiveContentUM, modifier: Modifier = Modifier) {
Column(
verticalArrangement = Arrangement.spacedBy(14.dp),
modifier = modifier.padding(
vertical = 8.dp,
horizontal = 16.dp,
),
) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.action)
.fillMaxWidth()
.padding(12.dp),
) {
Text(
text = stringResourceSafe(R.string.yield_module_earn_sheet_total_earnings_title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
ResizableText(
text = state.totalEarnings.resolveReference(),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
}
YieldSupplyActiveMyFunds(state = state)
}
}
@Composable
private fun YieldSupplyActiveMyFunds(state: YieldSupplyActiveContentUM) {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.action)
.fillMaxWidth()
.padding(12.dp),
) {
Text(
text = stringResourceSafe(R.string.yield_module_earn_sheet_my_funds_title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
SpacerH4()
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_aave_36),
contentDescription = null,
modifier = Modifier.size(20.dp),
)
Text(
text = state.providerTitle.resolveReference(),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
}
SpacerH8()
Text(
text = state.subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
SpacerH8()
HorizontalDivider(
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),
)
HorizontalDivider(
thickness = 0.5.dp,
color = TangemTheme.colors.stroke.primary,
)
InfoRow(
title = resourceReference(R.string.yield_module_earn_sheet_available_title),
info = state.availableBalance,
)
}
}
@Composable
private fun InfoRow(title: TextReference, info: TextReference) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.padding(horizontal = 4.dp, vertical = 12.dp),
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
SpacerWMax()
Text(
text = info.resolveReference(),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.tertiary,
)
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun YieldSupplyActiveBottomSheet_Preview(
@PreviewParameter(YieldSupplyActiveBottomSheetPreviewProvider::class) params: YieldSupplyActiveContentUM,
) {
TangemThemePreview {
YieldSupplyActiveContent(params)
}
}
private class YieldSupplyActiveBottomSheetPreviewProvider : PreviewParameterProvider<YieldSupplyActiveContentUM> {
override val values: Sequence<YieldSupplyActiveContentUM>
get() = sequenceOf(
YieldSupplyActiveContentUM(
totalEarnings = stringReference("0.006994219 USDT"),
availableBalance = stringReference("3,210.006994 aUSDT"),
providerTitle = stringReference("Aave"),
subtitle = resourceReference(
R.string.yield_module_earn_sheet_provider_description,
wrappedList("USDT", "USDT"),
),
),
)
}
// endregion

View file

@ -0,0 +1,48 @@
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>,
onDismiss: () -> Unit,
) {
TangemModalBottomSheetWithFooter<TangemBottomSheetConfigContent.Empty>(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onDismiss,
content = TangemBottomSheetConfigContent.Empty,
),
containerColor = TangemTheme.colors.background.tertiary,
title = { state ->
AnimatedContent(
stackState.active.instance,
) { currentState ->
currentState.Title()
}
},
footer = { state ->
AnimatedContent(
stackState.active.instance,
) { currentState ->
currentState.Footer()
}
},
content = { state ->
AnimatedContent(
stackState.active.instance,
) { currentState ->
currentState.Content(modifier = Modifier)
}
},
)
}

View file

@ -0,0 +1,51 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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()) {
Column(modifier = Modifier.align(Alignment.Center)) {
Text(
text = stringResourceSafe(R.string.yield_module_earn_sheet_title),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.size(8.dp)
.background(TangemTheme.colors.icon.accent, CircleShape),
)
Text(
text = stringResourceSafe(R.string.yield_module_status_active),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier,
)
}
}
TangemIconButton(
iconRes = R.drawable.ic_close_24,
onClick = onCloseClick,
modifier = Modifier
.padding(16.dp)
.align(Alignment.CenterEnd),
)
}
}

View file

@ -0,0 +1,96 @@
package com.tangem.features.yield.supply.impl.subcomponents.stopearning
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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 androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.decompose.ComposableModularContentComponent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.common.ui.YieldSupplyActionContent
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.model.YieldSupplyStopEarningModel
import kotlinx.coroutines.flow.StateFlow
internal class YieldSupplyStopEarningComponent(
private val appComponentContext: AppComponentContext,
private val params: Params,
) : ComposableModularContentComponent, AppComponentContext by appComponentContext {
private val model: YieldSupplyStopEarningModel = getOrCreateModel(params = params)
@Composable
override fun Title() {
TangemModalBottomSheetTitle(
startIconRes = R.drawable.ic_back_24,
onStartClick = params.callback::onBackClick,
)
}
@Suppress("MagicNumber")
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
YieldSupplyActionContent(
yieldSupplyActionUM = state,
modifier = modifier,
) {
Box(
modifier = Modifier
.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),
)
}
}
}
@Composable
override fun Footer() {
val state by model.uiState.collectAsStateWithLifecycle()
PrimaryButtonIconEnd(
text = stringResourceSafe(R.string.common_confirm),
onClick = model::onClick,
iconResId = R.drawable.ic_tangem_24,
enabled = state.isPrimaryButtonEnabled,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
)
}
data class Params(
val userWallet: UserWallet,
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
val callback: ModelCallback,
)
interface ModelCallback {
fun onBackClick()
fun onTransactionSent()
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.yield.supply.impl.subcomponents.stopearning.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.model.YieldSupplyStopEarningModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(ModelComponent::class)
internal interface YieldSupplyStopEarningModule {
@Binds
@IntoMap
@ClassKey(YieldSupplyStopEarningModel::class)
fun provideYieldSupplyStopEarningModel(impl: YieldSupplyStopEarningModel): Model
}

View file

@ -0,0 +1,160 @@
package com.tangem.features.yield.supply.impl.subcomponents.stopearning.model
import arrow.core.getOrElse
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent
import com.tangem.utils.StringsSigns.DOT
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class YieldSupplyStopEarningModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
private val getFeeUseCase: GetFeeUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
private val sendTransactionUseCase: SendTransactionUseCase,
private val yieldSupplyStopEarningUseCase: YieldSupplyStopEarningUseCase,
) : Model() {
private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require()
private val cryptoCurrencyStatus
get() = params.cryptoCurrencyStatusFlow.value
private val cryptoCurrency = cryptoCurrencyStatus.currency
private var userWallet = params.userWallet
private val feeCryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
field = MutableStateFlow(
CryptoCurrencyStatus(
cryptoCurrencyStatus.currency,
value = CryptoCurrencyStatus.Loading,
),
)
private var appCurrency = AppCurrency.Default
val uiState: StateFlow<YieldSupplyActionUM>
field: MutableStateFlow<YieldSupplyActionUM> = MutableStateFlow(
YieldSupplyActionUM(
title = resourceReference(R.string.yield_module_stop_earning),
subtitle = resourceReference(
id = R.string.yield_module_stop_earning_sheet_description,
formatArgs = wrappedList(cryptoCurrency.symbol),
),
footer = combinedReference(
resourceReference(R.string.yield_module_stop_earning_sheet_fee_note),
resourceReference(R.string.common_read_more),
),
currencyIconState = CryptoCurrencyToIconStateConverter().convert(cryptoCurrency),
yieldSupplyFeeUM = YieldSupplyFeeUM.Loading,
isPrimaryButtonEnabled = false,
),
)
init {
modelScope.launch {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
subscribeOnCurrencyStatusUpdates()
}
}
fun onClick() {
val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return
uiState.update { it.copy(isPrimaryButtonEnabled = false) }
modelScope.launch(dispatchers.default) {
sendTransactionUseCase(
txData = yieldSupplyFeeUM.transactionDataList.first(),
userWallet = userWallet,
network = cryptoCurrency.network,
).fold(
ifLeft = {
Timber.e(it.toString())
},
ifRight = {
params.callback.onTransactionSent()
},
)
}
}
private fun subscribeOnCurrencyStatusUpdates() {
modelScope.launch {
feeCryptoCurrencyStatusFlow.update {
getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = userWallet.walletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
).getOrNull() ?: cryptoCurrencyStatus
}
onLoadFee()
}
}
private suspend fun onLoadFee() {
val exitTransitionData = yieldSupplyStopEarningUseCase(
userWalletId = userWallet.walletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
fee = null,
).getOrNull() ?: return
val fee = getFeeUseCase(
transactionData = exitTransitionData,
userWallet = userWallet,
network = cryptoCurrency.network,
).getOrNull() ?: return
val crypto = fee.normal.amount.value.format { crypto(feeCryptoCurrencyStatusFlow.value.currency) }
val fiatFeeValue = cryptoCurrencyStatus.value.fiatRate?.let { rate ->
fee.normal.amount.value?.multiply(rate)
}
val fiat = fiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) }
uiState.update {
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
} else {
it.copy(
isPrimaryButtonEnabled = true,
yieldSupplyFeeUM = YieldSupplyFeeUM.Content(
transactionDataList = persistentListOf(exitTransitionData.copy(fee = fee.normal)),
feeValue = combinedReference(
stringReference(crypto),
stringReference(" $DOT "),
stringReference(fiat),
),
),
)
}
}
}
}