diff --git a/LICENSE b/LICENSE index b64d4ccedf..f5973ff5ba 100644 --- a/LICENSE +++ b/LICENSE @@ -2,17 +2,37 @@ Tangem Proprietary and Confidential Copyright (c) 2025 Tangem AG. All rights reserved. ================================================================================ NOTICE: THIS IS NOT AN OPEN SOURCE LICENSE. -This software, including all source code, documentation, and accompanying files (the "Software") stored in this repository, is the proprietary and confidential information of Tangem AG ("Tangem"). The publication of this Software on a public platform does not grant any license, express or implied. + +This software, including all source code, documentation, and accompanying files (the "Software") stored in this repository, is the proprietary and confidential information of Tangem AG ("Tangem"). +The publication of this Software on a public platform does not grant any license, express or implied, except as explicitly stated in Section 2 below. + 1. Definitions. "Software" refers to all contents of this repository. "Protocol" refers to the proprietary communication protocol, including the sequence of, data structures, and cryptographic methods used to interact with Tangem hardware. -"SDKs" refer to the official Software Development Kits (e.g., for Android, iOS, React Native) provided by Tangem in separate repositories and under their own distinct licenses. -2. Strict Prohibition of Use. Any use of the Software, in whole or in part, is strictly prohibited without the express prior written consent of Tangem. This prohibition includes, but is not limited to, the following actions for any purpose, whether commercial or non-commercial: -- Copying, modifying, or merging the Software. -- Publishing, distributing, or sublicensing the Software. + +2. Limited License for Personal Use and Contributions. +Notwithstanding the proprietary nature of the Software, Tangem grants you a personal, non-exclusive, non-transferable, revocable license to: +- Clone, fork, and modify the Software solely for personal, non-commercial purposes (e.g., for security auditing, verification, or building the application for installation on your personal device). +- Use the Software to interact with Tangem hardware for personal needs. +- Submit proposals for changes (Pull Requests) to improve the Software. + +3. Strict Prohibition of Commercial Use and Distribution. +Except for the rights expressly granted in Section 2, any use of the Software, in whole or in part, remains strictly prohibited without the express prior written consent of Tangem. +This prohibition applies to any commercial purpose and includes, but is not limited to: +- Publishing, distributing, or sublicensing the Software or its derivatives. - Selling copies of the Software. -- Creating derivative works based on the Software. -3. Protocol Usage. Reverse-engineering, analyzing, decompiling, or attempting to recreate the Protocol is strictly prohibited. The only authorized method for third-party applications to interact with Tangem hardware is by using the official Tangem SDKs. The SDKs are governed by their own license terms (e.g., MIT License), which permit their use in third-party applications. This Software is provided for transparency and reference purposes only. -4. No Implied Rights. The act of Tangem publishing the Software to a public repository does not grant users any implied rights or licenses to use, modify, or distribute the Software. All rights not expressly granted by Tangem in a separate written agreement are reserved. -5. Legal Action. Unauthorized use, reproduction, or distribution of the Software or the Protocol may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under applicable law. -For inquiries about licensing or permissions to use the Software or the Protocol, please contact Tangem AG at: legal@tangem.com. +- Creating a competing product or service based on the Software. +- Integrating the Software or the Protocol into third-party commercial applications or services. + +4. Protocol Usage. +Reverse-engineering, decompiling, or attempting to recreate the Protocol for use in unauthorized applications is strictly prohibited. +Any interaction with Tangem hardware in commercial or distributed applications requires a separate commercial agreement with Tangem. +This Software is provided for transparency and reference purposes. + +5. No Implied Rights. +The act of Tangem publishing the Software to a public repository does not grant users any implied rights or licenses to use, modify, or distribute the Software beyond the limited permissions described in Section 2. +All rights not expressly granted by Tangem in a separate written agreement are reserved. + +6. Legal Action. +Unauthorized use, reproduction, or distribution of the Software or the Protocol may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under applicable law. +For inquiries about licensing or permissions to use the Software or the Protocol, please contact Tangem AG at: legal@tangem.com. \ No newline at end of file diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 7697824250..5fc86d29bc 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 76978242504ab90803b99e2b0575f7ebe8e69335 +Subproject commit 5fc86d29bc2c0dc7c057ab0242b2cfa3e9f48daf diff --git a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt index de65ad7aa7..e27db731ed 100644 --- a/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/url/CustomTabsUrlOpener.kt @@ -35,7 +35,7 @@ internal class CustomTabsUrlOpener : UrlOpener { private fun openUrl(url: String, context: Context) { if (url.isEmpty()) return - val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri()) + val browserIntent = Intent(Intent.ACTION_VIEW, url.trim().toUri()) runCatching { if (checkCustomTabsAvailability(context, browserIntent)) { context.startActivity(browserIntent) diff --git a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt index 9ea7cbeb84..4f7f4a0166 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt @@ -18,8 +18,7 @@ import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton -private const val DEFAULT_KEY = "tangem_pay_default_key" -private const val ORDER_ID_KEY = "tangem_pay_order_id_key" +private const val AUTH_TOKENS_DEFAULT_KEY = "tangem_pay_default_key" private const val WITHDRAW_ORDER_ID_KEY = "tangem_pay_withdraw_order_id_key" @Singleton @@ -42,13 +41,15 @@ internal class DefaultTangemPayStorage @Inject constructor( override suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String) { withContext(dispatcherProvider.io) { - secureStorage.store(key = createCustomerAddressKey(userWalletId), value = customerWalletAddress) + appPreferencesStore + .store(PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId), customerWalletAddress) } } override suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String? { return withContext(dispatcherProvider.io) { - secureStorage.getAsString(createCustomerAddressKey(userWalletId)) + appPreferencesStore.getSyncOrNull(key = PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId)) + .takeIf { !it.isNullOrEmpty() } } } @@ -58,28 +59,28 @@ internal class DefaultTangemPayStorage @Inject constructor( secureStorage.store( json.encodeToByteArray(throwOnInvalidSequence = true), - createKey(customerWalletAddress), + createAuthTokensKey(customerWalletAddress), ) } override suspend fun getAuthTokens(customerWalletAddress: String): TangemPayAuthTokens? = withContext(dispatcherProvider.io) { - secureStorage.get(createKey(customerWalletAddress)) + secureStorage.get(createAuthTokensKey(customerWalletAddress)) ?.decodeToString(throwOnInvalidSequence = true) ?.let(tokensAdapter::fromJson) } override suspend fun storeOrderId(customerWalletAddress: String, orderId: String) { withContext(dispatcherProvider.io) { - secureStorage.store( - orderId.encodeToByteArray(throwOnInvalidSequence = true), - createOrderIdKey(customerWalletAddress), - ) + appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), orderId) } } - override suspend fun getOrderId(customerWalletAddress: String): String? = withContext(dispatcherProvider.io) { - secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true) + override suspend fun getOrderId(customerWalletAddress: String): String? { + return withContext(dispatcherProvider.io) { + appPreferencesStore.getSyncOrNull(key = PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress)) + .takeIf { !it.isNullOrEmpty() } + } } override suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean { @@ -97,7 +98,7 @@ internal class DefaultTangemPayStorage @Inject constructor( } override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) { - secureStorage.delete(createOrderIdKey(customerWalletAddress)) + appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), "") } override suspend fun storeCheckCustomerWalletResult(userWalletId: UserWalletId, isPaeraCustomer: Boolean) { @@ -110,9 +111,9 @@ internal class DefaultTangemPayStorage @Inject constructor( override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) = withContext(dispatcherProvider.io) { - secureStorage.delete(createCustomerAddressKey(userWalletId)) - secureStorage.delete(createKey(customerWalletAddress)) - secureStorage.delete(createOrderIdKey(customerWalletAddress)) + secureStorage.delete(createAuthTokensKey(customerWalletAddress)) + appPreferencesStore.store(PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId), "") + appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), "") appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), false) } @@ -143,11 +144,7 @@ internal class DefaultTangemPayStorage @Inject constructor( } } - private fun createCustomerAddressKey(userWalletId: UserWalletId): String = userWalletId.stringValue - - private fun createKey(address: String): String = "${DEFAULT_KEY}_$address" - - private fun createOrderIdKey(address: String): String = "${ORDER_ID_KEY}_$address" + private fun createAuthTokensKey(address: String): String = "${AUTH_TOKENS_DEFAULT_KEY}_$address" private fun createWithdrawOrderIdKey(userWalletId: UserWalletId): String = "${WITHDRAW_ORDER_ID_KEY}_$userWalletId" } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt index a151bfa469..b8c7e6dfc7 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt @@ -199,4 +199,30 @@ internal object YieldSupplyDomainModule { yieldSupplyRepository = yieldSupplyRepository, ) } + + @Provides + @Singleton + fun provideYieldSupplyGetShouldShowMainPromoUseCase( + yieldSupplyRepository: YieldSupplyRepository, + ): YieldSupplyGetShouldShowMainPromoUseCase { + return YieldSupplyGetShouldShowMainPromoUseCase( + yieldSupplyRepository = yieldSupplyRepository, + ) + } + + @Provides + @Singleton + fun provideYieldSupplySetShouldShowMainPromoUseCase( + yieldSupplyRepository: YieldSupplyRepository, + ): YieldSupplySetShouldShowMainPromoUseCase { + return YieldSupplySetShouldShowMainPromoUseCase( + yieldSupplyRepository = yieldSupplyRepository, + ) + } + + @Provides + @Singleton + fun provideYieldSupplyGetDustMinAmountUseCase(): YieldSupplyGetDustMinAmountUseCase { + return YieldSupplyGetDustMinAmountUseCase() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt index 74ad98e889..1c3cfc1d98 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt @@ -114,6 +114,7 @@ internal object UserWalletsListManagerModule { publicInformationRepository = publicInformationRepository, sensitiveInformationRepository = sensitiveInformationRepository, selectedUserWalletRepository = selectedUserWalletRepository, + dispatcherProvider = dispatchers, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 6e9ca21efb..8183415675 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -16,11 +16,13 @@ import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.lockAll import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.updateWith +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import timber.log.Timber @Suppress("LargeClass") @@ -30,6 +32,7 @@ internal class BiometricUserWalletsListManager( private val publicInformationRepository: UserWalletsPublicInformationRepository, private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository, private val selectedUserWalletRepository: SelectedUserWalletRepository, + private val dispatcherProvider: CoroutineDispatcherProvider, ) : UserWalletsListManager.Lockable { private val state = MutableStateFlow(State()) @@ -72,12 +75,14 @@ internal class BiometricUserWalletsListManager( return runBlocking { // workaround to avoid calling hasSavedEncryptionKeys many times because of performance savedWalletMutex.withLock { + Timber.i("Checking if user has saved wallets") val hasSavedWalletsLocal = hasSavedWallets if (hasSavedWalletsLocal == null || !hasSavedWalletsLocal) { val hasKeys = keysRepository.hasSavedEncryptionKeys() hasSavedWallets = hasKeys hasKeys } else { + Timber.i("User has saved wallets (from cache)") true } } @@ -93,23 +98,25 @@ internal class BiometricUserWalletsListManager( .distinctUntilChanged() override suspend fun unlock(type: UnlockType): CompletionResult { - return unlockAndSetSelectedUserWallet(type) - .mapFailure { error -> - Timber.e(error, "Unable to unlock user wallets") - if (error is UserWalletsListError) { - error - } else { - UserWalletsListError.UnableToUnlockUserWallets(error) + return withContext(dispatcherProvider.io) { + unlockAndSetSelectedUserWallet(type) + .mapFailure { error -> + Timber.e(error, "Unable to unlock user wallets") + if (error is UserWalletsListError) { + error + } else { + UserWalletsListError.UnableToUnlockUserWallets(error) + } } - } - .map { selectedUserWallet -> - if (selectedUserWallet == null || selectedUserWallet.isLocked) { - Timber.e("Unable to find selected user wallet") - throw UserWalletsListError.NoUserWalletSelected - } else { - selectedUserWallet + .map { selectedUserWallet -> + if (selectedUserWallet == null || selectedUserWallet.isLocked) { + Timber.e("Unable to find selected user wallet") + throw UserWalletsListError.NoUserWalletSelected + } else { + selectedUserWallet + } } - } + } } override fun lock() { @@ -141,18 +148,20 @@ internal class BiometricUserWalletsListManager( } override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { - return if (canOverride) { - saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false) - } else { - val isWalletSaved = state.value.userWallets - .any { - it.walletId == userWallet.walletId - } - - if (isWalletSaved) { - CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved) - } else { + return withContext(dispatcherProvider.io) { + if (canOverride) { saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false) + } else { + val isWalletSaved = state.value.userWallets + .any { + it.walletId == userWallet.walletId + } + + if (isWalletSaved) { + CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved) + } else { + saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false) + } } } } @@ -161,16 +170,18 @@ internal class BiometricUserWalletsListManager( userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet, ): CompletionResult { - return get(userWalletId) - .map { storedUserWallet -> - update(storedUserWallet) - } - .flatMap { updatedUserWallet -> - saveInternal(updatedUserWallet, changeSelectedUserWallet = false, canOverridePublicInfo = true) - } - .flatMap { - get(userWalletId) - } + return withContext(dispatcherProvider.io) { + get(userWalletId) + .map { storedUserWallet -> + update(storedUserWallet) + } + .flatMap { updatedUserWallet -> + saveInternal(updatedUserWallet, changeSelectedUserWallet = false, canOverridePublicInfo = true) + } + .flatMap { + get(userWalletId) + } + } } override suspend fun delete(userWalletIds: List): CompletionResult { @@ -187,44 +198,48 @@ internal class BiometricUserWalletsListManager( return clear() } - return sensitiveInformationRepository.delete(idsToRemove) - .flatMap { publicInformationRepository.delete(idsToRemove) } - .map { keysRepository.delete(idsToRemove) } - .map { - state.update { prevState -> - val remainingWallets = prevState.userWallets.filter { it.walletId !in idsToRemove } + return withContext(dispatcherProvider.io) { + sensitiveInformationRepository.delete(idsToRemove) + .flatMap { publicInformationRepository.delete(idsToRemove) } + .map { keysRepository.delete(idsToRemove) } + .map { + state.update { prevState -> + val remainingWallets = prevState.userWallets.filter { it.walletId !in idsToRemove } - val isSelectedWalletDeleted = prevState.selectedUserWalletId in idsToRemove - val newSelectedUserWallet = findOrSetSelectedWallet( - prevSelectedWalletId = prevState.selectedUserWalletId, - prevSelectedWalletIndex = prevState.userWallets.indexOfFirst { - it.walletId == prevState.selectedUserWalletId - }, - userWallets = remainingWallets, - ignorePrevSelectedWallet = isSelectedWalletDeleted, - ) + val isSelectedWalletDeleted = prevState.selectedUserWalletId in idsToRemove + val newSelectedUserWallet = findOrSetSelectedWallet( + prevSelectedWalletId = prevState.selectedUserWalletId, + prevSelectedWalletIndex = prevState.userWallets.indexOfFirst { + it.walletId == prevState.selectedUserWalletId + }, + userWallets = remainingWallets, + ignorePrevSelectedWallet = isSelectedWalletDeleted, + ) - prevState.copy( - encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in idsToRemove }, - userWallets = remainingWallets, - isLocked = remainingWallets.any { it.isLocked }, - selectedUserWalletId = newSelectedUserWallet?.walletId, - ) + prevState.copy( + encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in idsToRemove }, + userWallets = remainingWallets, + isLocked = remainingWallets.any { it.isLocked }, + selectedUserWalletId = newSelectedUserWallet?.walletId, + ) + } } - } + } } override suspend fun clear(): CompletionResult { savedWalletMutex.withLock { hasSavedWallets = null } - return sensitiveInformationRepository.clear() - .flatMap { publicInformationRepository.clear() } - .map { - keysRepository.clear() - selectedUserWalletRepository.set(null) - state.value = State() - } + return withContext(dispatcherProvider.io) { + sensitiveInformationRepository.clear() + .flatMap { publicInformationRepository.clear() } + .map { + keysRepository.clear() + selectedUserWalletRepository.set(null) + state.value = State() + } + } } override suspend fun get(userWalletId: UserWalletId): CompletionResult { diff --git a/app/src/main/java/com/tangem/tap/routing/RootContent.kt b/app/src/main/java/com/tangem/tap/routing/RootContent.kt index b95777a48b..add9354694 100644 --- a/app/src/main/java/com/tangem/tap/routing/RootContent.kt +++ b/app/src/main/java/com/tangem/tap/routing/RootContent.kt @@ -74,6 +74,7 @@ internal fun RootContent( startActivity(context, instance.intent, Bundle.EMPTY) } } + RoutingComponent.Child.DummyComponent -> Unit } } diff --git a/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt index 22bb2b0b4a..05486d63cc 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt @@ -20,6 +20,8 @@ internal interface RoutingComponent : ComposableContentComponent { data class ComposableComponent( val component: ComposableContentComponent, ) : Child() + + data object DummyComponent : Child() } interface Factory { diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index 9e4f210063..65ece0eaad 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.subscribe import com.arkivanov.essenty.lifecycle.subscribe @@ -11,7 +12,9 @@ import com.google.android.material.snackbar.Snackbar import com.tangem.common.routing.AppRoute import com.tangem.common.routing.entity.InitScreenLaunchMode import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.api.AnalyticsExceptionHandler import com.tangem.core.analytics.models.Basic +import com.tangem.core.analytics.models.ExceptionAnalyticsEvent import com.tangem.core.analytics.utils.TrackingContextProxy import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child @@ -46,6 +49,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.launch +import timber.log.Timber @Suppress("LongParameterList") internal class DefaultRoutingComponent @AssistedInject constructor( @@ -66,6 +70,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val trackingContextProxy: TrackingContextProxy, private val analyticsEventHandler: AnalyticsEventHandler, + private val analyticsExceptionHandler: AnalyticsExceptionHandler, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { @@ -80,13 +85,23 @@ internal class DefaultRoutingComponent @AssistedInject constructor( .create(child("hotAccessCodeRequestComponent"), Unit) } + private val navigation = navigationProvider.getOrCreateTyped() + private val stack: Value> = childStack( - source = navigationProvider.getOrCreateTyped(), + source = navigation, initialStack = { getInitialStackOrInit() }, serializer = null, // AppRoute.serializer(), // Disabled until Nav refactoring completes handleBackButton = true, childFactory = { route, childContext -> - childFactory.createChild(route, childByContext(childContext)) + try { + childFactory.createChild(route, childByContext(childContext)) + } catch (e: Exception) { + Timber.e(e, "App Router Failed") + analyticsExceptionHandler.sendException( + ExceptionAnalyticsEvent(exception = e, params = mapOf("Category" to "App Routing")), + ) + Child.DummyComponent + } }, ) @@ -96,6 +111,12 @@ internal class DefaultRoutingComponent @AssistedInject constructor( appRouterConfig.snackbarHandler = this stack.subscribe(lifecycle) { stack -> + // Handle DummyComponent by popping it immediately + if (stack.active.instance is Child.DummyComponent) { + navigation.pop() + return@subscribe + } + val stackItems = stack.items.map { it.configuration } wcRoutingComponent.onAppRouteChange(stack.active.configuration) diff --git a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt index 3d7fa216d8..7e54ffc6bd 100644 --- a/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManagerTest.kt @@ -23,6 +23,7 @@ internal class BiometricUserWalletsListManagerTest(private val model: Model) { publicInformationRepository = mockk(), sensitiveInformationRepository = mockk(), selectedUserWalletRepository = mockk(), + dispatcherProvider = mockk(), ) @Test diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index 3e700b1970..8408463879 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -43,12 +43,14 @@ import java.math.BigDecimal */ class TokenItemStateConverter( private val appCurrency: AppCurrency, - private val yieldModuleApyMap: Map = emptyMap(), + private val yieldModuleApyMap: Map = emptyMap(), private val stakingApyMap: Map> = emptyMap(), + private val yieldSupplyPromoBannerKey: String? = null, private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = { CryptoCurrencyToIconStateConverter().convert(it) }, private val onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)? = null, + private val onYieldPromoCloseClick: (() -> Unit)? = null, private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { currencyStatus -> createTitleState( currencyStatus = currencyStatus, @@ -66,6 +68,15 @@ class TokenItemStateConverter( private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = { createFiatAmountState(status = it, appCurrency = appCurrency) }, + private val promoBannerProvider: (CryptoCurrencyStatus) -> TokenItemState.PromoBannerState = { status -> + createPromoBannerState( + status = status, + yieldModuleApyMap = yieldModuleApyMap, + yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKey, + onApyLabelClick = onApyLabelClick, + onYieldPromoCloseClick = onYieldPromoCloseClick, + ) + }, private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null, private val onItemLongClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null, ) : Converter { @@ -102,6 +113,7 @@ class TokenItemStateConverter( subtitleState = requireNotNull(subtitleStateProvider(this)), fiatAmountState = requireNotNull(fiatAmountStateProvider(this)), subtitle2State = requireNotNull(subtitle2StateProvider(this)), + promoBannerState = promoBannerProvider(this), onItemClick = onItemClick?.let { onItemClick -> { onItemClick(it, this) } }, @@ -164,7 +176,7 @@ class TokenItemStateConverter( private fun createTitleState( currencyStatus: CryptoCurrencyStatus, - yieldModuleApyMap: Map, + yieldModuleApyMap: Map, stakingApyMap: Map>, onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?, ): TokenItemState.TitleState { @@ -204,7 +216,7 @@ class TokenItemStateConverter( // polygon-pos_0xc2132d05d31c914a87c6611c10748aeb04b58e8f private fun resolveEarnApy( cryptoCurrencyStatus: CryptoCurrencyStatus, - yieldModuleApyMap: Map, + yieldModuleApyMap: Map, stakingApyMap: Map>, ): EarnApyInfo? { val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token @@ -223,7 +235,7 @@ class TokenItemStateConverter( wrappedList(yieldSupplyApy), ), isActive = isActive, - apy = yieldSupplyApy, + apy = yieldSupplyApy.toString(), source = ApySource.YIELD_SUPPLY, ) } @@ -384,6 +396,36 @@ class TokenItemStateConverter( } } + private fun createPromoBannerState( + status: CryptoCurrencyStatus, + yieldModuleApyMap: Map, + yieldSupplyPromoBannerKey: String?, + onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?, + onYieldPromoCloseClick: (() -> Unit)?, + ): TokenItemState.PromoBannerState { + val token = status.currency as? CryptoCurrency.Token ?: return TokenItemState.PromoBannerState.Empty + if (yieldSupplyPromoBannerKey == null || yieldSupplyPromoBannerKey != token.yieldSupplyKey() || + yieldModuleApyMap[token.yieldSupplyKey()] == null + ) { + return TokenItemState.PromoBannerState.Empty + } + val yieldSupplyApy = + yieldModuleApyMap[token.yieldSupplyKey()] ?: return TokenItemState.PromoBannerState.Empty + + return TokenItemState.PromoBannerState.Content( + title = resourceReference( + R.string.yield_module_main_screen_promo_banner_message, + wrappedList(yieldSupplyApy), + ), + onPromoBannerClick = { + onApyLabelClick?.invoke(status, ApySource.YIELD_SUPPLY, yieldSupplyApy.toString()) + }, + onCloseClick = { + onYieldPromoCloseClick?.invoke() + }, + ) + } + private fun CryptoCurrencyStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState { val fiatRate = value.fiatRate val priceChange = value.priceChange diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 2c128557a6..037f71a2a6 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -122,6 +122,10 @@ object PreferencesKeys { val YIELD_SUPPLY_WARNINGS_STATES_KEY by lazy { stringPreferencesKey(name = "yieldSupplyWarningsStates") } + val YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY by lazy { + booleanPreferencesKey(name = "yieldSupplyShouldShowMainPromo") + } + val ACCESS_CODE_SKIPPED_STATES_KEY by lazy { stringPreferencesKey(name = "accessCodeSkippedStates") } // region Notifications @@ -179,6 +183,12 @@ object PreferencesKeys { fun getTangemPayAddToWalletKey(customerWalletAddress: String) = booleanPreferencesKey("tangem_pay_add_to_wallet_done_key_$customerWalletAddress") + fun getTangemPayOrderIdKey(customerWalletAddress: String) = + stringPreferencesKey("tangem_pay_order_id_key_$customerWalletAddress") + + fun getTangemPayCustomerWalletAddressKey(userWalletId: UserWalletId) = + stringPreferencesKey("tangem_pay_customer_wallet_address_key_${userWalletId.stringValue}") + fun getTangemPayCheckCustomerByWalletId(userWalletId: UserWalletId) = booleanPreferencesKey("tangem_pay_check_customer_by_wallet_id_$userWalletId") diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt index 58b21f35dd..9e3d88dcb4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt @@ -44,7 +44,12 @@ class NetworkLogsSaveInterceptor( throw e } - if (restrictedForLogURLs.contains(request.url.host + request.url.encodedPath)) { + val host = request.url.host + val path = request.url.encodedPath + val isRestrictedUrl = restrictedForLogURLs.contains(host + path) + val isRestrictedHost = restrictedForLogHosts.any { host.contains(it) } + + if (isRestrictedUrl || isRestrictedHost) { logResponseWithEmptyMessage(response, startNs) } else { logResponseMessage(response, startNs) @@ -222,5 +227,8 @@ class NetworkLogsSaveInterceptor( val restrictedForLogURLs = listOf( "api.stakek.it/v1/yields/enabled", ) + val restrictedForLogHosts = listOf( + "us.paera.com", + ) } } \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 95bfe4d9e6..a009fa485f 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -3,10 +3,14 @@ Ohne Zugangscode ist Deine Wallet nicht geschützt. Trotzdem überspringen Zugangscode nicht festgelegt + Code ändern + Dein Zugangscode dient zum Entsperren Deiner Wallet und zum Schutz des Zugriffs auf Deine Vermögenswerte. + Trotzdem verwenden + Dieser Zugangscode kann leicht erraten werden Zugangscode eingeben Falscher Zugangscode. Deine mobile Wallet wird nach %s weiteren Fehlversuchen gelöscht. Falscher Zugangscode. Die App wird nach %s weiteren Fehlversuchen gesperrt. - Falscher Zugangscode.\nBitte warte %s Sekunden und versuche es erneut. + Falscher Zugangscode.\n Bitte warte %s Sekunden und versuche es erneut. Bestätige Deinen Zugangscode, um fortzufahren. Zugangscode erneut eingeben Erstelle einen %s-stelliger Zugangscode, um Deine Wallet zu entsperren. @@ -32,6 +36,7 @@ Du archivierst dieses Konto, kannst es aber jederzeit entarchivieren. Archivierung... Konto + Ich kann das Konto nicht bearbeiten. Konto gespeichert Prämien berücksichtigen Kontonummer %s – wird zur Adressableitung verwendet. @@ -139,10 +144,17 @@ Guthaben sind ausgeblendet Laut den Blockchain-Entwicklern befinden sich der Kaspa-Token derzeit in der Betaphase. Bleibe dran für Updates! Beta-Phase + Die biometrischen Daten sind auf Deinem Gerät deaktiviert, sodass Du sie nicht zum Entsperren Deine Wallet verwenden kannst. Aktiviere die Biometrie in den Einstellungen Deines Geräts, um diese Methode wieder zu verwenden. + Biometrische Authentifizierung deaktiviert Bitte Karte oder Ring scannen + Du hast das Limit an biometrischen Entsperrversuchen erreicht. Bitte entsperren Deine Wallet durch Antippen Deines Geräts oder gib den Zugangscode ein. + Biometrische Authentifizierung gesperrt Bitte versuche es in 30 Sekunden erneut oder scanne die Karte oder Ring + Die biometrische Anmeldung ist vorübergehend gesperrt. Bitte versuche es in 30 Sekunden erneut oder entsperre Deine Wallet durch Antippen Deines Geräts oder mit einem Zugangscode. Zu viele Versuche Du hast die biometrische Authentifizierung auf deinem Telefon deaktiviert und kannst keine Wallets in der App speichern. Um Wallets zu speichern, aktiviere bitte die biometrische Authentifizierung in deinen Telefoneinstellungen. + Die biometrischen Daten Deines Geräts wurden aktualisiert. Bitte wähle Deine Wallet aus und gib den zugehörigen Zugangscode ein, um die biometrische Anmeldung wieder zu aktivieren. + Aufmerksamkeit erforderlich Bei der Bearbeitung Deines Aktionscodes ist ein Fehler aufgetreten. Bitte versuche es später noch einmal. Fehler bei der Aktivierung Dein Gutscheincode wurde erfolgreich aktiviert. Die Prämie wird deinem Bitcoin-Konto innerhalb von 14 Tagen gutgeschrieben. @@ -221,7 +233,7 @@ Transaktion fehlgeschlagen Kaufen Gehe zu %1$s - D hast keinen Zugang zur Kamera erteilt, bitte passe deine Datenschutzeinstellungen an + Du hast keinen Zugang zur Kamera erteilt, bitte passe Deine Datenschutzeinstellungen an Abbrechen Ändern Konto auswählen @@ -235,6 +247,7 @@ Demnächst verfügbar Bestätigen Verbinden + Kontakt zum Support Kontakt zum Tangem-Support Kontakt zum Visa-Support Weiter @@ -284,7 +297,7 @@ Zum Token Verstanden Ausblenden - stunde + Stunde Importieren In Arbeit Später @@ -294,7 +307,7 @@ Gesperrt Gesperrte Wallet Hauptnetz - monat + Monat Netzgebühr Der überwiesene Betrag wird um %1$s (%2$s) gekürzt, um die gewählte Gebührenhöhe zu decken. @@ -376,7 +389,7 @@ Staking beenden Aufgrund der Beschränkungen von %1$s können nur %2$d UTXOs in eine einzige Transaktion passen. Das bedeutet, dass du nur %3$s oder weniger senden kannst. Du musst den Betrag reduzieren. Wert kopiert - woche + Woche mit Ja Vertragsadresse kopiert! @@ -423,7 +436,6 @@ Flippe den Bildschirm deines Geräts nach unten, um Salden schnell ein- und auszublenden %s Hasch Geräte-ID - Kontakt zum Support Support-Chat öffnen Weitere Karten oder Ringe verknüpfen App Währung @@ -557,7 +569,7 @@ Neues Wallet erstellen Karte oder Ring bestellen Karte oder Ring scannen - Möchtest Du „Tangem“ die biometrische Authentifizierung erlauben? Um Deine Identität zu bestätigen und die App zu öffnen + Möchtest Du „Tangem“ die Verwendung biometrischer Authentifizierung erlauben? Um Deine Identität zu bestätigen und die App zu öffnen, klicke bitte hier. An %s Im %s Netzwerk Willst Du den Vorgang zur Erstellung des Zugangscodes wirklich beenden? @@ -583,7 +595,6 @@ Zuerst die Sicherung abschließen Unvollständig Andere Methoden - Physische Geräte, die Deine privaten Schlüssel sicher offline speichern. Wiederherstellungs-Phrase Um Deine Wallet auf Hardware umzustellen, erstelle vorher ein Backup. Deine privaten Schlüssel sind sicher verschlüsselt und auf Deinem Telefon gespeichert. @@ -679,11 +690,13 @@ APY %s Mein Portfolio Markt + Verdiene Geld mit Tangem Um Adressen für ausgewählte Netzwerke zu generieren, musst du eine Tangem-Karte oder Ring scannen Um Token hinzuzufügen, rufe dies auf oder tippe auf die Suchleiste Die Daten dieses Abschnitts stammen aus den folgenden Netzwerken: %s Die Daten konnten nicht geladen werden... Keine Daten + Marktimpuls Schnelle Aktionen Markt durchsuchen Ergebnis @@ -774,6 +787,10 @@ Volumen Rufe dies auf oder tippe auf die Suchleiste, um Token direkt vom Markt hinzuzufügen Token hinzufügen + Steiger die Leistung Deiner Assets und ermögliche Dir gleichzeitig den sofortigen Zugriff. %s + Yield-Modus aktivieren + Du musst auf die folgende Version aktualisieren: %1$s um eine mobile Wallet zu erstellen + Mobile Wallet erfordert %1$s oder später Alle Neuigkeiten Auf dem Laufenden bleiben NFC ist auf deinem Gerät nicht verfügbar @@ -1368,31 +1385,45 @@ Du erhältst Token auswählen Nicht verfügbar + Wir freuen uns über Ihr Feedback + Tangem Pay jetzt in der Beta + Karte eingefroren + Kartenzahlung Einzahlung Streitfall Transaktion erkunden Servicegebühren Gebühr - Schütze Dein Geld, falls Deine Karte verloren geht oder gestohlen wird. Du kannst die Sperre jederzeit aufheben. + Schütze Dein Geld. Du kannst die Sperre jederzeit aufheben. Karte sperren lassen? + Karte konnte nicht eingefroren werden. Versuchen Sie es später erneut. Einfrieren + Ihre Karte ist eingefroren. Hilfe erhalten + Andere Abgeschlossen Abgelehnt Ausstehend + Bedingungen, Gebühren & Limits Bedingungen und Einschränkungen Die Bank hat diese Transaktionsanfrage abgelehnt. - Diese Gebühr dient zur Deckung der Kosten für die Abwicklung Deiner Überweisung. + Diese Gebühr dient zur Deckung der Kosten für die Abwicklung Deiner Überweisung.Questa commissione copre il costo della gestione del tuo trasferimento. Nutze Dein Geld weiterhin. Du kannst es jederzeit einfrieren. Karte entsperren? + Entsperren der Karte fehlgeschlagen. Versuchen Sie es später erneut. + Ihre Karte ist entsperrt. Abhebung Guthaben hinzufügen Aufladeoptionen + Kartennummer PIN ändern Die Karte ist vollständig für Zahlungen bereit. PIN-Code erstellt + CVC Daten konnten nicht geladen werden. Versuche es später noch einmal. + Ablaufdatum Karte einfrieren + Details ausblenden Verstecken Google Wallet öffnen Richte Tangem Pay mit wenigen Klicks ein und bezahlen mit Google Pay. @@ -1400,8 +1431,10 @@ Karte zu Google Pay hinzufügen Karte zu Apple Pay hinzufügen Google Wallet öffnen + Tippen Sie auf die Schaltfläche \"+\" oben rechts Apple Wallet öffnen Tippe auf „Karte hinzufügen“. + Tippen Sie auf „Debit- oder Kreditkarte“ Gebe die Kartendaten manuell ein Verifiziere die Karte mit dem Einmalpasswort (OPT), das an Dein Gerät gesendet wird. Alles erledigt! Deine Karte ist einsatzbereit. @@ -1411,22 +1444,53 @@ Es wurden technische Probleme festgestellt. Bitte versuche es später erneut oder kontaktiere den Support. Empfangen ist jetzt nicht verfügbar Aufdecken + Details anzeigen Tausche beliebige Vermögenswerte in Deinem Portfolio gegen eine Karte. Kartendetails Karte entsperren + Auszahlung + Auszahlung derzeit nicht möglich + Sie können keinen Tausch oder eine neue Auszahlung starten, bis die aktuelle abgeschlossen ist. + Auszahlung läuft + PIN-Code ändern + Kehren Sie zur App zurück, falls Sie ihn vergessen. + Kartenausstellung fehlgeschlagen + Ein technischer Fehler ist aufgetreten, bitte versuchen Sie es erneut, indem Sie auf die Schaltfläche unten klicken + Ein technischer Fehler ist aufgetreten, bitte kontaktieren Sie den Support + Nutzen Sie Ihre Kryptowährungen für Einkäufe im Alltag. \nEine Zahlungskarte, die ihresgleichen sucht. + Tangem Pay erhalten + Zum Support Es dauert in der Regel bis zu 15 Minuten. + Einrichtung Ihrer Tangem-Karte + Ausstellung Deiner Karte + Wir bereiten Ihre Karte vor. Dies kann etwas dauern. Tangem Pay + Wir konnten Ihr Profil nicht verifizieren. Bei Fragen wenden Sie sich bitte an den Support. + Leider konnten wir Ihre Identität nicht verifizieren + KYC in Bearbeitung Status anzeigen KYC für Tangem Pay in Arbeit + Nutzen Sie Ihre Kryptowährungen für Einkäufe im echten Leben. \nEs ist eine Zahlungskarte, die ihresgleichen sucht. + Tangem Visa Card Karte erhalten - Füge es Deiner Wallet hinzu und bezahle überall mit Deinem Smartphone. - Apple Pay & Google Pay - Verwende Dein USDC-Guthaben, um alltägliche Einkäufe problemlos zu bezahlen. - Einkäufe im Alltag - Deine Kartendaten sind geschützt – volle Kontrolle in der App. - Integrierte Sicherheit - Hol Dir Deine kostenlose Crypto Card \n in wenigen Minuten + Mit digitaler Karte, die mit Apple Pay und Google Pay funktioniert + Geben Sie Ihre Vermögenswerte überall aus + Es fallen keine zusätzlichen Gebühren für Käufe an + Zahlen Sie genau das, was Sie sehen + Ein separates Zahlungskonto wird erstellt, ohne Ihre Adressen und Vermögenswerte offenzulegen + Unerreichte Privatsphäre + Erhalten Sie Ihre kostenlose Tangem Pay Card in wenigen Minuten + Payment account + Synchronisierung des Zahlungskontos erforderlich + Wir beheben ein technisches Problem. Bitte versuchen Sie es später erneut. + Service vorübergehend nicht verfügbar + Der Dienst ist derzeit nicht erreichbar. Bitte versuchen Sie es später erneut. + Synchronisation erforderlich + Tangem Visa Card + Tangem Pay ist vorübergehend nicht verfügbar Tangem Pay + Verwenden Sie Ihre Karte oder Ihren Ring, um den Zugriff auf Ihr Zahlungskonto wiederherzustellen + Ihr PIN-Code Das ist meine Wallet Guthaben versteckt Angezeigte Salden @@ -1470,6 +1534,7 @@ Mehrere Adressen Die Transaktionshistorie wird für diese Blockchain derzeit nicht verfügbar. Aber keine Sorge, wir arbeiten daran! In der Zwischenzeit kannst du es im Explorer überprüfen. Operation + für: %s von: %s zu: %s Validierer: %s @@ -1921,7 +1986,9 @@ Gebührenpolitik Tangem erhebt außerdem eine Servicegebühr von 15% auf den erzielten Ertrag. Deine Gelder werden automatisch an Aave überwiesen, sobald die Netzwerkgebühren niedriger sind oder Dein Guthaben den erforderlichen Mindestbetrag erreicht. + Hohe Netzwerkgebühren Historische Renditen + Aktiviere %1$s%% Jahreszins auf Dein Guthaben Die Genehmigung für Deine Token im Yield-Modus wurde widerrufen. Öffne den Token, um die Berechtigung erneut zu erteilen. Token-Genehmigung erforderlich Prüfe Deine Netzwerkverbindung @@ -1936,7 +2003,9 @@ Dezentral und selbstverwahrend Durch die Nutzung dieses Dienstes erklärst Du Dich mit provider\n%1$s und %2$s einverstanden Mit Aave verbinden + Aktiviere %1$s%% APY\nauf Deinem Kontostand Aave %1$s%% • Variabler Zinssatz + Variabler Zinssatz Aave Durchschnitt %s Renditen des letzten Jahres @@ -1967,8 +2036,11 @@ %1$s geliefert an Aave Ertragsmodus deaktiviert %1$s aus Aave zurücküberwiesen + Yield-Modus initialisieren + Yield-Modus reaktivieren Lieferung an Aave %1$s geliefert an Aave + Abheben von Aave Automatisch Einzahlung von %1$s %2$s zur Deckung der Netzwerkgebühr für Transaktionen Die Gebühr %s kann nicht gedeckt werden diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 039fe17b62..b0aabad52d 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -1,5 +1,21 @@ + Sin un código de acceso, su billetera no es segura. + Saltar de todos modos + Código de acceso no establecido + Cambiar código + Su código de acceso se utilizará para desbloquear su billetera y proteger el acceso a sus activos. + Usar de todos modos + Este código de acceso se puede adivinar fácilmente. + Introduzca el código de acceso + Código de acceso incorrecto. Su billetera móvil se bloqueará tras %s intentos incorrectos más. + Código de acceso incorrecto. La aplicación se bloqueará tras %s intentos incorrectos más. + Código de acceso incorrecto.\nPor favor, espere %s segundos e inténtelo de nuevo. + Confirme su código de acceso para continuar + Vuelva a introducir el código de acceso + Establezca un código de acceso de %sdígitos para desbloquear su billetera. + Crear código de acceso + Código de acceso Archivar cuenta Archivar Estás archivando esta cuenta, pero siempre puede recuperarla. @@ -38,16 +54,19 @@ Esta tarjeta no admite tokens en la red %1$s debido a una limitación del firmware. ¿Tiene dificultades para escanear su tarjeta/anillo? Esta tarjeta no está diseñada para funcionar con Tangem + Establezca primero un código de acceso para activar la biometría Utilice %1$s para desbloquear de forma rápida y segura su billetera y autorizar todas las acciones importantes, como la firma de transacciones. Al ser una billetera de hardware, seguirá necesitando una tarjeta para firmar. Tarifa por defecto Habilite las Tarifas predeterminadas para establecer automáticamente las tarifas de transacción y omitir la página de Tarifas al enviar fondos. Siempre puede volver a esta página si es necesario. Vaya a ajustes para habilitar la autenticación biométrica en la Tangem App Habilitar autenticación biométrica Para deshabilitar %1$s deberá ingresar su código de acceso para desbloquear la aplicación e interactuar con su billetera. + Más tarde se le solicitará el código de acceso a su billetera para que podamos almacenarlo de forma segura para usarlo posteriormente Esto eliminará todos los códigos de acceso guardados de la billetera. Cualquier operación posterior con la billetera requerirá introducir el código de acceso. Eliminar la tarjeta guardada borra todos las billeteras guardadas y sus códigos de acceso de la app. + Esto borrará todos los códigos de acceso a la billetera guardados. Cualquier otra interacción con la billetera requerirá el envío del código de acceso. Requerir código de acceso - Esta opción desactiva la autenticación biométrica para acciones importantes. Se le pedirá que introduzca su código de acceso cada vez que, por ejemplo, deba firmar una transacción. + Esta opción desactiva el uso de datos biométricos para operaciones críticas. Tendrá que introducir su código de acceso cada vez que firme una transacción. Guardar código de acceso Se solicitará la autenticación biométrica en lugar del código de acceso para las interacciones con su tarjeta o anillo. Mantener la billetera en la app @@ -57,6 +76,9 @@ Predeterminado del sistema Tema Ajustes de la app + Agregar billetera + Seleccione una billetera para iniciar sesión + ¡Bienvenido de nuevo! Realizó una copia de seguridad de su billetera correctamente. Copia de seguridad completada Su frase secreta de recuperación es un conjunto fijo de %s palabras aleatorias que se utilizan para acceder a su billetera y recuperarla. @@ -74,10 +96,17 @@ Los saldos están ocultos Según los desarrolladores de la blockchain, los tokens de Kaspa se encuentran actualmente en fase beta. ¡Estén atentos a las actualizaciones! Modo Beta + La biometría está desactivada en su dispositivo, por lo que no puede utilizarla para desbloquear sus billeteras. Active la biometría en los ajustes de su dispositivo para volver a utilizar este método. + Autenticación biométrica deshabilitada Por favor, escanee la tarjeta/anillo + Ha alcanzado el límite de intentos biométricos. Desbloquee su billetera con un toque en su dispositivo o introduzca su código de acceso. + Autenticación biométrica bloqueada Por favor, inténtelo de nuevo en 30 segundos o escanee la tarjeta/anillo + El inicio de sesión biométrico está bloqueado temporalmente. Inténtalo de nuevo en 30 segundos o desbloquee su billetera con un toque del dispositivo o un código de acceso. Demasiados intentos Ha desactivado la autenticación biométrica en su teléfono y no podrá guardar billeteras en la aplicación. Para guardar billeteras, active la función de autenticación biométrica en los ajustes de su teléfono. + Se han actualizado los datos biométricos de su dispositivo. Seleccione su billetera e introduzca su código de acceso para habilitar nuevamente el inicio de sesión biométrico. + Se requiere atención Se produjo un error al procesar tu código promocional. Inténtalo de nuevo más tarde. Error de activación Tu código promocional se activó correctamente. Una recompensa se acreditará en tu cuenta dentro de 14 días. @@ -98,6 +127,13 @@ %s token %s tokens + Por favor restablezca el siguiente dispositivo para continuar. + Restablecer billetera + Se han restablecido todos sus dispositivos Tangem. Ya puede seguir actualizando su billetera. + Actualizar de nuevo + Restablecimiento completo + Recomendamos completar el proceso de restablecimiento para todos los dispositivos Tangem en esta billetera. + No ha restablecido todos sus dispositivos Tangem Desactive esta opción si no quiere que esta tarjeta se use para reiniciar códigos de acceso en otras tarjetas de esta billetera. Tenga en cuenta que esto también evitará que reinicie el código de acceso en esta tarjeta. Permite usar esta tarjeta para reiniciar el código de acceso en otras tarjetas de esta billetera Recuperación de código de acceso @@ -123,19 +159,25 @@ ADA insuficiente Aceptar Acceso denegado + Cuenta + Cuentas + Activar Agregar Añadir al portafolio Agregar token + Agregado Dirección Todos Autorizar Cantidad Analítica + y Aplicar Aprobación Aprobar Atención Redes disponibles + Copia de seguridad Saldo: %s Saldo autenticación biométrica @@ -146,6 +188,7 @@ No ha otorgado acceso a su cámara, cambie su configuración de privacidad Cancelar Cambie + Elegir cuenta Elige una acción Elija red Elija token @@ -156,6 +199,7 @@ Próximamente Confirme Conectando + Contactar con el equipo de soporte Contacte con el soporte de Tangem Contacte con el soporte de Visa Continuar @@ -194,9 +238,13 @@ Lento Velocidad y tarifa Finalizar + Olvidar + Gratis + De De %s Sincronizar direcciones Comenzar + Obtener token Ir al proveedor Ir al token Entendido @@ -207,11 +255,19 @@ Más tarde Más información %1$s quedan + Legacy Bitcoin Bloqueado + Billeteras bloqueadas Red principal mes Tarifa de la red La cantidad enviada se reducirá en %1$s (%2$s) para cubrir el nivel de tarifa seleccionado + + %d red + %d redes + + Nueva dirección + Noticias Siguiente NFT No @@ -230,10 +286,12 @@ %1$s — %2$s Leer más Recibir + Recomendado Rechazar Recargar Renombrar Requerido + Resetear Guarde Guardar cambios Buscar @@ -252,6 +310,8 @@ Mostrar más Firme Firme y envíe + Saltar + Algo salió mal Stake Staking Empezar @@ -260,9 +320,12 @@ Soporte Redes soportadas Intercambiar + Tangem + Tangem Wallet términos y condiciones Condiciones de uso A + A %s Hoy %d token @@ -272,6 +335,7 @@ Estado de la transacción Transacciones Transferencia + No se pueden cargar los datos… Entiendo Hubo un error. Por favor inténtelo de nuevo. Inaccesible @@ -323,7 +387,6 @@ Gire la pantalla de su dispositivo hacia abajo para ocultar y mostrar rápidamente los saldos %s hashes ID del dispositivo - Contactar con el equipo de soporte Abrir chat de soporte Vincular más tarjetas Moneda de la aplicación @@ -447,6 +510,7 @@ Crear Nueva Billetera Pedir Tangem Escanee + ¿Quiere permitir que \"Tangem\" utilice la autenticación biométrica? Para confirmar su identidad y abrir la aplicación a %s En la red %s ¿Está seguro de que desea salir del proceso de creación de código de acceso? @@ -504,11 +568,13 @@ APY %s Mi portafolio Mercado + Gane con Tangem Para generar direcciones para las redes seleccionadas, debe escanear su tarjeta Tangem Para agregar tokens, abra esta página o pulse sobre la barra de búsqueda Los datos de este apartado proceden de las siguientes redes: %s No se pueden cargar los datos… Sin datos + Análisis del Mercado Acciones rápidas Busque en el mercado Resultado @@ -598,6 +664,10 @@ Volumen Tire hacia arriba o toque la barra de búsqueda para agregar tokens directamente desde el mercado Agregar tokens + Potencie sus activos mientras los suministra con acceso inmediato. %s + Activar Modo Rendimiento + Debes actualizar a %1$s para crear una mobile wallet + Mobile Wallet requiere %1$s o posterior NFC no está disponible en su dispositivo Acerca de NFT Activo NFT @@ -654,6 +724,9 @@ La cuenta de destino no tiene una Trustline (línea de confianza) para el activo que se envía. Obtén $10 en BTC con cada billetera \n ¡Date prisa! Black Friday: hasta 30% DESCUENTO + Vamos + Crea el par perfecto de packs de Tangem. Por tiempo limitado. + 1+1: Compra una billetera y obtén un 50% dto. en la segunda Únase ahora Comparta su código y gane 5 USDT por venta. Su amigo obtiene un 10% de descuento. ¡Obtenga RECOMPENSAS por cada amigo! @@ -1152,6 +1225,111 @@ Usted recibe Elige token no disponible + Nos encantaría recibir tus comentarios + Tangem Pay ya está en beta + Tarjeta congelada + Pago con tarjeta + Depósito + Disputar + Explorar transacción + Comisiones + Comisión + Mantén tu dinero seguro. Puedes desbloquear en cualquier momento. + ¿Congelar tu tarjeta? + No se pudo congelar la tarjeta. Inténtalo de nuevo más tarde. + Mantén tu dinero seguro. Puedes desbloquear en cualquier momento. + Tu tarjeta está congelada. + Obtener ayuda + Otro + Completado + Rechazado + Pendiente + Términos, tarifas y límites + Términos y límites + El banco rechazó esta solicitud de transacción. + Esta tarifa cubre el costo de procesar tu transferencia. + Sigue usando tu dinero. Puedes congelarlo en cualquier momento. + ¿Descongelar tu tarjeta? + No se pudo descongelar la tarjeta. Inténtalo de nuevo más tarde. + Tu tarjeta está descongelada. + Retirada + Agregar fondos + Opciones de recarga + Número de tarjeta + Modificar PIN + La tarjeta está completamente lista para pagos. + Código PIN creado + CVC + Error al cargar datos. Inténtalo de nuevo más tarde. + Caducidad + Congelar tarjeta + Ocultar detalles + Ocultar + Abrir Google Wallet + Configura Tangem Pay en unos pocos toques y empieza a pagar con Google Pay. + Configura Tangem Pay en unos pocos toques y empieza a pagar con Apple Pay + Añade tu tarjeta a Google Pay + Añade tu tarjeta a Apple Pay + Abrir Google Wallet + Toque el botón \"+\" en la parte superior derecha + Abrir Apple Wallet + Toca \"Añadir tarjeta\" + Toque “Tarjeta de débito o crédito” + Ingresa los detalles de la tarjeta manualmente + Verifica la tarjeta usando el OTP enviado a tu dispositivo. + ¡Todo listo! Tu tarjeta está lista para usar. + Añadir tarjeta a Google Pay + Añade tu tarjeta a Apple Pay + Comparte tu dirección o muestra el código QR + Recepción no disponible ahora + Mostrar + Mostrar detalles + Intercambia cualquier activo de tu portafolio por una tarjeta + Detalles de la tarjeta + Descongelar tarjeta + Retirar + Retiro no disponible ahora + No puedes iniciar un intercambio o un nuevo retiro hasta que el actual finalice. + Retiro en progreso + Cambiar código PIN + Vuelve a la app si lo olvidas. + Error al emitir la tarjeta + Ha ocurrido un error técnico, por favor inténtalo de nuevo haciendo clic en el botón de abajo + Ha ocurrido un error técnico, por favor contacta con el soporte + Usa tus criptomonedas para compras en el mundo real. \nEs una tarjeta de pago única en su tipo. + Obtener Tangem Pay + Ir a Soporte + Suele tardar hasta 15 minutos + Configurando tu tarjeta Tangem + Emisión de su tarjeta + Estamos preparando tu tarjeta. Esto puede llevar un poco de tiempo. + Tangem Pay + No pudimos verificar tu perfil. Si tienes alguna pregunta, contacta con el soporte. + Lamentablemente, no pudimos verificar tu identidad + KYC en curso + Ver estado + KYC en progreso para Tangem Pay + Usa tus criptomonedas para compras en el mundo real. \nEs una tarjeta de pago única en su tipo. + Tangem Visa Card + Obtener tarjeta + Con tarjeta digital que funciona con Apple Pay y Google Pay + Gasta tus activos en cualquier lugar + No hay comisiones adicionales por compras + Paga exactamente lo que ves + Se creará una cuenta de pago separada sin divulgar tus direcciones y activos + Privacidad inigualable + Obtén tu tarjeta Tangem Pay gratuita en minutos + Payment account + Sincronización de cuenta de pago necesaria + Estamos solucionando un problema técnico. Por favor, inténtelo de nuevo más tarde. + Servicio temporalmente no disponible + El servicio no está disponible actualmente. Inténtalo de nuevo más tarde. + Sincronización necesaria + Tangem Visa Card + Tangem Pay temporalmente no disponible + Tangem Pay + Usa tu tarjeta o anillo para restaurar el acceso a tu cuenta de pago + Tu código PIN Esta es mi billetera Saldos ocultos Saldos mostrados @@ -1239,6 +1417,7 @@ Renombrar la billetera Desbloquear todo Desbloquear todo con %s + Elija cómo agregar su billetera La blockchain está Inaccesible. Inténtelo más tarde Escanee la tarjeta o el anillo Esta billetera ya ha sido activada anteriormente.\nSi no fue usted quien lo hizo, comuníquese con el servicio de asistencia.\nTangem nunca vende billeteras con un código de acceso pre generado. @@ -1274,10 +1453,21 @@ Conectar a dApps WalletConnect La conexión puede tardar unos segundos. + Crear una billetera Tangem + ¿Quiere comprar Tangem Wallet? + Comprar ahora + Recuperar una billetera existente mediante copia de seguridad de Google Drive + Importar desde Google Drive + Agregar billetera existente + Dispositivos físicos que almacenan de forma segura su clave privada fuera de línea. + Escanee una billetera Tangem + Importa una billetera existente con su frase de recuperación. Importar billetera + Introduzca la frase de recuperación La copia de seguridad de su billetera se realizó con éxito. Importar billetera Importación completada + Importar billetera %s Precio de mercado últimas 24h %s red @@ -1286,8 +1476,11 @@ Consíguelo ahora con un 10 % de descuento Acceda a más de 13.000 criptomonedas. Compre, venda, intercambie y realice staking con un solo toque.\nVincule hasta tres tarjetas para hacer copias de seguridad. Descubra Tangem Wallet + Este código de acceso protege su billetera y se utiliza para iniciar sesión y firmar transacciones. + Establecer/Cambiar código de acceso Cambiar código de acceso Manténgase informado sobre las transacciones entrantes de la billetera y las actualizaciones de Tangem. + Es posible que las notificaciones push no funcionen actualmente en dispositivos Huawei. Estamos trabajando activamente en una solución y la publicaremos en una próxima actualización. ¡Gracias por su comprensión! Notificaciones de transacciones Establecer código de acceso Ajustes de la wallet @@ -1477,6 +1670,20 @@ URI ya utilizado WalletConnect Transacción sospechosa + ¿Ya tiene Tangem Wallet? + Miles de activos + La mejor billetera de hardware de su clase + Entrega rápida + Empiece con un solo toque + Fácil y seguro + Fácil de usar + Cree una billetera de hardware con Tangem. Delgada como una tarjeta bancaria, segura como una bóveda. + Crear o importar una billetera de software + Cree o importe una billetera de software en su teléfono. + Empezar con Mobile Wallet + Otro método + Utilice la billetera de hardware Tangem + Obtenga más información y compre Ignorar Tiene un backup interrumpido. ¿Quiere reanudarlo? Sí, reanudar @@ -1491,14 +1698,14 @@ No, enviar todo Reducir en %s XTZ Para evitar pagar una comisión mayor la próxima vez que recargue su billetera, reduzca el importe en %s XTZ - Con el modo Rendimiento activo, todos los depósitos futuros a esta dirección irán a Aave. Puede seguir gestionando sus fondos libremente. + Con el Modo Rendimiento activo, todos los depósitos futuros a esta dirección irán a Aave. Puede seguir gestionando sus fondos libremente. Su %s se suministra a Aave El suministro de %1$s %2$s a Aave está pendiente Aprobar Se ha revocado la aprobación de su token. Concédalo de nuevo para reanudar el servicio. Aprobación necesaria - Se le descontará la comisión y se le volverán a prestar sus activos. - Para seguir ganando, se requiere aprobación. + Se le descontará la comisión y se volverán a suministrar sus activos. + Para seguir generando rendimiento, se requiere aprobación. Confirmar aprobación Sus fondos se suministran actualmente al protocolo Aave, pero puede gestionarlos en cualquier momento. Su %s está depositado en Aave @@ -1527,8 +1734,11 @@ Política de tarifas Tangem también cobra una comisión de servicio del 15% sobre el rendimiento obtenido. Sus fondos se suministrarán automáticamente a Aave una vez que las comisiones de red sean más bajas o su saldo alcance el importe mínimo requerido. + Las comisiones son más altas de lo habitual debido a la alta actividad del mercado. Puede continuar ahora o volver a consultar más tarde cuando las comisiones sean más bajas. + Tarifas de red elevadas Rendimientos históricos - Se ha revocado la autorización para su token en el modo Rendimiento. Abra el token para volver a conceder el permiso. + Consiga un %1$s%% APY sobre su saldo + Se ha revocado la autorización para su token en el Modo Rendimiento. Abra el token para volver a conceder el permiso. Se necesita la aprobación de Token Compruebe su conexión de red Información de tarifas de red inaccesible @@ -1542,7 +1752,9 @@ Descentralizado y autocustodiado Al utilizar este servicio, usted acepta que el proveedor\n%1$s y %2$s Conectar Aave + Consiga un %1$s%% APY\nen su saldo Aave %1$s%% - Tipo de interés variable + Tipo de interés variable Aave Promedio %s Resultados del año pasado @@ -1558,7 +1770,7 @@ Activo En pausa Desactivación del modo de rendimiento - Al desactivar esto, retirará sus fondos de Aave a %s en su billetera y dejará de ganar recompensas. + Al desactivar esto, retirará sus fondos de Aave a %s en su billetera y dejará de generar rendimientos. Se cobra una comisión de red por la blockchain al salir del modo Yield. Desactivar el modo de rendimiento Suministrar @@ -1569,16 +1781,20 @@ Modo de rendimiento Procesando su depósito Modo Rendimiento + Implementación del contrato del Modo Rendimiento Modo Rendimiento activado %1$s suministrado a Aave Modo Rendimiento desactivado %1$s retirado de Aave + Iniciar Modo Rendimiento + Reactivar Modo Rendimiento Suministrar a Aave %1$s suministrado a Aave + Retirar de Aave Automático Deposite algo de %1$s %2$s para cubrir la tarifa de red para las transacciones No se puede cubrir la tarifa %s - El servicio de intereses no está disponible en este momento. Vuelva a intentarlo más tarde. + El Modo de Rendimiento no está disponible en este momento. Vuelva a intentarlo más tarde. Modo de rendimiento no disponible No se puede cargar el gráfico... diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 34d5e51c71..ca122acb3d 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1,5 +1,21 @@ + Sans code d\'accès, votre portefeuille n\'est pas sécurisé. + Ignorer quand même + Code d\'accès non défini + Modifier le code + Votre code d\'accès sera utilisé pour déverrouiller votre portefeuille et protéger l\'accès à vos actifs. + Utiliser quand même + Ce code d\'accès peut être facilement deviné. + Entrez le code d\'accès + Code d\'accès incorrect. Votre portefeuille mobile sera supprimé après %s tentatives incorrectes supplémentaires. + Code d\'accès incorrect. L\'application sera verrouillée après %s tentatives infructueuses supplémentaires. + Code d\'accès incorrect.\nVeuillez patienter %s secondes et réessayer. + Confirmez votre code d\'accès pour continuer + Saisissez à nouveau le code d\'accès + Définissez un code d\'accès à %s chiffres pour déverrouiller votre portefeuille. + Créer un code d\'accès + Code d\'accès Jeton non trouvé dans votre portefeuille ? Consultez les marchés pour le trouver et l\'ajouter à l\'achat Jeton non trouvé dans votre portefeuille ? Consultez les marchés pour le trouver et l\'ajouter à la vente Vendre @@ -34,12 +50,16 @@ Les jetons du réseau %1$s ne sont pas pris en charge par cette carte en raison d\'une limitation du micrologiciel. Avez-vous des difficultés à scanner votre carte ? Cette carte n\'est pas conçue pour fonctionner avec Tangem + Définissez d\'abord un code d\'accès pour activer la biométrie. Frais par défaut Activez les frais par défaut pour définir automatiquement les frais de transaction et ignorer la page Frais lors de l\'envoi de fonds. Vous pouvez toujours revenir sur cette page si nécessaire. Accédez aux paramètres pour activer l\'authentification biométrique dans l\'application Tangem Activer l\'authentification biométrique + Le code d\'accès à votre portefeuille vous sera demandé ultérieurement afin que nous puissions le stocker en toute sécurité pour une utilisation future. Cela supprimera tous les codes d\'accès des portefeuilles enregistrés. Toute opération ultérieure avec le portefeuille nécessitera la soumission du code d\'accès. La suppression de la carte enregistrée supprime de l\'application tous les portefeuilles enregistrés et leurs codes d\'accès. + Cela supprimera tous les codes d\'accès enregistrés pour le portefeuille. Toute interaction ultérieure avec le portefeuille nécessitera la saisie du code d\'accès. + Cette option désactive la biométrie pour les actions sensibles. Vous devrez saisir votre code d\'accès à chaque fois que vous signerez une transaction. Enregistrer le code d\'accès L\'authentification biométrique sera demandée à la place du code d\'accès pour les interactions avec votre carte. Conserver le portefeuille dans l\'application @@ -49,6 +69,9 @@ Par défaut du système Thème Paramètres de l\'application + Ajouter un wallet + Sélectionnez un wallet pour vous connecter + Heureux de te revoir! Ces mots ne peuvent pas être récupérés en cas de perte. Assurez-vous de les conserver en lieu sûr. Pour masquer ou afficher vos soldes, il suffit de retourner l\'écran de votre appareil vers le bas ou de le désactiver dans les paramètres Ne plus afficher @@ -56,10 +79,17 @@ Les soldes sont masqués Selon les développeurs de la blockchain, les jetons Kaspa sont actuellement en version bêta. Restez à l\'écoute des mises à jour ! Mode bêta + La biométrie est désactivée sur votre appareil, vous ne pouvez donc pas l\'utiliser pour déverrouiller vos portefeuilles. Activez la biométrie dans les paramètres de votre appareil pour pouvoir à nouveau utiliser cette méthode. + Authentification biométrique désactivée Veuillez scanner la carte/bague + Vous avez atteint la limite de tentatives biométriques. Veuillez déverrouiller votre portefeuille en tapotant votre appareil ou en saisissant votre code d\'accès. + Authentification biométrique verrouillée Veuillez réessayer dans 30 secondes ou scannez la carte/bague + La connexion biométrique est temporairement verrouillée. Veuillez réessayer dans 30 secondes ou déverrouiller votre portefeuille à l\'aide d\'un appareil ou d\'un code d\'accès. Trop de tentatives Vous avez désactivé l\'authentification biométrique sur votre téléphone et ne pourrez pas enregistrer de portefeuilles dans l\'application. Pour enregistrer des portefeuilles, veuillez activer la fonction d\'authentification biométrique dans les paramètres de votre téléphone. + Les données biométriques de votre appareil ont été mises à jour. Veuillez sélectionner votre portefeuille et saisir son code d\'accès pour réactiver la connexion biométrique. + Attention requise Une erreur s\'est produite lors du traitement de votre code promo. Veuillez réessayer plus tard. Erreur d\'activation Votre code promotionnel a été activé avec succès. Une récompense sera créditée sur votre compte dans un délai de 14 jours. @@ -80,6 +110,13 @@ %s token %s tokens + Veuillez réinitialiser l\'appareil suivant pour continuer. + Réinitialisation du portefeuille + Tous les appareils Tangem ont été réinitialisés. Vous pouvez maintenant continuer à mettre à jour votre portefeuille. + Mettre de nouveau à jour + Réinitialisation terminée + Nous vous recommandons d\'effectuer la réinitialisation de tous les appareils Tangem contenus dans ce portefeuille. + Vous n\'avez pas réinitialisé tous vos appareils Tangem. Désactivez cette option si vous ne voulez pas que cette carte soit utilisée pour réinitialiser les codes d\'accès sur d\'autres cartes de ce portefeuille. Veuillez noter que cela vous empêchera également de réinitialiser le code d\'accès sur cette carte. Vous permet d\'utiliser cette carte pour réinitialiser le code d\'accès sur d\'autres cartes de ce portefeuille Récupération du code d\'accès @@ -105,19 +142,25 @@ ADA insuffisant Accepter Accès refusé + Compte + Comptes + Activer Ajouter Ajouter au portfolio Ajouter un jeton + Ajouté Adresse Tous Permettre Montant Analytique + et Appliquer Approbation Approuver Attention Réseaux disponibles + Sauvegarde Solde : %s Solde authentification biométrique @@ -128,6 +171,7 @@ Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité Annuler Changez + Choisissez un compte Choisissez une action Choisissez le réseau Choisir le jeton @@ -135,8 +179,12 @@ Réclamer Réclamez des récompenses Fermer + À venir Confirmez Connexion + Contactez l\'équipe de support + Contacter l\'assistance Tangem + Contacter le service d\'assistance Visa Continuer Convertir Copier @@ -153,6 +201,7 @@ jours Supprimer + Désactiver Désactivé Se déconnecter Exécuté @@ -172,9 +221,13 @@ Lent Vitesse et frais Terminer + Oublier + Gratuit + Du De %s Synchroniser les adresses Commencer + Obtenir un token Aller au fournisseur Aller au jeton Compris @@ -185,16 +238,25 @@ Plus tard En savoir plus Il reste %1$s + Legacy Bitcoin Verrouillé + Portefeuilles verrouillés Réseau principal mois Commissions du réseau Le montant envoyé sera réduit de %1$s(%2$s) pour couvrir le niveau de frais sélectionné + + %d réseau + %d réseaux + + Nouvelle adresse + Actualités Suivant NFT Non Aucune adresse Non ajouté + Pas maintenant Maintenant OK Ouvrir dans le navigateur @@ -207,10 +269,12 @@ %1$s — %2$s En savoir plus Recevoir + Recommandé Rejeter Recharger Renommer Obligatoire + Réinitialiser Enregistrez Sauvegarder les modifications Rechercher @@ -229,6 +293,8 @@ Afficher plus Signez Signez et envoyez + Passer + Une erreur s\'est produite. Stake Staking Démarrer @@ -237,18 +303,22 @@ Support Réseaux pris en charge Échanger + Tangem + Tangem Wallet termes et conditions Conditions d\'utilisation À + À %s Aujourd\'hui - %d jeton - %d jetons + %d token + %d tokens La transaction a échoué Statut de la transaction Transactions Fourniture + Impossible de charger les données… Je comprends Il y avait une erreur. Veuillez réessayer. Inaccessible @@ -300,7 +370,6 @@ Retournez l\'écran de votre appareil vers le bas pour masquer et afficher rapidement les soldes %s hashes ID de l\'appareil - Contactez l\'équipe de support Ouvrir le chat d\'assistance Lier plus de cartes Monnaie de l\'application @@ -422,8 +491,10 @@ Créer un nouveau Portefeuille Commandez Scannez + Souhaitez-vous autoriser « Tangem » à utiliser l\'authentification biométrique ? Pour confirmer votre identité et ouvrir l\'application à %s Via %s + Êtes-vous sûr de vouloir annuler la configuration du code d\'accès ? Ces informations ont été générées avec l\'IA.\nAppuyez ici si vous trouvez des erreurs. Touchez, pour modifier le code d\'accès Touchez, pour modifier le mot de passe @@ -477,11 +548,13 @@ APY %s Mon portfolio Marché + Gagnez avec Tangem Pour générer des adresses pour les réseaux sélectionnés, vous devez scanner votre carte Tangem Pour ajouter des jetons, faites-le apparaître ou appuyez sur la barre de recherche Les données de cette section proviennent des réseaux suivants : %s Impossible de charger les données… Aucune donnée + Rythme du marché Actions rapides Rechercher sur le marché Résultat @@ -571,6 +644,10 @@ Volume Tirez vers le haut ou appuyez sur la barre de recherche pour ajouter des jetons directement depuis le marché Ajouter des jetons + Optimisez vos actifs tout en leur fournissant un accès instantané. %s + Activer le mode rendement + Vous devez effectuer la mise à jour %1$s afin de créer un portefeuille mobile. + Le portefeuille mobile nécessite %1$s ou une version supérieure. NFC n\'est pas disponible sur votre appareil À propos des NFT Actif NFT @@ -627,6 +704,9 @@ Le compte destinataire n\'a pas de Trustline pour l\'actif envoyé qu\'il tente d\'envoyer. Obtenez 10 $ en BTC avec chaque wallet\nDépêchez-vous ! Black Friday : jusqu\'à 30% de réduction + C\'est parti ! + Offre à durée limitée ! + 1 + 1 : achetez 1 wallet et bénéficiez de 50 % de réduction sur le 2ᵉ Rejoignez maintenant Partagez votre code et gagnez 5 USDT par vente. Votre ami bénéficie de 10 % de réduction. Recevez des RÉCOMPENSES pour chaque ami ! @@ -1126,6 +1206,111 @@ Vous recevez Choisir le jeton non disponible + Nous serions ravis de recevoir vos retours + Tangem Pay en version bêta + Carte gelée + Paiement par carte + Dépôt + Litige + Explorer la transaction + Frais de service + Frais + Protégez votre argent. Vous pouvez le débloquer à tout moment. + Geler votre carte ? + Échec du gel de la carte. Réessayez plus tard. + Geler + Votre carte est gelée. + Obtenir de l\'aide + Autre + Terminé + Refusé + En attente + Conditions, frais et limites + Conditions et limites + La banque a rejeté cette demande de transaction. + Ces frais couvrent le coût du traitement de votre virement. + Continuez à utiliser votre argent. Vous pouvez le geler à tout moment. + Dégeler votre carte ? + Échec du dégel de la carte. Réessayez plus tard. + Votre carte est dégelée. + Retrait + Ajouter des fonds + Options de recharge + Numéro de carte + Modifier le code PIN + La carte est totalement prête pour les paiements. + Code PIN créé + CVC + Échec du chargement des données. Réessayez plus tard. + Expiration + Geler la carte + Masquer les détails + Masquer + Ouvrir Google Wallet + Configurez Tangem Pay en quelques clics et commencez à payer avec Google Pay. + Configurez Tangem Pay en quelques clics et commencez à payer avec Apple Pay + Ajoutez votre carte à Google Pay + Ajouter votre carte à Apple Pay + Ouvrir Google Wallet + Appuyez sur le bouton « + » en haut à droite + Ouvrir Apple Wallet + Appuyez sur « Ajouter une carte » + Appuyez sur « Carte de débit ou de crédit » + Saisissez manuellement les détails de la carte + Vérifiez la carte à l\'aide de l\'OTP envoyé à votre appareil. + Tout est prêt ! Votre carte est prête à l\'emploi. + Ajouter une carte à Google Pay + Ajouter la carte à Apple Pay + Partagez votre adresse ou montrez le QR code + Réception indisponible pour le moment + Révéler + Afficher les détails + Échangez n\'importe quel actif de votre portefeuille contre une carte + Détails de la carte + Dégeler la carte + Retirer + Retrait indisponible pour le moment + Vous ne pouvez pas lancer d\'échange ou de nouveau retrait tant que le retrait actuel n\'est pas terminé. + Retrait en cours + Modifier le code PIN + Revenez dans l\'application si vous l\'oubliez. + Échec de l\'émission de la carte + Une erreur technique s\'est produite, veuillez réessayer en cliquant sur le bouton ci-dessous + Une erreur technique s\'est produite, veuillez contacter le support + Utilisez vos cryptomonnaies pour vos dépenses quotidiennes. \nC\'est une carte de paiement unique en son genre. + Obtenir Tangem Pay + Contacter le support + Cela prend généralement jusqu\'à 15 minutes + Configuration de votre carte Tangem + Émission de votre carte + Nous préparons votre carte. Cela peut prendre un peu de temps. + Tangem Pay + Nous n\'avons pas pu vérifier votre profil. Pour toute question, veuillez contacter le support. + Malheureusement, nous n\'avons pas pu vérifier votre identité + KYC en cours + Voir le statut + KYC en cours pour Tangem Pay + Utilisez vos cryptomonnaies pour vos dépenses du quotidien. \nC\'est une carte de paiement unique en son genre. + Tangem Visa Card + Obtenir la carte + Avec carte numérique compatible Apple Pay et Google Pay + Dépensez vos actifs partout + Aucuns frais supplémentaires pour les achats + Payez exactement ce que vous voyez + Un compte de paiement séparé sera créé sans divulguer vos adresses et actifs + Confidentialité inégalée + Obtenez votre carte Tangem Pay gratuite en quelques minutes + Payment account + Synchronisation du compte de paiement nécessaire + Nous réparons un problème technique. Veuillez réessayer plus tard. + Service temporairement indisponible + Le service est actuellement indisponible. Veuillez réessayer plus tard. + Synchronisation requise + Tangem Visa Card + Tangem Pay est temporairement indisponible + Tangem Pay + Utilisez votre carte ou votre bague pour restaurer l\'accès à votre compte de paiement + Votre code PIN C\'est mon portefeuille Soldes masqués Soldes affichés @@ -1217,6 +1402,8 @@ disponible pour %d jours Soldes et Limites + Le code d\'accès sera utilisé pour gérer votre compte de paiement et le protéger contre tout accès non autorisé. + Code d\'accès Êtes-vous sûr de vouloir quitter ? Vous pourrez reprendre plus tard là où vous vous étiez arrêté. Cela ne prendra pas longtemps. Nous configurons votre compte. Cela ne prendra pas longtemps. Nous terminons l\'activation. @@ -1230,6 +1417,7 @@ Déverrouiller Scannez votre carte pour déverrouiller l\'accès Déverrouillage nécessaire + Choisissez comment ajouter votre portefeuille La blockchain n\'est pas accessible. Réessayez plus tard Scanner la carte ou la bague Ce portefeuille a déjà été activé auparavant.\nSi cela n\'a pas été fait par vous, veuillez contacter le support.\nTangem ne vend jamais de portefeuilles avec le code d\'accès pré-généré. @@ -1265,6 +1453,21 @@ Se connecter aux dApps WalletConnect La connexion peut prendre quelques secondes + Créer un Tangem Wallet + Vous souhaitez acheter le Tangem Wallet ? + Acheter maintenant + Récupérer un portefeuille existant via la sauvegarde Google Drive + Importer depuis Google Drive + Ajouter un portefeuille existant + Dispositifs physiques qui stockent votre clé privée hors ligne en toute sécurité. + Scanner un Tangem Wallet + Importez un portefeuille existant à l\'aide de votre seedphrase. + Importer un wallet + Entrez la seedphrase + Vous avez sauvegardé votre portefeuille avec succès. + Importer un wallet + Importation terminée + Importer un portefeuille %s Prix du marché dernières 24h %s réseau @@ -1273,8 +1476,13 @@ Obtenez-le maintenant avec 10 % de réduction Accédez à plus de 13 000 cryptomonnaies. Achetez, vendez, échangez et stakez en un seul clic.\nAssociez jusqu\'à trois cartes pour une sauvegarde. Découvrez le Portefeuille Tangem + Ce code d\'accès protège votre portefeuille et sert à vous connecter et à signer des transactions. + Définir/Modifier le code d\'accès + Modifier le code d\'accès Recevez des notifications sur les transactions entrantes du portefeuille et les mises à jour de Tangem. + Les notifications push peuvent ne pas fonctionner actuellement sur les appareils Huawei. Nous travaillons activement à la résolution de ce problème et publierons un correctif dans une prochaine mise à jour. Merci de votre compréhension ! Notifications de transaction + Définir le code d\'accès Paramètres du portefeuille Tangem Utilisez %s ou scannez une carte/bague pour déverrouiller l\'accès à votre portefeuille @@ -1456,7 +1664,20 @@ Demande de transaction Montant illimité WalletConnect - En savoir plus + Vous avez déjà un portefeuille Tangem ? + Des milliers d\'actifs + Le meilleur hardware wallet de sa catégorie + Livraison rapide + Commencez en un seul clic + Sans faille et sécurisé + Facile à utiliser + Créez un harware wallet avec Tangem. Aussi fin qu\'une carte bancaire, aussi sûr qu\'un coffre-fort. + Créer ou importer un portefeuille logiciel + Créez ou importez un portefeuille logiciel sur votre téléphone. + Commencez avec Mobile Wallet + Autre méthode + Utilisez le hardware wallet Tangem + En savoir plus et acheter Ignorer Vous avez une sauvegarde interrompue. Voulez-vous la reprendre ? Oui, reprendre @@ -1507,7 +1728,10 @@ Politique tarifaire Tangem prélève également des frais de service de 15% sur les revenus générés. Vos fonds seront automatiquement transférés vers Aave dès que les frais de réseau seront moins élevés ou que votre solde atteindra le montant minimum requis. + Les frais sont plus élevés que d\'habitude car le marché est très actif. Vous pouvez procéder maintenant ou revenir plus tard lorsque les frais seront moins élevés. + Frais de réseau élevés Rendements historiques + Activez %1$s%% APY sur votre solde L\'autorisation pour votre token en mode Rendement a été révoquée. Ouvrez le token pour accorder à nouveau l\'autorisation. Approbations de tokens nécessaires Vérifiez votre connexion réseau. @@ -1522,7 +1746,9 @@ Décentralisé et auto-détenu En utilisant ce service, vous acceptez les conditions générales du fournisseur %1$s et %2$s. Connecter Aave + Activez %1$s%% APY\nsur votre solde Aave %1$s%% • Taux d\'intérêt variable + Taux d\'intérêt variable Aave Moyenne %s Rendements de l\'année dernière @@ -1541,6 +1767,7 @@ En désactivant cela, vos fonds seront retirés d\'Aave vers %s dans votre portefeuille et vous ne gagnerez plus de récompenses. Des frais de réseau sont prélevés par la blockchain lorsque vous quittez le mode Yield. Désactiver le mode rendement + Réserves Rendement annuel brut (APY) APY Les intérêts sont cumulés automatiquement. @@ -1548,12 +1775,16 @@ Mode Rendement Traitement de votre dépôt Mode Rendement + Déployer le contrat du mode rendement Mode rendement activé %1$s fourni à Aave Mode rendement désactivé %1$s retiré d\'Aave + Initialisation du mode de rendement + Réactiver le mode Rendement Approvisionnement à Aave %1$s fourni à Aave + Retrait depuis Aave Automatique Déposez %1$s %2$s pour couvrir les frais de réseau liés aux transactions. Impossible de couvrir les frais %s diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index a585f2e30a..179f7018a0 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -68,6 +68,111 @@ Importo non valido La commissione supera il saldo Il totale supera il saldo + Saremo felici di ricevere il tuo feedback + Tangem Pay ora in beta + Carta congelata + Pagamento con carta + Deposito + Contestazione + Esplora transazione + Commissioni + Commissione + Proteggi il tuo denaro. Puoi sbloccarlo in qualsiasi momento. + Bloccare la tua carta? + Impossibile congelare la carta. Riprova più tardi. + Blocca + La tua carta è congelata. + Ottenere aiuto + Altro + Completato + Rifiutato + In sospeso + Termini, commissioni e limiti + Termini e limiti + La banca ha rifiutato questa richiesta di transazione. + Questa commissione copre il costo della gestione del tuo trasferimento. + Continua a usare i tuoi soldi. Puoi congelarli in qualsiasi momento. + Sbloccare la tua carta? + Impossibile sbloccare la carta. Riprova più tardi. + La tua carta è sbloccata. + Prelievo + Aggiungi fondi + Opzioni di ricarica + Numero carta + Modifica PIN + La carta è completamente pronta per i pagamenti. + Codice PIN creato + CVC + Impossibile caricare i dati. Riprova più tardi. + Scadenza + Blocca carta + Nascondi dettagli + Nascondi + Apri Google Wallet + Configura Tangem Pay in pochi tocchi e inizia a pagare con Google Pay. + Configura Tangem Pay in pochi tocchi e inizia a pagare con Apple Pay + Aggiungi la tua carta a Google Pay + Aggiungi la tua carta ad Apple Pay + Apri Google Wallet + Tocca il pulsante \"+\" in alto a destra + Apri Apple Wallet + Tocca « Aggiungi una carta » + Tocca “Carta di debito o di credito” + Inserisci manualmente i dettagli della carta + Verifica la carta utilizzando l\'OTP inviato al tuo dispositivo. + Tutto pronto! La tua carta è pronta per l\'uso. + Aggiungi carta a Google Pay + Aggiungi carta ad Apple Pay + Condividi il tuo indirizzo o mostra il QR code + Ricezione non disponibile al momento + Rivela + Mostra dettagli + Scambia qualsiasi asset nel tuo portafoglio con una carta + Dettagli carta + Sblocca carta + Ritiro + Ritiro non disponibile ora + Non puoi avviare uno swap o un nuovo prelievo finché quello attuale non è terminato + Prelievo in corso + Cambia codice PIN + Torna all\'app se lo dimentichi. + Impossibile emettere la carta + Si è verificato un errore tecnico, riprova cliccando il pulsante qui sotto + Si è verificato un errore tecnico, contatta il supporto + Utilizza le tue attività per fare acquisti nel mondo reale. \nÈ una carta di pagamento diversa da qualsiasi altra. + Ottieni Tangem Pay + Vai al supporto + Di solito richiede fino a 15 minuti + Configurazione della tua carta Tangem + Emissione della tua carta + Stiamo preparando la tua carta. Ci vorrà un po\' di tempo. + Tangem Pay + Non siamo riusciti a verificare il tuo profilo. Per domande, contatta il supporto. + Purtroppo non siamo riusciti a verificare la tua identità + KYC in corso + Visualizza stato + KYC in corso per Tangem Pay + Utilizza le tue attività per fare acquisti nel mondo reale. \nÈ una carta di pagamento diversa da qualsiasi altra. + Tangem Visa Card + Ottieni carta + Con carta digitale che funziona con Apple Pay e Google Pay + Spendi i tuoi asset ovunque + Non ci sono costi aggiuntivi per gli acquisti + Paga esattamente quello che vedi + Verrà creato un conto di pagamento separato senza divulgare i tuoi indirizzi e asset + Privacy senza rivali + Ottieni la tua carta Tangem Pay gratuita in pochi minuti + Payment account + Sincronizzazione del conto di pagamento necessaria + Stiamo risolvendo un problema tecnico. Riprova più tardi. + Servizio temporaneamente non disponibile + Il servizio è attualmente non raggiungibile. Riprova più tardi. + Sincronizzazione necessaria + Tangem Visa Card + Tangem Pay è temporaneamente non disponibile + Tangem Pay + Usa la tua carta o il tuo anello per ripristinare l\'accesso al tuo conto di pagamento + Il tuo codice PIN Tangem Twin WalletConnect L\'indirizzo è stato copiato con successo diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 15b53db6d3..94e4382158 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -5,7 +5,7 @@ アクセスコードが設定されていません コードを変更 アクセスコードは、ウォレットのロック解除・資産へのアクセス保護に使用されます - このまま使用 + このまま使う このアクセスコードは簡単に推測される可能性があります アクセスコードを入力 アクセスコードが間違っています。あと%s回間違えると、モバイルウォレットが削除されます。 @@ -144,7 +144,7 @@ 残高は非表示 ブロックチェーン開発者によると、Kaspaトークンは現在ベータ版です。アップデートをお楽しみに! ベータモード - デバイスで生体認証がオフになっているため、ウォレットのロック解除に使用できません。\n生体認証を再度利用するには、デバイスの設定で有効にしてください。 + デバイスで生体認証がオフになっているため、ウォレットのロック解除に使用できません。生体認証を再度利用するには、デバイスの設定で有効にしてください。 生体認証が無効になっています カードまたはリングをスキャンしてください 生体認証の試行回数が上限に達しました。端末タップまたはアクセスコードでウォレットを解除してください。 @@ -152,7 +152,7 @@ 30秒後に再試行するか、カードまたはリングをスキャンしてください 生体認証ログインは一時的にロックされています。30秒後にもう一度お試しいただくか、端末タップまたはアクセスコードでウォレットを解除してください。 試行回数が多すぎます - お使いの携帯電話で生体認証が無効になっているため、アプリにウォレットを保存できません。ウォレットを保存するには、携帯電話の設定で生体認証機能を有効にしてください。 + スマートフォンの生体認証が無効になっているため、アプリにウォレットを保存できません。ウォレットを保存するには、スマートフォンの設定で生体認証機能を有効にしてください。 デバイスの生体認証が更新されました。再び生体認証ログインを有効にするため、ウォレットを選び、アクセスコードを入力してください。 対応が必要です プロモーションコードの処理中にエラーが発生しました。しばらくしてからもう一度お試しください。 @@ -217,7 +217,7 @@ 許可する 金額 アナリティクス - そして + 適用する 承認 承認 @@ -245,6 +245,7 @@ 近日公開 確認 接続中 + サポートへのお問い合わせ Tangemサポートへ問い合わせる Visaサポートへ問い合わせる 続ける @@ -269,7 +270,7 @@ 有効にする 有効 エラー - ネットワーク手数料 + 入金時のネットワーク手数料 スワップ 移動する 取引履歴を調べる @@ -366,6 +367,7 @@ 利用規約 利用規約 宛先 + %sへ 今日 %d トークン @@ -418,6 +420,7 @@ トークンは誰でも作成できることに注意してください。 Tangemウォレットを購入 チャット + Tangem Visaを入手 アクセスコード カードをスキャンする前に、正しいアクセスコードを送信する必要があります。 長くタップ @@ -429,7 +432,6 @@ デバイスの画面を下に向けると、残高をすばやく非表示にしたり表示したりできます。 %sハッシュ デバイスID - サポートへのお問い合わせ サポートチャットを開く 他のカードをリンクする アプリ通貨 @@ -518,7 +520,7 @@ %sを買い付けています %sを買い付けています... この取引を非表示にする - 一度非表示にすると、取引状況を再度表示することはできません。代わりに、スワイプして閉じることができます。 + この取引を非表示にすると、ステータス画面には表示されなくなります。ステータス画面を閉じて後で戻りたい場合は、画面をスワイプして閉じてください。 取引状況を非表示にしますか? このトークンはサポートされていません。別のトークンを選択してスワップしてください。 %sはサポートされていません @@ -590,7 +592,7 @@ まずバックアップを完了する まずバックアップを完了する その他の方法 - 秘密鍵をオフラインで安全に保存する物理デバイス。 + リカバリーフレーズは、ご自身で安全な場所に保管し、資金を守るために他人には絶対に共有しないでください。 リカバリーフレーズ アクセスコードを使用してウォレットを保護するには、まずバックアップを完了してください。 ウォレットをハードウェアにアップグレードするには、まずバックアップしてください。 @@ -684,11 +686,13 @@ APY %s 私のポートフォリオ マーケット + Tangemで稼ぐ 選択したネットワークのアドレスを生成するには、Tangemウォレットカードまたはリングをスキャンする必要があります。 トークンを追加するには、これをスワイプするか、検索バーをタップしてください。 このセクションのデータは、次のネットワークから取得されています: %s データを読み込めません… データなし + マーケット動向 クイックアクション マーケットから探す 結果 @@ -777,11 +781,17 @@ 取引量 これをドラッグするか、検索窓をタップして、マーケットから直接トークンを追加します トークンを追加 - 資産を即時アクセス可能な状態に保ったまま、パワーアップさせよう。%s + 資産を常時アクセス可能な状態に保ったまま、パワーアップさせよう。%s 利息モードを有効にする モバイルウォレットを作成するには、%1$sにアップデートする必要があります モバイルウォレットを使用するには、%1$s以降が必要です すべてのニュース + + %d時間前 + + + %d分前 + 最新情報を入手 お使いのデバイスではNFCが使用できません NFTについて @@ -837,6 +847,9 @@ 送信先アカウントには、送金されるアセットのトラストラインがありません。 ウォレットごとに$10相当のBTCをプレゼント\nお早めに! ブラックフライデー:最大30% オフ + 今すぐチェック + 理想のTangemセットを揃えよう。期間限定。 + 1+1:1つ購入で、2つ目が50%オフ 今すぐ参加 コードを共有すると、販売ごとに5 USDTを獲得できます。お友達は10%割引になります。 友達への紹介で報酬を獲得しよう! @@ -849,7 +862,7 @@ 保護する 後で各カードおよびリングに個別のアクセスコードを設定できます。 パーソナライズする - アクセスコードは、リンクされたカードおよびリング1つで復元できます。すべてのデバイスを1か所に保管しないでください。 + リンクされたカードまたはリングを使って、アクセスコードを復元できます。すべてのデバイスを同じ場所に保管しないでください。 復元する アクセスコードとして任意の単語、フレーズ、または数字を選択してください アクセスコードの作成 @@ -1193,6 +1206,8 @@ ネットワーク手数料とは、ブロックチェーン上で取引を処理し、承認するために支払う少額の料金です。 ステーキングを始めるには、1 TONを取引してTONアカウントを有効化する必要があります。資金はウォレット内にそのまま残ります。これは、ステーキング有効化のためのステップにすぎません。 アカウントの有効化 + ネットワーク手数料が変更されました。続行する前に新しい金額をご確認ください。 + ネットワーク手数料が更新されました ステーキング金額は %s 以上である必要があります ネットワークルールにより、ステーキング金額は%1$s TRX に切り上げられます。 ネットワークルールにより、ステーキング解除の量は%1$s TRX に切り上げられます。 @@ -1230,6 +1245,7 @@ ネットワークは、ステーキングのためにトークンの使用を承認していることを確認するために、トークン承認手数料を請求します。 ステーキング機能を使用すると、プロバイダーの%1$sと%2$sに同意したことになります ロック中 + 最大金額:%s 移行 ネイティブステーキング 現在、ステーキングに使用できるアクティブなバリデータは見つかりません。しばらくしてからもう一度お試しください。 @@ -1366,6 +1382,8 @@ 受け取る トークンを選択 利用不可 + 皆様からのフィードバックをお待ちしております + Tangem Payのベータ版を公開しました カードが凍結されています カード決済 入金 @@ -1379,6 +1397,7 @@ 一時停止 カードが凍結されています サポートを受ける + その他 完了 拒否 保留中 @@ -1430,9 +1449,14 @@ 現在、出金ができません 現在の処理が完了するまでは、スワップや新しい出金を開始することはできません。 出金処理中です + PINコードを変更 + 忘れた場合はアプリに戻って確認できます。 カードの発行に失敗しました 技術的なエラーが発生しました。下のボタンをクリックして、もう一度お試しください。 技術的なエラーが発生しました。サポートへお問い合わせください。 + 暗号資産を日常の支払いに使おう。\n\nこれまでにないタイプの決済カード。 + Tangem Payを入手 + サポートへ移動 通常は最大で15分ほどかかります Tangemカードのセットアップ カードを発行しています @@ -1440,8 +1464,11 @@ Tangem Pay プロフィールを確認できませんでした。ご不明な点があればサポートまでお問い合わせください。 申し訳ございませんが、本人確認を行うことができませんでした + KYC進行中 ステータスを表示 Tangem PayのKYC手続き進行中 + 暗号資産を、リアルな支払いに。\n他とはまったく違う、新しいタイプの決済カード。 + Tangem Visaカード カードをGET Apple PayとGoogle Payに対応したデジタルカード付き どこでも暗号資産を使える @@ -1455,9 +1482,12 @@ 技術的な問題を修正しています。後でもう一度お試しください。 サービスは一時的に利用できません 現在サービスに接続できません。後ほどもう一度お試しください。 + 同期が必要です + Tangem Visaカード Tangem Payは一時的に利用できません Tangem Pay カードまたはリングを使用して、支払いアカウントへのアクセスを復元してください。 + PINコード これは私のウォレットです 残高非表示 残高表示 @@ -1501,6 +1531,7 @@ 複数のアドレス 現在、このブロックチェーンでは取引履歴はサポートされていません。しかしご心配なく!弊社で対応中です。その間、エクスプローラーで確認することができます。 オペレーション + 対象:%s 送金元: %s 送金先: %s バリデーター: %s @@ -1551,6 +1582,7 @@ %d日間利用可能 残高と限度額 + アクセスコードは、支払いアカウントの管理および不正アクセスからの保護に使用されます。 アクセスコード 本当に終了してもよろしいですか?中断したところから後で続行できます。 長くはかかりません。アカウントを設定しています。 @@ -1847,7 +1879,7 @@ ワンタップで開始 シームレスで安全 シンプルな操作 - Tangemでハードウェアウォレットを作成しましょう。銀行のカードのようにスリムで、金庫のように安全です。 + Tangemでハードウェアウォレットを作成しよう。キャッシュカードのようにスリムで、金庫のように安全。 ソフトウェアウォレットを作成またはインポート スマートフォン上にソフトウェアウォレットを作成またはインポートする。 モバイルウォレットから始める @@ -1904,6 +1936,8 @@ 入金手数料ポリシー Tangemは、生成された利息に対して15%サービス手数料も徴収します。 ネットワーク手数料が下がるか、残高が最低必要額に達すると、資金は自動的にAaveに供給されます。 + 市場が非常に活発なため、現在の手数料は通常よりも高くなっています。今すぐ続行するか、手数料が下がるのを待って後で再確認することもできます。 + ネットワーク手数料が高額です 過去のリターン 保有資産に年利%1$s%%を適用 利息モードでのトークンの承認が取り消されました。トークンを開いて再度許可してください。 @@ -1920,7 +1954,7 @@ 分散型・自己管理型 このサービスを利用することにより、プロバイダー\n%1$sおよび%2$sに同意するものとします。 Aaveに接続 - 残高に %1$s%% の年利(APY)を適用 + 保有資産に\n年利%1$s%%を適用 Aave %1$s%% • 変動金利 変動金利 Aave @@ -1950,12 +1984,12 @@ 利息モードの有効化 利息モード 利息モードコントラクトのデプロイ - 利息モードを有効にする + 利息モードが有効になりました %1$sがAaveに供給されました - 利息モードを無効にする + 利息モードが無効になりました %1$sがAaveから引き出されました - 利息モードをセットアップ - 利息モードの再有効化 + 利息モードを初期化しました + 利息モードが再度有効になりました Aaveへの供給 %1$sがAaveに供給されました Aaveから引き出す diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 9cf3d2fac4..b5587d3945 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -3,6 +3,10 @@ Ваш кошелёк не защищён без кода доступа. Сбросить все равно Код доступа не задан + Изменить код + Ваш код доступа будет использоваться для разблокировки кошелька и защиты доступа к вашим активам. + Использовать + Код доступа может быть легко угадан Введите код доступа Неверный код доступа. Ваш мобильный кошелёк будет удалён после ещё %s неверных попыток. Подтвердите ваш код доступа, чтобы продолжить. @@ -14,6 +18,7 @@ Невозможно добавить новый аккаунт. Архивированные аккаунты Восстановить + Восстановить аккаунт Лимит в 20 активных аккаунтов достигнут. Пожалуйста, зархивируйте один аккаунт, чтобы продолжить. Невозможно восстановить аккаунт В архиве @@ -36,6 +41,7 @@ Новый аккаунт Добавить аккаунт Редактировать аккаунт + Попробуйте позже. Если проблема повторится, обратитесь в поддержку — мы поможем её решить. %1$s в %2$s Основной аккаунт Лимит %1$s активных аккаунтов превышен. Архивируйте один, чтобы продолжить. @@ -113,7 +119,7 @@ Вы успешно завершили бэкап вашего кошелька. Эти слова невозможно восстановить, если они будут потеряны. Храните их в надёжном месте. Бэкап завершен - Ваша секретная фраза восстановления — это фиксированный набор из %s случайных слов для доступа к вашему кошельку и его восстановления. + Ваша фраза восстановления — фиксированный набор из %s случайных слов для доступа и восстановления кошелька. Эти слова невозможно восстановить, если они будут потеряны. Храните их в безопасности. Храните в безопасности Сохраните эти %s слов в безопасном месте и никому их не сообщайте. @@ -129,10 +135,16 @@ Балансы скрыты Согласно информации от разработчиков сети, токены Kaspa находятся в режиме бета. Следите за обновлениями! Бета режим + Биометрия отключена на вашем устройстве, поэтому вы не можете использовать её для разблокировки кошельков. Включите биометрию в настройках устройства, чтобы снова использовать этот способ. + Биометрическая аутентификации отключена Пожалуйста, отсканируйте карту или кольцо + Вы достигли лимита попыток биометрии. Разблокируйте кошелёк с помощью касания устройства или введите код доступа. + Биометрическая аутентификации заблокирована Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту или кольцо + Вход по биометрии временно заблокирован. Попробуйте снова через 30 секунд или разблокируйте кошелёк с помощью прикладывания устройства или кода доступа. Слишком много попыток Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. + Внимание При обработке промокода произошла ошибка. Пожалуйста, попробуйте позже. Ошибка активации Ваш промокод был успешно активирован. Награда будет зачислена на ваш Bitcoin адрес в течение 14 дней. @@ -207,7 +219,7 @@ Разрешить Внимание Доступные сети - Резервное копирование + Резервная копия Баланс: %s Баланс биометрическую аутентификацию @@ -229,6 +241,7 @@ Скоро появится Подтвердить Подключение + Обратиться в поддержку Продолжить Конвертировать Копировать @@ -419,7 +432,6 @@ Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы %s хэшей Номер устройства - Обратиться в поддержку Открыть чат поддержки Добавить еще карты Валюта приложения @@ -576,16 +588,16 @@ Сначала завершите создание резервной копии Не завершено Другие способы - Физические устройства, которые надёжно хранят ваш приватный ключ офлайн. + Сохраните фразу восстановления в безопасном месте и держите её в секрете. Фраза восстановления Чтобы защитить ваш кошелёк с помощью кода доступа, сначала завершите резервное копирование. Чтобы улучшить кошелёк до аппаратного, сначала создайте резервную копию. Ваши приватные ключи надёжно зашифрованы и хранятся на вашем телефоне Ключи хранятся в приложении - Создайте или восстановите свой кошелёк с помощью фразы восстановления — вашей встроенной резервной копии - Резервная копия сид-фразы + Создайте или импортируйте кошелёк с помощью вашей фразы восстановления. + Резервная копия Создать мобильный кошелек - Импортировать существующий кошелек + Импортировать существующий Эта фраза восстановления уже была импортирована Мобильный кошелек Забыть кошелек @@ -602,7 +614,7 @@ Я понимаю, что если я не создал резервную копию кошелька перед его удалением, я могу потерять к нему доступ. Я понимаю, что удаление моего кошелька не стирает его — оно просто удаляет его с моего устройства. Фраза восстановления больше не нужна — ваша карта или кольцо Tangem становятся вашей надёжной резервной копией. - Резервное копирование с Tangem + Резервное копирование Это устройство не может быть использовано для апгрейда, оно уже содержит другой кошелек. Выберите другое устройство. Это нельзя использовать для обновления. Во время операции произошла ошибка. @@ -680,6 +692,7 @@ Данные раздела получены из следующих сетей: %s Невозможно загрузить данные Нет данных + Пульс рынка Быстрые действия Поиск на рынке Результат @@ -774,6 +787,10 @@ Объем Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из рынка Добавить токены + Увеличивайте доход с активов, сохраняя мгновенный доступ к ним. %s + Активировать режим доходности + Обновитесь до версии %1$s, чтобы создать мобильный кошелёк + Мобильный кошелек требует %1$s или новее. Функция NFC недоступна на вашем устройстве О NFT NFT @@ -834,6 +851,9 @@ Аккаунт получателя не содержит трастлайна для отправляемого актива. Получите $10 в BTC за каждый кошелёк.\nПоторопитесь! Чёрная пятница: скидки до 30% + Купить + Время ограничено! + 1+1: Купи один кошелёк — получи 50% скидку Присоединиться Поделись промокодом — заработай 5 USDT с каждой покупки. Твои друзья получат скидку 10% на карту Tangem! Получай бонусы за каждого друга! @@ -1379,12 +1399,112 @@ Вы получите Выберите токен не доступен - Пополнить + Будем рады вашей обратной связи + Tangem Pay в режиме beta + Карта заморожена + Оплата картой + Пополнение + Оспорить + Посмотреть в обозревателе + Сервисная комиссия Комиссия - Получить помощь - Скрыть детали - Всё готово! Ваша карта готова к использованию. - Получить карту + Всегда можно разморозить + Заморозить карту? + Не удалось заморозить карту, попробуйте еще раз + Заморозить + Карта заморожена + Обратиться в поддержку + Другое + Успешно завершено + Отклонено + В процессе + Тарифы и полные условия + Тарифы и лимиты + Банк отклонил транзакцию + Эта комиссия покрывает стоимость обработки вашего перевода. + Продолжайте пользоваться картой, заморозить всегда успеете + Разморозить карту? + Не удалось разморозить карту, попробуйте еще раз + Карта разморожена + Вывод + Пополнить + Способы пополнения + Номер + Сменить ПИН + Карта готова к покупкам + ПИН-код установлен + CVC + Не удалось загрузить данные. Повторите попытку позже. + Срок + Заморозить карту + Скрыть + Скрыть + Открыть Google Wallet + Настройте в пару кликов и начините платить + Настройте в пару кликов и начините платить + Добавьте карту в Google Pay + Добавьте карту в Apple Pay + Откройте Google Wallet + Нажмите на кнопку “+” сверху справа + Откройте Apple Wallet + Нажмите \"Добавить карту\" + Выберите \"Дебетовая или кредитная карта\" + Заполните данные карты вручную + Введите код подтверждения, направленный в email или СМС + Всё готово! Можно пользоваться картой + Добавьте карту в Google Pay + Добавить карту в Apple Pay + Скопируйте свой адрес или покажите QR + Техническая ошибка. Попробуйте позже или обратитесь в поддержку. + Пополнение недоступно + Показать + Реквизиты + Пополните карту любым активом через обмен + Реквизиты + Разморозить карту + Вывод + Вывод сейчас недоступен + Вы не можете начать обмен или новый вывод, пока не завершится текущий. + Вывод выполняется + Изменить PIN-код + Можно посмотреть здесь, если забудете его. + Не удалось выпустить карту + Техническая ошибка, попробуйте ещё раз, нажав кнопку ниже + Техническая ошибка, свяжитесь с поддержкой + Используйте криптовалюту в реальной жизни. \nКарта, не похожая ни на одну другую. + Получить Tangem Pay + Написать в поддержку + Обычно это занимает до 15 минут + Готовим вашу Tangem Card + Выпускаем карту + Выпускаем карту, это займет немного времени. + Tangem Pay + Не удалось подтвердить ваш профиль. Если у вас есть вопросы, обратитесь в службу поддержки. + К сожалению, нам не удалось подтвердить вашу личность + KYC в процессе + Посмотреть статус + KYC в процессе для Tangem Pay + Используйте криптовалюту в реальной жизни. \nКарта, не похожая ни на одну другую. + Tangem Visa Card + Открыть карту + Виртуальную карту можно добавить в Apple Pay и Google Pay + Покупайте где угодно + Никаких дополнительных комиссий за покупки + Сколько видишь – столько платишь + Мы создадим отдельный платежный счет без раскрытия ваших активов \nи их адресов + Абсолютная приватность + Откройте виртуальную \nTangem Pay Card + Payment account + Требуется синхронизация платежного счета + Мы устраняем техническую проблему. Пожалуйста, попробуйте позже. + Сервис временно недоступен + Сервис временно недоступен. Пожалуйста, попробуйте позже. + Требуется синхронизация + Tangem Visa Card + Tangem Pay временно недоступен + Tangem Pay + Используйте вашу карту или кольцо для восстановления доступа к платежному счету + Ваш PIN-код Это мой кошелек Балансы скрыты Балансы показаны @@ -1428,6 +1548,7 @@ Несколько адресов История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе. Операция + для: %s от: %s на: %s валидатор: %s @@ -1538,6 +1659,7 @@ Восстановите существующий кошелек через Google Drive. Импорт из Google Drive Добавить существующий кошелек + Физические устройства, которые безопасно хранят ваш приватный ключ офлайн. Сканировать кошелек Tangem Импортируйте существующий кошелек через вашу фразу восстановления. Имрортировать кошелек @@ -1813,6 +1935,7 @@ Tangem взимает комиссию за обслуживание в размере 15% от полученного дохода. Ваши средства будут автоматически переведены в Aave, как только комиссия сети снизится или баланс достигнет минимально необходимой суммы. Историческая доходность + Получай до %1$s APY на свой баланс Разрешение для вашего токена в режиме доходности было отозвано. Откройте токен, чтобы выдать разрешение снова. Необходимо разрешение для токена Проверьте ваше интернет соединение @@ -1827,7 +1950,9 @@ Децентрализованный и некастодиальный Используя сервис, вы соглашаетесь с %1$s и %2$s Подключить Aave + Подключите %1$s %% APY\nна ваш баланс Aave %1$s%% • Плавающая ставка + Динамическая процентная ставка Aave Сред. %s Доходность за прошлый год @@ -1854,12 +1979,16 @@ Режим доходности Включение режима доходности Режим доходности - Включение режима доходности + Установка контракта режима доходности + Режим доходности подключен %1$s отправлено в Aave - Отключение режима доходности + Режим доходности отключен %1$s выведено из Aave + Режим доходности инициализирован + Режим доходности реактивирован Перевод средств в Aave %1$s отправлено в Aave + Вывод из Aave Автоматически Внесите немного %1$s %2$s, чтобы покрыть комиссию сети за транзакции. Невозможно покрыть комиссию в %s 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 3bb44e8897..98ddcc642a 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -133,6 +133,7 @@ Закрити Підтвердити Підключення + Звернутися у підтримку Продовжити Конвертувати Копіювати @@ -298,7 +299,6 @@ Переверніть екран пристрою вниз, щоб швидко приховати та відобразити баланси %s хешів Номер пристрою - Звернутися у підтримку Додати більше карток Валюта застосунку Приховувати баланси жестом перевороту @@ -1127,6 +1127,22 @@ Ви отримаєте Оберіть токен недоступно + Номер картки + CVC + Термін дії + Сховати деталі + Додайте свою картку в Apple Pay + Натисніть кнопку \"+\" у верхньому правому куті + Відкрити Apple Wallet + Натисніть «Дебетова або кредитна картка» + Все готово! Ваша картка готова до використання. + Додайте свою картку в Apple Pay + Показати деталі + Обміняйте будь-який актив у вашому портфелі на картку + Розморозити картку + Tangem Visa Card + Ми усуваємо технічну проблему. Будь ласка, спробуйте пізніше. + Сервіс тимчасово недоступний Це мій гаманець Баланси приховано Баланси показано diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index 2e0760aa10..b2f04957dd 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -311,6 +311,111 @@ 交易 選擇代幣 無法使用 + 我們期待聆聽您的寶貴意見 + Tangem Pay現已開放測試版 + 卡片已凍結 + 信用卡支付 + 充值 + 爭議 + 探索交易 + 服務費 + 手續費 + 保護資金安全,隨時可解凍 + 凍結您的卡片? + 無法凍結卡片。請稍後再試。 + 凍結 + 您的卡片已凍結。 + 獲取幫助 + 其他 + 已完成 + 已拒絕 + 處理中 + 條款、費用與限制 + 條款與限制 + 銀行拒絕了此交易請求。 + 此費用用於支付處理您轉帳的成本。 + 繼續使用您的資金。您可以隨時凍結。 + 解凍您的卡片? + 無法解凍卡片。請稍後再試。 + 您的卡片已解凍。 + 提現 + 添加资金 + 充值选项 + 卡號 + 修改 PIN + 卡片已完全准备好进行支付。 + PIN 碼已建立 + CVC + 無法載入資料。請稍後再試。 + 有效期 + 冻结卡片 + 隱藏詳情 + 隐藏 + 打開 Google Wallet + 只需幾次點擊即可設置 Tangem Pay 並開始使用 Google Pay 付款。 + 设置Tangem Pay只需几次点击,即可开始使用Apple Pay支付 + 将您的卡片添加到 Google Pay + 將卡片添加到 Apple Pay + 打開 Google Wallet + 点击右上角的“+”按钮 + 打开Apple Wallet + 点击“添加卡片” + 點擊「借記卡或信用卡」 + 手動輸入卡片詳情 + 使用發送到您設備的 OTP 驗證卡片。 + 全部完成!您的卡片已準備就緒。 + 將卡片添加到 Google Pay + 添加卡片到 Apple Pay + 分享您的地址或显示二维码 + 暫時無法接收 + 显示 + 顯示詳情 + 將您投資組合中的任何資產兌換成卡片 + 卡片详情 + 解凍卡片 + 提现 + 目前无法提现 + 在当前操作完成之前,您无法启动兑换或新的提款。 + 提款进行中 + 修改PIN码 + 如果忘记了,请返回应用查看。 + 无法发行卡片 + 出现技术错误,请点击下方按钮重试 + 出现技术错误,请联系客服 + 使用您的加密货币进行真实世界消费。\n这是一张与众不同的支付卡。 + 获取Tangem Pay + 前往客服中心 + 通常需要最多15分钟 + 設置您的 Tangem 卡 + 正在发行您的卡片 + 正在为您准备卡片,这可能需要一些时间 + Tangem Pay + 我们无法验证您的资料。如有任何疑问,请联系客服。 + 很抱歉,我们无法验证您的身份 + KYC进行中 + 查看状态 + Tangem Pay 的 KYC 正在進行中 + 使用您的加密货币进行真实世界消费。这是一张与众不同的支付卡。 + Tangem Visa Card + 获取卡片 + 使用支援 Apple Pay 和 Google Pay 的數位卡 + 在任何地方花费您的资产 + 購物無額外費用 + 所見即所付 + 將創建單獨的支付帳戶,且不會透露您的地址和資產 + 無與倫比的隱私 + 在幾分鐘內獲得免費的 Tangem Pay 卡 + Payment account + 需要同步支付账户 + 我们正在修复技术问题。请稍后再试。 + 服務暫時無法使用 + 服务当前不可用,请稍后再试 + 需要同步 + Tangem Visa Card + Tangem Pay暂时不可用 + Tangem Pay + 使用您的卡片或戒指恢复对支付账户的访问 + 您的PIN码 隱藏 您即將在主屏幕上隱藏此代幣。您可以隨時通過管理代幣頁面將其添加回來。 隱藏 %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 5b8357caeb..8c52e119ff 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -428,6 +428,7 @@ Note that tokens can be created by anyone Buy Tangem Wallet Chat + Get Tangem Visa Access code You will have to submit the correct access code before scanning the card Long Tap @@ -439,7 +440,6 @@ Flip your device screen down to quickly hide and show balances %s hashes Device ID - Contact support Open support chat Link More Cards App Currency @@ -528,7 +528,7 @@ Buying %s Buying %s... Hide this transaction - Once hidden, the transaction status cannot be viewed again. You can simply swipe to dismiss instead. + If you hide this transaction, it won’t appear in the status screen anymore. If you just want to close the status screen and return later, simply swipe it away instead. Hide Transaction Status? This token is not supported. Please choose a different token to swap. %s is not supported @@ -601,9 +601,9 @@ Finalize backup first Incomplete Other methods - Physical devices that securely store your private key offline. + Manually save your recovery phrase in a secure place and keep it private to protect your funds. Recovery phrase - To secure your wallet with a Access Code, complete the backup first. + To secure your wallet with an Access Code, complete the backup first. To upgrade your wallet to hardware, back it up first. Your private keys are securely encrypted and stored on your phone Keys are stored in the app @@ -698,11 +698,13 @@ APY %s My portfolio Market + Earn with Tangem To generate addresses for selected networks, you must scan your Tangem Wallet card or ring To add tokens pull this up or tap the search bar This section’s data is sourced from the following networks: %s Unable to load the data… No data + Market Pulse Quick actions Search through the market Result @@ -798,6 +800,14 @@ You must update to %1$s in order to create mobile wallet Mobile Wallet requires %1$s or later All news + + + %dh ago + + + %d minute ago + %d minutes ago + Stay in the loop NFC is not available on your device About NFT @@ -855,6 +865,9 @@ The destination account does not have a trustline for the asset being sent. Get $10 in BTC with every wallet\nHurry! Black Friday: up to 30% OFF + Let’s go + Limited time! + 1+1: Buy One Wallet, Get 50% OFF Join Now Share your code - earn 5 USDT per sale. Your friend gets 10% OFF. Get REWARDS for every friend! @@ -1217,6 +1230,8 @@ A network fee is a small payment required to process and confirm your transaction on the blockchain. To begin staking, your TON account must be activated with a transaction of 1 TON. The funds stay in your account because this step only activates it for staking. Account activation + The network fee has changed. Please review the new amount before proceeding. + Network fee updated The amount to stake must be at least %s Staking amount will be rounded to %1$s TRX due to network rules. Unstaking amount will be rounded to %1$s TRX due to network rules. @@ -1255,6 +1270,7 @@ The network will charge a token approval fee to verify that you are authorizing the use of your token for the staking. By using staking functionality, you agree with provider’s %1$s and %2$s Locked + Maximum amount: %s Migrate Native staking No active validators available for staking at the moment. Please try again later. @@ -1400,7 +1416,7 @@ Explore transaction Service fees Fee - Keep your money safe if your card is lost or stolen. You can unfreeze anytime. + Keep your money safe. You can unfreeze anytime. Freeze your card? Failed to freeze the card. Try again later. Freeze @@ -1435,14 +1451,14 @@ Set up Tangem Pay in a few taps and start paying with Google Pay. Set up Tangem Pay in a few taps and start paying with Apple Pay. Add your card to Google Pay - Add your card to Apple Pay + Add your card to Apple Pay Open Google Wallet Tap “+” button on the top right Open Apple Wallet Tap “Add a card” Tap “Debit or Credit Card” Enter the card details manually - Verify card using the OTP sent to your device. + Verify card using the OTP sent to your device. All set! Your card is ready to use. Add card to Google Pay Add card to Apple Pay @@ -1458,9 +1474,14 @@ Withdraw unavailable now You can\'t initiate swap or new withdrawal till the current one is finished Withdrawal in progress + Change PIN-code + Come back to the app if you forget it. Failed to issue card A technical error has occurred, please try again by clicking the button below. A technical error has occurred, please contact support. + Use your crypto for real world spending. \nIt\'s a payment card unlike any other. + Get Tangem Pay + Go to Support It usually takes up to 15 minutes Setting up your Tangem Card Issuing your card @@ -1471,6 +1492,8 @@ KYC in progress View Status KYC in progress for Tangem Pay + Use your crypto for real world spending. \nIt’s a payment card unlike any other. + Tangem Visa Card Get card With digital card that works with Apple Pay and Google Pay Spend your assets anywhere @@ -1485,9 +1508,11 @@ Service temporarily unavailable The service is currently unreachable. Please try again later. Sync needed + Tangem Visa Card Tangem Pay is temporarily unavailable Tangem Pay Use your card or ring to restore access to your payment account + Your PIN code This is my wallet Balances hidden Balances shown @@ -1531,6 +1556,7 @@ Multiple addresses Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer. Operation + for: %s from: %s to: %s validator: %s @@ -1983,8 +2009,10 @@ Top-up fee policy Tangem also takes a 15% service fee on yield generated. Your funds will be automatically supplied to Aave once network fees are lower or your balance meets the minimum required amount. + Fees are higher than usual because the market is very active. You can proceed now or check back later when the fees are lower. + High Network Fees Historical returns - "Enable %1$s%% APY on your balance" + Enable %1$s%% APY on your balance Approval for your token in Yield Mode has been revoked. Open the token to grant permission again. Token approval needed Check your network connection @@ -2029,12 +2057,12 @@ Enabling Yield Mode Yield Mode Yield Mode contract deploy - Yield Mode enable + Yield Mode enabled %1$s supplied to Aave - Yield Mode disable + Yield Mode disabled %1$s withdrawn from Aave - Yield Mode initialize - Yield Mode reactivate + Yield Mode initialized + Yield Mode reactivated Supply to Aave %1$s supplied to Aave Withdraw from Aave diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt index 374b6de2e3..fc56519c01 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/TokenItem.kt @@ -3,6 +3,7 @@ package com.tangem.core.ui.components.token import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.dp import com.tangem.core.ui.R import com.tangem.core.ui.components.audits.AuditLabelUM import com.tangem.core.ui.components.currency.icon.CurrencyIcon @@ -28,6 +30,7 @@ import com.tangem.core.ui.components.token.internal.* import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState import com.tangem.core.ui.components.token.state.TokenItemState.Subtitle2State +import com.tangem.core.ui.components.token.state.TokenItemState.PromoBannerState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.rememberHapticFeedback import com.tangem.core.ui.extensions.stringReference @@ -44,7 +47,7 @@ private const val TITLE_MIN_WIDTH_COEFFICIENT = 0.3 private const val PRICE_MIN_WIDTH_COEFFICIENT = 0.32 private enum class LayoutId { - ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT + ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT, PROMO_BANNER } /** @@ -109,6 +112,14 @@ fun TokenItem( .testTag(TokenElementsTestTags.TOKEN_ICON), ) + YieldSupplyPromoBanner( + state = state.promoBannerState, + modifier = Modifier + .layoutId(layoutId = LayoutId.PROMO_BANNER) + .testTag(TokenElementsTestTags.TOKEN_YIELD_PROMO_BANNER) + .fillMaxWidth(), + ) + TokenTitle( state = state.titleState, modifier = Modifier @@ -220,6 +231,13 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier val nonFiatContent = measurables.measure(layoutId = LayoutId.NON_FIAT_CONTENT, constraints = constraints) + val promoBanner = when (state.promoBannerState) { + is PromoBannerState.Content -> measurables.measure( + layoutId = LayoutId.PROMO_BANNER, + constraints = constraints, + ) + else -> null + } var firstRowRemainingFreeSpace: Int? = null var secondRowRemainingFreeSpace: Int? = null @@ -283,10 +301,18 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier ) } + val promoBannerHeight = promoBanner?.height ?: 0 + val promoOffset = if (promoBannerHeight > 0) { + promoBannerHeight - 8.dp.roundToPx() + } else { + 0 + } + val layoutHeight = calculateLayoutHeight( state = state, minLayoutHeight = with(density) { dimens.size68.roundToPx() }, layoutPadding = verticalPadding, + promoOffset = promoOffset, title = title, fiatAmount = fiatAmount, cryptoAmount = cryptoAmount, @@ -294,16 +320,22 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier ) layout(width = constraints.maxWidth, height = layoutHeight) { - icon.placeRelative(x = 0, y = (layoutHeight - icon.height).div(other = 2)) + promoBanner?.placeRelative(x = 0, y = 0) + + icon.placeRelative( + x = 0, + y = promoOffset + (layoutHeight - promoOffset - icon.height) + .div(other = 2), + ) title.placeRelative( x = icon.width, - y = when (state) { + y = promoOffset + when (state) { is TokenItemState.NoAddress, is TokenItemState.Unreachable, -> { if (state.subtitleState == null) { - (layoutHeight - title.height).div(other = 2) + (layoutHeight - promoOffset - title.height).div(other = 2) } else { verticalPadding } @@ -314,8 +346,8 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier fiatAmount?.placeRelative( x = layoutWidth - fiatAmount.width, - y = when (state.subtitle2State) { - null -> (layoutHeight - fiatAmount.height).div(other = 2) + y = promoOffset + when (state.subtitle2State) { + null -> (layoutHeight - promoOffset - fiatAmount.height).div(other = 2) else -> verticalPadding }, ) @@ -335,7 +367,7 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier nonFiatContent.placeRelative( x = layoutWidth - nonFiatContent.width, - y = (layoutHeight - nonFiatContent.height).div(other = 2), + y = promoOffset + (layoutHeight - promoOffset - nonFiatContent.height).div(other = 2), ) } } @@ -443,6 +475,7 @@ private fun calculateLayoutHeight( state: TokenItemState, minLayoutHeight: Int, layoutPadding: Int, + promoOffset: Int, title: Placeable, fiatAmount: Placeable?, cryptoAmount: Placeable?, @@ -468,7 +501,7 @@ private fun calculateLayoutHeight( } } - return max(firstColumnHeight, secondColumnHeight).coerceAtLeast(minLayoutHeight) + return (promoOffset + max(firstColumnHeight, secondColumnHeight)).coerceAtLeast(promoOffset + minLayoutHeight) } @Preview(widthDp = 360, showBackground = true) @@ -587,6 +620,11 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider YieldSupplyPromoBanner(state = state, modifier = modifier) + is PromoBannerState.Empty -> Unit + } +} + +@Composable +internal fun YieldSupplyPromoBanner(state: PromoBannerState.Content, modifier: Modifier = Modifier) { + val bgColor = TangemTheme.colors.control.unchecked + Column(modifier = modifier) { + Row( + modifier = Modifier + .background(color = bgColor, shape = TangemTheme.shapes.roundedCornersXMedium) + .padding(horizontal = 12.dp, vertical = 8.dp) + .clickable(onClick = state.onPromoBannerClick) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_analytics_up_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + modifier = Modifier + .padding(end = TangemTheme.dimens.spacing8) + .size(TangemTheme.dimens.size16), + ) + Text( + text = state.title.resolveReference(), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.secondary, + modifier = Modifier + .weight(1f) + .padding(end = TangemTheme.dimens.spacing8), + ) + Icon( + painter = painterResource(id = R.drawable.ic_close_24), + contentDescription = null, + tint = TangemTheme.colors.text.secondary, + modifier = Modifier + .size(TangemTheme.dimens.size16) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false), + onClick = { state.onCloseClick() }, + ), + ) + } + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_rectangle_bottom), + contentDescription = null, + tint = bgColor, + modifier = Modifier + .size(width = 12.dp, height = 8.dp), + ) + } + } +} + +@Preview(widthDp = 360, showBackground = true) +@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_YieldSupplyPromoBanner() { + TangemThemePreview { + YieldSupplyPromoBanner( + state = PromoBannerState.Content( + title = TextReference.Str(value = "Earn up to 5% APY"), + onPromoBannerClick = {}, + onCloseClick = {}, + ), + modifier = Modifier.fillMaxWidth(), + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt index 7f05cdb7cb..a2180bb614 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt @@ -34,6 +34,8 @@ sealed class TokenItemState { */ abstract val subtitle2State: Subtitle2State? + abstract val promoBannerState: PromoBannerState + /** Callback which will be called when an item is clicked */ abstract val onItemClick: ((TokenItemState) -> Unit)? @@ -59,6 +61,7 @@ sealed class TokenItemState { ) : TokenItemState() { override val fiatAmountState: FiatAmountState = FiatAmountState.Loading override val subtitle2State: Subtitle2State = Subtitle2State.Loading + override val promoBannerState: PromoBannerState = PromoBannerState.Empty override val onItemClick: ((TokenItemState) -> Unit)? = null override val onItemLongClick: ((TokenItemState) -> Unit)? = null override val onApyLabelClick: ((TokenItemState) -> Unit)? = null @@ -75,6 +78,7 @@ sealed class TokenItemState { override val subtitleState: SubtitleState = SubtitleState.Locked override val fiatAmountState: FiatAmountState = FiatAmountState.Locked override val subtitle2State: Subtitle2State = Subtitle2State.Locked + override val promoBannerState: PromoBannerState = PromoBannerState.Empty override val onItemClick: ((TokenItemState) -> Unit)? = null override val onItemLongClick: ((TokenItemState) -> Unit)? = null override val onApyLabelClick: ((TokenItemState) -> Unit)? = null @@ -99,6 +103,7 @@ sealed class TokenItemState { override val subtitleState: SubtitleState, override val fiatAmountState: FiatAmountState?, override val subtitle2State: Subtitle2State?, + override val promoBannerState: PromoBannerState = PromoBannerState.Empty, override val onItemClick: ((TokenItemState) -> Unit)?, override val onItemLongClick: ((TokenItemState) -> Unit)?, override val onApyLabelClick: ((TokenItemState) -> Unit)? = null, @@ -120,6 +125,7 @@ sealed class TokenItemState { ) : TokenItemState() { override val subtitleState: SubtitleState? = null override val fiatAmountState: FiatAmountState? = null + override val promoBannerState: PromoBannerState = PromoBannerState.Empty override val onItemClick: ((TokenItemState) -> Unit)? = null override val onItemLongClick: ((TokenItemState) -> Unit)? = null override val onApyLabelClick: ((TokenItemState) -> Unit)? = null @@ -146,6 +152,7 @@ sealed class TokenItemState { ) : TokenItemState() { override val fiatAmountState: FiatAmountState? = null override val subtitle2State: Subtitle2State? = null + override val promoBannerState: PromoBannerState = PromoBannerState.Empty } /** @@ -168,6 +175,7 @@ sealed class TokenItemState { override val subtitle2State: Subtitle2State? = null override val onItemClick: ((TokenItemState) -> Unit)? = null override val onApyLabelClick: ((TokenItemState) -> Unit)? = null + override val promoBannerState: PromoBannerState = PromoBannerState.Empty } @Immutable @@ -254,4 +262,15 @@ sealed class TokenItemState { data object Locked : Subtitle2State() } + + @Immutable + sealed class PromoBannerState { + data class Content( + val title: TextReference, + val onPromoBannerClick: () -> Unit, + val onCloseClick: () -> Unit, + ) : PromoBannerState() + + data object Empty : PromoBannerState() + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt index 53ec6c23b8..884b8bfdae 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt @@ -19,10 +19,12 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ChainStyle import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import androidx.constraintlayout.compose.Visibility +import androidx.constraintlayout.compose.atLeast import com.tangem.core.ui.R import com.tangem.core.ui.components.CircleShimmer import com.tangem.core.ui.components.RectangleShimmer @@ -94,7 +96,7 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod bottom.linkTo(subtitleItem.top) start.linkTo(iconItem.end) end.linkTo(amountItem.start) - width = Dimension.fillToConstraints + width = Dimension.fillToConstraints.atLeast(50.dp) }, ) @@ -122,7 +124,6 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod visibility = state.isGoneIf { amount.isEmpty() } top.linkTo(parent.top) bottom.linkTo(timestampItem.top) - start.linkTo(titleItem.end) end.linkTo(parent.end) width = Dimension.fillToConstraints }, @@ -436,7 +437,7 @@ private class TransactionItemStateProvider : CollectionPreviewParameterProvider< ), TransactionState.Content( txHash = UUID.randomUUID().toString(), - amount = "0.625 USDT", + amount = "0.62521313 USDT", time = "€0.50", status = Status.Confirmed, direction = Direction.OUTGOING, diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt index a6c08721f7..d513f7791c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/TokenElementsTestTags.kt @@ -7,4 +7,5 @@ object TokenElementsTestTags { const val TOKEN_FIAT_AMOUNT = "TOKEN_FIAT_AMOUNT" const val TOKEN_CRYPTO_AMOUNT = "TOKEN_CRYPTO_AMOUNT" const val TOKEN_NON_FIAT_BLOCK = "TOKEN_NON_FIAT_BLOCK" + const val TOKEN_YIELD_PROMO_BANNER = "TOKEN_YIELD_PROMO_BANNER" } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_connect_24.xml b/core/ui/src/main/res/drawable/ic_connect_24.xml new file mode 100644 index 0000000000..ebab5fcf69 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_connect_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_disconnect_24.xml b/core/ui/src/main/res/drawable/ic_disconnect_24.xml new file mode 100644 index 0000000000..2baf3ca1c9 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_disconnect_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_gear_24.xml b/core/ui/src/main/res/drawable/ic_gear_24.xml new file mode 100644 index 0000000000..ea7f21abb2 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_gear_24.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core/ui/src/main/res/drawable/ic_rectangle_bottom.xml b/core/ui/src/main/res/drawable/ic_rectangle_bottom.xml new file mode 100644 index 0000000000..6b77c79cb9 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_rectangle_bottom.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/img_one_plus_one_promo.webp b/core/ui/src/main/res/drawable/img_one_plus_one_promo.webp new file mode 100644 index 0000000000..729a3af0c7 Binary files /dev/null and b/core/ui/src/main/res/drawable/img_one_plus_one_promo.webp differ diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index 8a2fd97b72..7d14d15efb 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -69,15 +69,16 @@ fun CoroutineScope.launchOnCancellation(block: suspend () -> Unit) { } @Suppress("LongParameterList", "MagicNumber") -inline fun combine6( +inline fun combine7( flow1: Flow, flow2: Flow, flow3: Flow, flow4: Flow, flow5: Flow, flow6: Flow, - crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R, -): Flow = combine(flow1, flow2, flow3, flow4, flow5, flow6) { arr -> + flow7: Flow, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R, +): Flow = combine(flow1, flow2, flow3, flow4, flow5, flow6, flow7) { arr -> @Suppress("UNCHECKED_CAST") transform( arr[0] as T1, @@ -86,5 +87,6 @@ inline fun combine6( arr[3] as T4, arr[4] as T5, arr[5] as T6, + arr[6] as T7, ) } \ No newline at end of file diff --git a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt index 1e465aa020..14a73f37dd 100644 --- a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt +++ b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt @@ -61,6 +61,11 @@ internal class DefaultPromoRepository( PromoId.BlackFriday -> { val isActive = getBlackFridayPromoBanner()?.isActive == true + isActive && shouldShow + } + PromoId.OnePlusOne -> { + val isActive = getOnePlusOnePromoBanner()?.isActive == true + isActive && shouldShow } } @@ -73,6 +78,7 @@ internal class DefaultPromoRepository( PromoId.Sepa -> flowOf(false) PromoId.VisaPresale -> flowOf(false) PromoId.BlackFriday -> flowOf(false) + PromoId.OnePlusOne -> flowOf(false) } } @@ -176,11 +182,20 @@ internal class DefaultPromoRepository( }.getOrNull() } + private suspend fun getOnePlusOnePromoBanner(): PromoBanner? { + return runCatching(dispatchers.io) { + promoBannerConverter.convert( + tangemApi.getPromoBanner(ONE_PLUS_ONE_NAME).getOrThrow(), + ) + }.getOrNull() + } + private companion object { const val SEPA_NAME = "sepa" const val VISA_NAME = "visa-waitlist" const val BLACK_FRIDAY_NAME = "black-friday" const val MOONPAY_NAME = "moonpay" + const val ONE_PLUS_ONE_NAME = "one-plus-one" const val STORIES_LOAD_DELAY = 1000L } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayCryptoCurrencyFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayCryptoCurrencyFactory.kt index a768bf84b1..505383a099 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayCryptoCurrencyFactory.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayCryptoCurrencyFactory.kt @@ -2,14 +2,12 @@ package com.tangem.data.pay import arrow.core.Either import arrow.core.Either.Companion.catch -import com.squareup.moshi.Moshi import com.tangem.blockchain.blockchains.ethereum.Chain import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.core.error.UniversalError import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.data.common.network.NetworkFactory import com.tangem.data.pay.util.TangemPayErrorConverter -import com.tangem.datasource.di.NetworkMoshi import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory @@ -26,8 +24,8 @@ private const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d private const val TOKEN_DECIMALS = 6 internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor( - @NetworkMoshi moshi: Moshi, excludedBlockchains: ExcludedBlockchains, + private val errorConverter: TangemPayErrorConverter, ) : TangemPayCryptoCurrencyFactory { private val cryptoCurrencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) { @@ -37,8 +35,6 @@ internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor( NetworkFactory(excludedBlockchains) } - private val errorConverter by lazy(mode = LazyThreadSafetyMode.NONE) { TangemPayErrorConverter(moshi) } - override fun create(userWallet: UserWallet, chainId: Int): Either { return catch { val chain = requireNotNull(Chain.entries.find { it.id == chainId }) { "Can not find chain with $chainId" } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt index 47a0e4d342..fc619e65ab 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt @@ -1,8 +1,12 @@ package com.tangem.data.pay.repository import arrow.core.Either +import arrow.core.left +import arrow.core.raise.catch +import arrow.core.right import com.tangem.core.error.UniversalError import com.tangem.data.pay.util.RainCryptoUtil +import com.tangem.data.pay.util.TangemPayErrorConverter import com.tangem.data.visa.config.VisaLibLoader import com.tangem.datasource.api.common.config.ApiConfig import com.tangem.datasource.api.common.config.ApiEnvironment @@ -40,6 +44,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( private val rainCryptoUtil: RainCryptoUtil, private val storage: TangemPayStorage, private val cardFrozenStateStore: TangemPayCardFrozenStateStore, + private val errorConverter: TangemPayErrorConverter, ) : TangemPayCardDetailsRepository { private val pollingScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) @@ -64,37 +69,39 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( } override suspend fun revealCardDetails(userWalletId: UserWalletId): Either { - return requestHelper.runWithErrorLogs(TAG) { - val publicKeyBase64 = getPublicKeyBase64() - val (secretKeyBytes, sessionId) = rainCryptoUtil.generateSecretKeyAndSessionId(publicKeyBase64) + return catch( + block = { + val publicKeyBase64 = getPublicKeyBase64() + val (secretKeyBytes, sessionId) = rainCryptoUtil.generateSecretKeyAndSessionId(publicKeyBase64) + val result = requestHelper.performRequest(userWalletId = userWalletId) { authHeader -> + tangemPayApi.revealCardDetails( + authHeader = authHeader, + body = CardDetailsRequest(sessionId = sessionId), + ) + }.getOrNull()?.result ?: error("Cannot reveal card details") - val result = requestHelper.request(userWalletId) { authHeader -> - tangemPayApi.revealCardDetails( - authHeader = authHeader, - body = CardDetailsRequest(sessionId = sessionId), + val pan = rainCryptoUtil.decryptSecret( + base64Secret = result.pan.secret, + base64Iv = result.pan.iv, + secretKeyBytes = secretKeyBytes, ) - }.result ?: error("Cannot reveal card details") - val pan = rainCryptoUtil.decryptSecret( - base64Secret = result.pan.secret, - base64Iv = result.pan.iv, - secretKeyBytes = secretKeyBytes, - ) + val cvv = rainCryptoUtil.decryptSecret( + base64Secret = result.cvv.secret, + base64Iv = result.cvv.iv, + secretKeyBytes = secretKeyBytes, + ) + secretKeyBytes.fill(0) - val cvv = rainCryptoUtil.decryptSecret( - base64Secret = result.cvv.secret, - base64Iv = result.cvv.iv, - secretKeyBytes = secretKeyBytes, - ) - secretKeyBytes.fill(0) - - TangemPayCardDetails( - pan = pan, - cvv = cvv, - expirationYear = result.expirationYear, - expirationMonth = result.expirationMonth, - ) - } + TangemPayCardDetails( + pan = pan, + cvv = cvv, + expirationYear = result.expirationYear, + expirationMonth = result.expirationMonth, + ).right() + }, + catch = { errorConverter.convert(it).left() }, + ) } override suspend fun setPin(userWalletId: UserWalletId, pin: String): Either { diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt index 7e6b5ec06c..788aafe9d8 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt @@ -5,14 +5,12 @@ import arrow.core.getOrElse import arrow.core.left import arrow.core.raise.catch import arrow.core.right -import com.squareup.moshi.Moshi import com.squareup.wire.Instant import com.tangem.data.pay.util.TangemPayErrorConverter import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.pay.TangemPayAuthApi import com.tangem.datasource.api.pay.models.request.RefreshCustomerWalletAccessTokenRequest import com.tangem.datasource.api.pay.models.response.TangemPayGetTokensResponse -import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId @@ -33,7 +31,7 @@ private const val TAG = "TangemPayRequestPerformer" @Singleton internal class TangemPayRequestPerformer @Inject constructor( - @NetworkMoshi moshi: Moshi, + private val errorConverter: TangemPayErrorConverter, private val environmentConfigStorage: EnvironmentConfigStorage, private val dispatchers: CoroutineDispatcherProvider, private val tangemPayAuthApi: TangemPayAuthApi, @@ -42,7 +40,6 @@ internal class TangemPayRequestPerformer @Inject constructor( private val customerWalletAddresses = ConcurrentHashMap() private val tokensMutex = Mutex() - private val errorConverter = TangemPayErrorConverter(moshi) @Deprecated("Do not use this method") suspend fun runWithErrorLogs(tag: String, requestBlock: suspend () -> T): Either { diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayErrorConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayErrorConverter.kt index 145269f0f5..8080f3b642 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayErrorConverter.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayErrorConverter.kt @@ -3,10 +3,16 @@ package com.tangem.data.pay.util import com.squareup.moshi.Moshi import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.pay.models.response.VisaErrorResponse +import com.tangem.datasource.di.NetworkMoshi import com.tangem.domain.visa.error.VisaApiError import com.tangem.utils.converter.Converter +import javax.inject.Inject +import javax.inject.Singleton -class TangemPayErrorConverter(moshi: Moshi) : Converter { +@Singleton +internal class TangemPayErrorConverter @Inject constructor( + @NetworkMoshi moshi: Moshi, +) : Converter { private val visaErrorAdapter by lazy { moshi.adapter(VisaErrorResponse::class.java) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt index b0e9c01c6e..9b04683948 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt @@ -148,14 +148,12 @@ internal object WalletConnectDataModule { @SdkMoshi moshi: Moshi, sessionsManager: WcSessionsManager, factories: WcEthNetwork.Factories, - walletManagersFacade: WalletManagersFacade, wcNetworksConverter: WcNetworksConverter, ): WcEthNetwork = WcEthNetwork( moshi = moshi, networksConverter = wcNetworksConverter, sessionsManager = sessionsManager, factories = factories, - walletManagersFacade = walletManagersFacade, ) @Provides @@ -165,24 +163,20 @@ internal object WalletConnectDataModule { wcNetworksConverter: WcNetworksConverter, sessionsManager: WcSessionsManager, factories: WcSolanaNetwork.Factories, - walletManagersFacade: WalletManagersFacade, ): WcSolanaNetwork = WcSolanaNetwork( moshi = moshi, sessionsManager = sessionsManager, factories = factories, networksConverter = wcNetworksConverter, - walletManagersFacade = walletManagersFacade, ) @Provides @Singleton fun caipNamespaceDelegate( namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>, - walletManagersFacade: WalletManagersFacade, wcNetworksConverter: WcNetworksConverter, ): CaipNamespaceDelegate = CaipNamespaceDelegate( namespaceConverters = namespaceConverters, - walletManagersFacade = walletManagersFacade, wcNetworksConverter = wcNetworksConverter, ) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt index a14460a566..b713e9f18b 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -18,7 +18,6 @@ import com.tangem.domain.walletconnect.model.* import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase -import com.tangem.domain.walletmanager.WalletManagersFacade import jakarta.inject.Inject internal class WcEthNetwork( @@ -26,7 +25,6 @@ internal class WcEthNetwork( private val sessionsManager: WcSessionsManager, private val factories: Factories, private val networksConverter: WcNetworksConverter, - private val walletManagersFacade: WalletManagersFacade, ) : WcRequestToUseCaseConverter { override fun toWcMethodName(request: WcSdkSessionRequest): WcEthMethodName? { @@ -57,7 +55,7 @@ internal class WcEthNetwork( is WcEthMethod.SwitchEthereumChain, -> anyExistNetwork() - ?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() } + ?.let { network -> networksConverter.getAddressForWC(wallet.walletId, network).orEmpty() } .orEmpty() } val walletNetwork = when (method) { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt index ebaf668ba0..db6f83849d 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt @@ -22,7 +22,6 @@ import com.tangem.domain.walletconnect.model.WcSolanaMethodName import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.repository.WcSessionsManager import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase -import com.tangem.domain.walletmanager.WalletManagersFacade import jakarta.inject.Inject internal class WcSolanaNetwork( @@ -30,7 +29,6 @@ internal class WcSolanaNetwork( private val sessionsManager: WcSessionsManager, private val factories: Factories, private val networksConverter: WcNetworksConverter, - private val walletManagersFacade: WalletManagersFacade, ) : WcRequestToUseCaseConverter { override fun toWcMethodName(request: WcSdkSessionRequest): WcSolanaMethodName? { @@ -52,7 +50,7 @@ internal class WcSolanaNetwork( val chainId = request.chainId.orEmpty() suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet) suspend fun anyAddress() = anyExistNetwork() - ?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() } + ?.let { network -> networksConverter.getAddressForWC(wallet.walletId, network).orEmpty() } .orEmpty() val accountAddress = when (method) { diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt index 4b5d2c336a..3013709c9f 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/CaipNamespaceDelegate.kt @@ -9,11 +9,9 @@ import com.tangem.data.walletconnect.utils.WcNetworksConverter import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.walletconnect.model.WcSessionApprove -import com.tangem.domain.walletmanager.WalletManagersFacade internal class CaipNamespaceDelegate( private val namespaceConverters: Set, - private val walletManagersFacade: WalletManagersFacade, private val wcNetworksConverter: WcNetworksConverter, ) { @@ -36,7 +34,7 @@ internal class CaipNamespaceDelegate( } suspend fun createCAIP10(userWalletId: UserWalletId, network: Network): CAIP10? { - val address = walletManagersFacade.getDefaultAddress(userWalletId, network) + val address = wcNetworksConverter.getAddressForWC(userWalletId, network) val chainId = allWcNetworks .find { (wcNetwork, _) -> network.rawId == wcNetwork.rawId } ?.second diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt index 880b142dbd..a494eb772e 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt @@ -1,6 +1,9 @@ package com.tangem.data.walletconnect.utils import com.reown.walletkit.client.Wallet +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.address.AddressType +import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.data.common.currency.isCustomCoin import com.tangem.data.walletconnect.model.CAIP10 import com.tangem.domain.account.producer.SingleAccountProducer @@ -44,7 +47,7 @@ internal class WcNetworksConverter @Inject constructor( val allCoinNetwork = filterWalletNetworkForRequest(request.chainId.orEmpty(), session.wallet) val requestNetwork = allCoinNetwork.find { network -> - val address = walletManagersFacade.getDefaultAddress(wallet.walletId, network) + val address = getAddressForWC(wallet.walletId, network) requestAddress.lowercase() == address?.lowercase() } return requestNetwork @@ -60,7 +63,18 @@ internal class WcNetworksConverter @Inject constructor( suspend fun allAddressForChain(rawChainId: String, wallet: UserWallet): List { return filterWalletNetworkForRequest(rawChainId, wallet) - .mapNotNull { walletManagersFacade.getDefaultAddress(wallet.walletId, it)?.lowercase() } + .mapNotNull { getAddressForWC(wallet.walletId, it)?.lowercase() } + } + + suspend fun getAddressForWC(userWalletId: UserWalletId, network: Network): String? { + return when (network.toBlockchain()) { + Blockchain.XDC, + Blockchain.XDCTestnet, + -> walletManagersFacade.getAddresses(userWalletId, network) + .find { address -> address.type == AddressType.Legacy } + ?.value + else -> walletManagersFacade.getDefaultAddress(userWalletId, network) + } } /** @@ -94,8 +108,8 @@ internal class WcNetworksConverter @Inject constructor( // find all derivation .filter { it.rawId == blockchain.id } // find equal address - .firstOrNull { - val walletAddress = walletManagersFacade.getDefaultAddress(wallet.walletId, it) + .firstOrNull { network -> + val walletAddress = getAddressForWC(wallet.walletId, network) walletAddress?.lowercase() == caip10.accountAddress.lowercase() } } diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionHistoryItemConverter.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionHistoryItemConverter.kt index 40e874e14e..1a677b9cdb 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionHistoryItemConverter.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionHistoryItemConverter.kt @@ -24,7 +24,7 @@ internal class SdkTransactionHistoryItemConverter( SdkTransactionHistoryItem.TransactionStatus.Failed -> TxInfo.TransactionStatus.Failed SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed }, - type = typeConverter.convert(value.type), + type = typeConverter.convert(value.type to value.destinationType.toDomain()), amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" }, ) diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionTypeConverter.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionTypeConverter.kt index 7e674c1f59..5e9fe1dc0b 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionTypeConverter.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/SdkTransactionTypeConverter.kt @@ -1,21 +1,27 @@ package com.tangem.data.walletmanager.utils import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem.TransactionType +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyExitCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyInitTokenCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyReactivateTokenCallData import com.tangem.domain.models.network.TxInfo import com.tangem.domain.walletmanager.model.SmartContractMethod import com.tangem.utils.converter.Converter internal class SdkTransactionTypeConverter( private val smartContractMethods: Map, -) : Converter { +) : Converter, TxInfo.TransactionType> { - override fun convert(value: TransactionType): TxInfo.TransactionType { - return when (value) { + override fun convert(value: Pair): TxInfo.TransactionType { + val (type, destination) = value + + return when (type) { is TransactionType.ContractMethod -> { - getTransactionType(methodName = smartContractMethods[value.id]?.name) + getTransactionType(methodName = smartContractMethods[type.id]?.name, type.callData, destination) } is TransactionType.ContractMethodName -> { - getTransactionType(methodName = value.name) + getTransactionType(methodName = type.name, type.callData, destination) } is TransactionType.Transfer -> { TxInfo.TransactionType.Transfer @@ -27,7 +33,7 @@ internal class SdkTransactionTypeConverter( TxInfo.TransactionType.Staking.Unstake } is TransactionType.TronStakingTransactionType.VoteWitnessContract -> { - TxInfo.TransactionType.Staking.Vote(value.validatorAddress) + TxInfo.TransactionType.Staking.Vote(type.validatorAddress) } is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> { TxInfo.TransactionType.Staking.ClaimRewards @@ -38,7 +44,12 @@ internal class SdkTransactionTypeConverter( } } - private fun getTransactionType(methodName: String?): TxInfo.TransactionType { + @Suppress("CyclomaticComplexMethod") + private fun getTransactionType( + methodName: String?, + callData: String?, + destination: TxInfo.DestinationType, + ): TxInfo.TransactionType { return when (methodName) { "transfer" -> TxInfo.TransactionType.Transfer "approve" -> TxInfo.TransactionType.Approve @@ -59,11 +70,33 @@ internal class SdkTransactionTypeConverter( "withdrawRewardsPOL", -> TxInfo.TransactionType.Staking.ClaimRewards "redelegate" -> TxInfo.TransactionType.Staking.Restake - "supplyEnter" -> TxInfo.TransactionType.YieldSupply.Enter - "supplyExit" -> TxInfo.TransactionType.YieldSupply.Exit + "yieldSend" -> TxInfo.TransactionType.YieldSupply.Send + "enterProtocolByOwner" -> callData?.let { data -> + TxInfo.TransactionType.YieldSupply.Enter( + EthereumYieldSupplyEnterCallData.decode(data)?.tokenContractAddress.orEmpty(), + ) + } + "withdrawAndDeactivate" -> callData?.let { data -> + TxInfo.TransactionType.YieldSupply.Exit( + EthereumYieldSupplyExitCallData.decode(data)?.tokenContractAddress.orEmpty(), + ) + } + "deployYieldModule" -> TxInfo.TransactionType.YieldSupply.DeployContract( + (destination as? TxInfo.DestinationType.Single)?.addressType?.address.orEmpty(), + ) + "initYieldToken" -> callData?.let { data -> + TxInfo.TransactionType.YieldSupply.InitializeToken( + EthereumYieldSupplyInitTokenCallData.decode(data)?.tokenContractAddress.orEmpty(), + ) + } + "reactivateToken" -> callData?.let { data -> + TxInfo.TransactionType.YieldSupply.ReactivateToken( + EthereumYieldSupplyReactivateTokenCallData.decode(data)?.tokenContractAddress.orEmpty(), + ) + } "supplyTopUp" -> TxInfo.TransactionType.YieldSupply.Topup null -> TxInfo.TransactionType.UnknownOperation else -> TxInfo.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() }) - } + } ?: TxInfo.TransactionType.Operation(name = methodName?.replaceFirstChar { it.titlecase() }.orEmpty()) } } \ No newline at end of file diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt index dae65d3ec7..36016b121b 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt @@ -45,7 +45,7 @@ internal class TransactionDataToTxHistoryItemConverter( TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed }, - type = getTransactionType(value.extras), + type = getTransactionType(value), amount = amount, ) } @@ -99,16 +99,25 @@ internal class TransactionDataToTxHistoryItemConverter( ) } - private fun getTransactionType(extras: TransactionExtras?): TxInfo.TransactionType { - return when (extras) { + private fun getTransactionType(transactionData: TransactionData.Uncompiled?): TxInfo.TransactionType { + return when (val extras = transactionData?.extras) { is EthereumTransactionExtras -> { - when (extras.callData) { - is EthereumYieldSupplyDeployCallData, - is EthereumYieldSupplyReactivateTokenCallData, - is EthereumYieldSupplyInitTokenCallData, - is EthereumYieldSupplyEnterCallData, - -> TxInfo.TransactionType.YieldSupply.Enter - is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit + when (val callData = extras.callData) { + is EthereumYieldSupplyDeployCallData -> TxInfo.TransactionType.YieldSupply.DeployContract( + transactionData.destinationAddress, + ) + is EthereumYieldSupplyReactivateTokenCallData -> TxInfo.TransactionType.YieldSupply.ReactivateToken( + callData.tokenContractAddress, + ) + is EthereumYieldSupplyInitTokenCallData -> TxInfo.TransactionType.YieldSupply.InitializeToken( + callData.tokenContractAddress, + ) + is EthereumYieldSupplyEnterCallData -> TxInfo.TransactionType.YieldSupply.Enter( + callData.tokenContractAddress, + ) + is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit( + callData.tokenContractAddress, + ) is ApprovalERC20TokenCallData -> TxInfo.TransactionType.Approve else -> TxInfo.TransactionType.Transfer } diff --git a/data/yield-supply/build.gradle.kts b/data/yield-supply/build.gradle.kts index 4fbb4f314a..cc7313cd67 100644 --- a/data/yield-supply/build.gradle.kts +++ b/data/yield-supply/build.gradle.kts @@ -19,6 +19,10 @@ dependencies { /** Tangem SDKs */ implementation(tangemDeps.blockchain) + // region AndroidX libraries + implementation(deps.androidx.datastore) + // endregion + /** Core */ implementation(projects.core.datasource) implementation(projects.core.utils) diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt index e652292644..a40499ba87 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt @@ -12,6 +12,10 @@ import com.tangem.data.yield.supply.converters.YieldTokenChartConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.YieldSupplyApi import com.tangem.datasource.api.tangemTech.models.YieldSupplyChangeTokenStatusBody +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.get +import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.yieldsupply.YieldMarketsStore import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -35,6 +39,7 @@ internal class DefaultYieldSupplyRepository( private val walletManagersFacade: WalletManagersFacade, private val dispatchers: CoroutineDispatcherProvider, private val analyticsExceptionHandler: AnalyticsExceptionHandler, + private val appPreferencesStore: AppPreferencesStore, ) : YieldSupplyRepository { private val statusMap: MutableMap = ConcurrentHashMap() @@ -174,6 +179,14 @@ internal class DefaultYieldSupplyRepository( null } + override fun getShouldShowYieldPromoBanner(): Flow { + return appPreferencesStore.get(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, true) + } + + override suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean) { + appPreferencesStore.store(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, shouldShow) + } + private fun Set.hasYieldEnterTransactions(yieldAddress: String) = any { it.type == TxInfo.TransactionType.YieldSupply.Enter || it.type == TxInfo.TransactionType.Approve && diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/di/YieldSupplyDataModule.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/di/YieldSupplyDataModule.kt index 409d1da57f..ee040a543a 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/di/YieldSupplyDataModule.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/di/YieldSupplyDataModule.kt @@ -5,6 +5,7 @@ import com.tangem.data.yield.supply.DefaultYieldSupplyRepository import com.tangem.data.yield.supply.DefaultYieldSupplyErrorResolver import com.tangem.data.yield.supply.DefaultYieldSupplyTransactionRepository import com.tangem.datasource.api.tangemTech.YieldSupplyApi +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.yieldsupply.YieldMarketsStore import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.yield.supply.YieldSupplyRepository @@ -41,6 +42,7 @@ internal object YieldSupplyDataModule { walletManagersFacade: WalletManagersFacade, dispatchers: CoroutineDispatcherProvider, analyticsExceptionHandler: AnalyticsExceptionHandler, + appPreferencesStore: AppPreferencesStore, ): YieldSupplyRepository { return DefaultYieldSupplyRepository( yieldSupplyApi = yieldSupplyApi, @@ -48,6 +50,7 @@ internal object YieldSupplyDataModule { dispatchers = dispatchers, walletManagersFacade = walletManagersFacade, analyticsExceptionHandler = analyticsExceptionHandler, + appPreferencesStore = appPreferencesStore, ) } diff --git a/domain/feedback/models/src/main/kotlin/com/tangem/domain/feedback/models/FeedbackEmailType.kt b/domain/feedback/models/src/main/kotlin/com/tangem/domain/feedback/models/FeedbackEmailType.kt index 78848710c5..520f49e395 100644 --- a/domain/feedback/models/src/main/kotlin/com/tangem/domain/feedback/models/FeedbackEmailType.kt +++ b/domain/feedback/models/src/main/kotlin/com/tangem/domain/feedback/models/FeedbackEmailType.kt @@ -57,7 +57,6 @@ sealed interface FeedbackEmailType { } sealed class Visa : FeedbackEmailType { - data class FailedIssueCard(override val walletMetaInfo: WalletMetaInfo) : Visa() data class DirectUserRequest(override val walletMetaInfo: WalletMetaInfo) : Visa() @@ -68,6 +67,8 @@ sealed interface FeedbackEmailType { override val walletMetaInfo: WalletMetaInfo, ) : Visa() + data class FailedIssueCard(override val walletMetaInfo: WalletMetaInfo) : Visa() + data class DisputeV2( val item: TangemPayTxHistoryItem, override val walletMetaInfo: WalletMetaInfo, diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt index 9a3a7de9b6..00815bea99 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt @@ -12,6 +12,7 @@ internal class FeedbackDataBuilder { fun addTangemPayTxInfo(item: TangemPayTxHistoryItem) { builder.append(item.jsonRepresentation) + builder.breakLine() } fun addVisaTxInfo(txDetails: VisaTxDetails) { @@ -108,6 +109,26 @@ internal class FeedbackDataBuilder { builder.appendKeyValue("App version", phoneInfo.appVersion) } + fun addTangemPayIssueType(type: FeedbackEmailType.Visa) { + val issueType = when (type) { + is FeedbackEmailType.Visa.Activation, + is FeedbackEmailType.Visa.DirectUserRequest, + is FeedbackEmailType.Visa.Dispute, + is FeedbackEmailType.Visa.FeatureIsBeta, + is FeedbackEmailType.Visa.Withdrawal, + -> return + is FeedbackEmailType.Visa.DisputeV2 -> when (type.item) { + is TangemPayTxHistoryItem.Collateral -> "Receive/Withdraw" + is TangemPayTxHistoryItem.Fee, + is TangemPayTxHistoryItem.Spend, + is TangemPayTxHistoryItem.Payment, + -> "Transaction" + } + is FeedbackEmailType.Visa.FailedIssueCard -> "Card issuing" + } + builder.appendKeyValue("Issue type", issueType) + } + fun addBlockchainError(info: BlockchainInfo, error: BlockchainErrorInfo) { builder.appendKeyValue("Blockchain", info.blockchain) builder.appendAddresses( diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt index adc8353252..07e0eb137e 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt @@ -6,6 +6,9 @@ import com.tangem.domain.feedback.models.FeedbackEmail import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.feedback.repository.FeedbackRepository import com.tangem.domain.feedback.utils.* +import java.io.File + +private const val BINDER_MAX_SIZE_BYTES = 500_000 // Safe limit under Android's 1MB Binder limit /** * Get email with feedback for support @@ -29,22 +32,30 @@ class SendFeedbackEmailUseCase( address = getAddress(type), subject = emailSubjectResolver.resolve(type), message = createMessage(type), - file = feedbackRepository.getZipLogFile(), + file = getFile(type), ) feedbackRepository.sendEmail(email) } + private suspend fun getFile(type: FeedbackEmailType): File? { + return if (type.isVisaEmail()) { + null + } else { + feedbackRepository.getZipLogFile() + } + } + private fun getAddress(type: FeedbackEmailType): String { return when { - type is FeedbackEmailType.Visa || type.walletMetaInfo?.isVisa == true -> TANGEM_VISA_SUPPORT_EMAIL + type.isVisaEmail() -> TANGEM_VISA_SUPPORT_EMAIL type.walletMetaInfo?.isStart2Coin == true -> START2COIN_SUPPORT_EMAIL else -> TANGEM_SUPPORT_EMAIL } } private suspend fun createMessage(type: FeedbackEmailType): String { - return StringBuilder().apply { + val fullMessage = buildString { val title = emailMessageTitleResolver.resolve(type) append(title) @@ -54,7 +65,25 @@ class SendFeedbackEmailUseCase( val body = emailMessageBodyResolver.resolve(type) append(body) - }.toString() + } + + return truncateMessageIfNeeded(fullMessage) + } + + private fun truncateMessageIfNeeded(message: String): String { + val messageBytes = message.toByteArray(Charsets.UTF_8) + + return if (messageBytes.size > BINDER_MAX_SIZE_BYTES) { + // Find a safe truncation point (avoid cutting in middle of UTF-8 chars) + val truncatedBytes = messageBytes.sliceArray(0 until BINDER_MAX_SIZE_BYTES) + String(truncatedBytes, Charsets.UTF_8) + } else { + message + } + } + + private fun FeedbackEmailType.isVisaEmail(): Boolean { + return this is FeedbackEmailType.Visa || this.walletMetaInfo?.isVisa == true } private fun StringBuilder.appendDisclaimerIfNeeded(type: FeedbackEmailType): StringBuilder { diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt index c8a13a4ef7..1e4ca44bd2 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt @@ -4,7 +4,6 @@ import com.tangem.domain.feedback.FeedbackDataBuilder import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.feedback.models.WalletMetaInfo import com.tangem.domain.feedback.repository.FeedbackRepository -import com.tangem.domain.visa.model.TangemPayTxHistoryItem import com.tangem.domain.visa.model.VisaTxDetails /** @@ -34,31 +33,39 @@ internal class EmailMessageBodyResolver( -> addPhoneInfoBody() is FeedbackEmailType.Visa.Activation -> addUserRequestBody(type.walletMetaInfo) is FeedbackEmailType.Visa.DirectUserRequest -> addUserRequestBody(type.walletMetaInfo) - is FeedbackEmailType.Visa.FailedIssueCard -> addUserRequestBody(type.walletMetaInfo) is FeedbackEmailType.Visa.Dispute -> addVisaRequestBody(type.walletMetaInfo, type.visaTxDetails) - is FeedbackEmailType.Visa.DisputeV2 -> addTangemPayRequestBody(type.walletMetaInfo, type.item) + is FeedbackEmailType.Visa.FailedIssueCard -> addTangemPayFailedIssuingCardBody(type) + is FeedbackEmailType.Visa.DisputeV2 -> addTangemPayDisputeRequestBody(type) is FeedbackEmailType.Visa.Withdrawal -> addTangemPayWithdrawalRequestBody(type) - is FeedbackEmailType.Visa.FeatureIsBeta -> addTangemPayBetaRequestBody(type.walletMetaInfo) + is FeedbackEmailType.Visa.FeatureIsBeta -> addTangemPayBetaRequestBody(type) } return build() } - private suspend fun FeedbackDataBuilder.addTangemPayRequestBody( - walletMetaInfo: WalletMetaInfo, - item: TangemPayTxHistoryItem, - ) { - addUserRequestBody(walletMetaInfo) + private fun FeedbackDataBuilder.addTangemPayFailedIssuingCardBody(type: FeedbackEmailType.Visa.FailedIssueCard) { + addTangemPayPhoneInfoBody(type) addDelimiter() - addTangemPayTxInfo(item) + type.walletMetaInfo.userWalletId?.let { userWalletId -> + addUserWalletId(userWalletId = userWalletId.stringValue) + } } - private fun FeedbackDataBuilder.addTangemPayBetaRequestBody(walletMetaInfo: WalletMetaInfo) { - addPhoneInfoBody() + private fun FeedbackDataBuilder.addTangemPayDisputeRequestBody(type: FeedbackEmailType.Visa.DisputeV2) { + addTangemPayPhoneInfoBody(type = type) addDelimiter() - walletMetaInfo.userWalletId?.let { userWalletId -> + addTangemPayTxInfo(type.item) + addDelimiter() + type.walletMetaInfo.userWalletId?.let { userWalletId -> + addUserWalletId(userWalletId = userWalletId.stringValue) + } + } + + private fun FeedbackDataBuilder.addTangemPayBetaRequestBody(type: FeedbackEmailType.Visa) { + addTangemPayPhoneInfoBody(type) + addDelimiter() + type.walletMetaInfo?.userWalletId?.let { userWalletId -> addUserWalletId(userWalletId = userWalletId.stringValue) - addDelimiter() } } @@ -122,6 +129,11 @@ internal class EmailMessageBodyResolver( addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo()) } + private fun FeedbackDataBuilder.addTangemPayPhoneInfoBody(type: FeedbackEmailType.Visa) { + addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo()) + addTangemPayIssueType(type) + } + private suspend fun FeedbackDataBuilder.addTransactionSendingProblemBody(walletMetaInfo: WalletMetaInfo) { addUserWalletMetaInfo(walletMetaInfo) addDelimiter() diff --git a/domain/legacy/src/main/assets/contract_methods.json b/domain/legacy/src/main/assets/contract_methods.json index 5ca94cd491..fe538d3526 100644 --- a/domain/legacy/src/main/assets/contract_methods.json +++ b/domain/legacy/src/main/assets/contract_methods.json @@ -205,32 +205,32 @@ "0xcbeda14c": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supply" + "name": "deployYieldModule" }, "0x79be55f7": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supplyEnter" + "name": "enterProtocolByOwner" }, "0xc65e6dcf": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supplyExit" + "name": "withdrawAndDeactivate" }, "0xebd4b81c": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supply" + "name": "initYieldToken" }, "0xc478e956": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "supply" + "name": "reactivateToken" }, "0x0779afe6": { "info": "yieldModule", "source": "https://github.com/tangem/tangem-yield-module-contracts/", - "name": "transfer" + "name": "yieldSend" }, "0xb9de6a93": { "info": "yieldModule", diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt index 4937c3c0af..09c43362de 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt @@ -11,9 +11,9 @@ fun CryptoCurrencyStatus.hasNotSuppliedAmount(): Boolean { return notSupplied > BigDecimal.ZERO } -fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount: BigDecimal): Boolean { +fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount: BigDecimal): Boolean { val notSupplied = notSuppliedAmountOrNull() ?: return false - return notSupplied >= minAmount + return notSupplied >= dustAmount } fun CryptoCurrencyStatus.notSuppliedAmountOrNull(): BigDecimal? { diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/network/TxInfo.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/network/TxInfo.kt index 65467f7460..d8fd074625 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/network/TxInfo.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/network/TxInfo.kt @@ -110,14 +110,32 @@ data class TxInfo( @Serializable sealed interface YieldSupply : TransactionType { - @Serializable - data object Enter : YieldSupply + val address: String? @Serializable - data object Exit : YieldSupply + data class Enter(override val address: String) : YieldSupply @Serializable - data object Topup : YieldSupply + data class Exit(override val address: String) : YieldSupply + + @Serializable + data object Topup : YieldSupply { + override val address: String? = null + } + + @Serializable + data object Send : YieldSupply { + override val address: String? = null + } + + @Serializable + data class DeployContract(override val address: String) : YieldSupply + + @Serializable + data class ReactivateToken(override val address: String) : YieldSupply + + @Serializable + data class InitializeToken(override val address: String) : YieldSupply } @Serializable diff --git a/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt b/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt index 764871ed95..9f8055dc60 100644 --- a/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt +++ b/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt @@ -30,4 +30,5 @@ enum class PromoId { Sepa, VisaPresale, BlackFriday, + OnePlusOne, } \ No newline at end of file diff --git a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt index d5b48a464e..5cab5b1cde 100644 --- a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt +++ b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt @@ -32,6 +32,7 @@ class ShouldShowPromoWalletUseCase( PromoId.Referral, PromoId.VisaPresale, PromoId.BlackFriday, + PromoId.OnePlusOne, -> true PromoId.Sepa -> { val walletFirstUsageDate = settingsRepository.getWalletFirstUsageDate() diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/PromoAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/PromoAnalyticsEvent.kt index dcacf45e9e..3d3f0fbdb1 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/PromoAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/PromoAnalyticsEvent.kt @@ -59,5 +59,6 @@ sealed class PromoAnalyticsEvent( Empty("Empty"), Sepa("Sepa"), BlackFriday("Black Friday"), + OnePlusOne("One-Plus-One"), } } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt index 50240853c7..2f944349f5 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt @@ -10,7 +10,7 @@ sealed class TangemPayAnalyticsEvents( class ActivationScreenOpened : TangemPayAnalyticsEvents( categoryName = "Visa Onboarding", - event = "Activation Screen Opened", + event = "Visa Activation Screen Opened", ) class ViewTermsClicked : TangemPayAnalyticsEvents( @@ -43,6 +43,11 @@ sealed class TangemPayAnalyticsEvents( event = "Button - Visa Receive", ) + class AddFundsClicked : TangemPayAnalyticsEvents( + categoryName = "Visa Screen", + event = "Button - Visa Add Funds", + ) + class SwapClicked : TangemPayAnalyticsEvents( categoryName = "Visa Screen", event = "Button - Visa Swap", diff --git a/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt b/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt new file mode 100644 index 0000000000..d3086742cb --- /dev/null +++ b/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.yield.supply.models + +data class YieldSupplyRewardBalance( + val fiatBalance: String?, + val cryptoBalance: String?, +) { + companion object { + fun empty() = YieldSupplyRewardBalance(null, null) + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt index 8ab13bbac1..9a73322ca8 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt @@ -108,4 +108,8 @@ interface YieldSupplyRepository { userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, ): YieldSupplyEnterStatus? + + fun getShouldShowYieldPromoBanner(): Flow + + suspend fun setShouldShowYieldPromoBanner(shouldShow: Boolean) } \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyApyFlowUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyApyFlowUseCase.kt index d93fcca1a1..3dda098013 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyApyFlowUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyApyFlowUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.yield.supply.usecase import com.tangem.domain.yield.supply.YieldSupplyRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import java.math.BigDecimal /** * Emits a map of APY values per token. @@ -15,11 +16,11 @@ class YieldSupplyApyFlowUseCase( private val yieldSupplyRepository: YieldSupplyRepository, ) { - operator fun invoke(): Flow> { + operator fun invoke(): Flow> { return yieldSupplyRepository.getMarketsFlow() .map { yieldMarketTokenList -> yieldMarketTokenList.filter { it.isActive }.associate { token -> - token.yieldSupplyKey to token.apy.toString() + token.yieldSupplyKey to token.apy } } } diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt new file mode 100644 index 0000000000..7814d60add --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.yield.supply.usecase + +import com.tangem.domain.appcurrency.model.AppCurrency +import java.math.BigDecimal + +class YieldSupplyGetDustMinAmountUseCase { + + operator fun invoke(minAmount: BigDecimal, appCurrency: AppCurrency): BigDecimal { + return if (appCurrency.code in SUPPORTED_DUST_CURRENCIES) { + DUST_MIN_AMOUNT + } else { + minAmount.stripTrailingZeros() + } + } + + companion object { + private val DUST_MIN_AMOUNT = BigDecimal("0.1") + private val SUPPORTED_DUST_CURRENCIES = setOf("EUR", "USD", "AUD", "CAD", "GBP") + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt index 5e1b8676a9..93e18928bf 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt @@ -1,11 +1,13 @@ package com.tangem.domain.yield.supply.usecase import com.tangem.core.ui.format.bigdecimal.anyDecimals +import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.delay @@ -22,7 +24,7 @@ class YieldSupplyGetRewardsBalanceUseCase( private val dispatcherProvider: CoroutineDispatcherProvider, ) { - operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow = flow { + operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow = flow { val cryptoAmount = status.value.amount val fiatRate = status.value.fiatRate @@ -30,11 +32,7 @@ class YieldSupplyGetRewardsBalanceUseCase( return@flow } - val amount = if (cryptoAmount != null && fiatRate != null) { - cryptoAmount.multiply(fiatRate) - } else { - return@flow - } + if (cryptoAmount == null) return@flow val tokenAddress = (status.currency as? CryptoCurrency.Token)?.contractAddress ?: return@flow val apy = try { @@ -50,37 +48,57 @@ class YieldSupplyGetRewardsBalanceUseCase( return@flow } - val initialPerTickDelta = amount - .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) - .abs() + val initialPerTickDeltaCrypto = perTickDelta(cryptoAmount, apyFraction).abs() - val minVisibleDecimals = calculateMinVisibleDecimals(initialPerTickDelta) + val minVisibleDecimalsCrypto = calculateMinVisibleDecimals( + perTickDeltaAbs = initialPerTickDeltaCrypto, + maxDecimals = status.currency.decimals, + ) - var currentBalance: BigDecimal = amount + val fiatAmountStart = fiatRate?.let { cryptoAmount.multiply(it) } + val minVisibleDecimalsFiat = fiatAmountStart?.let { amount -> + val initialPerTickDeltaFiat = perTickDelta(amount, apyFraction).abs() + calculateMinVisibleDecimals( + perTickDeltaAbs = initialPerTickDeltaFiat, + maxDecimals = FIAT_MAX_DECIMALS, + ) + } + + var currentCryptoBalance: BigDecimal = cryptoAmount + var currentFiatBalance: BigDecimal? = fiatAmountStart while (true) { + val fiatBalanceFormatted: String? = currentFiatBalance?.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ).anyDecimals(decimals = minVisibleDecimalsFiat ?: FIAT_MIN_DECIMALS) + } + + val cryptoBalanceFormatted: String = currentCryptoBalance.format { + crypto(status.currency).anyDecimals( + maxDecimals = minVisibleDecimalsCrypto, + minDecimals = minVisibleDecimalsCrypto, + ) + } + emit( - currentBalance.format { - fiat( - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ).anyDecimals(decimals = minVisibleDecimals) - }, + YieldSupplyRewardBalance(fiatBalance = fiatBalanceFormatted, cryptoBalance = cryptoBalanceFormatted), ) - val perTickDelta = currentBalance - .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + val perTickDeltaCrypto = perTickDelta(currentCryptoBalance, apyFraction) - currentBalance = currentBalance.add(perTickDelta) + currentCryptoBalance = currentCryptoBalance.add(perTickDeltaCrypto) + + currentFiatBalance = currentFiatBalance?.let { current -> + val perTickDeltaFiat = perTickDelta(current, apyFraction) + current.add(perTickDeltaFiat) + } delay(TICK_MILLIS) } }.flowOn(dispatcherProvider.default) - private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal): Int { + private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal, maxDecimals: Int): Int { if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS val perTickAsDouble = perTickDeltaAbs.toDouble() @@ -88,20 +106,28 @@ class YieldSupplyGetRewardsBalanceUseCase( val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble val raw = ceil(-ln(safe) / LN_10) - return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS) + return raw.toInt().coerceIn(MIN_DECIMALS, maxDecimals) } - private companion object { - const val TICK_MILLIS: Long = 300 - private val TICK_SECONDS_BD = BigDecimal("0.3") - private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") // 365 * 24 * 60 * 60 - private val HUNDRED_BD = BigDecimal("100") - private const val SCALE = 18 + private fun perTickDelta(amount: BigDecimal, apyFraction: BigDecimal): BigDecimal { + return amount + .multiply(apyFraction) + .multiply(TICK_SECONDS_BD) + .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + } - private const val MIN_DECIMALS = 3 - private const val MAX_DECIMALS = 12 + companion object { + internal const val TICK_MILLIS: Long = 800 + internal val TICK_SECONDS_BD: BigDecimal = BigDecimal("0.8") + internal val SECONDS_PER_YEAR_BD: BigDecimal = BigDecimal("31536000") // 365 * 24 * 60 * 60 + internal val HUNDRED_BD: BigDecimal = BigDecimal("100") + internal const val SCALE: Int = 18 - private val LN_10 = ln(10.0) - private const val EPSILON = 1e-18 + internal const val MIN_DECIMALS: Int = 3 + internal const val FIAT_MIN_DECIMALS: Int = 2 + internal const val FIAT_MAX_DECIMALS: Int = 12 + + internal val LN_10: Double = ln(10.0) + internal const val EPSILON: Double = 1e-18 } } \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetShouldShowMainPromoUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetShouldShowMainPromoUseCase.kt new file mode 100644 index 0000000000..42a1493d5b --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetShouldShowMainPromoUseCase.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.yield.supply.usecase + +import com.tangem.domain.yield.supply.YieldSupplyRepository +import kotlinx.coroutines.flow.Flow + +class YieldSupplyGetShouldShowMainPromoUseCase( + private val yieldSupplyRepository: YieldSupplyRepository, +) { + + operator fun invoke(): Flow { + return yieldSupplyRepository.getShouldShowYieldPromoBanner() + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplySetShouldShowMainPromoUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplySetShouldShowMainPromoUseCase.kt new file mode 100644 index 0000000000..dcc0a985e6 --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplySetShouldShowMainPromoUseCase.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.yield.supply.usecase + +import com.tangem.domain.yield.supply.YieldSupplyRepository + +class YieldSupplySetShouldShowMainPromoUseCase( + private val yieldSupplyRepository: YieldSupplyRepository, +) { + + suspend operator fun invoke(shouldShow: Boolean) { + yieldSupplyRepository.setShouldShowYieldPromoBanner(shouldShow) + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt new file mode 100644 index 0000000000..50a00ac69c --- /dev/null +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCaseTest.kt @@ -0,0 +1,31 @@ +package com.tangem.domain.yield.supply.usecase + +import com.google.common.truth.Truth.assertThat +import com.tangem.domain.appcurrency.model.AppCurrency +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +class YieldSupplyGetDustMinAmountUseCaseTest { + + private val useCase = YieldSupplyGetDustMinAmountUseCase() + + @Test + fun `GIVEN supported currency WHEN invoke THEN return dust min amount`() { + val minAmount = BigDecimal("123.456") + val appCurrency = AppCurrency(code = "EUR", name = "Euro", symbol = "€") + + val result = useCase(minAmount, appCurrency) + + assertThat(result).isEqualTo(BigDecimal("0.1")) + } + + @Test + fun `GIVEN unsupported currency WHEN invoke THEN return min amount stripped`() { + val minAmount = BigDecimal("1.2300") + val appCurrency = AppCurrency(code = "JPY", name = "Japanese Yen", symbol = "¥") + + val result = useCase(minAmount, appCurrency) + + assertThat(result).isEqualTo(BigDecimal("1.23")) + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt index 3636c6f6ec..5521464a9c 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt @@ -4,6 +4,7 @@ import com.google.common.truth.Truth.assertThat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.anyDecimals +import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -11,6 +12,7 @@ import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.domain.yield.supply.models.YieldMarketToken +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase.Companion.TICK_MILLIS import com.tangem.utils.coroutines.CoroutineDispatcherProvider import io.mockk.coEvery import io.mockk.mockk @@ -26,7 +28,6 @@ import org.junit.jupiter.api.Test import java.math.BigDecimal import java.math.RoundingMode import kotlin.math.ceil -import kotlin.math.ln class YieldSupplyGetRewardsBalanceUseCaseTest { @@ -165,9 +166,9 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { val deferred = async { useCase(status, appCurrency).take(3).toList() } testScheduler.advanceUntilIdle() - advanceTimeBy(300) + advanceTimeBy(TICK_MILLIS) testScheduler.advanceUntilIdle() - advanceTimeBy(300) + advanceTimeBy(TICK_MILLIS) testScheduler.advanceUntilIdle() val collected = deferred.await() @@ -175,25 +176,147 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { val expectedDecimals = calculateMinVisibleDecimalsForTest(initialPerTickDelta(amount, apy)) - val firstExpected = amount.format { fiat( - appCurrency.code, - appCurrency.symbol, - ).anyDecimals(decimals = expectedDecimals) } - assertThat(collected[0]).isEqualTo(firstExpected) + val firstExpected = amount.format { + fiat( + appCurrency.code, + appCurrency.symbol, + ).anyDecimals(decimals = expectedDecimals) + } + assertThat(collected[0].fiatBalance).isEqualTo(firstExpected) val firstNext = nextBalance(amount, apy) - val secondExpected = firstNext.format { fiat( - appCurrency.code, - appCurrency.symbol, - ).anyDecimals(decimals = expectedDecimals) } - assertThat(collected[1]).isEqualTo(secondExpected) + val secondExpected = firstNext.format { + fiat( + appCurrency.code, + appCurrency.symbol, + ).anyDecimals(decimals = expectedDecimals) + } + assertThat(collected[1].fiatBalance).isEqualTo(secondExpected) val secondNext = nextBalance(firstNext, apy) - val thirdExpected = secondNext.format { fiat( - appCurrency.code, - appCurrency.symbol, - ).anyDecimals(decimals = expectedDecimals) } - assertThat(collected[2]).isEqualTo(thirdExpected) + val thirdExpected = secondNext.format { + fiat( + appCurrency.code, + appCurrency.symbol, + ).anyDecimals(decimals = expectedDecimals) + } + assertThat(collected[2].fiatBalance).isEqualTo(thirdExpected) + } + + @Test + fun `GIVEN polygon USDT0 Loaded status WHEN invoke THEN emit formatted balances`() = runTest { + val network = Network( + id = Network.ID(Network.RawID("POLYGON"), Network.DerivationPath.Card("m/44'/60'/0'/0/0")), + backendId = "polygon-pos", + name = "Polygon", + currencySymbol = "POL", + derivationPath = Network.DerivationPath.Card("m/44'/60'/0'/0/0"), + isTestnet = false, + standardType = Network.StandardType.Unspecified("Polygon"), + hasFiatFeeRate = true, + canHandleTokens = true, + transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, + ) + val tokenId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId(network.rawId), + suffix = CryptoCurrency.ID.Suffix.RawID("usdt0", "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"), + ) + val currency = CryptoCurrency.Token( + id = tokenId, + network = network, + name = "USDT0", + symbol = "USDT0", + decimals = 6, + iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/usdt0.png", + isCustom = false, + contractAddress = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", + ) + + val amount = BigDecimal("9.241136") + val fiatRate = BigDecimal("0.9999761277273864") + val status = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loaded( + amount = amount, + fiatAmount = amount.multiply(fiatRate), + fiatRate = fiatRate, + priceChange = BigDecimal("-0.000058200000000008245"), + stakingBalance = null, + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = NetworkAddress.Single( + NetworkAddress.Address( + value = "0xb71fa0E20ba8579B3ec51cC79aaa84Bf5982BB49", + type = NetworkAddress.Address.Type.Primary, + ), + ), + sources = CryptoCurrencyStatus.Sources(), + ), + ) + + val apy = BigDecimal("5.0") + coEvery { repository.getCachedMarkets() } returns listOf( + YieldMarketToken( + tokenAddress = currency.contractAddress, + chainId = 137, + apy = apy, + isActive = true, + maxFeeNative = BigDecimal.ZERO, + maxFeeUSD = BigDecimal.ZERO, + backendId = "polygon-pos", + ), + ) + + val dispatcherProvider = testDispatcherProvider(this) + val useCase = YieldSupplyGetRewardsBalanceUseCase(repository, dispatcherProvider) + val appCurrency = AppCurrency.Default + + val deferred = async { useCase(status, appCurrency).take(2).toList() } + + testScheduler.advanceUntilIdle() + advanceTimeBy(TICK_MILLIS) + testScheduler.advanceUntilIdle() + + val emissions = deferred.await() + assertThat(emissions).hasSize(2) + + val apyFraction = apy.divide(BigDecimal("100"), 18, RoundingMode.HALF_UP) + val perTickCrypto = amount.multiply(apyFraction) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) + .abs() + val minCryptoDecimals = calculateMinVisibleDecimalsForTest(perTickCrypto).coerceAtMost(currency.decimals) + + val fiatAmountStart = amount.multiply(fiatRate) + val perTickFiat = fiatAmountStart.multiply(apyFraction) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) + .abs() + val minFiatDecimals = calculateMinVisibleDecimalsForTest(perTickFiat) + + val expectedCrypto0 = amount.format { + crypto(currency).anyDecimals( + maxDecimals = minCryptoDecimals, + minDecimals = minCryptoDecimals, + ) + } + val expectedFiat0 = fiatAmountStart.format { + fiat(appCurrency.code, appCurrency.symbol).anyDecimals(decimals = minFiatDecimals) + } + + assertThat(emissions[0].cryptoBalance).isEqualTo(expectedCrypto0) + assertThat(emissions[0].fiatBalance).isEqualTo(expectedFiat0) } private fun testDispatcherProvider(scope: TestScope): CoroutineDispatcherProvider { @@ -260,42 +383,48 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { } private fun initialPerTickDelta(amount: BigDecimal, apy: BigDecimal): BigDecimal { - val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP) + val apyFraction = apy.divide( + YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) return amount .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) .abs() } private fun nextBalance(current: BigDecimal, apy: BigDecimal): BigDecimal { - val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP) + val apyFraction = apy.divide( + YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) val perTickDelta = current .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) return current.add(perTickDelta) } private fun calculateMinVisibleDecimalsForTest(perTickDeltaAbs: BigDecimal): Int { - if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS + if (perTickDeltaAbs <= BigDecimal.ZERO) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS val perTickAsDouble = perTickDeltaAbs.toDouble() - if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return MIN_DECIMALS - val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble - val raw = ceil(-ln(safe) / LN_10) - return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS) - } - - private companion object { - private const val SCALE = 18 - private val TICK_SECONDS_BD = BigDecimal("0.3") - private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") - private val HUNDRED_BD = BigDecimal("100") - - private const val MIN_DECIMALS = 3 - private const val MAX_DECIMALS = 8 - - private val LN_10 = ln(10.0) - private const val EPSILON = 1e-18 + if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS + val safe = if (perTickAsDouble <= 0.0) YieldSupplyGetRewardsBalanceUseCase.EPSILON else perTickAsDouble + val raw = ceil(-kotlin.math.ln(safe) / YieldSupplyGetRewardsBalanceUseCase.LN_10) + return raw.toInt().coerceIn( + YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS, + YieldSupplyGetRewardsBalanceUseCase.FIAT_MAX_DECIMALS, + ) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampOffersStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampOffersStateFactory.kt index 60b8cc3570..c16f800f59 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampOffersStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampOffersStateFactory.kt @@ -19,7 +19,7 @@ internal class OnrampOffersStateFactory( return when (currentState) { is OnrampV2MainComponentUM.InitialLoading -> currentState is OnrampV2MainComponentUM.Content -> { - if (currentState.offersBlockState is OnrampOffersBlockUM.Loading && offers.isEmpty()) { + if (currentState.offersBlockState is OnrampOffersBlockUM.Loading) { return currentState } currentState.copy( diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2AmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2AmountStateFactory.kt index a1948fd6a5..a91f463964 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2AmountStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/entity/factory/OnrampV2AmountStateFactory.kt @@ -64,7 +64,11 @@ internal class OnrampV2AmountStateFactory( currencySymbol = currency.unit, onAmountValueChanged = onrampIntents::onAmountValueChanged, ), - offersBlockState = OnrampOffersBlockUM.Loading, + offersBlockState = if (amountState.amountFieldModel.fiatValue.isEmpty()) { + OnrampOffersBlockUM.Empty + } else { + OnrampOffersBlockUM.Loading + }, ) } @@ -98,6 +102,7 @@ internal class OnrampV2AmountStateFactory( amountBlockState = amountState.copy(secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty), onrampAmountButtonUMState = OnrampV2AmountButtonUMState.None, errorNotification = null, + offersBlockState = currentState.offersBlockState, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt index 82de8c4f75..517c304fd6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt @@ -236,8 +236,17 @@ internal class OnrampV2MainComponentModel @Inject constructor( maybeOffers.fold( ifLeft = ::handleOnrampError, ifRight = { offers -> - if (offers.isNotEmpty()) { - state.update { onrampOffersStateFactory.getOffersState(offers) } + val currentState = state.value + if (currentState is OnrampV2MainComponentUM.Content) { + if (currentState.amountBlockState.amountFieldModel.fiatValue.isEmpty()) { + state.update { + currentState.copy(offersBlockState = OnrampOffersBlockUM.Empty) + } + return@fold + } + state.update { + onrampOffersStateFactory.getOffersState(offers) + } } }, ) @@ -301,7 +310,17 @@ internal class OnrampV2MainComponentModel @Inject constructor( state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) } } else -> { - state.update { amountStateFactory.getAmountSecondaryFieldResetState() } + state.update { prevState -> + val resetState = amountStateFactory.getAmountSecondaryFieldResetState() + if (prevState is OnrampV2MainComponentUM.Content && + resetState is OnrampV2MainComponentUM.Content && + prevState.offersBlockState is OnrampOffersBlockUM.Loading + ) { + resetState.copy(offersBlockState = OnrampOffersBlockUM.Empty) + } else { + resetState + } + } } } } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 15900bd318..d5884d3b72 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -1651,8 +1651,7 @@ internal class SwapModel @Inject constructor( private fun onTangemPaySupportClick(txId: String?) { modelScope.launch { - val metaInfo = getWalletMetaInfoUseCase(userWallet.walletId) - .getOrElse { error("CardInfo must be not null") } + val metaInfo = getWalletMetaInfoUseCase(userWallet.walletId).getOrNull() ?: return@launch val email = FeedbackEmailType.Visa.Withdrawal( walletMetaInfo = metaInfo, providerName = dataState.selectedProvider?.name.orEmpty(), diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt index 7640e66ffd..df186cb3f8 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt @@ -101,6 +101,7 @@ internal class TangemPayDetailsModel @Inject constructor( val bottomSheetNavigation: SlotNavigation = SlotNavigation() init { + analytics.send(TangemPayAnalyticsEvents.MainScreenOpened()) handleBalanceHiding() fetchAddToWalletBanner() fetchBalance() @@ -212,6 +213,7 @@ internal class TangemPayDetailsModel @Inject constructor( } override fun onClickAddFunds() { + analytics.send(TangemPayAnalyticsEvents.AddFundsClicked()) val currentBalance = balance val depositAddress = currentBalance?.depositAddress if (currentBalance == null || depositAddress == null) { diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryDetailsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryDetailsModel.kt index 02fd1df5b7..8dae2f4f77 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryDetailsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayTxHistoryDetailsModel.kt @@ -9,8 +9,6 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.feedback.GetWalletMetaInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType -import com.tangem.domain.models.wallet.requireColdWallet -import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.tangempay.components.txHistory.TangemPayTxHistoryDetailsComponent import com.tangem.features.tangempay.entity.TangemPayTxHistoryDetailsUM import com.tangem.features.tangempay.model.transformers.TangemPayTxHistoryDetailsConverter @@ -21,10 +19,8 @@ import javax.inject.Inject @Stable @ModelScoped -@Suppress("LongParameterList") internal class TangemPayTxHistoryDetailsModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, - private val getUserWalletsUseCase: GetWalletsUseCase, private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val urlOpener: UrlOpener, @@ -66,12 +62,7 @@ internal class TangemPayTxHistoryDetailsModel @Inject constructor( private fun dispute() { modelScope.launch { - val userWalletId = params.userWalletId - val userWallet = getUserWalletsUseCase.invokeSync() - .firstOrNull { it.walletId == userWalletId } ?: return@launch - val walletMetaInfo = getWalletMetaInfoUseCase.invoke( - userWallet.requireColdWallet().scanResponse, - ).getOrNull() ?: return@launch + val walletMetaInfo = getWalletMetaInfoUseCase.invoke(params.userWalletId).getOrNull() ?: return@launch sendFeedbackEmailUseCase.invoke( FeedbackEmailType.Visa.DisputeV2( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt index 9bfd3722fc..28706d2143 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayTxHistoryDetailsConverter.kt @@ -234,7 +234,7 @@ internal object TangemPayTxHistoryDetailsConverter : ) is TangemPayTxHistoryItem.Spend -> persistentListOf( ButtonState( - text = resourceReference(R.string.tangem_pay_dispute), + text = resourceReference(R.string.tangem_pay_get_help), onClick = this.onDisputeClick, ), ) diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlock.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlock.kt index 14ad97f5fa..403e3fe778 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlock.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlock.kt @@ -182,8 +182,7 @@ private fun TangemPayCardDetailsShownBlock( Row( modifier = Modifier .padding(horizontal = 16.dp) - .fillMaxWidth() - .weight(1f), + .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { CardDetailsTextContainer( @@ -203,6 +202,7 @@ private fun TangemPayCardDetailsShownBlock( onCopy = onCopyCvv, ) } + Spacer(modifier = Modifier.weight(1f)) Row { SpacerWMax() TangemPayCardDetailsCustomButton( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangempayTxDetailsUi.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangempayTxDetailsUi.kt index 4e280d9cb7..c406c83696 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangempayTxDetailsUi.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangempayTxDetailsUi.kt @@ -6,6 +6,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -74,6 +75,7 @@ internal fun TangemPayTxHistoryDetailsContent(state: TangemPayTxHistoryDetailsUM text = state.transactionSubtitle.resolveReference(), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, ) Text( modifier = Modifier.padding(top = 8.dp), @@ -84,7 +86,7 @@ internal fun TangemPayTxHistoryDetailsContent(state: TangemPayTxHistoryDetailsUM state.localTransactionText?.let { localTransaction -> Text( modifier = Modifier.padding(top = 4.dp), - text = localTransaction, + text = localTransaction.orMaskWithStars(state.isBalanceHidden), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) @@ -207,11 +209,11 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr dismiss = {}, ), TangemPayTxHistoryDetailsUM( - isBalanceHidden = false, + isBalanceHidden = true, title = stringReference("12 June • 12:40"), iconState = ImageReference.Res(R.drawable.ic_category_24), transactionTitle = stringReference("Starbucks"), - transactionSubtitle = stringReference("Food and drinks"), + transactionSubtitle = stringReference("Food and drinks Food and drinks Food and drinks Food and drinks"), transactionAmount = "-$5.86", transactionAmountColor = themedColor { TangemTheme.colors.text.primary1 }, localTransactionText = "€ 5.36", diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt index 4e0ac4a548..17820dcc25 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt @@ -340,7 +340,9 @@ private fun AddressItem( onClick = onOpenQrCodeClick, ) { Column( - modifier = Modifier.padding(horizontal = 12.dp, vertical = 32.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 32.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { CurrencyIcon( diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 2d9665f967..05ec40ad40 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -88,6 +88,7 @@ dependencies { implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.yieldSupply) + implementation(projects.domain.yieldSupply.models) /** Temp dependency to swap domain */ implementation(projects.features.swap.domain) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 4e70a2faa4..a221f254ec 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -121,7 +121,7 @@ internal object TokenDetailsPreviewData { selectedBalanceType = BalanceType.ALL, onBalanceSelect = {}, displayCryptoBalance = "966,96 XLM", - displayYeildSupplyCryptoBalance = null, + displayYieldSupplyFiatBalance = null, displayFiatBalance = "91,50$", isBalanceSelectorEnabled = true, isBalanceFlickering = false, 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 2cbe54658a..0b39af391f 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 @@ -75,6 +75,7 @@ import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase +import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase import com.tangem.feature.tokendetails.deeplink.TokenDetailsDeepLinkActionListener import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter @@ -416,7 +417,9 @@ internal class TokenDetailsModel @Inject constructor( .saveIn(yieldSupplyBalanceJobHolder) } else { yieldSupplyBalanceJobHolder.cancel() - internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance(null) + internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance( + YieldSupplyRewardBalance.empty(), + ) } } @@ -1261,14 +1264,8 @@ internal class TokenDetailsModel @Inject constructor( } private fun handleNavigationParam() { - when (val action = params.navigationAction) { - is NavigationAction.Staking -> openStaking() - is NavigationAction.YieldSupply -> if (action.isActive) { - modelScope.launch(dispatchers.default) { - fetchCurrencyStatusUseCase(userWalletId = userWalletId, id = cryptoCurrency.id) - } - } - else -> Unit + if (params.navigationAction is NavigationAction.Staking) { + openStaking() } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt index a74d80f6b8..9e17b95c95 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt @@ -28,7 +28,8 @@ internal sealed class TokenDetailsBalanceBlockState { val isBalanceSelectorEnabled: Boolean, val isBalanceFlickering: Boolean, val yieldSupplyState: TokenDetailsYieldSupplyState = TokenDetailsYieldSupplyState.Empty, - val displayYeildSupplyCryptoBalance: String? = null, + val displayYieldSupplyFiatBalance: String? = null, + val displayYieldSupplyCryptoBalance: String? = null, ) : TokenDetailsBalanceBlockState() data class Error( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 976f4f2e87..ee290cda1f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -98,8 +98,8 @@ internal class TokenDetailsLoadedBalanceConverter( stakingCryptoAmount, currentState.selectedBalanceType, ), - displayYeildSupplyCryptoBalance = (currentState as? TokenDetailsBalanceBlockState.Content) - ?.displayYeildSupplyCryptoBalance, + displayYieldSupplyFiatBalance = (currentState as? TokenDetailsBalanceBlockState.Content) + ?.displayYieldSupplyFiatBalance, balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig, onBalanceSelect = clickIntents::onBalanceSelect, selectedBalanceType = currentState.selectedBalanceType, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index e19dda0395..da13ce39ba 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -26,6 +26,7 @@ import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase +import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsAppBarMenuConfig @@ -315,13 +316,18 @@ internal class TokenDetailsStateFactory( return balanceSelectStateConverter.convert(buttonConfig) } - fun getStateWithUpdatedYieldSupplyDisplayBalance(displayBalance: String?): TokenDetailsState { + fun getStateWithUpdatedYieldSupplyDisplayBalance( + yieldSupplyRewardBalance: YieldSupplyRewardBalance, + ): TokenDetailsState { val state = currentStateProvider() val balanceState = state.tokenBalanceBlockState return state.copy( tokenBalanceBlockState = when (balanceState) { is TokenDetailsBalanceBlockState.Content -> - balanceState.copy(displayYeildSupplyCryptoBalance = displayBalance) + balanceState.copy( + displayYieldSupplyFiatBalance = yieldSupplyRewardBalance.fiatBalance, + displayYieldSupplyCryptoBalance = yieldSupplyRewardBalance.cryptoBalance, + ) is TokenDetailsBalanceBlockState.Error -> balanceState is TokenDetailsBalanceBlockState.Loading -> balanceState }, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt index 9eaa517709..293e54545c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -122,12 +122,12 @@ private fun FiatBalance( height = TangemTheme.dimens.size32, ), ) - is TokenDetailsBalanceBlockState.Content -> if (state.displayYeildSupplyCryptoBalance != null && + is TokenDetailsBalanceBlockState.Content -> if (state.displayYieldSupplyFiatBalance != null && !isBalanceHidden ) { TextAnimatedCounter( modifier = modifier, - text = state.displayYeildSupplyCryptoBalance, + text = state.displayYieldSupplyFiatBalance, style = TangemTheme.typography.h2.applyBladeBrush( isEnabled = state.isBalanceFlickering, textColor = TangemTheme.colors.text.primary1, @@ -136,7 +136,7 @@ private fun FiatBalance( } else { Text( modifier = modifier, - text = (state.displayYeildSupplyCryptoBalance ?: state.displayFiatBalance).orMaskWithStars( + text = (state.displayYieldSupplyFiatBalance ?: state.displayFiatBalance).orMaskWithStars( isBalanceHidden, ), style = TangemTheme.typography.h2.applyBladeBrush( @@ -184,8 +184,9 @@ private fun CryptoBalance( tint = TangemTheme.colors.icon.inactive, contentDescription = null, ) - Text( - text = state.displayCryptoBalance.orMaskWithStars(isBalanceHidden), + TextAnimatedCounter( + text = (state.displayYieldSupplyCryptoBalance ?: state.displayCryptoBalance) + .orMaskWithStars(isBalanceHidden), style = TangemTheme.typography.caption2.applyBladeBrush( isEnabled = state.isBalanceFlickering, textColor = TangemTheme.colors.text.tertiary, diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt index 0fc75e0d67..bc5f160b47 100644 --- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt +++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionStateConverter.kt @@ -41,7 +41,9 @@ internal class TxHistoryItemToTransactionStateConverter( R.drawable.ic_close_24 } else { when (type) { - is TransactionType.Approve -> R.drawable.ic_doc_24 + is TransactionType.YieldSupply.DeployContract, + is TransactionType.Approve, + -> R.drawable.ic_doc_24 is TransactionType.Staking.Stake, is TransactionType.Staking.Vote, is TransactionType.Staking.Restake, @@ -51,11 +53,16 @@ internal class TxHistoryItemToTransactionStateConverter( is TransactionType.Staking.Unstake, is TransactionType.Staking.Withdraw, -> R.drawable.ic_transaction_history_unstaking_24 + is TransactionType.YieldSupply.Enter -> R.drawable.ic_connect_24 + is TransactionType.YieldSupply.InitializeToken -> R.drawable.ic_gear_24 + is TransactionType.YieldSupply.ReactivateToken -> R.drawable.ic_refresh_24 + is TransactionType.YieldSupply.Exit -> R.drawable.ic_disconnect_24 is TransactionType.Operation, is TransactionType.Swap, is TransactionType.Transfer, - is TransactionType.YieldSupply, is TransactionType.UnknownOperation, + TransactionType.YieldSupply.Send, + TransactionType.YieldSupply.Topup, -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 } } @@ -66,32 +73,76 @@ internal class TxHistoryItemToTransactionStateConverter( is TransactionType.Operation -> stringReference(type.name) is TransactionType.Swap -> resourceReference(R.string.common_swap) is TransactionType.Transfer -> resourceReference(R.string.common_transfer) - is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter) - is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit) - is TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup) is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake) is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake) is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote) is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards) is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw) is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake) + is TransactionType.YieldSupply -> when (type) { + is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter) + is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit) + TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup) + TransactionType.YieldSupply.Send -> if (isOutgoing) { + resourceReference(R.string.common_transfer) + } else { + resourceReference(R.string.yield_module_transaction_withdraw) + } + is TransactionType.YieldSupply.DeployContract -> resourceReference( + R.string + .yield_module_transaction_deploy_contract, + ) + is TransactionType.YieldSupply.InitializeToken -> resourceReference( + R.string + .yield_module_transaction_initialize, + ) + is TransactionType.YieldSupply.ReactivateToken -> resourceReference( + R.string + .yield_module_transaction_reactivate, + ) + } is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) } private fun TxInfo.extractSubtitle(): TextReference { - return when (this.type) { - is TransactionType.YieldSupply.Enter -> { - val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } - resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount)) - } - is TransactionType.YieldSupply.Topup, - -> { - val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } - resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount)) - } - is TransactionType.YieldSupply.Exit -> { - val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } - resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount)) + return when (val type = this.type) { + is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Coin) { + if (type == TransactionType.YieldSupply.Send) { + extractSubtitleByAddressType() + } else { + resourceReference( + R.string.transaction_history_transaction_for_address, + wrappedList(type.address?.toBriefAddressFormat().orEmpty()), + ) + } + } else { + when (type) { + is TransactionType.YieldSupply.Enter -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount)) + } + TransactionType.YieldSupply.Topup -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount)) + } + is TransactionType.YieldSupply.Exit -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount)) + } + TransactionType.YieldSupply.Send -> { + if (isOutgoing) { + extractSubtitleByAddressType() + } else { + val amount = + amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_exit_subtitle, + wrappedList(amount), + ) + } + } + else -> extractSubtitleByAddressType() + } } else -> extractSubtitleByAddressType() } @@ -133,14 +184,21 @@ internal class TxHistoryItemToTransactionStateConverter( @Suppress("ComplexCondition") private fun TxInfo.getAmount(): String { - if (type is TransactionType.Staking.Vote || - type == TransactionType.Staking.ClaimRewards || - type == TransactionType.Staking.Withdraw || - type == TransactionType.YieldSupply.Enter || - type == TransactionType.YieldSupply.Exit || - type == TransactionType.YieldSupply.Topup - ) { - return "" + when (type) { + is TransactionType.Staking.Vote, + TransactionType.Staking.ClaimRewards, + TransactionType.Staking.Withdraw, + -> return "" + + is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Token) { + when (type) { + TransactionType.YieldSupply.Send -> if (!isOutgoing) { + return "" + } + else -> return "" + } + } + else -> Unit } val prefix = when { status == TxInfo.TransactionStatus.Failed -> "" diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index bf812afdd1..291743c337 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -537,7 +537,7 @@ internal class WalletModel @Inject constructor( } } - private suspend fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) { + private fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) { walletScreenContentLoader.cancel(action.prevWalletId) tokenListStore.remove(action.prevWalletId) @@ -585,7 +585,7 @@ internal class WalletModel @Inject constructor( } } - private suspend fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) { + private fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) { if (accountsFeatureToggles.isFeatureEnabled) { fetchWalletContent(userWallet = action.selectedWallet) @@ -725,15 +725,15 @@ internal class WalletModel @Inject constructor( } } - private suspend fun fetchWalletContent(userWallet: UserWallet) { + private fun fetchWalletContent(userWallet: UserWallet) { if (userWallet.isLocked) return /* * Updating the balance of the current wallet is an essential part of InitializationWallets, * so the coroutine is launched in the current context */ - supervisorScope { - launch { walletContentFetcher(userWalletId = userWallet.walletId) } + modelScope.launch { + walletContentFetcher(userWalletId = userWallet.walletId) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/TangemPayClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/TangemPayClickIntents.kt index 332b9c24af..ed0005c791 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/TangemPayClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/TangemPayClickIntents.kt @@ -10,11 +10,9 @@ import com.tangem.domain.feedback.GetWalletMetaInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase -import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.features.tangempay.TangemPayFeatureToggles import kotlinx.coroutines.launch @@ -41,7 +39,6 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( private val onboardingRepository: OnboardingRepository, private val produceInitialDataTangemPay: ProduceTangemPayInitialDataUseCase, private val getWalletMetainfoUseCase: GetWalletMetaInfoUseCase, - private val getUserWalletUseCase: GetUserWalletUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, private val uiMessageSender: UiMessageSender, @@ -68,6 +65,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( val issuingBottomSheet = bottomSheetMessage { infoBlock { icon(com.tangem.core.ui.R.drawable.ic_clock_24) { + type = MessageBottomSheetUMV2.Icon.Type.Informative backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Informative } title = resourceReference(R.string.tangempay_issuing_your_card) @@ -93,7 +91,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( body = resourceReference(R.string.tangempay_failed_to_issue_card_support_description) } secondaryButton { - text = resourceReference(R.string.common_contact_support) + text = resourceReference(R.string.tangempay_go_to_support) onClick { onPaySupportClick() closeBs() @@ -106,10 +104,8 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( override fun onPaySupportClick() { modelScope.launch { - val userWalletId = stateHolder.getSelectedWalletId() - val userWallet = getUserWalletUseCase.invoke(userWalletId).getOrNull() ?: return@launch val cardInfo = getWalletMetainfoUseCase.invoke( - userWallet.requireColdWallet().scanResponse, + userWalletId = stateHolder.getSelectedWalletId(), ).getOrNull() ?: return@launch sendFeedbackEmailUseCase( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index 747e1364f5..1802e3e2a7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -22,6 +22,7 @@ import com.tangem.domain.tokens.model.details.NavigationAction import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.unwrap @@ -58,6 +59,8 @@ internal interface WalletContentClickIntents { apy: String, ) + fun onYieldPromoCloseClick() + fun onAccountExpandClick(account: Account) fun onAccountCollapseClick(account: Account) @@ -95,6 +98,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val accountDependencies: AccountDependencies, private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase, + private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase, ) : BaseWalletClickIntents(), WalletContentClickIntents { override fun onDetailsClick() { @@ -194,6 +198,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } } + override fun onYieldPromoCloseClick() { + modelScope.launch { + yieldSupplySetShouldShowMainPromoUseCase(false) + } + } + override fun onAccountExpandClick(account: Account) { val userWalletId = stateHolder.getSelectedWalletId() analyticsEventHandler.send(MainScreenAnalyticsEvent.AccountShowTokens()) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index 05b094761f..aba0e7c275 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -317,6 +317,11 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( program = Program.BlackFriday, action = PromotionBannerClicked.BannerAction.Closed(), ) + PromoId.OnePlusOne -> PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Main, + program = Program.OnePlusOne, + action = PromotionBannerClicked.BannerAction.Closed(), + ) }, ) @@ -364,6 +369,16 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( ) urlOpener.openUrl(BLACK_FRIDAY_PROMO_LINK) } + PromoId.OnePlusOne -> { + analyticsEventHandler.send( + PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Main, + program = Program.OnePlusOne, + action = PromotionBannerClicked.BannerAction.Clicked(), + ), + ) + urlOpener.openUrl(ONE_PLUS_ONE_PROMO_LINK) + } } } @@ -581,5 +596,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( "&utm_source=tangem-app-banner" + "&utm_medium=banner" + "&utm_campaign=BlackFriday2025" + const val ONE_PLUS_ONE_PROMO_LINK = "https://tangem.com/pricing/" + + "?cat=family" + + "&utm_source=tangem-app-banner" + + "&utm_medium=banner" + + "&utm_campaign=BOGO50" } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletTangemPayAnalyticsEventSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletTangemPayAnalyticsEventSender.kt index 6c1b1a5176..e2df7c8cca 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletTangemPayAnalyticsEventSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletTangemPayAnalyticsEventSender.kt @@ -27,7 +27,7 @@ internal class WalletTangemPayAnalyticsEventSender @Inject constructor( val event = when { customerInfo.orderStatus == OrderStatus.CANCELED -> return // ignore cancelled state on analytics !customerInfo.info.isKycApproved -> return // ignore kyc not approved state on analytics - cardInfo != null && productInstance != null -> TangemPayAnalyticsEvents.MainScreenOpened() + cardInfo != null && productInstance != null -> return else -> TangemPayAnalyticsEvents.IssuingBannerDisplayed() } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index 8cfc8b2ea0..94ede58c14 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -62,6 +62,10 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( source = AnalyticsParam.ScreensSources.Main, program = Program.BlackFriday, ) + is WalletNotification.OnePlusOnePromo -> NoticePromotionBanner( + source = AnalyticsParam.ScreensSources.Main, + program = Program.OnePlusOne, + ) is WalletNotification.ReferralPromo -> MainScreen.ReferralPromo() is WalletNotification.VisaPresalePromo -> VisaWaitlistPromo() is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender] diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index f8d2fb389d..b757338349 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -2,7 +2,6 @@ package com.tangem.feature.wallet.presentation.wallet.domain -import arrow.core.getOrElse import com.tangem.common.ui.notifications.NotificationId import com.tangem.common.ui.userwallet.ext.walletInterationIcon import com.tangem.core.decompose.di.ModelScoped @@ -21,12 +20,9 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.notifications.repository.NotificationsRepository -import com.tangem.domain.onramp.GetOnrampCountryUseCase -import com.tangem.domain.onramp.OnrampSepaAvailableUseCase import com.tangem.domain.promo.ShouldShowPromoWalletUseCase import com.tangem.domain.promo.models.PromoId import com.tangem.domain.settings.IsReadyToShowRateAppUseCase -import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase @@ -37,7 +33,6 @@ import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.hot.sdk.model.HotWalletId -import com.tangem.lib.crypto.BlockchainUtils.isBitcoin import com.tangem.utils.extensions.addIf import com.tangem.utils.extensions.isPositive import com.tangem.utils.extensions.orZero @@ -47,7 +42,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map -import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList", "LargeClass") @@ -60,9 +54,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private val backupValidator: BackupValidator, private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase, private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase, - private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase, - private val onrampSepaAvailableUseCase: OnrampSepaAvailableUseCase, - private val getOnrampCountryUseCase: GetOnrampCountryUseCase, private val notificationsRepository: NotificationsRepository, private val accountDependencies: AccountDependencies, private val getAccessCodeSkippedUseCase: GetAccessCodeSkippedUseCase, @@ -99,11 +90,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( isReadyToShowRateAppUseCase().distinctUntilChanged(), isNeedToBackupUseCase(userWallet.walletId).distinctUntilChanged(), seedPhraseNotificationUseCase(userWalletId = userWallet.walletId).distinctUntilChanged(), - shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.VisaPresale) - .distinctUntilChanged(), - shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa) - .distinctUntilChanged(), - shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.BlackFriday) + shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.OnePlusOne) .distinctUntilChanged(), notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key) .distinctUntilChanged(), @@ -117,11 +104,9 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( val isReadyToShowRating = array[1] as Boolean val isNeedToBackup = array[2] as Boolean val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus - val shouldShowVisaPromo = array[4] as Boolean - val shouldShowSepaBanner = array[5] as Boolean - val shouldShowBlackFridayPromo = array[6] as Boolean - val shouldShowEnablePushesReminderNotification = array[7] as Boolean - val shouldAccessCodeSkipped = array[8] as Boolean + val shouldShowOnePlusOnePromo = array[4] as Boolean + val shouldShowEnablePushesReminderNotification = array[5] as Boolean + val shouldAccessCodeSkipped = array[6] as Boolean buildList { addUsedOutdatedDataNotification(totalFiatBalance) @@ -135,16 +120,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( shouldAccessCodeSkipped = shouldAccessCodeSkipped, ) - addBlackFridayPromoNotification(clickIntents, shouldShowBlackFridayPromo) - - addVisaPresalePromoNotification(clickIntents, shouldShowVisaPromo) - - addSepaPromoNotification( - userWallet = userWallet, - flattenCurrencies = flattenCurrencies, - clickIntents = clickIntents, - shouldShowSepaPromo = shouldShowSepaBanner, - ) + addOnePlusOnePromoNotification(clickIntents, shouldShowOnePlusOnePromo) addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents) @@ -297,72 +273,19 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( .map(CryptoCurrencyStatus::currency) } - private fun MutableList.addVisaPresalePromoNotification( + private fun MutableList.addOnePlusOnePromoNotification( clickIntents: WalletClickIntents, shouldShowPromo: Boolean, ) { addIf( - element = WalletNotification.VisaPresalePromo( - onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.VisaPresale) }, - onClick = { clickIntents.onPromoClick(promoId = PromoId.VisaPresale) }, + element = WalletNotification.OnePlusOnePromo( + onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.OnePlusOne) }, + onClick = { clickIntents.onPromoClick(promoId = PromoId.OnePlusOne) }, ), condition = shouldShowPromo, ) } - private fun MutableList.addBlackFridayPromoNotification( - clickIntents: WalletClickIntents, - shouldShowPromo: Boolean, - ) { - addIf( - element = WalletNotification.BlackFridayPromo( - onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.BlackFriday) }, - onClick = { clickIntents.onPromoClick(promoId = PromoId.BlackFriday) }, - ), - condition = shouldShowPromo, - ) - } - - private suspend fun MutableList.addSepaPromoNotification( - userWallet: UserWallet, - flattenCurrencies: Lce>, - clickIntents: WalletClickIntents, - shouldShowSepaPromo: Boolean, - ) { - val currencies = if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) { - flattenCurrencies.map { statuses -> statuses.map { it.currency } }.getOrNull() ?: run { - Timber.e("Error on getting crypto currency list") - return - } - } else { - getCryptoCurrenciesUseCase(userWalletId = userWallet.walletId).getOrElse { - Timber.e("Error on getting crypto currency list") - return - } - } - - val bitcoinCurrency = currencies.find { isBitcoin(it.network.rawId) } ?: return - - val country = getOnrampCountryUseCase.invokeSync(userWallet).getOrElse { - Timber.e("Error on getting onramp country") - return - } - - val isSepaAvailable = onrampSepaAvailableUseCase( - userWallet = userWallet, - country = country, - cryptoCurrency = bitcoinCurrency, - ) - - addIf( - element = WalletNotification.Sepa( - onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.Sepa) }, - onClick = { clickIntents.onPromoClick(promoId = PromoId.Sepa, bitcoinCurrency) }, - ), - condition = shouldShowSepaPromo && isSepaAvailable, - ) - } - // private fun MutableList.addYieldSupplyNotifications( // flattenCurrencies: Lce>, // ) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index 0ce2d80a8d..e08e469afc 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -45,6 +46,7 @@ internal class MultiWalletContentLoader( private val currenciesRepository: CurrenciesRepository, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val tangemPayFeatureToggles: TangemPayFeatureToggles, private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory, @@ -63,6 +65,7 @@ internal class MultiWalletContentLoader( applyTokenListSortingUseCase = applyTokenListSortingUseCase, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ).let(::add) WalletNFTListSubscriber( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index d3fe3d60a4..1f9d0570e8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -44,6 +45,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val currenciesRepository: CurrenciesRepository, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val tangemPayFeatureToggles: TangemPayFeatureToggles, private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory, @@ -72,6 +74,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( hotWalletFeatureToggles = hotWalletFeatureToggles, tangemPayFeatureToggles = tangemPayFeatureToggles, tangemPayMainSubscriberFactory = tangemPayMainSubscriberFactory, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index 7f3e9f91b6..6d895697ed 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -6,6 +6,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -35,6 +36,7 @@ internal class SingleWalletWithTokenContentLoader( private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, private val hotWalletFeatureToggles: HotWalletFeatureToggles, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -49,6 +51,7 @@ internal class SingleWalletWithTokenContentLoader( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ).let(::add) MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index 0f6ac06ef5..4d89203c74 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -7,6 +7,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -36,6 +37,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, private val hotWalletFeatureToggles: HotWalletFeatureToggles, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) { fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader { @@ -55,6 +57,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, hotWalletFeatureToggles = hotWalletFeatureToggles, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index 9647213864..73a2e96b23 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -376,6 +376,23 @@ sealed class WalletNotification(val config: NotificationConfig) { ), ) + data class OnePlusOnePromo( + val onCloseClick: () -> Unit, + val onClick: () -> Unit, + ) : WalletNotification( + config = NotificationConfig( + title = resourceReference(R.string.notification_one_plus_one_title), + subtitle = resourceReference(R.string.notification_one_plus_one_text), + iconResId = R.drawable.img_one_plus_one_promo, + onCloseClick = onCloseClick, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.notification_one_plus_one_button), + onClick = onClick, + ), + iconSize = 54.dp, + ), + ) + data class PushNotifications( val onCloseClick: () -> Unit, val onEnabledClick: () -> Unit, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index 4d102bb5d8..0dec24b4b9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -11,14 +11,16 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.converte import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons import timber.log.Timber +import java.math.BigDecimal internal class SetTokenListTransformer( private val params: TokenConverterParams, private val userWallet: UserWallet, private val appCurrency: AppCurrency, private val clickIntents: WalletClickIntents, - private val yieldSupplyApyMap: Map = emptyMap(), + private val yieldSupplyApyMap: Map = emptyMap(), private val stakingApyMap: Map> = emptyMap(), + private val shouldShowMainPromo: Boolean, ) : WalletStateTransformer(userWallet.walletId) { override fun transform(prevState: WalletState): WalletState { @@ -63,6 +65,7 @@ internal class SetTokenListTransformer( clickIntents = clickIntents, yieldModuleApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ).convert(value = this) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt index 3b5dfff91c..fa92bb7623 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTxHistoryCountErrorTransformer.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers import com.tangem.blockchain.common.Blockchain import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.TxInfo import com.tangem.domain.txhistory.models.TxHistoryStateError import com.tangem.domain.models.wallet.UserWallet @@ -17,6 +18,7 @@ internal class SetTxHistoryCountErrorTransformer( private val error: TxHistoryStateError, private val pendingTransactions: Set, private val clickIntents: WalletClickIntents, + private val currency: CryptoCurrency, ) : WalletStateTransformer(userWallet.walletId) { private val txHistoryItemConverter by lazy { @@ -26,6 +28,7 @@ internal class SetTxHistoryCountErrorTransformer( } TxHistoryItemStateConverter( + currency = currency, symbol = blockchain.currency, decimals = blockchain.decimals(), clickIntents = clickIntents, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index 6dd7d7de6c..257e68fc6b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -28,17 +28,25 @@ import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList +import java.math.BigDecimal import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig +@Suppress("LongParameterList") internal class TokenListStateConverter( private val appCurrency: AppCurrency, private val params: TokenConverterParams, private val selectedWallet: UserWallet, private val clickIntents: WalletClickIntents, - private val yieldModuleApyMap: Map, + private val yieldModuleApyMap: Map, private val stakingApyMap: Map>, + private val shouldShowMainPromo: Boolean, ) : Converter { + private val yieldSupplyPromoBannerKeyConverter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap, + shouldShowMainPromo, + ) + private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit = { accountId, currencyStatus -> clickIntents.onTokenItemClick(selectedWallet.walletId, currencyStatus) @@ -63,10 +71,12 @@ internal class TokenListStateConverter( private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter( appCurrency = appCurrency, yieldModuleApyMap = yieldModuleApyMap, + yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKeyConverter.convert(params), stakingApyMap = stakingApyMap, onItemClick = { _, status -> onTokenClick(accountId, status) }, onItemLongClick = { _, status -> onTokenLongClick(accountId, status) }, onApyLabelClick = { status, apySource, apy -> onApyLabelClick(status, apySource, apy) }, + onYieldPromoCloseClick = clickIntents::onYieldPromoCloseClick, ) override fun convert(value: WalletTokensListState): WalletTokensListState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt index db435626e8..a30612c67e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt @@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.utils.toTimeFormat +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.network.TxInfo.TransactionStatus import com.tangem.domain.models.network.TxInfo.TransactionType @@ -20,6 +21,7 @@ import com.tangem.utils.converter.Converter import com.tangem.utils.toBriefAddressFormat internal class TxHistoryItemStateConverter( + private val currency: CryptoCurrency, private val symbol: String, private val decimals: Int, private val clickIntents: WalletClickIntents, @@ -49,7 +51,9 @@ internal class TxHistoryItemStateConverter( R.drawable.ic_close_24 } else { when (type) { - is TransactionType.Approve -> R.drawable.ic_doc_24 + is TransactionType.YieldSupply.DeployContract, + is TransactionType.Approve, + -> R.drawable.ic_doc_24 is TransactionType.Staking.Stake, is TransactionType.Staking.Vote, is TransactionType.Staking.Restake, @@ -59,11 +63,16 @@ internal class TxHistoryItemStateConverter( is TransactionType.Staking.Unstake, is TransactionType.Staking.Withdraw, -> R.drawable.ic_transaction_history_unstaking_24 + is TransactionType.YieldSupply.Enter -> R.drawable.ic_connect_24 + is TransactionType.YieldSupply.InitializeToken -> R.drawable.ic_gear_24 + is TransactionType.YieldSupply.ReactivateToken -> R.drawable.ic_refresh_24 + is TransactionType.YieldSupply.Exit -> R.drawable.ic_disconnect_24 is TransactionType.Operation, is TransactionType.Swap, is TransactionType.Transfer, - is TransactionType.YieldSupply, is TransactionType.UnknownOperation, + TransactionType.YieldSupply.Send, + TransactionType.YieldSupply.Topup, -> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24 } } @@ -74,32 +83,81 @@ internal class TxHistoryItemStateConverter( is TransactionType.Operation -> stringReference(type.name) is TransactionType.Swap -> resourceReference(R.string.common_swap) is TransactionType.Transfer -> resourceReference(R.string.common_transfer) - is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter) - is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit) - is TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup) is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake) is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake) is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote) is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards) is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw) is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake) + is TransactionType.YieldSupply -> when (type) { + is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter) + is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit) + TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup) + TransactionType.YieldSupply.Send -> if (isOutgoing) { + resourceReference(R.string.common_transfer) + } else { + resourceReference(R.string.yield_module_transaction_withdraw) + } + is TransactionType.YieldSupply.DeployContract -> resourceReference( + R.string + .yield_module_transaction_deploy_contract, + ) + is TransactionType.YieldSupply.InitializeToken -> resourceReference( + R.string + .yield_module_transaction_initialize, + ) + is TransactionType.YieldSupply.ReactivateToken -> resourceReference( + R.string + .yield_module_transaction_reactivate, + ) + } is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation) } private fun TxInfo.extractSubtitle(): TextReference { - return when (this.type) { - is TransactionType.YieldSupply.Enter -> { - val amount = amount.format { crypto(symbol = symbol, decimals = decimals) } - resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount)) - } - is TransactionType.YieldSupply.Topup, - -> { - val amount = amount.format { crypto(symbol = symbol, decimals = decimals) } - resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount)) - } - is TransactionType.YieldSupply.Exit -> { - val amount = amount.format { crypto(symbol = symbol, decimals = decimals) } - resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount)) + return when (val type = this.type) { + is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Coin) { + resourceReference( + R.string.transaction_history_transaction_for_address, + wrappedList(type.address?.toBriefAddressFormat().orEmpty()), + ) + } else { + when (type) { + is TransactionType.YieldSupply.Enter -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_enter_subtitle, + wrappedList(amount), + ) + } + TransactionType.YieldSupply.Topup -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_topup_subtitle, + wrappedList(amount), + ) + } + TransactionType.YieldSupply.Send -> { + if (isOutgoing) { + extractSubtitleByAddressType() + } else { + val amount = + amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_exit_subtitle, + wrappedList(amount), + ) + } + } + is TransactionType.YieldSupply.Exit -> { + val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) } + resourceReference( + R.string.yield_module_transaction_exit_subtitle, + wrappedList(amount), + ) + } + else -> extractSubtitleByAddressType() + } } else -> extractSubtitleByAddressType() } @@ -147,14 +205,18 @@ internal class TxHistoryItemStateConverter( @Suppress("ComplexCondition") private fun TxInfo.getAmount(): String { - if (type is TransactionType.Staking.Vote || - type == TransactionType.Staking.ClaimRewards || - type == TransactionType.Staking.Withdraw || - type == TransactionType.YieldSupply.Enter || - type == TransactionType.YieldSupply.Exit || - type == TransactionType.YieldSupply.Topup - ) { - return "" + when (type) { + is TransactionType.Staking.Vote, + TransactionType.Staking.ClaimRewards, + TransactionType.Staking.Withdraw, + -> return "" + + is TransactionType.YieldSupply -> { + if (currency is CryptoCurrency.Token && type == TransactionType.YieldSupply.Send && !isOutgoing) { + return "" + } + } + else -> Unit } val prefix = when { status == TransactionStatus.Failed -> "" diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverter.kt new file mode 100644 index 0000000000..dd2569e500 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverter.kt @@ -0,0 +1,48 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.currency.yieldSupplyKey +import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams +import com.tangem.lib.crypto.BlockchainUtils +import com.tangem.utils.converter.Converter +import java.math.BigDecimal + +internal class YieldSupplyPromoBannerKeyConverter( + private val yieldModuleApyMap: Map, + private val shouldShowMainPromo: Boolean, +) : Converter { + + override fun convert(value: TokenConverterParams): String? { + if (!shouldShowMainPromo) return null + + val currencies = when (value) { + is TokenConverterParams.Wallet -> value.tokenList.flattenCurrencies() + is TokenConverterParams.Account -> value.accountList.flattenCurrencies() + }.filter { status -> + status.value is CryptoCurrencyStatus.Loaded || + status.value is CryptoCurrencyStatus.Custom + } + + val tokens = currencies.filter { it.currency is CryptoCurrency.Token } + + if (tokens.any { it.value.yieldSupplyStatus?.isActive == true }) return null + if (yieldModuleApyMap.isEmpty()) return null + + val max = tokens.asSequence() + .mapNotNull { status -> + val token = status.currency as? CryptoCurrency.Token ?: return@mapNotNull null + val tokenKey = token.yieldSupplyKey() + val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId) + + val matchedKey = yieldModuleApyMap.keys.firstOrNull { mapKey -> + mapKey.equals(tokenKey, shouldIgnoreCase) + } ?: return@mapNotNull null + + status to matchedKey + } + .maxByOrNull { (status, _) -> status.value.amount ?: BigDecimal.ZERO } + + return max?.second + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt index 1a01d99ec3..d1cbb38064 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt @@ -5,16 +5,18 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.utils.coroutines.combine6 +import com.tangem.utils.coroutines.combine7 import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import java.math.BigDecimal /** * Subscriber that monitors account list related data and updates the wallet state accordingly. @@ -30,19 +32,21 @@ internal class AccountListSubscriber @AssistedInject constructor( override val clickIntents: WalletClickIntents, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : BasicAccountListSubscriber() { - override fun create(coroutineScope: CoroutineScope): Flow<*> = combine6( + override fun create(coroutineScope: CoroutineScope): Flow<*> = combine7( flow1 = getAccountStatusListFlow(), flow2 = getAppCurrencyFlow(), flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet), flow4 = accountDependencies.isAccountsModeEnabledUseCase(), flow5 = yieldSupplyApyFlow(), flow6 = stakingApyFlow(), + flow7 = yieldSupplyGetShouldShowMainPromoFlow(), transform = ::updateState, ) - private fun yieldSupplyApyFlow(): Flow> { + private fun yieldSupplyApyFlow(): Flow> { return yieldSupplyApyFlowUseCase().distinctUntilChanged() } @@ -50,6 +54,10 @@ internal class AccountListSubscriber @AssistedInject constructor( return stakingApyFlowUseCase().distinctUntilChanged() } + private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow { + return yieldSupplyGetShouldShowMainPromoUseCase().distinctUntilChanged() + } + @AssistedFactory interface Factory { fun create(userWallet: UserWallet): AccountListSubscriber diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt index c8e072d004..ae2648f6d7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt @@ -20,6 +20,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenCon import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import timber.log.Timber +import java.math.BigDecimal /** * Basic implementation of [WalletSubscriber] for wallet with accounts. @@ -46,8 +47,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { appCurrency: AppCurrency, expandedAccounts: Set, isAccountMode: Boolean, - yieldSupplyApyMap: Map = emptyMap(), + yieldSupplyApyMap: Map = emptyMap(), stakingApyMap: Map> = emptyMap(), + shouldShowMainPromo: Boolean = false, ) { val mainAccount = accountList.mainAccount @@ -66,11 +68,18 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { portfolioId = PortfolioId(mainAccount.accountId), yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ) } isAccountMode -> { val convertParams = TokenConverterParams.Account(accountList, expandedAccounts) - updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap) + updateContent( + params = convertParams, + appCurrency = appCurrency, + yieldSupplyApyMap = yieldSupplyApyMap, + stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, + ) } } } @@ -79,8 +88,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { maybeTokenList: Lce, appCurrency: AppCurrency, portfolioId: PortfolioId, - yieldSupplyApyMap: Map = emptyMap(), + yieldSupplyApyMap: Map = emptyMap(), stakingApyMap: Map> = emptyMap(), + shouldShowMainPromo: Boolean, ) { val tokenList = maybeTokenList.getOrElse( ifLoading = { maybeContent -> @@ -110,14 +120,16 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { appCurrency = appCurrency, yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ) } private fun updateContent( params: TokenConverterParams, appCurrency: AppCurrency, - yieldSupplyApyMap: Map = emptyMap(), + yieldSupplyApyMap: Map = emptyMap(), stakingApyMap: Map> = emptyMap(), + shouldShowMainPromo: Boolean, ) { stateController.update( SetTokenListTransformer( @@ -127,6 +139,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { clickIntents = clickIntents, yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index e827068c9b..36de78ef22 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -15,6 +15,7 @@ import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker @@ -28,6 +29,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber +import java.math.BigDecimal @Deprecated("Use AccountListSubscriber instead") @Suppress("LongParameterList") @@ -40,6 +42,7 @@ internal abstract class BasicTokenListSubscriber( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, private val stakingApyFlowUseCase: StakingApyFlowUseCase, + private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : WalletSubscriber() { private val sendAnalyticsJobHolder = JobHolder() @@ -69,7 +72,8 @@ internal abstract class BasicTokenListSubscriber( flow2 = appCurrencyFlow(), flow3 = yieldSupplyApyFlow(), flow4 = stakingApyFlow(), - transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap -> + flow5 = yieldSupplyGetShouldShowMainPromoFlow(), + transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap, shouldShowMainPromo -> val tokenList = maybeTokenList.getOrElse( ifLoading = { maybeContent -> val isRefreshing = stateHolder.getWalletState(userWallet.walletId) @@ -98,6 +102,7 @@ internal abstract class BasicTokenListSubscriber( appCurrency = appCurrency, yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ) walletWithFundsChecker.check(tokenList) @@ -122,8 +127,9 @@ internal abstract class BasicTokenListSubscriber( private fun updateContent( params: TokenConverterParams, appCurrency: AppCurrency, - yieldSupplyApyMap: Map, + yieldSupplyApyMap: Map, stakingApyMap: Map>, + shouldShowMainPromo: Boolean, ) { stateHolder.update( SetTokenListTransformer( @@ -133,6 +139,7 @@ internal abstract class BasicTokenListSubscriber( clickIntents = clickIntents, yieldSupplyApyMap = yieldSupplyApyMap, stakingApyMap = stakingApyMap, + shouldShowMainPromo = shouldShowMainPromo, ), ) } @@ -146,9 +153,12 @@ internal abstract class BasicTokenListSubscriber( } .distinctUntilChanged() - private fun yieldSupplyApyFlow(): Flow> = yieldSupplyApyFlowUseCase() + private fun yieldSupplyApyFlow(): Flow> = yieldSupplyApyFlowUseCase() .distinctUntilChanged() private fun stakingApyFlow(): Flow>> = stakingApyFlowUseCase() .distinctUntilChanged() + + private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow = yieldSupplyGetShouldShowMainPromoUseCase() + .distinctUntilChanged() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 36220cd6e1..d75d637dad 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -12,6 +12,7 @@ import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore @@ -32,6 +33,7 @@ internal class MultiWalletTokenListSubscriber( getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, stakingApyFlowUseCase: StakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -41,6 +43,7 @@ internal class MultiWalletTokenListSubscriber( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ) { override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index e686dd4e20..9e148dcf24 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore @@ -27,6 +28,7 @@ internal class SingleWalletWithTokenListSubscriber( getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, stakingApyFlowUseCase: StakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -36,6 +38,7 @@ internal class SingleWalletWithTokenListSubscriber( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, stakingApyFlowUseCase = stakingApyFlowUseCase, + yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase, ) { override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt index b9c65f6340..bef7b6defb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriber.kt @@ -5,6 +5,7 @@ import androidx.paging.cachedIn import androidx.paging.map import arrow.core.Either import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.wallet.UserWallet @@ -58,7 +59,7 @@ internal class TxHistorySubscriber( refresh = isRefresh, ).map { it.cachedIn(coroutineScope) } - setLoadedTxHistoryState(maybeTxHistoryItems) + setLoadedTxHistoryState(maybeTxHistoryItems, status.currency) } } } @@ -67,18 +68,19 @@ internal class TxHistorySubscriber( private fun setLoadingTxHistoryState(maybeTxHistoryItemCount: MaybeTxHistoryCount, status: CryptoCurrencyStatus) { stateHolder.update( maybeTxHistoryItemCount.fold( - ifLeft = { + ifLeft = { error -> SetTxHistoryCountErrorTransformer( userWallet = userWallet, - error = it, + error = error, pendingTransactions = status.value.pendingTransactions, + currency = status.currency, clickIntents = clickIntents, ) }, - ifRight = { + ifRight = { txCount -> SetTxHistoryCountTransformer( userWalletId = userWallet.walletId, - transactionsCount = it, + transactionsCount = txCount, clickIntents = clickIntents, ) }, @@ -86,7 +88,7 @@ internal class TxHistorySubscriber( ) } - private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems) { + private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems, currency: CryptoCurrency) { stateHolder.update( maybeTxHistoryItems.fold( ifLeft = { @@ -102,6 +104,7 @@ internal class TxHistorySubscriber( symbol = blockchain.currency, decimals = blockchain.decimals(), clickIntents = clickIntents, + currency = currency, ) SetTxHistoryItemsTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriberV2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriberV2.kt index 23e7c78681..78c388f87f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriberV2.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TxHistorySubscriberV2.kt @@ -5,6 +5,7 @@ import androidx.paging.cachedIn import androidx.paging.map import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.wallet.UserWallet @@ -51,7 +52,7 @@ internal class TxHistorySubscriberV2( refresh = isRefresh, ).map { it.cachedIn(coroutineScope) } - setLoadedTxHistoryState(maybeTxHistoryItems) + setLoadedTxHistoryState(maybeTxHistoryItems, currency = status.currency) } } } @@ -60,18 +61,19 @@ internal class TxHistorySubscriberV2( private fun setLoadingTxHistoryState(maybeTxHistoryItemCount: MaybeTxHistoryCount, status: CryptoCurrencyStatus) { stateController.update( maybeTxHistoryItemCount.fold( - ifLeft = { + ifLeft = { error -> SetTxHistoryCountErrorTransformer( userWallet = userWallet, - error = it, + error = error, pendingTransactions = status.value.pendingTransactions, clickIntents = clickIntents, + currency = status.currency, ) }, - ifRight = { + ifRight = { txCount -> SetTxHistoryCountTransformer( userWalletId = userWallet.walletId, - transactionsCount = it, + transactionsCount = txCount, clickIntents = clickIntents, ) }, @@ -79,7 +81,7 @@ internal class TxHistorySubscriberV2( ) } - private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems) { + private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems, currency: CryptoCurrency) { stateController.update( maybeTxHistoryItems.fold( ifLeft = { @@ -92,6 +94,7 @@ internal class TxHistorySubscriberV2( ifRight = { itemsFlow -> val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain() val itemConverter = TxHistoryItemStateConverter( + currency = currency, symbol = blockchain.currency, decimals = blockchain.decimals(), clickIntents = clickIntents, diff --git a/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverterTest.kt b/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverterTest.kt new file mode 100644 index 0000000000..bca8b45d6e --- /dev/null +++ b/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/YieldSupplyPromoBannerKeyConverterTest.kt @@ -0,0 +1,208 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +import com.google.common.truth.Truth.assertThat +import com.tangem.domain.models.PortfolioId +import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.currency.yieldSupplyKey +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.network.NetworkAddress +import com.tangem.domain.models.tokenlist.TokenList +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.yield.supply.YieldSupplyStatus +import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams +import org.junit.Test +import java.math.BigDecimal + +class YieldSupplyPromoBannerKeyConverterTest { + + @Test + fun `GIVEN promo disabled WHEN convert THEN return null`() { + val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xABCDEF") + val status = createStatus(token = token, amount = BigDecimal.ONE, isYieldActive = false) + val tokenList = ungroupedTokenList(status) + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = tokenList, + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.10")), + shouldShowMainPromo = false, + ) + + val result = converter.convert(params) + + assertThat(result).isNull() + } + + @Test + fun `GIVEN empty apy map WHEN convert THEN return null`() { + val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xA1") + val status = createStatus(token = token, amount = BigDecimal("2.0"), isYieldActive = false) + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = ungroupedTokenList(status), + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = emptyMap(), + shouldShowMainPromo = true, + ) + + val result = converter.convert(params) + + assertThat(result).isNull() + } + + @Test + fun `GIVEN active yield token present WHEN convert THEN return null`() { + val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xAA") + val statusActive = createStatus(token = token, amount = BigDecimal("5"), isYieldActive = true) + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = ungroupedTokenList(statusActive), + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.12")), + shouldShowMainPromo = true, + ) + + val result = converter.convert(params) + + assertThat(result).isNull() + } + + @Test + fun `GIVEN multiple candidates EVM case insensitive WHEN convert THEN return key of max amount`() { + val evmNetworkId = "ETH" + val tokenSmall = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xAbCd") + val tokenBig = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xBEEF") + + val statusSmall = createStatus(token = tokenSmall, amount = BigDecimal("1.00"), isYieldActive = false) + val statusBig = createStatus(token = tokenBig, amount = BigDecimal("10.00"), isYieldActive = false) + + val apyMap = mapOf( + "${tokenSmall.network.backendId}_${tokenSmall.contractAddress.lowercase()}" to BigDecimal("0.05"), + "${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}" to BigDecimal("0.15"), + ) + + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = ungroupedTokenList(statusSmall, statusBig), + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = apyMap, + shouldShowMainPromo = true, + ) + + val result = converter.convert(params) + + val expectedKey = "${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}" + assertThat(result).isEqualTo(expectedKey) + } + + @Test + fun `GIVEN non evm case sensitive mismatch WHEN convert THEN return null`() { + val nonEvmId = "xrp" + val token = createToken(networkId = nonEvmId, backendId = nonEvmId, contract = "rAbC123") + val status = createStatus(token = token, amount = BigDecimal("3"), isYieldActive = false) + + val mismatchedKey = "${token.network.backendId}_${token.contractAddress.lowercase()}" + val apyMap = mapOf(mismatchedKey to BigDecimal("0.07")) + + val params = TokenConverterParams.Wallet( + portfolioId = PortfolioId.Wallet(UserWalletId("00")), + tokenList = ungroupedTokenList(status), + ) + val converter = YieldSupplyPromoBannerKeyConverter( + yieldModuleApyMap = apyMap, + shouldShowMainPromo = true, + ) + + val result = converter.convert(params) + + assertThat(result).isNull() + } + + private fun ungroupedTokenList(vararg statuses: CryptoCurrencyStatus): TokenList.Ungrouped { + return TokenList.Ungrouped( + totalFiatBalance = com.tangem.domain.models.TotalFiatBalance.Loaded( + amount = BigDecimal.ZERO, + source = StatusSource.ACTUAL, + ), + sortedBy = com.tangem.domain.models.TokensSortType.NONE, + currencies = statuses.toList(), + ) + } + + private fun createStatus( + token: CryptoCurrency.Token, + amount: BigDecimal, + isYieldActive: Boolean, + ): CryptoCurrencyStatus { + val networkAddress = NetworkAddress.Single( + defaultAddress = NetworkAddress.Address( + value = "addr", + type = NetworkAddress.Address.Type.Primary, + ), + ) + val value = CryptoCurrencyStatus.Custom( + amount = amount, + fiatAmount = null, + fiatRate = null, + priceChange = null, + stakingBalance = null, + yieldSupplyStatus = if (isYieldActive) { + YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = null, + ) + } else { + null + }, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = networkAddress, + sources = CryptoCurrencyStatus.Sources(), + ) + return CryptoCurrencyStatus( + currency = token, + value = value, + ) + } + + private fun createToken(networkId: String, backendId: String, contract: String): CryptoCurrency.Token { + val network = Network( + id = Network.ID(value = networkId, derivationPath = Network.DerivationPath.None), + backendId = backendId, + name = backendId, + currencySymbol = "SYM", + derivationPath = Network.DerivationPath.None, + isTestnet = false, + standardType = when (backendId) { + "ethereum" -> Network.StandardType.ERC20 + else -> Network.StandardType.Unspecified("UNSPEC") + }, + hasFiatFeeRate = false, + canHandleTokens = true, + transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, + ) + return CryptoCurrency.Token( + id = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId(networkId), + suffix = CryptoCurrency.ID.Suffix.ContractAddress(contract), + ), + network = network, + name = "Token", + symbol = "TKN", + decimals = 18, + iconUrl = null, + isCustom = false, + contractAddress = contract, + ) + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt index 5acb41c803..038b9106de 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/common/WcCommonTransactionModel.kt @@ -20,4 +20,8 @@ internal interface WcCommonTransactionModel { fun showSuccessSignMessage(message: TextReference = resourceReference(R.string.wc_successfully_signed)) { messageSender.send(ToastMessage(message = message)) } + + fun showSuccessAddedMessage(message: TextReference = resourceReference(R.string.common_added)) { + messageSender.send(ToastMessage(message = message)) + } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index 08176516ce..8ed61a1b5d 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -111,7 +111,7 @@ internal class WcAddNetworkModel @Inject constructor( modelScope.launch { _uiState.update { it?.copy(transaction = it.transaction.copy(isLoading = true)) } useCase.approve().getOrNull()?.let { - showSuccessSignMessage() + showSuccessAddedMessage() router.pop() } ?: run { _uiState.update { it?.copy(transaction = it.transaction.copy(isLoading = false)) } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt index 8dbc13d8c2..d468e6d60d 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/YieldSupplyActiveModel.kt @@ -58,6 +58,7 @@ internal class YieldSupplyActiveModel @Inject constructor( private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val urlOpener: UrlOpener, private val appRouter: AppRouter, + private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase, ) : Model(), YieldSupplyStopEarningComponent.ModelCallback, YieldSupplyApproveComponent.ModelCallback { @@ -241,11 +242,13 @@ internal class YieldSupplyActiveModel @Inject constructor( userWalletId, cryptoCurrencyStatusFlow.value, ).onRight { minAmount -> + val dustAmount = yieldSupplyGetDustMinAmountUseCase(minAmount = minAmount, appCurrency = appCurrency) uiState.update( YieldSupplyActiveMinAmountTransformer( cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value, appCurrency = appCurrency, minAmount = minAmount, + dustMinAmount = dustAmount, analyticsHandler = analyticsHandler, onApprove = ::onApprove, ), diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt index c3b1fd4763..54dd971067 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt @@ -28,11 +28,12 @@ import java.math.BigDecimal * - Builds the fee policy note text using the minimum amount. * - Adds contextual notifications: * - Approval required notification when spending is not yet allowed (emits analytics on CTA). - * - "Not all amount supplied" info when wallet balance exceeds the supplied balance by more than [minAmount]. + * - "Not all amount supplied" info when the not-supplied balance exceeds the dust threshold [dustMinAmount]. * * @property cryptoCurrencyStatus Current currency status used to calculate values and flags. * @property appCurrency Preferred fiat currency for formatting. * @property minAmount Protocol-required minimal amount to deposit/supply (in crypto units). + * @property dustMinAmount Threshold used to detect dust/not-supplied balance (in crypto units). * @property analyticsHandler Analytics reporter for user actions. * @property onApprove Action invoked when the "Approve" notification button is tapped. */ @@ -40,6 +41,7 @@ internal class YieldSupplyActiveMinAmountTransformer( private val cryptoCurrencyStatus: CryptoCurrencyStatus, private val appCurrency: AppCurrency, private val minAmount: BigDecimal, + private val dustMinAmount: BigDecimal, private val analyticsHandler: AnalyticsEventHandler, private val onApprove: () -> Unit, ) : Transformer { @@ -89,7 +91,7 @@ internal class YieldSupplyActiveMinAmountTransformer( } private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? { - return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)) { + return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustMinAmount)) { val cryptoCurrency = cryptoCurrencyStatus.currency val notDepositedAmount = cryptoCurrencyStatus.notSuppliedAmountOrNull() val formattedAmount = diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt index 621430c377..c6bb211f29 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.yield.supply.impl.main.model +import arrow.core.getOrElse import com.tangem.common.routing.AppRoute import android.os.SystemClock import com.tangem.common.routing.AppRoute.YieldSupplyPromo @@ -12,6 +13,8 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.combinedReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -51,6 +54,7 @@ internal class YieldSupplyModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventsHandler: AnalyticsEventHandler, private val appRouter: AppRouter, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher, @@ -61,6 +65,7 @@ internal class YieldSupplyModel @Inject constructor( private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase, private val yieldSupplyRepository: YieldSupplyRepository, private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase, + private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase, ) : Model(), YieldSupplyClickIntents { private val params = paramsContainer.require() @@ -69,6 +74,7 @@ internal class YieldSupplyModel @Inject constructor( field = MutableStateFlow(YieldSupplyUM.Initial) private val cryptoCurrency = params.cryptoCurrency + private var appCurrency: AppCurrency = AppCurrency.Default var userWallet: UserWallet by Delegates.notNull() private val fetchCurrencyJobHolder = JobHolder() @@ -81,10 +87,17 @@ internal class YieldSupplyModel @Inject constructor( } private fun checkIfYieldSupplyIsAvailable() { - modelScope.launch(dispatchers.io) { + modelScope.launch(dispatchers.default) { + appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency) if (isAvailable) { subscribeOnCurrencyStatusUpdates() + singleNetworkStatusFetcher( + params = SingleNetworkStatusFetcher.Params( + userWalletId = params.userWalletId, + network = cryptoCurrency.network, + ), + ) } } } @@ -334,7 +347,11 @@ internal class YieldSupplyModel @Inject constructor( val minAmount = yieldSupplyMinAmountUseCase(userWalletId = userWallet.walletId, cryptoCurrencyStatus) .getOrNull() if (minAmount != null) { - cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount) + val dustAmount = yieldSupplyGetDustMinAmountUseCase( + minAmount = minAmount, + appCurrency = appCurrency, + ) + cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount) } else { false } diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 8c10bcaf9d..e5b0dbadd1 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,9 +5,9 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1317" +tangemBlockchainSdk = "develop-1327" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-568" +tangemCardSdk = "develop-573" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^