diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index d0ce3c2f74..23a552773c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -140,9 +140,9 @@ class DetailsMiddleware { val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync } .fold(onSuccess = { true }, onFailure = { false }) if (isLocked && userWalletsListManager.hasUserWallets) { - store.dispatchNavigationAction { popTo() } + store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } } else { - store.dispatchNavigationAction { popTo() } + store.dispatchNavigationAction { replaceAll(AppRoute.Home) } } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index d89b862c1b..658dfcd6ba 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -74,7 +74,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppS return DetailsState( scanResponse = action.scanResponse, // 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 } else { null diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 541ceefdcc..82799c76e6 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -157,11 +157,7 @@ internal class SaveWalletMiddleware { private fun navigateToWallet() { store.dispatchNavigationAction { - popTo { isSuccess -> - if (!isSuccess) { - clear { push(AppRoute.Wallet) } - } - } + replaceAll(AppRoute.Wallet) } } } \ No newline at end of file diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 2a337dbeaa..f912745220 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -6,6 +6,7 @@ import com.tangem.common.routing.bundle.bundle import com.tangem.common.routing.entity.SerializableIntent import com.tangem.core.decompose.navigation.Route 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.wallets.models.UserWalletId import kotlinx.serialization.Serializable @@ -160,5 +161,16 @@ sealed class AppRoute(val path: String) : Route, RouteBundleParams { data object ModalNotification : AppRoute(path = "/modal_notification") @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" + } + } } \ No newline at end of file diff --git a/domain/core/build.gradle.kts b/domain/core/build.gradle.kts index 63325166f7..46d2670d68 100644 --- a/domain/core/build.gradle.kts +++ b/domain/core/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(deps.plugins.kotlin.jvm) + alias(deps.plugins.kotlin.serialization) id("configuration") } @@ -7,4 +8,6 @@ dependencies { api(deps.kotlin.coroutines) api(deps.arrow.core) api(deps.arrow.fx) + + implementation(deps.kotlin.serialization) } \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/BigDecimalSerializer.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/BigDecimalSerializer.kt new file mode 100644 index 0000000000..ea7727681f --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/BigDecimalSerializer.kt @@ -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 { + + 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()) + } +} \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/SerializedBigDecimal.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/SerializedBigDecimal.kt new file mode 100644 index 0000000000..3243892b18 --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/SerializedBigDecimal.kt @@ -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 \ No newline at end of file diff --git a/domain/staking/build.gradle.kts b/domain/staking/build.gradle.kts index 252bfb49d9..fc6ced152c 100644 --- a/domain/staking/build.gradle.kts +++ b/domain/staking/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(deps.plugins.android.library) alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.serialization) id("configuration") } @@ -10,10 +11,10 @@ android { dependencies { - implementation(deps.kotlin.coroutines) - implementation(deps.arrow.core) - implementation(deps.kotlin.serialization) + api(projects.domain.staking.models) + api(projects.domain.core) + implementation(deps.kotlin.serialization) implementation(projects.domain.tokens.models) } \ No newline at end of file diff --git a/domain/staking/models/.gitignore b/domain/staking/models/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/domain/staking/models/.gitignore @@ -0,0 +1 @@ +/build diff --git a/domain/staking/models/build.gradle.kts b/domain/staking/models/build.gradle.kts new file mode 100644 index 0000000000..2a478f5d98 --- /dev/null +++ b/domain/staking/models/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/Yield.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/Yield.kt similarity index 88% rename from domain/staking/src/main/java/com/tangem/domain/staking/model/Yield.kt rename to domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/Yield.kt index 853c3d4247..aa55291bb5 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/model/Yield.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/Yield.kt @@ -1,54 +1,64 @@ package com.tangem.domain.staking.model -import java.io.Serializable -import java.math.BigDecimal +import com.tangem.domain.core.serialization.SerializedBigDecimal +import kotlinx.serialization.Serializable +@Serializable data class Yield( val id: String, val token: Token, val tokens: List, val args: Args, val status: Status, - val apy: BigDecimal, + val apy: SerializedBigDecimal, val rewardRate: Double, val rewardType: RewardType, val metadata: Metadata, val validators: List, val isAvailable: Boolean, -) : Serializable { +) { + + @Serializable data class Status( val enter: Boolean, val exit: Boolean?, - ) : Serializable + ) + @Serializable data class Args( val enter: Enter, val exit: Enter?, - ) : Serializable { + ) { + + @Serializable data class Enter( val addresses: Addresses, val args: Map, - ) : Serializable { + ) { + + @Serializable data class Addresses( val address: AddressArgument, val additionalAddresses: Map? = null, - ) : Serializable + ) } } + @Serializable data class Validator( val address: String, val status: String, val name: String, val image: String?, val website: String?, - val apr: BigDecimal?, + val apr: SerializedBigDecimal?, val commission: Double?, val stakedBalance: String?, val votingPower: Double?, val preferred: Boolean, - ) : Serializable + ) + @Serializable data class Metadata( val name: String, val logoUri: String, @@ -67,14 +77,17 @@ data class Yield( val supportsMultipleValidators: Boolean, val revshare: Enabled, val fee: Enabled, - ) : Serializable { + ) { + + @Serializable data class Period( val days: Int, - ) : Serializable + ) + @Serializable data class Enabled( val enabled: Boolean, - ) : Serializable + ) } enum class RewardType { @@ -84,6 +97,7 @@ data class Yield( } } +@Serializable data class Token( val name: String, val network: NetworkType, @@ -93,7 +107,7 @@ data class Token( val coinGeckoId: String?, val logoURI: String?, val isPoints: Boolean?, -) : Serializable { +) { enum class NetworkType { AVALANCHE_C, AVALANCHE_ATOMIC, @@ -165,9 +179,10 @@ data class Token( } } +@Serializable data class AddressArgument( val required: Boolean, val network: String? = null, val minimum: Double? = null, val maximum: Double? = null, -) : Serializable \ No newline at end of file +) \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt index e650d2b8ea..a0468f3100 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrency.kt @@ -188,11 +188,11 @@ sealed class CryptoCurrency { return "ID(value='$value')" } - private companion object { + companion object { // should use delimiters that could be used in URL not like path or query delimiters - const val PREFIX_DELIMITER = '_' - const val SUFFIX_DELIMITER = ';' - const val DERIVATION_PATH_DELIMITER = 'd' + private const val PREFIX_DELIMITER = '_' + private const val SUFFIX_DELIMITER = ';' + private const val DERIVATION_PATH_DELIMITER = 'd' } } diff --git a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/navigation/StakingRouter.kt b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/navigation/StakingRouter.kt index 05277770a8..d2b216513e 100644 --- a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/navigation/StakingRouter.kt +++ b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/navigation/StakingRouter.kt @@ -5,10 +5,4 @@ import androidx.fragment.app.Fragment interface StakingRouter { 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" - } } \ No newline at end of file diff --git a/features/staking/impl/build.gradle.kts b/features/staking/impl/build.gradle.kts index 072e1bcd7b..1547a55bd2 100644 --- a/features/staking/impl/build.gradle.kts +++ b/features/staking/impl/build.gradle.kts @@ -63,6 +63,7 @@ dependencies { /** Common */ implementation(projects.common.ui) + implementation(projects.common.routing) /** Libs */ implementation(projects.libs.crypto) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 7144e4d5ed..68a13a920a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.staking.impl.presentation.viewmodel +import android.os.Bundle import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel @@ -7,6 +8,8 @@ import androidx.lifecycle.viewModelScope import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase 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.staking.model.Yield import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase @@ -52,14 +55,18 @@ internal class StakingViewModel @Inject constructor( var stakingStateRouter: StakingStateRouter by Delegates.notNull() private set - private val cryptoCurrencyId: CryptoCurrency.ID = savedStateHandle[StakingRouter.CRYPTO_CURRENCY_ID_KEY] + private val cryptoCurrencyId: CryptoCurrency.ID = savedStateHandle.get( + AppRoute.Staking.CRYPTO_CURRENCY_ID_KEY, + ) + ?.let { it.unbundle(CryptoCurrency.ID.serializer()) } ?: error("This screen can't be opened without `CryptoCurrency.ID`") - private val userWalletId: UserWalletId = savedStateHandle.get(StakingRouter.USER_WALLET_ID_KEY) - ?.let { stringValue -> UserWalletId(stringValue) } + private val userWalletId: UserWalletId = savedStateHandle.get(AppRoute.Staking.USER_WALLET_ID_KEY) + ?.let { it.unbundle(UserWalletId.serializer()) } ?: error("This screen can't be opened without `UserWalletId`") - private val yield: Yield = savedStateHandle[StakingRouter.YIELD_KEY] + private val yield: Yield = savedStateHandle.get(AppRoute.Staking.YIELD_KEY) + ?.let { it.unbundle(Yield.serializer()) } ?: error("This screen can't be opened without `Yield`") private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt index 880d92fba4..c2ddee4828 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt @@ -5,6 +5,7 @@ import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.core.navigation.share.ShareManager 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.wallets.models.UserWalletId 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, + ), + ) + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt index 56eead22a5..7060190ec5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt @@ -1,5 +1,6 @@ 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.wallets.models.UserWalletId import com.tangem.features.tokendetails.navigation.TokenDetailsRouter @@ -16,4 +17,6 @@ internal interface InnerTokenDetailsRouter : TokenDetailsRouter { fun share(text: String) fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) + + fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yield: Yield) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 8542738099..09a908e65f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.core.os.bundleOf import androidx.lifecycle.* import androidx.paging.cachedIn 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.deeplink.DeepLinksRegistry 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.components.bottomsheets.tokenreceive.AddressModel 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.ui.components.exchange.ExchangeStatusBottomSheetConfig 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.impl.R 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() yield ?: error("Staking is unavailable") - reduxStateHolder.dispatch( - 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, - ), - ), - ) + router.openStaking(userWalletId, cryptoCurrency, yield) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 40841c6327..ab66fbb133 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -212,6 +212,7 @@ include(":domain:feedback") include(":domain:qr-scanning") include(":domain:qr-scanning:models") include(":domain:staking") +include(":domain:staking:models") include(":domain:wallet-connect") // endregion Domain modules