From 67261ff1504e72ad697e5786b3e3fdb7e4fcbaa3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 7 Feb 2025 12:25:03 +0300 Subject: [PATCH 01/15] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/MainActivity.kt | 3 +++ .../tokendetails/viewmodels/TokenDetailsViewModel.kt | 5 +++-- version.properties | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 48f1917235..3bde9503f5 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -452,6 +452,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac override fun onDestroy() { intentProcessor.removeAll() + // workaround: kill process when activity destroy to avoid state when lock() wallets + // and navigation to unlock screen was skipped because system kills activity but not process + android.os.Process.killProcess(android.os.Process.myPid()) super.onDestroy() } 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 d3ffbeb33b..2d974c6879 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 @@ -139,7 +139,9 @@ internal class TokenDetailsViewModel @Inject constructor( ?.unbundle(CryptoCurrency.serializer()) ?: error("This screen can't be opened without `CryptoCurrency`") - private val userWallet: UserWallet + private val userWallet: UserWallet by lazy { + getUserWalletUseCase(userWalletId).getOrNull() ?: error("UserWallet not found") + } lateinit var router: InnerTokenDetailsRouter @@ -203,7 +205,6 @@ internal class TokenDetailsViewModel @Inject constructor( ), ), ) - userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: error("UserWallet not found") } private fun onBuyCurrencyDeepLink(externalTxId: String) { diff --git a/version.properties b/version.properties index b87bff9bef..c5f50bcef8 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -versionName=5.20.0 \ No newline at end of file +versionName=5.20.1 \ No newline at end of file From e83e2885594bbfac90138fe79c306f7c3018e40b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Feb 2025 11:39:47 +0300 Subject: [PATCH 02/15] Updated on 2026-08-14 --- .../main/java/com/tangem/domain/common/util/ScanResponseExt.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExt.kt b/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExt.kt index 3c24fb103f..3314e43f63 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExt.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/util/ScanResponseExt.kt @@ -73,6 +73,7 @@ private fun ScanResponse.hasDerivation(curve: EllipticCurve, derivationPath: Der * @return null if wallet is not multi-currency or total cards count */ fun ScanResponse.getCardsCount(): Int? { + if (cardTypesResolver.isTangemTwins()) return 2 if (!cardTypesResolver.isMultiwalletAllowed()) return null return when (val status = card.backupStatus) { From c65a1ee4182e82f1b67dac8730e7c412f1f25884 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Feb 2025 11:55:32 +0300 Subject: [PATCH 03/15] Updated on 2026-08-14 --- .../transformers/SetCryptoCurrencyActionsTransformer.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt index c541164b5f..136fb6ffa8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetCryptoCurrencyActionsTransformer.kt @@ -44,7 +44,7 @@ internal class SetCryptoCurrencyActionsTransformer( when (action) { is TokenActionsState.ActionState.Buy -> { WalletManageButton.Buy( - enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None, + enabled = true, dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, onClick = { clickIntents.onBuyClick( @@ -56,7 +56,7 @@ internal class SetCryptoCurrencyActionsTransformer( } is TokenActionsState.ActionState.Receive -> { WalletManageButton.Receive( - enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None, + enabled = true, dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, onClick = { clickIntents.onReceiveClick(cryptoCurrencyStatus = cryptoCurrencyStatus) @@ -68,7 +68,7 @@ internal class SetCryptoCurrencyActionsTransformer( } is TokenActionsState.ActionState.Sell -> { WalletManageButton.Sell( - enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None, + enabled = true, dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, onClick = { clickIntents.onSellClick( @@ -80,7 +80,7 @@ internal class SetCryptoCurrencyActionsTransformer( } is TokenActionsState.ActionState.Send -> { WalletManageButton.Send( - enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None, + enabled = true, dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None, onClick = { clickIntents.onSendClick( From 81ae60fcd1f36f11fb46e30b2e9694e8a7167e90 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Feb 2025 14:01:54 +0300 Subject: [PATCH 04/15] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/DecomposeFragment.kt | 9 +++++++-- gradle/dependencies.toml | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt b/app/src/main/java/com/tangem/tap/DecomposeFragment.kt index ad37670303..b4ac28e100 100644 --- a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt +++ b/app/src/main/java/com/tangem/tap/DecomposeFragment.kt @@ -1,5 +1,6 @@ package com.tangem.tap +import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.core.os.bundleOf @@ -20,11 +21,15 @@ internal class DecomposeFragment : ComposeFragment() { @Inject override lateinit var uiDependencies: UiDependencies - private val component by lazy(mode = LazyThreadSafetyMode.NONE) { + private lateinit var component: ComposableContentComponent + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val tag = requireArguments().getString(TAG_KEY) val builder = componentsBuilders[tag] - requireNotNull(builder?.build()) { + component = requireNotNull(builder?.build()) { "Component builder is not set, call newInstance() for DecomposeFragment creation first." } } diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index e31e770f5a..11c13809b2 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -83,7 +83,7 @@ listenableFuture = "1.0" swipeRefreshLayout = "1.1.0" web3j = "4.10.1" leakcanary = "2.13" -decompose = "2.2.2" +decompose = "2.2.3" room = "2.6.1" markdown = "0.7.2" markdownComposeView = "0.5.4" From e32d1a2b658fbbc897606221425c7f866d17bff2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Feb 2025 14:22:21 +0300 Subject: [PATCH 05/15] Updated on 2026-08-14 --- .../data/tokens/repository/DefaultCurrenciesRepository.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index d6d0caaf7d..6e893fe151 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -107,9 +107,10 @@ internal class DefaultCurrenciesRepository( ) val currenciesToAdd = populateCurrenciesWithMissedCoins( - currencies = filterAlreadyAddedCurrencies(savedCurrencies.tokens, currencies), - ) - + currencies = currencies, + ).let { + filterAlreadyAddedCurrencies(savedCurrencies.tokens, it) + } val updatedResponse = savedCurrencies.copy( tokens = savedCurrencies.tokens + currenciesToAdd.map(userTokensResponseFactory::createResponseToken), ) From 592351f421ab458e830ec422979cc6fca22e170b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Feb 2025 13:35:09 +0400 Subject: [PATCH 06/15] Updated on 2026-08-14 --- .../repository/DefaultQuotesRepository.kt | 16 ++++++++- .../BaseCurrencyStatusOperations.kt | 36 +++++++++++++++---- .../CachedCurrenciesStatusesOperations.kt | 24 ++++++------- .../CurrenciesStatusesOperations.kt | 7 ++++ .../domain/GetSingleWalletWarningsFactory.kt | 15 ++++++++ 5 files changed, 77 insertions(+), 21 deletions(-) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt index 0874594282..1a40fa78b5 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt @@ -35,8 +35,22 @@ internal class DefaultQuotesRepository( private var quotesFetchedForAppCurrency: String? = null private val mutex = Mutex() + @OptIn(ExperimentalCoroutinesApi::class) override fun getQuotesUpdates(currenciesIds: Set): Flow> { - return quotesStore.get(currenciesIds) + return appPreferencesStore.getObject( + key = PreferencesKeys.SELECTED_APP_CURRENCY_KEY, + ) + .distinctUntilChanged() + .filterNotNull() + .flatMapLatest { appCurrency -> + fetchExpiredQuotes( + currenciesIds = currenciesIds, + appCurrencyId = appCurrency.id, + refresh = true, + ) + + quotesStore.get(currenciesIds) + } .flowOn(dispatchers.io) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index 474974bf82..f765bd2dec 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -17,6 +17,7 @@ import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch /** * Base operations for working with currency status @@ -28,6 +29,7 @@ import kotlinx.coroutines.flow.* * [REDACTED_AUTHOR] */ +@Suppress("LargeClass") abstract class BaseCurrencyStatusOperations( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, @@ -44,6 +46,13 @@ abstract class BaseCurrencyStatusOperations( network: Network, ): EitherFlow> + protected abstract suspend fun fetchComponents( + userWalletId: UserWalletId, + networks: Set, + currenciesIds: Set, + currencies: List, + ): Either + suspend fun getCurrencyStatusFlow( userWalletId: UserWalletId, currencyId: CryptoCurrency.ID, @@ -93,13 +102,26 @@ abstract class BaseCurrencyStatusOperations( val yieldBalanceFlow = getYieldBalance(userWalletId = userWalletId, cryptoCurrency = currency) - return combine(quoteFlow, statusFlow, yieldBalanceFlow) { maybeQuote, maybeNetworkStatus, maybeYieldBalance -> - currencyStatusProxyCreator.createCurrencyStatus( - currency = currency, - maybeQuote = maybeQuote, - maybeNetworkStatus = maybeNetworkStatus, - maybeYieldBalance = maybeYieldBalance, - ) + return channelFlow { + launch { + fetchComponents( + userWalletId = userWalletId, + networks = nonEmptySetOf(currency.network), + currenciesIds = nonEmptySetOf(currency.id), + currencies = nonEmptyListOf(currency), + ) + } + + combine(quoteFlow, statusFlow, yieldBalanceFlow) { maybeQuote, maybeNetworkStatus, maybeYieldBalance -> + currencyStatusProxyCreator.createCurrencyStatus( + currency = currency, + maybeQuote = maybeQuote, + maybeNetworkStatus = maybeNetworkStatus, + maybeYieldBalance = maybeYieldBalance, + ) + } + .onEach(::send) + .launchIn(this) } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index 969e56d6ef..c06a0481bd 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -1,7 +1,7 @@ package com.tangem.domain.tokens.operations import arrow.core.* -import arrow.core.raise.* +import arrow.core.raise.recover import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.core.lce.lce @@ -98,14 +98,14 @@ class CachedCurrenciesStatusesOperations( .launchIn(scope = this) } - private suspend fun Raise.fetchComponents( + override suspend fun fetchComponents( userWalletId: UserWalletId, - networks: NonEmptySet, - currenciesIds: NonEmptySet, - currencies: NonEmptyList, - ) = coroutineScope { - catch( - block = { + networks: Set, + currenciesIds: Set, + currencies: List, + ): Either { + return coroutineScope { + Either.catch { awaitAll( async { networksRepository.fetchNetworkStatuses(userWalletId, networks) }, async { @@ -114,11 +114,9 @@ class CachedCurrenciesStatusesOperations( }, async { stakingRepository.fetchMultiYieldBalance(userWalletId, currencies) }, ) - }, - catch = { - raise(TokenListError.DataError(it)) - }, - ) + } + .map { } + } } private fun createCurrenciesStatuses( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index 8178ee7ecd..e71d7a55f4 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -43,6 +43,13 @@ class CurrenciesStatusesOperations( .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } } + override suspend fun fetchComponents( + userWalletId: UserWalletId, + networks: Set, + currenciesIds: Set, + currencies: List, + ): Either = Unit.right() + sealed class Error { data object EmptyCurrencies : Error() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt index 21f40750f2..d36afb7f8d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetSingleWalletWarningsFactory.kt @@ -4,6 +4,7 @@ import arrow.core.Either import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.models.StatusSource import com.tangem.domain.settings.IsReadyToShowRateAppUseCase import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.error.CurrencyStatusError @@ -42,6 +43,8 @@ internal class GetSingleWalletWarningsFactory @Inject constructor( ) { maybePrimaryCurrencyStatus, isReadyToShowRating, isNeedToBackup, userWallets -> readyForRateAppNotification = true buildList { + addUsedOutdatedDataNotification(maybePrimaryCurrencyStatus) + addCriticalNotifications( cardTypesResolver = cardTypesResolver, ) @@ -68,6 +71,18 @@ internal class GetSingleWalletWarningsFactory @Inject constructor( } } + private fun MutableList.addUsedOutdatedDataNotification( + maybePrimaryCurrencyStatus: Either, + ) { + addIf( + element = WalletNotification.UsedOutdatedData, + condition = maybePrimaryCurrencyStatus.fold( + ifLeft = { false }, + ifRight = { it.value.source == StatusSource.ONLY_CACHE }, + ), + ) + } + private fun MutableList.addCriticalNotifications(cardTypesResolver: CardTypesResolver) { addIf( element = WalletNotification.Critical.DevCard, From 104d60aebd8df546bec567267c924117934d8962 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Feb 2025 17:30:29 +0400 Subject: [PATCH 07/15] Updated on 2026-08-14 --- .../tokens/repository/DefaultQuotesRepository.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt index 1a40fa78b5..4957144d0d 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt @@ -46,7 +46,7 @@ internal class DefaultQuotesRepository( fetchExpiredQuotes( currenciesIds = currenciesIds, appCurrencyId = appCurrency.id, - refresh = true, + refresh = false, ) quotesStore.get(currenciesIds) @@ -120,6 +120,7 @@ internal class DefaultQuotesRepository( val expiredCurrenciesIds = filterExpiredCurrenciesIds( currenciesIds = currenciesIds, + appCurrencyId = appCurrencyId, refresh = refresh || quotesFetchedForAppCurrency != appCurrencyId, ) if (expiredCurrenciesIds.isEmpty()) return @@ -150,7 +151,7 @@ internal class DefaultQuotesRepository( onError = { error -> Timber.e(error) - cacheRegistry.invalidate(rawCurrenciesIds.map { getQuoteCacheKey(it) }) + cacheRegistry.invalidate(rawCurrenciesIds.map { getQuoteCacheKey(it, appCurrencyId) }) quotesStore.storeEmptyQuotes(currenciesIds = rawCurrenciesIds) }, ) @@ -158,12 +159,13 @@ internal class DefaultQuotesRepository( private suspend fun filterExpiredCurrenciesIds( currenciesIds: Set, + appCurrencyId: String, refresh: Boolean, ): Set { return currenciesIds.fold(hashSetOf()) { acc, currencyId -> if (currencyId !in acc) { cacheRegistry.invokeOnExpire( - key = getQuoteCacheKey(currencyId), + key = getQuoteCacheKey(currencyId, appCurrencyId), skipCache = refresh, block = { acc.add(currencyId) }, ) @@ -172,5 +174,7 @@ internal class DefaultQuotesRepository( } } - private fun getQuoteCacheKey(rawCurrencyId: CryptoCurrency.RawID): String = "quote_${rawCurrencyId.value}" + private fun getQuoteCacheKey(rawCurrencyId: CryptoCurrency.RawID, appCurrencyId: String): String { + return "quote_${rawCurrencyId.value}_$appCurrencyId" + } } \ No newline at end of file From 34291959e51d43aebaf1133765b32a648321ef4f Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Feb 2025 18:22:47 +0300 Subject: [PATCH 08/15] Updated on 2026-08-14 --- .../domain/markets/SaveMarketTokensUseCase.kt | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt index d944b5f9de..f5b43a9bee 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt @@ -31,36 +31,40 @@ class SaveMarketTokensUseCase( addedNetworks: Set, removedNetworks: Set, ): Either = Either.catch { - currenciesRepository.removeCurrencies( - userWalletId = userWalletId, - currencies = removedNetworks.mapNotNull { + if (removedNetworks.isNotEmpty()) { + currenciesRepository.removeCurrencies( + userWalletId = userWalletId, + currencies = removedNetworks.mapNotNull { + marketsTokenRepository.createCryptoCurrency( + userWalletId = userWalletId, + token = tokenMarketParams, + network = it, + ) + }, + ) + } + + if (addedNetworks.isNotEmpty()) { + derivationsRepository.derivePublicKeysByNetworkIds( + userWalletId = userWalletId, + networkIds = addedNetworks.map { Network.ID(it.networkId) }, + ) + + val addedCurrencies = addedNetworks.mapNotNull { marketsTokenRepository.createCryptoCurrency( userWalletId = userWalletId, token = tokenMarketParams, network = it, ) - }, - ) + } - derivationsRepository.derivePublicKeysByNetworkIds( - userWalletId = userWalletId, - networkIds = addedNetworks.map { Network.ID(it.networkId) }, - ) + currenciesRepository.addCurrencies(userWalletId = userWalletId, currencies = addedCurrencies) - val addedCurrencies = addedNetworks.mapNotNull { - marketsTokenRepository.createCryptoCurrency( + networksRepository.getNetworkStatusesSync( userWalletId = userWalletId, - token = tokenMarketParams, - network = it, + networks = addedCurrencies.map(CryptoCurrency::network).toSet(), + refresh = true, ) } - - currenciesRepository.addCurrencies(userWalletId = userWalletId, currencies = addedCurrencies) - - networksRepository.getNetworkStatusesSync( - userWalletId = userWalletId, - networks = addedCurrencies.map(CryptoCurrency::network).toSet(), - refresh = true, - ) } } \ No newline at end of file From 193dea813ee1c29744c430368011b1c9c0eec7e4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Mar 2025 13:54:22 +0300 Subject: [PATCH 09/15] Updated on 2026-08-14 --- core/res/src/main/res/values-ja/strings.xml | 4 +++- core/res/src/main/res/values-ru/strings.xml | 3 ++- core/res/src/main/res/values-uk-rUA/strings.xml | 1 + core/res/src/main/res/values/strings.xml | 4 +++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index a89e339efe..aedabcb589 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -6,7 +6,7 @@ これには数秒かかる場合があります。しばらくしてからもう一度お試しください。 データはまだ読み込まれていません。 この操作は現在利用できません。しばらくしてからもう一度お試しいただくか、画面を下にスワイプしてデータを更新してください。 - ボタンは使用できません + アクションは利用できません トークンを選択 トークンが見つかりませんか?メインページのマーケットセクションに移動し、ポートフォリオに追加してスワップできるようにします。 選択したトークンと交換できるトークンがありません。別のトークンを選択してください。 @@ -913,8 +913,10 @@ 元に戻す この操作は現在利用できません。しばらくしてからもう一度お試しください。 %sの買付は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 + カスタムトークンでは使用できません。 売却できる資金がありません。アカウントに入金して、売却できるようにしてください。 送金する資金がありません。アカウントに入金して、送金できるようにしてください。 + データはまだ読み込まれていません。数秒かかる場合があります。しばらくしてからもう一度お試しください。 %sのスワップは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 表示残高はキャッシュにより古くなっている可能性があります。 %sネットワーク上の保留中の取引が完了すると、資金の売却が可能になります。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index e7e9bccc89..ad13b72401 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -6,7 +6,7 @@ Это может занять несколько секунд. Пожалуйста, попробуйте позже. Данные ещё не загрузились Это действие в данный момент недоступно, пожалуйста, попробуйте позже или обновите данные, сделав свайп экрана вниз. - Кнопка недоступна + Действие недоступно Выберите токен Не нашли свой токен? Перейдите в раздел «Рынок» на главной странице и добавьте его в свой портфель для обмена. Нет доступных вариантов для обмена с выбранным токеном, пожалуйста, выберите другой. @@ -1113,6 +1113,7 @@ Нравится Tangem? Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его Необходима плата за аренду сети + Требуется действие Вы обращались в службу поддержки через приложение в течение 7 дней после создания кошелька? Если обращались или не уверены, нажмите «Да» и следуйте инструкциям. Спасибо! Всё в порядке! Дальнейшие действия не требуются. Вы будете перенаправлены на официальный сайт Tangem. Пожалуйста, прочитайте и выполните указанные там инструкции. diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index eaaa71beea..c577030e59 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -1111,6 +1111,7 @@ Подобається Tangem? Вам необхідно провести асоціацію токена, щоб мати можливість приймати його Необхідна плата за оренду мережі + Потрібна дія Чи зверталися в службу підтримки через додаток протягом 7 днів після створення гаманця? Якщо зверталися або не впевнені, натисніть «Так» і дотримуйтесь інструкцій. Дякуємо! Все добре! Подальших дій не потрібно. Ви будете перенаправлені на офіційний сайт Tangem. Будь ласка, прочитайте та виконайте вказані там інструкції. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 258318407d..5b8998822d 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -6,7 +6,7 @@ This may take a few seconds. Please try again later. The data has not loaded yet. The action is currently unavailable. Please try again later or refresh the data by swiping down on the screen. - Button is unavailable + Action is unavailable Choose the Token Can’t find your token? Go to the Market section on the main page and add it to your portfolio for swapping. There are no available tokens to swap with the selected token. Please choose another one. @@ -923,8 +923,10 @@ Undo This operation is currently unavailable. Please try again later. Buying %s is not supported by current providers, but we are working to add more options. + Action is not available for custom tokens. You do not have funds to sell. Top up your account to be able to sell funds from it. You do not have funds to send. Top up your account to be able to send funds from it. + The data has not loaded yet. This may take a few seconds. Please try again later. Swapping %s is not supported by current providers, but we are working to add more options. The displayed balance might be outdated due to caching. Selling funds will be available once the pending transaction(s) on the %s network is complete. From ca286232ba061e97bb091b689acea8174edd2d8b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Mar 2025 15:30:31 +0200 Subject: [PATCH 10/15] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 4 +- .../com/tangem/common/routing/AppRoute.kt | 5 +- .../data/staking/DefaultStakingRepository.kt | 6 + .../tangem/domain/staking/GetYieldUseCase.kt | 6 + .../staking/repositories/StakingRepository.kt | 2 + .../repository/MockStakingRepository.kt | 150 +++++++++--------- .../impl/model/TokenActionsHandler.kt | 2 +- .../features/staking/api/StakingComponent.kt | 3 +- .../impl/presentation/model/StakingModel.kt | 13 +- .../router/DefaultTokenDetailsRouter.kt | 5 +- .../router/InnerTokenDetailsRouter.kt | 3 +- .../viewmodels/TokenDetailsViewModel.kt | 2 +- .../WalletCurrencyActionsClickIntents.kt | 2 +- 13 files changed, 111 insertions(+), 92 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 1efd190304..f1217ef2d0 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -219,7 +219,7 @@ internal class ChildFactory @Inject constructor( params = StakingComponent.Params( userWalletId = route.userWalletId, cryptoCurrencyId = route.cryptoCurrencyId, - yield = route.yield, + yieldId = route.yieldId, ), componentFactory = stakingComponentFactory, ) @@ -376,7 +376,7 @@ internal class ChildFactory @Inject constructor( params = StakingComponent.Params( userWalletId = route.userWalletId, cryptoCurrencyId = route.cryptoCurrencyId, - yield = route.yield, + yieldId = route.yieldId, ), componentFactory = stakingComponentFactory, ) 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 6028d6a876..d7a1d74eeb 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 @@ -10,7 +10,6 @@ import com.tangem.domain.markets.TokenMarketParams import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.onramp.model.OnrampSource import com.tangem.domain.qrscanning.models.SourceType -import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId import kotlinx.serialization.Serializable @@ -257,8 +256,8 @@ sealed class AppRoute(val path: String) : Route { data class Staking( val userWalletId: UserWalletId, val cryptoCurrencyId: CryptoCurrency.ID, - val yield: Yield, - ) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/${yield.id}") + val yieldId: String, + ) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId") @Serializable data object PushNotification : AppRoute(path = "/push_notification") diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 620bad0ce9..8956f6f26a 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -133,6 +133,12 @@ internal class DefaultStakingRepository( } } + override suspend fun getYield(yieldId: String): Yield { + return withContext(dispatchers.io) { + getEnabledYieldsSync().find { it.id == yieldId } ?: error("Staking is unavailable") + } + } + override suspend fun getActions( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetYieldUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetYieldUseCase.kt index 3bf59574a6..383ca53ad5 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetYieldUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetYieldUseCase.kt @@ -20,4 +20,10 @@ class GetYieldUseCase( .catch { stakingRepository.getYield(cryptoCurrencyId, symbol) } .mapLeft { stakingErrorResolver.resolve(it) } } + + suspend operator fun invoke(yieldId: String): Either { + return Either + .catch { stakingRepository.getYield(yieldId) } + .mapLeft { stakingErrorResolver.resolve(it) } + } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 35d7f0c0d9..ffd12cda37 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -33,6 +33,8 @@ interface StakingRepository { suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield + suspend fun getYield(yieldId: String): Yield + suspend fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): StakingAvailability suspend fun getActions( diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt index dbb5002018..37fe931195 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt @@ -39,78 +39,9 @@ class MockStakingRepository : StakingRepository { rewardSchedule = Yield.Metadata.RewardSchedule.DAY, ) - override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield = Yield( - id = "1", - token = Token( - name = "Solana", - network = NetworkType.SOLANA, - symbol = "SOL", - decimals = 18, - address = null, - coinGeckoId = "solana", - logoURI = null, - isPoints = null, - ), - tokens = listOf(), - args = Yield.Args( - enter = Yield.Args.Enter( - addresses = Yield.Args.Enter.Addresses( - address = AddressArgument( - required = false, - network = null, - minimum = null, - maximum = null, - ), - additionalAddresses = mapOf(), - ), - args = mapOf(), - ), - exit = null, - ), - status = Yield.Status(enter = false, exit = null), - apy = 1.toBigDecimal(), - rewardRate = 2.3, - rewardType = Yield.RewardType.APR, - metadata = Yield.Metadata( - name = "Yield", - logoUri = "", - description = "", - documentation = null, - gasFeeToken = Token( - name = "Solana", - network = NetworkType.SOLANA, - symbol = "SOL", - decimals = 18, - address = null, - coinGeckoId = null, - logoURI = null, - isPoints = null, - ), - token = Token( - name = "Solana", - network = NetworkType.SOLANA, - symbol = "SOL", - decimals = 18, - address = null, - coinGeckoId = null, - logoURI = null, - isPoints = null, - ), - tokens = listOf(), - type = "auto", - rewardSchedule = Yield.Metadata.RewardSchedule.DAY, - cooldownPeriod = Yield.Metadata.Period(days = 1), - warmupPeriod = Yield.Metadata.Period(days = 1), - rewardClaiming = Yield.Metadata.RewardClaiming.AUTO, - defaultValidator = null, - minimumStake = null, - supportsMultipleValidators = false, - revshare = Yield.Metadata.Enabled(enabled = false), - fee = Yield.Metadata.Enabled(enabled = false), - ), - validators = listOf(), - isAvailable = false, - ) + override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield = yield + + override suspend fun getYield(yieldId: String) = yield override suspend fun getStakingAvailability( userWalletId: UserWalletId, @@ -245,4 +176,79 @@ class MockStakingRepository : StakingRepository { override fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval = StakingApproval.Empty override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean = false + + private companion object { + val yield = Yield( + id = "1", + token = Token( + name = "Solana", + network = NetworkType.SOLANA, + symbol = "SOL", + decimals = 18, + address = null, + coinGeckoId = "solana", + logoURI = null, + isPoints = null, + ), + tokens = listOf(), + args = Yield.Args( + enter = Yield.Args.Enter( + addresses = Yield.Args.Enter.Addresses( + address = AddressArgument( + required = false, + network = null, + minimum = null, + maximum = null, + ), + additionalAddresses = mapOf(), + ), + args = mapOf(), + ), + exit = null, + ), + status = Yield.Status(enter = false, exit = null), + apy = 1.toBigDecimal(), + rewardRate = 2.3, + rewardType = Yield.RewardType.APR, + metadata = Yield.Metadata( + name = "Yield", + logoUri = "", + description = "", + documentation = null, + gasFeeToken = Token( + name = "Solana", + network = NetworkType.SOLANA, + symbol = "SOL", + decimals = 18, + address = null, + coinGeckoId = null, + logoURI = null, + isPoints = null, + ), + token = Token( + name = "Solana", + network = NetworkType.SOLANA, + symbol = "SOL", + decimals = 18, + address = null, + coinGeckoId = null, + logoURI = null, + isPoints = null, + ), + tokens = listOf(), + type = "auto", + rewardSchedule = Yield.Metadata.RewardSchedule.DAY, + cooldownPeriod = Yield.Metadata.Period(days = 1), + warmupPeriod = Yield.Metadata.Period(days = 1), + rewardClaiming = Yield.Metadata.RewardClaiming.AUTO, + defaultValidator = null, + minimumStake = null, + supportsMultipleValidators = false, + revshare = Yield.Metadata.Enabled(enabled = false), + fee = Yield.Metadata.Enabled(enabled = false), + ), + validators = listOf(), + isAvailable = false, + ) + } } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index 471770d6a4..7b854e3c07 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -179,7 +179,7 @@ internal class TokenActionsHandler @AssistedInject constructor( AppRoute.Staking( userWalletId = cryptoCurrencyData.userWallet.walletId, cryptoCurrencyId = cryptoCurrencyData.status.currency.id, - yield = yield, + yieldId = yield.id, ), ) } diff --git a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/StakingComponent.kt b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/StakingComponent.kt index 207a7bdc94..ad3a44fc15 100644 --- a/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/StakingComponent.kt +++ b/features/staking/api/src/main/kotlin/com/tangem/features/staking/api/StakingComponent.kt @@ -2,7 +2,6 @@ package com.tangem.features.staking.api import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId @@ -11,7 +10,7 @@ interface StakingComponent : ComposableContentComponent { data class Params( val userWalletId: UserWalletId, val cryptoCurrencyId: CryptoCurrency.ID, - val yield: Yield, + val yieldId: String, ) interface Factory : ComponentFactory diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index bb2c07e2c4..01d1f570c7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -26,10 +26,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType -import com.tangem.domain.staking.GetActionsUseCase -import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase -import com.tangem.domain.staking.IsAnyTokenStakedUseCase -import com.tangem.domain.staking.IsApproveNeededUseCase +import com.tangem.domain.staking.* import com.tangem.domain.staking.analytics.StakeScreenSource import com.tangem.domain.staking.analytics.StakingAnalyticsEvent import com.tangem.domain.staking.model.StakingApproval @@ -81,6 +78,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import timber.log.Timber import java.math.BigDecimal import java.util.concurrent.CopyOnWriteArrayList @@ -119,6 +117,7 @@ internal class StakingModel @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val getActionsUseCase: GetActionsUseCase, + private val getYieldUseCase: GetYieldUseCase, private val paramsInterceptorHolder: ParamsInterceptorHolder, private val shareManager: ShareManager, @DelayedWork private val coroutineScope: CoroutineScope, @@ -139,7 +138,11 @@ internal class StakingModel @Inject constructor( private val cryptoCurrencyId: CryptoCurrency.ID = params.cryptoCurrencyId private val userWalletId: UserWalletId = params.userWalletId - private val yield: Yield = params.yield + private val yield: Yield = runBlocking { + getYieldUseCase(params.yieldId).getOrElse { + error("yield must be not null") + } + } private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() private var processingActions: List = emptyList() 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 d8a3eb4733..f0e4b2fee5 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,7 +5,6 @@ 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.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.tokendetails.presentation.TokenDetailsFragment @@ -39,12 +38,12 @@ internal class DefaultTokenDetailsRouter( ) } - override fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yield: Yield) { + override fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yieldId: String) { router.push( AppRoute.Staking( userWalletId = userWalletId, cryptoCurrencyId = cryptoCurrency.id, - yield = yield, + yieldId = yieldId, ), ) } 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 e4a143ccd1..4f40d92a2b 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,6 +1,5 @@ package com.tangem.feature.tokendetails.presentation.router -import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.tokendetails.navigation.TokenDetailsRouter @@ -18,7 +17,7 @@ internal interface InnerTokenDetailsRouter : TokenDetailsRouter { fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) - fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yield: Yield) + fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yieldId: String) fun openOnrampSuccess(externalTxId: String) } \ 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 68fe1de07a..d779c92d75 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 @@ -994,7 +994,7 @@ internal class TokenDetailsViewModel @Inject constructor( error("Staking is unavailable for ${cryptoCurrency.name}") } - router.openStaking(userWalletId, cryptoCurrency, yield) + router.openStaking(userWalletId, cryptoCurrency, yield.id) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 47b8fc42ec..b9647e9365 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -409,7 +409,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( AppRoute.Staking( userWalletId = userWalletId, cryptoCurrencyId = cryptoCurrency.id, - yield = yield ?: return@launch, + yieldId = yield?.id ?: return@launch, ), ) } From a10b94fa4586c4e0dfd05b1dc742ee7625cb68d5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Mar 2025 16:45:50 +0400 Subject: [PATCH 11/15] Updated on 2026-08-14 --- .../tap/di/domain/ManageTokensDomainModule.kt | 3 ++ .../local/token/DefaultStakingBalanceStore.kt | 50 +++++++++++++------ .../local/token/StakingBalanceStore.kt | 2 +- .../data/staking/DefaultStakingRepository.kt | 20 +++++++- domain/manage-tokens/build.gradle.kts | 1 + .../managetokens/SaveManagedTokensUseCase.kt | 15 ++++++ 6 files changed, 75 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt index e15eea4635..c3520a41cf 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt @@ -4,6 +4,7 @@ import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.managetokens.* import com.tangem.domain.managetokens.repository.CustomTokensRepository import com.tangem.domain.managetokens.repository.ManageTokensRepository +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.walletmanager.WalletManagersFacade @@ -65,6 +66,7 @@ internal object ManageTokensDomainModule { currenciesRepository: CurrenciesRepository, networksRepository: NetworksRepository, derivationsRepository: DerivationsRepository, + stakingRepository: StakingRepository, ): SaveManagedTokensUseCase { return SaveManagedTokensUseCase( customTokensRepository = customTokensRepository, @@ -72,6 +74,7 @@ internal object ManageTokensDomainModule { currenciesRepository = currenciesRepository, networksRepository = networksRepository, derivationsRepository = derivationsRepository, + stakingRepository = stakingRepository, ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt index aaadb1d435..18aaa9d968 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingBalanceStore.kt @@ -72,21 +72,35 @@ internal class DefaultStakingBalanceStore( override suspend fun store(userWalletId: UserWalletId, items: Set) { coroutineScope { launch { - updateRuntimeStore(userWalletId = userWalletId) { - YieldBalanceConverter(isCached = false).convertSet(input = items) + val newBalances = YieldBalanceConverter(isCached = false).convertSet(input = items) + + runtimeStore.update(default = emptyMap()) { saved -> + saved.toMutableMap().apply { + this[userWalletId] = saved[userWalletId] + ?.addOrReplace(newBalances) { old, new -> + old.integrationId == new.integrationId && old.address == new.address + } + ?: newBalances + } } } launch { storeInPersistenceStore(userWalletId = userWalletId, items = items) } } } - override suspend fun refresh(userWalletId: UserWalletId) { + override suspend fun refresh(userWalletId: UserWalletId, addressWithIntegrationIdMap: Map) { updateRuntimeStore(userWalletId = userWalletId) { saved -> - saved.mapTo(hashSetOf()) { - when (it) { - is YieldBalance.Data -> it.copy(source = StatusSource.CACHE) - is YieldBalance.Empty -> it.copy(source = StatusSource.CACHE) - is YieldBalance.Error -> it + saved.mapTo(hashSetOf()) { balance -> + val refreshIntegrationId = addressWithIntegrationIdMap[balance.address] + + if (balance.integrationId == refreshIntegrationId) { + when (balance) { + is YieldBalance.Data -> balance.copy(source = StatusSource.CACHE) + is YieldBalance.Empty -> balance.copy(source = StatusSource.CACHE) + is YieldBalance.Error -> balance + } + } else { + balance } } } @@ -114,11 +128,13 @@ internal class DefaultStakingBalanceStore( ) { val newBalance = YieldBalanceConverter(isCached = false).convert(value = item) - val balances = getSyncOrNull(userWalletId) - ?.addOrReplace(newBalance) { it.integrationId == integrationId && it.address == address } - ?: setOf(newBalance) - - updateRuntimeStore(userWalletId = userWalletId) { balances } + runtimeStore.update(default = emptyMap()) { saved -> + saved.toMutableMap().apply { + this[userWalletId] = saved[userWalletId] + ?.addOrReplace(newBalance) { it.integrationId == integrationId && it.address == address } + ?: setOf(newBalance) + } + } } private suspend fun updateRuntimeStore( @@ -135,7 +151,11 @@ internal class DefaultStakingBalanceStore( private suspend fun storeInPersistenceStore(userWalletId: UserWalletId, items: Set) { persistenceStore.updateData { current -> current.toMutableMap().apply { - this[userWalletId.stringValue] = items + this[userWalletId.stringValue] = current[userWalletId.stringValue] + ?.addOrReplace(items = items) { old, new -> + old.integrationId == new.integrationId && old.addresses.address == new.addresses.address + } + ?: items } } } @@ -159,6 +179,8 @@ internal class DefaultStakingBalanceStore( cachedBalances: Set, runtimeBalances: Set, ): Set { + if (runtimeBalances.isEmpty()) return cachedBalances + return runtimeBalances .map { runtime -> runtime.takeIf { runtime !is YieldBalance.Error } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt index c09987f7f4..7722a21c10 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingBalanceStore.kt @@ -27,5 +27,5 @@ interface StakingBalanceStore { /** Store [item] by [userWalletId], [integrationId] and [address] */ suspend fun store(userWalletId: UserWalletId, integrationId: String, address: String, item: YieldBalanceWrapperDTO) - suspend fun refresh(userWalletId: UserWalletId) + suspend fun refresh(userWalletId: UserWalletId, addressWithIntegrationIdMap: Map) } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 8956f6f26a..32ecdae0d4 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -403,13 +403,31 @@ internal class DefaultStakingRepository( ?: YieldBalance.Error(integrationId, address) } + @Suppress("LongMethod") override suspend fun fetchMultiYieldBalance( userWalletId: UserWalletId, cryptoCurrencies: List, refresh: Boolean, ) = withContext(dispatchers.io) { if (refresh) { - stakingBalanceStore.refresh(userWalletId = userWalletId) + stakingBalanceStore.refresh( + userWalletId = userWalletId, + addressWithIntegrationIdMap = cryptoCurrencies + .mapNotNull { currency -> + val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network) + val integrationId = integrationIdMap[getIntegrationKey(currency.id)] + + if (integrationId != null) { + addresses to integrationId + } else { + null + } + } + .flatMap { (addresses, integrationId) -> + addresses.map { address -> integrationId to address.value } + } + .toMap(), + ) } cacheRegistry.invokeOnExpire( diff --git a/domain/manage-tokens/build.gradle.kts b/domain/manage-tokens/build.gradle.kts index 5ed6875afa..958503009f 100644 --- a/domain/manage-tokens/build.gradle.kts +++ b/domain/manage-tokens/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { api(projects.domain.core) implementation(projects.domain.wallets.models) implementation(projects.domain.tokens.models) + implementation(projects.domain.staking) implementation(projects.domain.tokens) implementation(projects.domain.card) implementation(projects.domain.legacy) diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt index 817a68b712..4143444b7b 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/SaveManagedTokensUseCase.kt @@ -5,6 +5,7 @@ import arrow.core.flatten import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.managetokens.model.ManagedCryptoCurrency import com.tangem.domain.managetokens.repository.CustomTokensRepository +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -18,6 +19,7 @@ class SaveManagedTokensUseCase( private val currenciesRepository: CurrenciesRepository, private val networksRepository: NetworksRepository, private val derivationsRepository: DerivationsRepository, + private val stakingRepository: StakingRepository, ) { suspend operator fun invoke( @@ -50,6 +52,8 @@ class SaveManagedTokensUseCase( existingCurrencies = existingCurrencies, currenciesToAdd = addingCurrencies, ) + + refreshUpdatedYieldBalances(userWalletId, existingCurrencies) } private suspend fun removeCurrenciesFromWalletManager( @@ -90,6 +94,17 @@ class SaveManagedTokensUseCase( ) } + private suspend fun refreshUpdatedYieldBalances( + userWalletId: UserWalletId, + existingCurrencies: List, + ) { + stakingRepository.fetchMultiYieldBalance( + userWalletId = userWalletId, + cryptoCurrencies = existingCurrencies, + refresh = true, + ) + } + /** * Determines if the [existingCurrencies] list contains a coin that corresponds * to the given [network]. From 03b555e463a812200a20ad23cac8aaafc495bcca Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Mar 2025 17:33:13 +0300 Subject: [PATCH 12/15] Updated on 2026-08-14 --- .../com/tangem/tap/LockUserWalletsTimer.kt | 53 ++++++------------- .../main/java/com/tangem/tap/MainActivity.kt | 3 +- 2 files changed, 19 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 33d7f81b88..c1ac40b684 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -2,7 +2,6 @@ package com.tangem.tap import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope import com.tangem.common.routing.AppRoute import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager @@ -17,6 +16,7 @@ internal class LockUserWalletsTimer( private val settingsRepository: SettingsRepository, private val duration: Duration = with(Duration) { 10.minutes }, private val userWalletsListManager: UserWalletsListManager, + private val coroutineScope: CoroutineScope, ) : LifecycleOwner by owner, DefaultLifecycleObserver { @@ -31,7 +31,7 @@ internal class LockUserWalletsTimer( } override fun onStart(owner: LifecycleOwner) { - owner.lifecycleScope.launch { + coroutineScope.launch { val wasApplicationStopped = settingsRepository.wasApplicationStopped() val shouldOpenWelcomeScreenOnResume = settingsRepository.shouldOpenWelcomeScreenOnResume() @@ -59,16 +59,11 @@ internal class LockUserWalletsTimer( override fun onStop(owner: LifecycleOwner) { Timber.i("Owner stopped") - owner.lifecycleScope.launch { + coroutineScope.launch { settingsRepository.setWasApplicationStopped(value = true) } } - override fun onDestroy(owner: LifecycleOwner) { - Timber.i("Owner destroyed") - stop() - } - fun restart() { if (delayJob == null) return Timber.i( @@ -92,44 +87,30 @@ internal class LockUserWalletsTimer( delayJob = createDelayJob() } - private fun stop(log: Boolean = true) { - if (log) { - Timber.i( - """ - Timer stop - |- Was started: ${delayJob?.isActive ?: false} - """.trimIndent(), - ) - } - delayJob = null - } - - private fun createDelayJob(): Job = lifecycleScope.launch(Dispatchers.Default) { + private fun createDelayJob(): Job = coroutineScope.launch { val startTime = System.currentTimeMillis() delay(duration) - if (isActive) { - val userWalletsListManager = userWalletsListManager.asLockable() ?: return@launch + val userWalletsListManager = userWalletsListManager.asLockable() ?: return@launch - if (userWalletsListManager.hasUserWallets) { - val currentTime = System.currentTimeMillis() - val wasApplicationStopped = settingsRepository.wasApplicationStopped() + if (userWalletsListManager.hasUserWallets) { + val currentTime = System.currentTimeMillis() + val wasApplicationStopped = settingsRepository.wasApplicationStopped() - Timber.i( - """ + Timber.i( + """ Finished |- App is stopped: $wasApplicationStopped |- Millis passed: ${currentTime - startTime} - """.trimIndent(), - ) + """.trimIndent(), + ) - userWalletsListManager.lock() - if (wasApplicationStopped) { - settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true) - } else { - store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } - } + userWalletsListManager.lock() + if (wasApplicationStopped) { + settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true) + } else { + store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) } } } } diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index fa76d52ce9..2ae886e907 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -327,7 +327,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac context = rootComponentContext, ) - appRouterConfig.routerScope = lifecycleScope + appRouterConfig.routerScope = mainScope appRouterConfig.componentRouter = routingComponent.router appRouterConfig.snackbarHandler = this @@ -368,6 +368,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac owner = this, settingsRepository = settingsRepository, userWalletsListManager = userWalletsListManager, + coroutineScope = mainScope, ) initIntentHandlers() From 54e15b1fa960be902344fe0a55658fff8f7fa55f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Mar 2025 20:02:43 +0300 Subject: [PATCH 13/15] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-ru/strings.xml | 1 + core/res/src/main/res/values-uk-rUA/strings.xml | 2 ++ core/res/src/main/res/values/strings.xml | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index e4e17b8896..6edea905ea 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -676,7 +676,7 @@ Scanne die Karte oder Ring, um ihre Einstellungen zu ändern. Die Änderungen wirken sich nur auf die von dir gescannte Karte oder Ring aus und haben keine Auswirkungen auf andere mit deiner Wallet verknüpften Geräte. Halte deine Karte oder Ring bereit! Sicherheitswarnung - Nein, habe ich nicht + Nein Ja, leite mich Dein Konto verfügt nicht über ausreichend Guthaben, um Kryptowährungen zu verkaufen. Bitte zahle den gewünschten Vermögenswert ein, um fortzufahren. Unzureichendes Guthaben diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index ad13b72401..8793740014 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -694,6 +694,7 @@ Отсканируйте карту или кольцо, чтобы изменить ее настройки. Изменения затронут только ту карту или кольцо, которые вы отсканировали, и не повлияют на другие устройства, привязанные к вашему кошельку. Приготовьте свой Tangem! Уведомление безопасности + Нет Да На вашем балансе недостаточно средств для продажи криптовалюты. Пожалуйста, пополните нужный актив, чтобы продолжить. Недостаточно средств diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index c577030e59..91701bc5c1 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -694,6 +694,8 @@ Відскануйте картку або кільце, щоб змінити її налаштування. Зміни торкнуться лише тої картки або кільця, які ви відсканували, і не вплинуть на інші пристрої, прив\'язані до вашого гаманця. Підготуйте свій Tangem! Оповіщення безпеки + Ні, не робив + Так На вашому балансі недостатньо коштів для продажу криптовалюти. Будь ласка, внесіть бажаний актив, щоб продовжити. Недостатній баланс Продаж криптовалюти у вашому регіоні тимчасово недоступний. Ми активно працюємо над тим, щоб додати цю можливість. Слідкуйте за нашими новинами! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 5b8998822d..9b4f9524be 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -241,6 +241,7 @@ This mechanism protects against proximity attacks on a card or ring. It will enforce a delay between reception and execution of a command. Passcode Before executing any command entailing a change of the card state, you will have to enter the passcode. + NFT Referral program Flip your device screen down to quickly hide and show balances %s hashes From 0953fc93b48a0fa92d8576924d24638464be7e28 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Mar 2025 17:03:07 +0000 Subject: [PATCH 14/15] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 3be6bacbc0..d20b17869e 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -1,7 +1,7 @@ [versions] -tangemBlockchainSdk = "release-app_5.21-963" +tangemBlockchainSdk = "develop-968" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.21-435" +tangemCardSdk = "develop-437" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem-developments8" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ From 354dc7d9b416ea4c116d2f6c9272eba030f36246 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Mar 2025 21:06:12 +0200 Subject: [PATCH 15/15] Updated on 2026-08-14 --- .../presentation/tokendetails/model/TokenDetailsModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 81bc77d74f..545c8d7f8e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -971,7 +971,7 @@ internal class TokenDetailsModel @Inject constructor( error("Staking is unavailable for ${cryptoCurrency.name}") } - router.openStaking(userWalletId, cryptoCurrency, yield) + router.openStaking(userWalletId, cryptoCurrency, yield.id) } }