diff --git a/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt b/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt index 62b73d6c4b..863e387a71 100644 --- a/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt +++ b/app/src/androidTest/kotlin/com/tangem/screens/DetailsPageObject.kt @@ -44,7 +44,7 @@ class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) : } val contactSupportButton: KNode = child { hasTestTag(DetailsScreenTestTags.SCREEN_ITEM) - hasText(getResourceString(R.string.details_row_title_contact_to_support)) + hasText(getResourceString(R.string.common_contact_support)) } val toSButton: KNode = child { hasTestTag(DetailsScreenTestTags.SCREEN_ITEM) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 60e77f7ace..ef63e16db7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -279,6 +279,17 @@ android:host="onboard-visa" android:scheme="tangem" /> + + + + + + + + diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt deleted file mode 100644 index 0396cf84fc..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.tap.common.extensions - -fun String.removePrefixOrNull(prefix: String): String? = when { - startsWith(prefix) -> substring(prefix.length) - else -> null -} \ No newline at end of file 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/domain/tasks/visa/TangemPaySignWithdrawalHashTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPaySignWithdrawalHashTask.kt index faf32983a0..d5bc3b64c1 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPaySignWithdrawalHashTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPaySignWithdrawalHashTask.kt @@ -15,7 +15,6 @@ import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.card.common.visa.VisaUtilities import com.tangem.domain.visa.error.VisaActivationError -import com.tangem.operations.ScanTask import com.tangem.operations.derivation.DeriveWalletPublicKeyTask import com.tangem.operations.sign.SignHashCommand @@ -92,25 +91,7 @@ class TangemPaySignWithdrawalHashTask( ?: targetWalletPublicKey.toDecompressedPublicKey(), ).asRSVLegacyEVM().toHexString().lowercase() - scanCard( - session = session, - callback = callback, - signedData = rsvSignature, - ) - } - is CompletionResult.Failure -> { - callback(CompletionResult.Failure(result.error)) - } - } - } - } - - private fun scanCard(signedData: String, session: CardSession, callback: CompletionCallback) { - val scanTask = ScanTask() - scanTask.run(session) { result -> - when (result) { - is CompletionResult.Success -> { - callback(CompletionResult.Success(signedData)) + callback(CompletionResult.Success(rsvSignature)) } is CompletionResult.Failure -> { callback(CompletionResult.Failure(result.error)) 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 e0b08af719..e9bc518930 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 @@ -112,6 +112,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 85b958f1d1..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,19 +16,29 @@ 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") @OptIn(ExperimentalCoroutinesApi::class) internal class BiometricUserWalletsListManager( private val keysRepository: UserWalletsKeysRepository, private val publicInformationRepository: UserWalletsPublicInformationRepository, private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository, private val selectedUserWalletRepository: SelectedUserWalletRepository, + private val dispatcherProvider: CoroutineDispatcherProvider, ) : UserWalletsListManager.Lockable { private val state = MutableStateFlow(State()) + private var hasSavedWallets: Boolean? = null + private val savedWalletMutex = Mutex() + override val isLockable: Boolean = true override val userWallets: Flow> @@ -61,7 +71,23 @@ internal class BiometricUserWalletsListManager( get() = state.value.isLocked override val hasUserWallets: Boolean - get() = keysRepository.hasSavedEncryptionKeys() + get() { + 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 + } + } + } + } override val walletsCount: Int get() = state.value.userWallets.size @@ -72,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() { @@ -120,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) + } } } } @@ -140,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 { @@ -166,41 +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 { - return sensitiveInformationRepository.clear() - .flatMap { publicInformationRepository.clear() } - .map { - keysRepository.clear() - selectedUserWalletRepository.set(null) - state.value = State() - } + savedWalletMutex.withLock { + hasSavedWallets = null + } + 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 7025664f0b..e51b49f725 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,12 +4,15 @@ 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 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.AnalyticsExceptionHandler +import com.tangem.core.analytics.models.ExceptionAnalyticsEvent import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.context.childByContext @@ -42,6 +45,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( @@ -59,6 +63,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val userWalletsListRepository: UserWalletsListRepository, private val cardRepository: CardRepository, private val onboardingRepository: OnboardingRepository, + private val analyticsExceptionHandler: AnalyticsExceptionHandler, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { @@ -73,13 +78,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 + } }, ) @@ -89,6 +104,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/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 57dc3d376e..a3a9cf4047 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -660,8 +660,13 @@ internal class ChildFactory @Inject constructor( createComponentChild( context = context, params = when (val mode = route.mode) { - is AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding -> ContinueOnboarding - is AppRoute.TangemPayOnboarding.Mode.Deeplink -> Deeplink(deeplink = mode.deeplink) + is AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding -> ContinueOnboarding( + userWalletId = mode.userWalletId, + ) + is AppRoute.TangemPayOnboarding.Mode.Deeplink -> Deeplink( + deeplink = mode.deeplink, + userWalletId = mode.userWalletId, + ) }, componentFactory = tangemPayOnboardingComponentFactory, ) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt index 45a9e27528..097b57b518 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt @@ -102,6 +102,7 @@ internal class DeepLinkFactory @Inject constructor( private fun launchDeepLink(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) { when (deeplinkUri.scheme) { + DeepLinkScheme.Https.scheme -> handleHttpDeepLinks(deeplinkUri) DeepLinkScheme.Tangem.scheme -> handleTangemDeepLinks(deeplinkUri, coroutineScope, isFromOnNewIntent) DeepLinkScheme.WalletConnect.scheme -> walletConnectDeepLink.create(deeplinkUri) else -> { @@ -115,6 +116,13 @@ internal class DeepLinkFactory @Inject constructor( } } + private fun handleHttpDeepLinks(deeplinkUri: Uri) { + if (deeplinkUri.host == DeepLinkRoute.PayApp.host && deeplinkUri.path?.startsWith("/pay-app") == true) { + onboardVisaDeepLink.create(deeplinkUri) + return + } + } + @Suppress("CyclomaticComplexMethod") private fun handleTangemDeepLinks(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) { val queryParams = getQueryParams(deeplinkUri) 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/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index f1c07144f6..4e22012973 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -431,10 +431,13 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class Deeplink( val deeplink: String, + val userWalletId: UserWalletId?, ) : Mode() @Serializable - object ContinueOnboarding : Mode() + data class ContinueOnboarding( + val userWalletId: UserWalletId?, + ) : Mode() } } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt index 9b5ba24bfa..d57eb5e7f0 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt @@ -63,9 +63,14 @@ sealed class DeepLinkRoute { data object OnboardVisa : DeepLinkRoute() { override val host: String = "onboard-visa" } + + data object PayApp : DeepLinkRoute() { + override val host: String = "tangem.com" + } } enum class DeepLinkScheme(val scheme: String) { Tangem(scheme = "tangem"), WalletConnect(scheme = "wc"), + Https(scheme = "https"), } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt index 47233d7291..1941c34ff9 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt @@ -35,8 +35,8 @@ import com.tangem.core.ui.extensions.isNullOrEmpty import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.utils.singleEvent import com.tangem.core.ui.test.SendScreenTestTags +import com.tangem.core.ui.utils.singleEvent @Composable fun NavigationButtonsBlock( @@ -78,7 +78,9 @@ fun NavigationButtonsBlockV2( horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { PreviousButton(navigationUM?.prevButton) - NavigationPrimaryButton(navigationUM?.primaryButton, modifier = Modifier.weight(1f)) + key(navigationUM?.source) { + NavigationPrimaryButton(navigationUM?.primaryButton, modifier = Modifier.weight(1f)) + } } } } diff --git a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationUM.kt index dcde9ac935..dc1953b1d9 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationUM.kt @@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.TextReference @Immutable sealed class NavigationUM { data class Content( + val source: String, val title: TextReference, val subtitle: TextReference?, @DrawableRes val backIconRes: Int, 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 516a15539c..ce46ff400f 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 @@ -49,7 +49,7 @@ class TokenItemStateConverter( private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = { CryptoCurrencyToIconStateConverter().convert(it) }, - private val onApyLabelClick: ((CryptoCurrencyStatus, String) -> Unit)? = null, + private val onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)? = null, private val onYieldPromoCloseClick: (() -> Unit)? = null, private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { currencyStatus -> createTitleState( @@ -178,7 +178,7 @@ class TokenItemStateConverter( currencyStatus: CryptoCurrencyStatus, yieldModuleApyMap: Map, stakingApyMap: Map>, - onApyLabelClick: ((CryptoCurrencyStatus, String) -> Unit)?, + onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?, ): TokenItemState.TitleState { return when (val value = currencyStatus.value) { is CryptoCurrencyStatus.Loading, @@ -204,7 +204,7 @@ class TokenItemStateConverter( earnApy = apyInfo?.text, earnApyIsActive = apyInfo?.isActive == true, onApyLabelClick = if (apyInfo?.apy != null && onApyLabelClick != null) { - { onApyLabelClick.invoke(currencyStatus, apyInfo.apy) } + { onApyLabelClick.invoke(currencyStatus, apyInfo.source, apyInfo.apy) } } else { null }, @@ -236,6 +236,7 @@ class TokenItemStateConverter( ), isActive = isActive, apy = yieldSupplyApy.toString(), + source = ApySource.YIELD_SUPPLY, ) } } @@ -261,6 +262,7 @@ class TokenItemStateConverter( ), isActive = stakingInfo.isActive, apy = apyString, + source = ApySource.STAKING, ) } } @@ -395,7 +397,7 @@ class TokenItemStateConverter( status: CryptoCurrencyStatus, yieldModuleApyMap: Map, yieldSupplyPromoBannerKey: String?, - onApyLabelClick: ((CryptoCurrencyStatus, String) -> Unit)?, + onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?, onYieldPromoCloseClick: (() -> Unit)?, ): TokenItemState.PromoBannerState { val token = status.currency as? CryptoCurrency.Token ?: return TokenItemState.PromoBannerState.Empty @@ -413,7 +415,7 @@ class TokenItemStateConverter( wrappedList(yieldSupplyApy), ), onPromoBannerClick = { - onApyLabelClick?.invoke(status, yieldSupplyApy.toString()) + onApyLabelClick?.invoke(status, ApySource.YIELD_SUPPLY, yieldSupplyApy.toString()) }, onCloseClick = { onYieldPromoCloseClick?.invoke() @@ -472,5 +474,11 @@ class TokenItemStateConverter( val text: TextReference?, val isActive: Boolean, val apy: String?, + val source: ApySource, ) + + enum class ApySource { + STAKING, + YIELD_SUPPLY, + } } \ No newline at end of file diff --git a/core/config-toggles/detekt-baseline-debug.xml b/core/config-toggles/detekt-baseline-debug.xml index b0e03ef827..274a75d8d4 100644 --- a/core/config-toggles/detekt-baseline-debug.xml +++ b/core/config-toggles/detekt-baseline-debug.xml @@ -2,8 +2,6 @@ - DoubleMutabilityForCollection:DevExcludedBlockchainsManager.kt$DevExcludedBlockchainsManager$private var blockchainTogglesMap: MutableMap<String, Boolean> by Delegates.notNull() - DoubleMutabilityForCollection:DevFeatureTogglesManager.kt$DevFeatureTogglesManager$private var featureTogglesMap: MutableMap<String, Boolean> by Delegates.notNull() Indentation:ExcludedBlockchainToggles.kt$ExcludedBlockchainToggles$ Indentation:FeatureToggles.kt$FeatureToggles$ diff --git a/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json index b3c1dd8f8d..59809c4e40 100644 --- a/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json +++ b/core/config-toggles/src/main/assets/configs/excluded_blockchains_config.json @@ -29,7 +29,7 @@ }, { "name": "zklink", - "version": "5.31" + "version": "undefined" }, { "name": "plasma", diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 1d456f1591..03e0a8cfd3 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -49,7 +49,7 @@ }, { "name": "NEW_ONRAMP_MAIN_ENABLED", - "version": "undefined" + "version": "5.31.0" }, { "name": "ACCOUNTS_FEATURE_ENABLED", diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManager.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManager.kt index 6f6645c865..98311b8d7a 100644 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManager.kt +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/blockchain/impl/DevExcludedBlockchainsManager.kt @@ -21,6 +21,8 @@ internal class DevExcludedBlockchainsManager( ) : MutableExcludedBlockchainsManager { private val fileBlockchainToggles: Map = getFileBlockchainToggles() + + @Suppress("DoubleMutabilityForCollection") private var blockchainTogglesMap: MutableMap by Delegates.notNull() override val excludedBlockchainsIds: Set diff --git a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/feature/impl/DevFeatureTogglesManager.kt b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/feature/impl/DevFeatureTogglesManager.kt index 79d78151b7..d59bac9d71 100644 --- a/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/feature/impl/DevFeatureTogglesManager.kt +++ b/core/config-toggles/src/main/kotlin/com/tangem/core/configtoggle/feature/impl/DevFeatureTogglesManager.kt @@ -22,6 +22,8 @@ internal class DevFeatureTogglesManager( ) : MutableFeatureTogglesManager { private val fileFeatureTogglesMap: Map = getFileFeatureToggles() + + @Suppress("DoubleMutabilityForCollection") private var featureTogglesMap: MutableMap by Delegates.notNull() init { 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 f3f7fbf07d..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 @@ -183,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 58da791e4e..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? @@ -678,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 @@ -773,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 @@ -1367,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. @@ -1399,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. @@ -1410,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 @@ -1469,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 @@ -1920,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 @@ -1935,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 @@ -1966,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 849f728af4..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はサポートされていません @@ -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,11 +1449,14 @@ 現在、出金ができません 現在の処理が完了するまでは、スワップや新しい出金を開始することはできません。 出金処理中です + PINコードを変更 + 忘れた場合はアプリに戻って確認できます。 カードの発行に失敗しました 技術的なエラーが発生しました。下のボタンをクリックして、もう一度お試しください。 技術的なエラーが発生しました。サポートへお問い合わせください。 暗号資産を日常の支払いに使おう。\n\nこれまでにないタイプの決済カード。 Tangem Payを入手 + サポートへ移動 通常は最大で15分ほどかかります Tangemカードのセットアップ カードを発行しています @@ -1442,8 +1464,11 @@ Tangem Pay プロフィールを確認できませんでした。ご不明な点があればサポートまでお問い合わせください。 申し訳ございませんが、本人確認を行うことができませんでした + KYC進行中 ステータスを表示 Tangem PayのKYC手続き進行中 + 暗号資産を、リアルな支払いに。\n他とはまったく違う、新しいタイプの決済カード。 + Tangem Visaカード カードをGET Apple PayとGoogle Payに対応したデジタルカード付き どこでも暗号資産を使える @@ -1457,10 +1482,12 @@ 技術的な問題を修正しています。後でもう一度お試しください。 サービスは一時的に利用できません 現在サービスに接続できません。後ほどもう一度お試しください。 + 同期が必要です Tangem Visaカード Tangem Payは一時的に利用できません Tangem Pay カードまたはリングを使用して、支払いアカウントへのアクセスを復元してください。 + PINコード これは私のウォレットです 残高非表示 残高表示 @@ -1555,6 +1582,7 @@ %d日間利用可能 残高と限度額 + アクセスコードは、支払いアカウントの管理および不正アクセスからの保護に使用されます。 アクセスコード 本当に終了してもよろしいですか?中断したところから後で続行できます。 長くはかかりません。アカウントを設定しています。 @@ -1851,7 +1879,7 @@ ワンタップで開始 シームレスで安全 シンプルな操作 - Tangemでハードウェアウォレットを作成しましょう。銀行のカードのようにスリムで、金庫のように安全です。 + Tangemでハードウェアウォレットを作成しよう。キャッシュカードのようにスリムで、金庫のように安全。 ソフトウェアウォレットを作成またはインポート スマートフォン上にソフトウェアウォレットを作成またはインポートする。 モバイルウォレットから始める @@ -1908,6 +1936,8 @@ 入金手数料ポリシー Tangemは、生成された利息に対して15%サービス手数料も徴収します。 ネットワーク手数料が下がるか、残高が最低必要額に達すると、資金は自動的にAaveに供給されます。 + 市場が非常に活発なため、現在の手数料は通常よりも高くなっています。今すぐ続行するか、手数料が下がるのを待って後で再確認することもできます。 + ネットワーク手数料が高額です 過去のリターン 保有資産に年利%1$s%%を適用 利息モードでのトークンの承認が取り消されました。トークンを開いて再度許可してください。 @@ -1924,7 +1954,7 @@ 分散型・自己管理型 このサービスを利用することにより、プロバイダー\n%1$sおよび%2$sに同意するものとします。 Aaveに接続 - 残高に %1$s%% の年利(APY)を適用 + 保有資産に\n年利%1$s%%を適用 Aave %1$s%% • 変動金利 変動金利 Aave @@ -1954,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 62446f8321..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 @@ Сначала завершите создание резервной копии Не завершено Другие способы - Сохраните свою recovery-фразу вручную в надёжном месте. Держите её в секрете, чтобы защитить свои средства. + Сохраните фразу восстановления в безопасном месте и держите её в секрете. Фраза восстановления Чтобы защитить ваш кошелёк с помощью кода доступа, сначала завершите резервное копирование. Чтобы улучшить кошелёк до аппаратного, сначала создайте резервную копию. Ваши приватные ключи надёжно зашифрованы и хранятся на вашем телефоне Ключи хранятся в приложении - Создайте или восстановите свой кошелёк с помощью фразы восстановления — вашей встроенной резервной копии - Резервная копия сид-фразы + Создайте или импортируйте кошелёк с помощью вашей фразы восстановления. + Резервная копия Создать мобильный кошелек - Импортировать существующий кошелек + Импортировать существующий Эта фраза восстановления уже была импортирована Мобильный кошелек Забыть кошелек @@ -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 @@ -1814,6 +1935,7 @@ Tangem взимает комиссию за обслуживание в размере 15% от полученного дохода. Ваши средства будут автоматически переведены в Aave, как только комиссия сети снизится или баланс достигнет минимально необходимой суммы. Историческая доходность + Получай до %1$s APY на свой баланс Разрешение для вашего токена в режиме доходности было отозвано. Откройте токен, чтобы выдать разрешение снова. Необходимо разрешение для токена Проверьте ваше интернет соединение @@ -1828,7 +1950,9 @@ Децентрализованный и некастодиальный Используя сервис, вы соглашаетесь с %1$s и %2$s Подключить Aave + Подключите %1$s %% APY\nна ваш баланс Aave %1$s%% • Плавающая ставка + Динамическая процентная ставка Aave Сред. %s Доходность за прошлый год @@ -1855,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 0bbcf90b6a..8c52e119ff 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -248,6 +248,7 @@ Coming soon Confirm Connecting + Contact support Contact Tangem Support Contact Visa Support Continue @@ -427,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 @@ -438,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 @@ -602,7 +603,7 @@ Other methods 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 @@ -799,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 @@ -856,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! @@ -1218,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. @@ -1256,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. @@ -1392,6 +1407,8 @@ You receive Choose token not available + We would be happy to receive your feedback + Tangem Pay is now in beta Card frozen Card payment Deposit @@ -1399,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 @@ -1434,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 @@ -1457,11 +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 @@ -1472,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 @@ -1490,6 +1512,7 @@ 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 @@ -1986,6 +2009,8 @@ 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 Approval for your token in Yield Mode has been revoked. Open the token to grant permission again. @@ -2032,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/rows/NetworkTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/NetworkTitle.kt index e7644ee705..25b0a54929 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/NetworkTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/NetworkTitle.kt @@ -61,7 +61,6 @@ fun NetworkTitle( ) { Box( modifier = Modifier - .weight(weight = 1f) .heightIn(min = TangemTheme.dimens.size20), contentAlignment = Alignment.CenterStart, content = title, @@ -99,4 +98,4 @@ private fun NetworkTitlePreview(@PreviewParameter(NetworkTitleIconVisibilityProv } } -private object NetworkTitleIconVisibilityProvider : CollectionPreviewParameterProvider(listOf(true, false)) \ No newline at end of file +private class NetworkTitleIconVisibilityProvider : CollectionPreviewParameterProvider(listOf(true, false)) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt index a3f882b7ba..719f825aed 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/tokenlist/internal/GroupTitleItem.kt @@ -124,4 +124,4 @@ private fun NetworkTitleItemPreview(@PreviewParameter(GroupTitleItemProvider::cl } } -private object GroupTitleItemProvider : CollectionPreviewParameterProvider(collection = listOf(true, false)) \ No newline at end of file +private class GroupTitleItemProvider : CollectionPreviewParameterProvider(collection = listOf(true, false)) \ No newline at end of file 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/ui/src/main/res/drawable/img_visa_label_26_16.xml b/core/ui/src/main/res/drawable/img_visa_label_26_16.xml deleted file mode 100644 index c5859a2fda..0000000000 --- a/core/ui/src/main/res/drawable/img_visa_label_26_16.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/core/ui/src/main/res/drawable/img_visa_notification.webp b/core/ui/src/main/res/drawable/img_visa_notification.webp new file mode 100644 index 0000000000..b0b4afb8b6 Binary files /dev/null and b/core/ui/src/main/res/drawable/img_visa_notification.webp differ diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/StringExt.kt b/core/utils/src/main/java/com/tangem/utils/extensions/StringExt.kt index bba0975985..fd6c6e5846 100644 --- a/core/utils/src/main/java/com/tangem/utils/extensions/StringExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/extensions/StringExt.kt @@ -9,4 +9,8 @@ fun String.uriValidate(): Boolean { val regex = DEEPLINK_VALIDATION_REGEX.toRegex() return !regex.containsMatchIn(this) +} + +fun String.addHexPrefix(): String { + return if (this.startsWith("0x")) this else "0x$this" } \ No newline at end of file diff --git a/data/account/detekt-baseline-debug.xml b/data/account/detekt-baseline-debug.xml index f0b34d22a4..014cb3a866 100644 --- a/data/account/detekt-baseline-debug.xml +++ b/data/account/detekt-baseline-debug.xml @@ -6,8 +6,6 @@ MultilineLambdaItParameter:DefaultAccountsCRUDRepository.kt$DefaultAccountsCRUDRepository${ if (it is HttpException && it.code == HttpException.Code.NOT_MODIFIED) { null } else { throw it } } MultilineLambdaItParameter:GetWalletAccountsResponseExt.kt${ enrichedTokensByAccountId[it].orEmpty().map { token -> // Tokens from unexisting accounts should be copied to the main account token.copy(accountId = accountDTO.id) } } NoNameShadowing:GetWalletAccountsResponseExt.kt$tokens - NullableToStringCall:AccountListCryptoCurrenciesProducer.kt$AccountListCryptoCurrenciesProducer$${this::class.simpleName} - NullableToStringCall:DefaultMultiWalletCryptoCurrenciesProducer.kt$DefaultMultiWalletCryptoCurrenciesProducer$${this::class.simpleName} UnnecessaryLet:DefaultAccountsCRUDRepository.kt$DefaultAccountsCRUDRepository$let(AccountName::invoke) diff --git a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt index 99d4ac7ff6..3bc99dc784 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt @@ -115,7 +115,7 @@ internal class DefaultWalletAccountsFetcher @Inject constructor( saveETag(userWalletId, apiResponse) - apiResponse.bind() + apiResponse.bind().enrichByAccountId() }, onError = { error -> if (error.isNetworkError(code = Code.PRECONDITION_FAILED)) { @@ -142,10 +142,11 @@ internal class DefaultWalletAccountsFetcher @Inject constructor( saveETag(userWalletId, apiResponse) - val responseBody = apiResponse.bind() - store(userWalletId = userWalletId, response = responseBody) + val response = apiResponse.bind().enrichByAccountId() - FetchResult(responseBody) + store(userWalletId = userWalletId, response = response) + + FetchResult(response) }, onError = { throwable -> // pushWalletAccounts and storeWalletAccounts help to avoid cyclic dependency @@ -215,6 +216,18 @@ internal class DefaultWalletAccountsFetcher @Inject constructor( } } + private fun GetWalletAccountsResponse.enrichByAccountId(): GetWalletAccountsResponse { + return copy( + accounts = accounts.map { accountDTO -> + accountDTO.copy( + tokens = accountDTO.tokens?.map { token -> + token.copy(accountId = accountDTO.id) + }, + ) + }, + ) + } + private fun getAccountsResponseStore(userWalletId: UserWalletId): AccountsResponseStore { return accountsResponseStoreFactory.create(userWalletId = userWalletId) } diff --git a/data/account/src/main/kotlin/com/tangem/data/account/producer/AccountListCryptoCurrenciesProducer.kt b/data/account/src/main/kotlin/com/tangem/data/account/producer/AccountListCryptoCurrenciesProducer.kt index 9722001f29..4cd149ae75 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/producer/AccountListCryptoCurrenciesProducer.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/producer/AccountListCryptoCurrenciesProducer.kt @@ -36,11 +36,12 @@ internal class AccountListCryptoCurrenciesProducer @AssistedInject constructor( override val fallback: Option> = emptySet().some() + @Suppress("NullableToStringCall") override fun produce(): Flow> { val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) if (!userWallet.isMultiCurrency) { - error("${this::class.simpleName} supports only multi-currency wallet") + error("${this::class.simpleName ?: this::class.toString()} supports only multi-currency wallet") } return accountsResponseStoreFactory.create(userWalletId = userWallet.walletId).data @@ -49,10 +50,13 @@ internal class AccountListCryptoCurrenciesProducer @AssistedInject constructor( if (response == null) return@map emptySet() response.accounts.flatMapTo(hashSetOf()) { accountDTO -> + val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull() + ?: return@map emptySet() + responseCryptoCurrenciesFactory.createCurrencies( tokens = accountDTO.tokens.orEmpty(), userWallet = userWallet, - accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull(), + accountIndex = accountIndex, ) } } diff --git a/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducer.kt b/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducer.kt index 8c46088747..25255d60b4 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducer.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducer.kt @@ -5,6 +5,7 @@ import arrow.core.some import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer @@ -39,7 +40,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducer @AssistedInject constr val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) if (!userWallet.isMultiCurrency) { - error("${this::class.simpleName} supports only multi-currency wallet") + error("${this::class.simpleName ?: this::class.toString()} supports only multi-currency wallet") } return userTokensResponseStore.get(userWalletId = params.userWalletId) @@ -50,6 +51,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducer @AssistedInject constr responseCryptoCurrenciesFactory.createCurrencies( response = response, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ).toSet() } .onEmpty { emit(emptySet()) } diff --git a/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt b/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt index d1a0449ebc..d4e9d0325f 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/tokens/DefaultMainAccountTokensMigration.kt @@ -61,7 +61,7 @@ internal class DefaultMainAccountTokensMigration( val unassignedTokens = mainAccount.findUnassignedTokens(derivationIndex) - if (unassignedTokens == null) { + if (unassignedTokens.isNullOrEmpty()) { Timber.i("No unassigned tokens found for migration") return@either } diff --git a/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducerTest.kt b/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducerTest.kt index 983ba0706e..c622f073a4 100644 --- a/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducerTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducerTest.kt @@ -10,6 +10,7 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.configs.GenericCardConfig +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.isMultiCurrency @@ -72,7 +73,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { } verify(inverse = true) { - responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any()) + responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any(), accountIndex = any()) } } @@ -115,6 +116,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { responseCryptoCurrenciesFactory.createCurrencies( response = userTokensResponse, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } returns cryptoCurrencies.toList() @@ -122,6 +124,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { responseCryptoCurrenciesFactory.createCurrencies( response = updatedUserTokensResponse, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } returns updatedCryptoCurrencies.toList() @@ -144,6 +147,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { responseCryptoCurrenciesFactory.createCurrencies( response = userTokensResponse, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } @@ -162,6 +166,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { responseCryptoCurrenciesFactory.createCurrencies( response = updatedUserTokensResponse, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } } @@ -186,6 +191,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { responseCryptoCurrenciesFactory.createCurrencies( response = userTokensResponse, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } returns cryptoCurrencies.toList() @@ -208,6 +214,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { responseCryptoCurrenciesFactory.createCurrencies( response = userTokensResponse, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } @@ -252,6 +259,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { responseCryptoCurrenciesFactory.createCurrencies( response = userTokensResponse, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } returns cryptoCurrencies.toList() @@ -283,6 +291,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { responseCryptoCurrenciesFactory.createCurrencies( response = userTokensResponse, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } } @@ -307,7 +316,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { } verify(inverse = true) { - responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any()) + responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any(), accountIndex = any()) } } @@ -335,7 +344,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest { verify(inverse = true) { userTokensResponseStore.get(any()) - responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any()) + responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any(), accountIndex = any()) } } diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt index 626f998bfb..2d7a8855d7 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt @@ -144,6 +144,9 @@ internal class DefaultCardCryptoCurrencyFactory( ?: return emptyMap() response.accounts.flatMapTo(hashSetOf()) { accountDTO -> + val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull() + ?: return@flatMapTo emptySet() + responseCryptoCurrenciesFactory.createCurrencies( tokens = accountDTO.tokens.orEmpty().filter { token -> networks.any { @@ -151,7 +154,7 @@ internal class DefaultCardCryptoCurrencyFactory( } }, userWallet = userWallet, - accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull(), + accountIndex = accountIndex, ) } } else { @@ -163,6 +166,7 @@ internal class DefaultCardCryptoCurrencyFactory( networks.any { it.backendId == token.networkId && it.derivationPath.value == token.derivationPath } }, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } .groupBy(CryptoCurrency::network) @@ -181,10 +185,13 @@ internal class DefaultCardCryptoCurrencyFactory( ?: return emptyMap() response.accounts.flatMapTo(hashSetOf()) { accountDTO -> + val accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull() + ?: return@flatMapTo emptySet() + responseCryptoCurrenciesFactory.createCurrencies( tokens = accountDTO.tokens.orEmpty().filter { token -> token.networkId in networkIds }, userWallet = userWallet, - accountIndex = DerivationIndex(accountDTO.derivationIndex).getOrNull(), + accountIndex = accountIndex, ) } } else { @@ -194,6 +201,7 @@ internal class DefaultCardCryptoCurrencyFactory( responseCryptoCurrenciesFactory.createCurrencies( tokens = response.tokens.filter { token -> token.networkId in networkIds }, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } .groupBy { it.network.id.rawId } diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt index 66faea0a74..eccf6e4952 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt @@ -22,7 +22,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor( fun createCurrencies( response: UserTokensResponse, userWallet: UserWallet, - accountIndex: DerivationIndex? = null, + accountIndex: DerivationIndex, ): List { return createCurrencies(tokens = response.tokens, userWallet = userWallet, accountIndex = accountIndex) } @@ -30,7 +30,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor( fun createCurrencies( tokens: List, userWallet: UserWallet, - accountIndex: DerivationIndex? = null, + accountIndex: DerivationIndex, ): List { return tokens .asSequence() @@ -42,7 +42,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor( fun createCurrency( responseToken: UserTokensResponse.Token, userWallet: UserWallet, - accountIndex: DerivationIndex? = null, + accountIndex: DerivationIndex, ): CryptoCurrency? { var blockchain = Blockchain.fromNetworkId(responseToken.networkId) if (blockchain == null || blockchain == Blockchain.Unknown) { @@ -103,7 +103,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor( blockchain: Blockchain, responseToken: UserTokensResponse.Token, network: Network, - ): CryptoCurrency.Coin? { + ): CryptoCurrency.Coin { return CryptoCurrency.Coin( id = getCoinId(network, blockchain.toCoinId()), network = network, @@ -127,7 +127,7 @@ class ResponseCryptoCurrenciesFactory @Inject constructor( } } - private fun createToken(blockchain: Blockchain, sdkToken: Token, network: Network): CryptoCurrency.Token? { + private fun createToken(blockchain: Blockchain, sdkToken: Token, network: Network): CryptoCurrency.Token { val id = getTokenId(network, sdkToken) return CryptoCurrency.Token( diff --git a/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt index 4ffb1c9210..265418339e 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt @@ -74,6 +74,7 @@ class NetworkFactory @Inject constructor( blockchain = blockchain, excludedBlockchains = excludedBlockchains, ), + shouldCheckChia = false, ) } @@ -128,9 +129,10 @@ class NetworkFactory @Inject constructor( derivationPath: Network.DerivationPath, canHandleTokens: Boolean, accountIndex: DerivationIndex? = null, + shouldCheckChia: Boolean = true, ): Network? { if (!blockchain.isBlockchainSupported()) return null - if (blockchain == Blockchain.Chia && accountIndex != DerivationIndex.Main) return null + if (shouldCheckChia && blockchain == Blockchain.Chia && accountIndex != DerivationIndex.Main) return null return runCatching { Network( diff --git a/data/feedback/detekt-baseline-debug.xml b/data/feedback/detekt-baseline-debug.xml index 596e50e191..d10388bff4 100644 --- a/data/feedback/detekt-baseline-debug.xml +++ b/data/feedback/detekt-baseline-debug.xml @@ -3,7 +3,5 @@ BooleanPropertyNaming:DefaultFeedbackRepository.kt$DefaultFeedbackRepository$private val useNewUserWalletsRepository: Boolean - MultilineLambdaItParameter:DefaultFeedbackRepository.kt$DefaultFeedbackRepository${ it.toMutableMap().apply { put(userWallet.walletId, error) } } - UseOrEmpty:BlockchainInfoConverter.kt$BlockchainInfoConverter$value.wallet.publicKey.derivationPath?.rawPath ?: "" diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt index 10bb463509..dcadace780 100644 --- a/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt +++ b/data/feedback/src/main/java/com/tangem/data/feedback/DefaultFeedbackRepository.kt @@ -97,9 +97,9 @@ internal class DefaultFeedbackRepository( override fun saveBlockchainErrorInfo(error: BlockchainErrorInfo) { val userWallet = getSelectedWalletUseCase.sync().getOrNull() ?: error("UserWallet is not selected") - blockchainsErrors.update { - it.toMutableMap().apply { - put(userWallet.walletId, error) + blockchainsErrors.update { map -> + map.toMutableMap().apply { + this[userWallet.walletId] = error } } } diff --git a/data/feedback/src/main/java/com/tangem/data/feedback/converters/BlockchainInfoConverter.kt b/data/feedback/src/main/java/com/tangem/data/feedback/converters/BlockchainInfoConverter.kt index 686cec71dd..8c430ced24 100644 --- a/data/feedback/src/main/java/com/tangem/data/feedback/converters/BlockchainInfoConverter.kt +++ b/data/feedback/src/main/java/com/tangem/data/feedback/converters/BlockchainInfoConverter.kt @@ -16,9 +16,11 @@ import com.tangem.domain.feedback.models.BlockchainInfo.Addresses as BlockchainA internal object BlockchainInfoConverter : Converter { override fun convert(value: WalletManager): BlockchainInfo { + val derivationPath = value.wallet.publicKey.derivationPath + return BlockchainInfo( blockchain = value.wallet.blockchain.fullName, - derivationPath = value.wallet.publicKey.derivationPath?.rawPath ?: "", + derivationPath = derivationPath?.rawPath.orEmpty(), outputsCount = value.outputsCount?.toString(), host = value.currentHost, addresses = value.wallet.mapAddresses(Address::value), diff --git a/data/manage-tokens/detekt-baseline-debug.xml b/data/manage-tokens/detekt-baseline-debug.xml index d384bf15ce..a9b6d430bb 100644 --- a/data/manage-tokens/detekt-baseline-debug.xml +++ b/data/manage-tokens/detekt-baseline-debug.xml @@ -3,15 +3,7 @@ MultilineLambdaItParameter:DefaultCustomTokensRepository.kt$DefaultCustomTokensRepository${ // TODO: refactor https://tangem.atlassian.net/browse/AND-10006\ if (it.isTestnet() || it in excludedBlockchains || it in hotWalletExcludedBlockchains) { return@mapNotNull null } networkFactory.create( blockchain = it, extraDerivationPath = null, userWallet = userWallet, ) } - MultilineLambdaItParameter:DefaultManageTokensRepository.kt$DefaultManageTokensRepository${ it.contractAddress != null && it.networkId == network.backendId && it.derivationPath == network.derivationPath.value } MultilineLambdaItParameter:ManageTokensUpdateFetcher.kt$ManageTokensUpdateFetcher${ if (it.key == toUpdate[index].key) { Batch(it.key, updatedItems) } else { null } } - NamedArguments:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$create(coinsResponse, tokensResponse, userWallet, accountIndex) - NamedArguments:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$createToken(coin, tokensResponse, coinsResponse.imageHost, userWallet, accountIndex) - NamedArguments:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$findAddedInNetworks(coinResponse.id, tokensResponse, userWallet, accountIndex) - NamedArguments:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$findAddedInNetworks(testnetToken.id, tokensResponse, userWallet, accountIndex) - SuspendFunSwallowedCancellation:DefaultManageTokensRepository.kt$DefaultManageTokensRepository$runCatching UnsafeCallOnNullableType:DefaultCustomTokensRepository.kt$DefaultCustomTokensRepository$coinNetwork.decimalCount!! - UseOrEmpty:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$testnetToken.networks?.mapNotNull { network -> createSource( networkId = network.id, contractAddress = network.address, decimals = network.decimalCount, userWallet = userWallet, accountIndex = accountIndex, ) } ?: emptyList() - UseOrEmpty:ManagedCryptoCurrencyFactory.kt$ManagedCryptoCurrencyFactory$tokensResponse ?.let { createCustomTokens(it, userWallet, accountIndex) } ?: emptyList() diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index ed2887e114..84eea67a90 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -41,6 +41,7 @@ import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher.Request import com.tangem.pagination.toBatchFlow import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runSuspendCatching @Suppress("LongParameterList", "LargeClass") internal class DefaultManageTokensRepository( @@ -176,7 +177,7 @@ internal class DefaultManageTokensRepository( val shouldFetch = loadUserTokensFromRemote && userWallet != null val fetchedResponse = if (shouldFetch) { - runCatching { walletAccountsFetcher.fetch(userWalletId = userWallet.walletId) }.getOrNull() + runSuspendCatching { walletAccountsFetcher.fetch(userWalletId = userWallet.walletId) }.getOrNull() } else { null } @@ -214,19 +215,22 @@ internal class DefaultManageTokensRepository( userWallet != null && query == null + val accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull() + ?: return emptyList() + val items = if (isCreateWithCustom) { managedCryptoCurrencyFactory.createWithCustomTokens( coinsResponse = updatedCoinsResponse, tokensResponse = tokensResponse, userWallet = userWallet, - accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull(), + accountIndex = accountIndex, ) } else { managedCryptoCurrencyFactory.create( coinsResponse = updatedCoinsResponse, tokensResponse = tokensResponse, userWallet = userWallet, - accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull(), + accountIndex = accountIndex, ) } @@ -262,14 +266,14 @@ internal class DefaultManageTokensRepository( coinsResponse = updatedCoinsResponse, tokensResponse = tokensResponse, userWallet = userWallet, - accountIndex = null, + accountIndex = DerivationIndex.Main, ) } else { managedCryptoCurrencyFactory.create( coinsResponse = updatedCoinsResponse, tokensResponse = tokensResponse, userWallet = userWallet, - accountIndex = null, + accountIndex = DerivationIndex.Main, ) } } @@ -307,6 +311,13 @@ internal class DefaultManageTokensRepository( ) } + val accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull() + ?: return BatchFetchResult.Success( + data = emptyList(), + empty = true, + last = true, + ) + val items = managedCryptoCurrencyFactory.createTestnetWithCustomTokens( testnetTokensConfig = if (!searchText.isNullOrBlank()) { testnetTokensConfig.copy( @@ -320,7 +331,7 @@ internal class DefaultManageTokensRepository( }, tokensResponse = tokensResponse, userWallet = userWallet, - accountIndex = accountDTO?.derivationIndex?.let(DerivationIndex::invoke)?.getOrNull(), + accountIndex = accountIndex, ) return BatchFetchResult.Success( @@ -350,7 +361,7 @@ internal class DefaultManageTokensRepository( }, tokensResponse = getSavedUserTokensResponseSync(userWallet.walletId), userWallet = userWallet, - accountIndex = null, + accountIndex = DerivationIndex.Main, ) return BatchFetchResult.Success( @@ -392,10 +403,10 @@ internal class DefaultManageTokensRepository( ) val newTokensList = storedTokens.tokens + addedTokens - removedTokens.toSet() - return newTokensList.any { - it.contractAddress != null && - it.networkId == network.backendId && - it.derivationPath == network.derivationPath.value + return newTokensList.any { token -> + token.contractAddress != null && + token.networkId == network.backendId && + token.derivationPath == network.derivationPath.value } } diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt index 5ecaec838c..49ec7c78de 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManagedCryptoCurrencyFactory.kt @@ -35,10 +35,16 @@ internal class ManagedCryptoCurrencyFactory( coinsResponse: CoinsResponse, tokensResponse: UserTokensResponse?, userWallet: UserWallet?, - accountIndex: DerivationIndex?, + accountIndex: DerivationIndex, ): List { return coinsResponse.coins.mapNotNull { coin -> - createToken(coin, tokensResponse, coinsResponse.imageHost, userWallet, accountIndex) + createToken( + coinResponse = coin, + tokensResponse = tokensResponse, + imageHost = coinsResponse.imageHost, + userWallet = userWallet, + accountIndex = accountIndex, + ) } } @@ -46,10 +52,15 @@ internal class ManagedCryptoCurrencyFactory( coinsResponse: CoinsResponse, tokensResponse: UserTokensResponse, userWallet: UserWallet, - accountIndex: DerivationIndex?, + accountIndex: DerivationIndex, ): List { val customTokens = createCustomTokens(tokensResponse, userWallet, accountIndex) - val tokens = create(coinsResponse, tokensResponse, userWallet, accountIndex) + val tokens = create( + coinsResponse = coinsResponse, + tokensResponse = tokensResponse, + userWallet = userWallet, + accountIndex = accountIndex, + ) return customTokens + tokens } @@ -58,11 +69,11 @@ internal class ManagedCryptoCurrencyFactory( testnetTokensConfig: TestnetTokensConfig, tokensResponse: UserTokensResponse?, userWallet: UserWallet, - accountIndex: DerivationIndex?, + accountIndex: DerivationIndex, ): List { val customTokens = tokensResponse ?.let { createCustomTokens(it, userWallet, accountIndex) } - ?: emptyList() + .orEmpty() val testnetTokens = testnetTokensConfig.tokens.map { testnetToken -> ManagedCryptoCurrency.Token( id = ManagedCryptoCurrency.ID(testnetToken.id), @@ -77,8 +88,13 @@ internal class ManagedCryptoCurrencyFactory( userWallet = userWallet, accountIndex = accountIndex, ) - } ?: emptyList(), - addedIn = findAddedInNetworks(testnetToken.id, tokensResponse, userWallet, accountIndex), + }.orEmpty(), + addedIn = findAddedInNetworks( + currencyId = testnetToken.id, + tokensResponse = tokensResponse, + userWallet = userWallet, + accountIndex = accountIndex, + ), ) } @@ -88,7 +104,7 @@ internal class ManagedCryptoCurrencyFactory( private fun createCustomTokens( tokensResponse: UserTokensResponse, userWallet: UserWallet, - accountIndex: DerivationIndex?, + accountIndex: DerivationIndex, ): List = tokensResponse.tokens .mapNotNull { token -> maybeCreateCustomToken(token, userWallet, accountIndex) @@ -97,7 +113,7 @@ internal class ManagedCryptoCurrencyFactory( private fun maybeCreateCustomToken( token: UserTokensResponse.Token, userWallet: UserWallet, - accountIndex: DerivationIndex?, + accountIndex: DerivationIndex, ): ManagedCryptoCurrency? { val blockchain = Blockchain.fromNetworkId(token.networkId) ?.takeUnless { it in excludedBlockchains } @@ -161,7 +177,7 @@ internal class ManagedCryptoCurrencyFactory( tokensResponse: UserTokensResponse?, imageHost: String?, userWallet: UserWallet?, - accountIndex: DerivationIndex?, + accountIndex: DerivationIndex, ): ManagedCryptoCurrency? { if (coinResponse.networks.isEmpty() || !coinResponse.active) return null @@ -184,7 +200,12 @@ internal class ManagedCryptoCurrencyFactory( symbol = coinResponse.symbol, iconUrl = getIconUrl(coinResponse.id, imageHost), availableNetworks = availableNetworks, - addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, userWallet, accountIndex), + addedIn = findAddedInNetworks( + currencyId = coinResponse.id, + tokensResponse = tokensResponse, + userWallet = userWallet, + accountIndex = accountIndex, + ), ) } @@ -194,7 +215,7 @@ internal class ManagedCryptoCurrencyFactory( decimals: Int?, userWallet: UserWallet?, extraDerivationPath: String? = null, - accountIndex: DerivationIndex?, + accountIndex: DerivationIndex, ): SourceNetwork? { val blockchain = Blockchain.fromNetworkId(networkId) ?.takeUnless { it in excludedBlockchains } @@ -235,7 +256,7 @@ internal class ManagedCryptoCurrencyFactory( currencyId: String, tokensResponse: UserTokensResponse?, userWallet: UserWallet?, - accountIndex: DerivationIndex?, + accountIndex: DerivationIndex, ): Set { if (tokensResponse == null) return emptySet() diff --git a/data/promo/detekt-baseline-debug.xml b/data/promo/detekt-baseline-debug.xml index 0f6f378450..7ac3d42cb4 100644 --- a/data/promo/detekt-baseline-debug.xml +++ b/data/promo/detekt-baseline-debug.xml @@ -2,8 +2,6 @@ - NullableBooleanCheck:DefaultPromoRepository.kt$DefaultPromoRepository$getSepaPromoBanner()?.isActive ?: false - NullableBooleanCheck:DefaultPromoRepository.kt$DefaultPromoRepository$getVisaPromoBanner()?.isActive ?: false SuspendFunSwallowedCancellation:DefaultPromoRepository.kt$DefaultPromoRepository$runCatching SuspendFunWithFlowReturnType:DefaultPromoRepository.kt$DefaultPromoRepository$suspend 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 86c501406d..d6041cb705 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 @@ -58,6 +58,11 @@ internal class DefaultPromoRepository( PromoId.BlackFriday -> { val isActive = getBlackFridayPromoBanner()?.isActive == true + isActive && shouldShow + } + PromoId.OnePlusOne -> { + val isActive = getOnePlusOnePromoBanner()?.isActive == true + isActive && shouldShow } } @@ -70,6 +75,7 @@ internal class DefaultPromoRepository( PromoId.Sepa -> flowOf(false) PromoId.VisaPresale -> flowOf(false) PromoId.BlackFriday -> flowOf(false) + PromoId.OnePlusOne -> flowOf(false) } } @@ -161,10 +167,19 @@ 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 ONE_PLUS_ONE_NAME = "one-plus-one" const val STORIES_LOAD_DELAY = 1000L } } \ No newline at end of file diff --git a/data/swap/build.gradle.kts b/data/swap/build.gradle.kts index 78b5b851c7..a7ea5bd55c 100644 --- a/data/swap/build.gradle.kts +++ b/data/swap/build.gradle.kts @@ -44,6 +44,7 @@ dependencies { /** Libs */ implementation(projects.libs.blockchainSdk) + implementation(projects.libs.crypto) /** Other */ implementation(deps.androidx.datastore) diff --git a/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionConverter.kt b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionConverter.kt index c07926cd50..8895c05ffd 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionConverter.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionConverter.kt @@ -1,12 +1,16 @@ package com.tangem.data.swap.converter.transaction +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory import com.tangem.data.swap.models.SwapStatusDTO import com.tangem.data.swap.models.SwapTransactionDTO import com.tangem.data.swap.models.SwapTxTypeDTO +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.swap.models.SwapTransactionModel import com.tangem.domain.swap.models.SwapTxType +import com.tangem.lib.crypto.derivation.AccountNodeRecognizer import com.tangem.utils.converter.TwoWayConverter internal class SavedSwapTransactionConverter( @@ -48,9 +52,23 @@ internal class SavedSwapTransactionConverter( ): SwapTransactionModel { val status = txStatuses[value.txId] val refundCurrency = status?.refundTokensResponse?.let { id -> + val blockchain = Blockchain.fromNetworkId(id.networkId) ?: return@let null + val derivationPath = id.derivationPath ?: return@let null + + val accountIndex = if (blockchain == Blockchain.Chia) { + DerivationIndex.Main + } else { + val recognizer = AccountNodeRecognizer(blockchain = blockchain) + val index = recognizer.recognize(derivationPathValue = derivationPath)?.toInt() + ?: return@let null + + DerivationIndex(index).getOrNull() ?: return@let null + } + responseCryptoCurrenciesFactory.createCurrency( responseToken = id, userWallet = userWallet, + accountIndex = accountIndex, ) } val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency) diff --git a/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionListConverter.kt b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionListConverter.kt index 9688b3086b..051b7d9a47 100644 --- a/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionListConverter.kt +++ b/data/swap/src/main/java/com/tangem/data/swap/converter/transaction/SavedSwapTransactionListConverter.kt @@ -72,7 +72,11 @@ internal class SavedSwapTransactionListConverter( return SwapTransactionListModel( transactions = value.transactions.map { tx -> - savedSwapTransactionConverter.convertBack(tx, userWallet, txStatuses) + savedSwapTransactionConverter.convertBack( + value = tx, + userWallet = userWallet, + txStatuses = txStatuses, + ) }, userWalletId = value.userWalletId, fromCryptoCurrencyId = value.fromCryptoCurrencyId, diff --git a/data/tokens/detekt-baseline-debug.xml b/data/tokens/detekt-baseline-debug.xml index 8be9ea8bd3..bc443d0652 100644 --- a/data/tokens/detekt-baseline-debug.xml +++ b/data/tokens/detekt-baseline-debug.xml @@ -3,11 +3,8 @@ MultilineLambdaItParameter:CustomTokensMerger.kt$CustomTokensMerger${ Timber.e(it, "Unable to fetch token:\n$token") null } - MultilineLambdaItParameter:DefaultCurrenciesRepository.kt$DefaultCurrenciesRepository${ it.networkId == blockchainNetworkId && compareIdWithMigrations(it, coinId) && it.derivationPath == derivationPath.value } NullableToStringCall:AccountListCryptoCurrenciesFetcher.kt$AccountListCryptoCurrenciesFetcher$${this::class.simpleName} NullableToStringCall:DefaultMultiWalletCryptoCurrenciesFetcher.kt$DefaultMultiWalletCryptoCurrenciesFetcher$${this::class.simpleName} - SuspendFunSwallowedCancellation:DefaultCurrenciesRepository.kt$DefaultCurrenciesRepository$runCatching - SuspendFunWithFlowReturnType:DefaultCurrenciesRepository.kt$DefaultCurrenciesRepository$suspend UseOrEmpty:DefaultYieldSupplyWarningsViewedRepository.kt$DefaultYieldSupplyWarningsViewedRepository$appPreferencesStore.getObjectSet<String>(PreferencesKeys.YIELD_SUPPLY_WARNINGS_STATES_KEY).firstOrNull() ?: emptySet() diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index e2988f92ce..ffb98afb6c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -21,6 +21,7 @@ import com.tangem.domain.core.error.DataError import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.express.ExpressServiceFetcher import com.tangem.domain.express.models.ExpressAsset +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.Network @@ -31,6 +32,7 @@ import com.tangem.domain.tokens.model.FeePaidCurrency import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runSuspendCatching import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import timber.log.Timber @@ -301,8 +303,9 @@ internal class DefaultCurrenciesRepository( ) responseCryptoCurrenciesFactory.createCurrencies( - storedTokens, + response = storedTokens, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } @@ -357,15 +360,16 @@ internal class DefaultCurrenciesRepository( val coinId = blockchain.toCoinId() val storedCoin = storedTokens.tokens - .find { - it.networkId == blockchainNetworkId && - compareIdWithMigrations(it, coinId) && - it.derivationPath == derivationPath.value + .find { token -> + token.networkId == blockchainNetworkId && + compareIdWithMigrations(token, coinId) && + token.derivationPath == derivationPath.value } ?: error("Coin in this network $networkId not found") val coin = responseCryptoCurrenciesFactory.createCurrency( responseToken = storedCoin, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) coin as? CryptoCurrency.Coin ?: error("Unable to create currency") @@ -525,6 +529,7 @@ internal class DefaultCurrenciesRepository( } } + @Suppress("SuspendFunWithFlowReturnType") private suspend fun getCurrenciesForWallet( userWallet: UserWallet, currencyRawId: CryptoCurrency.RawID, @@ -539,6 +544,7 @@ internal class DefaultCurrenciesRepository( responseCryptoCurrenciesFactory.createCurrencies( response = storedTokens.copy(tokens = filterResponse), userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } } @@ -570,7 +576,7 @@ internal class DefaultCurrenciesRepository( } override suspend fun syncTokens(userWalletId: UserWalletId) { - runCatching { + runSuspendCatching { val savedCurrencies = requireNotNull( value = getSavedUserTokensResponseSync(key = userWalletId), lazyMessage = { "Saved tokens empty. Can not perform add currencies action" }, @@ -591,6 +597,7 @@ internal class DefaultCurrenciesRepository( responseCryptoCurrenciesFactory.createCurrencies( response = storedTokens, userWallet = userWallet, + accountIndex = DerivationIndex.Main, ) } } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index 8c77f51a78..de056fb431 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -155,9 +155,10 @@ internal class DefaultCurrencyChecksRepository( override suspend fun getRentExemptionError( userWalletId: UserWalletId, - currencyStatus: CryptoCurrencyStatus, + currencyStatus: CryptoCurrencyStatus?, balanceAfterTransaction: BigDecimal, ): CryptoCurrencyWarning.Rent? { + if (currencyStatus == null) return null val rentData = walletManagersFacade.getRentInfo(userWalletId, currencyStatus.currency.network) ?: return null return when { balanceAfterTransaction.isZero() -> null 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 new file mode 100644 index 0000000000..505383a099 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayCryptoCurrencyFactory.kt @@ -0,0 +1,60 @@ +package com.tangem.data.pay + +import arrow.core.Either +import arrow.core.Either.Companion.catch +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.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory +import timber.log.Timber +import javax.inject.Inject + +private const val TAG = "TangemPay: DefaultTangemPayCryptoCurrencyFactory" +/** + * Custom token parameters. Will be used only for F&F. + */ +private const val TOKEN_ID = "usd-coin" +private const val TOKEN_NAME = "USDC" +private const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359" +private const val TOKEN_DECIMALS = 6 + +internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor( + excludedBlockchains: ExcludedBlockchains, + private val errorConverter: TangemPayErrorConverter, +) : TangemPayCryptoCurrencyFactory { + + private val cryptoCurrencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) { + CryptoCurrencyFactory(excludedBlockchains) + } + private val networkFactory by lazy(mode = LazyThreadSafetyMode.NONE) { + NetworkFactory(excludedBlockchains) + } + + 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" } + val blockchain = requireNotNull(chain.blockchain) + val network = networkFactory.create( + blockchain = blockchain, + extraDerivationPath = null, + userWallet = userWallet, + ) + cryptoCurrencyFactory.createToken( + network = requireNotNull(network), + rawId = CryptoCurrency.RawID(TOKEN_ID), + name = TOKEN_NAME, + symbol = TOKEN_NAME, + contractAddress = TOKEN_CONTRACT_ADDRESS, + decimals = TOKEN_DECIMALS, + ) + }.mapLeft { exception -> + Timber.tag(TAG).e(exception) + errorConverter.convert(exception) + } + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPaySwapDataFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPaySwapDataFactory.kt deleted file mode 100644 index b7f0683742..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPaySwapDataFactory.kt +++ /dev/null @@ -1,87 +0,0 @@ -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.data.pay.util.TangemPayWalletsManager -import com.tangem.datasource.di.NetworkMoshi -import com.tangem.domain.models.ReceiveAddressModel -import com.tangem.domain.models.ReceiveAddressModel.NameService -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.pay.TangemPayTopUpData -import com.tangem.domain.pay.TangemPaySwapDataFactory -import timber.log.Timber -import java.math.BigDecimal -import javax.inject.Inject - -private const val TAG = "TangemPay: DefaultDataForTopUpFactory" -/** - * Custom token parameters. Will be used only for F&F. - */ -private const val TOKEN_ID = "usd-coin" -private const val TOKEN_NAME = "USDC" -private const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359" -private const val TOKEN_DECIMALS = 6 - -internal class DefaultTangemPaySwapDataFactory @Inject constructor( - @NetworkMoshi moshi: Moshi, - private val tangemPayWalletsManager: TangemPayWalletsManager, - excludedBlockchains: ExcludedBlockchains, -) : TangemPaySwapDataFactory { - - private val cryptoCurrencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) { - CryptoCurrencyFactory(excludedBlockchains) - } - private val networkFactory by lazy(mode = LazyThreadSafetyMode.NONE) { - NetworkFactory(excludedBlockchains) - } - private val errorConverter by lazy(mode = LazyThreadSafetyMode.NONE) { TangemPayErrorConverter(moshi) } - - private fun getCurrency(userWallet: UserWallet, chainId: Int): CryptoCurrency { - val chain = requireNotNull(Chain.entries.find { it.id == chainId }) { "Can not find chain with $chainId" } - val blockchain = requireNotNull(chain.blockchain) - val network = networkFactory.create( - blockchain = blockchain, - extraDerivationPath = null, - userWallet = userWallet, - ) - return cryptoCurrencyFactory.createToken( - network = requireNotNull(network), - rawId = CryptoCurrency.RawID(TOKEN_ID), - name = TOKEN_NAME, - symbol = TOKEN_NAME, - contractAddress = TOKEN_CONTRACT_ADDRESS, - decimals = TOKEN_DECIMALS, - ) - } - - override fun create( - depositAddress: String, - chainId: Int, - cryptoBalance: BigDecimal, - fiatBalance: BigDecimal, - ): Either { - return catch { - val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking() - val currency = getCurrency(wallet, chainId) - TangemPayTopUpData( - currency = currency, - walletId = wallet.walletId, - cryptoBalance = cryptoBalance, - fiatBalance = fiatBalance, - depositAddress = depositAddress, - receiveAddress = listOf(ReceiveAddressModel(nameService = NameService.Default, value = depositAddress)), - ) - }.mapLeft { exception -> - Timber.tag(TAG).e(exception) - errorConverter.convert(exception) - } - } -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index 49bc15d0db..bae2d97baf 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -1,10 +1,10 @@ package com.tangem.data.pay.di -import com.tangem.data.pay.DefaultTangemPaySwapDataFactory +import com.tangem.data.pay.DefaultTangemPayCryptoCurrencyFactory import com.tangem.data.pay.repository.* import com.tangem.data.pay.usecase.DefaultGetTangemPayCurrencyStatusUseCase import com.tangem.data.pay.usecase.DefaultTangemPayWithdrawUseCase -import com.tangem.domain.pay.TangemPaySwapDataFactory +import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory import com.tangem.domain.pay.repository.* import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase @@ -48,7 +48,9 @@ internal interface TangemPayDataModule { @Binds @Singleton - fun bindTangemPaySwapDataFactory(factory: DefaultTangemPaySwapDataFactory): TangemPaySwapDataFactory + fun bindTangemPayCryptoCurrencyFactory( + factory: DefaultTangemPayCryptoCurrencyFactory, + ): TangemPayCryptoCurrencyFactory @Binds @Singleton 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 21077cd428..1dafcfd572 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()) @@ -55,6 +60,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( fiatBalance = result.fiat.availableBalance, currencyCode = result.fiat.currency, cryptoBalance = result.crypto.balance, + availableForWithdrawal = result.availableForWithdrawal.amount, chainId = result.crypto.chainId, depositAddress = result.crypto.depositAddress, contractAddress = result.crypto.tokenContractAddress, @@ -63,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/DefaultTangemPaySwapRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt index d1d72250d3..042686b6e7 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt @@ -18,6 +18,7 @@ import com.tangem.domain.pay.repository.TangemPaySwapRepository import com.tangem.domain.visa.error.VisaApiError import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.features.hotwallet.HotWalletFeatureToggles +import com.tangem.utils.extensions.addHexPrefix import java.math.BigDecimal import java.math.RoundingMode import java.util.Currency @@ -66,7 +67,7 @@ internal class DefaultTangemPaySwapRepository @Inject constructor( recipientAddress = receiverAddress, adminSalt = result.salt, senderAddress = result.senderAddress, - adminSignature = signatureResult.signature, + adminSignature = signatureResult.signature.addHexPrefix(), ) tangemPayApi.withdraw(authHeader = authHeader, body = request) } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt index 243e008c99..021605d89a 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayTxHistoryRepository.kt @@ -21,7 +21,6 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import javax.inject.Inject private const val INITIAL_CURSOR = "initial_cursor_key" -private const val TAG = "TangemPay: TangemPayTxHistoryRepository:" internal class DefaultTangemPayTxHistoryRepository @Inject constructor( private val requestPerformer: TangemPayRequestPerformer, @@ -106,12 +105,14 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor( cursor: String?, pageSize: Int, ) { - requestPerformer.runWithErrorLogs(TAG) { - val result = requestPerformer.request(userWalletId) { authHeader -> - visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor) - }.result + requestPerformer.performRequest(userWalletId = userWalletId) { authHeader -> + visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor) + }.onLeft { + error(it.toString()) + }.onRight { response -> + val result = response.result val items = txHistoryItemConverter.convertList(result.transactions).filterNotNull() txHistoryItemsStore.store(key = customerWalletAddress, cursor = cursor ?: INITIAL_CURSOR, value = items) - }.onLeft { error(it.toString()) } + } } } \ No newline at end of file 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 f3aacb104e..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 @@ -21,11 +19,11 @@ import com.tangem.domain.visa.model.TangemPayAuthTokens import com.tangem.domain.visa.model.getAuthHeader import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import javax.inject.Singleton @@ -33,16 +31,15 @@ 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, private val tangemPayStorage: TangemPayStorage, ) { - private val customerWalletAddress = MutableStateFlow(null) + 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 { @@ -110,7 +107,7 @@ internal class TangemPayRequestPerformer @Inject constructor( } suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String { - val existingAddress = customerWalletAddress.value + val existingAddress = customerWalletAddresses[userWalletId] if (existingAddress != null) { return existingAddress } @@ -118,7 +115,7 @@ internal class TangemPayRequestPerformer @Inject constructor( userWalletId = userWalletId, ) ?: error("Can not find customer address") - customerWalletAddress.value = storedAddress + customerWalletAddresses[userWalletId] = storedAddress return storedAddress } 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/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayWalletsManager.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayWalletsManager.kt index b4c31a08cb..4423dddf32 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayWalletsManager.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayWalletsManager.kt @@ -15,12 +15,14 @@ class TangemPayWalletsManager @Inject constructor( private val hotWalletFeatureToggles: HotWalletFeatureToggles, ) { + @Deprecated("Don't use and put userWallet in features that need it") suspend fun getDefaultWalletForTangemPay(): UserWallet.Cold { val userWalletsFlow = if (useNewRepository()) repository.userWallets else manager.userWallets val userWallets = userWalletsFlow.filter { !it.isNullOrEmpty() }.first() return findColdWallet(userWallets) } + @Deprecated("Don't use and put userWallet in features that need it") fun getDefaultWalletForTangemPayBlocking(): UserWallet.Cold { val userWallets = if (useNewRepository()) repository.userWallets.value else manager.userWalletsSync return findColdWallet(userWallets) @@ -29,7 +31,8 @@ class TangemPayWalletsManager @Inject constructor( private fun useNewRepository(): Boolean = hotWalletFeatureToggles.isHotWalletEnabled private fun findColdWallet(userWallets: List?): UserWallet.Cold { - return userWallets?.find { it is UserWallet.Cold } as? UserWallet.Cold - ?: error("Cannot find cold user wallet") + return userWallets?.find { + it is UserWallet.Cold && it.isMultiCurrency + } as? UserWallet.Cold ?: error("Cannot find cold user wallet") } } \ No newline at end of file diff --git a/data/wallets/detekt-baseline-debug.xml b/data/wallets/detekt-baseline-debug.xml index 26f5591932..7ac40bc693 100644 --- a/data/wallets/detekt-baseline-debug.xml +++ b/data/wallets/detekt-baseline-debug.xml @@ -3,8 +3,6 @@ MultilineLambdaItParameter:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository${ networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, ) } - MultilineLambdaItParameter:DefaultDerivationsRepository.kt$DefaultDerivationsRepository${ userWallet.update(it.first) it.second } - MultilineLambdaItParameter:DefaultHotMapDerivationsRepository.kt$DefaultHotMapDerivationsRepository${ networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, ) } MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ AttemptsPersistentData( attempts = it[PreferencesKeys.getHotWalletUnlockAttemptsKey(id.attemptIdKey())] ?: 0, bootCount = it[PreferencesKeys.getHotWalletUnlockBootKey(id.attemptIdKey())] ?: 0, deadline = it[PreferencesKeys.getHotWalletUnlockDeadlineKey(id.attemptIdKey())] ?: 0L, ) } MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ it.remove(PreferencesKeys.getHotWalletUnlockAttemptsKey(authAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockAttemptsKey(noAuthAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockBootKey(authAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockBootKey(noAuthAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockDeadlineKey(authAttemptId.attemptIdKey())) it.remove(PreferencesKeys.getHotWalletUnlockDeadlineKey(noAuthAttemptId.attemptIdKey())) } MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ while (true) { emit(toState(id, it.attempts, it.deadline, it.bootCount)) val remaining = remainingSeconds(it.deadline, it.bootCount) if (remaining <= 0) break delay(timeMillis = 1000) } } @@ -16,9 +14,7 @@ SuspendFunSwallowedCancellation:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor$runCatching SuspendFunSwallowedCancellation:TangemHotWalletSigner.kt$TangemHotWalletSigner$runCatching UnnecessaryLet:MissedDerivationsFinder.kt$MissedDerivationsFinder$let(::findByNetworks) - UnusedImports:DefaultDerivationsRepository.kt$import com.tangem.common.map UseOrEmpty:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository$oldKeys[walletKey] ?: emptyMap() - UseOrEmpty:DefaultHotMapDerivationsRepository.kt$DefaultHotMapDerivationsRepository$oldKeys[walletKey] ?: emptyMap() VarCouldBeVal:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor$private var contextualUnlockHotWallet: ConcurrentHashMap<HotWalletId, UnlockHotWallet?> = ConcurrentHashMap() diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepository.kt index 862b90b035..3a3dcf27ac 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepository.kt @@ -2,16 +2,16 @@ package com.tangem.data.wallets.derivations import com.tangem.common.CompletionResult import com.tangem.common.extensions.ByteArrayKey -import com.tangem.common.map import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository -import com.tangem.domain.wallets.derivations.DerivationsRepository -import com.tangem.domain.wallets.derivations.HotMapDerivationsRepository +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository +import com.tangem.domain.wallets.derivations.DerivationsRepository +import com.tangem.domain.wallets.derivations.HotMapDerivationsRepository import com.tangem.domain.wallets.usecase.BackendId import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -29,11 +29,17 @@ internal class DefaultDerivationsRepository @Inject constructor( derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network)) } - override suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List) { + override suspend fun derivePublicKeysByNetworkIds( + userWalletId: UserWalletId, + networkIds: List, + accountIndex: DerivationIndex, + ) { val userWallet = userWalletsStore.getSyncStrict(userWalletId) when (userWallet) { is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds) - is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds) + is UserWallet.Hot -> { + hotDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds, accountIndex) + } }.also { userWallet.update(it) } @@ -57,9 +63,9 @@ internal class DefaultDerivationsRepository @Inject constructor( return when (userWallet) { is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeys(userWallet, derivations) is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeys(userWallet, derivations) - }.let { - userWallet.update(it.first) - it.second + }.let { publicKeysMapByUserWallet -> + userWallet.update(publicKeysMapByUserWallet.first) + publicKeysMapByUserWallet.second } } diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotMapDerivationsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotMapDerivationsRepository.kt index bdc3f7fdf2..987dd65cc5 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotMapDerivationsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotMapDerivationsRepository.kt @@ -7,6 +7,8 @@ import com.tangem.common.extensions.toMapKey import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.data.common.network.NetworkFactory import com.tangem.data.wallets.derivations.MissedDerivationsFinder +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet @@ -21,6 +23,7 @@ import timber.log.Timber import javax.inject.Inject internal class DefaultHotMapDerivationsRepository @Inject constructor( + private val userWalletsStore: UserWalletsStore, private val networkFactory: NetworkFactory, private val hotWalletAccessor: HotWalletAccessor, private val dispatchers: CoroutineDispatcherProvider, @@ -36,14 +39,16 @@ internal class DefaultHotMapDerivationsRepository @Inject constructor( override suspend fun derivePublicKeysByNetworkIds( userWallet: UserWallet.Hot, networkIds: List, + accountIndex: DerivationIndex, ): UserWallet.Hot { return derivePublicKeysByNetworks( userWallet = userWallet, - networks = networkIds.mapNotNull { + networks = networkIds.mapNotNull { networkRawId -> networkFactory.create( - blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, + blockchain = Blockchain.fromNetworkId(networkRawId.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, + accountIndex = accountIndex, ) }, ) @@ -82,10 +87,15 @@ internal class DefaultHotMapDerivationsRepository @Inject constructor( hotWalletId = userWallet.hotWalletId, request = request, ) + + // Get the updated user wallet from the store to ensure we have the latest data + // in case it was modified during the derive operation + val updatedUserWallet = userWalletsStore.getSyncStrict(userWallet.walletId) as UserWallet.Hot + val newKeys = result.responses.associate { ByteArrayKey(it.seedKey.publicKey) to ExtendedPublicKeysMap(it.publicKeys) } - return userWallet.updateWithNewKeys(newKeys) to newKeys + return updatedUserWallet.updateWithNewKeys(newKeys) to newKeys } override suspend fun hasMissedDerivations( @@ -131,7 +141,7 @@ internal class DefaultHotMapDerivationsRepository @Inject constructor( ): Map { return (oldKeys.keys + newKeys.keys).toSet() .associateWith { walletKey -> - val oldDerivations = ExtendedPublicKeysMap(oldKeys[walletKey] ?: emptyMap()) + val oldDerivations = ExtendedPublicKeysMap(oldKeys[walletKey].orEmpty()) val newDerivations = newKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap()) ExtendedPublicKeysMap(oldDerivations + newDerivations) diff --git a/detekt_baseline_report.txt b/detekt_baseline_report.txt index 3a9f9a9044..85ca6a00e5 100644 --- a/detekt_baseline_report.txt +++ b/detekt_baseline_report.txt @@ -1,7 +1,7 @@ ========================================== Detekt Baseline Updater & Issue Counter ========================================== -Date: 2025-11-27 14:13:20 +Date: 2025-12-02 13:19:49 Step 1: Running detekt to check for new issues... @@ -17,13 +17,13 @@ Counting issues in baseline files... ========================================== Summary: - Total Issues: 1593 - Modules with Issues: 68 + Total Issues: 1435 + Modules with Issues: 62 Average Issues per Module: 23 Progress: - Fixed: 209 out of 1802 (11%) - Remaining: 1593 + Fixed: 367 out of 1802 (20%) + Remaining: 1435 ========================================== All Modules with Issues (sorted by count) @@ -31,72 +31,66 @@ All Modules with Issues (sorted by count) Module Issues ──────────────────────────────────────────────────────────────── -features/wallet/impl 169 -features/markets/impl 155 -features/onboarding-v2/impl 131 -features/send-v2/impl 80 -features/swap/impl 73 -features/hot-wallet/impl 57 -features/staking/impl 56 +features/wallet/impl 148 +features/markets/impl 148 +features/onboarding-v2/impl 130 +features/swap/impl 67 +features/send-v2/impl 57 data/wallet-connect 55 -features/swap-v2/impl 53 -features/walletconnect/impl 51 -features/tokendetails/impl 49 +features/hot-wallet/impl 53 +features/tokendetails/impl 48 +features/staking/impl 48 +features/walletconnect/impl 47 features/manage-tokens/impl 45 -domain/wallets 39 -features/nft/impl 36 +features/swap-v2/impl 40 +domain/wallets 37 +features/nft/impl 34 features/tester/impl 31 domain/tokens 28 -features/swap/domain 27 core/ui 27 -features/yield-supply/impl 26 common/ui 26 -data/visa 23 -features/tangempay/details/impl 22 +features/swap/domain 25 +data/visa 22 +features/yield-supply/impl 21 +features/tangempay/details/impl 21 data/nft 20 -data/wallets 18 features/swap/data 15 +data/wallets 14 data/swap 13 features/token-recieve/impl 11 features/qr-scanning/impl 11 domain/account/status 11 data/onramp 11 -data/manage-tokens 11 core/datasource 11 -features/referral/impl 10 -features/details/impl 10 data/markets 10 +features/details/impl 9 domain/staking 9 data/yield-supply 9 data/networks 9 -features/welcome/impl 8 -features/home/impl 8 +features/referral/impl 8 domain/transaction 8 libs/tangem-sdk-api 7 -features/onramp/impl 7 data/txhistory 7 -data/tokens 7 -data/account 7 -features/send-v2/api 6 -domain/markets 6 +features/welcome/impl 6 data/wallet-manager 6 libs/visa 5 +features/send-v2/api 5 +features/home/impl 5 +domain/markets 5 domain/legacy 5 data/transaction 5 +data/account 5 features/referral/domain 4 features/biometry/impl 4 -features/account/impl 4 -features/account/api 4 -data/promo 4 -core/config-toggles 4 -features/wallet-settings/impl 3 +data/tokens 4 features/txhistory/impl 3 features/tangempay/onboarding/impl 3 features/create-wallet-start/impl 3 domain/manage-tokens 3 -domain/demo/models 3 -domain/card 3 -data/feedback 3 +data/manage-tokens 3 common/routing 3 -features/yield-supply/api 2 +features/account/api 2 +data/promo 2 +core/config-toggles 2 +data/feedback 1 ──────────────────────────────────────────────────────────────── \ No newline at end of file diff --git a/domain/card/detekt-baseline-debug.xml b/domain/card/detekt-baseline-debug.xml deleted file mode 100644 index 277a01c1fe..0000000000 --- a/domain/card/detekt-baseline-debug.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - NamedArguments:TangemCardTypesResolver.kt$TangemCardTypesResolver$Token( cardToken.name, cardToken.symbol, cardToken.contractAddress, cardToken.decimals, ) - PropertyUsedBeforeDeclaration:TapWorkarounds.kt$TapWorkarounds$excludedBatches - PropertyUsedBeforeDeclaration:TapWorkarounds.kt$TapWorkarounds$excludedIssuers - - diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/TangemCardTypesResolver.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/TangemCardTypesResolver.kt index db4b0973cb..3e6943f724 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/TangemCardTypesResolver.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/TangemCardTypesResolver.kt @@ -36,15 +36,15 @@ internal class TangemCardTypesResolver( override fun isTangemWallet(): Boolean { return card.settings.isBackupAllowed && card.settings.isHDWalletAllowed && - card.firmwareVersion >= FirmwareVersion.Companion.MultiWalletAvailable + card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable } override fun isShibaWallet(): Boolean { - return card.firmwareVersion.compareTo(FirmwareVersion.Companion.KeysImportAvailable) == 0 + return card.firmwareVersion.compareTo(FirmwareVersion.KeysImportAvailable) == 0 } override fun isWhiteWallet(): Boolean { - return walletData == null && card.firmwareVersion <= FirmwareVersion.Companion.HDWalletAvailable + return walletData == null && card.firmwareVersion <= FirmwareVersion.HDWalletAvailable } override fun isWallet2(): Boolean = card.isWallet2 @@ -73,7 +73,7 @@ internal class TangemCardTypesResolver( (multiWalletAvailable() || card.wallets.firstOrNull()?.curve == EllipticCurve.Secp256k1) } - private fun multiWalletAvailable() = card.firmwareVersion >= FirmwareVersion.Companion.MultiWalletAvailable + private fun multiWalletAvailable() = card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable override fun getBlockchain(): Blockchain { return when (productType) { @@ -93,12 +93,15 @@ internal class TangemCardTypesResolver( override fun getPrimaryToken(): Token? { val cardToken = walletData?.token ?: return null - return Token( - cardToken.name, - cardToken.symbol, - cardToken.contractAddress, - cardToken.decimals, - ) + + return with(cardToken) { + Token( + name = name, + symbol = symbol, + contractAddress = contractAddress, + decimals = decimals, + ) + } } override fun isReleaseFirmwareType(): Boolean = card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/common/TapWorkarounds.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/common/TapWorkarounds.kt index 550ddd0749..f012a0bfaf 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/common/TapWorkarounds.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/common/TapWorkarounds.kt @@ -38,6 +38,7 @@ object TapWorkarounds { val CardDTO.hasOldStyleDerivation: Boolean get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95" + @Suppress("PropertyUsedBeforeDeclaration") val CardDTO.isExcluded: Boolean get() { val isBatchExcluded = excludedBatches.contains(batchId) diff --git a/domain/demo/models/detekt-baseline-debug.xml b/domain/demo/models/detekt-baseline-debug.xml deleted file mode 100644 index 638c5c1870..0000000000 --- a/domain/demo/models/detekt-baseline-debug.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - PropertyUsedBeforeDeclaration:DemoConfig.kt$DemoConfig$debugTestDemoCardIds - PropertyUsedBeforeDeclaration:DemoConfig.kt$DemoConfig$releaseDemoCardIds - PropertyUsedBeforeDeclaration:DemoConfig.kt$DemoConfig$testDemoCardIds - - diff --git a/domain/demo/models/src/main/kotlin/com/tangem/domain/demo/models/DemoConfig.kt b/domain/demo/models/src/main/kotlin/com/tangem/domain/demo/models/DemoConfig.kt index 0ec194c730..5ed3b8e893 100644 --- a/domain/demo/models/src/main/kotlin/com/tangem/domain/demo/models/DemoConfig.kt +++ b/domain/demo/models/src/main/kotlin/com/tangem/domain/demo/models/DemoConfig.kt @@ -4,7 +4,7 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import java.math.BigDecimal -@Suppress("LargeClass") +@Suppress("LargeClass", "ClassOrdering", "PropertyUsedBeforeDeclaration") object DemoConfig { /** @@ -65,7 +65,6 @@ object DemoConfig { return (releaseDemoCardIds + testDemoCardIds).distinct() } - @Suppress("ClassOrdering") private val releaseDemoCardIds = mutableListOf( // === Not from the Google Sheet table === "AC01000000041225", @@ -449,7 +448,6 @@ object DemoConfig { "AF10100000000084", ) - @Suppress("ClassOrdering") private val testDemoCardIds = listOf( "FB20000000000186", // Note ETH "FB10000000000196", // Note BTC @@ -457,6 +455,5 @@ object DemoConfig { "FB04000000000152", // Wallet 2 ) - @Suppress("ClassOrdering") private val debugTestDemoCardIds = emptyList() } \ No newline at end of file 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 5786ee9dac..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, @@ -78,5 +79,7 @@ sealed interface FeedbackEmailType { val providerName: String, val txId: String, ) : Visa() + + data class FeatureIsBeta(override val walletMetaInfo: WalletMetaInfo) : Visa() } } \ No newline at end of file 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 fac64d171a..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) { @@ -49,6 +50,10 @@ internal class FeedbackDataBuilder { builder.appendKeyValue("Total saved wallets", userWalletsInfo.totalUserWallets.toString()) } + fun addUserWalletId(userWalletId: String) { + builder.appendKeyValue("User Wallet ID", userWalletId) + } + fun addUserWalletMetaInfo(walletMetaInfo: WalletMetaInfo) { builder.appendKeyValue("Mobile Wallet is backed up", walletMetaInfo.hotWalletIsBackedUp?.toString()) builder.appendKeyValue("Card ID", walletMetaInfo.cardId) @@ -104,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 04e4d5db8f..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 { @@ -65,6 +94,7 @@ class SendFeedbackEmailUseCase( is FeedbackEmailType.CardAttestationFailed, is FeedbackEmailType.Visa.Dispute, is FeedbackEmailType.Visa.DisputeV2, + is FeedbackEmailType.Visa.FeatureIsBeta, -> this is FeedbackEmailType.DirectUserRequest, is FeedbackEmailType.RateCanBeBetter, 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 12bd80d66a..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,22 +33,40 @@ 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) } 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.addTangemPayDisputeRequestBody(type: FeedbackEmailType.Visa.DisputeV2) { + addTangemPayPhoneInfoBody(type = type) + addDelimiter() + 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) + } } private suspend fun FeedbackDataBuilder.addTangemPayWithdrawalRequestBody( @@ -112,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/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt index 7d42928a99..36940c948e 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt @@ -26,6 +26,8 @@ internal class EmailMessageTitleResolver(private val resources: Resources) { is FeedbackEmailType.Visa.DisputeV2, is FeedbackEmailType.Visa.FailedIssueCard, is FeedbackEmailType.Visa.Withdrawal, + is FeedbackEmailType.Visa.FeatureIsBeta, + is FeedbackEmailType.PreActivatedWallet, -> R.string.feedback_preface_support is FeedbackEmailType.RateCanBeBetter -> R.string.feedback_preface_rate_negative is FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed @@ -33,7 +35,6 @@ internal class EmailMessageTitleResolver(private val resources: Resources) { is FeedbackEmailType.StakingProblem, is FeedbackEmailType.SwapProblem, -> R.string.feedback_preface_tx_failed - is FeedbackEmailType.PreActivatedWallet -> R.string.feedback_preface_support } return resources.getStringSafe(resId) diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt index 6e01c9989f..4b1e4f9799 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt @@ -15,6 +15,7 @@ import com.tangem.domain.feedback.models.FeedbackEmailType internal class EmailSubjectResolver(private val resources: Resources) { /** Resolve email message body by [type] */ + @Suppress("CyclomaticComplexMethod") fun resolve(type: FeedbackEmailType): String { return when (type) { is FeedbackEmailType.DirectUserRequest -> { @@ -43,8 +44,8 @@ internal class EmailSubjectResolver(private val resources: Resources) { is FeedbackEmailType.Visa.Dispute, is FeedbackEmailType.Visa.DisputeV2, -> "[Visa] [DISPUTE] {auto-filled subject}" - is FeedbackEmailType.Visa.Withdrawal, - -> "[Visa] [WITHDRAWAL] {auto-filled subject}" + is FeedbackEmailType.Visa.Withdrawal -> "[Visa] [WITHDRAWAL] {auto-filled subject}" + is FeedbackEmailType.Visa.FeatureIsBeta -> "[VISA] [FEEDBACK]" } } } \ No newline at end of file diff --git a/domain/markets/detekt-baseline-debug.xml b/domain/markets/detekt-baseline-debug.xml index ef5fed0f16..64d62630c1 100644 --- a/domain/markets/detekt-baseline-debug.xml +++ b/domain/markets/detekt-baseline-debug.xml @@ -5,7 +5,6 @@ BooleanPropertyNaming:FilterAvailableNetworksForWalletUseCase.kt$FilterAvailableNetworksForWalletUseCase$private val useNewRepository: Boolean BooleanPropertyNaming:GetStakingNotificationMaxApyUseCase.kt$GetStakingNotificationMaxApyUseCase$val showStakingNotification = if (!hideClicked && walletFirstUsageDate != 0L) { currentDate - walletFirstUsageDate > TWO_WEEKS_IN_MILLIS } else { false } MultilineLambdaItParameter:FilterAvailableNetworksForWalletUseCase.kt$FilterAvailableNetworksForWalletUseCase${ val blockchain = Blockchain.fromNetworkId(it.networkId) supportedBlockchains.contains(blockchain) } - MultilineLambdaItParameter:SaveMarketTokensUseCase.kt$SaveMarketTokensUseCase${ marketsTokenRepository.createCryptoCurrency( userWalletId = userWalletId, token = tokenMarketParams, network = it, ) } SuspendFunWithFlowReturnType:GetStakingNotificationMaxApyUseCase.kt$GetStakingNotificationMaxApyUseCase$suspend SuspendFunWithFlowReturnType:MarketsTokenRepository.kt$MarketsTokenRepository$suspend diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt index 77e9666e1e..c1d1b50552 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/SaveMarketTokensUseCase.kt @@ -2,6 +2,7 @@ package com.tangem.domain.markets import arrow.core.Either import com.tangem.domain.markets.repositories.MarketsTokenRepository +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWalletId @@ -45,11 +46,11 @@ class SaveMarketTokensUseCase( removedNetworks: Set, ): Either = Either.catch { if (removedNetworks.isNotEmpty()) { - val removedCurrencies = removedNetworks.mapNotNull { + val removedCurrencies = removedNetworks.mapNotNull { network -> marketsTokenRepository.createCryptoCurrency( userWalletId = userWalletId, token = tokenMarketParams, - network = it, + network = network, ) } @@ -60,13 +61,15 @@ class SaveMarketTokensUseCase( derivationsRepository.derivePublicKeysByNetworkIds( userWalletId = userWalletId, networkIds = addedNetworks.map { Network.RawID(it.networkId) }, + accountIndex = DerivationIndex.Main, ) - val addedCurrencies = addedNetworks.mapNotNull { + val addedCurrencies = addedNetworks.mapNotNull { network -> marketsTokenRepository.createCryptoCurrency( userWalletId = userWalletId, token = tokenMarketParams, - network = it, + network = network, + accountIndex = DerivationIndex.Main, ) } 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 e0c863bf61..8eb3724038 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/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt index 43c65d397a..e1073f1bd0 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyCheckUseCase.kt @@ -14,9 +14,11 @@ class GetCurrencyCheckUseCase( private val dispatchers: CoroutineDispatcherProvider, ) { + @Suppress("LongParameterList") suspend operator fun invoke( userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus, + feeCurrencyStatus: CryptoCurrencyStatus?, amount: BigDecimal?, fee: BigDecimal?, feeCurrencyBalanceAfterTransaction: BigDecimal?, @@ -31,7 +33,7 @@ class GetCurrencyCheckUseCase( val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, network) val rentWarning = currencyChecksRepository.getRentExemptionError( userWalletId = userWalletId, - currencyStatus = currencyStatus, + currencyStatus = feeCurrencyStatus, balanceAfterTransaction = feeCurrencyBalanceAfterTransaction ?: BigDecimal.ZERO, ) val isAccountFunded = recipientAddress?.let { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt index 8351958b10..41266e0b39 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt @@ -58,7 +58,7 @@ interface CurrencyChecksRepository { */ suspend fun getRentExemptionError( userWalletId: UserWalletId, - currencyStatus: CryptoCurrencyStatus, + currencyStatus: CryptoCurrencyStatus?, balanceAfterTransaction: BigDecimal, ): CryptoCurrencyWarning.Rent? } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayCryptoCurrencyFactory.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayCryptoCurrencyFactory.kt new file mode 100644 index 0000000000..5f3223e514 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayCryptoCurrencyFactory.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.pay + +import arrow.core.Either +import com.tangem.core.error.UniversalError +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet + +interface TangemPayCryptoCurrencyFactory { + + fun create(userWallet: UserWallet, chainId: Int): Either +} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt index 7f1092646f..ad9583243f 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/CustomerInfo.kt @@ -3,6 +3,11 @@ package com.tangem.domain.pay.model import com.tangem.domain.visa.model.TangemPayCardFrozenState import java.math.BigDecimal +sealed class MainCustomerInfoContentState { + object Loading : MainCustomerInfoContentState() + data class Content(val info: MainScreenCustomerInfo) : MainCustomerInfoContentState() +} + data class MainScreenCustomerInfo( val info: CustomerInfo, val orderStatus: OrderStatus, diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayCardBalance.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayCardBalance.kt index 90d3375e9c..911aead866 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayCardBalance.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayCardBalance.kt @@ -6,6 +6,7 @@ data class TangemPayCardBalance( val fiatBalance: BigDecimal, val currencyCode: String, val cryptoBalance: BigDecimal, + val availableForWithdrawal: BigDecimal, val chainId: Int, val depositAddress: String?, val contractAddress: String, diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPaySwapDataFactory.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayTopUpData.kt similarity index 57% rename from domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPaySwapDataFactory.kt rename to domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayTopUpData.kt index fe6cb7af82..3d40efda8e 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPaySwapDataFactory.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/TangemPayTopUpData.kt @@ -1,22 +1,10 @@ -package com.tangem.domain.pay +package com.tangem.domain.pay.model -import arrow.core.Either -import com.tangem.core.error.UniversalError import com.tangem.domain.models.ReceiveAddressModel import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId import java.math.BigDecimal -interface TangemPaySwapDataFactory { - - fun create( - depositAddress: String, - chainId: Int, - cryptoBalance: BigDecimal, - fiatBalance: BigDecimal, - ): Either -} - data class TangemPayTopUpData( val walletId: UserWalletId, val currency: CryptoCurrency, diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt index 313aa94bb3..07950a5cec 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt @@ -2,16 +2,13 @@ package com.tangem.domain.pay.usecase import arrow.core.Either import arrow.core.left -import arrow.core.raise.catch import arrow.core.right import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.pay.model.CustomerInfo -import com.tangem.domain.pay.model.MainScreenCustomerInfo -import com.tangem.domain.pay.model.OrderStatus -import com.tangem.domain.pay.model.TangemPayCustomerInfoError +import com.tangem.domain.pay.model.* import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.visa.error.VisaApiError +import kotlinx.coroutines.flow.* import timber.log.Timber private const val TAG = "TangemPayMainScreenCustomerInfoUseCase" @@ -26,32 +23,50 @@ class TangemPayMainScreenCustomerInfoUseCase( private val tangemPayOnboardingRepository: OnboardingRepository, ) { - suspend operator fun invoke( - userWalletId: UserWalletId, - ): Either = catch( - block = { - Timber.tag(TAG).d("checkCustomerWallet") - repository.checkCustomerWallet(userWalletId) - .fold( - ifLeft = { error -> - Timber.tag(TAG).e("Failed to check customer wallet ${error.javaClass.simpleName}") - TangemPayCustomerInfoError.UnknownError.left() - }, - ifRight = { hasTangemPay -> - Timber.tag(TAG).i("checkCustomerWallet $hasTangemPay") - if (hasTangemPay) { - proceedWithPaeraCustomerResult(userWalletId) - } else { - TangemPayCustomerInfoError.UnknownError.left() // ignore if there's no TangemPay + val state: StateFlow>> + field = MutableStateFlow(value = mapOf()) + + suspend fun fetch(userWalletId: UserWalletId) { + Timber.tag(TAG).i("fetch: $userWalletId") + repository.checkCustomerWallet(userWalletId) + .fold( + ifLeft = { error -> + Timber.tag(TAG).e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}") + updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) + }, + ifRight = { hasTangemPay -> + Timber.tag(TAG).i("checkCustomerWallet for $userWalletId: $hasTangemPay") + if (hasTangemPay) { + val oldResult = state.value[userWalletId] + if (oldResult == null) { + updateState(userWalletId, MainCustomerInfoContentState.Loading.right()) } - }, - ) - }, - catch = { error -> - Timber.tag(TAG).e(error) - TangemPayCustomerInfoError.UnknownError.left() - }, - ) + + val result = proceedWithPaeraCustomerResult(userWalletId) + .map(MainCustomerInfoContentState::Content) + updateState(userWalletId, result) + } else { + // ignore if there's no TangemPay + updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) + } + }, + ) + } + + operator fun invoke( + userWalletId: UserWalletId, + ): Flow> { + return state.mapNotNull { map -> map[userWalletId] } + } + + private fun updateState( + userWalletId: UserWalletId, + either: Either, + ) { + state.update { currentMap -> + currentMap.toMutableMap().apply { this[userWalletId] = either } + } + } private suspend fun proceedWithPaeraCustomerResult( userWalletId: UserWalletId, @@ -70,14 +85,13 @@ class TangemPayMainScreenCustomerInfoUseCase( private suspend fun proceedWithoutOrder( userWalletId: UserWalletId, ): Either { - Timber.tag(TAG).d("proceedWithoutOrder") return repository.getCustomerInfo(userWalletId) .mapLeft { error -> Timber.tag(TAG).e("mapErrorForCustomer: $error") error.mapErrorForCustomer() } .map { customerInfo -> - Timber.tag(TAG).d("customerInfo") + Timber.tag(TAG).i("customerInfo") if (customerInfo.cardInfo == null && customerInfo.isKycApproved) { // If order id wasn't saved -> start order creation and get customer info repository.createOrder(userWalletId) 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 af92401e5b..82b9328d08 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 @@ -8,42 +8,47 @@ sealed class TangemPayAnalyticsEvents( params: Map = emptyMap(), ) : AnalyticsEvent(category = categoryName, event = event, params = params) { - data object ActivationScreenOpened : TangemPayAnalyticsEvents( + class ActivationScreenOpened : TangemPayAnalyticsEvents( categoryName = "Visa Onboarding", - event = "Activation Screen Opened", + event = "Visa Activation Screen Opened", ) - data object ViewTermsClicked : TangemPayAnalyticsEvents( + class ViewTermsClicked : TangemPayAnalyticsEvents( categoryName = "Visa Onboarding", event = "Button - Visa View Terms", ) - data object GetCardClicked : TangemPayAnalyticsEvents( + class GetCardClicked : TangemPayAnalyticsEvents( categoryName = "Visa Onboarding", event = "Button - Visa Get Card", ) - data object KycFlowOpened : TangemPayAnalyticsEvents( + class KycFlowOpened : TangemPayAnalyticsEvents( categoryName = "Visa Onboarding", event = "Visa KYC Flow Opened", ) - data object IssuingBannerDisplayed : TangemPayAnalyticsEvents( + class IssuingBannerDisplayed : TangemPayAnalyticsEvents( categoryName = "Visa Onboarding", event = "Visa Issuing Banner Displayed", ) - data object MainScreenOpened : TangemPayAnalyticsEvents( + class MainScreenOpened : TangemPayAnalyticsEvents( categoryName = "Visa Screen", event = "Visa Main Screen Opened", ) - data object ReceiveFundsClicked : TangemPayAnalyticsEvents( + class AddFundsClicked : TangemPayAnalyticsEvents( + categoryName = "Visa Screen", + event = "Button - Visa Add Funds", + ) + + class ReceiveFundsClicked : TangemPayAnalyticsEvents( categoryName = "Visa Screen", event = "Button - Visa Receive", ) - data object SwapClicked : TangemPayAnalyticsEvents( + class SwapClicked : TangemPayAnalyticsEvents( categoryName = "Visa Screen", event = "Button - Visa Swap", ) diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/derivations/DerivationsRepository.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/derivations/DerivationsRepository.kt index 43b5190947..9eefdc6899 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/derivations/DerivationsRepository.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/derivations/DerivationsRepository.kt @@ -2,6 +2,7 @@ package com.tangem.domain.wallets.derivations import com.tangem.common.extensions.ByteArrayKey import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWalletId @@ -13,7 +14,11 @@ interface DerivationsRepository { @Throws suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List) - suspend fun derivePublicKeysByNetworkIds(userWalletId: UserWalletId, networkIds: List) + suspend fun derivePublicKeysByNetworkIds( + userWalletId: UserWalletId, + networkIds: List, + accountIndex: DerivationIndex, + ) @Throws suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List) diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/derivations/HotMapDerivationsRepository.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/derivations/HotMapDerivationsRepository.kt index 65364e9a80..27b260db1d 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/derivations/HotMapDerivationsRepository.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/derivations/HotMapDerivationsRepository.kt @@ -2,6 +2,7 @@ package com.tangem.domain.wallets.derivations import com.tangem.common.extensions.ByteArrayKey import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet @@ -16,6 +17,7 @@ interface HotMapDerivationsRepository { suspend fun derivePublicKeysByNetworkIds( userWallet: UserWallet.Hot, networkIds: List, + accountIndex: DerivationIndex, ): UserWallet.Hot @Throws diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt index ebabf476c2..617de52a47 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt @@ -87,7 +87,7 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) { DetailsItemUM.Basic.Item( id = "support_email", block = BlockUM( - text = resourceReference(R.string.details_row_title_contact_to_support), + text = resourceReference(R.string.common_contact_support), iconRes = R.drawable.ic_comment_24, onClick = onSupportEmailClick, ), diff --git a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt index 2d7b7e1558..ba0446ea8a 100644 --- a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt +++ b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt @@ -30,7 +30,7 @@ class DefaultKycModel @Inject constructor( val uiState = _uiState.asStateFlow() init { - analytics.send(TangemPayAnalyticsEvents.KycFlowOpened) + analytics.send(TangemPayAnalyticsEvents.KycFlowOpened()) modelScope.launch { try { kycRepository.getKycStartInfo(params.userWalletId).getOrNull()?.let { _uiState.emit(it) } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ChangedCurrenciesManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ChangedCurrenciesManager.kt index 0e54d8528d..44529b9688 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ChangedCurrenciesManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ChangedCurrenciesManager.kt @@ -7,6 +7,11 @@ import kotlinx.coroutines.flow.update internal typealias ChangedCurrencies = Map> +internal data class CurrencyUpdates( + val toAdd: ChangedCurrencies = emptyMap(), + val toRemove: ChangedCurrencies = emptyMap(), +) + internal class ChangedCurrenciesManager { val currenciesToAdd: MutableStateFlow = MutableStateFlow(emptyMap()) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt index 59bf275e11..d0221350ce 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt @@ -329,6 +329,10 @@ internal class ManageTokensListManager @AssistedInject constructor( val updatedUiItem = uiBatch.data[currencyIndex].toggleExpanded( currency = currencyBatch.data[currencyIndex], isEditable = batches.canEditItems, + updates = CurrencyUpdates( + toAdd = currenciesToAdd.value, + toRemove = currenciesToRemove.value, + ), onSelectCurrencyNetwork = { networkId, isSelected -> selectNetwork(currencyBatch.key, currency, networkId, isSelected) }, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyNetworksMapper.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyNetworksMapper.kt index 58cea15030..c92243274e 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyNetworksMapper.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/CurrencyNetworksMapper.kt @@ -5,20 +5,28 @@ import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.models.network.Network import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM +import com.tangem.features.managetokens.utils.list.CurrencyUpdates import com.tangem.features.managetokens.utils.ui.getIconRes import kotlinx.collections.immutable.toImmutableList internal fun ManagedCryptoCurrency.Token.toUiNetworksModel( isExpanded: Boolean, isItemsEditable: Boolean, + updates: CurrencyUpdates, onSelectedStateChange: (SourceNetwork, Boolean) -> Unit, onLongTap: (SourceNetwork) -> Unit, ): NetworksUM { return if (isExpanded) { NetworksUM.Expanded( - networks = availableNetworks.map { - it.toCurrencyNetworkModel( - isSelected = it.network in addedIn, + networks = availableNetworks.map { sourceNetwork -> + val isSelectedByDefault = sourceNetwork.network in addedIn + val isSelected = when (sourceNetwork.network) { + in updates.toAdd[this].orEmpty() -> true + in updates.toRemove[this].orEmpty() -> false + else -> isSelectedByDefault + } + sourceNetwork.toCurrencyNetworkModel( + isSelected = isSelected, isEditable = isItemsEditable, onSelectedStateChange = onSelectedStateChange, onLongTap = onLongTap, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyItemOperations.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyItemOperations.kt index d92b7108d0..fd675f17cd 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyItemOperations.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CurrencyItemOperations.kt @@ -4,12 +4,14 @@ import com.tangem.domain.managetokens.model.ManagedCryptoCurrency import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.features.managetokens.entity.item.CurrencyItemUM import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM +import com.tangem.features.managetokens.utils.list.CurrencyUpdates import com.tangem.features.managetokens.utils.mapper.toUiNetworksModel import kotlinx.collections.immutable.toImmutableList internal fun CurrencyItemUM.toggleExpanded( currency: ManagedCryptoCurrency, isEditable: Boolean, + updates: CurrencyUpdates, onSelectCurrencyNetwork: (SourceNetwork, Boolean) -> Unit, onLongTap: (SourceNetwork) -> Unit, ): CurrencyItemUM { @@ -30,6 +32,7 @@ internal fun CurrencyItemUM.toggleExpanded( networks = currency.toUiNetworksModel( isExpanded = isExpanded, isItemsEditable = isEditable, + updates = updates, onSelectedStateChange = onSelectCurrencyNetwork, onLongTap = onLongTap, ), diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt index 311f6709fe..fc3737ddd0 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt @@ -20,28 +20,26 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.Dp -import com.tangem.common.ui.charts.state.MarketChartDataProducer import com.tangem.core.ui.components.* import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons import com.tangem.core.ui.components.currency.icon.CoinIcon import com.tangem.core.ui.components.marketprice.PriceChangeInPercent import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent -import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.markets.PriceChangeInterval import com.tangem.features.markets.details.impl.ui.components.* +import com.tangem.features.markets.details.impl.ui.preview.MarketsTokenDetailsPreview import com.tangem.features.markets.details.impl.ui.state.ExchangesBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.InfoBottomSheetContent import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM @@ -317,36 +315,16 @@ fun PriceChangeInterval.getText(): TextReference { } } -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +// region Preview @Composable -private fun Preview() { +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun MarketsTokenDetailsContent_Preview( + @PreviewParameter(MarketsTokenDetailsContentPreviewProvider::class) params: MarketsTokenDetailsUM, +) { TangemThemePreview { MarketsTokenDetailsContent( - state = MarketsTokenDetailsUM( - tokenName = "Token Name", - priceText = "$0.00000000324", - dateTimeText = stringReference("Today"), - priceChangePercentText = "52.00%", - iconUrl = "", - priceChangeType = PriceChangeType.UP, - chartState = MarketsTokenDetailsUM.ChartState( - dataProducer = MarketChartDataProducer.build { }, - onLoadRetryClick = {}, - status = MarketsTokenDetailsUM.ChartState.Status.LOADING, - onMarkerPointSelected = { _, _ -> }, - ), - selectedInterval = PriceChangeInterval.H24, - onSelectedIntervalChange = { }, - body = MarketsTokenDetailsUM.Body.Loading, - bottomSheetConfig = TangemBottomSheetConfig( - isShown = false, - onDismissRequest = {}, - content = TangemBottomSheetConfigContent.Empty, - ), - markerSet = false, - triggerPriceChange = consumedEvent(), - ), + state = params, onHeaderSizeChange = {}, onBackClick = {}, backgroundColor = TangemTheme.colors.background.tertiary, @@ -356,4 +334,13 @@ private fun Preview() { addTopBarStatusBarPadding = false, ) } -} \ No newline at end of file +} + +private class MarketsTokenDetailsContentPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + MarketsTokenDetailsPreview.loadingState, + MarketsTokenDetailsPreview.contentState, + ) +} +// endregion \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt index e13502279d..8b5e829a64 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InsightsBlock.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.block.information.GridItems import com.tangem.core.ui.components.block.information.InformationBlock @@ -51,12 +52,16 @@ internal fun InsightsBlock(state: InsightsUM, modifier: Modifier = Modifier) { currentInterval = it state.onIntervalChanged(it) }, + modifier = Modifier.width(IntrinsicSize.Min), ) { Box( Modifier .fillMaxSize() .align(Alignment.Center) - .padding(vertical = TangemTheme.dimens.spacing4), + .padding( + horizontal = 14.dp, + vertical = 4.dp, + ), ) { Text( modifier = Modifier.align(Alignment.Center), diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt index 4486732b67..efede75d0e 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/PricePerformanceBlock.kt @@ -57,12 +57,16 @@ internal fun PricePerformanceBlock(state: PricePerformanceUM, modifier: Modifier currentInterval = it state.onIntervalChanged(it) }, + modifier = Modifier.width(IntrinsicSize.Min), ) { Box( Modifier .fillMaxSize() .align(Alignment.Center) - .padding(vertical = TangemTheme.dimens.spacing4), + .padding( + horizontal = 14.dp, + vertical = TangemTheme.dimens.spacing4, + ), ) { Text( modifier = Modifier.align(Alignment.Center), diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/MarketsTokenDetailsPreview.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/MarketsTokenDetailsPreview.kt new file mode 100644 index 0000000000..46ad847bf6 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/preview/MarketsTokenDetailsPreview.kt @@ -0,0 +1,129 @@ +package com.tangem.features.markets.details.impl.ui.preview + +import com.tangem.common.ui.charts.state.MarketChartDataProducer +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.event.consumedEvent +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.markets.PriceChangeInterval +import com.tangem.features.markets.details.impl.ui.state.* +import kotlinx.collections.immutable.persistentListOf + +internal object MarketsTokenDetailsPreview { + private val infoPoint = InfoPointUM( + title = stringReference("1"), + value = "2", + change = InfoPointUM.ChangeType.DOWN, + onInfoClick = {}, + ) + + val loadingState = MarketsTokenDetailsUM( + tokenName = "Token Name", + priceText = "$0.00000000324", + dateTimeText = stringReference("Today"), + priceChangePercentText = "52.00%", + iconUrl = "", + priceChangeType = PriceChangeType.UP, + chartState = MarketsTokenDetailsUM.ChartState( + dataProducer = MarketChartDataProducer.build { }, + onLoadRetryClick = {}, + status = MarketsTokenDetailsUM.ChartState.Status.LOADING, + onMarkerPointSelected = { _, _ -> }, + ), + selectedInterval = PriceChangeInterval.H24, + onSelectedIntervalChange = { }, + body = MarketsTokenDetailsUM.Body.Loading, + bottomSheetConfig = TangemBottomSheetConfig( + isShown = false, + onDismissRequest = {}, + content = TangemBottomSheetConfigContent.Empty, + ), + markerSet = false, + triggerPriceChange = consumedEvent(), + ) + + val contentState = MarketsTokenDetailsUM( + tokenName = "Token Name", + priceText = "$0.00000000324", + dateTimeText = stringReference("Today"), + priceChangePercentText = "52.00%", + iconUrl = "", + priceChangeType = PriceChangeType.UP, + chartState = MarketsTokenDetailsUM.ChartState( + dataProducer = MarketChartDataProducer.build { }, + onLoadRetryClick = {}, + status = MarketsTokenDetailsUM.ChartState.Status.LOADING, + onMarkerPointSelected = { _, _ -> }, + ), + selectedInterval = PriceChangeInterval.H24, + onSelectedIntervalChange = { }, + body = MarketsTokenDetailsUM.Body.Content( + description = MarketsTokenDetailsUM.Description( + shortDescription = stringReference("markets_token_details_description_short"), + fullDescription = stringReference("markets_token_details_description_full"), + onReadMoreClick = {}, + ), + infoBlocks = MarketsTokenDetailsUM.InformationBlocks( + insights = InsightsUM( + h24Info = persistentListOf( + infoPoint, + infoPoint, + infoPoint, + ), + weekInfo = persistentListOf( + infoPoint, + infoPoint, + infoPoint, + ), + monthInfo = persistentListOf( + infoPoint, + infoPoint, + infoPoint, + ), + onInfoClick = {}, + onIntervalChanged = {}, + ), + securityScore = SecurityScoreUM( + score = 2.3f, + description = stringReference("markets_token_details_security_score_description"), + onInfoClick = {}, + ), + metrics = MetricsUM( + metrics = persistentListOf( + infoPoint, + infoPoint, + infoPoint, + ), + ), + pricePerformance = PricePerformanceUM( + h24 = PricePerformanceUM.Value( + low = "1", + high = "2", + indicatorFraction = 0.3f, + ), + month = PricePerformanceUM.Value( + low = "1", + high = "2", + indicatorFraction = 0.3f, + ), + all = PricePerformanceUM.Value( + low = "1", + high = "2", + indicatorFraction = 0.3f, + ), + onIntervalChanged = {}, + ), + listedOn = ListedOnUM.Empty, + links = null, + ), + ), + bottomSheetConfig = TangemBottomSheetConfig( + isShown = false, + onDismissRequest = {}, + content = TangemBottomSheetConfigContent.Empty, + ), + markerSet = true, + triggerPriceChange = consumedEvent(), + ) +} \ 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 67f09bf351..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 @@ -17,14 +17,15 @@ internal class OnrampOffersStateFactory( fun getOffersState(offers: List): OnrampV2MainComponentUM { val currentState = currentStateProvider.invoke() return when (currentState) { + is OnrampV2MainComponentUM.InitialLoading -> currentState is OnrampV2MainComponentUM.Content -> { + if (currentState.offersBlockState is OnrampOffersBlockUM.Loading) { + return currentState + } currentState.copy( offersBlockState = mapOnrampOffersBlockToUM(offersBlocks = offers), ) } - is OnrampV2MainComponentUM.InitialLoading -> { - currentState - } } } 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 2af5055bd7..5e4d4022dd 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,6 +64,11 @@ internal class OnrampV2AmountStateFactory( currencySymbol = currency.unit, onAmountValueChanged = onrampIntents::onAmountValueChanged, ), + offersBlockState = if (amountState.amountFieldModel.fiatValue.isEmpty()) { + OnrampOffersBlockUM.Empty + } else { + OnrampOffersBlockUM.Loading + }, ) } @@ -97,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 1fc8da3ee7..4efed551f6 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/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt index 9fe53c12a0..e7eba0a83f 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/settings/model/OnrampSettingsModel.kt @@ -29,13 +29,13 @@ internal class OnrampSettingsModel @Inject constructor( paramsContainer: ParamsContainer, ) : Model() { + private val params: OnrampSettingsComponent.Params = paramsContainer.require() + val state: StateFlow field = MutableStateFlow(getInitialState()) val bottomSheetNavigation: SlotNavigation = SlotNavigation() - private val params: OnrampSettingsComponent.Params = paramsContainer.require() - init { analyticsEventHandler.send(OnrampAnalyticsEvent.SettingsOpened) subscribeOnUpdateState() diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index 0fb799cf17..5c00ee1e19 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -523,6 +523,7 @@ internal class SendConfirmModel @Inject constructor( params.callback.onResult( state.copy( navigationUM = NavigationUM.Content( + source = CommonSendRoute.Confirm.javaClass.simpleName, title = resourceReference(id = R.string.common_send), subtitle = null, backIconRes = when (confirmUM) { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/model/SendConfirmSuccessModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/model/SendConfirmSuccessModel.kt index 0d6a56bba6..29d2b390d8 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/model/SendConfirmSuccessModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/model/SendConfirmSuccessModel.kt @@ -52,6 +52,7 @@ internal class SendConfirmSuccessModel @Inject constructor( params.callback.onResult( state.copy( navigationUM = NavigationUM.Content( + source = CommonSendRoute.ConfirmSuccess.javaClass.simpleName, title = stringReference(""), subtitle = null, backIconRes = R.drawable.ic_close_24, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt index aab81e8c77..1c7c92bc7e 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt @@ -371,11 +371,12 @@ internal class NFTSendConfirmModel @Inject constructor( flow = uiState, flow2 = params.currentRoute, transform = { state, route -> state to route }, - ).onEach { (state, _) -> + ).onEach { (state, route) -> val confirmUM = state.confirmUM params.callback.onResult( state.copy( navigationUM = NavigationUM.Content( + source = CommonSendRoute.Confirm.javaClass.simpleName, title = resourceReference(R.string.nft_send), subtitle = null, backIconRes = when (confirmUM) { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/model/NFTSendSuccessModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/model/NFTSendSuccessModel.kt index 7fc3951005..92c860fb82 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/model/NFTSendSuccessModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/model/NFTSendSuccessModel.kt @@ -50,6 +50,7 @@ internal class NFTSendSuccessModel @Inject constructor( params.callback.onResult( state.copy( navigationUM = NavigationUM.Content( + source = CommonSendRoute.ConfirmSuccess.javaClass.simpleName, title = stringReference(""), subtitle = null, backIconRes = R.drawable.ic_close_24, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt index 7926083e2e..9be514d748 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt @@ -378,6 +378,7 @@ internal class SendAmountModel @Inject constructor( setSendWithSwapAvailability() params.callback.onNavigationResult( NavigationUM.Content( + source = CommonSendRoute.Amount::class.java.simpleName, title = resourceReference(R.string.send_amount_label), subtitle = null, backIconRes = if (route.isEditMode) { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt index becc704e91..5870afd0fb 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt @@ -35,6 +35,7 @@ import com.tangem.features.send.v2.api.entity.PredefinedValues import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM +import com.tangem.features.send.v2.common.CommonSendRoute import com.tangem.features.send.v2.impl.R import com.tangem.features.send.v2.subcomponents.destination.analytics.EnterAddressSource import com.tangem.features.send.v2.subcomponents.destination.analytics.SendDestinationAnalyticEvents @@ -359,6 +360,7 @@ internal class SendDestinationModel @Inject constructor( ).onEach { (state, route) -> params.callback.onNavigationResult( NavigationUM.Content( + source = CommonSendRoute.Destination::class.java.simpleName, title = params.title, subtitle = null, backIconRes = if (route.isEditMode) { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt index 7642f338a8..7efbe2186d 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt @@ -136,6 +136,7 @@ internal class NotificationsModel @Inject constructor( val currencyCheck = getCurrencyCheckUseCase( userWalletId = userWalletId, currencyStatus = cryptoCurrencyStatus, + feeCurrencyStatus = feeCryptoCurrencyStatus, amount = sendingAmount, fee = feeValue, recipientAddress = destinationAddress, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index eebecedbcf..9e1aaa62ba 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -772,6 +772,7 @@ internal class StakingModel @Inject constructor( val currencyStatus = getCurrencyCheckUseCase( userWalletId = userWalletId, currencyStatus = cryptoCurrencyStatus, + feeCurrencyStatus = feeCryptoCurrencyStatus, amount = amount, fee = fee, feeCurrencyBalanceAfterTransaction = balanceAfterTransaction, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt index 70fe7d5312..da46d20b3f 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt @@ -720,6 +720,7 @@ internal class SwapAmountModel @Inject constructor( ).filter { (_, route) -> route is SendWithSwapRoute.Amount }.onEach { (state, route) -> params.callback.onNavigationResult( NavigationUM.Content( + source = SendWithSwapRoute.Amount::class.java.simpleName, title = resourceReference(R.string.common_amount), subtitle = null, backIconRes = if (route.isEditMode) { diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index 8d04b3d804..c0d119b50f 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -428,6 +428,7 @@ internal class SendWithSwapConfirmModel @Inject constructor( route = SendWithSwapRoute.Confirm, sendWithSwapUM = state.copy( navigationUM = NavigationUM.Content( + source = SendWithSwapRoute.Confirm.javaClass.simpleName, title = resourceReference(id = R.string.send_with_swap_confirm_title), subtitle = null, backIconRes = R.drawable.ic_back_24, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt index 33909f7563..6b743b5db5 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt @@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.features.swap.v2.impl.R import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM +import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM import com.tangem.features.swap.v2.impl.sendviaswap.success.SendWithSwapSuccessComponent import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -40,6 +41,7 @@ internal class SendWithSwapSuccessModel @Inject constructor( private fun configConfirmSuccessNavigation() { params.callback.onNavigationResult( NavigationUM.Content( + source = SendWithSwapRoute.Success.javaClass.simpleName, title = TextReference.EMPTY, subtitle = null, backIconRes = R.drawable.ic_close_24, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt index c948e842c3..0ccf5aada7 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt @@ -55,6 +55,7 @@ import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountContentPreview import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM +import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM import kotlinx.collections.immutable.persistentListOf import java.math.BigDecimal @@ -368,6 +369,7 @@ private fun SendWithSwapSuccessContent_Preview() { ), ), navigationUM = NavigationUM.Content( + source = SendWithSwapRoute.Success.javaClass.simpleName, title = TextReference.EMPTY, subtitle = null, backIconRes = R.drawable.ic_close_24, diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 046da4aa6a..4efc95da23 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -37,7 +37,6 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.legacy) implementation(projects.domain.walletManager) - implementation(projects.libs.blockchainSdk) implementation(projects.domain.models) implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) @@ -45,6 +44,9 @@ dependencies { implementation(projects.domain.express.models) implementation(projects.domain.account.status) + implementation(projects.libs.blockchainSdk) + implementation(projects.libs.crypto) + /** Data */ implementation(projects.data.common) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SavedSwapTransactionListConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SavedSwapTransactionListConverter.kt index 3144aa0a95..8ec8958bd3 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SavedSwapTransactionListConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SavedSwapTransactionListConverter.kt @@ -17,6 +17,7 @@ import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModelInner import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel +import com.tangem.lib.crypto.derivation.AccountNodeRecognizer import com.tangem.utils.converter.Converter internal class SavedSwapTransactionListConverter( @@ -75,9 +76,23 @@ internal class SavedSwapTransactionListConverter( .map { tx -> val status = txStatuses[tx.txId] val refundCurrency = status?.refundTokensResponse?.let { id -> + val blockchain = Blockchain.fromNetworkId(id.networkId) ?: return@let null + val derivationPath = id.derivationPath ?: return@let null + + val accountIndex = if (blockchain == Blockchain.Chia) { + DerivationIndex.Main + } else { + val recognizer = AccountNodeRecognizer(blockchain = blockchain) + val index = recognizer.recognize(derivationPathValue = derivationPath)?.toInt() + ?: return@let null + + DerivationIndex(index).getOrNull() ?: return@let null + } + responseCryptoCurrenciesFactory.createCurrency( responseToken = id, userWallet = userWallet, + accountIndex = accountIndex, ) } val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency) diff --git a/features/swap/domain/detekt-baseline-debug.xml b/features/swap/domain/detekt-baseline-debug.xml index 02326b7307..e1d3527ea0 100644 --- a/features/swap/domain/detekt-baseline-debug.xml +++ b/features/swap/domain/detekt-baseline-debug.xml @@ -25,8 +25,6 @@ NamedArguments:SwapInteractorImpl.kt$SwapInteractorImpl$tryGetFromCacheV2(userWallet, initialCryptoCurrency, state, isReverseFromTo) NoNameShadowing:SwapInteractorImpl.kt$SwapInteractorImpl$account NoNameShadowing:SwapInteractorImpl.kt$SwapInteractorImpl${ it.isAvailable } - NonBooleanPropertyPrefixedWithIs:SwapInteractorImpl.kt$SwapInteractorImpl$private val isDemoCardUseCase: IsDemoCardUseCase - NullableToStringCall:SwapInteractorImpl.kt$SwapInteractorImpl$$swapData NullableToStringCall:SwapInteractorImpl.kt$SwapInteractorImpl$${e.message} SuspendFunSwallowedCancellation:SwapInteractorImpl.kt$SwapInteractorImpl$runCatching diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 8146ac1c63..aa8415a3c9 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -92,6 +92,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val amountFormatter: AmountFormatter, private val rampStateManager: RampStateManager, private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier, + private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val accountsFeatureToggles: AccountsFeatureToggles, @Assisted private val userWalletId: UserWalletId, ) : SwapInteractor { @@ -631,9 +632,14 @@ internal class SwapInteractorImpl @AssistedInject constructor( } else { amount } + val feePaidCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase( + userWalletId = userWalletId, + cryptoCurrencyStatus = fromTokenStatus, + ).getOrNull() val currencyCheck = getCurrencyCheckUseCase( userWalletId = userWalletId, currencyStatus = fromTokenStatus, + feeCurrencyStatus = feePaidCurrencyStatus, amount = amountToRequest.value, fee = fee, feeCurrencyBalanceAfterTransaction = balanceAfterTransaction, diff --git a/features/swap/impl/detekt-baseline-debug.xml b/features/swap/impl/detekt-baseline-debug.xml index e7ed3b9972..44b4ff0d46 100644 --- a/features/swap/impl/detekt-baseline-debug.xml +++ b/features/swap/impl/detekt-baseline-debug.xml @@ -48,8 +48,6 @@ MultilineLambdaItParameter:SwapModel.kt$SwapModel${ uiState = stateBuilder.dismissBottomSheet(uiState) dataState = dataState.copy(selectedFee = it) modelScope.launch(dispatchers.io) { startLoadingQuotesFromLastState(false) } } MultilineLambdaItParameter:SwapModel.kt$SwapModel${ val balance = swapInteractor.getTokenBalance(it) onAmountChanged(balance.formatToUIRepresentation()) } MultilineLambdaItParameter:SwapModel.kt$SwapModel${ val provider = findAndSelectProvider(it) val swapState = dataState.lastLoadedSwapStates[provider] val fromToken = dataState.fromCryptoCurrency if (provider != null && swapState != null && fromToken != null) { analyticsEventHandler.send(SwapEvents.ProviderChosen(provider)) uiState = stateBuilder.dismissBottomSheet(uiState) setupLoadedState( provider = provider, state = swapState, fromToken = fromToken, ) } } - MultilineLambdaItParameter:SwapModel.kt$SwapModel${ when (it) { is SwapTransactionState.TxSent -> { sendApproveSuccessEvent(fromToken, feeForPermission.feeType, approveType) updateWalletBalance() uiState = stateBuilder.loadingPermissionState(uiState) uiState = stateBuilder.dismissBottomSheet(uiState) startLoadingQuotesFromLastState(isSilent = true) } is SwapTransactionState.Error -> { uiState = stateBuilder.createErrorTransactionAlert( uiState = uiState, error = it, onDismiss = { uiState = stateBuilder.clearAlert(uiState) }, onSupportClick = ::onFailedTxEmailClick, isReverseSwapPossible = isReverseSwapPossible(), ) } SwapTransactionState.DemoMode -> { uiState = stateBuilder.createDemoModeAlert( uiState = uiState, onDismiss = { uiState = stateBuilder.clearAlert(uiState) }, isReverseSwapPossible = isReverseSwapPossible(), ) } } } - MultilineLambdaItParameter:SwapModel.kt$SwapModel${ when (it) { is SwapTransactionState.TxSent -> { sendSuccessSwapEvent(fromCurrency.currency, fee.feeType) val url = getExplorerTransactionUrlUseCase( txHash = it.txHash, networkId = fromCurrency.currency.network.id, ).getOrElse { Timber.i("tx hash explore not supported") "" } updateWalletBalance() uiState = stateBuilder.createSuccessState( uiState = uiState, swapTransactionState = it, dataState = dataState, txUrl = url, onExploreClick = { if (it.txHash.isNotEmpty()) { urlOpener.openUrl(url) } analyticsEventHandler.send( event = SwapEvents.ButtonExplore(initialCurrencyFrom.symbol), ) }, onStatusClick = { val txExternalUrl = it.txExternalUrl if (!txExternalUrl.isNullOrBlank()) { urlOpener.openUrl(txExternalUrl) analyticsEventHandler.send( event = SwapEvents.ButtonStatus(initialCurrencyFrom.symbol), ) } }, ) sendSuccessEvent() swapRouter.openScreen(SwapNavScreen.Success) } SwapTransactionState.DemoMode -> { uiState = stateBuilder.createDemoModeAlert( uiState = uiState, onDismiss = { uiState = stateBuilder.clearAlert(uiState) }, isReverseSwapPossible = isReverseSwapPossible(), ) } is SwapTransactionState.Error -> { startLoadingQuotesFromLastState() uiState = stateBuilder.createErrorTransactionAlert( uiState = uiState, error = it, onDismiss = { uiState = stateBuilder.clearAlert(uiState) }, onSupportClick = ::onFailedTxEmailClick, isReverseSwapPossible = isReverseSwapPossible(), ) } } } MultilineLambdaItParameter:TransactionCard.kt${ Text( text = it, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, ) } MultilineLambdaItParameter:TransactionCard.kt${ Text( text = it, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, modifier = Modifier .align(Alignment.CenterVertically) .testTag(SwapTokenScreenTestTags.BALANCE), ) } MultilineLambdaItParameter:TransactionCard.kt${ Text( text = it, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20), ) } 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 ffa4642c46..d227b21dfd 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 @@ -1647,8 +1647,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/components/TangemPayAddFundsComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddFundsComponent.kt index db43708e08..7058acc0a1 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddFundsComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayAddFundsComponent.kt @@ -5,7 +5,7 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.pay.TangemPayTopUpData +import com.tangem.domain.pay.model.TangemPayTopUpData import com.tangem.features.tangempay.model.TangemPayAddFundsModel import com.tangem.features.tangempay.ui.TangemPayAddFundsContent import java.math.BigDecimal diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt index 38d29ced5d..44a23b3ed8 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayDetailsComponent.kt @@ -84,6 +84,7 @@ internal class TangemPayDetailsComponent( appComponentContext = context, params = TangemPayTxHistoryDetailsComponent.Params( transaction = navigation.transaction, + isBalanceHidden = navigation.isBalanceHidden, userWalletId = params.userWalletId, onDismiss = model.bottomSheetNavigation::dismiss, ), diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/TangemPayTxHistoryDetailsComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/TangemPayTxHistoryDetailsComponent.kt index 345ca4a497..eeb2b77c8f 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/TangemPayTxHistoryDetailsComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/txHistory/TangemPayTxHistoryDetailsComponent.kt @@ -1,10 +1,11 @@ package com.tangem.features.tangempay.components.txHistory import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableBottomSheetComponent -import com.tangem.core.ui.extensions.* import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.visa.model.TangemPayTxHistoryItem import com.tangem.features.tangempay.model.TangemPayTxHistoryDetailsModel @@ -23,11 +24,13 @@ internal class TangemPayTxHistoryDetailsComponent( @Composable override fun BottomSheet() { - TangemPayTxHistoryDetailsContent(state = model.uiState) + val state by model.uiState.collectAsStateWithLifecycle() + TangemPayTxHistoryDetailsContent(state = state) } data class Params( val transaction: TangemPayTxHistoryItem, + val isBalanceHidden: Boolean, val userWalletId: UserWalletId, val onDismiss: () -> Unit, ) diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNavigation.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNavigation.kt index 9e4c9f8854..86009f09f0 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNavigation.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNavigation.kt @@ -22,5 +22,8 @@ internal sealed class TangemPayDetailsNavigation { ) : TangemPayDetailsNavigation() @Serializable - data class TransactionDetails(val transaction: TangemPayTxHistoryItem) : TangemPayDetailsNavigation() + data class TransactionDetails( + val transaction: TangemPayTxHistoryItem, + val isBalanceHidden: Boolean, + ) : TangemPayDetailsNavigation() } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt index 44b0b79cb1..9853165dad 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt @@ -1,8 +1,10 @@ package com.tangem.features.tangempay.entity +import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem +import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.themedColor import com.tangem.core.ui.res.TangemTheme @@ -21,6 +23,7 @@ internal class TangemPayDetailsStateFactory( private val converter: TangemPayCardFrozenStateConverter, ) { + @Suppress("LongMethod") fun getInitialState(): TangemPayDetailsUM { val cardFrozenStateItem = when (cardFrozenState) { is TangemPayCardFrozenState.Pending -> null @@ -41,7 +44,6 @@ internal class TangemPayDetailsStateFactory( ), ) } - return TangemPayDetailsUM( topBarConfig = TangemPayDetailsTopBarConfig( onBackClick = onBack, @@ -89,6 +91,16 @@ internal class TangemPayDetailsStateFactory( isBalanceHidden = false, addFundsEnabled = true, cardFrozenState = converter.convert(cardFrozenState), + betaNotificationConfig = NotificationConfig( + title = resourceReference(R.string.tangem_pay_beta_notification_title), + subtitle = resourceReference(R.string.tangem_pay_beta_notification_subtitle), + iconResId = R.drawable.img_visa_notification, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.common_contact_support), + onClick = intents::onContactSupportClicked, + ), + iconSize = 36.dp, + ), ) } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt index 6faa4a89ea..a8b31086f1 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt @@ -2,6 +2,7 @@ package com.tangem.features.tangempay.entity import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig +import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.visa.model.TangemPayCardFrozenState import kotlinx.collections.immutable.ImmutableList @@ -14,6 +15,7 @@ internal data class TangemPayDetailsUM( val isBalanceHidden: Boolean, val addFundsEnabled: Boolean, val cardFrozenState: CardFrozenState, + val betaNotificationConfig: NotificationConfig, ) internal data class TangemPayCardDetailsUM( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayTxHistoryDetailsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayTxHistoryDetailsUM.kt index d6d716c254..4d2aa7c66e 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayTxHistoryDetailsUM.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayTxHistoryDetailsUM.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.TextReference import kotlinx.collections.immutable.ImmutableList internal data class TangemPayTxHistoryDetailsUM( + val isBalanceHidden: Boolean, val title: TextReference, val iconState: ImageReference, val transactionTitle: TextReference, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt index eb7110543b..b1a7a7133a 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayAddFundsModel.kt @@ -4,7 +4,11 @@ import androidx.compose.runtime.Stable import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.domain.pay.TangemPaySwapDataFactory +import com.tangem.domain.models.ReceiveAddressModel +import com.tangem.domain.models.ReceiveAddressModel.NameService +import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory +import com.tangem.domain.pay.model.TangemPayTopUpData +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.tangempay.components.TangemPayAddFundsComponent import com.tangem.features.tangempay.entity.TangemPayAddFundsUM import com.tangem.features.tangempay.model.transformers.TangemPayAddFundsUMConverter @@ -16,7 +20,8 @@ import javax.inject.Inject internal class TangemPayAddFundsModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, - private val tangemPaySwapDataFactory: TangemPaySwapDataFactory, + private val tangemPayCryptoCurrencyFactory: TangemPayCryptoCurrencyFactory, + private val getUserWalletUseCase: GetUserWalletUseCase, ) : Model() { private val params = paramsContainer.require() @@ -24,13 +29,25 @@ internal class TangemPayAddFundsModel @Inject constructor( val uiState: TangemPayAddFundsUM = getInitialState() private fun getInitialState(): TangemPayAddFundsUM { - val data = tangemPaySwapDataFactory.create( - depositAddress = params.depositAddress, - chainId = params.chainId, - cryptoBalance = params.cryptoBalance, - fiatBalance = params.fiatBalance, - ).getOrNull() - + val userWallet = getUserWalletUseCase(params.walletId).getOrNull() + val currency = userWallet?.let { + tangemPayCryptoCurrencyFactory.create(userWallet = userWallet, chainId = params.chainId).getOrNull() + } + val data = currency?.let { + TangemPayTopUpData( + currency = currency, + walletId = params.walletId, + cryptoBalance = params.cryptoBalance, + fiatBalance = params.fiatBalance, + depositAddress = params.depositAddress, + receiveAddress = listOf( + ReceiveAddressModel( + nameService = NameService.Default, + value = params.depositAddress, + ), + ), + ) + } return TangemPayAddFundsUMConverter(listener = params.listener).convert(data) } 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 b3a558564e..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 @@ -17,15 +17,19 @@ import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfi import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase +import com.tangem.domain.feedback.SendFeedbackEmailUseCase +import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.feedback.models.WalletMetaInfo import com.tangem.domain.models.TokenReceiveConfig -import com.tangem.domain.pay.TangemPayTopUpData -import com.tangem.domain.pay.TangemPaySwapDataFactory +import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory import com.tangem.domain.pay.model.TangemPayCardBalance +import com.tangem.domain.pay.model.TangemPayTopUpData import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.domain.visa.model.TangemPayCardFrozenState import com.tangem.domain.visa.model.TangemPayTxHistoryItem +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.tangempay.TangemPayConstants import com.tangem.features.tangempay.components.AddFundsListener import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent @@ -55,7 +59,7 @@ import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") @Stable @ModelScoped internal class TangemPayDetailsModel @Inject constructor( @@ -69,8 +73,10 @@ internal class TangemPayDetailsModel @Inject constructor( private val uiMessageSender: UiMessageSender, private val cardDetailsEventListener: CardDetailsEventListener, private val txHistoryUpdateListener: TangemPayTxHistoryUpdateListener, - private val tangemPaySwapDataFactory: TangemPaySwapDataFactory, + private val tangemPayCryptoCurrencyFactory: TangemPayCryptoCurrencyFactory, private val orderRepository: CustomerOrderRepository, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, ) : Model(), TangemPayTxHistoryUiActions, TangemPayDetailIntents, AddFundsListener { private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require() @@ -95,6 +101,7 @@ internal class TangemPayDetailsModel @Inject constructor( val bottomSheetNavigation: SlotNavigation = SlotNavigation() init { + analytics.send(TangemPayAnalyticsEvents.MainScreenOpened()) handleBalanceHiding() fetchAddToWalletBanner() fetchBalance() @@ -206,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) { @@ -234,23 +242,22 @@ internal class TangemPayDetailsModel @Inject constructor( if (hasActiveWithdrawal) { showBottomSheetError(TangemPayDetailsErrorType.WithdrawInProgress) } else { - val data = tangemPaySwapDataFactory.create( - depositAddress = depositAddress, - chainId = params.config.chainId, - cryptoBalance = currentBalance.cryptoBalance, - fiatBalance = currentBalance.fiatBalance, - ).getOrNull() - if (data != null) { + val userWallet = getUserWalletUseCase(params.userWalletId).getOrNull() + val currency = userWallet?.let { + tangemPayCryptoCurrencyFactory.create(userWallet = userWallet, chainId = params.config.chainId) + .getOrNull() + } + if (currency != null) { router.push( AppRoute.Swap( - currencyFrom = data.currency, - userWalletId = data.walletId, + currencyFrom = currency, + userWalletId = params.userWalletId, isInitialReverseOrder = false, screenSource = AnalyticsParam.ScreensSources.TangemPay.value, tangemPayInput = AppRoute.Swap.TangemPayInput( - cryptoAmount = data.cryptoBalance, - fiatAmount = data.fiatBalance, - depositAddress = data.depositAddress, + cryptoAmount = currentBalance.availableForWithdrawal, + fiatAmount = currentBalance.availableForWithdrawal, + depositAddress = depositAddress, isWithdrawal = true, ), ), @@ -271,7 +278,13 @@ internal class TangemPayDetailsModel @Inject constructor( Timber.e(e) return@launch } - uiState.update(DetailsBalanceTransformer(balance = result)) + uiState.update( + transformer = DetailsBalanceTransformer( + balance = result, + userWallet = getUserWalletUseCase(params.userWalletId).getOrNull(), + cryptoCurrencyFactory = tangemPayCryptoCurrencyFactory, + ), + ) }.saveIn(fetchBalanceJobHolder) } @@ -299,6 +312,16 @@ internal class TangemPayDetailsModel @Inject constructor( }.saveIn(addToWalletBannerJobHolder) } + override fun onContactSupportClicked() { + modelScope.launch { + sendFeedbackEmailUseCase.invoke( + type = FeedbackEmailType.Visa.FeatureIsBeta( + walletMetaInfo = WalletMetaInfo(userWalletId = params.userWalletId), + ), + ) + } + } + override fun onRefreshSwipe(refreshState: ShowRefreshState) { modelScope.launch { uiState.update(TangemPayDetailsRefreshTransformer(isRefreshing = refreshState.value)) @@ -331,7 +354,7 @@ internal class TangemPayDetailsModel @Inject constructor( } override fun onClickSwap(data: TangemPayTopUpData) { - analytics.send(TangemPayAnalyticsEvents.SwapClicked) + analytics.send(TangemPayAnalyticsEvents.SwapClicked()) bottomSheetNavigation.dismiss() router.push( AppRoute.Swap( @@ -350,7 +373,7 @@ internal class TangemPayDetailsModel @Inject constructor( } override fun onClickReceive(data: TangemPayTopUpData) { - analytics.send(TangemPayAnalyticsEvents.ReceiveFundsClicked) + analytics.send(TangemPayAnalyticsEvents.ReceiveFundsClicked()) bottomSheetNavigation.dismiss() val config = TokenReceiveConfig( shouldShowWarning = false, @@ -367,7 +390,12 @@ internal class TangemPayDetailsModel @Inject constructor( } override fun onTransactionClick(item: TangemPayTxHistoryItem) { - bottomSheetNavigation.activate(TangemPayDetailsNavigation.TransactionDetails(item)) + bottomSheetNavigation.activate( + configuration = TangemPayDetailsNavigation.TransactionDetails( + transaction = item, + isBalanceHidden = uiState.value.isBalanceHidden, + ), + ) } override fun onClickTermsAndLimits() { 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 48d190991e..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 @@ -5,15 +5,15 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.url.UrlOpener +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 import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject @@ -21,39 +21,48 @@ import javax.inject.Inject @ModelScoped 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, + private val balanceHidingSettings: GetBalanceHidingSettingsUseCase, paramsContainer: ParamsContainer, ) : Model() { private val params = paramsContainer.require() - val uiState: TangemPayTxHistoryDetailsUM = TangemPayTxHistoryDetailsConverter.convert( - TangemPayTxHistoryDetailsConverter.Input( - item = params.transaction, - onExplorerClick = ::openExplorer, - onDisputeClick = ::dispute, - onDismiss = ::dismiss, - ), - ) + val uiState: StateFlow + field = MutableStateFlow( + value = TangemPayTxHistoryDetailsConverter.convert( + value = TangemPayTxHistoryDetailsConverter.Input( + item = params.transaction, + isBalanceHidden = params.isBalanceHidden, + onExplorerClick = ::openExplorer, + onDisputeClick = ::dispute, + onDismiss = ::dismiss, + ), + ), + ) + + init { + subscribeToBalanceHiding() + } fun dismiss() { params.onDismiss() } - fun openExplorer(txHash: String?) { + private fun subscribeToBalanceHiding() { + balanceHidingSettings.isBalanceHidden() + .onEach { isBalanceHidden -> uiState.update { it.copy(isBalanceHidden = isBalanceHidden) } } + .launchIn(modelScope) + } + + private fun openExplorer(txHash: String?) { txHash?.let(urlOpener::openUrlExternalBrowser) } - fun dispute() { + 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/DetailsBalanceTransformer.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt index 7817286e55..857920d456 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/DetailsBalanceTransformer.kt @@ -2,17 +2,24 @@ package com.tangem.features.tangempay.model.transformers import arrow.core.Either import com.tangem.core.error.UniversalError +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.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory import com.tangem.domain.pay.model.TangemPayCardBalance import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState import com.tangem.features.tangempay.entity.TangemPayDetailsUM import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.persistentListOf +import java.math.BigDecimal import java.util.Currency internal class DetailsBalanceTransformer( private val balance: Either, + private val cryptoCurrencyFactory: TangemPayCryptoCurrencyFactory, + private val userWallet: UserWallet?, ) : Transformer { override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM { @@ -21,22 +28,32 @@ internal class DetailsBalanceTransformer( TangemPayDetailsBalanceBlockState.Error(actionButtons = persistentListOf()) } is Either.Right -> { - TangemPayDetailsBalanceBlockState.Content( - isBalanceFlickering = false, - fiatBalance = getBalanceText(balance.value), - // TODO [REDACTED_TASK_KEY]: Add crypto balance when the BFF is ready - cryptoBalance = "", - actionButtons = prevState.balanceBlockState.actionButtons, - ) + val cryptoCurrency = userWallet?.let { + cryptoCurrencyFactory.create(userWallet, balance.value.chainId).getOrNull() + } + if (cryptoCurrency == null) { + TangemPayDetailsBalanceBlockState.Error(actionButtons = persistentListOf()) + } else { + TangemPayDetailsBalanceBlockState.Content( + isBalanceFlickering = false, + fiatBalance = getFiatBalanceText(balance.value), + cryptoBalance = getCryptoBalanceText(balance.value.cryptoBalance, cryptoCurrency), + actionButtons = prevState.balanceBlockState.actionButtons, + ) + } } } return prevState.copy(balanceBlockState = balance) } - private fun getBalanceText(balance: TangemPayCardBalance): String { + private fun getFiatBalanceText(balance: TangemPayCardBalance): String { val currency = Currency.getInstance(balance.currencyCode) return balance.fiatBalance.format { fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol) } } + + private fun getCryptoBalanceText(cryptoBalance: BigDecimal, cryptoCurrency: CryptoCurrency): String { + return cryptoBalance.format { crypto(cryptoCurrency = cryptoCurrency) } + } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayAddFundsUMConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayAddFundsUMConverter.kt index b770bcc649..4de43025ef 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayAddFundsUMConverter.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayAddFundsUMConverter.kt @@ -1,7 +1,7 @@ package com.tangem.features.tangempay.model.transformers import com.tangem.core.ui.extensions.TextReference -import com.tangem.domain.pay.TangemPayTopUpData +import com.tangem.domain.pay.model.TangemPayTopUpData import com.tangem.features.tangempay.components.AddFundsListener import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.entity.TangemPayAddFundsItemUM diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayFreezeUnfreezeStateTransformer.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayFreezeUnfreezeStateTransformer.kt index c6dc60c3d9..7fb2efcb5e 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayFreezeUnfreezeStateTransformer.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayFreezeUnfreezeStateTransformer.kt @@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.themedColor import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.visa.model.TangemPayCardFrozenState import com.tangem.features.tangempay.details.impl.R +import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItem import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItemType.FreezeCard import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItemType.UnfreezeCard @@ -31,9 +32,20 @@ internal class TangemPayFreezeUnfreezeStateTransformer( it.type == FreezeCard || it.type == UnfreezeCard } val dropdownMenuItems = createUpdatedMenuItems(filteredItems?.toPersistentList()) + val balanceBlockState = if (prevState.balanceBlockState is TangemPayDetailsBalanceBlockState.Content) { + val actionButtons = prevState.balanceBlockState.actionButtons.map { + it.copy(isEnabled = cardFrozenState == TangemPayCardFrozenState.Unfrozen) + } + prevState.balanceBlockState.copy( + actionButtons = actionButtons.toPersistentList(), + ) + } else { + prevState.balanceBlockState + } return prevState.copy( topBarConfig = prevState.topBarConfig.copy(items = dropdownMenuItems), cardFrozenState = converter.convert(cardFrozenState), + balanceBlockState = balanceBlockState, ) } 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 b6e45d0c9a..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 @@ -29,6 +29,7 @@ internal object TangemPayTxHistoryDetailsConverter : override fun convert(value: Input): TangemPayTxHistoryDetailsUM { val transaction = value.item return TangemPayTxHistoryDetailsUM( + isBalanceHidden = value.isBalanceHidden, title = transaction.extractDate(), iconState = transaction.extractIcon(), transactionTitle = transaction.extractTransactionTitle(), @@ -233,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, ), ) @@ -254,6 +255,7 @@ internal object TangemPayTxHistoryDetailsConverter : data class Input( val item: TangemPayTxHistoryItem, + val isBalanceHidden: Boolean, val onExplorerClick: (String?) -> Unit, val onDisputeClick: () -> Unit, val onDismiss: () -> Unit, 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/TangemPayDetailsScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt index aa81c82ede..6addbcf3d6 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt @@ -31,11 +31,10 @@ import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfi import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer import com.tangem.core.ui.components.dropdownmenu.TangemDropdownItem import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu +import com.tangem.core.ui.components.notifications.Notification +import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.components.text.applyBladeBrush -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.orMaskWithStars -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.TokenDetailsTopBarTestTags @@ -81,7 +80,9 @@ internal fun TangemPayDetailsScreen( ) { item(TangemPayCardDetailsUM::class.java) { cardDetailsBlockComponent.CardDetailsBlockContent( - modifier = Modifier.padding(horizontal = 16.dp).padding(top = 8.dp), + modifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 8.dp), state = cardDetailsState, ) } @@ -99,19 +100,6 @@ internal fun TangemPayDetailsScreen( } else -> Unit } - item( - key = TangemPayDetailsBalanceBlockState::class.java, - content = { - TangemPayDetailsBalanceBlock( - modifier = modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .padding(top = 12.dp) - .fillMaxWidth(), - state = state.balanceBlockState, - isBalanceHidden = state.isBalanceHidden, - ) - }, - ) if (state.addToWalletBlockState != null) { item( key = AddToWalletBlockState::class.java, @@ -125,15 +113,45 @@ internal fun TangemPayDetailsScreen( }, ) } + item( + key = TangemPayDetailsBalanceBlockState::class.java, + content = { + TangemPayDetailsBalanceBlock( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .padding(top = 12.dp) + .fillMaxWidth(), + state = state.balanceBlockState, + isBalanceHidden = state.isBalanceHidden, + ) + }, + ) + item( + key = "TANGEM_PAY_IS_IN_BETA", + content = { + TangemPayBetaBlock( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .padding(top = 12.dp) + .fillMaxWidth(), + config = state.betaNotificationConfig, + ) + }, + ) with(txHistoryComponent) { txHistoryContent(listState = listState, state = txHistoryState) } } } } } +@Composable +private fun TangemPayBetaBlock(config: NotificationConfig, modifier: Modifier = Modifier) { + Notification(modifier = modifier, config = config) +} + // region Balance block @Composable -internal fun TangemPayDetailsBalanceBlock( +private fun TangemPayDetailsBalanceBlock( state: TangemPayDetailsBalanceBlockState, isBalanceHidden: Boolean, modifier: Modifier = Modifier, @@ -157,12 +175,11 @@ internal fun TangemPayDetailsBalanceBlock( state = state, isBalanceHidden = isBalanceHidden, ) - // TODO [REDACTED_TASK_KEY]: Uncomment after adding crypto balance when the BFF is ready - // CryptoBalance( - // modifier = Modifier.padding(start = 12.dp, top = 4.dp), - // state = state, - // isBalanceHidden = isBalanceHidden, - // ) + CryptoBalance( + modifier = Modifier.padding(start = 12.dp, top = 4.dp), + state = state, + isBalanceHidden = isBalanceHidden, + ) if (state.actionButtons.isNotEmpty()) { HorizontalActionChips( modifier = Modifier.padding(top = 12.dp), @@ -336,6 +353,16 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider Text( modifier = Modifier.padding(top = 4.dp), - text = localTransaction, + text = localTransaction.orMaskWithStars(state.isBalanceHidden), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) @@ -158,6 +160,7 @@ private fun TangemPayTxHistoryDetailsContentPreview( private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterProvider( listOf( TangemPayTxHistoryDetailsUM( + isBalanceHidden = true, title = stringReference("12 June • 12:40"), iconState = ImageReference.Res(R.drawable.ic_category_24), transactionTitle = stringReference("Starbucks"), @@ -180,6 +183,7 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr dismiss = {}, ), TangemPayTxHistoryDetailsUM( + isBalanceHidden = true, title = stringReference("12 June • 12:40"), iconState = ImageReference.Res(R.drawable.ic_category_24), transactionTitle = stringReference("Starbucks"), @@ -205,10 +209,11 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr dismiss = {}, ), TangemPayTxHistoryDetailsUM( + 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", @@ -226,6 +231,7 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr dismiss = {}, ), TangemPayTxHistoryDetailsUM( + isBalanceHidden = false, title = stringReference("12 June • 12:40"), iconState = ImageReference.Res(R.drawable.ic_percent_24), transactionTitle = stringReference("Fee"), @@ -248,6 +254,7 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr dismiss = {}, ), TangemPayTxHistoryDetailsUM( + isBalanceHidden = false, title = stringReference("12 June • 12:40"), iconState = ImageReference.Res(R.drawable.ic_arrow_down_24), transactionTitle = stringReference("Deposit"), @@ -266,6 +273,7 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr dismiss = {}, ), TangemPayTxHistoryDetailsUM( + isBalanceHidden = false, title = stringReference("12 June • 12:40"), iconState = ImageReference.Res(R.drawable.ic_arrow_up_24), transactionTitle = stringReference("Withdrawal"), diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayDetailIntents.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayDetailIntents.kt index d4c2320e2e..912bfefbd5 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayDetailIntents.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayDetailIntents.kt @@ -3,6 +3,7 @@ package com.tangem.features.tangempay.utils import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig.ShowRefreshState internal interface TangemPayDetailIntents { + fun onContactSupportClicked() fun onRefreshSwipe(refreshState: ShowRefreshState) fun onClickAddFunds() fun onClickWithdraw() diff --git a/features/tangempay/onboarding/api/build.gradle.kts b/features/tangempay/onboarding/api/build.gradle.kts index a52e5bbccd..5d9c35cc08 100644 --- a/features/tangempay/onboarding/api/build.gradle.kts +++ b/features/tangempay/onboarding/api/build.gradle.kts @@ -13,6 +13,9 @@ dependencies { implementation(projects.core.decompose) implementation(projects.core.ui) + /** Domain */ + implementation(projects.domain.models) + /** Compose */ implementation(deps.compose.runtime) } \ No newline at end of file diff --git a/features/tangempay/onboarding/api/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayOnboardingComponent.kt b/features/tangempay/onboarding/api/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayOnboardingComponent.kt index 0ad307aa79..408d809672 100644 --- a/features/tangempay/onboarding/api/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayOnboardingComponent.kt +++ b/features/tangempay/onboarding/api/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayOnboardingComponent.kt @@ -2,15 +2,22 @@ package com.tangem.features.tangempay.components import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.wallet.UserWalletId interface TangemPayOnboardingComponent : ComposableContentComponent { sealed class Params { + + abstract val userWalletId: UserWalletId? + data class Deeplink( val deeplink: String, + override val userWalletId: UserWalletId?, ) : Params() - object ContinueOnboarding : Params() + data class ContinueOnboarding( + override val userWalletId: UserWalletId?, + ) : Params() } interface Factory : ComponentFactory diff --git a/features/tangempay/onboarding/impl/build.gradle.kts b/features/tangempay/onboarding/impl/build.gradle.kts index 0d109bd508..7b71ee3d94 100644 --- a/features/tangempay/onboarding/impl/build.gradle.kts +++ b/features/tangempay/onboarding/impl/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { /** Domain */ implementation(projects.domain.visa) + implementation(projects.domain.wallets) /** Data **/ implementation(projects.data.visa) diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/deeplink/DefaultOnboardVisaDeepLinkHandler.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/deeplink/DefaultOnboardVisaDeepLinkHandler.kt index 15313ee912..cb6bf2f491 100644 --- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/deeplink/DefaultOnboardVisaDeepLinkHandler.kt +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/deeplink/DefaultOnboardVisaDeepLinkHandler.kt @@ -1,10 +1,11 @@ package com.tangem.features.tangempay.deeplink import android.net.Uri -import dagger.assisted.Assisted import com.tangem.common.routing.AppRoute -import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.common.routing.AppRouter +import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase +import com.tangem.features.tangempay.TangemPayFeatureToggles +import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -12,11 +13,16 @@ internal class DefaultOnboardVisaDeepLinkHandler @AssistedInject constructor( @Assisted uri: Uri, appRouter: AppRouter, tangemPayFeatureToggles: TangemPayFeatureToggles, + getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, ) : OnboardVisaDeepLinkHandler { init { if (tangemPayFeatureToggles.isTangemPayEnabled) { - val mode = AppRoute.TangemPayOnboarding.Mode.Deeplink(uri.toString()) + val userWallet = getSelectedWalletSyncUseCase.invoke().getOrNull() + val mode = AppRoute.TangemPayOnboarding.Mode.Deeplink( + deeplink = uri.toString(), + userWalletId = userWallet?.walletId, + ) appRouter.push(AppRoute.TangemPayOnboarding(mode)) } else { appRouter.push(AppRoute.Home()) diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt index de4d3018a3..dc16c58424 100644 --- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt @@ -9,9 +9,13 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.navigation.url.UrlOpener import com.tangem.data.pay.util.TangemPayWalletsManager +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase import com.tangem.domain.tangempay.TangemPayAnalyticsEvents +import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.tangempay.TangemPayConstants import com.tangem.features.tangempay.components.TangemPayOnboardingComponent import com.tangem.features.tangempay.model.transformers.TangemPayOnboardingButtonLoadingTransformer @@ -37,6 +41,8 @@ internal class TangemPayOnboardingModel @Inject constructor( private val produceInitialDataUseCase: ProduceTangemPayInitialDataUseCase, private val urlOpener: UrlOpener, private val tangemPayWalletsManager: TangemPayWalletsManager, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val getSelectedWalletUseCase: GetSelectedWalletUseCase, ) : Model() { private val params = paramsContainer.require() @@ -44,7 +50,6 @@ internal class TangemPayOnboardingModel @Inject constructor( field = MutableStateFlow(getInitialState()) init { - analytics.send(TangemPayAnalyticsEvents.ActivationScreenOpened) modelScope.launch { when (params) { is TangemPayOnboardingComponent.Params.ContinueOnboarding -> { @@ -52,7 +57,7 @@ internal class TangemPayOnboardingModel @Inject constructor( } is TangemPayOnboardingComponent.Params.Deeplink -> { repository.validateDeeplink(params.deeplink) - .onRight { isValid -> if (isValid) showOnboarding() } + .onRight { isValid -> if (isValid) showOnboarding() else back() } .onLeft { back() } } } @@ -60,6 +65,8 @@ internal class TangemPayOnboardingModel @Inject constructor( } private fun showOnboarding() { + // TODO: move analytics to init block [REDACTED_JIRA] + analytics.send(TangemPayAnalyticsEvents.ActivationScreenOpened()) uiState.update { TangemPayOnboardingScreenState.Content( onBack = it.onBack, @@ -74,8 +81,9 @@ internal class TangemPayOnboardingModel @Inject constructor( private suspend fun checkCustomerInfo() { // TODO implement selector + val userWalletId = getUserWalletForPay(params.userWalletId) repository.getCustomerInfo( - userWalletId = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking().walletId, + userWalletId = userWalletId, ) // selector .onRight { customerInfo -> @@ -92,17 +100,35 @@ internal class TangemPayOnboardingModel @Inject constructor( .onLeft { back() } } + private fun getUserWalletForPay(userWalletId: UserWalletId?): UserWalletId { + val userWallet = userWalletId?.let { getUserWalletUseCase(it).getOrNull() } + return if (userWallet?.isMultiCurrency == true) { + userWallet.walletId + } else { + tryGetSelectedWalletId() + } + } + + private fun tryGetSelectedWalletId(): UserWalletId { + val selectedWallet = getSelectedWalletUseCase.sync().getOrNull() + return if (selectedWallet?.isMultiCurrency == true) { + selectedWallet.walletId + } else { + tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking().walletId + } + } + private fun onTermsClick() { - analytics.send(TangemPayAnalyticsEvents.ViewTermsClicked) + analytics.send(TangemPayAnalyticsEvents.ViewTermsClicked()) urlOpener.openUrl(TangemPayConstants.TERMS_AND_LIMITS_LINK) } private fun onGetCardClick() { - analytics.send(TangemPayAnalyticsEvents.GetCardClicked) + analytics.send(TangemPayAnalyticsEvents.GetCardClicked()) uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true)) modelScope.launch { // TODO implement selector - val userWalletId = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking().walletId + val userWalletId = getUserWalletForPay(params.userWalletId) val result = produceInitialDataUseCase(userWalletId) if (result.isLeft()) { Timber.e("Error producing initial data: ${result.leftOrNull()?.message}") @@ -134,7 +160,7 @@ internal class TangemPayOnboardingModel @Inject constructor( router.replaceAll( AppRoute.Wallet, AppRoute.Kyc( - userWalletId = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking().walletId, + userWalletId = getUserWalletForPay(params.userWalletId), ), ) } 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/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt index 4f4dbe32e0..8c3ca7e009 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt @@ -77,7 +77,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor( params = YieldSupplyComponent.Params( userWalletId = params.userWalletId, cryptoCurrency = params.currency, - handleNavigation = (params.navigationAction as? NavigationAction.YieldSupply) + shouldHandleNavigation = (params.navigationAction as? NavigationAction.YieldSupply) ?.isActive, ), ) diff --git a/features/wallet/impl/detekt-baseline-debug.xml b/features/wallet/impl/detekt-baseline-debug.xml index cf87ca958c..f13bc25643 100644 --- a/features/wallet/impl/detekt-baseline-debug.xml +++ b/features/wallet/impl/detekt-baseline-debug.xml @@ -5,7 +5,6 @@ BooleanPropertyNaming:DefaultUserWalletsFetcher.kt$DefaultUserWalletsFetcher$@Assisted("onlyMultiCurrency") private val onlyMultiCurrency: Boolean BooleanPropertyNaming:DraggableItem.kt$DraggableItem$abstract val showShadow: Boolean BooleanPropertyNaming:DraggableItem.kt$DraggableItem.RoundingMode$abstract val showGap: Boolean - BooleanPropertyNaming:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$val accessCodeSkipped = array[7] as Boolean BooleanPropertyNaming:GetSingleWalletWarningsFactory.kt$GetSingleWalletWarningsFactory$private var readyForRateAppNotification = false BooleanPropertyNaming:GetSingleWalletWarningsFactory.kt$GetSingleWalletWarningsFactory$val userHasWalletOrWallet2 = userWallets.filterIsInstance<UserWallet.Cold>().any { val typesResolver = it.scanResponse.cardTypesResolver typesResolver.isTangemWallet() || typesResolver.isWallet2() } BooleanPropertyNaming:OrganizeTokensState.kt$OrganizeTokensState.ActionsConfig$val showApplyProgress: Boolean = false @@ -78,7 +77,6 @@ MultilineLambdaItParameter:WalletModel.kt$WalletModel${ walletsUpdateActionResolver.resolve( wallets = it, currentState = stateHolder.value, ) } MultilineLambdaItParameter:WalletNFTListSubscriber.kt$WalletNFTListSubscriber${ stateHolder.update( SetNFTCollectionsTransformer( userWalletId = userWallet.walletId, nftCollections = it, onItemClick = { clickIntents.onNFTClick(userWallet) }, ), ) } MultilineLambdaItParameter:WalletNameMigrationUseCase.kt$WalletNameMigrationUseCase${ val defaultName = it.name val suggestedWalletName = suggestedWalletName(defaultName, existingNames) if (defaultName != suggestedWalletName) { userWalletsListRepository.saveWithoutLock(it.copy(name = suggestedWalletName), canOverride = true) } Timber.tag("Migrated names").e(it.walletId.toString() + " " + suggestedWalletName) } - MultilineLambdaItParameter:WalletNotifications.kt${ // TODO develop promo banner general component when (it) { is WalletNotification.SwapPromo -> { // Use it on new promo action } is WalletNotification.NoteMigration -> { NoteMigrationNotification( config = it.config, modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), ) } is WalletNotification.FinishWalletActivation -> { Notification( config = it.config, modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), ) } else -> { Notification( config = it.config, modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null), iconTint = when (it) { is WalletNotification.Critical -> TangemTheme.colors.icon.warning is WalletNotification.Informational -> TangemTheme.colors.icon.accent is WalletNotification.RateApp -> TangemTheme.colors.icon.attention is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1 is WalletNotification.UsedOutdatedData -> TangemTheme.colors.text.attention else -> null }, ) } } } MultilineLambdaItParameter:WalletScreen.kt${ PaddingValues( bottom = it.calculateBottomPadding() + marketHintAproxHeight + 52.dp, ) } MultilineLambdaItParameter:WalletScreen.kt${ WalletSnackbarHost( snackbarHostState = it, event = state.event, modifier = Modifier .padding(bottom = TangemTheme.dimens.spacing4) .navigationBarsPadding(), ) } MultilineLambdaItParameter:WalletScreen.kt${ balancesAndLimitsBlock( modifier = itemModifier, state = it.balancesAndLimitBlockState, ) } @@ -91,7 +89,6 @@ MultilineLambdaItParameter:WalletWithFundsChecker.kt$WalletWithFundsChecker${ val amount = it.value.amount ?: return@any false !amount.isZero() } MultilineLambdaItParameter:WalletsUpdateActionResolver.kt$WalletsUpdateActionResolver${ if (it.warnings.any { it is WalletNotification.FinishWalletActivation }) { it.walletCardState.id } else { null } } NamedArguments:BasicAccountListSubscriber.kt$BasicAccountListSubscriber$updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap) - NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addFinishWalletActivationNotification(userWallet, totalFiatBalance, clickIntents, accessCodeSkipped) NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents) NamedArguments:GetMultiWalletWarningsFactory.kt$GetMultiWalletWarningsFactory$addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents) NamedArguments:TangemSnapFlingBehavior.kt$HighVelocityApproachAnimation$animateDecay(offset, animationState, decayAnimationSpec, onAnimationStep) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/TangemPayMainInfoManager.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/TangemPayMainInfoManager.kt deleted file mode 100644 index 184cc926bd..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/TangemPayMainInfoManager.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.feature.wallet.child.wallet.model - -import arrow.core.Either -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.pay.model.MainScreenCustomerInfo -import com.tangem.domain.pay.model.TangemPayCustomerInfoError -import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -internal class TangemPayMainInfoManager @Inject constructor( - private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, -) { - - val mainScreenCustomerInfo: - StateFlow>?> - field = MutableStateFlow(null) - - suspend fun refreshTangemPayInfo(userWalletId: UserWalletId) { - val info = tangemPayMainScreenCustomerInfoUseCase(userWalletId) - mainScreenCustomerInfo.value = Pair(userWalletId, info) - } -} \ No newline at end of file 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 fe327cc2e0..2338f56503 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 @@ -20,6 +20,7 @@ import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.pay.repository.OnboardingRepository +import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.domain.settings.* import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase import com.tangem.domain.wallets.usecase.* @@ -94,7 +95,7 @@ internal class WalletModel @Inject constructor( private val tangemPayOnboardingRepository: OnboardingRepository, private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles, private val accountsFeatureToggles: AccountsFeatureToggles, - private val tangemPayMainInfoManager: TangemPayMainInfoManager, + private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, val screenLifecycleProvider: ScreenLifecycleProvider, val innerWalletRouter: InnerWalletRouter, ) : Model() { @@ -357,7 +358,10 @@ internal class WalletModel @Inject constructor( }.distinctUntilChanged(), transform = ::Pair, ).onEach { (inBackground, userWalletId) -> - if (inBackground) return@onEach + if (inBackground) { + updateTangemPayJobHolder.cancel() + return@onEach + } val savedCustomerInfo = tangemPayOnboardingRepository.getSavedCustomerInfo(userWalletId) @@ -368,15 +372,15 @@ internal class WalletModel @Inject constructor( if (isShouldLaunchPeriodicUpdate) { updateTangemPayJobHolder.cancel() modelScope.launch { - tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) while (isActive) { delay(TANGEM_PAY_UPDATE_INTERVAL) - tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } }.saveIn(updateTangemPayJobHolder) } else { // Don't refresh customer info periodically if the card was already issued, only update on swipe to refresh - tangemPayMainInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } }.launchIn(modelScope) } @@ -494,7 +498,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) @@ -516,7 +520,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) @@ -656,15 +660,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 7ccd2dac17..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.wallets.usecase.GetUserWalletUseCase -import com.tangem.feature.wallet.child.wallet.model.TangemPayMainInfoManager +import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.features.tangempay.TangemPayFeatureToggles import kotlinx.coroutines.launch @@ -41,9 +39,8 @@ 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 tangemPayInfoManager: TangemPayMainInfoManager, + private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, private val uiMessageSender: UiMessageSender, ) : BaseWalletClickIntents(), TangemPayIntents { @@ -54,13 +51,13 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( ) { return } - tangemPayInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } override fun onRefreshPayToken(userWalletId: UserWalletId) { modelScope.launch { produceInitialDataTangemPay.invoke(userWalletId) - tangemPayInfoManager.refreshTangemPayInfo(userWalletId) + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } } @@ -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.details_row_title_contact_to_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 0cd9d5e7b1..4c0038bfe2 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 @@ -2,11 +2,11 @@ package com.tangem.feature.wallet.child.wallet.model.intents import arrow.core.getOrElse import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.tokens.TokenItemStateConverter.ApySource import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.models.account.Account -import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.staking.YieldBalance import com.tangem.domain.models.wallet.UserWallet @@ -51,7 +51,12 @@ internal interface WalletContentClickIntents { fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) - fun onApyLabelClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus, apy: String) + fun onApyLabelClick( + userWalletId: UserWalletId, + currencyStatus: CryptoCurrencyStatus, + apySource: ApySource, + apy: String, + ) fun onYieldPromoCloseClick() @@ -166,11 +171,17 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } } - override fun onApyLabelClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus, apy: String) { - val navigationAction = if (currencyStatus.currency is CryptoCurrency.Token) { - NavigationAction.YieldSupply(currencyStatus.value.yieldSupplyStatus?.isActive == true) - } else { - NavigationAction.Staking + override fun onApyLabelClick( + userWalletId: UserWalletId, + currencyStatus: CryptoCurrencyStatus, + apySource: ApySource, + apy: String, + ) { + val navigationAction = when (apySource) { + ApySource.STAKING -> NavigationAction.Staking + ApySource.YIELD_SUPPLY -> { + NavigationAction.YieldSupply(currencyStatus.value.yieldSupplyStatus?.isActive == true) + } } sendApyLabelClickAnalytics(navigationAction, currencyStatus) 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 279b90f3ee..2572b3c5ff 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 @@ -319,6 +319,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, + ) }, ) @@ -366,6 +371,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) + } } } @@ -610,5 +625,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/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 5d2fbf72f2..98d68adf5f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -111,8 +111,14 @@ internal class DefaultWalletRouter @Inject constructor( ) } - override fun openTangemPayOnboarding() { - router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding)) + override fun openTangemPayOnboarding(userWalletId: UserWalletId) { + router.push( + AppRoute.TangemPayOnboarding( + AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding( + userWalletId = userWalletId, + ), + ), + ) } override fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index a71880095a..cf9d3ea617 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -63,7 +63,7 @@ internal interface InnerWalletRouter { fun openTokenReceiveBottomSheet(tokenReceiveConfig: TokenReceiveConfig) - fun openTangemPayOnboarding() + fun openTangemPayOnboarding(userWalletId: UserWalletId) fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig) 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 ff945a2974..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,8 +27,8 @@ 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 - else -> TangemPayAnalyticsEvents.IssuingBannerDisplayed + cardInfo != null && productInstance != null -> return + else -> TangemPayAnalyticsEvents.IssuingBannerDisplayed() } if (sentEvents.add(event)) { 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 b080fd487e..c56c99c1b2 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 @@ -61,6 +61,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 c2db89f8c8..010639c906 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 @@ -22,12 +21,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 @@ -38,7 +34,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 kotlinx.collections.immutable.ImmutableList @@ -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/state/model/TangemPayState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt index 71ad45e71a..bbe8472d49 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TangemPayState.kt @@ -9,6 +9,8 @@ internal sealed class TangemPayState { object Empty : TangemPayState() + data object Loading : TangemPayState() + data class Progress( val title: TextReference, val description: TextReference, 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 4553d72fc9..22028e514d 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 @@ -50,7 +50,7 @@ sealed class WalletNotification(val config: NotificationConfig) { title = resourceReference(R.string.warning_backup_errors_title), subtitle = resourceReference(R.string.warning_backup_errors_message), buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig( - text = resourceReference(id = R.string.details_row_title_contact_to_support), + text = resourceReference(id = R.string.common_contact_support), onClick = onSupportClick, ), ) @@ -367,6 +367,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/TangemPayLoadingStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayLoadingStateTransformer.kt new file mode 100644 index 0000000000..f731cb119d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayLoadingStateTransformer.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers + +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState + +internal class TangemPayLoadingStateTransformer(userWalletId: UserWalletId) : WalletStateTransformer(userWalletId) { + override fun transform(prevState: WalletState): WalletState { + return if (prevState is WalletState.MultiCurrency.Content) { + prevState.copy(tangemPayState = TangemPayState.Loading) + } else { + prevState + } + } +} \ No newline at end of file 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 9037cb7b57..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 @@ -57,9 +57,15 @@ internal class TokenListStateConverter( clickIntents.onTokenItemLongClick(selectedWallet.walletId, currencyStatus) } - private val onApyLabelClick: (currencyStatus: CryptoCurrencyStatus, apy: String) -> Unit = - { currencyStatus, apy -> - clickIntents.onApyLabelClick(selectedWallet.walletId, currencyStatus, apy) + private val onApyLabelClick: + (currencyStatus: CryptoCurrencyStatus, apySource: TokenItemStateConverter.ApySource, apy: String) -> Unit = + { currencyStatus, apySource, apy -> + clickIntents.onApyLabelClick( + userWalletId = selectedWallet.walletId, + currencyStatus = currencyStatus, + apySource = apySource, + apy = apy, + ) } private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter( @@ -69,7 +75,7 @@ internal class TokenListStateConverter( stakingApyMap = stakingApyMap, onItemClick = { _, status -> onTokenClick(accountId, status) }, onItemLongClick = { _, status -> onTokenLongClick(accountId, status) }, - onApyLabelClick = { status, apy -> onApyLabelClick(status, apy) }, + onApyLabelClick = { status, apySource, apy -> onApyLabelClick(status, apySource, apy) }, onYieldPromoCloseClick = clickIntents::onYieldPromoCloseClick, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt index 0664439d54..6f182566d8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/TangemPayMainSubscriber.kt @@ -2,24 +2,24 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.model.MainCustomerInfoContentState import com.tangem.domain.pay.model.MainScreenCustomerInfo import com.tangem.domain.pay.model.TangemPayCustomerInfoError import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository +import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.domain.visa.model.TangemPayCardFrozenState -import com.tangem.feature.wallet.child.wallet.model.TangemPayMainInfoManager import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletTangemPayAnalyticsEventSender import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController -import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayHiddenStateTransformer -import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshNeededStateTransformer -import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayUnavailableStateTransformer -import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayUpdateInfoStateTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.* import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.onEach import timber.log.Timber @Suppress("LongParameterList") @@ -29,7 +29,7 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( private val clickIntents: WalletClickIntents, private val innerWalletRouter: InnerWalletRouter, private val cardDetailsRepository: TangemPayCardDetailsRepository, - private val tangemPayMainInfoManager: TangemPayMainInfoManager, + private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, private val analytics: WalletTangemPayAnalyticsEventSender, ) : WalletSubscriber() { @@ -38,11 +38,10 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( } private fun subscribeOnTangemPayInfoUpdates(): Flow<*> { - return tangemPayMainInfoManager.mainScreenCustomerInfo - .filterNotNull() - .filter { it.first == userWallet.walletId } + return tangemPayMainScreenCustomerInfoUseCase(userWalletId = userWallet.walletId) .distinctUntilChanged() - .onEach { (userWalletId, mainInfoData) -> + .onEach { mainInfoData -> + val userWalletId = userWallet.walletId mainInfoData.onLeft { tangemPayError -> when (tangemPayError) { TangemPayCustomerInfoError.RefreshNeededError -> { @@ -66,13 +65,23 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( ) } } - }.onRight { data -> - updateTangemPay(data, userWalletId) - analytics.send(customerInfo = data) - } + }.onRight { contentState -> handleContentState(state = contentState) } } } + private suspend fun handleContentState(state: MainCustomerInfoContentState) { + val userWalletId = userWallet.walletId + when (state) { + MainCustomerInfoContentState.Loading -> stateController.update( + transformer = TangemPayLoadingStateTransformer(userWalletId), + ) + is MainCustomerInfoContentState.Content -> { + updateTangemPay(data = state.info, userWalletId = userWalletId) + analytics.send(customerInfo = state.info) + } + } + } + private suspend fun updateTangemPay(data: MainScreenCustomerInfo, userWalletId: UserWalletId) { val cardFrozenState = data.info.productInstance?.cardId?.let { cardDetailsRepository.cardFrozenStateSync(it) } @@ -82,7 +91,7 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( userWalletId = userWalletId, value = data, cardFrozenState = cardFrozenState, - onClickKyc = innerWalletRouter::openTangemPayOnboarding, + onClickKyc = { innerWalletRouter.openTangemPayOnboarding(userWalletId) }, onIssuingCard = clickIntents::onIssuingCardClicked, onIssuingFailed = clickIntents::onIssuingFailedClicked, openDetails = { config -> diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 3a0f5202b9..16212dc865 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -224,9 +224,9 @@ private fun WalletContent( contentType = selectedWallet.tangemPayState::class.java, ) { TangemPayMainScreenBlock( - selectedWallet.tangemPayState, + state = selectedWallet.tangemPayState, isBalanceHidden = state.isHidingMode, - itemModifier, + modifier = itemModifier, ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayLoadingScreenBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayLoadingScreenBlock.kt new file mode 100644 index 0000000000..a2dbe13375 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayLoadingScreenBlock.kt @@ -0,0 +1,37 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components.visa + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.CircleShimmer +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.res.TangemTheme + +@Composable +internal fun TangemPayLoadingScreenBlock(modifier: Modifier = Modifier) { + Row( + modifier = modifier + .fillMaxWidth() + .background(color = TangemTheme.colors.background.primary, shape = TangemTheme.shapes.roundedCornersXMedium) + .padding(horizontal = 12.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + CircleShimmer(modifier = Modifier.size(36.dp)) + Column( + modifier = Modifier.padding(start = 12.dp), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + RectangleShimmer(modifier = Modifier.padding(vertical = 4.dp).sizeIn(minWidth = 70.dp, minHeight = 12.dp)) + RectangleShimmer(modifier = Modifier.padding(vertical = 2.dp).sizeIn(minWidth = 52.dp, minHeight = 12.dp)) + } + SpacerWMax() + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + RectangleShimmer(modifier = Modifier.padding(vertical = 4.dp).sizeIn(minWidth = 40.dp, minHeight = 12.dp)) + RectangleShimmer(modifier = Modifier.padding(vertical = 2.dp).sizeIn(minWidth = 40.dp, minHeight = 12.dp)) + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt index d2750a8903..24f11c043c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayMainScreenBlock.kt @@ -23,6 +23,7 @@ internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Bo is TangemPayState.RefreshNeeded -> TangemPayRefreshBlock(state, modifier) is TangemPayState.TemporaryUnavailable -> TangemPayUnavailableBlock(state, modifier) is TangemPayState.FailedIssue -> TangemPayFailedIssueState(state, modifier) + TangemPayState.Loading -> TangemPayLoadingScreenBlock(modifier) } } @@ -32,6 +33,8 @@ internal fun TangemPayMainScreenBlock(state: TangemPayState, isBalanceHidden: Bo private fun TangemPayMainScreenBlockPreview() { TangemThemePreview { Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { + TangemPayMainScreenBlock(state = TangemPayState.Loading, isBalanceHidden = false) + TangemPayMainScreenBlock( Progress( title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title), diff --git a/features/yield-supply/api/detekt-baseline-debug.xml b/features/yield-supply/api/detekt-baseline-debug.xml deleted file mode 100644 index 2c1d5f69ba..0000000000 --- a/features/yield-supply/api/detekt-baseline-debug.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - BooleanPropertyNaming:YieldSupplyComponent.kt$YieldSupplyComponent.Params$val handleNavigation: Boolean? = null - UseEmptyCounterpart:YieldSupplyAnalytics.kt$YieldSupplyAnalytics$mapOf() - - diff --git a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyComponent.kt b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyComponent.kt index c2f5cf26f0..d1919f0781 100644 --- a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyComponent.kt +++ b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyComponent.kt @@ -10,7 +10,7 @@ interface YieldSupplyComponent : ComposableContentComponent { data class Params( val userWalletId: UserWalletId, val cryptoCurrency: CryptoCurrency, - val handleNavigation: Boolean? = null, + val shouldHandleNavigation: Boolean? = null, ) interface Factory : ComponentFactory diff --git a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt index 942e3a3d8a..37b00d61b2 100644 --- a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt +++ b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt @@ -8,7 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM sealed class YieldSupplyAnalytics( event: String, - params: Map = mapOf(), + params: Map = emptyMap(), ) : AnalyticsEvent(category = "Earning", event = event, params = params) { data class EarningScreenInfoOpened( 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 266b8f2df4..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 @@ -27,12 +27,7 @@ import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus -import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase -import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase -import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase -import com.tangem.domain.yield.supply.usecase.YieldSupplyIsAvailableUseCase -import com.tangem.domain.yield.supply.usecase.YieldSupplyGetDustMinAmountUseCase -import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase +import com.tangem.domain.yield.supply.usecase.* import com.tangem.features.yield.supply.api.YieldSupplyComponent import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.impl.R @@ -288,8 +283,7 @@ internal class YieldSupplyModel @Inject constructor( private fun loadActiveState(cryptoCurrencyStatus: CryptoCurrencyStatus, yieldSupplyStatus: YieldSupplyStatus) { val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return val showWarningIcon = !yieldSupplyStatus.isAllowedToSpend - val state = uiState.value - val isShowInfoIconPrevState = when (state) { + val isShowInfoIconPrevState = when (val state = uiState.value) { is YieldSupplyUM.Content -> state.showInfoIcon else -> false }