Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-17 13:29:44 +03:00
parent e8a2b10e6f
commit c1900053f7
27 changed files with 270 additions and 107 deletions

View file

@ -56,9 +56,13 @@ dependencies {
implementation(projects.domain.staking)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.legacy)
implementation(projects.domain.models)
/** Common */
implementation(projects.common)
implementation(projects.common.ui)
/** Libs */
implementation(projects.libs.crypto)

View file

@ -1,8 +1,9 @@
package com.tangem.features.staking.impl.presentation.state
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.event.consumedEvent
import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub
import com.tangem.features.staking.impl.presentation.state.transformers.StakingScreenStateTransformer
import com.tangem.utils.transformer.Transformer
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -23,7 +24,7 @@ internal class StakingStateController @Inject constructor() {
mutableUiState.update(function = function)
}
fun update(transformer: StakingScreenStateTransformer) {
fun update(transformer: Transformer<StakingUiState>) {
mutableUiState.update(function = transformer::transform)
}
@ -37,7 +38,7 @@ internal class StakingStateController @Inject constructor() {
cryptoCurrencyName = "",
currentStep = StakingStep.InitialInfo,
initialInfoState = StakingStates.InitialInfoState.Empty(),
amountState = StakingStates.AmountState.Empty(),
amountState = AmountState.Empty(),
validatorState = StakingStates.ValidatorState.Empty(),
confirmStakingState = StakingStates.ConfirmStakingState.Empty(),
isBalanceHidden = false,

View file

@ -1,6 +1,7 @@
package com.tangem.features.staking.impl.presentation.state
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
@ -14,7 +15,7 @@ internal data class StakingUiState(
val cryptoCurrencyName: String,
val currentStep: StakingStep,
val initialInfoState: StakingStates.InitialInfoState,
val amountState: StakingStates.AmountState,
val amountState: AmountState,
val validatorState: StakingStates.ValidatorState,
val confirmStakingState: StakingStates.ConfirmStakingState,
val isBalanceHidden: Boolean,
@ -23,7 +24,7 @@ internal data class StakingUiState(
fun copyWrapped(
initialInfoState: StakingStates.InitialInfoState = this.initialInfoState,
amountState: StakingStates.AmountState = this.amountState,
amountState: AmountState = this.amountState,
validatorState: StakingStates.ValidatorState = this.validatorState,
confirmStakingState: StakingStates.ConfirmStakingState = this.confirmStakingState,
): StakingUiState = copy(
@ -57,17 +58,6 @@ internal sealed class StakingStates {
) : InitialInfoState()
}
/** Amount state */
sealed class AmountState : StakingStates() {
data class Data(
override val isPrimaryButtonEnabled: Boolean,
) : AmountState()
data class Empty(
override val isPrimaryButtonEnabled: Boolean = false,
) : AmountState()
}
/** Validator state */
sealed class ValidatorState : StakingStates() {
data class Data(

View file

@ -9,4 +9,14 @@ object StakingClickIntentsStub : StakingClickIntents {
override fun onNextClick() {}
override fun onPrevClick() {}
override fun onAmountValueChange(value: String) {}
override fun onAmountPasteTriggerDismiss() {}
override fun onMaxValueClick() {}
override fun onCurrencyChangeClick(isFiat: Boolean) {}
override fun onAmountNext() {}
}

View file

@ -1,10 +1,11 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class HideBalanceStateTransformer(
private val isBalanceHidden: Boolean,
) : StakingScreenStateTransformer {
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(isBalanceHidden = isBalanceHidden)

View file

@ -1,31 +1,52 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.common.ui.amountScreen.converters.AmountStateConverter
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.TextReference
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.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.StakingStep
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.transformer.Transformer
import java.math.BigDecimal
internal class SetInitialDataStateTransformer(
private val clickIntents: StakingClickIntents,
private val yield: Yield,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
) : StakingScreenStateTransformer {
private val userWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
) : Transformer<StakingUiState> {
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
private val amountStateConverter by lazy(LazyThreadSafetyMode.NONE) {
AmountStateConverter(
clickIntents = clickIntents,
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
appCurrencyProvider = appCurrencyProvider,
userWalletProvider = userWalletProvider,
iconStateConverter = iconStateConverter,
)
}
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
clickIntents = clickIntents,
currentStep = StakingStep.InitialInfo,
initialInfoState = createInitialInfoState(),
amountState = createInitialAmountState(),
)
}
@ -50,6 +71,10 @@ internal class SetInitialDataStateTransformer(
)
}
private fun createInitialAmountState(): AmountState {
return amountStateConverter.convert("")
}
private fun getAprRange(): TextReference {
val aprValues = yield.validators.mapNotNull { it.apr }

View file

@ -1,8 +0,0 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.features.staking.impl.presentation.state.StakingUiState
internal interface StakingScreenStateTransformer {
fun transform(prevState: StakingUiState): StakingUiState
}

View file

@ -0,0 +1,18 @@
package com.tangem.features.staking.impl.presentation.state.transformers.amount
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldChangeTransformer
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class AmountChangeStateTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val value: String,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
amountState = AmountFieldChangeTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState),
)
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.features.staking.impl.presentation.state.transformers.amount
import com.tangem.common.ui.amountScreen.converters.AmountCurrencyTransformer
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class AmountCurrencyChangeStateTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val value: Boolean,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
amountState = AmountCurrencyTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState),
)
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.features.staking.impl.presentation.state.transformers.amount
import com.tangem.common.ui.amountScreen.converters.field.AmountFieldMaxAmountTransformer
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class AmountMaxValueStateTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
amountState = AmountFieldMaxAmountTransformer(cryptoCurrencyStatus).transform(prevState.amountState),
)
}
}

View file

@ -0,0 +1,14 @@
package com.tangem.features.staking.impl.presentation.state.transformers.amount
import com.tangem.common.ui.amountScreen.converters.AmountPastedTriggerDismissTransformer
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class AmountPasteDismissStateTransformer : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
amountState = AmountPastedTriggerDismissTransformer().transform(prevState.amountState),
)
}
}

View file

@ -1,11 +0,0 @@
package com.tangem.features.staking.impl.presentation.ui
import androidx.compose.runtime.Composable
import com.tangem.features.staking.impl.presentation.state.StakingStates
@Composable
internal fun StakingAmountContent(state: StakingStates.AmountState) {
if (state !is StakingStates.AmountState.Data) return
// TODO staking
}

View file

@ -12,11 +12,12 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.tangem.common.ui.amountScreen.AmountScreenContent
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.StakingStep
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@ -121,8 +122,10 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
StakingStep.InitialInfo -> StakingInitialInfoContent(
state = uiState.initialInfoState,
)
StakingStep.Amount -> StakingAmountContent(
state = uiState.amountState,
StakingStep.Amount -> AmountScreenContent(
amountState = uiState.amountState,
isBalanceHiding = uiState.isBalanceHidden,
clickIntents = uiState.clickIntents,
)
StakingStep.ValidatorAndFee -> StakingValidatorAndFeeContent(
state = uiState.validatorState,

View file

@ -1,10 +1,14 @@
package com.tangem.features.staking.impl.presentation.viewmodel
internal interface StakingClickIntents {
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
internal interface StakingClickIntents : AmountScreenClickIntents {
fun onBackClick()
fun onNextClick()
fun onPrevClick()
override fun onAmountNext() = onNextClick()
}

View file

@ -4,19 +4,28 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
import com.tangem.features.staking.impl.presentation.state.StakingStateController
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.StakingStateRouter
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.transformers.HideBalanceStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.SetInitialDataStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountChangeStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountCurrencyChangeStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountMaxValueStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountPasteDismissStateTransformer
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
@ -25,12 +34,15 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.properties.Delegates
@Suppress("LongParameterList")
@HiltViewModel
internal class StakingViewModel @Inject constructor(
private val stateController: StakingStateController,
private val dispatchers: CoroutineDispatcherProvider,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
@ -53,6 +65,8 @@ internal class StakingViewModel @Inject constructor(
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var innerRouter: InnerStakingRouter by Delegates.notNull()
private var userWallet: UserWallet by Delegates.notNull()
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
init {
subscribeOnBalanceHiding()
@ -71,6 +85,22 @@ internal class StakingViewModel @Inject constructor(
stakingStateRouter.onBackClick()
}
override fun onAmountValueChange(value: String) {
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, value))
}
override fun onAmountPasteTriggerDismiss() {
stateController.update(AmountPasteDismissStateTransformer())
}
override fun onMaxValueClick() {
stateController.update(AmountMaxValueStateTransformer(cryptoCurrencyStatus))
}
override fun onCurrencyChangeClick(isFiat: Boolean) {
stateController.update(AmountCurrencyChangeStateTransformer(cryptoCurrencyStatus, isFiat))
}
fun setRouter(router: InnerStakingRouter, stateRouter: StakingStateRouter) {
innerRouter = router
this.stakingStateRouter = stateRouter
@ -78,6 +108,14 @@ internal class StakingViewModel @Inject constructor(
private fun subscribeOnCurrencyStatusUpdates() {
viewModelScope.launch {
getUserWalletUseCase(userWalletId).fold(
ifRight = { wallet ->
userWallet = wallet
},
ifLeft = {
// TODO staking error
},
)
getCryptoCurrencyStatusSyncUseCase(userWalletId, cryptoCurrencyId).fold(
ifRight = {
cryptoCurrencyStatus = it
@ -86,6 +124,8 @@ internal class StakingViewModel @Inject constructor(
clickIntents = this@StakingViewModel,
yield = yield,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { selectedAppCurrencyFlow.value },
),
)
},
@ -106,4 +146,19 @@ internal class StakingViewModel @Inject constructor(
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
}
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.conflate()
.distinctUntilChanged()
.map { maybeAppCurrency ->
maybeAppCurrency.getOrElse { AppCurrency.Default }
}
.flowOn(dispatchers.main)
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = AppCurrency.Default,
)
}
}