Updated on 2026-08-14
This commit is contained in:
parent
9d162d2092
commit
0da45924c6
14 changed files with 833 additions and 1 deletions
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue