diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 5fc86d29bc..78be297b0f 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 5fc86d29bc2c0dc7c057ab0242b2cfa3e9f48daf +Subproject commit 78be297b0f09bc2be1e9b0fbab9384f17bf94c37 diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 690ab43218..076a21b682 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -40,6 +40,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.settings.SetGooglePayAvailabilityUseCase import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase +import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.staking.SendUnsubmittedHashesUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager @@ -129,6 +130,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject lateinit var cardRepository: CardRepository + @Inject + lateinit var shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase + @Inject lateinit var backupServiceHolder: BackupServiceHolder diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 2815df1738..abbd2d80de 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -19,6 +19,7 @@ import com.tangem.common.routing.AppRouter import com.tangem.core.abtests.manager.ABTestsManager import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.api.ParamsInterceptor +import com.tangem.core.analytics.filter.AppsFlyerEventFilter import com.tangem.core.analytics.filter.OneTimeEventFilter import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam @@ -423,6 +424,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration. factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder()) factory.addFilter(oneTimeEventFilter) + factory.addFilter(AppsFlyerEventFilter()) val buildData = AnalyticsHandlerBuilder.Data( application = application, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt index 1fdb08a185..a9a25c5acc 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.analytics.handlers.amplitude import com.tangem.core.analytics.api.AnalyticsHandler import com.tangem.core.analytics.api.AnalyticsUserIdHandler +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder class AmplitudeAnalyticsHandler( @@ -9,7 +10,6 @@ class AmplitudeAnalyticsHandler( ) : AnalyticsHandler, AnalyticsUserIdHandler { override fun id(): String = ID - override fun setUserId(userId: String) { client.setUserId(userId) } @@ -18,8 +18,8 @@ class AmplitudeAnalyticsHandler( client.clearUserId() } - override fun send(eventId: String, params: Map) { - client.logEvent(eventId, params) + override fun send(event: AnalyticsEvent) { + client.logEvent(event.id, event.params) } companion object { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsClient.kt index aa2b3b40db..755f393182 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsClient.kt @@ -3,8 +3,10 @@ package com.tangem.tap.common.analytics.handlers.appsflyer import android.content.Context import com.appsflyer.AppsFlyerLib import com.tangem.core.analytics.api.EventLogger +import com.tangem.core.analytics.api.UserIdHolder +import com.tangem.tap.common.analytics.handlers.firebase.UnderscoreAnalyticsEventConverter -interface AppsFlyerAnalyticsClient : EventLogger +interface AppsFlyerAnalyticsClient : EventLogger, UserIdHolder internal class AppsFlyerClient( private val context: Context, @@ -13,6 +15,7 @@ internal class AppsFlyerClient( ) : AppsFlyerAnalyticsClient { private val appsFlyerLib: AppsFlyerLib = AppsFlyerLib.getInstance() + private val eventConverter = UnderscoreAnalyticsEventConverter() init { appsFlyerLib.init(key, null, context) @@ -20,7 +23,19 @@ internal class AppsFlyerClient( appsFlyerLib.start(context) } + override fun setUserId(userId: String) { + appsFlyerLib.setCustomerUserId(userId) + } + + override fun clearUserId() { + appsFlyerLib.setCustomerUserId(null) + } + override fun logEvent(event: String, params: Map) { - appsFlyerLib.logEvent(context, event, params) + appsFlyerLib.logEvent( + context, + event, + eventConverter.convertEventParams(params), + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt index bd7036b570..d9f80b3476 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt @@ -1,21 +1,38 @@ package com.tangem.tap.common.analytics.handlers.appsflyer import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.api.AnalyticsUserIdHandler import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent +import com.tangem.core.analytics.models.AppsFlyerOnlyEvent import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder class AppsFlyerAnalyticsHandler( private val client: AppsFlyerAnalyticsClient, -) : AnalyticsHandler { +) : AnalyticsHandler, AnalyticsUserIdHandler { override fun id(): String = ID - override fun send(eventId: String, params: Map) { - client.logEvent(eventId, params) + override fun send(event: AnalyticsEvent) { + when (event) { + is AppsFlyerOnlyEvent -> { + client.logEvent(event.id, event.params) + } + is AppsFlyerIncludedEvent -> { + client.logEvent( + event = AnalyticsEvent(category = event.category, event = event.appsFlyerReplacedEvent).id, + params = event.params, + ) + } + } } - override fun send(event: AnalyticsEvent) { - super.send(event) + override fun setUserId(userId: String) { + client.setUserId(userId) + } + + override fun clearUserId() { + client.clearUserId() } companion object { @@ -23,12 +40,10 @@ class AppsFlyerAnalyticsHandler( } class Builder : AnalyticsHandlerBuilder { - override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = null - // disabled for now until analytics strategy is defined - // when { - // !data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerApiKey, data.config.appsAppId) - // data.isDebug && data.logConfig.appsflyer -> AppsFlyerLogClient(data.jsonConverter) - // else -> null - // }?.let { AppsFlyerAnalyticsHandler(it) } + override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when { + !data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerApiKey, data.config.appsAppId) + data.isDebug && data.logConfig.isAppsflyerLogEnabled -> AppsFlyerLogClient(data.jsonConverter) + else -> null + }?.let { AppsFlyerAnalyticsHandler(it) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerLogClient.kt index 36284bd8df..a953e00897 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerLogClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerLogClient.kt @@ -12,4 +12,12 @@ internal class AppsFlyerLogClient( override fun logEvent(event: String, params: Map) { logger.logEvent(event, params) } + + override fun setUserId(userId: String) { + // No-op + } + + override fun clearUserId() { + // No-op + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt index 7e7106ebb4..4d662a7828 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt @@ -1,8 +1,8 @@ package com.tangem.tap.common.analytics.handlers.firebase import com.tangem.core.analytics.api.AnalyticsErrorHandler -import com.tangem.core.analytics.api.AnalyticsHandler import com.tangem.core.analytics.api.AnalyticsExceptionHandler +import com.tangem.core.analytics.api.AnalyticsHandler import com.tangem.core.analytics.api.AnalyticsUserIdHandler import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.ExceptionAnalyticsEvent @@ -25,8 +25,8 @@ class FirebaseAnalyticsHandler( client.clearUserId() } - override fun send(eventId: String, params: Map) { - client.logEvent(eventId, params) + override fun send(event: AnalyticsEvent) { + client.logEvent(event.id, event.params) } override fun sendException(event: ExceptionAnalyticsEvent) { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt index 6268ebf230..d982f6e7ae 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseClient.kt @@ -20,7 +20,7 @@ internal class FirebaseClient : FirebaseAnalyticsClient { private val fbAnalytics = Firebase.analytics private val fbCrashlytics = Firebase.crashlytics - private val eventConverter = FirebaseAnalyticsEventConverter() + private val eventConverter = UnderscoreAnalyticsEventConverter() override fun setUserId(userId: String) { Firebase.analytics.setUserId(userId) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/UnderscoreAnalyticsEventConverter.kt similarity index 95% rename from app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsEventConverter.kt rename to app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/UnderscoreAnalyticsEventConverter.kt index 559fc3418b..8d4e3d1261 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/UnderscoreAnalyticsEventConverter.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.handlers.firebase -internal class FirebaseAnalyticsEventConverter { +internal class UnderscoreAnalyticsEventConverter { fun convertEventName(event: String): String { return convertString(event, FIREBASE_EVENT_NAME_MAX_LENGTH) 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 80e06699c1..fb02cff093 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt @@ -6,6 +6,7 @@ import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys import com.tangem.datasource.local.preferences.utils.getObjectMapSync +import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.getSyncOrNull import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.visa.TangemPayStorage @@ -21,6 +22,7 @@ import javax.inject.Singleton private const val AUTH_TOKENS_DEFAULT_KEY = "tangem_pay_default_key" private const val WITHDRAW_ORDER_ID_KEY = "tangem_pay_withdraw_order_id_key" +@Suppress("TooManyFunctions") @Singleton internal class DefaultTangemPayStorage @Inject constructor( @ApplicationContext applicationContext: Context, @@ -53,6 +55,12 @@ internal class DefaultTangemPayStorage @Inject constructor( } } + override suspend fun clearCustomerWalletAddress(userWalletId: UserWalletId) { + withContext(dispatcherProvider.io) { + appPreferencesStore.store(PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId), "") + } + } + override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: TangemPayAuthTokens) = withContext(dispatcherProvider.io) { val json = tokensAdapter.toJson(tokens) @@ -70,6 +78,10 @@ internal class DefaultTangemPayStorage @Inject constructor( ?.let(tokensAdapter::fromJson) } + override suspend fun clearAuthTokens(customerWalletAddress: String) { + secureStorage.delete(createAuthTokensKey(customerWalletAddress)) + } + override suspend fun storeOrderId(customerWalletAddress: String, orderId: String) { withContext(dispatcherProvider.io) { appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), orderId) @@ -109,15 +121,6 @@ internal class DefaultTangemPayStorage @Inject constructor( return appPreferencesStore.getSyncOrNull(PreferencesKeys.getTangemPayCheckCustomerByWalletId(userWalletId)) } - override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) = - withContext(dispatcherProvider.io) { - secureStorage.delete(createAuthTokensKey(customerWalletAddress)) - appPreferencesStore.store(PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId), "") - appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), "") - appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), false) - appPreferencesStore.store(PreferencesKeys.getTangemPayHideOnboardingKey(userWalletId), false) - } - override suspend fun storeWithdrawOrder(userWalletId: UserWalletId, orderId: String) { appPreferencesStore.editData { mutablePreferences -> val orders = mutablePreferences.getObjectMap(PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY) @@ -159,6 +162,28 @@ internal class DefaultTangemPayStorage @Inject constructor( } } + override suspend fun storeTangemPayEligibility(eligibility: Boolean) { + withContext(dispatcherProvider.io) { + appPreferencesStore.store(key = PreferencesKeys.TANGEM_PAY_ELIGIBILITY_KEY, value = eligibility) + } + } + + override suspend fun getTangemPayEligibility(): Boolean { + return withContext(dispatcherProvider.io) { + appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.TANGEM_PAY_ELIGIBILITY_KEY, default = false) + } + } + + override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) = + withContext(dispatcherProvider.io) { + secureStorage.delete(createAuthTokensKey(customerWalletAddress)) + appPreferencesStore.store(PreferencesKeys.getTangemPayCheckCustomerByWalletId(userWalletId), false) + appPreferencesStore.store(PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId), "") + appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), "") + appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), false) + appPreferencesStore.store(PreferencesKeys.getTangemPayHideOnboardingKey(userWalletId), false) + } + private fun createAuthTokensKey(address: String): String = "${AUTH_TOKENS_DEFAULT_KEY}_$address" private fun createWithdrawOrderIdKey(userWalletId: UserWalletId): String = "${WITHDRAW_ORDER_ID_KEY}_$userWalletId" diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index 981b99ecf8..ef82b7c01f 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -519,13 +519,14 @@ internal class DefaultTangemSdkManager( } override suspend fun tangemPayProduceInitialCredentials( - cardId: String, + preflightReadFilter: PreflightReadFilter, ): Either { return coroutineScope { val result = runTaskAsyncReturnOnMain( runnable = tangemPayChallengeTaskFactory.create(coroutineScope = this), - cardId = cardId, + cardId = null, initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)), + preflightReadFilter = preflightReadFilter, ) return@coroutineScope when (result) { @@ -536,14 +537,15 @@ internal class DefaultTangemSdkManager( } override suspend fun getWithdrawalSignature( - cardId: String, hash: String, + preflightReadFilter: PreflightReadFilter, ): Either { return coroutineScope { val result = runTaskAsyncReturnOnMain( - runnable = TangemPaySignWithdrawalHashTask(cardId = cardId, hash = hash.hexToBytes()), - cardId = cardId, + runnable = TangemPaySignWithdrawalHashTask(hash = hash.hexToBytes()), + cardId = null, initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)), + preflightReadFilter = preflightReadFilter, ) return@coroutineScope when (result) { diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index 54e9e33aca..00e4ea971e 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -219,14 +219,14 @@ class MockTangemSdkManager( } override suspend fun tangemPayProduceInitialCredentials( - cardId: String, + preflightReadFilter: PreflightReadFilter, ): Either { error("Not implemented") } override suspend fun getWithdrawalSignature( - cardId: String, hash: String, + preflightReadFilter: PreflightReadFilter, ): Either { error("Not implemented") } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPayGenerateAddressAndSignChallengeTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPayGenerateAddressAndSignChallengeTask.kt index 4550d3dc52..c4e437b4ff 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPayGenerateAddressAndSignChallengeTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/TangemPayGenerateAddressAndSignChallengeTask.kt @@ -62,7 +62,6 @@ class TangemPayGenerateAddressAndSignChallengeTask @AssistedInject constructor( val dataToSign = VisaDataToSignByCustomerWallet(hashToSign = challenge.challenge) val approveResult = runVisaCustomerWalletApproveTask( session = session, - cardId = card.cardId, targetAddress = address, dataToSign = dataToSign, ) @@ -103,14 +102,13 @@ class TangemPayGenerateAddressAndSignChallengeTask @AssistedInject constructor( private suspend fun runVisaCustomerWalletApproveTask( session: CardSession, - cardId: String, targetAddress: String, dataToSign: VisaDataToSignByCustomerWallet, ): CompletionResult { val deferred = CompletableDeferred>() val task = VisaCustomerWalletApproveTask( visaDataForApprove = VisaCustomerWalletApproveTask.Input( - cardId = cardId, + cardId = null, targetAddress = targetAddress, hashToSign = dataToSign.hashToSign, sign = dataToSign::sign, 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 2cf1b0f42c..150c5c35ba 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 @@ -14,10 +14,7 @@ import com.tangem.domain.visa.error.VisaActivationError import com.tangem.operations.derivation.DeriveWalletPublicKeyTask import com.tangem.operations.sign.SignHashCommand -class TangemPaySignWithdrawalHashTask( - private val cardId: String, - private val hash: ByteArray, -) : CardSessionRunnable { +class TangemPaySignWithdrawalHashTask(private val hash: ByteArray) : CardSessionRunnable { override fun run(session: CardSession, callback: CompletionCallback) { val card = session.environment.card ?: run { @@ -25,11 +22,6 @@ class TangemPaySignWithdrawalHashTask( return } - if (card.cardId != cardId) { - callback(CompletionResult.Failure(VisaActivationError.CardIdNotMatched.tangemError)) - return - } - proceedSign(card, session, callback) } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt index d4c6dc4800..3f416e65e0 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt @@ -126,7 +126,8 @@ class VisaCustomerWalletApproveTask( extendedPublicKey = extendedPublicKey, ) - visaDataForApprove.sign(rsvSignature, visaDataForApprove.targetAddress) + val signedData = visaDataForApprove.sign(rsvSignature, visaDataForApprove.targetAddress) + callback(CompletionResult.Success(signedData)) } is CompletionResult.Failure -> { callback(CompletionResult.Failure(result.error)) 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 424ce82802..dedbfff746 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 @@ -52,6 +52,8 @@ class TokenItemStateConverter( }, private val onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)? = null, private val onYieldPromoCloseClick: (() -> Unit)? = null, + private val onYieldPromoShown: ((cryptoCurrency: CryptoCurrency) -> Unit)? = null, + private val onYieldPromoClicked: ((cryptoCurrency: CryptoCurrency) -> Unit)? = null, private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { currencyStatus -> createTitleState( currencyStatus = currencyStatus, @@ -76,6 +78,8 @@ class TokenItemStateConverter( yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKey, onApyLabelClick = onApyLabelClick, onYieldPromoCloseClick = onYieldPromoCloseClick, + onYieldPromoShown = onYieldPromoShown, + onYieldPromoClicked = onYieldPromoClicked, ) }, private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null, @@ -395,12 +399,15 @@ class TokenItemStateConverter( } } + @Suppress("LongParameterList") private fun createPromoBannerState( status: CryptoCurrencyStatus, yieldModuleApyMap: Map, yieldSupplyPromoBannerKey: String?, onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?, onYieldPromoCloseClick: (() -> Unit)?, + onYieldPromoShown: ((cryptoCurrency: CryptoCurrency) -> Unit)?, + onYieldPromoClicked: ((cryptoCurrency: CryptoCurrency) -> Unit)?, ): TokenItemState.PromoBannerState { val token = status.currency as? CryptoCurrency.Token ?: return TokenItemState.PromoBannerState.Empty if (status.value !is CryptoCurrencyStatus.Loaded) { @@ -420,11 +427,15 @@ class TokenItemStateConverter( wrappedList(yieldSupplyApy), ), onPromoBannerClick = { + onYieldPromoClicked?.invoke(status.currency) onApyLabelClick?.invoke(status, ApySource.YIELD_SUPPLY, yieldSupplyApy.toString()) }, onCloseClick = { onYieldPromoCloseClick?.invoke() }, + onPromoShown = { + onYieldPromoShown?.invoke(status.currency) + }, ) } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AppsFlyerOnlyEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AppsFlyerOnlyEvent.kt new file mode 100644 index 0000000000..a265fcfd9f --- /dev/null +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AppsFlyerOnlyEvent.kt @@ -0,0 +1,15 @@ +package com.tangem.core.analytics.models + +/** + * Marker interface for AppsFlyer events + * Only events implementing this interface will be sent to AppsFlyer + */ +interface AppsFlyerOnlyEvent + +/** + * Marker interface for AppsFlyer included events + * Events implementing this interface will be sent to AppsFlyer along with other analytics handlers + */ +interface AppsFlyerIncludedEvent { + val appsFlyerReplacedEvent: String +} \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt index b511dd504e..c05783e382 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt @@ -134,6 +134,28 @@ sealed class MainScreenAnalyticsEvent( STATE to state, ), ) + + data class YieldPromo( + val token: String, + val blockchain: String, + ) : MainScreenAnalyticsEvent( + event = "Yield Promo", + params = mapOf( + TOKEN_PARAM to token, + BLOCKCHAIN to blockchain, + ), + ) + + data class YieldPromoClicked( + val token: String, + val blockchain: String, + ) : MainScreenAnalyticsEvent( + event = "Yield Promo Clicked", + params = mapOf( + TOKEN_PARAM to token, + BLOCKCHAIN to blockchain, + ), + ) // endregion companion object { diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt index f4bc6d2ca5..c1cad4c1a7 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt @@ -2,6 +2,8 @@ package com.tangem.core.analytics.models.event import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent +import com.tangem.core.analytics.models.AppsFlyerOnlyEvent sealed class OnboardingAnalyticsEvent( category: String, @@ -14,6 +16,8 @@ sealed class OnboardingAnalyticsEvent( params: Map = mapOf(), ) : OnboardingAnalyticsEvent(category = "Onboarding", event = event, params = params) { + class AppsFlyerOnlyEntryScreenView : Onboarding(event = "wallet_entry_screen_view"), AppsFlyerOnlyEvent + class Started( source: String, ) : Onboarding( @@ -64,7 +68,12 @@ sealed class OnboardingAnalyticsEvent( put("Seed Phrase Length", seedPhraseLength.toString()) } }, - ) + ), AppsFlyerIncludedEvent { + override val appsFlyerReplacedEvent = when (creationType) { + WalletCreationType.NewSeed -> "wallet_created_successfully" + WalletCreationType.SeedImport -> "wallet_imported" + } + } sealed class WalletCreationType(val value: String) { data object NewSeed : WalletCreationType(value = "New Seed") @@ -85,6 +94,7 @@ sealed class OnboardingAnalyticsEvent( AnalyticsParam.SOURCE to source, ), ) + class ButtonImportWallet : SeedPhrase("Button - Import Wallet") class ImportSeedPhraseScreenOpened : SeedPhrase("Import Seed Phrase Screen Opened") class ButtonImport : SeedPhrase("Button - Import") diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt index 08e59781a0..868a56d99c 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt @@ -27,11 +27,7 @@ interface AnalyticsHandler : AnalyticsEventHandler { fun id(): String - fun send(eventId: String, params: Map = emptyMap()) - - override fun send(event: AnalyticsEvent) { - send(event.id, event.params) - } + override fun send(event: AnalyticsEvent) } interface AnalyticsHandlerHolder { diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/filter/AppsFlyerEventFilter.kt b/core/analytics/src/main/java/com/tangem/core/analytics/filter/AppsFlyerEventFilter.kt new file mode 100644 index 0000000000..d316469bb1 --- /dev/null +++ b/core/analytics/src/main/java/com/tangem/core/analytics/filter/AppsFlyerEventFilter.kt @@ -0,0 +1,23 @@ +package com.tangem.core.analytics.filter + +import com.tangem.core.analytics.api.AnalyticsEventFilter +import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent +import com.tangem.core.analytics.models.AppsFlyerOnlyEvent + +class AppsFlyerEventFilter : AnalyticsEventFilter { + + override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = + event is AppsFlyerOnlyEvent || event is AppsFlyerIncludedEvent + + override suspend fun canBeSent(event: AnalyticsEvent): Boolean = true + + override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean { + return when (event) { + is AppsFlyerOnlyEvent -> handler.id() == "AppsFlyer" + is AppsFlyerIncludedEvent -> true + else -> false + } + } +} \ No newline at end of file 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 dce534df8d..1cb7bb7d24 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 @@ -39,6 +39,10 @@ "name": "TANGEM_PAY_ENABLED", "version": "5.31.0" }, + { + "name": "TANGEM_PAY_ENTRYPOINT_ENABLED", + "version": "undefined" + }, { "name": "NEW_TOKEN_RECEIVE_ENABLED", "version": "5.28.0" diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt index 07719ff953..773dbb7b72 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt @@ -29,6 +29,12 @@ interface TangemPayApi { @Path("customer_wallet_id") customerWalletId: String, ): ApiResponse + @PATCH("v1/customer/pay-enabled") + suspend fun setTangemPayEnabledStatus( + @Header("Authorization") authHeader: String, + @Body body: SetTangemPayEnabledRequest, + ): ApiResponse + @POST("v1/deeplink/validate") suspend fun validateDeeplink(@Body body: DeeplinkValidityRequest): ApiResponse @@ -56,6 +62,12 @@ interface TangemPayApi { @Body body: CardDetailsRequest, ): ApiResponse + @GET("v1/customer/card/pin") + suspend fun getPin( + @Header("Authorization") authHeader: String, + @Header("X-Session-Id") sessionId: String, + ): ApiResponse + @PUT("v1/customer/card/pin") suspend fun setPin( @Header("Authorization") authHeader: String, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GetPinResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GetPinResponse.kt new file mode 100644 index 0000000000..ad9da0eda5 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GetPinResponse.kt @@ -0,0 +1,14 @@ +package com.tangem.datasource.api.pay.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetPinResponse(@Json(name = "result") val result: Result?) { + + @JsonClass(generateAdapter = true) + data class Result( + @Json(name = "secret") val secret: String, + @Json(name = "iv") val iv: String, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/SetTangemPayEnabledRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/SetTangemPayEnabledRequest.kt new file mode 100644 index 0000000000..c159a2418b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/SetTangemPayEnabledRequest.kt @@ -0,0 +1,9 @@ +package com.tangem.datasource.api.pay.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SetTangemPayEnabledRequest( + @Json(name = "is_tangem_pay_enabled") val isTangemPayEnabled: Boolean, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardDetailsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardDetailsResponse.kt index 17b226bacc..599d94d6b1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardDetailsResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CardDetailsResponse.kt @@ -17,7 +17,6 @@ data class CardDetailsResponse( @Json(name = "card_number_end") val cardNumberEnd: String, @Json(name = "pan") val pan: Secret, @Json(name = "cvv") val cvv: Secret, - @Json(name = "pin") val pin: Secret?, ) @JsonClass(generateAdapter = true) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CheckCustomerWalletResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CheckCustomerWalletResponse.kt index 01ee25582c..0a522486f5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CheckCustomerWalletResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CheckCustomerWalletResponse.kt @@ -9,5 +9,6 @@ data class CheckCustomerWalletResponse( ) { data class Result( @Json(name = "id") val id: String?, + @Json(name = "is_tangem_pay_enabled") val isTangemPayEnabled: Boolean?, ) } \ No newline at end of file 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 78e99e42bd..544a55357f 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 @@ -154,6 +154,7 @@ object PreferencesKeys { } val TANGEM_PAY_WITHDRAW_ORDERS_KEY by lazy { stringPreferencesKey(name = "tangemPayWithdrawOrders") } + val TANGEM_PAY_ELIGIBILITY_KEY by lazy { booleanPreferencesKey(name = "tangemPayEligibility") } fun getShouldShowNotificationKey(key: String) = booleanPreferencesKey("showShowNotificationUM_$key") // endregion diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt index a9279e9193..591bc9f6be 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt @@ -3,14 +3,18 @@ package com.tangem.datasource.local.visa import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.visa.model.TangemPayAuthTokens +@Suppress("TooManyFunctions") interface TangemPayStorage { suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String) suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String? + suspend fun clearCustomerWalletAddress(userWalletId: UserWalletId) + suspend fun storeAuthTokens(customerWalletAddress: String, tokens: TangemPayAuthTokens) suspend fun getAuthTokens(customerWalletAddress: String): TangemPayAuthTokens? + suspend fun clearAuthTokens(customerWalletAddress: String) suspend fun storeOrderId(customerWalletAddress: String, orderId: String) @@ -33,6 +37,8 @@ interface TangemPayStorage { suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean suspend fun storeHideOnboardingBanner(userWalletId: UserWalletId, hide: Boolean) + suspend fun storeTangemPayEligibility(eligibility: Boolean) + suspend fun getTangemPayEligibility(): Boolean suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) } \ No newline at end of file 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 9e3d88dcb4..55d88cd09e 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 @@ -32,8 +32,16 @@ class NetworkLogsSaveInterceptor( @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() + val host = request.url.host + val path = request.url.encodedPath + val isRestrictedUrl = restrictedForLogURLs.contains(host + path) + val isRestrictedHost = restrictedForLogHosts.any { host.contains(it) } - logRequestMessage(chain, request) + if (isRestrictedUrl || isRestrictedHost) { + logEmptyRequestMessage(chain, request) + } else { + logRequestMessage(chain, request) + } val startNs = System.nanoTime() val response: Response @@ -44,11 +52,6 @@ class NetworkLogsSaveInterceptor( throw e } - 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 { @@ -58,6 +61,13 @@ class NetworkLogsSaveInterceptor( return response } + private fun logEmptyRequestMessage(chain: Interceptor.Chain, request: Request) { + val connection = chain.connection() + val connectionProtocol = if (connection != null) " ${connection.protocol()}" else "" + + saveLogMessage("--> ${request.method} ${request.url}$connectionProtocol\n") + } + private fun logRequestMessage(chain: Interceptor.Chain, request: Request) { val connection = chain.connection() val connectionProtocol = if (connection != null) " ${connection.protocol()}" else "" diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index edaac6a969..81ee6497c5 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -4,7 +4,7 @@ Trotzdem überspringen Zugangscode nicht festgelegt Code ändern - Dein Zugangscode dient zum Entsperren Deiner Wallet und zum Schutz des Zugriffs auf Deine Vermögenswerte. + Dein Zugangscode entsperrt und schützt den Zugriff auf Deine Geldbörse. Trotzdem verwenden Dieser Zugangscode kann leicht erraten werden Zugangscode eingeben @@ -44,10 +44,12 @@ Speichern Kontoname Der Kontoname ist bereits vorhanden + Der Kontoname wird bereits verwendet. Konto Neues Konto Konto hinzufügen Konto bearbeiten + Bitte versuche es später erneut. Sollte das Problem weiterhin bestehen, kontaktiere bitte unseren Support. Wir helfen Dir gerne bei der Lösung. %1$s in %2$s Hauptkonto Du hast das Limit von %1$s aktiven Konten bereits überschritten. Archiviere eines zur Wiederherstellung @@ -95,7 +97,7 @@ Tokens im %1$s -Netzwerk werden von dieser Karte oder Ring aufgrund einer Firmware-Einschränkung nicht unterstützt. Hast du Probleme beim Scannen deiner Karte oder Ring? Diese Karte oder Ring ist für die Zusammenarbeit mit Tangem nicht geeignet - Lege zunächst einen Zugangscode fest, um die Biometrie zu aktivieren. + Lege einen Zugangscode fest, um die Biometrie zu aktivieren. Verwende die %1$s um Deine Wallet zu entsperren und sensible Aktionen wie das Signieren von Transaktionen zu bestätigen. Bei Hardware-Wallets ist zum Signieren weiterhin eine Karte oder ein Ring erforderlich. Standardgebühr Aktiviere die Option Standardgebühr, um die Transaktionsgebühren automatisch festzulegen und die Gebührenseite beim Senden von Geldern zu überspringen. Du kannst bei Bedarf jederzeit zu dieser Seite zurückkehren. @@ -144,10 +146,10 @@ 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. + Die Biometrie ist auf Deinem Gerät deaktiviert, daher kannst Du sie nicht zum Entsperren Deiner Wallets verwenden. Aktiviere die Biometrie in den Geräteeinstellungen, um diese Methode wieder nutzen zu können. 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. + Du hast das Limit an biometrischen Entsperrversuchen erreicht. Bitte entsperre Deine Wallet mit einer Karte/einem Ring oder gib Deinen 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. @@ -373,6 +375,7 @@ Allgemeine Geschäftsbedingungen Nutzungsbedingungen An + Zu %s Heute %d Token @@ -384,6 +387,7 @@ Überweisung Die Daten konnten nicht geladen werden… Ich verstehe + Ich verstehe, fahre bitte fort. Es ist ein Fehler aufgetreten. Bitte versuche es erneut. Nicht erreichbar Staking beenden @@ -397,6 +401,7 @@ Verfügbare Netzwerke Die Ableitung Deines Tokens entspricht der Ableitung von %1$s. Dein Token wird diesem Konto gutgeschrieben. Die Herleitung stammt aus einem anderen Bericht. + Token hinzugefügt %1$s Konto Vertragsadresse Vertragsadresse ist ungültig Bitte wähle das Netzwerk @@ -448,7 +453,7 @@ Überprüfe deine Internetverbindung oder wechseln zu einem anderen Netzwerk Nutzungsbedingungen Standardadresse - Legacy adresse + Legacy %s adresse Empfangen von Vermögenswerten %s Adresse Das Senden von Vermögenswerten in anderen Netzwerken führt zu dauerhaftem Verlust. @@ -541,6 +546,7 @@ Bitte sag uns, welche Karte oder Ring du hast Hallo Support-Team, Bitte erzähle uns mehr über dein Problem. Jedes kleine Detail kann helfen. + Problem mit der Sicherung Zuvor aktivierte Wallet Meine Vorschläge Kann eine Karte oder Ring nicht scannen @@ -593,10 +599,13 @@ Aktualisiere Deine aktuelle Wallet Gehe zum Backup Bitte sicher Deine Wallet, bevor Du einen Zugangscode erstellst. + Sicherung zuerst beenden Zuerst die Sicherung abschließen Unvollständig Andere Methoden + Speicher Deinen Wiederherstellungssatz an einem sicheren Ort und halte diesen stets geheim, um Dein Geld zu schützen. Wiederherstellungs-Phrase + Um Deine Wallet mit einem Zugangscode zu sichern, schließe den Sicherungsvorgang ab. Um Deine Wallet auf Hardware umzustellen, erstelle vorher ein Backup. Deine privaten Schlüssel sind sicher verschlüsselt und auf Deinem Telefon gespeichert. Schlüssel werden in der App gespeichert @@ -617,8 +626,9 @@ Für diese Wallet existiert ein Backup. Überprüfe dieses bitte, bevor Du sie entfernst, um sicherzustellen, dass Du sie später wiederherstellen kannst. Wenn Du diese Wallet ohne Backup entfernst, verlierst Du dauerhaft den Zugriff auf Deine Assets. Diese Wallet dauerhaft entfernen? - Mir ist bewusst, dass ich den Zugriff auf meine Wallet verliere kann, wenn ich sie vor der Entfernung nicht gesichert habe. + Mir ist bewusst, dass ich den Zugriff auf meine Wallet verlieren kann, wenn ich sie vor der Entfernung nicht gesichert habe. Mir ist bewusst, dass durch das Entfernen meiner Wallet diese nicht gelöscht, sondern lediglich von meinem Gerät entfernt wird. + Upgrade Es wird keine Seed-Phrase mehr benötigt – Deine Tangem-Karte oder Dein Tangem-Ring wird zu Deinem sicheren Backup. Backup mit Tangem Ein Upgrade ist nicht möglich. Auf diesem Gerät ist bereits eine Wallet vorhanden. @@ -719,6 +729,7 @@ Top-Gewinner Top-Verlierer Beliebt + Ertragsmodus Staking ist der einfachste Weg, um Belohnungen für Deine Kryptowährung zu erhalten. %s Verdiene bis zu %s APY Token hinzugefügt @@ -793,6 +804,19 @@ 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 + Gefällt mir + + %dStunde her + %dStunden her + + + %dMinute her + %dMinuten her + + Kurze Zusammenfassung + Verwandte Nachrichten + Verwandte Token + Quellen Auf dem Laufenden bleiben NFC ist auf deinem Gerät nicht verfügbar Über NFT @@ -968,6 +992,7 @@ Andere Währungen Beliebte Fiats Suche nach Währung + Diese Transaktion wurde bereits verarbeitet. Es sind keine weiteren Maßnahmen erforderlich. Die besten Preise erzielen... Sofort Durch die Nutzung der Onramp-Funktionalität stimmst Du den %1$s und %2$s des Anbieters zu. @@ -1064,6 +1089,7 @@ Karte oder Ring zurücksetzen Mir ist bewusst, dass ich nach der Durchführung dieser Aktion keinen Zugriff mehr auf die aktuelle Wallet habe. Mir ist klar, dass ich diese Karte oder Ring nicht verwenden kann, um meinen Zugangscode auf den anderen Karten oder Ringe der aktuellen Wallet wiederherzustellen + Mir ist bewusst, dass ich den Zugang zu meiner Tangem Pay Karte und allen darauf befindlichen Geldern vollständig verliere, ohne die Möglichkeit der Wiederherstellung Durch das Zurücksetzen auf Werkseinstellungen wird die Wallet vollständig von der ausgewählten Karte oder Ring gelöscht. Du kannst die aktuelle Wallet nicht wiederherstellen oder die Karte oder Ring verwenden, um den Zugangscode wiederherzustellen. Beim Zurücksetzen auf die Werkseinstellungen wird die Wallet der ausgewählten Karte oder Ring vollständig gelöscht und aus der App entfernt. Es ist nicht möglich, die aktuelle Wallet wiederherzustellen. Alle Tangem-Geräte wurden zurückgesetzt. @@ -1217,6 +1243,8 @@ Eine Netzwerkgebühr ist eine kleine Zahlung, die erforderlich ist, um Deine Transaktion auf der Blockchain zu verarbeiten und zu bestätigen. Um mit dem Staking zu beginnen, muss Dein TON-Konto mit einer Transaktion von 1 TON aktiviert werden. Das Guthaben verbleibt auf Deinem Konto, dieser Schritt dient lediglichder aktivierung für das Staking. Kontoaktivierung + Die Netzwerkgebühr hat sich geändert. Bitte überprüfe den neuen Betrag, bevor Du fortfährst. + Netzwerkgebühr aktualisiert Die Anzahl der zu stakenden Krypros muss mindesten %s betragen Der Stakingbetrag wird aufgrund der Netzwerkregeln auf %1$s TRX aufgerundet. Der Betrag der unstaked wird, wird aufgrund der Netzwerkregeln auf %1$s TRX gerundet. @@ -1255,6 +1283,7 @@ Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Du die Verwendung Deines Tokens für das Staking genehmigst. Indem Du die Staking-Funktionalität nutzt, stimmst Du den %1$s und %2$s des Anbieters zu. Gesperrt + Höchstbetrag: %s Migrieren Natives Staking Derzeit sind keine aktiven Validatoren für das Staking verfügbar. Bitte versuchen Sie es später erneut. @@ -1407,6 +1436,7 @@ Ihre Karte ist eingefroren. Hilfe erhalten Andere + Nicht nutzbar auf gerooteten Geräten Abgeschlossen Abgelehnt Ausstehend @@ -1419,6 +1449,8 @@ Entsperren der Karte fehlgeschlagen. Versuchen Sie es später erneut. Ihre Karte ist entsperrt. Abhebung + Auf gerooteten Geräten nicht nutzbar. + KYC abbrechen Guthaben hinzufügen Aufladeoptionen Kartennummer @@ -1446,6 +1478,7 @@ Alles erledigt! Deine Karte ist einsatzbereit. Karte zu Google Pay hinzufügen Karte zu Apple Pay hinzufügen + Pin Code Teile Deine Adresse mit oder zeig den QR-Code. Es wurden technische Probleme festgestellt. Bitte versuche es später erneut oder kontaktiere den Support. Empfangen ist jetzt nicht verfügbar @@ -1454,12 +1487,15 @@ Tausche beliebige Vermögenswerte in Deinem Portfolio gegen eine Karte. Kartendetails Karte entsperren + Komm zurück zur App, falls du es vergisst. + Dein PIN-Code 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. + Mir ist bewusst, dass ich den Zugriff auf meine Tangem Pay Card und alle darauf befindlichen Guthaben vollständig und ohne Möglichkeit der Wiederherstellung verliere. 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 @@ -1471,11 +1507,14 @@ Ausstellung Deiner Karte Wir bereiten Ihre Karte vor. Dies kann etwas dauern. Tangem Pay + Konvertierung bestätigen + Möchtest Du den KYC wirklich abbrechen? Du kannst später jederzeit weiter machen. 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 + Über die Schaltflächen unten kannst Du Deinen aktuellen KYC-Status einsehen oder ihn abbrechen. Holen Sie sich Ihre kostenlose virtuelle Tangem Visa-Karte Nutzen Sie USDC für alltägliche Zahlungen Karte erhalten @@ -1487,15 +1526,18 @@ Unerreichte Privatsphäre Erhalten Sie Ihre kostenlose Tangem Pay Card in wenigen Minuten Zahlungskonto - Synchronisierung des Zahlungskontos erforderlich + Zahlungskonto ist nicht synchronisiert + Ungültige PIN: Sequenzen oder Wiederholungen vermeiden Wir beheben ein technisches Problem. Bitte versuchen Sie es später erneut. Service vorübergehend nicht verfügbar Daten können derzeit nicht angezeigt werden, Kartenzahlungen funktionieren jedoch weiterhin. - Synchronisation erforderlich + Satz \nPIN-Code + Nicht synchronisiert + Zugang wiederherstellen Nutzen Sie USDC für alltägliche Zahlungen - Tangem Pay ist vorübergehend nicht verfügbar + Tangem Pay ist vorübergehend nicht erreichbar. Tangem Pay - Verwenden Sie Ihre Karte oder Ihren Ring, um den Zugriff auf Ihr Zahlungskonto wiederherzustellen + Klicken Sie auf die Schaltfläche unten, um den Zugriff wiederherzustellen Ihr PIN-Code Das ist meine Wallet Guthaben versteckt @@ -1756,6 +1798,8 @@ OK, habe ich verstanden! Echt toll! Aktualisieren + Laut der offiziellen Dokumentation von Clore werden alle Münzen, die vor dem 21. Dezember erhalten wurden, in Clore (ERC-20 Token) migriert; Münzen, die nach diesem Datum erhalten wurden, nicht. Eine Lösung für den Transfer ist in Arbeit — bleibt dran. + Migration des Clore-Netzwerks Du befindest sich derzeit im Demo-Modus Demo-Modus aktiv Die Karte, die du gescannt hast, ist eine Entwicklerkarte. Verwenden diese nicht zur Erstellung Ihrer Wallet. @@ -1958,7 +2002,7 @@ Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden Wenn der Yield-Modus aktiviert ist, gehen alle zukünftigen Einzahlungen an diese Adresse an Aave. Du kannst über Dein Guthaben weiterhin frei verfügen. Deine %s wird an Aave übermittelt - Die Lieferung von %1$s %2$s an Aave steht noch aus. + Lieferung %1$s %2$s nach Aave Genehmigen Die Genehmigung Deines Tokens wurde widerrufen. Erteilen diese erneut, um die Servicefunktionalität fortzusetzen. Genehmigung erforderlich @@ -2039,6 +2083,7 @@ Ertragsmodus Bearbeitung Deiner Einzahlung Ertragsmodus + Yield-Mode-Vertragsbereitstellung Ertragsmodus aktivieren %1$s geliefert an Aave Ertragsmodus deaktiviert diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 61e6f5eefa..1c463c9d8e 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -80,6 +80,7 @@ Seleccione una billetera para iniciar sesión ¡Bienvenido de nuevo! Realizó una copia de seguridad de su billetera correctamente. + Estas palabras son irrecuperables si se pierden. Guárdelas en un lugar seguro. 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. Estas palabras no pueden recuperarse si se pierden. Asegúrese de guardarlas en un lugar seguro. @@ -87,6 +88,7 @@ Guarde estas %s palabras en un lugar seguro, como un administrador de contraseñas, y nunca las comparta con nadie. No se puede restaurar Frase de recuperación + Nunca comparta estas palabras con nadie. Tangem nunca se las pedirá. Las palabras %s que aparecen a continuación son la frase de recuperación de su billetera. Úselas para restaurar su billetera si pierde su dispositivo. Escriba estas %s palabras en orden y guárdelas en un lugar privado y seguro La responsabilidad total sobre la seguridad y copia de seguridad de la billetera y su frase de recuperación recae en el usuario, no en Tangem. Frase de recuperación @@ -399,7 +401,7 @@ Compruebe su conexión a internet o cambia a una red diferente Condiciones de uso Dirección por defecto - Dirección Legacy + Dirección %s Legacy Recibir activos %s dirección Enviar activos a otras redes resultará en una pérdida permanente. @@ -489,6 +491,7 @@ Por favor, díganos qué tarjeta o anillo tiene Hola equipo de soporte, Por favor, cuéntenos más sobre tu problema. Cada pequeño detalle puede ayudar. + Problema con la copia de seguridad Billetera previamente activada Mis recomendaciones No se puede escanear una tarjeta/anillo @@ -507,6 +510,13 @@ Para continuar, conceda a los contratos inteligentes de %1s permiso para utilizar su %2s Dar autorización Ilimitado + Su copia de seguridad se crea utilizando 2 o 3 tarjetas Tangem. Guárdalas en lugares seguros y separados para protegerlas de pérdidas o daños. + Copia de seguridad con varias tarjetas + Agregar billetera Tangem + Su clave privada se genera directamente dentro de la tarjeta Tangem y nunca sale de ella. + Generación de claves + Todas las operaciones criptográficas ocurren dentro del chip seguro, certificado contra la clonación y la manipulación física. + Seguridad a nivel de hardware Agregar Billetera Existente Crear Nueva Billetera Pedir Tangem @@ -515,7 +525,73 @@ a %s En la red %s ¿Está seguro de que desea salir del proceso de creación de código de acceso? + Hacer copia ahora + Para completar la configuración, haga una copia de seguridad de su billetera y proteja la aplicación con un código de acceso. + Finalizar ahora + Finalizar la configuración de la billetera + Complete la configuración protegiendo la aplicación con un código de acceso. + Si sale, tendrá que empezar de nuevo. + ¿Está seguro de que desea salir del proceso de configuración? + Mantiene sus criptomonedas seguras y sin conexión. Tan delgadas como una tarjeta de crédito, más seguras que una bóveda bancaria. Si lo hace, tendrá que empezar de nuevo. + Recuperar la billetera existente a través de una copia de seguridad de Google Drive + Copia de seguridad de Google Drive + Cree una billetera segura y transfiera sus fondos para mayor protección. + Crear nueva billetera + Mejore su seguridad con la billetera de hardware superior Tangem. + Billetera de hardware + Mueva su billetera actual a Tangem. + Actualizar la billetera actual + Ir a copia de seguridad + Por favor, haga una copia de seguridad de su billetera antes de crear un código de acceso. + Finalice la copia de seguridad primero + Finalizar la copia de seguridad primero + Incompleto + Otros métodos + Guarde su frase de recuperación en un lugar seguro y manténgala en privado para proteger sus fondos. + Frase de recuperación + Para proteger su billetera con un código de acceso, complete el proceso de copia de seguridad. + Para actualizar a una billetera de hardware, complete el proceso de copia de seguridad. + Sus claves privadas están encriptadas de forma segura y almacenadas en su teléfono + Las claves privadas permanecen en su dispositivo + Cree o restaure su billetera con una frase de recuperación. + Copia de seguridad de la frase semilla + Crear una billetera móvil + Importar billetera existente + Esta frase de recuperación ya ha sido importada + Billetera móvil + Olvidar billetera + Esta billetera se eliminará permanentemente de su dispositivo. + ¿Está seguro que desea hacer esto? + Olvidar billetera + Ir a copia de seguridad + Ver copia de seguridad + Olvidar la billetera + Olvidar de todos modos + Esta billetera tiene una copia de seguridad. Asegúrese de poder recuperarla antes de olvidarla. + Si olvida esta billetera sin una copia de seguridad, perderá permanentemente el acceso a sus fondos. + ¿Estás seguro de que desea olvidar esta billetera? + Entiendo que si no he hecho una copia de seguridad de mi billetera antes de eliminarla, perderé el acceso a ella. + Entiendo que quitar mi billetera no la borra, solo la elimina de mi dispositivo. + Actualizar + No se requiere frase semilla. Su tarjeta o anillo Tangem se convierte en su copia de seguridad. + Copia de seguridad con Tangem + No se puede actualizar. Ya existe una billetera en este dispositivo. + Elija otro dispositivo. Este no se puede usar para la actualización. + Se produjo un error durante la operación. + Sus fondos permanecen seguros y totalmente accesibles durante el proceso + Acceso a los fondos + Los datos de su billetera se borrarán de la aplicación y se almacenarán en su billetera de hardware. + Seguridad general + Las claves privadas se transferirán de la aplicación a su billetera de hardware Tangem + Migración de claves + Escanear dispositivo + Iniciar actualización + Estás a punto de actualizarte a nuestra billetera de hardware. Mantendrá sus activos seguros en almacenamiento en frío. + Tangem Wallet + Actualice a nuestra billetera de hardware + Mantenga sus criptomonedas seguras con la billetera de hardware de primer nivel de Tangem. + Actualice su billetera a la seguridad del hardware Esta información fue generada con IA.\nPulse aquí si encuentra algún error. Para cambiar el código de acceso coloque la tarjeta o el anillo como se muestra arriba y no lo retire hasta el fin de la operación Toque para cambiar la contraseña @@ -1323,7 +1399,8 @@ Privacidad inigualable Obtén tu tarjeta Tangem Pay gratuita en minutos Cuenta de pago - Sincronización de cuenta de pago necesaria + La cuenta de pago no está sincronizada + PIN no válido: evitar secuencias o repeticiones Estamos solucionando un problema técnico. Por favor, inténtelo de nuevo más tarde. Servicio temporalmente no disponible No es posible mostrar los datos en este momento, pero los pagos con tarjeta siguen funcionando. @@ -1331,7 +1408,7 @@ Usa USDC para pagos cotidianos Tangem Pay temporalmente no disponible Tangem Pay - Usa tu tarjeta o anillo para restaurar el acceso a tu cuenta de pago + Haga clic en el botón de abajo para restaurar el acceso Tu código PIN Esta es mi billetera Saldos ocultos @@ -1412,6 +1489,7 @@ Active las notificaciones push y le avisaremos al instante cuando le lleguen fondos. No se pierda ninguna transacción Agregar una nueva billetera + Si olvida esta billetera sin una copia de seguridad, perderá permanentemente el acceso a sus fondos. ¿Estás seguro de que deseas olvidar esta billetera? Ha ocurrido un error, por favor escanee su tarjeta o anillo para iniciar sesión Esta billetera ya se ha guardado, puede agregar otro @@ -1506,6 +1584,8 @@ Entendido ¡Realmente genial! Actualizar + Según la documentación oficial de Clore, todas las monedas recibidas antes del 21 de diciembre serán migradas a Clore (token ERC-20); las monedas recibidas después de esa fecha no lo serán. Se está desarrollando una solución de transferencia — mantente atento. + Migración de la red Clore Actualmente estás en el modo Demo Modo demo activo La tarjeta que ha escaneado es una tarjeta de desarrollador. No la use para crear su billetera. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index f612094144..16ebbde2fe 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -72,7 +72,19 @@ Ajouter un wallet Sélectionnez un wallet pour vous connecter Heureux de te revoir! + Vous avez sauvegardé votre portefeuille avec succès. Ces mots ne peuvent pas être récupérés en cas de perte. Assurez-vous de les conserver en lieu sûr. + Sauvegarde terminée + Votre seed phrase est un ensemble fixe de %s mots aléatoires permettant d\'accéder à votre portefeuille et de le récupérer. + Ces mots ne peuvent être récupérés s\'ils sont perdus. Conservez-les précieusement. + Gardez-les en sécurité + Conservez ces %s mots dans un endroit sûr et ne les communiquez à personne. + Aucune récupération possible + Seed phrase + Ne communiquez jamais ces mots à qui que ce soit. Tangem ne vous les demandera jamais. Les %s mots ci-dessous constituent la seed phrase de votre portefeuille. Utilisez-les pour restaurer votre portefeuille si vous perdez votre appareil. + Notez ces %s mots dans l\'ordre numérique et conservez-les en lieu sûr et confidentiel. + Vous êtes entièrement responsable de la sécurité de votre portefeuille et de la sauvegarde de votre seed phrase. + Seed phrase 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 Compris @@ -382,7 +394,7 @@ Vérifiez votre connexion Internet ou passez à un réseau différent Conditions d\'utilisation Adresse par défaut - Legacy adresse + Legacy %s adresse Recevoir des actifs %s adresse L’envoi d’actifs sur d’autres réseaux entraînera une perte définitive. @@ -470,6 +482,7 @@ Veuillez nous dire quelle carte vous avez Chère équipe de support, Veuillez nous en dire plus sur votre problème. Chaque petit détail peut nous aider. + Problème de sauvegarde Portefeuille précédemment activé Mes suggestions Impossible de scanner une carte @@ -488,6 +501,13 @@ Pour continuer, accordez aux smart contracts de %1s l\'autorisation d\'utiliser votre %2s Donner l\'autorisation Illimité + Votre sauvegarde est créée à l\'aide de 2 ou 3 cartes Tangem. Conservez-les dans des endroits sûrs distincts afin de les protéger contre toute perte ou tout dommage. Aucune seed phrase n\'est nécessaire. + Sauvegarde avec plusieurs cartes + Ajouter le wallet Tangem + Votre clé privée est générée directement dans la carte Tangem et ne la quitte jamais. + Génération de clés + Toutes les opérations cryptographiques s\'effectuent à l\'intérieur de la puce sécurisée, certifiée contre le clonage et la falsification physique. + Sécurité au niveau matériel Ajouter un Portefeuille existant Créer un nouveau Portefeuille Commandez @@ -496,6 +516,73 @@ à %s Via %s Êtes-vous sûr de vouloir annuler la configuration du code d\'accès ? + Sauvegarder maintenant + Pour terminer la configuration, sauvegardez votre portefeuille et sécurisez l\'application à l\'aide d\'un code d\'accès. + Finaliser maintenant + Finaliser la configuration du portefeuille + Terminez la configuration en sécurisant l\'application à l\'aide d\'un code d\'accès. + Si vous quittez, vous devrez recommencer depuis le début. + Êtes-vous sûr de vouloir quitter le processus d\'installation ? + Gardez vos cryptomonnaies en sécurité et hors ligne. Aussi fin qu\'une carte de crédit, plus sûr qu\'un coffre-fort bancaire. + Si vous le faites, vous devrez recommencer depuis le début. + Récupérer un portefeuille existant via la sauvegarde Google Drive + Sauvegarde Google Drive + Créez un portefeuille sécurisé et transférez vos fonds pour bénéficier d\'une protection supplémentaire. + Créer un nouveau portefeuille + Renforcez votre sécurité grâce au Hardware wallet haut de gamme Tangem. + Hardware wallet + Transférez votre portefeuille actuel vers Tangem. + Améliorer le wallet actuel + Aller à la sauvegarde + Veuillez sauvegarder votre portefeuille avant de créer un code d\'accès. + Terminez d\'abord la sauvegarde. + Finalisez d\'abord la sauvegarde. + Incomplet + Autres méthodes + Conservez votre seed phrase dans un endroit sûr et gardez-la confidentielle afin de protéger vos fonds. + Seed phrase + Pour sécuriser votre wallet avec un code d\'accès, effectuez la procédure de sauvegarde. + Pour passer à un portefeuille matériel, terminez le processus de sauvegarde. + Vos clés privées sont cryptées et stockées en toute sécurité sur votre téléphone. + Les clés privées restent sur votre appareil + Créez ou restaurez votre portefeuille à l\'aide d\'une seed phrase. + Sauvegarde de la seed phrase + Créer un wallet mobile + Importer un portefeuille existant + Cette seed phrase a déjà été importée. + Wallet mobile + Oubliez votre portefeuille + Ce portefeuille sera définitivement supprimé de votre appareil. + Êtes-vous sûr de vouloir faire cela ? + Oubliez votre portefeuille + Aller à la sauvegarde + Afficher la sauvegarde + Oubliez votre portefeuille + Oubliez quand même + Ce portefeuille dispose d\'une sauvegarde. Assurez-vous de pouvoir la récupérer avant d\'oublier le portefeuille. + Si vous oubliez ce portefeuille sans sauvegarde, vous perdrez définitivement l\'accès à vos fonds. + Êtes-vous sûr de vouloir oublier ce portefeuille ? + Je comprends que si je n\'ai pas sauvegardé mon portefeuille avant de le supprimer, je perdrai l\'accès à celui-ci. + Je comprends que le fait de supprimer mon portefeuille ne l\'efface pas, mais le supprime uniquement de mon appareil. + Améliorer + Aucune seed phrase n\'est requise. Votre carte ou bague Tangem devient votre sauvegarde sécurisée. + Sauvegarde avec Tangem + Impossible de mettre à niveau. Un portefeuille existe déjà sur cet appareil. + Choisissez un autre appareil. Celui-ci ne peut pas être utilisé pour la mise à niveau. + Une erreur s\'est produite pendant l\'opération. + Vos fonds restent en sécurité et entièrement accessibles pendant toute la durée du processus. + Accès aux fonds + Les données de votre wallet seront effacées de l\'application et stockées sur votre hardware wallet. + Sécurité générale + Les clés privées seront transférées de l\'application vers votre hardware wallet Tangem. + Migration des clés + Scannez l\'appareil + Lancer la mise à niveau + Vous êtes sur le point de passer à notre hardware wallet. Il assurera la sécurité de vos actifs grâce au stockage hors ligne. + Tangem Wallet + Passez à notre Hardware Wallet + Protégez vos cryptomonnaies grâce au hardware wallet haut de gamme de Tangem. + Améliorez la sécurité de votre wallet grâce à un dispositif matériel 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 @@ -1304,7 +1391,8 @@ Confidentialité inégalée Obtenez votre carte Tangem Pay gratuite en quelques minutes Compte de paiement - Synchronisation du compte de paiement nécessaire + Le compte de paiement n\'est pas synchronisé + Code PIN invalide : évitez les séquences ou les répétitions Nous réparons un problème technique. Veuillez réessayer plus tard. Service temporairement indisponible Les données ne peuvent pas être affichées pour le moment, mais les paiements par carte fonctionnent toujours. @@ -1312,7 +1400,7 @@ Utilisez USDC pour les paiements quotidiens Tangem Pay est temporairement indisponible Tangem Pay - Utilisez votre carte ou votre bague pour restaurer l\'accès à votre compte de paiement + Cliquez sur le bouton ci-dessous pour restaurer l\'accès Votre code PIN C\'est mon portefeuille Soldes masqués @@ -1392,6 +1480,7 @@ Activez les notifications pour recevoir des alertes lorsque des fonds arrivent dans votre portefeuille. Ne manquez aucune transaction Ajouter un nouveau portefeuille + Si vous supprimez ce portefeuille sans sauvegarde, vous perdrez définitivement l\'accès à vos fonds. Êtes-vous sûr de vouloir supprimer ce portefeuille ? Une erreur s\'est produite, veuillez scanner votre carte ou bague pour vous connecter Ce portefeuille a déjà été enregistré, vous pouvez en ajouter un autre @@ -1505,6 +1594,8 @@ Ok, compris! Vraiment cool ! Rafraîchir + Selon la documentation officielle de Clore, toutes les pièces reçues avant le 21 décembre seront migrées vers Clore (token ERC-20) ; les pièces reçues après cette date ne le seront pas. Une solution de transfert arrive — restez à l\'écoute. + Migration du réseau Clore Vous êtes actuellement en mode démo Mode démo actif La carte que vous avez scannée est une carte de développeur. Ne l\'utilisez pas pour créer votre portefeuille. diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index a38c395035..9128efeb7f 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -163,7 +163,8 @@ Privacy senza rivali Ottieni la tua carta Tangem Pay gratuita in pochi minuti Conto di pagamento - Sincronizzazione del conto di pagamento necessaria + Il conto di pagamento non è sincronizzato + PIN non valido: evitare sequenze o ripetizioni Stiamo risolvendo un problema tecnico. Riprova più tardi. Servizio temporaneamente non disponibile Al momento non è possibile visualizzare i dati, ma i pagamenti con carta continuano a funzionare. @@ -171,7 +172,7 @@ Usa USDC per i pagamenti quotidiani Tangem Pay è temporaneamente non disponibile Tangem Pay - Usa la tua carta o il tuo anello per ripristinare l\'accesso al tuo conto di pagamento + Fare clic sul pulsante in basso per ripristinare l\'accesso Il tuo codice PIN Tangem Twin Imposta un codice di 4 cifre.\nVerrà utilizzato per i pagamenti. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index d8a7ec5d6d..666fe8e6bb 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -423,7 +423,6 @@ トークンは誰でも作成できることに注意してください。 Tangemウォレットを購入 チャット - Tangem Visaを入手 アクセスコード カードをスキャンする前に、正しいアクセスコードを送信する必要があります。 長くタップ @@ -1504,7 +1503,8 @@ 他に類を見ないプライバシー 無料のTangem Payカードを数分でゲットしましょう 支払いアカウント - 支払いアカウントの同期が必要です + 支払アカウントが同期されていません + 無効な暗証番号:連続や繰り返しを避けてください 技術的な問題を修正しています。後でもう一度お試しください。 サービスは一時的に利用できません 現在、データを表示できませんが、カードでのお支払いは引き続きご利用いただけます。 @@ -1512,7 +1512,7 @@ 日常の支払いにUSDCを利用 Tangem Payは現在一時的に利用できません。 Tangem Pay - カードまたはリングを使用して、支払いアカウントへのアクセスを復元してください。 + 下のボタンをクリックしてアクセスを復元してください PINコード これは私のウォレットです 残高非表示 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index d1cbd557be..ee6d3b99d7 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -460,7 +460,7 @@ Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования Основной адрес - Legacy адрес + Legacy %s адрес Получить активы %s адрес Отправка средств в другой сети может повлечь потерю средств. @@ -551,6 +551,7 @@ Скажите, пожалуйста, какая у вас карта или кольцо? Привет, команда поддержки, Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. + Проблема резервного копирования Ранее активированный кошелек Мои предложения Не могу отсканировать карту/кольцо @@ -1457,6 +1458,7 @@ Не удалось разморозить карту, попробуйте еще раз Карта разморожена Вывести + Запрещено использовать на root-устройствах Пополнить Способы пополнения Номер @@ -1484,6 +1486,7 @@ Всё готово! Можно пользоваться картой Добавьте карту в Google Pay Добавить карту в Apple Pay + ПИН-код Скопируйте свой адрес или покажите QR Техническая ошибка. Попробуйте позже или обратитесь в поддержку. Пополнение недоступно @@ -1492,6 +1495,7 @@ Пополните карту любым активом через обмен Реквизиты Разморозить карту + Ваш ПИН Вывести Вывод сейчас недоступен Вы не можете начать обмен или новый вывод, пока не завершится текущий. @@ -1515,6 +1519,7 @@ KYC в процессе Посмотреть статус KYC в процессе для Tangem Pay + Вы можете посмотреть текущий статус KYC или отменить его Откройте бесплатную виртуальную карту Tangem Visa Оплачивайте ежедневные покупки в USDC Открыть карту @@ -1526,15 +1531,16 @@ Абсолютная приватность Откройте виртуальную \nTangem Pay Card Платежный аккаунт - Требуется синхронизация платежного аккаунта + Платежный аккаунт не синхронизирован + Слабый ПИН: не используйте повторы или последовательности. Мы устраняем техническую проблему. Пожалуйста, попробуйте позже. Сервис временно недоступен Не можем показать данные карты, но оплаты продолжают работать. - Требуется синхронизация + Не синхронизирован Оплачивайте ежедневные покупки в USDC Tangem Pay временно недоступен Tangem Pay - Используйте вашу карту или кольцо для восстановления доступа к платежному аккаунту + Нажмите на кнопку ниже, чтобы восстановить доступ Ваш PIN-код Это мой кошелек Балансы скрыты @@ -1735,6 +1741,8 @@ Понятно! Очень круто! Обновить + Согласно официальной документации Clore, все монеты, полученные до 21 декабря, будут мигрированы в токен Clore (ERC-20); монеты, полученные после этой даты, — нет. Решение для перевода находится в разработке — следите за обновлениями. + Миграция сети Clore Вы находитесь в режиме демо Демо режим включен Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. 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 ffe733b8c5..6df04c2899 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -49,6 +49,19 @@ Системна Тема Налаштування застосунку + Ви успішно створили резервну копію свого гаманця. + Ці слова неможливо відновити у разі втрати. Зберігайте їх надійно. + Бекап завершено + Ваша фраза відновлення — це набір випадкових слів %s для доступу та відновлення гаманця. + Ці слова неможливо відновити, якщо їх загубити. Зберігайте їх у безпеці. + Зберігайте в безпеці + Збережіть ці %s слова в безпечному місці та нікому не розповідайте про них. + Відновлення неможливе + Фраза відновлення + Наведені нижче слова %s - це фраза для відновлення вашого гаманця. Ніколи і нікому не повідомляйте ці слова. Tangem ніколи не запитає їх у вас. Використовуйте їх, щоб відновити свій гаманець, якщо ви втратите пристрій. + Запишіть ці %s слова в указаному порядку і зберігайте їх у безпеці та таємниці. + Ви несете повну відповідальність за безпеку свого гаманця і фрази відновлення. + Фраза відновлення Щоб приховати або показати свій баланс, просто переверніть екран пристрою вниз або вимкніть його в налаштуваннях Більше не показувати Зрозуміло @@ -310,7 +323,7 @@ Перевірте підключення до інтернету або змініть мережу Умови використання Основна адреса - Legacy адреса + Legacy %s адреса Отримати активи %s адреса Надсилання активів в інші мережі призведе до безповоротної втрати. @@ -395,6 +408,7 @@ Розкажіть, будь ласка, яку картку або кільце ви маєте? Привіт, команда підтримки, Будь ласка, розкажіть нам більше про вашу проблему. Кожна дрібниця може допомогти. + Проблема з резервним копіюванням Раніше активований гаманець Мої пропозиції Не вдається відсканувати картку/кільце @@ -413,11 +427,85 @@ Щоб продовжити, вам потрібно дозволити смарт-контракту %1s використовувати ваш %2s Надати дозвіл Необмежено + Резервна копія створюється на 2–3 картках Tangem. Зберігайте їх окремо для безпеки — seed-фраза не потрібна. + Бекап на декілька карток + Додати гаманець Tangem + Ваш приватний ключ генерується на картці Tangem і ніколи не покидає її. + Генерація ключа + Операції з криптографією відбуваються всередині захищеного чіпу, стійкого до клонування та фізичному взлому. + Безпека на апаратному рівні Створити новий гаманець Купити Сканувати в %s В мережі %s + Ви впевнені, що хочете скасувати процес створення коду доступу? + Створити бекап + Щоб завершити налаштування, створіть резервну копію свого гаманця та захистіть додаток за допомогою коду доступу. + Завершити зараз + Завершити налаштування гаманця + Завершіть налаштування, захистивши додаток кодом доступу. + Якщо ви вийдете, вам доведеться починати спочатку. + Ви впевнені, що хочете вийти з процесу активації? + Зберігає ваші криптовалюти в безпеці та в режимі офлайн. Тонкий, як кредитна картка, безпечніший за банківське сховище. + Якщо ви це зробите, доведеться почати спочатку. + Відновлення існуючого гаманця за допомогою резервної копії Google Диску + Google Диск бекап + Створіть новий захищений гаманець і переведіть свої кошти для додаткового захисту. + Створити новий гаманець + Підвищіть рівень своєї безпеки за допомогою просунутого апаратного гаманця Tangem. + Апаратний гаманець + Перенесіть свій поточний гаманець у Tangem. + Оновіть поточний гаманець + Перейти до бекапу + Будь ласка, створіть резервну копію свого гаманця, перш ніж створювати код доступу. + Спочатку завершіть резервне копіювання + Спочатку завершіть резервне копіювання + Не завершено + Інші методи + Збережіть фразу відновлення у безпечному місці і тримайте її у таємниці. + Фраза відновлення + Щоб захистити свій гаманець за допомогою коду доступу, завершіть процес резервного копіювання. + Щоб покращити гаманець до апаратного, спочатку створіть резервну копію. + Ваші приватні ключі надійно зашифровані та зберігаються на вашому телефоні + Ключі зберігаються у застосунку + Створіть або відновіть свій гаманець за допомогою вашої фрази відновлення. + Резервна копія + Створити мобільний гаманець + Імпортувати існуючий гаманець + Ця фраза відновлення вже була імпортована + Мобільний гаманець + Забути гаманець + Цей гаманець буде назавжди видалено з вашого пристрою + Ви впевнені, що хочете виконати цю операцію? + Забути гаманець + Перейти до резервної копії + Переглянути резервне копіювання + Забути гаманець + Все одно забути + Резервна копія цього гаманця існує. Перевірте її перед видаленням, щоб переконатися, що зможете відновити гаманець пізніше. + Якщо ви видалите цей гаманець без резервної копії, ви назавжди втратите доступ до своїх коштів. + Забути цей гаманець? + Я розумію, що якщо я не створив резервну копію гаманця перед його видаленням, я можу втратити досту до нього. + Я розумію, що видалення мого гаманця не видаляє його повністю, а просто видаляє його з мого пристрою. + Фраза відновлення більше не потрібна — ваша картка або кільце Tangem стає вашою безпечною резервною копією. + Резервне копіювання з Tangem + Цей пристрій не може бути використаний для оновлення, він вже містить інший гаманець. + Виберіть інший пристрій. Цей не можна використовувати для оновлення. + Під час операції виникла помилка. + Ваші кошти залишаються в безпеці та повністю доступними протягом усього процесу + Доступ до коштів + Данні вашого гаманця будуть видалені із застосунку і збережені на вашому пристрої Tangem. + Загальна безпека + Приватні ключі будуть переміщені з додатку у вашу Tangem картрку або кільце + Міграція ключів + Сканувати пристрій + Розпочати оновлення + Ви збираєтеся перейти на пристрій Tangem, де ваші активи будуть у безпеці в холодному сховищі. + Tangem Wallet + Оновіть до апаратного гаманця + Зберігайте свою криптовалюту в безпеці за допомогою першокласного апаратного гаманця Tangem. + Оновіть свій гаманець до апаратної версії. Ця інформація була створена за допомогою ШІ.\nНатисніть тут, якщо знайшли помилку. Щоб змінити код доступу, прикладіть картку або кільце, як показано вище, і не прибирайте її до закінчення операції Щоб змінити пароль, прикладіть картку, як показано вище, і не прибирайте її до закінчення операції @@ -1144,9 +1232,11 @@ Отримайте безкоштовну віртуальну картку Tangem Visa Отримайте безкоштовну віртуальну картку Tangem Visa Використовуйте USDC для щоденних платежів + Платіжний рахунок не синхронізовано Ми усуваємо технічну проблему. Будь ласка, спробуйте пізніше. Сервіс тимчасово недоступний Використовуйте USDC для щоденних платежів + Натисніть кнопку нижче, щоб відновити доступ Це мій гаманець Баланси приховано Баланси показано @@ -1223,6 +1313,7 @@ Нові функції та важливі новини Бажаєте використовувати Push-повідомлення? Додати новий гаманець + Якщо ви видалите цей гаманець без резервної копії, ви назавжди втратите доступ до своїх коштів. Ви впевнені, що хочете видалити цей гаманець? Сталася помилка, будь ласка, відскануйте свою картку або кільце, для входу Цей гаманець вже збережено, ви можете додати інший @@ -1293,6 +1384,8 @@ Зрозуміло! Дуже круто! Оновити + Згідно з офіційною документацією Clore, усі монети, отримані до 21 грудня, будуть мігровані в токен Clore (ERC-20); монети, отримані після цієї дати, — ні. Рішення для переказу перебуває в розробці — стежте за оновленнями. + Міграція мережі Clore Ви перебуваєте в демонстраційному режимі Демонстраційний режим активовано Відсканована вами картка є карткою розробника. Не використовуйте її для створення гаманця. 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 1dd6969ad7..a79d66c1d7 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -406,7 +406,7 @@ 無與倫比的隱私 在幾分鐘內獲得免費的 Tangem Pay 卡 付款帳戶 - 需要同步支付账户 + 付款帳戶未同步 我们正在修复技术问题。请稍后再试。 服務暫時無法使用 目前無法顯示資料,但卡片支付仍可正常使用。 @@ -414,7 +414,7 @@ 使用 USDC 進行日常支付 Tangem Pay暂时不可用 Tangem Pay - 使用您的卡片或戒指恢复对支付账户的访问 + 點擊下方按鈕以恢復存取權限 您的PIN码 隱藏 您即將在主屏幕上隱藏此代幣。您可以隨時通過管理代幣頁面將其添加回來。 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index bd4e096fbd..582d2ca04c 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -61,8 +61,8 @@ Are you sure you want to discard new account? Are you sure you want to discard edits? Unsaved Changes - Some custom tokens were moved from “%1$s” to “%2$s” as their derivation belongs to that account. - Some custom tokens were moved + Some custom tokens will be automatically moved from “%1$s” to “%2$s” as their derivation belongs to that account. + Some custom tokens will be automatically moved Can’t find your token? Go to the Market section on the main page and add it to your portfolio for purchase Can’t find your token? Go to the Market section on the main page and add it to your portfolio for selling. Sell @@ -128,15 +128,15 @@ Mobile Wallet You successfully backed up your wallet. - These words are unrecoverable if lost. Keep them somewhere safe. + These words cannot be recovered if lost. Store them securely. Backup completed - Your secret recovery phrase is a fixed set of %s random words for accessing and recovering your wallet. + Your secret recovery phrase is a set of %s random words for accessing and recovering your wallet. These words cannot be recovered if lost. Keep them safe. Keep it safe Save these %s words in a secure location and never share them with anyone. No recovery possible Recovery phrase - Never share these words with anyone. Tangem will never ask you for them. The %s words below are your wallet\'s recovery phrase. Use them to restore your wallet if you lose your device. + The %s words below are your wallet\'s recovery phrase. Never share these words with anyone. Tangem will never ask you for them. Use them to restore your wallet if you lose your device. Write down these %s words in numerical order and keep them safe and private You are fully responsible for securing your wallet and safely backing up your recovery phrase. Recovery phrase @@ -431,7 +431,6 @@ 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 @@ -567,8 +566,8 @@ To continue, grant %1s smart contracts permission to use your %2s Give Permission Unlimited - Your backup is created using 2 or 3 Tangem cards. Keep them in separate safe places to protect against loss or damage — no seed phrase needed. - Backup with Multiple Cards + A backup is created using 2–3 Tangem cards. Store them separately in secure locations to protect against loss or damage. No seed phrase needed. + Backup With Multiple Cards Add Tangem Wallet Your private key is generated directly inside the Tangem card and never leaves it. Key Generation @@ -588,7 +587,7 @@ Finalize wallet setup Complete setup by securing the app with an access code. If you exit, you\'ll need to start over. - Are you sure you want to quit the setup process? + Are you sure you want to quit activation? Keeps your crypto safe and offline. Slim as a credit card, safer than a bank vault. If you do, you\'ll need to start over. Recover existing wallet via Google Drive backup @@ -601,7 +600,7 @@ Upgrade current wallet Go to backup Please back up your wallet before creating an access code. - Finish backup first + Complete the backup first Finalize backup first Incomplete Other methods @@ -632,13 +631,13 @@ I understand that removing my wallet does not delete it, only removes it from my device. Upgrade Seed phrase not required. Your Tangem card or ring becomes your secure backup. - Backup with Tangem + Backup With Tangem Can\'t upgrade. A wallet already exists on this device. - Pick another device. This one can’t be used for the upgrade. + Pick another device. This one can\'t be used for the upgrade. An error occurred during the operation. Your funds remain safe and fully accessible during the process Access to funds - Your wallet data will be erased from the app and stored on your hardware wallet + Your wallet information will be erased from the app and stored on your hardware wallet General security Private keys will be moved from the app to your Tangem hardware wallet Key migration @@ -902,8 +901,8 @@ Please repeat the operation. The card will be reset to factory settings. Activation error Add tokens - You\'ve added one backup card or ring. When backup process is finished you can\'t add more backup devices. If you have one more card or ring, add it to the backup. Would you like to continue the backup process? - The backup process is partly complete. You can\'t exit it now. + You\'ve added one backup card or ring. Once backup is finalized, you can\'t add more devices. If you have one more card or ring, add it now. Do you want to continue? + The backup is partially complete and can\'t be quit now. A passphrase is an optional security feature that adds a word or phrase to your recovery phrase, creating a new set of wallet addresses for extra protection. Add a card or ring Scan card @@ -953,7 +952,7 @@ Legacy To check whether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words So, let’s check - To start the backup process add up to two backup cards or rings. + To start the backup process, add up to two backup cards or rings. You can add one more card or ring or finalize the backup process Prepare the backup card with number %s Scan the primary card or ring to start the backup process. @@ -1092,8 +1091,8 @@ I understand that after performing this action, I will no longer have access to the current wallet I realize that I can\'t use this card to recover my access code on the other cards of the current wallet I understand that I will completely lose access to my Tangem Pay Card and all funds on it without the possibility of recovery - Factory Reset will completely delete the wallet from the selected card or ring. You will not be able to restore the current wallet or use the card or ring to recover the access code. - Factory Reset will completely delete the wallet from the selected card or ring and remove it from the app. You will not be able to restore the current wallet. + A factory reset completely erases the wallet from the selected card or ring. You will not be able to restore the current wallet or use this card or ring to recover the access code. + A factory reset completely erases the wallet from the selected card or ring and removes it from the app. You will not be able to restore the current wallet. All Tangem devices have been reset. Something went wrong with the activation process. Please reset the cards one by one. Card verification failed @@ -1378,7 +1377,7 @@ Your stakes Store your crypto assets secure while keeping private keys contained in your card or ring Revolutionary Hardware Wallet - Up to 3 physical cards or rings to one wallet + Add up to 3 cards or rings to one wallet Ultra Secure Backup A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously — all in one card or ring Thousands of Currencies @@ -1528,15 +1527,18 @@ Unrivaled privacy Get your free Tangem Pay Card in minutes Payment account - Payment account sync needed + Payment account is not synced + Invalid PIN: avoid sequences or repeats We’re fixing a technical issue. Please try again later. Service temporarily unavailable Unable to display details. However, card payments are still working. - Sync needed + Set \nPIN code + Not synced + Restore access Use USDC for everyday payments Tangem Pay is temporarily unreachable Tangem Pay - Use your card or ring to restore access to your payment account + Click the button below to restore access Your PIN code This is my wallet Balances hidden @@ -1786,7 +1788,7 @@ Use %s or scan a card/ring to unlock access to your wallet The permission-granting process is currently underway and will be completed shortly Approval in Progress - It seems that the card or ring activation was not completed correctly. This could be due to an issue with your device\'s NFC module or incorrect tapping of the card or ring to your device. Please contact our Support team for assistance. + Activation was not completed successfully. This may be due to an NFC issue or incorrect tapping. Please contact our Support team for assistance. Activation error On December 3, 2024, the BEP-2 network was disabled by decision of the network developers and is no longer supported BNB Beacon Chain shut down @@ -1797,6 +1799,8 @@ Ok, Got it! Really cool! Refresh + According to Clore’s official documentation, all coins received before December 21 will be migrated to Clore (ERC-20 token); coins received after that date will not. A transfer solution is coming — stay tuned. + Clore Network Migration You are currently in the Demo mode Demo mode active The card you scanned is a developer card. Do not use it to create your wallet. @@ -1845,7 +1849,7 @@ The network is currently unreachable. Please try again later. Network is unreachable Top up your wallet - Your wallet hasn\'t been backed up. Carry out this procedure to protect your assets now. + Your wallet isn\'t backed up yet. Back it up now to protect your assets. Missing backup This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required. Card has already signed transactions @@ -1985,10 +1989,10 @@ Use a Tangem hardware wallet Learn more & buy Discard - You have an interrupted backup. Do you want to resume? + Your backup was interrupted. Do you want to resume? Yes, resume Discard - If you discard the backup now, then you will have to reset the devices to factory settings to start over again + If you discard the backup now, you will have to reset the devices to factory settings to start over again Resume backup This is an irreversible action Log in with %s diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUMV2.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUMV2.kt index 0fc219bba4..a776ddd445 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUMV2.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUMV2.kt @@ -41,6 +41,9 @@ data class MessageBottomSheetUMV2( } } + @Immutable + data class IconImage(@DrawableRes internal var res: Int) : Element + @Immutable data class Chip( internal var text: TextReference, @@ -54,6 +57,7 @@ data class MessageBottomSheetUMV2( @Immutable data class InfoBlock( internal var icon: Icon? = null, + internal var iconImage: IconImage? = null, internal var chip: Chip? = null, var title: TextReference? = null, var body: TextReference? = null, @@ -106,6 +110,10 @@ fun MessageBottomSheetUMV2.InfoBlock.icon(@DrawableRes res: Int, init: MessageBo icon = MessageBottomSheetUMV2.Icon(res).apply(init) } +fun MessageBottomSheetUMV2.InfoBlock.iconImage(@DrawableRes res: Int) = apply { + iconImage = MessageBottomSheetUMV2.IconImage(res) +} + fun MessageBottomSheetUMV2.InfoBlock.chip(text: TextReference, init: MessageBottomSheetUMV2.Chip.() -> Unit = {}) = apply { chip = MessageBottomSheetUMV2.Chip(text).apply(init) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt index cb3f2c538d..7b3ab2dd0b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt @@ -1,14 +1,18 @@ package com.tangem.core.ui.components.bottomsheets.message +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -88,9 +92,7 @@ fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifie @Composable private fun ContentContainer(state: MessageBottomSheetUMV2.InfoBlock, modifier: Modifier = Modifier) { Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { - state.icon?.let { - BottomSheetIcon(it) - } + BottomSheetIconContainer(state.icon, state.iconImage) state.title?.let { title -> Text( modifier = Modifier @@ -122,6 +124,26 @@ private fun ContentContainer(state: MessageBottomSheetUMV2.InfoBlock, modifier: } } +@Suppress("CanBeNonNullable") +@Composable +private fun BottomSheetIconContainer( + icon: MessageBottomSheetUMV2.Icon?, + iconImage: MessageBottomSheetUMV2.IconImage?, + modifier: Modifier = Modifier, +) { + if (icon != null) { + BottomSheetIcon(icon, modifier) + } else if (iconImage != null) { + Image( + modifier = modifier + .size(TangemTheme.dimens.size56) + .clip(CircleShape), + painter = painterResource(id = iconImage.res), + contentDescription = null, + ) + } +} + @Composable private fun BottomSheetIcon(icon: MessageBottomSheetUMV2.Icon, modifier: Modifier = Modifier) { val tint = when (icon.type) { @@ -236,4 +258,33 @@ private fun Preview() { onDismissRequest = {}, ) } +} + +@Preview +@Composable +private fun Preview2() { + TangemThemePreview { + MessageBottomSheetV2( + messageBottomSheetUM { + infoBlock { + iconImage = MessageBottomSheetUMV2.IconImage(R.drawable.img_visa_notification) + title = TextReference.Str("Title Title Title") + body = TextReference.Str("Body") + chip(text = TextReference.Str("Some chip information")) + } + primaryButton { + text = TextReference.Str("Test") + icon = R.drawable.ic_tangem_24 + } + secondaryButton { + icon = R.drawable.ic_tangem_24 + text = TextReference.Str("asdasd") + onClick { + closeBs() + } + } + }, + onDismissRequest = {}, + ) + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index 20bb9fce3b..d1b06a5538 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -296,6 +296,7 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, enabled = isEnabled, + showProgress = config.shouldShowProgress, ) } else { PrimaryButton( @@ -304,6 +305,7 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo modifier = Modifier.fillMaxWidth(), size = TangemButtonSize.WideAction, enabled = isEnabled, + showProgress = config.shouldShowProgress, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt index 57338f98b4..9da575b6c2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt @@ -38,6 +38,7 @@ data class NotificationConfig( val additionalText: TextReference? = null, @DrawableRes val iconResId: Int? = null, val onClick: () -> Unit, + val shouldShowProgress: Boolean = false, ) : ButtonsState() data class SecondaryButtonConfig( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/YieldSupplyPromoBanner.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/YieldSupplyPromoBanner.kt index b949061451..71ccba070a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/YieldSupplyPromoBanner.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/YieldSupplyPromoBanner.kt @@ -18,6 +18,7 @@ import android.content.res.Configuration import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.material3.ripple +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.vectorResource @@ -39,6 +40,9 @@ internal fun YieldSupplyPromoBanner(state: PromoBannerState, modifier: Modifier @Composable internal fun YieldSupplyPromoBanner(state: PromoBannerState.Content, modifier: Modifier = Modifier) { + LaunchedEffect(state) { + state.onPromoShown() + } val bgColor = TangemTheme.colors.control.unchecked Column(modifier = modifier) { Row( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt index a2180bb614..7193eed339 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt @@ -269,6 +269,7 @@ sealed class TokenItemState { val title: TextReference, val onPromoBannerClick: () -> Unit, val onCloseClick: () -> Unit, + val onPromoShown: () -> Unit = {}, ) : PromoBannerState() data object Empty : PromoBannerState() diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index f893a530d6..3c9ef9ff07 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(projects.core.error.ext) implementation(projects.core.security) implementation(projects.data.common) + implementation(projects.data.wallets) /** Project - Domain */ implementation(projects.domain.visa) @@ -39,6 +40,9 @@ dependencies { /** Feature API - remove after removing [HotWalletFeatureToggles] */ implementation(projects.features.hotWallet.api) + /** Feature API - remove after removing [TangemPayFeatureToggles] */ + implementation(projects.features.tangempay.details.api) + /** Project - Utils */ implementation(projects.core.utils) implementation(projects.domain.legacy) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt index f2d850f463..7ccc87275c 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt @@ -9,15 +9,10 @@ import com.tangem.domain.pay.TangemPayEligibilityManager import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.features.hotwallet.HotWalletFeatureToggles +import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.* import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import javax.inject.Inject @@ -27,6 +22,7 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( private val userWalletsListManager: UserWalletsListManager, private val userWalletsListRepository: UserWalletsListRepository, private val hotWalletFeatureToggles: HotWalletFeatureToggles, + private val tangemPayFeatureToggles: TangemPayFeatureToggles, private val onboardingRepository: OnboardingRepository, ) : TangemPayEligibilityManager { @@ -45,6 +41,10 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( } } + override suspend fun getTangemPayAvailability(): Boolean { + return onboardingRepository.checkCustomerEligibility() + } + private suspend fun getUserWalletsData(): List { cachedEligibleWallets?.let { return it } @@ -69,7 +69,7 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( } private suspend fun getPossibleWalletsForTangemPay(): List { - if (!onboardingRepository.checkCustomerEligibility()) { + if (!checkTangemPayEligibility()) { return emptyList() } @@ -125,6 +125,12 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( eligibleWalletsDeferred = null } + private suspend fun checkTangemPayEligibility(): Boolean { + if (!tangemPayFeatureToggles.isEntryPointsEnabled) return true + + return onboardingRepository.getCustomerEligibility() || onboardingRepository.checkCustomerEligibility() + } + private data class UserWalletData( val userWallet: UserWallet, val isPaeraCustomer: Boolean, diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt index 6af402c591..8198c181ce 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt @@ -1,6 +1,7 @@ package com.tangem.data.pay.datasource import arrow.core.Either +import com.tangem.data.wallets.cold.UserWalletIdPreflightReadFilter import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.pay.WithdrawalSignatureResult import com.tangem.domain.pay.datasource.TangemPayAuthDataSource @@ -17,7 +18,10 @@ internal class DefaultTangemPayAuthDataSource @Inject constructor( userWallet: UserWallet, ): Either { return when (userWallet) { - is UserWallet.Cold -> tangemSdkManager.tangemPayProduceInitialCredentials(cardId = userWallet.cardId) + is UserWallet.Cold -> { + val preflightReadFilter = UserWalletIdPreflightReadFilter(userWallet.walletId) + tangemSdkManager.tangemPayProduceInitialCredentials(preflightReadFilter = preflightReadFilter) + } is UserWallet.Hot -> tangemPayHotSdkManager.produceInitialCredentials(userWallet) } } @@ -27,7 +31,10 @@ internal class DefaultTangemPayAuthDataSource @Inject constructor( hash: String, ): Either { return when (userWallet) { - is UserWallet.Cold -> tangemSdkManager.getWithdrawalSignature(cardId = userWallet.cardId, hash = hash) + is UserWallet.Cold -> { + val preflightReadFilter = UserWalletIdPreflightReadFilter(userWallet.walletId) + tangemSdkManager.getWithdrawalSignature(hash = hash, preflightReadFilter = preflightReadFilter) + } is UserWallet.Hot -> tangemPayHotSdkManager.getWithdrawalSignature(hotWallet = userWallet, hash = hash) } } 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 719542728a..93f2455ffb 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 @@ -13,6 +13,7 @@ import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase import com.tangem.domain.tangempay.TangemPayWithdrawUseCase import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository +import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.security.DeviceSecurityInfoProvider import dagger.Binds import dagger.Module @@ -77,12 +78,14 @@ internal interface TangemPayDataModule { customerOrderRepository: CustomerOrderRepository, tangemPayOnboardingRepository: OnboardingRepository, eligibilityManager: TangemPayEligibilityManager, + tangemPayFeatureToggles: TangemPayFeatureToggles, deviceSecurity: DeviceSecurityInfoProvider, ): TangemPayMainScreenCustomerInfoUseCase { return TangemPayMainScreenCustomerInfoUseCase( onboardingRepository = repository, customerOrderRepository = customerOrderRepository, eligibilityManager = eligibilityManager, + tangemPayFeatureToggles = tangemPayFeatureToggles, deviceSecurity = deviceSecurity, ) } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt index e11aa1c09b..d877f0f1f3 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt @@ -1,9 +1,11 @@ package com.tangem.data.pay.repository import arrow.core.Either +import arrow.core.raise.catch import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest import com.tangem.datasource.api.pay.models.request.OrderRequest +import com.tangem.datasource.api.pay.models.request.SetTangemPayEnabledRequest import com.tangem.datasource.api.pay.models.response.CustomerMeResponse import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore import com.tangem.datasource.local.visa.TangemPayStorage @@ -22,6 +24,7 @@ import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject @@ -109,15 +112,21 @@ internal class DefaultOnboardingRepository @Inject constructor( override suspend fun createOrder(userWalletId: UserWalletId) = withContext(dispatcherProvider.io) { launch { - requestHelper.runWithErrorLogs(TAG) { - val walletAddress = requestHelper.getCustomerWalletAddress(userWalletId) - val result = requestHelper.request(userWalletId) { authHeader -> - tangemPayApi.createOrder(authHeader, body = OrderRequest(walletAddress)) - }.result ?: error("Create order result is null") + catch( + block = { + val walletAddress = requestHelper.getCustomerWalletAddress(userWalletId) + val response = requestHelper.performRequest(userWalletId) { authHeader -> + tangemPayApi.createOrder(authHeader, body = OrderRequest(walletAddress)) + }.getOrNull() - val customerWalletAddress = requireNotNull(result.data.customerWalletAddress) - tangemPayStorage.storeOrderId(customerWalletAddress, result.id) - } + val result = requireNotNull(response?.result) + val customerWalletAddress = requireNotNull(result.data.customerWalletAddress) + tangemPayStorage.storeOrderId(customerWalletAddress, result.id) + }, + catch = { + Timber.tag(TAG).e("createOrder: $it") + }, + ) } } @@ -180,9 +189,10 @@ internal class DefaultOnboardingRepository @Inject constructor( ) }.map { response -> val id = response.result?.id - val isPaeraCustomer = !id.isNullOrEmpty() - tangemPayStorage.storeCheckCustomerWalletResult(userWalletId = userWalletId, isPaeraCustomer) - isPaeraCustomer + val isTangemPayEnabled = response.result?.isTangemPayEnabled == true + val shouldShowTangemPayBlock = !id.isNullOrEmpty() && isTangemPayEnabled + tangemPayStorage.storeCheckCustomerWalletResult(userWalletId = userWalletId, shouldShowTangemPayBlock) + shouldShowTangemPayBlock }.mapLeft { error -> if (error is VisaApiError.NotPaeraCustomer) { tangemPayStorage.storeCheckCustomerWalletResult(userWalletId = userWalletId, false) @@ -195,7 +205,15 @@ internal class DefaultOnboardingRepository @Inject constructor( val response = requestHelper.performWithoutToken { tangemPayApi.checkCustomerEligibility() }.getOrNull() - return response?.result?.isTangemPayAvailable == true + + val isAvailable = response?.result?.isTangemPayAvailable == true + tangemPayStorage.storeTangemPayEligibility(eligibility = isAvailable) + + return isAvailable + } + + override suspend fun getCustomerEligibility(): Boolean { + return tangemPayStorage.getTangemPayEligibility() } override suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean { @@ -205,4 +223,17 @@ internal class DefaultOnboardingRepository @Inject constructor( override suspend fun setHideMainOnboardingBanner(userWalletId: UserWalletId) { tangemPayStorage.storeHideOnboardingBanner(userWalletId, hide = true) } + + override suspend fun disableTangemPay(userWalletId: UserWalletId): Either { + return requestHelper.performRequest(userWalletId) { authHeader -> + tangemPayApi.setTangemPayEnabledStatus( + authHeader = authHeader, + body = SetTangemPayEnabledRequest(isTangemPayEnabled = false), + ) + }.map { + val address = requestHelper.getCustomerWalletAddress(userWalletId) + tangemPayStorage.clearAll(userWalletId = userWalletId, customerWalletAddress = address) + setHideMainOnboardingBanner(userWalletId) + } + } } \ No newline at end of file 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 a05f92ee7a..3252c49c4e 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 @@ -120,27 +120,18 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( block = { val publicKeyBase64 = getPublicKeyBase64() val (secretKeyBytes, sessionId) = rainCryptoUtil.generateSecretKeyAndSessionId(publicKeyBase64) - val result = requireNotNull( - requestHelper.performRequest(userWalletId = userWalletId) { authHeader -> - tangemPayApi.revealCardDetails( - authHeader = authHeader, - body = CardDetailsRequest(sessionId = sessionId), - ) - }.getOrNull()?.result, - ) + val response = requestHelper.performRequest(userWalletId = userWalletId) { authHeader -> + tangemPayApi.getPin(authHeader = authHeader, sessionId = sessionId) + }.getOrNull() + val result = requireNotNull(response?.result) + + val pin = rainCryptoUtil.decryptPin( + base64Secret = result.secret, + base64Iv = result.iv, + secretKeyBytes = secretKeyBytes, + ).takeIf { !it.isNullOrEmpty() } - val encryptedPin = result.pin - val pin = if (encryptedPin != null) { - rainCryptoUtil.decryptPin( - base64Secret = encryptedPin.secret, - base64Iv = encryptedPin.iv, - secretKeyBytes = secretKeyBytes, - ).takeIf { !it.isNullOrEmpty() } - } else { - null - } secretKeyBytes.fill(0) - pin.right() }, catch = ::catchException, @@ -148,14 +139,14 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( } override suspend fun setPin(userWalletId: UserWalletId, pin: String): Either { - return requestHelper.runWithErrorLogs(TAG) { - val publicKeyBase64 = getPublicKeyBase64() - val (secretKeyBytes, sessionId) = rainCryptoUtil.generateSecretKeyAndSessionId(publicKeyBase64) - val encryptedData = rainCryptoUtil.encryptPin(pin = pin, secretKeyBytes = secretKeyBytes) - secretKeyBytes.fill(0) + return catch( + block = { + val publicKeyBase64 = getPublicKeyBase64() + val (secretKeyBytes, sessionId) = rainCryptoUtil.generateSecretKeyAndSessionId(publicKeyBase64) + val encryptedData = rainCryptoUtil.encryptPin(pin = pin, secretKeyBytes = secretKeyBytes) + secretKeyBytes.fill(0) - val status = requireNotNull( - requestHelper.request(userWalletId) { authHeader -> + val response = requestHelper.performRequest(userWalletId) { authHeader -> tangemPayApi.setPin( authHeader = authHeader, body = SetPinRequest( @@ -164,27 +155,41 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( iv = encryptedData.ivBase64, ), ) - }.result?.result, - ) - when (status) { - SetPinResult.SUCCESS.name -> SetPinResult.SUCCESS - SetPinResult.PIN_TOO_WEAK.name -> SetPinResult.PIN_TOO_WEAK - SetPinResult.DECRYPTION_ERROR.name -> SetPinResult.DECRYPTION_ERROR - else -> SetPinResult.UNKNOWN_ERROR - } - } + }.getOrNull() + val status = requireNotNull(response?.result?.result) + val result = when (status) { + SetPinResult.SUCCESS.name -> SetPinResult.SUCCESS + SetPinResult.PIN_TOO_WEAK.name -> SetPinResult.PIN_TOO_WEAK + SetPinResult.DECRYPTION_ERROR.name -> SetPinResult.DECRYPTION_ERROR + else -> SetPinResult.UNKNOWN_ERROR + } + result.right() + }, + catch = ::catchException, + ) } override suspend fun isAddToWalletDone(userWalletId: UserWalletId): Either { - return requestHelper.runWithErrorLogs(TAG) { - storage.getAddToWalletDone(requestHelper.getCustomerWalletAddress(userWalletId)) - } + return catch( + block = { + storage.getAddToWalletDone( + customerWalletAddress = requestHelper.getCustomerWalletAddress(userWalletId), + ).right() + }, + catch = ::catchException, + ) } override suspend fun setAddToWalletAsDone(userWalletId: UserWalletId): Either { - return requestHelper.runWithErrorLogs(TAG) { - storage.storeAddToWalletDone(requestHelper.getCustomerWalletAddress(userWalletId), isDone = true) - } + return catch( + block = { + storage.storeAddToWalletDone( + customerWalletAddress = requestHelper.getCustomerWalletAddress(userWalletId), + isDone = true, + ).right() + }, + catch = ::catchException, + ) } override suspend fun freezeCard( 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 95b6decdf8..a788639680 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 @@ -18,7 +18,6 @@ import com.tangem.domain.visa.error.VisaApiError 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.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext @@ -41,38 +40,6 @@ internal class TangemPayRequestPerformer @Inject constructor( private val customerWalletAddresses = ConcurrentHashMap() private val tokensMutex = Mutex() - @Deprecated("Do not use this method") - suspend fun runWithErrorLogs(tag: String, requestBlock: suspend () -> T): Either { - return try { - val result = requestBlock() - Either.Right(result) - } catch (exception: Exception) { - when (exception) { - is CancellationException -> { - throw exception - } - else -> { - Timber.tag(tag).e(exception) - Either.Left(errorConverter.convert(exception)) - } - } - } - } - - @Deprecated("Use perform request instead", replaceWith = ReplaceWith("performRequest")) - suspend fun request( - userWalletId: UserWalletId, - requestBlock: suspend (header: String) -> - ApiResponse, - ): T = withContext(dispatchers.io) { - performRequest(userWalletId, requestBlock = requestBlock) - // to keep behaviour as previous - .fold( - ifRight = { it }, - ifLeft = { error -> error("Cannot perform request: $error") }, - ) - } - suspend fun performWithStaticToken( requestBlock: suspend (header: String) -> ApiResponse, ): Either = withContext(dispatchers.io) { diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt index 797d06f372..45b444d02f 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt @@ -53,6 +53,7 @@ internal class DefaultWalletsRepository( private val upgradeWalletNotificationDisabled: MutableStateFlow> = MutableStateFlow(mutableSetOf()) + @Deprecated("Hot wallet feature makes app always save user wallets. Do not use this method") override suspend fun shouldSaveUserWalletsSync(): Boolean { return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false) } diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/hot/TangemHotWalletSigner.kt b/data/wallets/src/main/java/com/tangem/data/wallets/hot/TangemHotWalletSigner.kt index ccef106815..58df536d84 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/hot/TangemHotWalletSigner.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/hot/TangemHotWalletSigner.kt @@ -64,7 +64,7 @@ class TangemHotWalletSigner @AssistedInject constructor( hotWalletId = userWallet.hotWalletId, dataToSign = dataToSign.map { signData -> val wallet = - userWallet.wallets.orEmpty().firstOrNull { it.publicKey.contentEquals(signData.publicKey) } + userWallet.wallets.orEmpty().firstOrNull { it.publicKey.contentEquals(publicKey.seedKey) } ?: return CompletionResult.Failure( TangemSdkError.ExceptionError(IllegalStateException("wallet is locked")), ) diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt index a40499ba87..d7faa6176a 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt @@ -27,6 +27,7 @@ import com.tangem.domain.yield.supply.models.YieldMarketToken import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus import com.tangem.domain.yield.supply.models.YieldSupplyMarketChartData import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runSuspendCatching import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext @@ -155,7 +156,7 @@ internal class DefaultYieldSupplyRepository( override suspend fun getTokenPendingStatus( userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, - ): YieldSupplyEnterStatus? = try { + ): YieldSupplyEnterStatus? = runSuspendCatching { val cryptoCurrency = cryptoCurrencyStatus.currency val walletManager = walletManagersFacade.getOrCreateWalletManager( userWalletId = userWalletId, @@ -174,10 +175,9 @@ internal class DefaultYieldSupplyRepository( hasRecentYieldExitTxs -> YieldSupplyEnterStatus.Exit else -> null } - } catch (e: Exception) { - Timber.e(e, "Failed to get pending yield supply status") - null - } + }.onFailure { exception -> + Timber.w(exception, "Failed to get pending yield supply status") + }.getOrNull() override fun getShouldShowYieldPromoBanner(): Flow { return appPreferencesStore.get(PreferencesKeys.YIELD_SUPPLY_SHOULD_SHOW_MAIN_PROMO_KEY, true) diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index 9edbf243da..1d69153c6e 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -57,6 +57,8 @@ sealed class CryptoCurrencyWarning { data object MigrationMaticToPol : CryptoCurrencyWarning() + data object MigrationClore : CryptoCurrencyWarning() + /** * Shows a warning about an available fee resource for a transaction in several blockchains (ex. Koinos) */ diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index d4611e9d40..6ca1aa64ce 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -41,16 +41,16 @@ class GetCurrencyWarningsUseCase( // don't add here notifications that require async requests return combine( - getCoinRelatedWarnings( + flow = getCoinRelatedWarnings( userWalletId = userWalletId, networkId = currency.network.id, currencyId = currency.id, derivationPath = derivationPath, isSingleWalletWithTokens = isSingleWalletWithTokens, ), - flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus)), - flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)), - flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)), + flow2 = flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus)), + flow3 = flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)), + flow4 = flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)), ) { coinRelatedWarnings, maybeRentWarning, maybeEdWarning, maybeFeeResource -> setOfNotNull( maybeRentWarning, @@ -62,6 +62,7 @@ class GetCurrencyWarningsUseCase( getBeaconChainShutdownWarning(rawId = currency.network.id.rawId), getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency), getMigrationFromMaticToPolWarning(currency), + getCloreMigrationWarning(currency), ) }.flowOn(dispatchers.io) } @@ -261,11 +262,20 @@ class GetCurrencyWarningsUseCase( } } + private fun getCloreMigrationWarning(currency: CryptoCurrency): CryptoCurrencyWarning? { + return if (currency.symbol == CLORE_SYMBOL && BlockchainUtils.isClore(currency.network.rawId)) { + CryptoCurrencyWarning.MigrationClore + } else { + null + } + } + private fun BigDecimal?.isZero(): Boolean { return this?.signum() == 0 } companion object { private const val MATIC_SYMBOL = "MATIC" + private const val CLORE_SYMBOL = "CLORE" } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt index 18401f9621..11760b5e2f 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt @@ -26,9 +26,9 @@ class GetWalletTotalBalanceUseCase( private val walletBalanceCache = ConcurrentHashMap() operator fun invoke( - userTallestIds: Collection, + userWalletsIds: Collection, ): LceFlow> { - val flows = userTallestIds.distinct() + val flows = userWalletsIds.distinct() .map { userWalletId -> invoke(userWalletId).map { maybeBalance -> userWalletId to maybeBalance diff --git a/domain/visa/build.gradle.kts b/domain/visa/build.gradle.kts index 3c13ebe644..3dfad027a7 100644 --- a/domain/visa/build.gradle.kts +++ b/domain/visa/build.gradle.kts @@ -25,6 +25,9 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + /** Feature API - remove after removing [TangemPayFeatureToggles] */ + implementation(projects.features.tangempay.details.api) + /** Security */ implementation(deps.spongecastle.core) diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt index 5ad9a9d3d9..cc26e4832a 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/TangemPayEligibilityManager.kt @@ -5,4 +5,5 @@ import com.tangem.domain.models.wallet.UserWallet interface TangemPayEligibilityManager { suspend fun getEligibleWallets(shouldExcludePaeraCustomers: Boolean): List + suspend fun getTangemPayAvailability(): Boolean } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt index e3fc78e653..568cf85006 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt @@ -26,10 +26,13 @@ interface OnboardingRepository { suspend fun checkCustomerWallet(userWalletId: UserWalletId): Either suspend fun checkCustomerEligibility(): Boolean + suspend fun getCustomerEligibility(): Boolean fun getSavedCustomerInfo(userWalletId: UserWalletId): CustomerInfo? suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean suspend fun setHideMainOnboardingBanner(userWalletId: UserWalletId) + + suspend fun disableTangemPay(userWalletId: UserWalletId): Either } \ No newline at end of file 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 16ebd064e6..6cf5c28a6f 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 @@ -9,6 +9,7 @@ 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 com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.security.DeviceSecurityInfoProvider import com.tangem.security.isSecurityExposed import kotlinx.coroutines.flow.* @@ -16,14 +17,11 @@ import timber.log.Timber private const val TAG = "TangemPayMainScreenCustomerInfoUseCase" -/** - * Returns tangem pay customer info for the main screen banner - * Works only if the user already authorised at least once (won't emit anything otherwise) - */ class TangemPayMainScreenCustomerInfoUseCase( private val onboardingRepository: OnboardingRepository, private val customerOrderRepository: CustomerOrderRepository, private val eligibilityManager: TangemPayEligibilityManager, + private val tangemPayFeatureToggles: TangemPayFeatureToggles, private val deviceSecurity: DeviceSecurityInfoProvider, ) { @@ -46,7 +44,7 @@ class TangemPayMainScreenCustomerInfoUseCase( .fold( ifLeft = { error -> Timber.tag(TAG).e("Failed checkCustomerWallet for $userWalletId: ${error.javaClass.simpleName}") - if (error is VisaApiError.NotPaeraCustomer) { + if (error is VisaApiError.NotPaeraCustomer && tangemPayFeatureToggles.isEntryPointsEnabled) { showOnboardingBannerIfEligible(userWalletId) } else { updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) @@ -64,8 +62,13 @@ class TangemPayMainScreenCustomerInfoUseCase( .map(MainCustomerInfoContentState::Content) updateState(userWalletId, result) } else { - // if there's no tangem pay, check eligibility and show onboarding banner - showOnboardingBannerIfEligible(userWalletId) + if (tangemPayFeatureToggles.isEntryPointsEnabled) { + // if there's no tangem pay, check eligibility and show onboarding banner + showOnboardingBannerIfEligible(userWalletId) + } else { + // ignore if there's no TangemPay + updateState(userWalletId, TangemPayCustomerInfoError.UnknownError.left()) + } } }, ) diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt index 96266b809d..430653e85c 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt @@ -11,12 +11,13 @@ import kotlinx.coroutines.flow.Flow @Suppress("TooManyFunctions") interface WalletsRepository { + @Deprecated("Hot wallet feature makes app always save user wallets. Do not use this method") suspend fun shouldSaveUserWalletsSync(): Boolean - @Deprecated("Hot wallet make always save user wallets. Do not use this method") + @Deprecated("Hot wallet feature makes app always save user wallets. Do not use this method") fun shouldSaveUserWallets(): Flow - @Deprecated("Hot wallet make always save user wallets. Do not use this method") + @Deprecated("Hot wallet feature makes app always save user wallets. Do not use this method") suspend fun saveShouldSaveUserWallets(item: Boolean) suspend fun useBiometricAuthentication(): Boolean diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ShouldSaveUserWalletsSyncUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ShouldSaveUserWalletsSyncUseCase.kt index 16efc0eadb..85e7b0265f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ShouldSaveUserWalletsSyncUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ShouldSaveUserWalletsSyncUseCase.kt @@ -2,6 +2,7 @@ package com.tangem.domain.wallets.usecase import com.tangem.domain.wallets.repository.WalletsRepository +@Deprecated("Hot wallet feature makes app always save user wallets. Do not use this method") class ShouldSaveUserWalletsSyncUseCase(private val walletsRepository: WalletsRepository) { suspend operator fun invoke(): Boolean = walletsRepository.shouldSaveUserWalletsSync() diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt index f0b5c39b43..9dcaed0bd4 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt @@ -102,14 +102,16 @@ class YieldSupplyGetRewardsBalanceUseCase( }.flowOn(dispatcherProvider.default) private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal, maxDecimals: Int): Int { - if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS + val effectiveMin = MIN_DECIMALS.coerceAtMost(maxDecimals) + + if (perTickDeltaAbs <= BigDecimal.ZERO) return effectiveMin val perTickAsDouble = perTickDeltaAbs.toDouble() - if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return MIN_DECIMALS + if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return effectiveMin val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble val raw = ceil(-ln(safe) / LN_10) - return raw.toInt().coerceIn(MIN_DECIMALS, maxDecimals) + return raw.toInt().coerceIn(effectiveMin, maxDecimals) } private fun perTickDelta(amount: BigDecimal, apyFraction: BigDecimal): BigDecimal { diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt index 878b0ca49e..6d40c26c08 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt @@ -459,4 +459,72 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { YieldSupplyGetRewardsBalanceUseCase.FIAT_MAX_DECIMALS, ) } + + @Test + fun `GIVEN token with decimals less than MIN_DECIMALS WHEN invoke THEN does not crash`() = runTest { + val network = createNetwork() + val tokenId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId(network.rawId), + suffix = CryptoCurrency.ID.Suffix.RawID("low-decimals-token", "0xLowDecimals"), + ) + val tokenWithLowDecimals = CryptoCurrency.Token( + id = tokenId, + network = network, + name = "Low Decimals Token", + symbol = "LDT", + decimals = 0, + iconUrl = null, + isCustom = false, + contractAddress = "0xLowDecimals", + ) + + val amount = BigDecimal("100.00") + val apy = BigDecimal("10.0") + + val status = CryptoCurrencyStatus( + currency = tokenWithLowDecimals, + value = CryptoCurrencyStatus.Custom( + amount = amount, + fiatAmount = null, + fiatRate = BigDecimal.ONE, + priceChange = null, + stakingBalance = null, + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = NetworkAddress.Single( + NetworkAddress.Address(value = "0xabc", type = NetworkAddress.Address.Type.Primary), + ), + sources = CryptoCurrencyStatus.Sources(), + ), + ) + + coEvery { repository.getCachedMarkets() } returns listOf( + YieldMarketToken( + tokenAddress = tokenWithLowDecimals.contractAddress, + chainId = 1, + apy = apy, + isActive = true, + maxFeeNative = BigDecimal.ZERO, + maxFeeUSD = BigDecimal.ZERO, + backendId = "id", + ), + ) + + val dispatcherProvider = testDispatcherProvider(this) + val useCase = YieldSupplyGetRewardsBalanceUseCase(repository, dispatcherProvider) + val appCurrency = AppCurrency.Default + + val deferred = async { useCase(status, appCurrency).take(2).toList() } + + testScheduler.advanceUntilIdle() + advanceTimeBy(TICK_MILLIS) + testScheduler.advanceUntilIdle() + + val emissions = deferred.await() + assertThat(emissions).hasSize(2) + assertThat(emissions[0].cryptoBalance).isNotNull() + assertThat(emissions[1].cryptoBalance).isNotNull() + } } \ No newline at end of file diff --git a/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/CreateWalletSelectionModel.kt b/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/CreateWalletSelectionModel.kt index e3eab1b7e1..14265dcd96 100644 --- a/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/CreateWalletSelectionModel.kt +++ b/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/CreateWalletSelectionModel.kt @@ -54,32 +54,12 @@ internal class CreateWalletSelectionModel @Inject constructor( style = LabelStyle.ACCENT, ), description = resourceReference(R.string.wallet_add_hardware_description), - features = persistentListOf( - CreateWalletSelectionUM.Feature( - iconResId = R.drawable.ic_add_wallet_16, - title = resourceReference(R.string.wallet_add_hardware_info_create), - ), - CreateWalletSelectionUM.Feature( - iconResId = R.drawable.ic_import_seed_16, - title = resourceReference(R.string.wallet_add_import_seed_phrase), - ), - ), onClick = ::onHardwareWalletClick, ), CreateWalletSelectionUM.Block( title = resourceReference(R.string.wallet_create_mobile_title), titleLabel = null, description = resourceReference(R.string.wallet_add_mobile_description), - features = persistentListOf( - CreateWalletSelectionUM.Feature( - iconResId = R.drawable.ic_mobile_wallet_16, - title = resourceReference(R.string.hw_create_title), - ), - CreateWalletSelectionUM.Feature( - iconResId = R.drawable.ic_import_seed_16, - title = resourceReference(R.string.wallet_add_import_seed_phrase), - ), - ), onClick = ::onMobileWalletClick, ), ), diff --git a/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/entity/CreateWalletSelectionUM.kt b/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/entity/CreateWalletSelectionUM.kt index 2b1adb4ca1..685efc6da4 100644 --- a/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/entity/CreateWalletSelectionUM.kt +++ b/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/entity/CreateWalletSelectionUM.kt @@ -16,12 +16,6 @@ internal data class CreateWalletSelectionUM( val title: TextReference, val titleLabel: LabelUM?, val description: TextReference, - val features: ImmutableList, val onClick: () -> Unit, ) - - data class Feature( - val iconResId: Int, - val title: TextReference, - ) } \ No newline at end of file diff --git a/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/ui/CreateWalletSelectionContent.kt b/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/ui/CreateWalletSelectionContent.kt index 37e1523bed..067a969771 100644 --- a/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/ui/CreateWalletSelectionContent.kt +++ b/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/ui/CreateWalletSelectionContent.kt @@ -33,7 +33,6 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.createwalletselection.entity.CreateWalletSelectionUM import com.tangem.features.createwalletselection.impl.R -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @Suppress("LongMethod") @@ -103,7 +102,6 @@ internal fun CreateWalletSelectionContent(state: CreateWalletSelectionUM, modifi .padding(top = 8.dp), title = block.title.resolveReference(), description = block.description.resolveReference(), - features = block.features, badge = block.titleLabel?.let { { Label(it) } }, @@ -126,7 +124,6 @@ private fun WalletBlock( title: String, description: String, onClick: () -> Unit, - features: ImmutableList, modifier: Modifier = Modifier, badge: @Composable (() -> Unit)? = null, ) { @@ -163,43 +160,6 @@ private fun WalletBlock( style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) - if (features.isNotEmpty()) { - HorizontalDivider( - modifier = Modifier.padding(top = 12.dp), - thickness = 0.5.dp, - color = TangemTheme.colors.stroke.primary, - ) - features.forEach { feature -> - Feature( - feature = feature, - modifier = Modifier - .padding(top = 12.dp), - ) - } - } - } -} - -@Composable -private fun Feature(feature: CreateWalletSelectionUM.Feature, modifier: Modifier = Modifier) { - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size16), - painter = painterResource(id = feature.iconResId), - contentDescription = null, - tint = TangemTheme.colors.icon.accent, - ) - Text( - modifier = Modifier - .weight(1f, fill = false) - .padding(start = 6.dp), - text = feature.title.resolveReference(), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.secondary, - ) } } @@ -259,32 +219,12 @@ private fun PreviewCreateWalletContent() { style = LabelStyle.ACCENT, ), description = resourceReference(R.string.wallet_add_hardware_description), - features = persistentListOf( - CreateWalletSelectionUM.Feature( - iconResId = R.drawable.ic_add_wallet_16, - title = resourceReference(R.string.wallet_add_hardware_info_create), - ), - CreateWalletSelectionUM.Feature( - iconResId = R.drawable.ic_import_seed_16, - title = resourceReference(R.string.wallet_add_import_seed_phrase), - ), - ), onClick = { }, ), CreateWalletSelectionUM.Block( title = resourceReference(R.string.wallet_create_mobile_title), titleLabel = null, description = resourceReference(R.string.wallet_add_mobile_description), - features = persistentListOf( - CreateWalletSelectionUM.Feature( - iconResId = R.drawable.ic_mobile_wallet_16, - title = resourceReference(R.string.hw_create_title), - ), - CreateWalletSelectionUM.Feature( - iconResId = R.drawable.ic_import_seed_16, - title = resourceReference(R.string.wallet_add_import_seed_phrase), - ), - ), onClick = { }, ), ), diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index b730125b22..1c4c53f372 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -53,19 +53,19 @@ import javax.inject.Inject @Suppress("LongParameterList") internal class DetailsModel @Inject constructor( socialsBuilder: SocialsBuilder, + paramsContainer: ParamsContainer, + feedbackFeatureToggles: FeedbackFeatureToggles, private val itemsBuilder: ItemsBuilder, private val appVersionProvider: AppVersionProvider, private val checkIsWalletConnectAvailableUseCase: CheckIsWalletConnectAvailableUseCase, private val router: Router, private val urlOpener: UrlOpener, private val appInstanceIdProvider: AppInstanceIdProvider, - paramsContainer: ParamsContainer, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, private val appStateHolder: ReduxStateHolder, private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val getWalletsUseCase: GetWalletsUseCase, - private val feedbackFeatureToggles: FeedbackFeatureToggles, override val dispatchers: CoroutineDispatcherProvider, private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase, private val hotWalletFeatureToggles: HotWalletFeatureToggles, @@ -248,13 +248,24 @@ internal class DetailsModel @Inject constructor( } private fun addTangemPayItemIfEligible() { - if (!tangemPayFeatureToggles.isTangemPayEnabled) return + if (!tangemPayFeatureToggles.isEntryPointsEnabled) return modelScope.launch { val isEligible = tangemPayEligibilityManager .getEligibleWallets(shouldExcludePaeraCustomers = true) .isNotEmpty() if (isEligible) { - items.update { itemsBuilder.addVisaItem(it) } + items.update { itemsBuilder.addTangemPayItem(items = it, onClick = ::onTangemPayItemClicked) } + } + } + } + + private fun onTangemPayItemClicked() { + modelScope.launch { + val isEligible = tangemPayEligibilityManager.getTangemPayAvailability() + if (isEligible) { + router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.FromBannerInSettings)) + } else { + items.update { itemsBuilder.removeTangemPayItem(it) } } } } 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 0cdd6b6a10..7ee2b6eccd 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 @@ -14,6 +14,8 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList import javax.inject.Inject +private const val TANGEM_PAY_ITEM_ID = "get_tangem_pay" + @ModelScoped internal class ItemsBuilder @Inject constructor(private val router: Router) { @@ -37,10 +39,13 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) { ).let(::add) }.toImmutableList() - fun addVisaItem(items: ImmutableList): ImmutableList { + fun addTangemPayItem(items: ImmutableList, onClick: () -> Unit): ImmutableList { return items.toMutableList().map { block -> if (block.id == "shop" && block is DetailsItemUM.Basic) { - val newItems = block.items.toMutableList().apply { add(getVisaItem()) } + val newItems = block + .items + .toMutableList() + .apply { add(getTangemPayItem(onClick = onClick)) } block.copy(items = newItems.toImmutableList()) } else { block @@ -48,6 +53,16 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) { }.toImmutableList() } + fun removeTangemPayItem(items: ImmutableList): ImmutableList { + return items.map { block -> + if (block is DetailsItemUM.Basic && block.items.any { it.id == TANGEM_PAY_ITEM_ID }) { + block.copy(items = block.items.filter { it.id != TANGEM_PAY_ITEM_ID }.toImmutableList()) + } else { + block + } + }.toImmutableList() + } + private fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean, userWalletId: UserWalletId): DetailsItemUM? { return if (isWalletConnectAvailable) { DetailsItemUM.WalletConnect( @@ -126,14 +141,12 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) { }.toPersistentList(), ) - private fun getVisaItem(): DetailsItemUM.Basic.Item = DetailsItemUM.Basic.Item( - id = "get_tangem_visa", + private fun getTangemPayItem(onClick: () -> Unit): DetailsItemUM.Basic.Item = DetailsItemUM.Basic.Item( + id = TANGEM_PAY_ITEM_ID, block = BlockUM( - text = resourceReference(R.string.details_get_visa), + text = resourceReference(R.string.tangempay_get_tangem_pay), iconRes = R.drawable.ic_tangem_pay_24, - onClick = { - router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.FromBannerInSettings)) - }, + onClick = onClick, ), ) } \ No newline at end of file diff --git a/features/hot-wallet/impl/build.gradle.kts b/features/hot-wallet/impl/build.gradle.kts index 36d956f24c..380b51678e 100644 --- a/features/hot-wallet/impl/build.gradle.kts +++ b/features/hot-wallet/impl/build.gradle.kts @@ -38,7 +38,6 @@ dependencies { implementation(projects.domain.feedback) implementation(projects.domain.feedback.models) implementation(projects.domain.hotWallet) - implementation(projects.domain.notifications) /** Common */ implementation(projects.common.ui) diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/AddExistingWalletModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/AddExistingWalletModel.kt index dc5afd1b9f..cd8a7936ba 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/AddExistingWalletModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/entry/AddExistingWalletModel.kt @@ -15,8 +15,8 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.DialogMessage import com.tangem.core.ui.message.EventMessageAction import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.settings.ShouldAskPermissionUseCase import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase -import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents import com.tangem.features.hotwallet.addexistingwallet.entry.routing.AddExistingWalletRoute import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent @@ -25,6 +25,7 @@ import com.tangem.features.hotwallet.accesscode.AccessCodeComponent import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks +import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.MutableStateFlow @@ -36,7 +37,7 @@ import javax.inject.Inject internal class AddExistingWalletModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val router: Router, - private val notificationsRepository: NotificationsRepository, + private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, @GlobalUiMessageSender private val uiMessageSender: UiMessageSender, private val trackingContextProxy: TrackingContextProxy, private val setAccessCodeSkippedUseCase: SetAccessCodeSkippedUseCase, @@ -78,8 +79,8 @@ internal class AddExistingWalletModel @Inject constructor( private fun navigateToPushNotificationsOrNext() { modelScope.launch { - val shouldAskNotificationPermissions = notificationsRepository.shouldAskNotificationPermissionsViaBs() - if (shouldAskNotificationPermissions) { + val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION) + if (shouldRequestPush) { stackNavigation.replaceAll(AddExistingWalletRoute.PushNotifications) } else { stackNavigation.replaceAll(AddExistingWalletRoute.SetupFinished) diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/CreateHardwareWalletModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/CreateHardwareWalletModel.kt index 0373179468..39a2c8b242 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/CreateHardwareWalletModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/CreateHardwareWalletModel.kt @@ -21,6 +21,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.error.SaveWalletError import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents import com.tangem.domain.wallets.builder.ColdUserWalletBuilder @@ -133,19 +134,16 @@ internal class CreateHardwareWalletModel @Inject constructor( } saveWalletUseCase(userWallet = userWallet).fold( - ifLeft = { + ifLeft = { saveWalletError -> delay(HIDE_PROGRESS_DELAY) setLoading(false) - when (it) { - is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet") - is SaveWalletError.WalletAlreadySaved -> { - userWalletsListRepository.unlock( - userWalletId = userWallet.walletId, - unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), - ).onRight { - router.replaceAll(AppRoute.Wallet) - } - } + when (saveWalletError) { + is SaveWalletError.DataError -> Timber.e(saveWalletError.toString(), "Unable to save user wallet") + is SaveWalletError.WalletAlreadySaved -> handleAlreadySavedCard( + saveWalletError.messageId, + walletId = userWallet.walletId, + scanResponse = scanResponse, + ) } }, ifRight = { @@ -175,4 +173,18 @@ internal class CreateHardwareWalletModel @Inject constructor( ), ) } + + private suspend fun handleAlreadySavedCard(messageId: Int, walletId: UserWalletId, scanResponse: ScanResponse) { + uiMessageSender.send( + message = DialogMessage( + message = resourceReference(messageId), + ), + ) + userWalletsListRepository.unlock( + userWalletId = walletId, + unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), + ).onRight { + router.replaceAll(AppRoute.Wallet) + } + } } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt index 0f188dfe43..1d9409b60b 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt @@ -60,9 +60,8 @@ internal class CreateMobileWalletModel @Inject constructor( init { trackingContextProxy.addHotWalletContext() - analyticsEventHandler.send( - event = OnboardingAnalyticsEvent.Onboarding.Started(source = params.source), - ) + analyticsEventHandler.send(event = OnboardingAnalyticsEvent.Onboarding.Started(source = params.source)) + analyticsEventHandler.send(event = OnboardingAnalyticsEvent.Onboarding.AppsFlyerOnlyEntryScreenView()) analyticsEventHandler.send( event = OnboardingAnalyticsEvent.SeedPhrase.CreateMobileScreenOpened(source = params.source), ) diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationModel.kt index 2a1f014021..70cffd7a58 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletactivation/entry/WalletActivationModel.kt @@ -18,8 +18,8 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.DialogMessage import com.tangem.core.ui.message.EventMessageAction import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.settings.ShouldAskPermissionUseCase import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase -import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent @@ -28,6 +28,7 @@ import com.tangem.features.hotwallet.manualbackup.start.ManualBackupStartCompone import com.tangem.features.hotwallet.accesscode.AccessCodeComponent import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent import com.tangem.features.hotwallet.walletactivation.entry.routing.WalletActivationRoute +import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.hotwallet.WalletActivationComponent import com.tangem.features.hotwallet.stepper.api.HotWalletStepperComponent import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks @@ -43,7 +44,7 @@ internal class WalletActivationModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, private val router: Router, - private val notificationsRepository: NotificationsRepository, + private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, private val setAccessCodeSkippedUseCase: SetAccessCodeSkippedUseCase, @GlobalUiMessageSender private val uiMessageSender: UiMessageSender, private val trackingContextProxy: TrackingContextProxy, @@ -117,8 +118,8 @@ internal class WalletActivationModel @Inject constructor( private fun navigateToPushNotificationsOrNext() { modelScope.launch { - val shouldAskNotificationPermissions = notificationsRepository.shouldAskNotificationPermissionsViaBs() - if (shouldAskNotificationPermissions) { + val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION) + if (shouldRequestPush) { stackNavigation.replaceAll(WalletActivationRoute.PushNotifications) } else { stackNavigation.replaceAll(WalletActivationRoute.SetupFinished) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index dc82bfb8a8..e62d311737 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -20,6 +20,8 @@ import com.tangem.domain.onramp.model.OnrampSource import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.TokenActionsState +import com.tangem.domain.tokens.model.details.NavigationAction +import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase import com.tangem.features.markets.impl.R import com.tangem.features.markets.portfolio.impl.loader.PortfolioData import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM @@ -41,6 +43,7 @@ internal class TokenActionsHandler @AssistedInject constructor( private val isDemoCardUseCase: IsDemoCardUseCase, private val messageSender: UiMessageSender, private val shareManager: ShareManager, + private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase, ) { private val disabledActionsInDemoMode = buildSet { @@ -186,22 +189,37 @@ internal class TokenActionsHandler @AssistedInject constructor( val (userWalletId, cryptoCurrencyStatus) = cryptoCurrencyData.let { currencyData -> currencyData.userWallet.walletId to currencyData.status } - if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true) { - router.push( - AppRoute.YieldSupplyActive( + val tokenEnterStatus = yieldSupplyEnterStatusUseCase(userWalletId, cryptoCurrencyStatus).getOrNull() + val isActiveYield = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true + + when { + tokenEnterStatus != null -> router.push( + AppRoute.CurrencyDetails( userWalletId = userWalletId, - cryptoCurrency = cryptoCurrencyStatus.currency, - apy = yieldSupplyApy, - ), - ) - } else { - router.push( - AppRoute.YieldSupplyPromo( - userWalletId = userWalletId, - cryptoCurrency = cryptoCurrencyStatus.currency, - apy = yieldSupplyApy, + currency = cryptoCurrencyStatus.currency, + navigationAction = NavigationAction.YieldSupply( + isActive = isActiveYield, + ), ), ) + isActiveYield -> { + router.push( + AppRoute.YieldSupplyActive( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + apy = yieldSupplyApy, + ), + ) + } + else -> { + router.push( + AppRoute.YieldSupplyPromo( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + apy = yieldSupplyApy, + ), + ) + } } } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/QuickActionUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/QuickActionUM.kt index e0987c16e7..777dd019c1 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/QuickActionUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/QuickActionUM.kt @@ -44,7 +44,7 @@ internal sealed class QuickActionUM( data class YieldMode( private val apy: String, ) : QuickActionUM( - title = resourceReference(R.string.yield_module_start_earning), + title = resourceReference(R.string.common_yield_mode), description = resourceReference(R.string.yield_module_main_screen_promo_banner_message, wrappedList(apy)), icon = R.drawable.ic_analytics_up_mini_24, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/TokenActionsBSContentUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/TokenActionsBSContentUM.kt index 078c25562f..20db6ec796 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/TokenActionsBSContentUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/TokenActionsBSContentUM.kt @@ -48,7 +48,7 @@ internal data class TokenActionsBSContentUM( iconRes = R.drawable.ic_staking_24, ), YieldMode( - text = resourceReference(R.string.yield_module_start_earning), + text = resourceReference(R.string.common_yield_mode), iconRes = R.drawable.ic_analytics_up_mini_24, ), ; diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt index e214f30658..d33935747e 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt @@ -18,6 +18,7 @@ import com.tangem.domain.promo.PromoRepository import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.settings.usercountry.models.UserCountryError +import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions import com.tangem.features.markets.tokenlist.impl.analytics.MarketsListAnalyticsEvent import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListBatchFlowManager import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListUMStateManager @@ -116,7 +117,7 @@ internal class MarketsListModel @Inject constructor( flow4 = shouldShowYieldModeMarketPromoUseCase( appCurrency = currentAppCurrency.value, interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(), - ), + ).conflate(), flow5 = getUserCountryUseCase.invoke(), ) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo, userCountry -> MarketsItemsData( @@ -134,7 +135,7 @@ internal class MarketsListModel @Inject constructor( flow3 = shouldShowYieldModeMarketPromoUseCase( appCurrency = currentAppCurrency.value, interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(), - ), + ).conflate(), flow4 = getUserCountryUseCase.invoke(), ) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo, userCountry -> MarketsItemsData( @@ -147,7 +148,8 @@ internal class MarketsListModel @Inject constructor( } } }.collect { marketsItemsData -> - val shouldShowYieldModePromo = marketsItemsData.shouldShowYieldModePromo + val isApplyFCARestrictions = marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions() + val shouldShowYieldModePromo = marketsItemsData.shouldShowYieldModePromo && !isApplyFCARestrictions if (marketsListUMStateManager.state.value.marketsNotificationUM == null && shouldShowYieldModePromo) { analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoShown()) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt index 0930d3c1e4..0a7e31ebfc 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt @@ -176,14 +176,14 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif ) } - val marketsNotification = state.marketsNotificationUM + val marketsNotificationUM = state.marketsNotificationUM AnimatedVisibility( - state.isInSearchMode.not() && - state.selectedSortBy != SortByTypeUM.YieldSupply, + state.list !is ListUM.LoadingError && + state.isInSearchMode.not() && state.selectedSortBy != SortByTypeUM.YieldSupply, ) { val showMore = stringResourceSafe(R.string.common_show_more) - when (marketsNotification) { + when (marketsNotificationUM) { is MarketsNotificationUM.YieldSupplyPromo -> { val description = stringResourceSafe( R.string.markets_yield_supply_banner_description, @@ -199,7 +199,7 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif } YieldSupplyInMarketsPromoNotification( - config = marketsNotification.config.copy( + config = marketsNotificationUM.config.copy( subtitle = clickableDescription, ), modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt index b74e9a3d29..bbdcbf2697 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag +import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.components.UnableToLoadData import com.tangem.core.ui.components.buttons.SecondarySmallButton import com.tangem.core.ui.components.buttons.SmallButtonConfig @@ -174,6 +175,7 @@ private fun ShowTokensUnder100kItem(onShowTokensClick: () -> Unit, modifier: Mod onClick = onShowTokensClick, ), ) + SpacerH12() } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/analytics/OnboardingEvent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/analytics/OnboardingEvent.kt index 1aa8695bab..101eb5ff04 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/analytics/OnboardingEvent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/analytics/OnboardingEvent.kt @@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.common.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent sealed class OnboardingEvent( category: String, @@ -33,7 +34,12 @@ sealed class OnboardingEvent( put("Seed Phrase Length", seedPhraseLength.toString()) } }, - ) + ), AppsFlyerIncludedEvent { + override val appsFlyerReplacedEvent = when (creationType) { + WalletCreationType.NewSeed, WalletCreationType.PrivateKey -> "wallet_created_successfully" + WalletCreationType.SeedImport -> "wallet_imported" + } + } sealed class WalletCreationType(val value: String) { data object PrivateKey : WalletCreationType(value = "Private Key") 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 9121e9d618..067870cc0e 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 @@ -707,6 +707,11 @@ internal class SwapInteractorImpl @AssistedInject constructor( userWalletId: UserWalletId, ): Throwable? { val currency = fromToken.currency + val blockchain = currency.network.toBlockchain() + // Stellar validation removed because swap uses destination = "0" and throws an error + if (blockchain == Blockchain.Stellar) { + return null + } val fee = Fee.Common( amount = Amount( value = when (feeState) { @@ -714,7 +719,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue is TxFeeState.SingleFeeState -> feeState.fee.feeValue }, - blockchain = currency.network.toBlockchain(), + blockchain = blockchain, ), ) diff --git a/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt b/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt index 393e589bce..0345c3cb32 100644 --- a/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt +++ b/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt @@ -2,4 +2,5 @@ package com.tangem.features.tangempay interface TangemPayFeatureToggles { val isTangemPayEnabled: Boolean + val isEntryPointsEnabled: Boolean } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt index a51c11a3bc..9b909186a0 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt @@ -7,4 +7,6 @@ internal class DefaultTangemPayFeatureToggles( ) : TangemPayFeatureToggles { override val isTangemPayEnabled get() = featureTogglesManager.isFeatureEnabled("TANGEM_PAY_ENABLED") + override val isEntryPointsEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("TANGEM_PAY_ENTRYPOINT_ENABLED") } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayChangePinModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayChangePinModel.kt index 405a45ad13..6d9f9d47f2 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayChangePinModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayChangePinModel.kt @@ -5,9 +5,13 @@ 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.decompose.navigation.Router +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.pay.model.SetPinResult import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent +import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.entity.TangemPayChangePinUM import com.tangem.features.tangempay.model.transformers.PinCodeChangeTransformer import com.tangem.features.tangempay.navigation.TangemPayDetailsInnerRoute @@ -23,6 +27,7 @@ import javax.inject.Inject internal class TangemPayChangePinModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val uiMessageSender: UiMessageSender, private val router: Router, private val cardDetailsRepository: TangemPayCardDetailsRepository, ) : Model() { @@ -49,8 +54,12 @@ internal class TangemPayChangePinModel @Inject constructor( } uiState.update { it.copy(submitButtonLoading = false) } when (result) { + SetPinResult.PIN_TOO_WEAK -> { + uiMessageSender.send( + message = ToastMessage(resourceReference(R.string.tangempay_pin_validation_error_message)), + ) + } SetPinResult.SUCCESS -> router.push(TangemPayDetailsInnerRoute.ChangePINSuccess) - SetPinResult.PIN_TOO_WEAK, SetPinResult.DECRYPTION_ERROR, SetPinResult.UNKNOWN_ERROR, null, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayViewPinErrorStateTransformer.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayViewPinErrorStateTransformer.kt index eacc9656fd..abf2326617 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayViewPinErrorStateTransformer.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/TangemPayViewPinErrorStateTransformer.kt @@ -4,11 +4,11 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2 import com.tangem.core.ui.components.bottomsheets.message.icon import com.tangem.core.ui.components.bottomsheets.message.infoBlock +import com.tangem.core.ui.components.bottomsheets.message.messageBottomSheetUM import com.tangem.core.ui.components.bottomsheets.message.onClick import com.tangem.core.ui.components.bottomsheets.message.secondaryButton import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.message.bottomSheetMessage import com.tangem.features.tangempay.entity.TangemPayViewPinUM import com.tangem.utils.transformer.Transformer @@ -16,7 +16,7 @@ internal class TangemPayViewPinErrorStateTransformer : Transformer null } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index b1ae6761a1..5733db0e18 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -258,6 +258,11 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { subtitle = resourceReference(id = R.string.warning_matic_migration_message), ) + data object MigrationClore : Warning( + title = resourceReference(id = R.string.warning_clore_migration_title), + subtitle = resourceReference(id = R.string.warning_clore_migration_message), + ) + data object UsedOutdatedData : TokenDetailsNotification( config = NotificationConfig( subtitle = resourceReference(R.string.warning_some_token_balances_not_updated), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index 0b4d3a4e71..eb54aca878 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -155,6 +155,7 @@ internal class TokenDetailsNotificationConverter( }, ) is CryptoCurrencyWarning.MigrationMaticToPol -> MigrationMaticToPol + is CryptoCurrencyWarning.MigrationClore -> MigrationClore is CryptoCurrencyWarning.UsedOutdatedDataWarning -> UsedOutdatedData } } 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 c4b8aeb87c..ba456f80f5 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 @@ -304,7 +304,9 @@ internal class WalletModel @Inject constructor( notificationsRepository.setShouldAskNotificationPermissionsViaBs(true) return@launch } - if (!isBiometricsEnabled) return@launch + if (!hotWalletFeatureToggles.isHotWalletEnabled && !isBiometricsEnabled) { + return@launch + } if (!shouldShow) { return@launch } @@ -677,7 +679,7 @@ internal class WalletModel @Inject constructor( } private suspend fun unlockWallet(action: WalletsUpdateActionResolver.Action.UnlockWallet) { - withContext(dispatchers.io) { delay(timeMillis = 700) } + delay(timeMillis = 700) stateHolder.update( transformer = UnlockWalletTransformer( @@ -694,7 +696,7 @@ internal class WalletModel @Inject constructor( ) action.unlockedWallets.onEach { userWallet -> - modelScope.launch { fetchWalletContent(userWallet = userWallet) } + fetchWalletContent(userWallet = userWallet) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletsUpdateActionResolver.kt index d498e480a0..4946ef99e9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletsUpdateActionResolver.kt @@ -64,6 +64,15 @@ internal class WalletsUpdateActionResolver @Inject constructor( selectedWallet: UserWallet, ): Action { return when { + isAnyWalletUnlocked(state, wallets) -> { + Action.UnlockWallet( + selectedWallet = selectedWallet, + unlockedWallets = wallets.filterNot(UserWallet::isLocked), + ) + } + isAnyWalletNameChanged(state, wallets) -> { + getRenameWalletsAction(state, wallets) + } isAnyHotWalletUpgraded(state, wallets) -> { getHotWalletsUpgradedAction(state, wallets, selectedWallet) } @@ -79,15 +88,6 @@ internal class WalletsUpdateActionResolver @Inject constructor( selectedWallet = selectedWallet, ) } - isAnyWalletNameChanged(state, wallets) -> { - getRenameWalletsAction(state, wallets) - } - isAnyWalletUnlocked(state, wallets) -> { - Action.UnlockWallet( - selectedWallet = selectedWallet, - unlockedWallets = wallets.filterNot(UserWallet::isLocked), - ) - } isSelectedWalletCardsCountChanged(state, selectedWallet) -> { Action.UpdateWalletCardCount(selectedWallet) } 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 b00b4b5886..e7a6f9325e 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 @@ -6,16 +6,24 @@ import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.res.R import com.tangem.core.ui.components.bottomsheets.message.* import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.message.DialogMessage +import com.tangem.core.ui.message.EventMessageAction +import com.tangem.core.ui.message.ToastMessage import com.tangem.core.ui.message.bottomSheetMessage 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.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayDetailsConfig +import com.tangem.domain.pay.TangemPayEligibilityManager import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayHideOnboardingStateTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshNeededStateTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshShowProgressTransformer import com.tangem.features.tangempay.TangemPayFeatureToggles import kotlinx.coroutines.launch import javax.inject.Inject @@ -24,7 +32,11 @@ internal interface TangemPayIntents { suspend fun onPullToRefresh() - fun onRefreshPayToken(userWalletId: UserWalletId) + fun onRefreshPayToken(userWallet: UserWallet) + + fun openDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig) + + fun onKycProgressClicked(userWalletId: UserWalletId) fun onIssuingCardClicked() @@ -47,6 +59,8 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( private val getWalletMetainfoUseCase: GetWalletMetaInfoUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, + private val tangemPayOnboardingRepository: OnboardingRepository, + private val tangemPayEligibilityManager: TangemPayEligibilityManager, private val uiMessageSender: UiMessageSender, ) : BaseWalletClickIntents(), TangemPayIntents { @@ -60,13 +74,69 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } - override fun onRefreshPayToken(userWalletId: UserWalletId) { + override fun onRefreshPayToken(userWallet: UserWallet) { + stateHolder.update(TangemPayRefreshShowProgressTransformer(userWallet.walletId)) + modelScope.launch { - produceInitialDataTangemPay.invoke(userWalletId) - tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) + produceInitialDataTangemPay.invoke(userWallet.walletId) + .onRight { tangemPayMainScreenCustomerInfoUseCase.fetch(userWallet.walletId) } + .onLeft { + stateHolder.update( + transformer = TangemPayRefreshNeededStateTransformer( + userWallet = userWallet, + userWalletId = userWallet.walletId, + onRefreshClick = { onRefreshPayToken(userWallet) }, + ), + ) + } } } + override fun openDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig) { + router.openTangemPayDetails( + userWalletId = userWalletId, + config = config, + ) + } + + override fun onKycProgressClicked(userWalletId: UserWalletId) { + val cancelKycConfirmDialogMessage = DialogMessage( + title = resourceReference(R.string.tangempay_kyc_confirm_cancellation_alert_title), + message = resourceReference(R.string.tangempay_kyc_confirm_cancellation_description), + firstAction = EventMessageAction( + title = resourceReference(R.string.common_not_now), + onClick = { }, + ), + secondAction = EventMessageAction( + title = resourceReference(R.string.common_confirm), + onClick = { disableTangemPay(userWalletId) }, + ), + ) + val kycInfoBottomSheet = bottomSheetMessage { + infoBlock { + iconImage(res = com.tangem.core.ui.R.drawable.img_visa_notification) + title = resourceReference(R.string.tangempay_kyc_in_progress) + body = resourceReference(R.string.tangempay_kyc_in_progress_popup_description) + } + primaryButton { + text = resourceReference(R.string.tangempay_kyc_in_progress_notification_button) + onClick = { + router.openTangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding(userWalletId)) + closeBs() + } + } + secondaryButton { + text = resourceReference(R.string.tangempay_cancel_kyc) + onClick = { + uiMessageSender.send(cancelKycConfirmDialogMessage) + closeBs() + } + } + } + + uiMessageSender.send(kycInfoBottomSheet) + } + override fun onIssuingCardClicked() { val issuingBottomSheet = bottomSheetMessage { infoBlock { @@ -123,7 +193,14 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( } override fun onOnboardingBannerClick(userWalletId: UserWalletId) { - router.openTangemPayOnboarding(mode = AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain(userWalletId)) + modelScope.launch { + val isEligible = tangemPayEligibilityManager.getTangemPayAvailability() + if (isEligible) { + router.openTangemPayOnboarding(mode = AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain(userWalletId)) + } else { + tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) + } + } } override fun onOnboardingBannerCloseClick(userWalletId: UserWalletId) { @@ -132,4 +209,12 @@ internal class TangemPayClickIntentsImplementor @Inject constructor( onboardingRepository.setHideMainOnboardingBanner(userWalletId) } } + + private fun disableTangemPay(userWalletId: UserWalletId) { + modelScope.launch { + tangemPayOnboardingRepository.disableTangemPay(userWalletId) + .onRight { tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId) } + .onLeft { uiMessageSender.send(ToastMessage(resourceReference(R.string.common_something_went_wrong))) } + } + } } \ No newline at end of file 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 1802e3e2a7..2fa16a3f5f 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 @@ -8,6 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam 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.StakingBalance import com.tangem.domain.models.wallet.UserWallet @@ -24,6 +25,7 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase import com.tangem.feature.wallet.presentation.account.AccountDependencies +import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController @@ -61,6 +63,10 @@ internal interface WalletContentClickIntents { fun onYieldPromoCloseClick() + fun onYieldPromoShown(cryptoCurrency: CryptoCurrency) + + fun onYieldPromoClicked(cryptoCurrency: CryptoCurrency) + fun onAccountExpandClick(account: Account) fun onAccountCollapseClick(account: Account) @@ -80,7 +86,7 @@ internal interface WalletContentClickIntents { fun onNFTClick(userWallet: UserWallet) } -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") @ModelScoped internal class WalletContentClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, @@ -99,6 +105,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val accountDependencies: AccountDependencies, private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase, private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase, + private val tokenListAnalyticsSender: TokenListAnalyticsSender, ) : BaseWalletClickIntents(), WalletContentClickIntents { override fun onDetailsClick() { @@ -204,6 +211,27 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } } + override fun onYieldPromoShown(cryptoCurrency: CryptoCurrency) { + modelScope.launch(dispatchers.io) { + tokenListAnalyticsSender.sendYieldPromoShown( + userWalletId = stateHolder.getSelectedWalletId(), + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + ) + } + } + + override fun onYieldPromoClicked(cryptoCurrency: CryptoCurrency) { + modelScope.launch(dispatchers.io) { + analyticsEventHandler.send( + MainScreenAnalyticsEvent.YieldPromoClicked( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + ), + ) + } + } + override fun onAccountExpandClick(account: Account) { val userWalletId = stateHolder.getSelectedWalletId() analyticsEventHandler.send(MainScreenAnalyticsEvent.AccountShowTokens()) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index c54ebcef16..033a18e216 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.AppsFlyerOnlyEvent import com.tangem.core.analytics.models.OneTimeAnalyticsEvent import com.tangem.domain.models.wallet.UserWalletId @@ -22,6 +23,11 @@ sealed class WalletScreenAnalyticsEvent { override val oneTimeEventId: String = id + userWalletId.stringValue } + class AppsFlyerWalletFunded(userWalletId: UserWalletId) : Basic(event = "wallet_funded"), + AppsFlyerOnlyEvent, OneTimeAnalyticsEvent { + override val oneTimeEventId: String = id + userWalletId.stringValue + } + class CardWasScanned(source: AnalyticsParam.ScreensSources) : Basic( event = "Card Was Scanned", params = mapOf( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index b8d4f81c17..c3de527e77 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -8,6 +8,7 @@ import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.common.extensions.isZero import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase import com.tangem.domain.analytics.model.WalletBalanceState @@ -34,6 +35,7 @@ internal class TokenListAnalyticsSender @Inject constructor( ) { private val balanceWasSentMap = mutableMapOf() + private val yieldPromoShownMap = mutableMapOf() private val mutex = Mutex() private val loadingTraces = mutableMapOf() @@ -206,6 +208,7 @@ internal class TokenListAnalyticsSender @Inject constructor( } analyticsEventHandler.send(Basic.WalletToppedUp(userWallet.walletId, walletType)) + analyticsEventHandler.send(Basic.AppsFlyerWalletFunded(userWallet.walletId)) } } @@ -233,6 +236,19 @@ internal class TokenListAnalyticsSender @Inject constructor( } } + fun sendYieldPromoShown(userWalletId: UserWalletId, token: String, blockchain: String) { + val key = "${userWalletId.stringValue}_${blockchain}_$token" + if (yieldPromoShownMap[key] == true) return + + analyticsEventHandler.send( + MainScreenAnalyticsEvent.YieldPromo( + token = token, + blockchain = blockchain, + ), + ) + yieldPromoShownMap[key] = true + } + companion object { const val BALANCE_LOADED_TRACE_NAME = "Total_balance_loaded" const val HAS_ERROR = "has_error" 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 73a2e96b23..e2dc6648fc 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 @@ -151,15 +151,18 @@ sealed class WalletNotification(val config: NotificationConfig) { ) data class TangemPayRefreshNeeded( - @DrawableRes val tangemIcon: Int?, - val onRefreshClick: () -> Unit, + @DrawableRes private val tangemIcon: Int?, + private val onRefreshClick: () -> Unit, + private val buttonText: TextReference, + private val shouldShowProgress: Boolean, ) : Warning( title = resourceReference(id = R.string.tangempay_payment_account_sync_needed), subtitle = resourceReference(id = R.string.tangempay_use_tangem_device_to_restore_payment_account), buttonsState = ButtonsState.PrimaryButtonConfig( - text = resourceReference(id = R.string.home_button_scan), + text = buttonText, iconResId = tangemIcon, onClick = onRefreshClick, + shouldShowProgress = shouldShowProgress, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayRefreshNeededStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayRefreshNeededStateTransformer.kt index b3f487ec56..98a3ee0caa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayRefreshNeededStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayRefreshNeededStateTransformer.kt @@ -1,5 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState @@ -8,6 +10,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState internal class TangemPayRefreshNeededStateTransformer( userWalletId: UserWalletId, + private val userWallet: UserWallet, private val onRefreshClick: () -> Unit, ) : WalletStateTransformer(userWalletId = userWalletId) { @@ -15,7 +18,12 @@ internal class TangemPayRefreshNeededStateTransformer( val tangemPayState = TangemPayState.RefreshNeeded( notification = TangemPayRefreshNeeded( tangemIcon = R.drawable.ic_tangem_24, + buttonText = when (userWallet) { + is UserWallet.Cold -> resourceReference(id = R.string.home_button_scan) + is UserWallet.Hot -> resourceReference(id = R.string.tangempay_sync_needed_restore_access) + }, onRefreshClick = onRefreshClick, + shouldShowProgress = false, ), ) return if (prevState is WalletState.MultiCurrency.Content) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayRefreshShowProgressTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayRefreshShowProgressTransformer.kt new file mode 100644 index 0000000000..cd5885b406 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayRefreshShowProgressTransformer.kt @@ -0,0 +1,24 @@ +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.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState + +internal class TangemPayRefreshShowProgressTransformer( + userWalletId: UserWalletId, +) : WalletStateTransformer(userWalletId) { + + override fun transform(prevState: WalletState): WalletState { + val multiContentState = prevState as? WalletState.MultiCurrency.Content ?: return prevState + val refreshNeededState = multiContentState.tangemPayState as? TangemPayState.RefreshNeeded ?: return prevState + val refreshNotification = + refreshNeededState.notification as? WalletNotification.Warning.TangemPayRefreshNeeded ?: return prevState + + return multiContentState.copy( + tangemPayState = refreshNeededState.copy( + notification = refreshNotification.copy(shouldShowProgress = true), + ), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt index a47a0aec3a..b6ad2482ff 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers +import com.tangem.common.ui.R import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.format.bigdecimal.fiat @@ -11,11 +12,10 @@ import com.tangem.domain.pay.model.CustomerInfo.ProductInstance import com.tangem.domain.pay.model.MainScreenCustomerInfo import com.tangem.domain.pay.model.OrderStatus import com.tangem.domain.visa.model.TangemPayCardFrozenState +import com.tangem.feature.wallet.child.wallet.model.intents.TangemPayIntents import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState +import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState -import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createCancelledState -import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueProgressState -import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createKycInProgressState import java.util.Currency /** @@ -26,12 +26,9 @@ private const val POLYGON_CHAIN_ID = 137 internal class TangemPayUpdateInfoStateTransformer( userWalletId: UserWalletId, - private val value: MainScreenCustomerInfo? = null, + private val value: MainScreenCustomerInfo, private val cardFrozenState: TangemPayCardFrozenState, - private val onClickKyc: () -> Unit = {}, - private val onIssuingCard: () -> Unit = {}, - private val onIssuingFailed: () -> Unit = {}, - private val openDetails: (config: TangemPayDetailsConfig) -> Unit = {}, + private val tangemPayClickIntents: TangemPayIntents, ) : WalletStateTransformer(userWalletId = userWalletId) { override fun transform(prevState: WalletState): WalletState { @@ -44,16 +41,15 @@ internal class TangemPayUpdateInfoStateTransformer( } private fun createInitialState(): TangemPayState { - val cardInfo = value?.info?.cardInfo - val productInstance = value?.info?.productInstance + val cardInfo = value.info.cardInfo + val productInstance = value.info.productInstance // when statement copied to WalletTangemPayAnalyticsEventSender. Be careful when editing. return when { - value == null -> TangemPayState.Empty - value.orderStatus == OrderStatus.CANCELED -> createCancelledState(onIssuingFailed) - !value.info.isKycApproved -> createKycInProgressState(onClickKyc) + value.orderStatus == OrderStatus.CANCELED -> createCancelledState() + !value.info.isKycApproved -> createKycInProgressState() cardInfo != null && productInstance != null -> getCardInfoState(cardInfo, productInstance) - else -> createIssueProgressState(onIssuingCard) + else -> createIssueProgressState() } } @@ -63,7 +59,8 @@ internal class TangemPayUpdateInfoStateTransformer( balanceText = TextReference.Str(getBalanceText(cardInfo)), balanceSymbol = stringReference("USDC"), // TODO hardcode for now onClick = { - openDetails( + tangemPayClickIntents.openDetails( + userWalletId, TangemPayDetailsConfig( cardId = productInstance.cardId, isPinSet = cardInfo.isPinSet, @@ -82,4 +79,28 @@ internal class TangemPayUpdateInfoStateTransformer( fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol) } } + + private fun createKycInProgressState(): TangemPayState = Progress( + title = TextReference.Res(R.string.tangempay_payment_account), + description = TextReference.Res(R.string.tangempay_kyc_in_progress), + buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button), + iconRes = R.drawable.ic_promo_kyc_36, + onButtonClick = { tangemPayClickIntents.onKycProgressClicked(userWalletId) }, + ) + + private fun createIssueProgressState(): TangemPayState = Progress( + title = TextReference.Res(R.string.tangempay_payment_account), + description = TextReference.Res(R.string.tangempay_issuing_your_card), + buttonText = TextReference.EMPTY, + iconRes = R.drawable.ic_tangem_pay_promo_card_36, + onButtonClick = tangemPayClickIntents::onIssuingCardClicked, + showProgress = true, + ) + + private fun createCancelledState(): TangemPayState = TangemPayState.FailedIssue( + title = TextReference.Res(R.string.tangempay_payment_account), + description = TextReference.Res(R.string.tangempay_failed_to_issue_card), + iconRes = R.drawable.ic_alert_24, + onButtonClick = tangemPayClickIntents::onIssuingFailedClicked, + ) } \ 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 2520491085..2d42286d42 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 @@ -78,6 +78,8 @@ internal class TokenListStateConverter( onItemLongClick = { _, status -> onTokenLongClick(accountId, status) }, onApyLabelClick = { status, apySource, apy -> onApyLabelClick(status, apySource, apy) }, onYieldPromoCloseClick = clickIntents::onYieldPromoCloseClick, + onYieldPromoShown = clickIntents::onYieldPromoShown, + onYieldPromoClicked = clickIntents::onYieldPromoClicked, ) override fun convert(value: WalletTokensListState): WalletTokensListState { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/util/TangemPayStateCreator.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/util/TangemPayStateCreator.kt deleted file mode 100644 index 51b584d38f..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/util/TangemPayStateCreator.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state.util - -import com.tangem.common.ui.R -import com.tangem.core.ui.extensions.TextReference -import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState -import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress - -internal object TangemPayStateCreator { - - fun createKycInProgressState(onClickKyc: () -> Unit): TangemPayState = Progress( - title = TextReference.Res(R.string.tangempay_payment_account), - description = TextReference.Res(R.string.tangempay_kyc_in_progress), - buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button), - iconRes = R.drawable.ic_promo_kyc_36, - onButtonClick = onClickKyc, - ) - - fun createIssueProgressState(onIssuingCardClick: () -> Unit): TangemPayState = Progress( - title = TextReference.Res(R.string.tangempay_payment_account), - description = TextReference.Res(R.string.tangempay_issuing_your_card), - buttonText = TextReference.EMPTY, - iconRes = R.drawable.ic_tangem_pay_promo_card_36, - onButtonClick = onIssuingCardClick, - showProgress = true, - ) - - fun createCancelledState(onIssueFailedClick: () -> Unit): TangemPayState = TangemPayState.FailedIssue( - title = TextReference.Res(R.string.tangempay_payment_account), - description = TextReference.Res(R.string.tangempay_failed_to_issue_card), - iconRes = R.drawable.ic_alert_24, - onButtonClick = onIssueFailedClick, - ) -} \ No newline at end of file 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 6a7cf0a950..e6f5955bb8 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 @@ -1,6 +1,5 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers -import com.tangem.common.routing.AppRoute import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.model.MainCustomerInfoContentState @@ -10,7 +9,6 @@ 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.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.* @@ -28,7 +26,6 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( @Assisted private val userWallet: UserWallet, private val stateController: WalletStateController, private val clickIntents: WalletClickIntents, - private val innerWalletRouter: InnerWalletRouter, private val cardDetailsRepository: TangemPayCardDetailsRepository, private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase, private val analytics: WalletTangemPayAnalyticsEventSender, @@ -49,7 +46,8 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( stateController.update( transformer = TangemPayRefreshNeededStateTransformer( userWalletId = userWalletId, - onRefreshClick = { clickIntents.onRefreshPayToken(userWalletId) }, + userWallet = userWallet, + onRefreshClick = { clickIntents.onRefreshPayToken(userWallet) }, ), ) } @@ -102,19 +100,7 @@ internal class TangemPayMainSubscriber @AssistedInject constructor( userWalletId = userWalletId, value = data, cardFrozenState = cardFrozenState, - onClickKyc = { - innerWalletRouter.openTangemPayOnboarding( - mode = AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding(userWalletId), - ) - }, - onIssuingCard = clickIntents::onIssuingCardClicked, - onIssuingFailed = clickIntents::onIssuingFailedClicked, - openDetails = { config -> - innerWalletRouter.openTangemPayDetails( - userWalletId = userWalletId, - config = config, - ) - }, + tangemPayClickIntents = clickIntents, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayRefreshBlock.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayRefreshBlock.kt index fc3d9c852b..b3c06f3a28 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayRefreshBlock.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/visa/TangemPayRefreshBlock.kt @@ -68,7 +68,9 @@ private fun TangemPayRefreshBlockPreview() { state = TangemPayState.RefreshNeeded( TangemPayRefreshNeeded( tangemIcon = R.drawable.ic_tangem_24, + buttonText = resourceReference(id = R.string.tangempay_sync_needed_restore_access), onRefreshClick = {}, + shouldShowProgress = true, ), ), modifier = Modifier, diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index e43e2690a6..76d81ec009 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -120,6 +120,10 @@ object BlockchainUtils { return blockchain == Blockchain.Ethereum || blockchain == Blockchain.EthereumTestnet } + fun isClore(blockchainId: String): Boolean { + return Blockchain.fromId(blockchainId) == Blockchain.Clore + } + data class BlockchainInfo( val blockchainId: String, val name: String, diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt index 07a2d02773..64016c2cf8 100644 --- a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt @@ -161,8 +161,13 @@ interface TangemSdkManager { visaDataForApprove: VisaDataForApprove, ): CompletionResult - suspend fun tangemPayProduceInitialCredentials(cardId: String): Either + suspend fun tangemPayProduceInitialCredentials( + preflightReadFilter: PreflightReadFilter, + ): Either - suspend fun getWithdrawalSignature(cardId: String, hash: String): Either + suspend fun getWithdrawalSignature( + hash: String, + preflightReadFilter: PreflightReadFilter, + ): Either // endregion } \ No newline at end of file