Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-14 13:24:05 +04:00
parent 10171a3b3e
commit ecf8e5fc96
19 changed files with 125 additions and 55 deletions

View file

@ -140,9 +140,9 @@ class DetailsMiddleware {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync } val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
.fold(onSuccess = { true }, onFailure = { false }) .fold(onSuccess = { true }, onFailure = { false })
if (isLocked && userWalletsListManager.hasUserWallets) { if (isLocked && userWalletsListManager.hasUserWallets) {
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() } store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
} else { } else {
store.dispatchNavigationAction { popTo<AppRoute.Home>() } store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
} }
} }
} }

View file

@ -74,7 +74,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppS
return DetailsState( return DetailsState(
scanResponse = action.scanResponse, scanResponse = action.scanResponse,
// If the current screen is ResetToFactory, we must save the ResetToFactory's state // If the current screen is ResetToFactory, we must save the ResetToFactory's state
cardSettingsState = if (router.backStack.lastOrNull() is AppRoute.ResetToFactory) { cardSettingsState = if (router.stack.lastOrNull() is AppRoute.ResetToFactory) {
state.detailsState.cardSettingsState state.detailsState.cardSettingsState
} else { } else {
null null

View file

@ -157,11 +157,7 @@ internal class SaveWalletMiddleware {
private fun navigateToWallet() { private fun navigateToWallet() {
store.dispatchNavigationAction { store.dispatchNavigationAction {
popTo<AppRoute.Wallet> { isSuccess -> replaceAll(AppRoute.Wallet)
if (!isSuccess) {
clear { push(AppRoute.Wallet) }
}
}
} }
} }
} }

View file

@ -6,6 +6,7 @@ import com.tangem.common.routing.bundle.bundle
import com.tangem.common.routing.entity.SerializableIntent import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.decompose.navigation.Route import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.qrscanning.models.SourceType import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -160,5 +161,16 @@ sealed class AppRoute(val path: String) : Route, RouteBundleParams {
data object ModalNotification : AppRoute(path = "/modal_notification") data object ModalNotification : AppRoute(path = "/modal_notification")
@Serializable @Serializable
data object Staking : AppRoute(path = "/staking") data class Staking(
val userWalletId: UserWalletId,
val cryptoCurrencyId: CryptoCurrency.ID,
val yield: Yield,
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/${yield.id}") {
companion object {
const val USER_WALLET_ID_KEY = "userWalletId"
const val CRYPTO_CURRENCY_ID_KEY = "cryptoCurrencyId"
const val YIELD_KEY = "yield"
}
}
} }

View file

@ -1,5 +1,6 @@
plugins { plugins {
alias(deps.plugins.kotlin.jvm) alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.serialization)
id("configuration") id("configuration")
} }
@ -7,4 +8,6 @@ dependencies {
api(deps.kotlin.coroutines) api(deps.kotlin.coroutines)
api(deps.arrow.core) api(deps.arrow.core)
api(deps.arrow.fx) api(deps.arrow.fx)
implementation(deps.kotlin.serialization)
} }

View file

@ -0,0 +1,21 @@
package com.tangem.domain.core.serialization
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.math.BigDecimal
internal object BigDecimalSerializer : KSerializer<BigDecimal> {
override val descriptor = PrimitiveSerialDescriptor(serialName = "BigDecimal", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: BigDecimal) {
encoder.encodeString(value.toString())
}
override fun deserialize(decoder: Decoder): BigDecimal {
return BigDecimal(decoder.decodeString())
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.domain.core.serialization
import kotlinx.serialization.Serializable
import java.math.BigDecimal
typealias SerializedBigDecimal = @Serializable(with = BigDecimalSerializer::class) BigDecimal

View file

@ -1,6 +1,7 @@
plugins { plugins {
alias(deps.plugins.android.library) alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android) alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.serialization)
id("configuration") id("configuration")
} }
@ -10,10 +11,10 @@ android {
dependencies { dependencies {
implementation(deps.kotlin.coroutines) api(projects.domain.staking.models)
implementation(deps.arrow.core)
implementation(deps.kotlin.serialization)
api(projects.domain.core)
implementation(deps.kotlin.serialization)
implementation(projects.domain.tokens.models) implementation(projects.domain.tokens.models)
} }

1
domain/staking/models/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,11 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
dependencies {
implementation(projects.domain.core)
implementation(deps.kotlin.serialization)
}

View file

@ -1,54 +1,64 @@
package com.tangem.domain.staking.model package com.tangem.domain.staking.model
import java.io.Serializable import com.tangem.domain.core.serialization.SerializedBigDecimal
import java.math.BigDecimal import kotlinx.serialization.Serializable
@Serializable
data class Yield( data class Yield(
val id: String, val id: String,
val token: Token, val token: Token,
val tokens: List<Token>, val tokens: List<Token>,
val args: Args, val args: Args,
val status: Status, val status: Status,
val apy: BigDecimal, val apy: SerializedBigDecimal,
val rewardRate: Double, val rewardRate: Double,
val rewardType: RewardType, val rewardType: RewardType,
val metadata: Metadata, val metadata: Metadata,
val validators: List<Validator>, val validators: List<Validator>,
val isAvailable: Boolean, val isAvailable: Boolean,
) : Serializable { ) {
@Serializable
data class Status( data class Status(
val enter: Boolean, val enter: Boolean,
val exit: Boolean?, val exit: Boolean?,
) : Serializable )
@Serializable
data class Args( data class Args(
val enter: Enter, val enter: Enter,
val exit: Enter?, val exit: Enter?,
) : Serializable { ) {
@Serializable
data class Enter( data class Enter(
val addresses: Addresses, val addresses: Addresses,
val args: Map<String, AddressArgument>, val args: Map<String, AddressArgument>,
) : Serializable { ) {
@Serializable
data class Addresses( data class Addresses(
val address: AddressArgument, val address: AddressArgument,
val additionalAddresses: Map<String, AddressArgument>? = null, val additionalAddresses: Map<String, AddressArgument>? = null,
) : Serializable )
} }
} }
@Serializable
data class Validator( data class Validator(
val address: String, val address: String,
val status: String, val status: String,
val name: String, val name: String,
val image: String?, val image: String?,
val website: String?, val website: String?,
val apr: BigDecimal?, val apr: SerializedBigDecimal?,
val commission: Double?, val commission: Double?,
val stakedBalance: String?, val stakedBalance: String?,
val votingPower: Double?, val votingPower: Double?,
val preferred: Boolean, val preferred: Boolean,
) : Serializable )
@Serializable
data class Metadata( data class Metadata(
val name: String, val name: String,
val logoUri: String, val logoUri: String,
@ -67,14 +77,17 @@ data class Yield(
val supportsMultipleValidators: Boolean, val supportsMultipleValidators: Boolean,
val revshare: Enabled, val revshare: Enabled,
val fee: Enabled, val fee: Enabled,
) : Serializable { ) {
@Serializable
data class Period( data class Period(
val days: Int, val days: Int,
) : Serializable )
@Serializable
data class Enabled( data class Enabled(
val enabled: Boolean, val enabled: Boolean,
) : Serializable )
} }
enum class RewardType { enum class RewardType {
@ -84,6 +97,7 @@ data class Yield(
} }
} }
@Serializable
data class Token( data class Token(
val name: String, val name: String,
val network: NetworkType, val network: NetworkType,
@ -93,7 +107,7 @@ data class Token(
val coinGeckoId: String?, val coinGeckoId: String?,
val logoURI: String?, val logoURI: String?,
val isPoints: Boolean?, val isPoints: Boolean?,
) : Serializable { ) {
enum class NetworkType { enum class NetworkType {
AVALANCHE_C, AVALANCHE_C,
AVALANCHE_ATOMIC, AVALANCHE_ATOMIC,
@ -165,9 +179,10 @@ data class Token(
} }
} }
@Serializable
data class AddressArgument( data class AddressArgument(
val required: Boolean, val required: Boolean,
val network: String? = null, val network: String? = null,
val minimum: Double? = null, val minimum: Double? = null,
val maximum: Double? = null, val maximum: Double? = null,
) : Serializable )

View file

@ -188,11 +188,11 @@ sealed class CryptoCurrency {
return "ID(value='$value')" return "ID(value='$value')"
} }
private companion object { companion object {
// should use delimiters that could be used in URL not like path or query delimiters // should use delimiters that could be used in URL not like path or query delimiters
const val PREFIX_DELIMITER = '_' private const val PREFIX_DELIMITER = '_'
const val SUFFIX_DELIMITER = ';' private const val SUFFIX_DELIMITER = ';'
const val DERIVATION_PATH_DELIMITER = 'd' private const val DERIVATION_PATH_DELIMITER = 'd'
} }
} }

View file

@ -5,10 +5,4 @@ import androidx.fragment.app.Fragment
interface StakingRouter { interface StakingRouter {
fun getEntryFragment(): Fragment fun getEntryFragment(): Fragment
companion object {
const val CRYPTO_CURRENCY_ID_KEY = "CRYPTO_CURRENCY_ID_KEY"
const val USER_WALLET_ID_KEY = "USER_WALLET_ID_KEY"
const val YIELD_KEY = "YIELD_KEY"
}
} }

View file

@ -63,6 +63,7 @@ dependencies {
/** Common */ /** Common */
implementation(projects.common.ui) implementation(projects.common.ui)
implementation(projects.common.routing)
/** Libs */ /** Libs */
implementation(projects.libs.crypto) implementation(projects.libs.crypto)

View file

@ -1,5 +1,6 @@
package com.tangem.features.staking.impl.presentation.viewmodel package com.tangem.features.staking.impl.presentation.viewmodel
import android.os.Bundle
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@ -7,6 +8,8 @@ import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.staking.model.Yield import com.tangem.domain.staking.model.Yield
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
@ -52,14 +55,18 @@ internal class StakingViewModel @Inject constructor(
var stakingStateRouter: StakingStateRouter by Delegates.notNull() var stakingStateRouter: StakingStateRouter by Delegates.notNull()
private set private set
private val cryptoCurrencyId: CryptoCurrency.ID = savedStateHandle[StakingRouter.CRYPTO_CURRENCY_ID_KEY] private val cryptoCurrencyId: CryptoCurrency.ID = savedStateHandle.get<Bundle>(
AppRoute.Staking.CRYPTO_CURRENCY_ID_KEY,
)
?.let { it.unbundle(CryptoCurrency.ID.serializer()) }
?: error("This screen can't be opened without `CryptoCurrency.ID`") ?: error("This screen can't be opened without `CryptoCurrency.ID`")
private val userWalletId: UserWalletId = savedStateHandle.get<String>(StakingRouter.USER_WALLET_ID_KEY) private val userWalletId: UserWalletId = savedStateHandle.get<Bundle>(AppRoute.Staking.USER_WALLET_ID_KEY)
?.let { stringValue -> UserWalletId(stringValue) } ?.let { it.unbundle(UserWalletId.serializer()) }
?: error("This screen can't be opened without `UserWalletId`") ?: error("This screen can't be opened without `UserWalletId`")
private val yield: Yield = savedStateHandle[StakingRouter.YIELD_KEY] private val yield: Yield = savedStateHandle.get<Bundle>(AppRoute.Staking.YIELD_KEY)
?.let { it.unbundle(Yield.serializer()) }
?: error("This screen can't be opened without `Yield`") ?: error("This screen can't be opened without `Yield`")
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()

View file

@ -5,6 +5,7 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.tokendetails.presentation.TokenDetailsFragment import com.tangem.feature.tokendetails.presentation.TokenDetailsFragment
@ -37,4 +38,14 @@ internal class DefaultTokenDetailsRouter(
), ),
) )
} }
override fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yield: Yield) {
router.push(
AppRoute.Staking(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrency.id,
yield = yield,
),
)
}
} }

View file

@ -1,5 +1,6 @@
package com.tangem.feature.tokendetails.presentation.router package com.tangem.feature.tokendetails.presentation.router
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -16,4 +17,6 @@ internal interface InnerTokenDetailsRouter : TokenDetailsRouter {
fun share(text: String) fun share(text: String)
fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency)
fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yield: Yield)
} }

View file

@ -4,7 +4,6 @@ import android.os.Bundle
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.core.os.bundleOf
import androidx.lifecycle.* import androidx.lifecycle.*
import androidx.paging.cachedIn import androidx.paging.cachedIn
import arrow.core.getOrElse import arrow.core.getOrElse
@ -14,8 +13,6 @@ import com.tangem.common.routing.bundle.unbundle
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.global.BuyCurrencyDeepLink import com.tangem.core.deeplink.global.BuyCurrencyDeepLink
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels
@ -70,7 +67,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.tokendetails.impl.R import com.tangem.features.tokendetails.impl.R
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
@ -448,16 +444,7 @@ internal class TokenDetailsViewModel @Inject constructor(
val yield = getYieldUseCase.invoke(cryptoCurrency.id, cryptoCurrency.symbol).getOrNull() val yield = getYieldUseCase.invoke(cryptoCurrency.id, cryptoCurrency.symbol).getOrNull()
yield ?: error("Staking is unavailable") yield ?: error("Staking is unavailable")
reduxStateHolder.dispatch( router.openStaking(userWalletId, cryptoCurrency, yield)
action = NavigationAction.NavigateTo(
screen = AppScreen.Staking,
bundle = bundleOf(
StakingRouter.USER_WALLET_ID_KEY to userWalletId.stringValue,
StakingRouter.CRYPTO_CURRENCY_ID_KEY to cryptoCurrency.id,
StakingRouter.YIELD_KEY to yield,
),
),
)
} }
} }

View file

@ -212,6 +212,7 @@ include(":domain:feedback")
include(":domain:qr-scanning") include(":domain:qr-scanning")
include(":domain:qr-scanning:models") include(":domain:qr-scanning:models")
include(":domain:staking") include(":domain:staking")
include(":domain:staking:models")
include(":domain:wallet-connect") include(":domain:wallet-connect")
// endregion Domain modules // endregion Domain modules