Updated on 2026-08-14
This commit is contained in:
parent
9d162d2092
commit
0da45924c6
14 changed files with 833 additions and 1 deletions
|
|
@ -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