From 5899667c14e5310a7445d5394d54de6d1059c2ab Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 27 Oct 2022 18:36:42 +0500 Subject: [PATCH 001/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 20 ++++- ...GlobalAnalyticsHandler.kt => Analytics.kt} | 87 +++++++------------ .../analytics/AnalyticsHandlersFactory.kt | 23 +++++ .../common/analytics/AnalyticsInterfaces.kt | 3 - .../analytics/GlobalAnalyticsEventHandler.kt | 18 ++++ .../GlobalAnalyticsEventHandlerBuilder.kt | 79 ----------------- .../common/analytics/api/EventHandlerApi.kt | 41 +++++---- .../amplitude/AmplitudeAnalyticsHandler.kt | 17 +++- .../handlers/amplitude/AmplitudeLogClient.kt | 5 +- .../appsFlyer/AppsFlyerAnalyticsHandler.kt | 18 +++- .../handlers/appsFlyer/AppsFlyerLogClient.kt | 5 +- .../firebase/FirebaseAnalyticsHandler.kt | 31 +++++-- .../handlers/firebase/FirebaseLogClient.kt | 5 +- .../tap/common/redux/global/GlobalState.kt | 4 +- .../tap/common/shop/TangemShopService.kt | 2 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 2 +- .../tap/domain/twins/TwinCardsManager.kt | 6 +- .../walletconnect/WalletConnectSdkHelper.kt | 8 +- .../details/redux/DetailsMiddleware.kt | 4 +- .../redux/OnboardingSaltPayMiddleware.kt | 16 ++-- .../send/redux/middlewares/SendMiddleware.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 2 +- 22 files changed, 204 insertions(+), 194 deletions(-) rename app/src/main/java/com/tangem/tap/common/analytics/{GlobalAnalyticsHandler.kt => Analytics.kt} (60%) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandlersFactory.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index c0d6e566b4..1913ac6e16 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -14,7 +14,12 @@ import com.tangem.domain.common.LogConfig import com.tangem.network.common.MoshiConverter import com.tangem.tap.common.AndroidAssetReader import com.tangem.tap.common.AssetReader -import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandlerBuilder +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.AnalyticsHandlersFactory +import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder +import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler +import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler +import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.images.createCoilImageLoader @@ -102,14 +107,21 @@ class TapApplication : Application(), ImageLoaderFactory { } private fun initAnalytics(application: Application, config: Config) { - val globalAnalyticsHandler = GlobalAnalyticsEventHandlerBuilder( + val factory = AnalyticsHandlersFactory() + factory.addBuilder(AmplitudeAnalyticsHandler.Builder()) + factory.addBuilder(AppsFlyerAnalyticsHandler.Builder()) + factory.addBuilder(FirebaseAnalyticsHandler.Builder()) + + val buildData = AnalyticsHandlerBuilder.Data( application = application, config = config, isDebug = BuildConfig.DEBUG, logConfig = LogConfig.analyticsHandlers, jsonConverter = MoshiJsonConverter.INSTANCE, - ).default() - store.dispatch(GlobalAction.SetGlobalAnalyticsHandler(globalAnalyticsHandler)) + ) + factory.build(buildData).forEach { + Analytics.addHandler(it.id(), it) + } } private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt similarity index 60% rename from app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt rename to app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt index d1bef7d01b..9eaee82a3a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt @@ -7,68 +7,43 @@ import com.tangem.common.core.TangemSdkError import com.tangem.tap.common.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.BlockchainSdkErrorEventHandler import com.tangem.tap.common.analytics.api.CardSdkErrorEventHandler -import com.tangem.tap.common.analytics.api.ErrorEventHandler import com.tangem.tap.common.analytics.api.ErrorEventLogger -import com.tangem.tap.common.analytics.api.SdkErrorEventHandler import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.extensions.filterNotNull -interface GlobalAnalyticsEventHandler : AnalyticsEventHandler, - ErrorEventHandler, - SdkErrorEventHandler, - ShopifyOrderEventHandler { +/** +[REDACTED_AUTHOR] + */ +object Analytics : GlobalAnalyticsEventHandler { - fun attachToAllEvents(key: String, value: String) + private val handlers = mutableMapOf() - companion object { - fun stub(): GlobalAnalyticsEventHandler { - return object : GlobalAnalyticsEventHandler { - override fun attachToAllEvents(key: String, value: String) {} - override fun handleEvent(event: String, params: Map) {} - override fun handleErrorEvent(error: Throwable, params: Map) {} - override fun handleCardSdkErrorEvent( - error: TangemSdkError, - action: AnalyticsAnOld.ActionToLog, - params: Map, - card: Card?, - ) { - } - - override fun handleBlockchainSdkErrorEvent( - error: BlockchainError, - action: AnalyticsAnOld.ActionToLog, - params: Map, - card: Card?, - ) { - } - - override fun handleShopifyOrderEvent(order: Storefront.Order) {} - } - } - } -} - -class GlobalAnalyticsHandler( - private val analyticsHandlers: List, -) : GlobalAnalyticsEventHandler { + private val analyticsHandlers: List + get() = handlers.values.toList() private val attachToAllEventsParams: MutableMap = mutableMapOf() + override fun id(): String = analyticsHandlers.joinToString(", ") { it.id() } + + override fun addHandler(name: String, handler: AnalyticsEventHandler) { + handlers[name] = handler + } + + override fun removeHandler(name: String): AnalyticsEventHandler? { + return handlers.remove(name) + } + override fun attachToAllEvents(key: String, value: String) { attachToAllEventsParams[key] = value } - override fun handleEvent(event: String, params: Map) { - analyticsHandlers.forEach { it.handleEvent(event, params) } + override fun send(event: String, params: Map) { + analyticsHandlers.forEach { it.send(event, params) } } - override fun handleAnalyticsEvent( - event: AnalyticsEvent, - card: Card?, - blockchain: String?, - ) { - analyticsHandlers.forEach { it.handleAnalyticsEvent(event, card, blockchain) } + override fun send(event: AnalyticsEvent, card: Card?, blockchain: String?) { + analyticsHandlers.forEach { it.send(event, card, blockchain) } } override fun handleAnalyticsEvent( @@ -77,40 +52,42 @@ class GlobalAnalyticsHandler( card: Card?, blockchain: String?, ) { - analyticsHandlers.forEach { it.handleAnalyticsEvent(event, params, card, blockchain) } + analyticsHandlers.forEach { + it.handleAnalyticsEvent(event, params, card, blockchain) + } } - override fun handleErrorEvent(error: Throwable, params: Map) { + override fun send(error: Throwable, params: Map) { analyticsHandlers.filterIsInstance().forEach { it.logErrorEvent(error, params) } } - override fun handleCardSdkErrorEvent( + override fun send( error: TangemSdkError, action: AnalyticsAnOld.ActionToLog, params: Map, card: Card?, ) { analyticsHandlers.filterIsInstance().forEach { - it.handleCardSdkErrorEvent(error, action, params, card) + it.send(error, action, params, card) } } - override fun handleBlockchainSdkErrorEvent( + override fun send( error: BlockchainError, action: AnalyticsAnOld.ActionToLog, params: Map, card: Card?, ) { analyticsHandlers.filterIsInstance().forEach { - it.handleBlockchainSdkErrorEvent(error, action, params, card) + it.send(error, action, params, card) } } - override fun handleShopifyOrderEvent(order: Storefront.Order) { + override fun send(order: Storefront.Order) { analyticsHandlers.filterIsInstance().forEach { - it.handleShopifyOrderEvent(order) + it.send(order) } } @@ -136,7 +113,7 @@ fun GlobalAnalyticsEventHandler.logWcEvent(event: AnalyticsAnOld.WcAnalyticsEven AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action?.name, AnalyticsParamAnOld.ERROR_DESCRIPTION.param to event.error.message, ).filterNotNull() - handleErrorEvent(event.error, params) + send(event.error, params) } is AnalyticsAnOld.WcAnalyticsEvent.InvalidRequest -> handleAnalyticsEvent( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandlersFactory.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandlersFactory.kt new file mode 100644 index 0000000000..a287f6fac2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandlersFactory.kt @@ -0,0 +1,23 @@ +package com.tangem.tap.common.analytics + +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder + +/** +[REDACTED_AUTHOR] + */ +class AnalyticsHandlersFactory { + + private val builders = mutableListOf() + + fun addBuilder(builder: AnalyticsHandlerBuilder) { + builders.add(builder) + } + + fun build(data: AnalyticsHandlerBuilder.Data): List { + val handlers = builders.mapNotNull { it.build(data) } + builders.clear() + + return handlers + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt deleted file mode 100644 index 6fd6fc7dc8..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsInterfaces.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.tangem.tap.common.analytics - - diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt new file mode 100644 index 0000000000..139ffa5582 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.common.analytics + +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.ErrorEventHandler +import com.tangem.tap.common.analytics.api.SdkErrorEventHandler +import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler + +interface GlobalAnalyticsEventHandler : AnalyticsEventHandler, + ErrorEventHandler, + SdkErrorEventHandler, + ShopifyOrderEventHandler { + + fun addHandler(name: String, handler: AnalyticsEventHandler) + + fun removeHandler(name: String): AnalyticsEventHandler? + + fun attachToAllEvents(key: String, value: String) +} diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt deleted file mode 100644 index 8e2b439e08..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandlerBuilder.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.tangem.tap.common.analytics - -import android.app.Application -import com.tangem.common.json.MoshiJsonConverter -import com.tangem.domain.common.AnalyticsHandlersLogConfig -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler -import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler -import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeClient -import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeLogClient -import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler -import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerClient -import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerLogClient -import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler -import com.tangem.tap.common.analytics.handlers.firebase.FirebaseClient -import com.tangem.tap.common.analytics.handlers.firebase.FirebaseLogClient -import com.tangem.tap.domain.configurable.config.Config - -/** -[REDACTED_AUTHOR] - */ -class GlobalAnalyticsEventHandlerBuilder( - val application: Application, - val config: Config, - val isDebug: Boolean, - val logConfig: AnalyticsHandlersLogConfig, - val jsonConverter: MoshiJsonConverter, -) { - - private val analyticHandlers = mutableListOf() - - fun build(): GlobalAnalyticsEventHandler { - return GlobalAnalyticsHandler(analyticHandlers.toList()) - } - - fun default(): GlobalAnalyticsEventHandler { - addFirebaseAnalytics() - addAppsFlyer() - addAmplitude() - - return GlobalAnalyticsHandler(analyticHandlers.toList()) - } - - fun addFirebaseAnalytics(): GlobalAnalyticsEventHandlerBuilder { - if (isDebug) { - if (logConfig.firebase) FirebaseLogClient(createLogger("FirebaseAnalyticsHandler")) else null - } else { - FirebaseClient() - }?.let { - analyticHandlers.add(FirebaseAnalyticsHandler(it)) - } - return this - } - - fun addAppsFlyer(): GlobalAnalyticsEventHandlerBuilder { - if (isDebug) { - if (logConfig.appsFlyer) AppsFlyerLogClient(createLogger("AppsFlyerAnalyticsHandler")) else null - } else { - AppsFlyerClient(application, config.appsFlyerDevKey) - }?.let { - analyticHandlers.add(AppsFlyerAnalyticsHandler(it)) - } - return this - } - - fun addAmplitude(): GlobalAnalyticsEventHandlerBuilder { - if (isDebug) { - if (logConfig.firebase) AmplitudeLogClient(createLogger("AmplitudeAnalyticsHandler")) else null - } else { - AmplitudeClient(application, config.amplitudeApiKey) - }?.let { - analyticHandlers.add(AmplitudeAnalyticsHandler(it)) - } - return this - } - - private fun createLogger(name: String): AnalyticsEventsLogger { - return AnalyticsEventsLogger(name, jsonConverter) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt index 08e1609e3c..ea77e75dfd 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt @@ -1,31 +1,30 @@ package com.tangem.tap.common.analytics.api +import android.app.Application import com.shopify.buy3.Storefront import com.tangem.blockchain.common.BlockchainError import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.domain.common.AnalyticsHandlersLogConfig import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.extensions.filterNotNull +import com.tangem.tap.domain.configurable.config.Config /** [REDACTED_AUTHOR] */ interface AnalyticsEventHandler { - fun handleEvent( - event: String, - params: Map = emptyMap(), - ) + fun id(): String - fun handleAnalyticsEvent( - event: AnalyticsEvent, - card: Card? = null, - blockchain: String? = null, - ) { - handleEvent( + fun send(event: String, params: Map = emptyMap()) + + fun send(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null) { + send( event = prepareEventString(event.category, event.event), params = prepareParams(card, blockchain, event.params), ) @@ -38,7 +37,7 @@ interface AnalyticsEventHandler { card: Card? = null, blockchain: String? = null, ) { - handleEvent(event.event, prepareParams(card, blockchain, params)) + send(event.event, prepareParams(card, blockchain, params)) } fun prepareParams( @@ -57,7 +56,7 @@ interface AnalyticsEventHandler { } interface ErrorEventHandler { - fun handleErrorEvent( + fun send( error: Throwable, params: Map = emptyMap(), ) @@ -66,7 +65,7 @@ interface ErrorEventHandler { interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler interface CardSdkErrorEventHandler { - fun handleCardSdkErrorEvent( + fun send( error: TangemSdkError, action: AnalyticsAnOld.ActionToLog, params: Map = emptyMap(), @@ -75,7 +74,7 @@ interface CardSdkErrorEventHandler { } interface BlockchainSdkErrorEventHandler { - fun handleBlockchainSdkErrorEvent( + fun send( error: BlockchainError, action: AnalyticsAnOld.ActionToLog, params: Map = mapOf(), @@ -84,5 +83,17 @@ interface BlockchainSdkErrorEventHandler { } interface ShopifyOrderEventHandler { - fun handleShopifyOrderEvent(order: Storefront.Order) + fun send(order: Storefront.Order) } + +interface AnalyticsHandlerBuilder { + fun build(data: Data): AnalyticsEventHandler? + + data class Data( + val application: Application, + val config: Config, + val isDebug: Boolean, + val logConfig: AnalyticsHandlersLogConfig, + val jsonConverter: MoshiJsonConverter, + ) +} \ No newline at end of file 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 27132a1f61..5a03cc2f36 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 @@ -1,12 +1,27 @@ package com.tangem.tap.common.analytics.handlers.amplitude import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder class AmplitudeAnalyticsHandler( private val client: AmplitudeAnalyticsClient, ) : AnalyticsEventHandler { - override fun handleEvent(event: String, params: Map) { + override fun id(): String = ID + + override fun send(event: String, params: Map) { client.logEvent(event, params) } + + companion object { + const val ID = "Amplitude" + } + + class Builder : AnalyticsHandlerBuilder { + override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when { + !data.isDebug -> AmplitudeClient(data.application, data.config.amplitudeApiKey) + data.isDebug && data.logConfig.appsFlyer -> AmplitudeLogClient(data.jsonConverter) + else -> null + }?.let { AmplitudeAnalyticsHandler(it) } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt index b800259b5f..f60d2edfcf 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/amplitude/AmplitudeLogClient.kt @@ -1,14 +1,17 @@ package com.tangem.tap.common.analytics.handlers.amplitude +import com.tangem.common.json.MoshiJsonConverter import com.tangem.tap.common.analytics.AnalyticsEventsLogger /** [REDACTED_AUTHOR] */ internal class AmplitudeLogClient( - private val logger: AnalyticsEventsLogger, + jsonConverter: MoshiJsonConverter, ) : AmplitudeAnalyticsClient { + private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(AmplitudeAnalyticsHandler.ID, jsonConverter) + override fun logEvent(event: String, params: Map) { logger.logEvent(event, params) } 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 0acfb1dc2f..008b6189b9 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 @@ -5,22 +5,34 @@ import com.appsflyer.AFInAppEventType import com.shopify.buy3.Storefront import com.tangem.common.Converter import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler class AppsFlyerAnalyticsHandler( private val client: AppsFlyerAnalyticsClient, ) : AnalyticsEventHandler, ShopifyOrderEventHandler { - override fun handleEvent(event: String, params: Map) { + override fun id(): String = ID + + override fun send(event: String, params: Map) { client.logEvent(event, params) } - override fun handleShopifyOrderEvent(order: Storefront.Order) { - handleEvent(ORDER_EVENT, OrderToParamsConverter().convert(order)) + override fun send(order: Storefront.Order) { + send(ORDER_EVENT, OrderToParamsConverter().convert(order)) } companion object { const val ORDER_EVENT = AFInAppEventType.PURCHASE + const val ID = "AppsFlyer" + } + + class Builder : AnalyticsHandlerBuilder { + override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when { + !data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerDevKey) + data.isDebug && data.logConfig.appsFlyer -> AppsFlyerLogClient(data.jsonConverter) + else -> null + }?.let { AppsFlyerAnalyticsHandler(it) } } } 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 0d3737a161..8e87061b2c 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 @@ -1,14 +1,17 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer +import com.tangem.common.json.MoshiJsonConverter import com.tangem.tap.common.analytics.AnalyticsEventsLogger /** [REDACTED_AUTHOR] */ internal class AppsFlyerLogClient( - private val logger: AnalyticsEventsLogger, + jsonConverter: MoshiJsonConverter, ) : AppsFlyerAnalyticsClient { + private val logger = AnalyticsEventsLogger(AppsFlyerAnalyticsHandler.ID, jsonConverter) + override fun logEvent(event: String, params: Map) { logger.logEvent(event, params) } 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 3ab6326670..65ef31ac8e 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 @@ -9,6 +9,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.api.ErrorEventHandler import com.tangem.tap.common.analytics.api.SdkErrorEventHandler import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler @@ -19,15 +20,17 @@ class FirebaseAnalyticsHandler( private val client: FirebaseAnalyticsClient, ) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler, ShopifyOrderEventHandler { - override fun handleEvent(event: String, params: Map) { + override fun id(): String = ID + + override fun send(event: String, params: Map) { client.logEvent(event, params) } - override fun handleErrorEvent(error: Throwable, params: Map) { + override fun send(error: Throwable, params: Map) { client.logErrorEvent(error, params) } - override fun handleCardSdkErrorEvent( + override fun send( error: TangemSdkError, action: AnalyticsAnOld.ActionToLog, params: Map, @@ -36,11 +39,11 @@ class FirebaseAnalyticsHandler( val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card)) val converter = CardSdkErrorConverter() converter.convert(model)?.let { - handleErrorEvent(it.throwable, it.params) + send(it.throwable, it.params) } } - override fun handleBlockchainSdkErrorEvent( + override fun send( error: BlockchainError, action: AnalyticsAnOld.ActionToLog, params: Map, @@ -50,12 +53,24 @@ class FirebaseAnalyticsHandler( val model = BlockchainSdkErrorConverter.Model(error, action, params, prepareParams(card)) val converter = BlockchainSdkErrorConverter(CardSdkErrorConverter()) converter.convert(model)?.let { - handleErrorEvent(it.throwable, it.params) + send(it.throwable, it.params) } } - override fun handleShopifyOrderEvent(order: Storefront.Order) { - handleEvent(FirebaseClient.ORDER_EVENT, OrderToParamsConverter().convert(order)) + override fun send(order: Storefront.Order) { + send(FirebaseClient.ORDER_EVENT, OrderToParamsConverter().convert(order)) + } + + companion object { + const val ID = "Firebase" + } + + class Builder : AnalyticsHandlerBuilder { + override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when { + !data.isDebug -> FirebaseClient() + data.isDebug && data.logConfig.firebase -> FirebaseLogClient(data.jsonConverter) + else -> null + }?.let { FirebaseAnalyticsHandler(it) } } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt index ccacc09f9e..e460f9dbf9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseLogClient.kt @@ -1,14 +1,17 @@ package com.tangem.tap.common.analytics.handlers.firebase +import com.tangem.common.json.MoshiJsonConverter import com.tangem.tap.common.analytics.AnalyticsEventsLogger /** [REDACTED_AUTHOR] */ internal class FirebaseLogClient( - private val logger: AnalyticsEventsLogger, + jsonConverter: MoshiJsonConverter, ) : FirebaseAnalyticsClient { + private val logger: AnalyticsEventsLogger = AnalyticsEventsLogger(FirebaseAnalyticsHandler.ID, jsonConverter) + override fun logEvent(event: String, params: Map) { logger.logEvent(event, params) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index a02172db23..b31226d6da 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.redux.global import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.feedback.FeedbackManager @@ -27,11 +28,10 @@ data class GlobalState( val dialog: StateDialog? = null, val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(), val resources: AndroidResources = AndroidResources(), - val analyticsHandler: GlobalAnalyticsEventHandler = GlobalAnalyticsEventHandler.stub(), + val analyticsHandler: GlobalAnalyticsEventHandler = Analytics, val userCountryCode: String? = null, ) : StateType - data class AndroidResources( val strings: RString = RString(), ) { diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index c8bd0a5405..d26f38e508 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -199,7 +199,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { suspend fun waitForCheckout(productType: ProductType, analyticsHandler: GlobalAnalyticsEventHandler?) { val result = shopifyService.checkout(true, checkouts[productType]!!.id) result.onSuccess { - ifNotNull(analyticsHandler, it.order) { handler, order -> handler.handleShopifyOrderEvent(order) } + ifNotNull(analyticsHandler, it.order) { handler, order -> handler.send(order) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 296b47185b..94bf9fcaec 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -91,7 +91,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } is CompletionResult.Failure -> (result.error as? TangemSdkError)?.let { error -> - analyticsHandler?.handleCardSdkErrorEvent( + analyticsHandler?.send( error = error, action = AnalyticsAnOld.ActionToLog.Scan, ) diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index feb58ca6f0..7a39dc917d 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -37,7 +37,7 @@ class TwinCardsManager( is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString() is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> - analyticsHandler?.handleCardSdkErrorEvent( + analyticsHandler?.send( error, AnalyticsAnOld.ActionToLog.CreateWallet, card = card @@ -67,7 +67,7 @@ class TwinCardsManager( } is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> - analyticsHandler?.handleCardSdkErrorEvent( + analyticsHandler?.send( error, AnalyticsAnOld.ActionToLog.CreateWallet, card = card @@ -87,7 +87,7 @@ class TwinCardsManager( is CompletionResult.Success -> Result.Success(response.data) is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> - analyticsHandler?.handleCardSdkErrorEvent( + analyticsHandler?.send( error, AnalyticsAnOld.ActionToLog.WriteIssuerData, card = card diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index f90eb7cbb9..c09bfcbfe9 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -143,7 +143,7 @@ class WalletConnectSdkHelper { HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash } is SimpleResult.Failure -> { - store.state.globalState.analyticsHandler?.handleBlockchainSdkErrorEvent( + store.state.globalState.analyticsHandler?.send( result.error, AnalyticsAnOld.ActionToLog.WalletConnectTransaction, ) @@ -173,7 +173,7 @@ class WalletConnectSdkHelper { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent( + store.state.globalState.analyticsHandler?.send( error, AnalyticsAnOld.ActionToLog.WalletConnectSign, ) @@ -202,7 +202,7 @@ class WalletConnectSdkHelper { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent( + store.state.globalState.analyticsHandler?.send( error, AnalyticsAnOld.ActionToLog.WalletConnectTransaction, ) @@ -278,7 +278,7 @@ class WalletConnectSdkHelper { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent( + store.state.globalState.analyticsHandler?.send( error, AnalyticsAnOld.ActionToLog.WalletConnectSign, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 309c2900d3..719e666a32 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -110,7 +110,7 @@ class DetailsMiddleware { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent( + store.state.globalState.analyticsHandler?.send( error, AnalyticsAnOld.ActionToLog.PurgeWallet, card = store.state.detailsState.scanResponse?.card, @@ -154,7 +154,7 @@ class DetailsMiddleware { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent( + store.state.globalState.analyticsHandler?.send( error = error, action = AnalyticsAnOld.ActionToLog.ChangeSecOptions, params = mapOf( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index b969f87719..fdd09a7dd8 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -77,7 +77,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } is OnboardingSaltPayAction.RegisterCard -> { - analyticsHandler.handleAnalyticsEvent(Onboarding.ButtonConnect()) + analyticsHandler.send(Onboarding.ButtonConnect()) handleInProgress = true val state = getState() @@ -151,7 +151,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App is OnboardingSaltPayAction.TrySetPin -> { try { assertPinValid(action.pin, getState().pinLength) - analyticsHandler.handleAnalyticsEvent(Onboarding.PinCodeSet()) + analyticsHandler.send(Onboarding.PinCodeSet()) store.dispatch(OnboardingSaltPayAction.SetPin(action.pin)) store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.CardRegistration)) } catch (error: SaltPayActivationError) { @@ -159,7 +159,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } is OnboardingSaltPayAction.Claim -> { - analyticsHandler.handleAnalyticsEvent(Onboarding.ButtonClaim()) + analyticsHandler.send(Onboarding.ButtonClaim()) val state = getState() handleInProgress = true @@ -192,7 +192,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } handleInProgress = false - analyticsHandler.handleAnalyticsEvent(Onboarding.ClaimWasSuccessfully()) + analyticsHandler.send(Onboarding.ClaimWasSuccessfully()) dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimInProgress)) dispatchOnMain(OnboardingSaltPayAction.RefreshClaim) } @@ -242,10 +242,10 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App private fun handleAnalytics(analyticsHandler: GlobalAnalyticsEventHandler, step: SaltPayActivationStep) { when (step) { - SaltPayActivationStep.KycStart -> analyticsHandler.handleAnalyticsEvent(Onboarding.KYCStarted()) - SaltPayActivationStep.KycWaiting -> analyticsHandler.handleAnalyticsEvent(Onboarding.KYCInProgress()) - SaltPayActivationStep.KycReject -> analyticsHandler.handleAnalyticsEvent(Onboarding.KYCRejected()) - SaltPayActivationStep.Claim -> analyticsHandler.handleAnalyticsEvent(Onboarding.ClaimScreenOpened()) + SaltPayActivationStep.KycStart -> analyticsHandler.send(Onboarding.KYCStarted()) + SaltPayActivationStep.KycWaiting -> analyticsHandler.send(Onboarding.KYCInProgress()) + SaltPayActivationStep.KycReject -> analyticsHandler.send(Onboarding.KYCRejected()) + SaltPayActivationStep.Claim -> analyticsHandler.send(Onboarding.ClaimScreenOpened()) else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 22cd5ad13a..49bbefe866 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -250,7 +250,7 @@ private fun sendTransaction( feeAmount = feeAmount, destinationAddress = destinationAddress, ) - store.state.globalState.analyticsHandler?.handleBlockchainSdkErrorEvent( + store.state.globalState.analyticsHandler?.send( error = sendResult.error, action = AnalyticsAnOld.ActionToLog.SendTransaction, params = mapOf(AnalyticsParamAnOld.BLOCKCHAIN to walletManager.wallet.blockchain.currency), diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index ca2323f38e..5e34717f5d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -186,7 +186,7 @@ class WalletMiddleware { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.handleCardSdkErrorEvent( + store.state.globalState.analyticsHandler?.send( error, AnalyticsAnOld.ActionToLog.CreateWallet, card = store.state.detailsState.scanResponse?.card From f5bad316181c7cd0bc851a6a8071f7e0244fe0ee Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Oct 2022 12:02:07 +0500 Subject: [PATCH 002/176] Updated on 2026-08-14 --- .../tap/common/redux/global/GlobalAction.kt | 2 -- .../common/redux/global/GlobalMidlleware.kt | 1 - .../tap/common/redux/global/GlobalReducer.kt | 1 - .../tap/common/redux/global/GlobalState.kt | 3 --- .../tap/common/shop/TangemShopService.kt | 7 +++--- .../com/tangem/tap/domain/TangemSdkManager.kt | 14 +++++------ .../tap/domain/twins/TwinCardsManager.kt | 9 ++++---- .../walletconnect/WalletConnectManager.kt | 13 ++++++----- .../walletconnect/WalletConnectSdkHelper.kt | 21 ++++------------- .../details/redux/DetailsMiddleware.kt | 5 ++-- .../tap/features/home/redux/HomeMiddleware.kt | 5 ++-- .../twins/redux/TwinCardsMiddleware.kt | 1 - .../redux/OnboardingSaltPayMiddleware.kt | 23 +++++++++---------- .../send/redux/middlewares/SendMiddleware.kt | 5 ++-- .../tap/features/shop/redux/ShopMiddleware.kt | 7 +----- .../redux/middlewares/WalletMiddleware.kt | 3 ++- .../redux/middlewares/WarningsMiddleware.kt | 3 ++- .../ui/adapters/WarningMessagesAdapter.kt | 8 +++---- 18 files changed, 54 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index b226be3401..c1e6d85f11 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -5,7 +5,6 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError import com.tangem.domain.common.ScanResponse -import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.feedback.FeedbackData import com.tangem.tap.common.feedback.FeedbackManager @@ -89,7 +88,6 @@ sealed class GlobalAction : Action { data class SetConfigManager(val configManager: ConfigManager) : GlobalAction() data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction() data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction() - data class SetGlobalAnalyticsHandler(val analyticsHandler: GlobalAnalyticsEventHandler) : GlobalAction() data class SendEmail(val feedbackData: FeedbackData) : GlobalAction() data class OpenChat(val feedbackData: FeedbackData, val zendeskConfig: ZendeskConfig? = null) : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index a805bbd798..bd346b13a6 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -163,7 +163,6 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di is GlobalAction.ScanCard -> { scope.launch { val result = tangemSdkManager.scanProduct( - store.state.globalState.analyticsHandler, userTokensRepository, action.additionalBlockchainsToDerive, action.messageResId, diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index b0cefc6d9e..820506ab18 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -48,7 +48,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState.copy(configManager = action.configManager) } is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager) - is GlobalAction.SetGlobalAnalyticsHandler -> globalState.copy(analyticsHandler = action.analyticsHandler) is GlobalAction.UpdateWalletSignedHashes -> { val card = globalState.scanResponse?.card ?: return globalState val wallet = card.wallet(action.walletPublicKey) ?: return globalState diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index b31226d6da..11c84ed856 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -1,8 +1,6 @@ package com.tangem.tap.common.redux.global import com.tangem.domain.common.ScanResponse -import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.redux.StateDialog @@ -28,7 +26,6 @@ data class GlobalState( val dialog: StateDialog? = null, val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(), val resources: AndroidResources = AndroidResources(), - val analyticsHandler: GlobalAnalyticsEventHandler = Analytics, val userCountryCode: String? = null, ) : StateType diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index d26f38e508..87dacd7bc1 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -4,8 +4,7 @@ import android.app.Application import android.content.Intent import com.google.android.gms.wallet.PaymentData import com.shopify.buy3.Storefront -import com.tangem.common.extensions.ifNotNull -import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.common.shop.data.ProductType import com.tangem.tap.common.shop.data.TangemProduct @@ -196,10 +195,10 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { return checkouts[productType]!!.webUrl } - suspend fun waitForCheckout(productType: ProductType, analyticsHandler: GlobalAnalyticsEventHandler?) { + suspend fun waitForCheckout(productType: ProductType) { val result = shopifyService.checkout(true, checkouts[productType]!!.id) result.onSuccess { - ifNotNull(analyticsHandler, it.order) { handler, order -> handler.send(order) } + it.order?.let { Analytics.send(it) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 94bf9fcaec..55ec76e903 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -26,10 +26,10 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld -import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask @@ -45,18 +45,17 @@ import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { suspend fun scanProduct( - analyticsHandler: GlobalAnalyticsEventHandler?, userTokensRepository: UserTokensRepository, additionalBlockchainsToDerive: Collection? = null, messageRes: Int? = null, ): CompletionResult { - analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.READY_TO_SCAN, card = null) + Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.READY_TO_SCAN, card = null) val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) return runTaskAsyncReturnOnMain( runnable = ScanProductTask(null, userTokensRepository, additionalBlockchainsToDerive), cardId = null, initialMessage = message, - ).also { sendScanResultsToAnalytics(analyticsHandler, it) } + ).also { sendScanResultsToAnalytics(it) } } suspend fun createProductWallet( @@ -70,18 +69,17 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } private fun sendScanResultsToAnalytics( - analyticsHandler: GlobalAnalyticsEventHandler?, result: CompletionResult, ) { when (result) { is CompletionResult.Success -> { - analyticsHandler?.handleAnalyticsEvent( + Analytics.handleAnalyticsEvent( event = AnalyticsEventAnOld.CARD_IS_SCANNED, card = result.data.card, blockchain = result.data.walletData?.blockchain, ) if (DemoHelper.isDemoCard(result.data)) { - analyticsHandler?.handleAnalyticsEvent( + Analytics.handleAnalyticsEvent( event = AnalyticsEventAnOld.DEMO_MODE_ACTIVATED, params = mapOf(AnalyticsParamAnOld.CARD_ID.param to result.data.card.cardId), card = result.data.card, @@ -91,7 +89,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } is CompletionResult.Failure -> (result.error as? TangemSdkError)?.let { error -> - analyticsHandler?.send( + Analytics.send( error = error, action = AnalyticsAnOld.ActionToLog.Scan, ) diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 7a39dc917d..fc80f3c5b0 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -14,14 +14,13 @@ import com.tangem.domain.common.ScanResponse import com.tangem.network.common.MoshiConverter import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.common.AssetReader +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld -import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler import com.tangem.tap.tangemSdkManager class TwinCardsManager( private val card: Card, assetReader: AssetReader, - val analyticsHandler: GlobalAnalyticsEventHandler? ) { private val currentCardId: String = card.cardId @@ -37,7 +36,7 @@ class TwinCardsManager( is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString() is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> - analyticsHandler?.send( + Analytics.send( error, AnalyticsAnOld.ActionToLog.CreateWallet, card = card @@ -67,7 +66,7 @@ class TwinCardsManager( } is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> - analyticsHandler?.send( + Analytics.send( error, AnalyticsAnOld.ActionToLog.CreateWallet, card = card @@ -87,7 +86,7 @@ class TwinCardsManager( is CompletionResult.Success -> Result.Success(response.data) is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> - analyticsHandler?.send( + Analytics.send( error, AnalyticsAnOld.ActionToLog.WriteIssuerData, card = card diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 6bd6cddada..f9ed7f61a5 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.logWcEvent import com.tangem.tap.common.extensions.dispatchOnMain @@ -223,7 +224,7 @@ class WalletConnectManager { } private fun onSessionClosed(session: WCSession) { - store.state.globalState.analyticsHandler?.logWcEvent( + Analytics.logWcEvent( AnalyticsAnOld.WcAnalyticsEvent.Session( AnalyticsAnOld.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url ) @@ -372,7 +373,7 @@ class WalletConnectManager { ), ) } - store.state.globalState.analyticsHandler?.logWcEvent( + Analytics.logWcEvent( AnalyticsAnOld.WcAnalyticsEvent.Session( AnalyticsAnOld.WcSessionEvent.Connect, peer.url, ), @@ -387,7 +388,7 @@ class WalletConnectManager { } client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction -> Timber.d("onEthSendTransaction: $transaction") - store.state.globalState.analyticsHandler?.logWcEvent( + Analytics.logWcEvent( AnalyticsAnOld.WcAnalyticsEvent.Action( AnalyticsAnOld.WcAction.SendTransaction ) @@ -405,7 +406,7 @@ class WalletConnectManager { } client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction -> Timber.d("onEthSignTransaction: $transaction") - store.state.globalState.analyticsHandler?.logWcEvent( + Analytics.logWcEvent( AnalyticsAnOld.WcAnalyticsEvent.Action( AnalyticsAnOld.WcAction.SignTransaction ) @@ -423,7 +424,7 @@ class WalletConnectManager { } client.onEthSign = { id: Long, message: WCEthereumSignMessage -> Timber.d("onEthSign: $message") - store.state.globalState.analyticsHandler?.logWcEvent( + Analytics.logWcEvent( AnalyticsAnOld.WcAnalyticsEvent.Action( AnalyticsAnOld.WcAction.PersonalSign ) @@ -499,7 +500,7 @@ class WalletConnectManager { val message = EthSignHelper.tryToParseEthTypedMessage(request) if (message != null) { Timber.d("onEthSign_v4: $message") - store.state.globalState.analyticsHandler?.logWcEvent( + Analytics.logWcEvent( AnalyticsAnOld.WcAnalyticsEvent.Action( AnalyticsAnOld.WcAction.PersonalSign ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index c09bfcbfe9..36e8fc2a1d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -24,6 +24,7 @@ import com.tangem.common.extensions.toDecompressedPublicKey import com.tangem.common.extensions.toHexString import com.tangem.crypto.CryptoUtils import com.tangem.operations.sign.SignHashCommand +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.toFormattedString @@ -143,10 +144,7 @@ class WalletConnectSdkHelper { HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash } is SimpleResult.Failure -> { - store.state.globalState.analyticsHandler?.send( - result.error, - AnalyticsAnOld.ActionToLog.WalletConnectTransaction, - ) + Analytics.send(result.error, AnalyticsAnOld.ActionToLog.WalletConnectTransaction) Timber.e(result.error as BlockchainSdkError) null } @@ -173,10 +171,7 @@ class WalletConnectSdkHelper { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.send( - error, - AnalyticsAnOld.ActionToLog.WalletConnectSign, - ) + Analytics.send(error, AnalyticsAnOld.ActionToLog.WalletConnectSign) } Timber.e(result.error.customMessage) null @@ -202,10 +197,7 @@ class WalletConnectSdkHelper { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.send( - error, - AnalyticsAnOld.ActionToLog.WalletConnectTransaction, - ) + Analytics.send(error, AnalyticsAnOld.ActionToLog.WalletConnectTransaction) } Timber.e(result.error.customMessage) null @@ -278,10 +270,7 @@ class WalletConnectSdkHelper { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.send( - error, - AnalyticsAnOld.ActionToLog.WalletConnectSign, - ) + Analytics.send(error, AnalyticsAnOld.ActionToLog.WalletConnectSign) } Timber.e(result.error.customMessage) null diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 719e666a32..6df912853f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.details.redux import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.domain.common.TapWorkarounds.isTangemTwins +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.extensions.dispatchNotification @@ -110,7 +111,7 @@ class DetailsMiddleware { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.send( + Analytics.send( error, AnalyticsAnOld.ActionToLog.PurgeWallet, card = store.state.detailsState.scanResponse?.card, @@ -154,7 +155,7 @@ class DetailsMiddleware { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.send( + Analytics.send( error = error, action = AnalyticsAnOld.ActionToLog.ChangeSecOptions, params = mapOf( diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index e7be6e2145..8df39141c7 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -6,6 +6,7 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withIOContext import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.analytics.GetCardSourceParamsAnOld @@ -102,7 +103,7 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL) else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop)) } - store.state.globalState.analyticsHandler?.handleAnalyticsEvent( + Analytics.handleAnalyticsEvent( event = AnalyticsEventAnOld.GET_CARD, params = mapOf(AnalyticsParamAnOld.SOURCE.param to GetCardSourceParamsAnOld.WELCOME.param), ) @@ -149,7 +150,7 @@ private fun onScanSuccess(scanResponse: ScanResponse) { val tapWalletManager = globalState.tapWalletManager tapWalletManager.updateConfigManager(scanResponse) - globalState.analyticsHandler.attachToAllEvents(AnalyticsParam.BatchId, scanResponse.card.batchId) + Analytics.attachToAllEvents(AnalyticsParam.BatchId, scanResponse.card.batchId) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 47dd842d1d..35f7e1d7bd 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -139,7 +139,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) { val manager = TwinCardsManager( card = getScanResponse().card, assetReader = action.reader, - analyticsHandler = store.state.globalState.analyticsHandler ) store.dispatch(TwinCardsAction.CardsManager.Set(manager)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index fdd09a7dd8..d5442968ca 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -10,7 +10,7 @@ import com.tangem.domain.common.extensions.successOr import com.tangem.domain.common.extensions.withMainContext import com.tangem.network.api.paymentology.KYCStatus import com.tangem.network.api.paymentology.RegistrationResponse -import com.tangem.tap.common.analytics.GlobalAnalyticsEventHandler +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.isPositive @@ -59,7 +59,6 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App fun getAppState(): AppState = appState()!! fun getOnboardingWalletState(): OnboardingWalletState = getAppState().onboardingWalletState fun getState(): OnboardingSaltPayState = getOnboardingWalletState().onboardingSaltPayState!! - val analyticsHandler = getAppState().globalState.analyticsHandler when (action) { is OnboardingSaltPayAction.Update -> { @@ -77,7 +76,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } is OnboardingSaltPayAction.RegisterCard -> { - analyticsHandler.send(Onboarding.ButtonConnect()) + Analytics.send(Onboarding.ButtonConnect()) handleInProgress = true val state = getState() @@ -151,7 +150,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App is OnboardingSaltPayAction.TrySetPin -> { try { assertPinValid(action.pin, getState().pinLength) - analyticsHandler.send(Onboarding.PinCodeSet()) + Analytics.send(Onboarding.PinCodeSet()) store.dispatch(OnboardingSaltPayAction.SetPin(action.pin)) store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.CardRegistration)) } catch (error: SaltPayActivationError) { @@ -159,7 +158,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } is OnboardingSaltPayAction.Claim -> { - analyticsHandler.send(Onboarding.ButtonClaim()) + Analytics.send(Onboarding.ButtonClaim()) val state = getState() handleInProgress = true @@ -192,7 +191,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } handleInProgress = false - analyticsHandler.send(Onboarding.ClaimWasSuccessfully()) + Analytics.send(Onboarding.ClaimWasSuccessfully()) dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimInProgress)) dispatchOnMain(OnboardingSaltPayAction.RefreshClaim) } @@ -233,19 +232,19 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } } - is OnboardingSaltPayAction.SetStep -> handleAnalytics(analyticsHandler, action.newStep) + is OnboardingSaltPayAction.SetStep -> handleAnalytics(action.newStep) else -> { /* do nothing, only reduce */ } } } -private fun handleAnalytics(analyticsHandler: GlobalAnalyticsEventHandler, step: SaltPayActivationStep) { +private fun handleAnalytics(step: SaltPayActivationStep) { when (step) { - SaltPayActivationStep.KycStart -> analyticsHandler.send(Onboarding.KYCStarted()) - SaltPayActivationStep.KycWaiting -> analyticsHandler.send(Onboarding.KYCInProgress()) - SaltPayActivationStep.KycReject -> analyticsHandler.send(Onboarding.KYCRejected()) - SaltPayActivationStep.Claim -> analyticsHandler.send(Onboarding.ClaimScreenOpened()) + SaltPayActivationStep.KycStart -> Analytics.send(Onboarding.KYCStarted()) + SaltPayActivationStep.KycWaiting -> Analytics.send(Onboarding.KYCInProgress()) + SaltPayActivationStep.KycReject -> Analytics.send(Onboarding.KYCRejected()) + SaltPayActivationStep.Claim -> Analytics.send(Onboarding.ClaimScreenOpened()) else -> {} } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 49bbefe866..1e886dd335 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -17,6 +17,7 @@ import com.tangem.common.services.Result import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld @@ -221,7 +222,7 @@ private fun sendTransaction( when (sendResult) { is SimpleResult.Success -> { - store.state.globalState.analyticsHandler?.handleAnalyticsEvent( + Analytics.handleAnalyticsEvent( event = AnalyticsEventAnOld.TRANSACTION_IS_SENT, card = card, blockchain = walletManager.wallet.blockchain.currency, @@ -250,7 +251,7 @@ private fun sendTransaction( feeAmount = feeAmount, destinationAddress = destinationAddress, ) - store.state.globalState.analyticsHandler?.send( + Analytics.send( error = sendResult.error, action = AnalyticsAnOld.ActionToLog.SendTransaction, params = mapOf(AnalyticsParamAnOld.BLOCKCHAIN to walletManager.wallet.blockchain.currency), diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt index ceba9a5217..f7f126c05f 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt @@ -119,13 +119,8 @@ private fun handle(action: Action) { is ShopAction.FinishSuccessfulOrder -> { scope.launch { - shopService.waitForCheckout( - productType = shopState.selectedProduct, - analyticsHandler = store.state.globalState.analyticsHandler - ) + shopService.waitForCheckout(shopState.selectedProduct) } } - } - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 5e34717f5d..157c8786b4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -11,6 +11,7 @@ import com.tangem.common.services.Result import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.attestation.Attestation import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -186,7 +187,7 @@ class WalletMiddleware { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - store.state.globalState.analyticsHandler?.send( + Analytics.send( error, AnalyticsAnOld.ActionToLog.CreateWallet, card = store.state.detailsState.scanResponse?.card diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index 8bf72ef3c7..4a30816169 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -8,6 +8,7 @@ import com.tangem.common.card.Card import com.tangem.common.card.FirmwareVersion import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isTestCard +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.isGreaterThan @@ -81,7 +82,7 @@ class WarningsMiddleware { preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds() } if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) { - store.state.globalState.analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_DISPLAYED) + Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_DISPLAYED) addWarningMessage(WarningMessagesManager.appRatingWarning(), true) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index 57e8e81f77..6a38cb2622 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt @@ -7,6 +7,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.google.android.play.core.review.ReviewManagerFactory +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.extensions.getActivity import com.tangem.tap.common.extensions.getColor @@ -107,14 +108,13 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi binding.groupControlsTemporary.hide() binding.groupControlsRating.show() binding.btnClose.show() - val analyticsHandler = store.state.globalState.analyticsHandler binding.btnClose.setOnClickListener { - analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_DISMISS) + Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_DISMISS) store.dispatch(GlobalAction.HideWarningMessage(warning)) store.dispatch(WalletAction.Warnings.AppRating.RemindLater) } binding.btnCanBeBetter.setOnClickListener { - analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_NEGATIVE) + Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_NEGATIVE) store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) store.dispatch(GlobalAction.HideWarningMessage(warning)) store.dispatch(GlobalAction.SendEmail(RateCanBeBetterEmail())) @@ -122,7 +122,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi binding.btnReallyCool.setOnClickListener { val activity = binding.root.context.getActivity() ?: return@setOnClickListener - analyticsHandler?.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_POSITIVE) + Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_POSITIVE) store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) val reviewManager = ReviewManagerFactory.create(activity) val task = reviewManager.requestReviewFlow() From 2c19b748b0a0a3df33bfeffd8cc4b4c5aa96d704 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Oct 2022 12:27:15 +0500 Subject: [PATCH 003/176] Updated on 2026-08-14 --- .../tap/features/home/redux/HomeMiddleware.kt | 2 +- .../features/onboarding/OnboardingHelper.kt | 4 +- .../features/onboarding/OnboardingManager.kt | 22 ++++++--- .../products/note/OnboardingNoteFragment.kt | 1 + .../note/redux/OnboardingNoteAction.kt | 1 + .../note/redux/OnboardingNoteMiddleware.kt | 8 ++++ .../OnboardingOtherCardsFragment.kt | 1 + .../redux/OnboardingOtherCardsAction.kt | 1 + .../redux/OnboardingOtherCardsMiddleware.kt | 10 +++- .../twins/redux/TwinCardsMiddleware.kt | 23 ++++++--- .../wallet/redux/OnboardingWalletAction.kt | 1 - .../redux/OnboardingWalletMiddleware.kt | 30 +++++++----- .../saltPay/redux/OnboardingSaltPayAction.kt | 12 ++--- .../redux/OnboardingSaltPayMiddleware.kt | 47 ++++++++++++++----- .../saltPay/redux/OnboardingSaltPayReducer.kt | 2 +- .../wallet/ui/OnboardingWalletFragment.kt | 3 +- .../tap/persistence/UsedCardsPrefStorage.kt | 18 +++---- 17 files changed, 127 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 8df39141c7..5f77bee19f 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -166,7 +166,7 @@ private fun onScanSuccess(scanResponse: ScanResponse) { val isOnboardingCase = result.data if (isOnboardingCase) { store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) - store.dispatch(OnboardingSaltPayAction.Init.SetDependencies(manager, config)) + store.dispatch(OnboardingSaltPayAction.SetDependencies(manager, config)) store.dispatch(OnboardingSaltPayAction.Update) navigateTo(AppScreen.OnboardingWallet) } else { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index f2227fcc32..48d1e70e68 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -19,10 +19,10 @@ class OnboardingHelper { if (!response.twinsIsTwinned()) { true } else { - cardInfoStorage.activationIsStarted(response.card.cardId) + cardInfoStorage.isActivationStarted(response.card.cardId) } } - response.card.hasWallets() -> cardInfoStorage.activationIsStarted(response.card.cardId) + response.card.hasWallets() -> cardInfoStorage.isActivationStarted(response.card.cardId) else -> true } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt index 8284efcd3d..853044464b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -67,8 +67,10 @@ class OnboardingManager( } } - return balance.copy(currency = Currency.Blockchain( - walletManager.wallet.blockchain, walletManager.wallet.publicKey.derivationPath?.rawPath) + return balance.copy( + currency = Currency.Blockchain( + walletManager.wallet.blockchain, walletManager.wallet.publicKey.derivationPath?.rawPath, + ), ) } @@ -79,6 +81,14 @@ class OnboardingManager( fun activationFinished(cardId: String) { usedCardsPrefStorage.activationFinished(cardId) } + + fun isActivationFinished(cardId: String): Boolean { + return usedCardsPrefStorage.isActivationFinished(cardId) + } + + fun isActivationStarted(cardId: String): Boolean { + return usedCardsPrefStorage.isActivationStarted(cardId) + } } data class OnboardingWalletBalance( @@ -98,23 +108,23 @@ data class OnboardingWalletBalance( companion object { fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( state = ProgressState.Error, - error = error + error = error, ) fun criticalError(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance( state = ProgressState.Error, - criticalError = error + criticalError = error, ) fun loading(value: BigDecimal = BigDecimal.ZERO): OnboardingWalletBalance = OnboardingWalletBalance( value, - state = ProgressState.Loading + state = ProgressState.Loading, ) fun done(value: BigDecimal, hasTransactions: Boolean): OnboardingWalletBalance = OnboardingWalletBalance( value, hasIncomingTransaction = hasTransactions, - state = ProgressState.Done + state = ProgressState.Done, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt index a54a27bd28..865726f3dc 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -41,6 +41,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { binding.toolbar.setTitle(R.string.onboarding_title) btnRefreshBalanceWidget = RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer) + store.dispatch(OnboardingNoteAction.Init) store.dispatch(OnboardingNoteAction.LoadCardArtwork) store.dispatch(OnboardingNoteAction.DetermineStepOfScreen) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt index 5b377fd072..8e7046f114 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt @@ -10,6 +10,7 @@ import org.rekotlin.Action */ sealed class OnboardingNoteAction : Action { // from user, ui + object Init : OnboardingNoteAction() object LoadCardArtwork : OnboardingNoteAction() object CreateWallet : OnboardingNoteAction() object TopUp : OnboardingNoteAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index d990941bd5..a71a55f30a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -4,6 +4,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOpenUrl @@ -58,6 +60,11 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch val noteState = store.state.onboardingNoteState when (action) { + is OnboardingNoteAction.Init -> { + if (!onboardingManager.isActivationStarted(card.cardId)) { + Analytics.send(Onboarding.Started()) + } + } is OnboardingNoteAction.LoadCardArtwork -> { scope.launch { val artworkUrl = onboardingManager.loadArtworkUrl() @@ -78,6 +85,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch store.dispatch(OnboardingNoteAction.Balance.Update) } OnboardingNoteStep.Done -> { + Analytics.send(Onboarding.Finished()) onboardingManager.activationFinished(card.cardId) postUi(DELAY_SDK_DIALOG_CLOSE) { store.dispatch(OnboardingNoteAction.Confetti.Show) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index 0ed23143da..960062acab 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -33,6 +33,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment { + if (!onboardingManager.isActivationStarted(card.cardId)) { + Analytics.send(Onboarding.Started()) + } + } is OnboardingOtherCardsAction.LoadCardArtwork -> { scope.launch { val artworkUrl = onboardingManager.loadArtworkUrl() @@ -60,6 +67,7 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { } is OnboardingOtherCardsAction.SetStepOfScreen -> { if (action.step == OnboardingOtherCardsStep.Done) { + Analytics.send(Onboarding.Finished()) onboardingManager.activationFinished(card.cardId) postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) } } @@ -71,7 +79,7 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { when (result) { is CompletionResult.Success -> { val updatedResponse = onboardingManager.scanResponse.copy( - card = result.data.card + card = result.data.card, ) onboardingManager.scanResponse = updatedResponse onboardingManager.activationStarted(updatedResponse.card.cardId) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 35f7e1d7bd..864549e54d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -6,6 +6,8 @@ import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOpenUrl @@ -76,6 +78,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { fun finishCardActivation() { if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) { + Analytics.send(Onboarding.Finished()) onboardingManager?.activationFinished(getScanResponse().card.cardId) twinCardsState.pairCardId?.let { onboardingManager?.activationFinished(it) } } @@ -85,11 +88,18 @@ private fun handle(action: Action, dispatch: DispatchFunction) { is TwinCardsAction.Init -> { if (twinCardsState.currentStep == TwinCardsStep.WelcomeOnly) return + val scanResponse = getScanResponse() + onboardingManager?.apply { + if (!isActivationStarted(scanResponse.card.cardId)) { + Analytics.send(Onboarding.Started()) + } + } + when (twinCardsState.mode) { CreateTwinWalletMode.CreateWallet -> { if (preferencesStorage.wasTwinsOnboardingShown()) { val step = when { - !getScanResponse().twinsIsTwinned() -> TwinCardsStep.CreateFirstWallet + !scanResponse.twinsIsTwinned() -> TwinCardsStep.CreateFirstWallet twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done else -> TwinCardsStep.TopUpWallet } @@ -105,8 +115,8 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } is TwinCardsAction.Wallet.HandleOnBackPressed -> { val shouldReturnCardBack = twinCardsState.mode == CreateTwinWalletMode.CreateWallet - && twinCardsState.currentStep != TwinCardsStep.TopUpWallet - && twinCardsState.currentStep != TwinCardsStep.Done + && twinCardsState.currentStep != TwinCardsStep.TopUpWallet + && twinCardsState.currentStep != TwinCardsStep.Done if (twinCardsState.showAlert) { val onInterruptPrompt = { @@ -163,7 +173,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { val result = manager.createSecondWallet( action.initialMessage, action.preparingMessage, - action.creatingWalletMessage + action.creatingWalletMessage, ) when (result) { is CompletionResult.Success -> { @@ -221,11 +231,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) { val balanceIsLoading = twinCardsState.walletBalance.copy( currency = Currency.Blockchain( walletManager.wallet.blockchain, - walletManager.wallet.publicKey.derivationPath?.rawPath + walletManager.wallet.publicKey.derivationPath?.rawPath, ), state = ProgressState.Loading, error = null, - criticalError = null + criticalError = null, ) store.dispatch(TwinCardsAction.Balance.Set(balanceIsLoading)) @@ -239,7 +249,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) { store.dispatch(TwinCardsAction.Balance.SetNonCriticalError(loadedBalance.error)) } } - } is TwinCardsAction.Balance.Set -> { if (action.balance.balanceIsToppedUp()) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index 0de2a90471..728e86280e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -7,7 +7,6 @@ import org.rekotlin.Action sealed class OnboardingWalletAction : Action { object Init : OnboardingWalletAction() - object StartSaltPayCardActivation : OnboardingWalletAction() object GetToCreateWalletStep : OnboardingWalletAction() object GetToSaltPayStep : OnboardingWalletAction() object CreateWallet : OnboardingWalletAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 48e9c32f2f..ebdae723ea 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -3,10 +3,14 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.card.Card +import com.tangem.common.extensions.ifNotNull import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.backup.BackupService import com.tangem.tap.backupService +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.primaryCardIsSaltPay @@ -61,6 +65,12 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: when (action) { OnboardingWalletAction.Init -> { + ifNotNull(onboardingManager, card) { manager, card -> + if (!card.isSaltPay && !manager.isActivationStarted(card.cardId)) { + Analytics.send(Onboarding.Started()) + } + } + when { card == null -> { // it's possible when found unfinished backup for standard Wallet cards @@ -87,13 +97,6 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } } } - is OnboardingWalletAction.StartSaltPayCardActivation -> { - // we need to do this because the standard activation happens in the OnboardingWalletAction.CreateWallet - // step which is omitted for SaltPay cards - if (onboardingWalletState.isSaltPay && scanResponse != null) { - startCardActivation(scanResponse) - } - } is OnboardingWalletAction.LoadArtwork -> { scope.launch { val artwork = onboardingManager?.loadArtworkUrl() @@ -148,9 +151,6 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } else { val backupState = store.state.onboardingWalletState.backupState val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState) - if (onboardingWalletState.isSaltPay) { - finishCardActivation(backupState, scanResponse.card) - } scope.launch { globalState.tapWalletManager.onCardScanned(updatedScanResponse) } store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } @@ -313,6 +313,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } is BackupAction.FinishBackup -> { if (!onboardingWalletState.isSaltPay) { + Analytics.send(Onboarding.Finished()) finishCardActivation(backupState, card) } } @@ -320,7 +321,10 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) backupService.discardSavedBackup() } is BackupAction.DiscardSavedBackup -> { - backupService.primaryCardId?.let { finishCardsActivationForDiscardedUnfinishedBackup(it) } + backupService.primaryCardId?.let { + Analytics.send(Onboarding.Finished()) + finishCardsActivationForDiscardedUnfinishedBackup(it) + } backupService.discardSavedBackup() } is BackupAction.CheckForUnfinishedBackup -> { @@ -351,7 +355,7 @@ private fun startCardActivation(scanResponse: ScanResponse) { * Standard Wallet cards finish activation at BackupAction.FinishBackup * SaltPay cards finish activation at OnboardingWalletAction.FinishOnboarding */ -private fun finishCardActivation(backupState: BackupState, card: Card?) { +internal fun finishCardActivation(backupState: BackupState, card: Card?) { (listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds) .distinct().filterNotNull() .forEach { cardId -> @@ -377,7 +381,7 @@ private fun initSaltPayOnBackupFinishedIfNeeded( if (scanResponse == null) throw IllegalArgumentException() val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) - store.dispatchOnMain(OnboardingSaltPayAction.Init.SetDependencies(manager, config)) + store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config)) store.dispatchOnMain(OnboardingSaltPayAction.Update) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt index 009f2350ad..a2194e9568 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt @@ -10,12 +10,12 @@ import java.math.BigDecimal [REDACTED_AUTHOR] */ sealed class OnboardingSaltPayAction : Action { - sealed class Init : OnboardingSaltPayAction() { - data class SetDependencies( - val registrationManager: SaltPayActivationManager, - val saltPayConfig: SaltPayConfig, - ) : Init() - } + object Init : OnboardingSaltPayAction() + + data class SetDependencies( + val registrationManager: SaltPayActivationManager, + val saltPayConfig: SaltPayConfig, + ) : OnboardingSaltPayAction() data class SetInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction() data class SetClaimRefreshInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index d5442968ca..ea6bec2c95 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -12,13 +12,16 @@ import com.tangem.network.api.paymentology.KYCStatus import com.tangem.network.api.paymentology.RegistrationResponse import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.isPositive import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TangemSigner import com.tangem.tap.features.demo.DemoHelper +import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState +import com.tangem.tap.features.onboarding.products.wallet.redux.finishCardActivation import com.tangem.tap.features.onboarding.products.wallet.saltPay.AllSymbolsTheSameFilter import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler @@ -58,9 +61,23 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App fun getAppState(): AppState = appState()!! fun getOnboardingWalletState(): OnboardingWalletState = getAppState().onboardingWalletState + fun getOnboardingManager(): OnboardingManager? = getAppState().globalState.onboardingState.onboardingManager fun getState(): OnboardingSaltPayState = getOnboardingWalletState().onboardingSaltPayState!! when (action) { + is OnboardingSaltPayAction.Init -> { + if (getOnboardingWalletState().isSaltPay) return + val onboardingManager = getOnboardingManager().guard { + // onboardingManager may be null if it's started from standard Wallet unfinished backup + return + } + + val card = onboardingManager.scanResponse.card + if (!onboardingManager.isActivationStarted(card.cardId)) { + Analytics.send(Onboarding.Started()) + onboardingManager.activationStarted(card.cardId) + } + } is OnboardingSaltPayAction.Update -> { handleInProgress = true @@ -232,23 +249,31 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } } - is OnboardingSaltPayAction.SetStep -> handleAnalytics(action.newStep) + is OnboardingSaltPayAction.SetStep -> { + when (action.newStep) { + SaltPayActivationStep.KycStart -> Analytics.send(Onboarding.KYCStarted()) + SaltPayActivationStep.KycWaiting -> Analytics.send(Onboarding.KYCInProgress()) + SaltPayActivationStep.KycReject -> Analytics.send(Onboarding.KYCRejected()) + SaltPayActivationStep.Claim -> Analytics.send(Onboarding.ClaimScreenOpened()) + SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success -> { + Analytics.send(Onboarding.Finished()) + val onboardingManager = getOnboardingManager().guard { + // Null is possible if it is started from a standard pending backup and in this case + // it is impossible to get here + store.dispatchDebugErrorNotification("OnboardingManager can't be NULL") + return + } + finishCardActivation(getOnboardingWalletState().backupState, onboardingManager.scanResponse.card) + } + else -> {} + } + } else -> { /* do nothing, only reduce */ } } } -private fun handleAnalytics(step: SaltPayActivationStep) { - when (step) { - SaltPayActivationStep.KycStart -> Analytics.send(Onboarding.KYCStarted()) - SaltPayActivationStep.KycWaiting -> Analytics.send(Onboarding.KYCInProgress()) - SaltPayActivationStep.KycReject -> Analytics.send(Onboarding.KYCRejected()) - SaltPayActivationStep.Claim -> Analytics.send(Onboarding.ClaimScreenOpened()) - else -> {} - } -} - suspend fun SaltPayActivationManager.update( currentStep: SaltPayActivationStep, currentAmountToClaim: Amount?, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt index 264829c157..71570fd52e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt @@ -17,7 +17,7 @@ class OnboardingSaltPayReducer { private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingWalletState): OnboardingWalletState { val action = anyAction as? OnboardingSaltPayAction ?: return onboardingWalletState - if (onboardingWalletState.onboardingSaltPayState == null && action is OnboardingSaltPayAction.Init.SetDependencies) { + if (onboardingWalletState.onboardingSaltPayState == null && action is OnboardingSaltPayAction.SetDependencies) { return onboardingWalletState.copy( onboardingSaltPayState = OnboardingSaltPayState( saltPayManager = action.registrationManager, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 47b6beb3aa..e016d5fe8d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -32,6 +32,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep +import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayView import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog import com.tangem.tap.store @@ -81,7 +82,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle addBackPressHandler(this) store.dispatch(OnboardingWalletAction.Init) - store.dispatch(OnboardingWalletAction.StartSaltPayCardActivation) + store.dispatch(OnboardingSaltPayAction.Init) store.dispatch(OnboardingWalletAction.LoadArtwork) } diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt index 5d7f241fb8..fb7f6a75e1 100644 --- a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -11,7 +11,7 @@ import timber.log.Timber */ class UsedCardsPrefStorage( private val preferences: SharedPreferences, - private val jsonConverter: MoshiJsonConverter + private val jsonConverter: MoshiJsonConverter, ) { internal fun migrate() { @@ -43,14 +43,6 @@ class UsedCardsPrefStorage( save(foundItem, restoredList) } - fun isActivationFinished(cardId: String): Boolean { - return !(findCardInfo(cardId)?.isActivationStarted ?: true) - } - - fun activationIsStarted(cardId: String): Boolean { - return findCardInfo(cardId)?.isActivationStarted ?: false - } - fun activationFinished(cardId: String) { val restoredList = restore() val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = false) @@ -59,6 +51,14 @@ class UsedCardsPrefStorage( save(foundItem, restoredList) } + fun isActivationFinished(cardId: String): Boolean { + return !(findCardInfo(cardId)?.isActivationStarted ?: true) + } + + fun isActivationStarted(cardId: String): Boolean { + return findCardInfo(cardId)?.isActivationStarted ?: false + } + private fun findCardInfo(cardId: String, list: MutableList? = null): UsedCardInfo? { val findInList = list ?: restore() return findInList.firstOrNull { it.cardId == cardId } From cc4292319d29cfaaf42efd86f03ebf31046db052 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Oct 2022 18:02:29 +0500 Subject: [PATCH 004/176] Updated on 2026-08-14 --- .../note/redux/OnboardingNoteMiddleware.kt | 5 +++++ .../redux/OnboardingOtherCardsMiddleware.kt | 15 +++++++++++---- .../products/twins/redux/TwinCardsMiddleware.kt | 7 ++++++- .../wallet/redux/OnboardingWalletMiddleware.kt | 3 +++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index a71a55f30a..f55bf9668a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -81,6 +81,9 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch } is OnboardingNoteAction.SetStepOfScreen -> { when (action.step) { + OnboardingNoteStep.CreateWallet -> { + Analytics.send(Onboarding.CreateWallet.ScreenOpened()) + } OnboardingNoteStep.TopUpWallet -> { store.dispatch(OnboardingNoteAction.Balance.Update) } @@ -92,11 +95,13 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch } } is OnboardingNoteAction.CreateWallet -> { + Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) scope.launch { val result = tangemSdkManager.createProductWallet(scanResponse) withMainContext { when (result) { is CompletionResult.Success -> { + Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) val updatedResponse = scanResponse.copy(card = result.data.card) onboardingManager.scanResponse = updatedResponse onboardingManager.activationStarted(updatedResponse.card.cardId) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index efb2226659..ff988cc8d5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -66,18 +66,25 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { store.dispatch((OnboardingOtherCardsAction.SetStepOfScreen(step))) } is OnboardingOtherCardsAction.SetStepOfScreen -> { - if (action.step == OnboardingOtherCardsStep.Done) { - Analytics.send(Onboarding.Finished()) - onboardingManager.activationFinished(card.cardId) - postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) } + when (action.step) { + OnboardingOtherCardsStep.CreateWallet -> { + Analytics.send(Onboarding.CreateWallet.ScreenOpened()) + } + OnboardingOtherCardsStep.Done -> { + Analytics.send(Onboarding.Finished()) + onboardingManager.activationFinished(card.cardId) + postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) } + } } } is OnboardingOtherCardsAction.CreateWallet -> { + Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) scope.launch { val result = tangemSdkManager.createProductWallet(onboardingManager.scanResponse) withMainContext { when (result) { is CompletionResult.Success -> { + Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) val updatedResponse = onboardingManager.scanResponse.copy( card = result.data.card, ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 864549e54d..9865fc9c06 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -99,7 +99,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) { CreateTwinWalletMode.CreateWallet -> { if (preferencesStorage.wasTwinsOnboardingShown()) { val step = when { - !scanResponse.twinsIsTwinned() -> TwinCardsStep.CreateFirstWallet + !scanResponse.twinsIsTwinned() -> { + Analytics.send(Onboarding.CreateWallet.ScreenOpened()) + TwinCardsStep.CreateFirstWallet + } twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done else -> TwinCardsStep.TopUpWallet } @@ -146,6 +149,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } } is TwinCardsAction.Wallet.LaunchFirstStep -> { + Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) val manager = TwinCardsManager( card = getScanResponse().card, assetReader = action.reader, @@ -155,6 +159,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { scope.launch { when (val result = manager.createFirstWallet(action.initialMessage)) { is CompletionResult.Success -> { + Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) startCardActivation(result.data.cardId) delay(DELAY_SDK_DIALOG_CLOSE) withMainContext { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 4e34f48b54..ab507c9a6e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -94,6 +94,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } else -> { store.dispatch(OnboardingWalletAction.GetToCreateWalletStep) + Analytics.send(Onboarding.CreateWallet.ScreenOpened()) } } } @@ -109,12 +110,14 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } } is OnboardingWalletAction.CreateWallet -> { + Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) scope.launch { scanResponse ?: return@launch val result = tangemSdkManager.createProductWallet(scanResponse) withMainContext { when (result) { is CompletionResult.Success -> { + Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully()) //here we must use updated scanResponse after createWallet & derivation val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy( card = result.data.card, From d963d65f2ec591cc717e533e1f8e318257009dcf Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Oct 2022 19:49:32 +0500 Subject: [PATCH 005/176] Updated on 2026-08-14 --- .../tap/common/analytics/events/Onboarding.kt | 20 +++++++++---------- .../redux/OnboardingWalletMiddleware.kt | 9 +++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt index b65408b7d5..5634c25afa 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt @@ -34,9 +34,9 @@ sealed class Onboarding( class AccessCodeEntered : Backup("Access Code Entered") class AccessCodeReEntered : Backup("Access Code Re-entered") - class Finished(cardsCount: String) : Backup( + class Finished(cardsCount: Int) : Backup( event = "Backup Finished", - params = mapOf("Cards count" to cardsCount), + params = mapOf("Cards count" to "$cardsCount"), ) } @@ -65,12 +65,12 @@ sealed class Onboarding( class SetupFinished : Twins("Twin Setup Finished") } - class PinCodeSet: Onboarding("Onboarding", "PIN code set") - class ButtonConnect: Onboarding("Onboarding", "Button - Connect") - class KYCStarted: Onboarding("Onboarding", "KYC started") - class KYCInProgress: Onboarding("Onboarding", "KYC in progress") - class KYCRejected: Onboarding("Onboarding", "KYC rejected") - class ClaimScreenOpened: Onboarding("Onboarding", "Claim screen opened") - class ButtonClaim: Onboarding("Onboarding", "Button - Claim") - class ClaimWasSuccessfully: Onboarding("Onboarding", "Claim was successfully") + class PinCodeSet : Onboarding("Onboarding", "PIN code set") + class ButtonConnect : Onboarding("Onboarding", "Button - Connect") + class KYCStarted : Onboarding("Onboarding", "KYC started") + class KYCInProgress : Onboarding("Onboarding", "KYC in progress") + class KYCRejected : Onboarding("Onboarding", "KYC rejected") + class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened") + class ButtonClaim : Onboarding("Onboarding", "Button - Claim") + class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index ab507c9a6e..e468e440f5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -169,6 +169,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: if (onboardingWalletState.isSaltPay) { BackupAction.StartBackup } else { + Analytics.send(Onboarding.Backup.ScreenOpened()) BackupAction.IntroduceBackup } } else { @@ -224,6 +225,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) when (action) { is BackupAction.StartBackup -> { + Analytics.send(Onboarding.Backup.Started()) backupService.discardSavedBackup() val primaryCard = scanResponse?.primaryCard if (primaryCard != null) { @@ -269,7 +271,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) store.dispatchOnMain(GlobalAction.ShowDialog(BackupDialog.AddMoreBackupCards)) } } + is BackupAction.ShowEnterAccessCodeScreen -> { + Analytics.send(Onboarding.Backup.SettingAccessCodeStarted()) + } is BackupAction.CheckAccessCode -> { + Analytics.send(Onboarding.Backup.AccessCodeEntered()) if (action.accessCode.length < 4) { store.dispatch(BackupAction.SetAccessCodeError(AccessCodeError.CodeTooShort)) } else { @@ -278,6 +284,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.SaveAccessCodeConfirmation -> { + Analytics.send(Onboarding.Backup.AccessCodeReEntered()) if (action.accessCodeConfirmation == backupState.accessCode) { store.dispatch(BackupAction.SetAccessCodeError(null)) backupService.setAccessCode(action.accessCodeConfirmation) @@ -315,6 +322,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.FinishBackup -> { + Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber)) if (!onboardingWalletState.isSaltPay) { Analytics.send(Onboarding.Finished()) finishCardActivation(backupState, card) @@ -340,6 +348,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } is BackupAction.DismissBackup -> { + Analytics.send(Onboarding.Backup.Skipped()) if (onboardingWalletState.isSaltPay) throw UnsupportedOperationException() store.dispatch(BackupAction.FinishBackup) } From 7d0835f28c7c6bdf702733ad646ba36d004aec69 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Oct 2022 23:15:07 +0500 Subject: [PATCH 006/176] Updated on 2026-08-14 --- .../domain/tasks/product/ScanProductTask.kt | 4 +- .../features/onboarding/OnboardingHelper.kt | 5 +- .../tap/persistence/PreferencesStorage.kt | 5 - .../tap/persistence/UsedCardsPrefStorage.kt | 97 ++++++++++++++----- 4 files changed, 78 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 252d3585e6..a5d9fc3932 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -162,9 +162,9 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - val activationIsFinished = preferencesStorage.usedCardsPrefStorage.isActivationFinished(card.cardId) + val activationInProgress = preferencesStorage.usedCardsPrefStorage.isActivationInProgress(card.cardId) - if (card.backupStatus == Card.BackupStatus.NoBackup && !activationIsFinished && card.wallets.isNotEmpty()) { + if (card.backupStatus == Card.BackupStatus.NoBackup && activationInProgress && card.wallets.isNotEmpty()) { StartPrimaryCardLinkingTask().run(session) { linkingResult -> when (linkingResult) { is CompletionResult.Success -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index f2227fcc32..2917a052fe 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -14,15 +14,16 @@ class OnboardingHelper { fun isOnboardingCase(response: ScanResponse): Boolean { val cardInfoStorage = preferencesStorage.usedCardsPrefStorage + val cardId = response.card.cardId return when { response.isTangemTwins() -> { if (!response.twinsIsTwinned()) { true } else { - cardInfoStorage.activationIsStarted(response.card.cardId) + cardInfoStorage.isActivationInProgress(cardId) } } - response.card.hasWallets() -> cardInfoStorage.activationIsStarted(response.card.cardId) + response.card.hasWallets() -> cardInfoStorage.isActivationInProgress(cardId) else -> true } } diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 723af0df99..40e4cd1bd2 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -33,11 +33,6 @@ class PreferencesStorage(applicationContext: Application) { fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1) - @Deprecated("Use UsedCardsPrefStorage instead") - fun wasCardScannedBefore(cardId: String): Boolean { - return usedCardsPrefStorage.wasScanned(cardId) - } - fun saveDisclaimerAccepted() { preferences.edit { putBoolean(DISCLAIMER_ACCEPTED_KEY, true) } } diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt index 5d7f241fb8..f814eb7e16 100644 --- a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -11,16 +11,16 @@ import timber.log.Timber */ class UsedCardsPrefStorage( private val preferences: SharedPreferences, - private val jsonConverter: MoshiJsonConverter + private val jsonConverter: MoshiJsonConverter, ) { - internal fun migrate() { - val scannedIds = preferences.getString(SCANNED_CARDS_IDS_KEY, null) ?: return + private val migrationList = mutableListOf( + UserCardInfoToV2(this), + ) - val usedCardsInfo = scannedIds.split(",").map { UsedCardInfo(it.trim(), true) } - if (save(usedCardsInfo.toMutableList())) { - preferences.edit { this.remove(SCANNED_CARDS_IDS_KEY) } - } + internal fun migrate() { + migrationList.forEach { it.migrate() } + migrationList.clear() } fun scanned(cardId: String) { @@ -43,20 +43,28 @@ class UsedCardsPrefStorage( save(foundItem, restoredList) } - fun isActivationFinished(cardId: String): Boolean { - return !(findCardInfo(cardId)?.isActivationStarted ?: true) + fun activationFinished(cardId: String) { + val restoredList = restore() + var foundItem = findCardInfo(cardId, restoredList) ?: UsedCardInfo(cardId) + foundItem = foundItem.copy( + isActivationStarted = true, + isActivationFinished = true, + ) + + save(foundItem, restoredList) } - fun activationIsStarted(cardId: String): Boolean { + fun isActivationStarted(cardId: String): Boolean { return findCardInfo(cardId)?.isActivationStarted ?: false } - fun activationFinished(cardId: String) { - val restoredList = restore() - val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = false) - ?: UsedCardInfo(cardId, isActivationStarted = false) + fun isActivationFinished(cardId: String): Boolean { + return findCardInfo(cardId)?.isActivationFinished ?: false + } - save(foundItem, restoredList) + fun isActivationInProgress(cardId: String): Boolean { + val cardInfo = findCardInfo(cardId) ?: return false + return cardInfo.isActivationStarted && !cardInfo.isActivationFinished } private fun findCardInfo(cardId: String, list: MutableList? = null): UsedCardInfo? { @@ -74,7 +82,7 @@ class UsedCardsPrefStorage( private fun save(list: MutableList): Boolean { return try { val json = jsonConverter.toJson(list) - preferences.edit { putString(USED_CARDS_INFO, json) } + preferences.edit { putString(USED_CARDS_INFO_V2, json) } true } catch (ex: Exception) { Timber.e(ex) @@ -83,23 +91,64 @@ class UsedCardsPrefStorage( } private fun restore(): MutableList { - val json = preferences.getString(USED_CARDS_INFO, null) ?: return mutableListOf() + val json = preferences.getString(USED_CARDS_INFO_V2, null) ?: return mutableListOf() return try { jsonConverter.fromJson(json, jsonConverter.typedList(UsedCardInfo::class.java))!! } catch (ex: Exception) { - preferences.edit(true) { remove(USED_CARDS_INFO) } + preferences.edit(true) { remove(USED_CARDS_INFO_V2) } mutableListOf() } } companion object { + private const val USED_CARDS_INFO_V2 = "usedCardsInfo_v2" private const val USED_CARDS_INFO = "usedCardsInfo" - private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds" } + + private class UserCardInfoToV2( + private val storage: UsedCardsPrefStorage, + ) : Migration { + override fun migrate() { + val restoredCardsInfo = restore() + if (restoredCardsInfo.isEmpty()) return + + val newCardsInfo = restoredCardsInfo.map { cardInfo -> + UsedCardInfo( + cardId = cardInfo.cardId, + isScanned = cardInfo.isScanned, + isActivationStarted = true, + isActivationFinished = !cardInfo.isActivationStarted, + ) + }.toMutableList() + storage.save(newCardsInfo) + } + + private fun restore(): MutableList { + val json = storage.preferences.getString(USED_CARDS_INFO, null) ?: return mutableListOf() + return try { + storage.jsonConverter.fromJson(json, storage.jsonConverter.typedList(UsedCardInfoOld::class.java))!! + } catch (ex: Exception) { + mutableListOf() + } finally { + storage.preferences.edit(true) { remove(USED_CARDS_INFO) } + } + } + } + + private data class UsedCardInfo( + val cardId: String, + val isScanned: Boolean = false, + val isActivationStarted: Boolean = false, + val isActivationFinished: Boolean = false, + ) + + private data class UsedCardInfoOld( + val cardId: String, + val isScanned: Boolean = false, + val isActivationStarted: Boolean = false, + ) } -private data class UsedCardInfo( - val cardId: String, - val isScanned: Boolean = false, - val isActivationStarted: Boolean = false, -) \ No newline at end of file +private interface Migration { + fun migrate() +} \ No newline at end of file From a71116a0aa5312c44b3c38a914d98be67deccbdb Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Oct 2022 23:15:23 +0500 Subject: [PATCH 007/176] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index c8f577f2ab..cf6ea50867 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit c8f577f2ab48e0d6c49f79b6cff21eabe67cd63e +Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354 From 7b90bb0848dd1bcabb6210d90562016312055916 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 29 Oct 2022 00:22:43 +0500 Subject: [PATCH 008/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/persistence/PreferencesStorage.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 2250427b09..32ff1aeb79 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -33,14 +33,6 @@ class PreferencesStorage(applicationContext: Application) { fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1) - fun saveDisclaimerAccepted() { - preferences.edit { putBoolean(DISCLAIMER_ACCEPTED_KEY, true) } - } - - fun wasDisclaimerAccepted(): Boolean { - return preferences.getBoolean(DISCLAIMER_ACCEPTED_KEY, false) - } - fun saveTwinsOnboardingShown() { preferences.edit { putBoolean(TWINS_ONBOARDING_SHOWN_KEY, true) } } From 9f031964463ec2d4d4a8e9d6851bbc13469524ab Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 30 Oct 2022 20:41:30 +0500 Subject: [PATCH 009/176] Updated on 2026-08-14 --- .../features/onboarding/products/wallet/ui/BackupAnimator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt index d57205ce46..66f90f159c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt @@ -68,7 +68,7 @@ class WalletBackupAnimator( when (backupCardsCounts) { 0 -> { when (currentStep) { - STEP_CREATE_WALLET -> { + UNDEFINED, STEP_CREATE_WALLET -> { cardsWidget.toFolded(false) { cardsWidget.toFan() } FIRST_BACKUP.animateAlpha(0.6f, 0) From 936112008744895f8e42d6e696a824b589c61906 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 30 Oct 2022 20:43:03 +0500 Subject: [PATCH 010/176] Updated on 2026-08-14 --- .../wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index 90c9d33f06..f550b730ac 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -66,7 +66,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App when (action) { is OnboardingSaltPayAction.Init -> { - if (getOnboardingWalletState().isSaltPay) return + if (!getOnboardingWalletState().isSaltPay) return val onboardingManager = getOnboardingManager().guard { // onboardingManager may be null if it's started from standard Wallet unfinished backup return @@ -289,7 +289,7 @@ suspend fun SaltPayActivationManager.update( return Result.Failure(ex) } - checkGasIfNeeded(this, newStep).successOr { return it } + // checkGasIfNeeded(this, newStep).successOr { return it } Timber.d("update: success: %s", newStep) return Result.Success(newStep) From 085ffaf2c5d0be5d5b8be04613eb719d4da0df72 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 30 Oct 2022 20:43:48 +0500 Subject: [PATCH 011/176] Updated on 2026-08-14 --- .../tap/domain/tasks/product/ScanProductTask.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index a5d9fc3932..459191b260 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -73,7 +73,7 @@ class ScanProductTask( // it need because processorResult.data.card doesn't contains attestation result // and CardWallet.derivedKeys val processorScanResponseWithNewCard = processorResult.data.copy( - card = scanTaskResult.data + card = scanTaskResult.data, ) callback(CompletionResult.Success(processorScanResponseWithNewCard)) } @@ -96,7 +96,7 @@ private class ScanNoteProcessor : ProductCommandProcessor { override fun proceed( card: Card, session: CardSession, - callback: (result: CompletionResult) -> Unit + callback: (result: CompletionResult) -> Unit, ) { callback( CompletionResult.Success( @@ -145,7 +145,7 @@ private class ScanWalletProcessor( is CompletionResult.Success -> { PreflightReadTask( readMode = PreflightReadMode.FullCardRead, - cardId = card.cardId + cardId = card.cardId, ).run(session) { readResult -> when (readResult) { is CompletionResult.Success -> startLinkingForBackupIfNeeded(card, session, callback) @@ -157,6 +157,7 @@ private class ScanWalletProcessor( } } } + private fun startLinkingForBackupIfNeeded( card: Card, session: CardSession, @@ -164,7 +165,9 @@ private class ScanWalletProcessor( ) { val activationInProgress = preferencesStorage.usedCardsPrefStorage.isActivationInProgress(card.cardId) - if (card.backupStatus == Card.BackupStatus.NoBackup && activationInProgress && card.wallets.isNotEmpty()) { + if ((card.backupStatus == Card.BackupStatus.NoBackup && card.wallets.isNotEmpty()) + && (activationInProgress || card.isSaltPay) + ) { StartPrimaryCardLinkingTask().run(session) { linkingResult -> when (linkingResult) { is CompletionResult.Success -> { @@ -180,6 +183,7 @@ private class ScanWalletProcessor( deriveKeysIfNeeded(card, session, callback) } } + private fun deriveKeysIfNeeded( card: Card, session: CardSession, @@ -251,7 +255,7 @@ private class ScanWalletProcessor( Blockchain.RSK, Blockchain.Fantom, Blockchain.FantomTestnet, Blockchain.Avalanche, Blockchain.AvalancheTestnet, - ).map { BlockchainNetwork(it, card) } + ).map { BlockchainNetwork(it, card) }, ) } return blockchainsToDerive.distinct() From 41703c9f113ac0545b5bb5aeb19c42450cce72d5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 30 Oct 2022 20:44:26 +0500 Subject: [PATCH 012/176] Updated on 2026-08-14 --- .../analytics/handlers/amplitude/AmplitudeAnalyticsHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5a03cc2f36..06f77c15b3 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 @@ -20,7 +20,7 @@ class AmplitudeAnalyticsHandler( class Builder : AnalyticsHandlerBuilder { override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when { !data.isDebug -> AmplitudeClient(data.application, data.config.amplitudeApiKey) - data.isDebug && data.logConfig.appsFlyer -> AmplitudeLogClient(data.jsonConverter) + data.isDebug && data.logConfig.amplitude -> AmplitudeLogClient(data.jsonConverter) else -> null }?.let { AmplitudeAnalyticsHandler(it) } } From 08b9e7f8f4fc8362f548cc4201bba699b7fcad72 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 31 Oct 2022 11:27:31 +0500 Subject: [PATCH 013/176] Updated on 2026-08-14 --- .../onboarding/products/note/OnboardingNoteFragment.kt | 7 ++++++- .../products/note/redux/OnboardingNoteMiddleware.kt | 1 - .../products/otherCards/OnboardingOtherCardsFragment.kt | 7 ++++++- .../otherCards/redux/OnboardingOtherCardsMiddleware.kt | 1 - .../onboarding/products/twins/redux/TwinCardsMiddleware.kt | 1 - .../onboarding/products/twins/ui/TwinsCardsFragment.kt | 3 +++ .../products/wallet/redux/OnboardingWalletMiddleware.kt | 1 - .../wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt | 3 --- .../products/wallet/saltPay/ui/OnboardingSaltPayView.kt | 4 ++++ .../products/wallet/ui/OnboardingWalletFragment.kt | 7 ++++++- 10 files changed, 25 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt index 865726f3dc..82d575fafe 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -9,6 +9,8 @@ import androidx.core.view.isVisible import androidx.transition.TransitionManager import coil.load import com.tangem.blockchain.common.Blockchain +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.navigation.ShareElement @@ -109,7 +111,10 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { private fun setupCreateWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) { btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet) - btnMainAction.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) } + btnMainAction.setOnClickListener { + Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) + store.dispatch(OnboardingNoteAction.CreateWallet) + } btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean) btnAlternativeAction.setOnClickListener { } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index f55bf9668a..6e1d907be1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -95,7 +95,6 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch } } is OnboardingNoteAction.CreateWallet -> { - Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) scope.launch { val result = tangemSdkManager.createProductWallet(scanResponse) withMainContext { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index 960062acab..f01eb6fbcd 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -8,6 +8,8 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.isVisible import androidx.transition.TransitionManager import coil.load +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.redux.navigation.ShareElement import com.tangem.tap.common.transitions.InternalNoteLayoutTransition @@ -69,7 +71,10 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment { - Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) scope.launch { val result = tangemSdkManager.createProductWallet(onboardingManager.scanResponse) withMainContext { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 9865fc9c06..a872bfef97 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -149,7 +149,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } } is TwinCardsAction.Wallet.LaunchFirstStep -> { - Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) val manager = TwinCardsManager( card = getScanResponse().card, assetReader = action.reader, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index d4c6826a3c..450e9ecd50 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -16,6 +16,8 @@ import com.tangem.common.extensions.VoidCallback import com.tangem.domain.common.TwinCardNumber import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.AndroidAssetReader +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.hide @@ -238,6 +240,7 @@ class TwinsCardsFragment : BaseOnboardingFragment() { btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber) btnMainAction.setOnClickListener { + Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) store.dispatch( TwinCardsAction.Wallet.LaunchFirstStep( Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)), diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index e468e440f5..dfe53282ed 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -110,7 +110,6 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } } is OnboardingWalletAction.CreateWallet -> { - Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) scope.launch { scanResponse ?: return@launch val result = tangemSdkManager.createProductWallet(scanResponse) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index f550b730ac..69f3e0a674 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -93,8 +93,6 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } is OnboardingSaltPayAction.RegisterCard -> { - Analytics.send(Onboarding.ButtonConnect()) - handleInProgress = true val state = getState() @@ -175,7 +173,6 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } is OnboardingSaltPayAction.Claim -> { - Analytics.send(Onboarding.ButtonClaim()) val state = getState() handleInProgress = true diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt index 90321f426a..dc934546aa 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt @@ -14,6 +14,8 @@ import coil.size.Scale import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.button.MaterialButton import com.tangem.common.extensions.guard +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.compose.PinCodeWidget import com.tangem.tap.common.extensions.configureSettings import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -178,6 +180,7 @@ internal class OnboardingSaltPayView( showOnlyView(connectCard.root) { progressButton = SaltPayProgressButton(connectCard.root) connectCard.btnConnect.setOnClickListener { + Analytics.send(Onboarding.ButtonConnect()) store.dispatch(OnboardingSaltPayAction.RegisterCard) } } @@ -286,6 +289,7 @@ internal class OnboardingSaltPayView( btnMain.text = getText(R.string.onboarding_button_claim) btnMain.isEnabled btnMain.setOnClickListener { + Analytics.send(Onboarding.ButtonClaim()) store.dispatch(OnboardingSaltPayAction.Claim) } progressButton = SaltPayProgressButton(actionContainer.root) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index e016d5fe8d..59b784620f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -19,6 +19,8 @@ import com.tangem.common.CardIdFormatter import com.tangem.common.core.CardIdDisplayFormat import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.PropertyCalculator +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.inflate import com.tangem.tap.common.extensions.show @@ -171,7 +173,10 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle private fun setupCreateWalletState() = with(binding) { layoutButtonsCommon.btnWalletMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet) - layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) } + layoutButtonsCommon.btnWalletMainAction.setOnClickListener { + Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet()) + store.dispatch(OnboardingWalletAction.CreateWallet) + } layoutButtonsCommon.btnWalletAlternativeAction.hide() toolbar.title = getText(R.string.onboarding_getting_started) From 03eeebd2806f7da4df18ad2374e114f2e42b0fba Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 31 Oct 2022 12:05:13 +0500 Subject: [PATCH 014/176] Updated on 2026-08-14 --- .../com/tangem/tap/features/home/HomeFragment.kt | 14 ++++++++++++-- .../tap/features/home/redux/HomeMiddleware.kt | 2 ++ .../features/wallet/ui/dialogs/ScanFailsDialog.kt | 3 +++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index 839c7a3828..313471aa5d 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -11,6 +11,8 @@ import androidx.compose.ui.platform.ComposeView import androidx.core.view.WindowInsetsControllerCompat import androidx.fragment.app.Fragment import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.home.compose.StoriesScreen @@ -27,6 +29,7 @@ class HomeFragment : Fragment(), StoreSubscriber { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Analytics.send(IntroductionProcess.ScreenOpened()) store.dispatch(HomeAction.Init) } @@ -78,9 +81,16 @@ class HomeFragment : Fragment(), StoreSubscriber { private fun ScreenContent() { StoriesScreen( homeState, - onScanButtonClick = { store.dispatch(HomeAction.ReadCard) }, - onShopButtonClick = { store.dispatch(HomeAction.GoToShop(store.state.globalState.userCountryCode)) }, + onScanButtonClick = { + Analytics.send(IntroductionProcess.ButtonScanCard()) + store.dispatch(HomeAction.ReadCard) + }, + onShopButtonClick = { + Analytics.send(IntroductionProcess.ButtonBuyCards()) + store.dispatch(HomeAction.GoToShop(store.state.globalState.userCountryCode)) + }, onSearchTokensClick = { + Analytics.send(IntroductionProcess.ButtonTokensList()) store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens)) store.dispatch(TokensAction.AllowToAddTokens(false)) store.dispatch(TokensAction.LoadCurrencies()) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 5a4e3d2ca2..460c05f0a2 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -14,6 +14,7 @@ import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.analytics.GetCardSourceParamsAnOld import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOpenUrl @@ -161,6 +162,7 @@ private fun showDisclaimerIfNeed(scanResponse: ScanResponse, nextHandler: (ScanR } private fun onScanSuccess(scanResponse: ScanResponse) { + Analytics.send(IntroductionProcess.CardWasScanned()) val globalState = store.state.globalState val tapWalletManager = globalState.tapWalletManager tapWalletManager.updateConfigManager(scanResponse) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index 91afcc3819..d3bc64fa17 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -2,6 +2,8 @@ package com.tangem.tap.features.wallet.ui.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.feedback.ScanFailsEmail import com.tangem.tap.common.redux.global.GlobalAction @@ -19,6 +21,7 @@ class ScanFailsDialog { setTitle(context.getString(R.string.common_warning)) setMessage(R.string.alert_troubleshooting_scan_card_title) setPositiveButton(R.string.alert_button_request_support) { _, _ -> + Analytics.send(IntroductionProcess.ButtonRequestSupport()) store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) } setNeutralButton(R.string.alert_troubleshooting_scan_card_ok) { _, _ -> } From 1d5360126656ad9d84ed65566c1b1af5181025c2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 31 Oct 2022 12:30:11 +0500 Subject: [PATCH 015/176] Updated on 2026-08-14 --- .../tap/common/extensions/WalletManager.kt | 5 ++--- .../note/redux/OnboardingNoteMiddleware.kt | 17 +++++++++++++++-- .../products/twins/redux/TwinCardsMiddleware.kt | 4 ++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index 15d02907bf..cda489f536 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -55,11 +55,10 @@ suspend fun WalletManager.safeUpdate(): Result = try { } } -fun WalletManager?.getToUpUrl(): String? { +fun WalletManager.getTopUpUrl(): String? { val globalState = store.state.globalState - val wallet = this?.wallet ?: return null - val defaultAddress = wallet.address + return globalState.exchangeManager.getUrl( action = CurrencyExchangeManager.Action.Buy, blockchain = wallet.blockchain, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 6e1d907be1..ef0c5a743a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -5,12 +5,14 @@ import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.getAddressData -import com.tangem.tap.common.extensions.getToUpUrl +import com.tangem.tap.common.extensions.getTopUpUrl import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppDialog @@ -85,6 +87,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch Analytics.send(Onboarding.CreateWallet.ScreenOpened()) } OnboardingNoteStep.TopUpWallet -> { + Analytics.send(Onboarding.Topup.ScreenOpened()) store.dispatch(OnboardingNoteAction.Balance.Update) } OnboardingNoteStep.Done -> { @@ -159,11 +162,21 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch is OnboardingNoteAction.ShowAddressInfoDialog -> { val addressData = noteState.walletManager?.getAddressData() ?: return + Analytics.send(Onboarding.Topup.ButtonShowWalletAddress()) val appDialog = AppDialog.AddressInfoDialog(noteState.walletBalance.currency, addressData) store.dispatchDialogShow(appDialog) } is OnboardingNoteAction.TopUp -> { - val topUpUrl = noteState.walletManager?.getToUpUrl() ?: return + val walletManager = noteState.walletManager.guard { + store.dispatchDebugErrorNotification("NPE: WalletManager") + return + } + + val blockchain = walletManager.wallet.blockchain + val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain) + Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType)) + + val topUpUrl = walletManager.getTopUpUrl() ?: return store.dispatchOpenUrl(topUpUrl) } OnboardingNoteAction.Done -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index a872bfef97..83514174a1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -12,7 +12,7 @@ import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.getAddressData -import com.tangem.tap.common.extensions.getToUpUrl +import com.tangem.tap.common.extensions.getTopUpUrl import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppDialog @@ -268,7 +268,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { store.dispatchDialogShow(appDialog) } is TwinCardsAction.TopUp -> { - val topUpUrl = twinCardsState.walletManager?.getToUpUrl() ?: return + val topUpUrl = twinCardsState.walletManager?.getTopUpUrl() ?: return store.dispatchOpenUrl(topUpUrl) } TwinCardsAction.Done -> { From 67ad64b128a598313bd884542de69ebdf545b0e7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 31 Oct 2022 12:44:29 +0500 Subject: [PATCH 016/176] Updated on 2026-08-14 --- .../products/twins/redux/TwinCardsMiddleware.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 83514174a1..bf3cb1296c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -139,8 +139,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } is TwinCardsAction.SetStepOfScreen -> { when (action.step) { - TwinCardsStep.WelcomeOnly -> preferencesStorage.saveTwinsOnboardingShown() - TwinCardsStep.Welcome -> preferencesStorage.saveTwinsOnboardingShown() + TwinCardsStep.WelcomeOnly, TwinCardsStep.Welcome -> { + Analytics.send(Onboarding.Twins.ScreenOpened()) + preferencesStorage.saveTwinsOnboardingShown() + } TwinCardsStep.TopUpWallet -> store.dispatch(TwinCardsAction.Balance.Update) TwinCardsStep.Done -> { finishCardActivation() @@ -149,6 +151,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } } is TwinCardsAction.Wallet.LaunchFirstStep -> { + Analytics.send(Onboarding.Twins.SetupStarted()) val manager = TwinCardsManager( card = getScanResponse().card, assetReader = action.reader, @@ -199,6 +202,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { scope.launch { when (val result = manager.complete(action.message)) { is Result.Success -> { + Analytics.send(Onboarding.Twins.SetupFinished()) updateScanResponse(result.data) delay(DELAY_SDK_DIALOG_CLOSE) withMainContext { From 8f61c0b19f91cc65edfbec1672c9dfaad46aaf1c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 31 Oct 2022 15:45:44 +0500 Subject: [PATCH 017/176] Updated on 2026-08-14 --- .../common/analytics/events/AnalyticsParam.kt | 9 ---- .../tap/common/analytics/events/Settings.kt | 6 ++- .../details/ui/details/DetailsFragment.kt | 4 +- .../details/ui/details/DetailsScreen.kt | 8 +-- .../details/ui/details/DetailsScreenState.kt | 50 +++++++++++-------- .../details/ui/details/DetailsViewModel.kt | 14 ++++-- .../walletconnect/WalletConnectViewModel.kt | 8 ++- 7 files changed, 59 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 8c69239429..cca03e068f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -41,15 +41,6 @@ sealed class AnalyticsParam { object LongTap : SecurityMode("Long Tap") } - sealed class SocialNetwork(val value: String) { - object Facebook : SocialNetwork("Facebook") - object Instagram : SocialNetwork("Instagram") - object Youtube : SocialNetwork("Youtube") - object Twitter : SocialNetwork("Twitter") - object LinkedIn : SocialNetwork("LinkedIn") - object GitHub : SocialNetwork("GitHub") - } - companion object Key { const val Blockchain = "blockchain" const val BatchId = "batch_id" diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index 56893d2f31..fff6794867 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.tap.features.details.ui.details.SocialNetwork + /** [REDACTED_AUTHOR] */ @@ -18,9 +20,9 @@ sealed class Settings( class ButtonAppSettings : Settings(event = "Button - App Settings") class ButtonCreateBackup : Settings(event = "Button - Create Backup") - sealed class ButtonSocialNetwork(network: AnalyticsParam.SocialNetwork) : Settings( + class ButtonSocialNetwork(network: SocialNetwork) : Settings( event = "Button - Social Network", - params = mapOf("Network" to network.value), + params = mapOf("Network" to network.id), ) sealed class CardSettings( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt index 6674371319..2c206736e5 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt @@ -10,6 +10,8 @@ import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store @@ -24,7 +26,7 @@ class DetailsFragment : Fragment(), StoreSubscriber { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + Analytics.send(Settings.ScreenOpened()) val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(android.R.transition.fade) exitTransition = inflater.inflateTransition(android.R.transition.fade) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt index e0b4f1f186..47946d1e73 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt @@ -162,8 +162,8 @@ fun DetailsItem( @Composable fun TangemSocialAccounts( - links: List, - onSocialNetworkClick: (String) -> Unit, + links: List, + onSocialNetworkClick: (SocialNetworkLink) -> Unit, modifier: Modifier = Modifier, ) { LazyRow( @@ -171,11 +171,11 @@ fun TangemSocialAccounts( ) { items(links) { Icon( - painter = painterResource(id = it.iconRes), + painter = painterResource(id = it.network.iconRes), contentDescription = "", modifier = modifier .padding(8.dp) - .clickable { onSocialNetworkClick(it.url) }, + .clickable { onSocialNetworkClick(it) }, tint = colorResource(id = R.color.icon_informative), ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index 1962a53261..a92efc7e2b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -4,11 +4,11 @@ import com.tangem.wallet.R data class DetailsScreenState( val elements: List, - val tangemLinks: List, + val tangemLinks: List, val tangemVersion: String, val appCurrency: String, val onItemsClick: (SettingsElement) -> Unit, - val onSocialNetworkClick: (String) -> Unit, + val onSocialNetworkClick: (SocialNetworkLink) -> Unit, ) { val appNameRes: Int = R.string.app_name } @@ -30,28 +30,38 @@ enum class SettingsElement( ; } -data class TangemLink( - val iconRes: Int, +data class SocialNetworkLink( + val network: SocialNetwork, val url: String, ) +sealed class SocialNetwork(val id: String, val iconRes: Int) { + object Telegram : SocialNetwork("Telegram", R.drawable.ic_telegram) + object Twitter : SocialNetwork("Twitter", R.drawable.ic_twitter) + object Facebook : SocialNetwork("Facebook", R.drawable.ic_facebook) + object Instagram : SocialNetwork("Instagram", R.drawable.ic_instagram) + object GitHub : SocialNetwork("GitHub", R.drawable.ic_github) + object Youtube : SocialNetwork("Youtube", R.drawable.ic_youtube) + object LinkedIn : SocialNetwork("LinkedIn", R.drawable.ic_linkedin) +} + object TangemSocialAccounts { - val accountsEn: List = listOf( - TangemLink(R.drawable.ic_telegram, "https://t.me/TangemCards"), - TangemLink(R.drawable.ic_twitter, "https://twitter.com/tangem"), - TangemLink(R.drawable.ic_facebook, "https://m.facebook.com/TangemCards/"), - TangemLink(R.drawable.ic_instagram, "https://instagram.com/tangemcards"), - TangemLink(R.drawable.ic_github, "https://github.com/tangem"), - TangemLink(R.drawable.ic_youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"), - TangemLink(R.drawable.ic_linkedin, "https://www.linkedin.com/company/tangem"), + val accountsEn: List = listOf( + SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/TangemCards"), + SocialNetworkLink(SocialNetwork.Twitter, "https://twitter.com/tangem"), + SocialNetworkLink(SocialNetwork.Facebook, "https://m.facebook.com/TangemCards/"), + SocialNetworkLink(SocialNetwork.Instagram, "https://instagram.com/tangemcards"), + SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"), + SocialNetworkLink(SocialNetwork.Youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"), + SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"), ) - val accountsRu: List = listOf( - TangemLink(R.drawable.ic_telegram, "https://t.me/tangem_ru"), - TangemLink(R.drawable.ic_twitter, "https://twitter.com/tangem"), - TangemLink(R.drawable.ic_facebook, "https://m.facebook.com/TangemCards/"), - TangemLink(R.drawable.ic_instagram, "https://instagram.com/tangemcards"), - TangemLink(R.drawable.ic_github, "https://github.com/tangem"), - TangemLink(R.drawable.ic_youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"), - TangemLink(R.drawable.ic_linkedin, "https://www.linkedin.com/company/tangem"), + val accountsRu: List = listOf( + SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/tangem_ru"), + SocialNetworkLink(SocialNetwork.Twitter, "https://twitter.com/tangem"), + SocialNetworkLink(SocialNetwork.Facebook, "https://m.facebook.com/TangemCards/"), + SocialNetworkLink(SocialNetwork.Instagram, "https://instagram.com/tangemcards"), + SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"), + SocialNetworkLink(SocialNetwork.Youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"), + SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"), ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 1de39fa063..c48aeab31d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -2,6 +2,8 @@ package com.tangem.tap.features.details.ui.details import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.feedback.FeedbackEmail import com.tangem.tap.common.feedback.SupportInfo import com.tangem.tap.common.redux.AppState @@ -51,8 +53,9 @@ class DetailsViewModel(private val store: Store) { ) } - private fun handleSocialNetworkClick(url: String) { - store.dispatch(NavigationAction.OpenUrl(url)) + private fun handleSocialNetworkClick(link: SocialNetworkLink) { + Analytics.send(Settings.ButtonSocialNetwork(link.network)) + store.dispatch(NavigationAction.OpenUrl(link.url)) } private fun handleClickingSettingsItem(item: SettingsElement) { @@ -61,21 +64,26 @@ class DetailsViewModel(private val store: Store) { store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions)) } SettingsElement.Chat -> { + Analytics.send(Settings.ButtonChat()) store.dispatch(GlobalAction.OpenChat(SupportInfo())) } SettingsElement.SendFeedback -> { + Analytics.send(Settings.ButtonSendFeedback()) store.dispatch(GlobalAction.SendEmail(FeedbackEmail())) } SettingsElement.CardSettings -> { + Analytics.send(Settings.ButtonCardSettings()) store.dispatch(NavigationAction.NavigateTo(AppScreen.CardSettings)) } SettingsElement.AppCurrency -> { store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) } SettingsElement.AppSettings -> { + Analytics.send(Settings.ButtonAppSettings()) store.dispatch(NavigationAction.NavigateTo(AppScreen.AppSettings)) //TODO: To be available later } SettingsElement.LinkMoreCards -> { + Analytics.send(Settings.ButtonCreateBackup()) store.dispatch(DetailsAction.CreateBackup) } SettingsElement.TermsOfService -> { @@ -90,7 +98,7 @@ class DetailsViewModel(private val store: Store) { } } - private fun getSocialLinks(): List { + private fun getSocialLinks(): List { val locale = LocaleRegionProvider().getRegion() return if (locale.lowercase() == RUSSIA_COUNTRY_CODE) { TangemSocialAccounts.accountsRu diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt index 6bbde4c1ae..9f4c8dd659 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt @@ -1,5 +1,7 @@ package com.tangem.tap.features.details.ui.walletconnect +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession @@ -12,7 +14,10 @@ class WalletConnectViewModel(private val store: Store) { state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) }, isLoading = state.loading, onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions) }, - onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) }, + onAddSession = { copiedUri -> + Analytics.send(Settings.ButtonStartWalletConnectSession()) + store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) + }, ) } @@ -20,6 +25,7 @@ class WalletConnectViewModel(private val store: Store) { sessions .firstOrNull { it.session.toUri() == sessionUri } ?.let { baseSession -> + Analytics.send(Settings.ButtonStopWalletConnectSession()) store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session)) } } From 1b2c087d0858b0c973bdac26963a9b85ae8f57fc Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 31 Oct 2022 15:49:34 +0500 Subject: [PATCH 018/176] Updated on 2026-08-14 --- .../ui/walletconnect/WalletConnectScreen.kt | 14 ++++++++++++-- .../ui/walletconnect/WalletConnectViewModel.kt | 8 +------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt index 168bd4a024..499f31ddcf 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -30,6 +30,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -53,7 +55,12 @@ fun WalletConnectScreen( }, fab = { if (!state.isLoading) { - AddSessionFab(onAddSession = { state.onAddSession(context.getFromClipboard()?.toString()) }) + AddSessionFab( + onAddSession = { + Analytics.send(Settings.ButtonStartWalletConnectSession()) + state.onAddSession(context.getFromClipboard()?.toString()) + }, + ) } }, titleRes = R.string.wallet_connect_title, @@ -147,7 +154,10 @@ private fun WalletConnectSessions( modifier = modifier.weight(1f), ) IconButton( - onClick = { state.onRemoveSession(session.sessionId) }, + onClick = { + Analytics.send(Settings.ButtonStopWalletConnectSession()) + state.onRemoveSession(session.sessionId) + }, ) { Icon( painter = painterResource(id = R.drawable.ic_cross_rounded_24), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt index 9f4c8dd659..6bbde4c1ae 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt @@ -1,7 +1,5 @@ package com.tangem.tap.features.details.ui.walletconnect -import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession @@ -14,10 +12,7 @@ class WalletConnectViewModel(private val store: Store) { state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) }, isLoading = state.loading, onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions) }, - onAddSession = { copiedUri -> - Analytics.send(Settings.ButtonStartWalletConnectSession()) - store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) - }, + onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) }, ) } @@ -25,7 +20,6 @@ class WalletConnectViewModel(private val store: Store) { sessions .firstOrNull { it.session.toUri() == sessionUri } ?.let { baseSession -> - Analytics.send(Settings.ButtonStopWalletConnectSession()) store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session)) } } From e4ff41dc503e7fc84792d8354e144456e8a8d364 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 31 Oct 2022 20:25:53 +0500 Subject: [PATCH 019/176] Updated on 2026-08-14 --- .../tap/common/analytics/events/AnalyticsParam.kt | 10 ++++++++++ .../tangem/tap/common/analytics/events/Settings.kt | 4 ++-- .../tap/features/details/redux/DetailsMiddleware.kt | 12 +++++++++++- .../details/ui/cardsettings/CardSettingsViewModel.kt | 6 ++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index cca03e068f..5e3f8a81c5 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.tap.features.details.redux.SecurityOption + sealed class AnalyticsParam { sealed class CurrencyType(val value: String) { @@ -39,6 +41,14 @@ sealed class AnalyticsParam { object AccessCode : SecurityMode("Access Code") object Passcode : SecurityMode("Passcode") object LongTap : SecurityMode("Long Tap") + + companion object { + fun from(option: SecurityOption): SecurityMode = when(option){ + SecurityOption.AccessCode -> AccessCode + SecurityOption.PassCode -> Passcode + SecurityOption.LongTap -> LongTap + } + } } companion object Key { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index fff6794867..b24d6ef042 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -35,12 +35,12 @@ sealed class Settings( class UserCodeChanged : CardSettings("User Code Changed") class ButtonChangeSecurityMode : CardSettings("Button - Change Security Mode") - sealed class ButtonChangeUserCode(type: AnalyticsParam.UserCode) : CardSettings( + class ButtonChangeUserCode(type: AnalyticsParam.UserCode) : CardSettings( event = "Button - Change User Code", params = mapOf("Type" to type.value), ) - sealed class SecurityModeChanged(mode: AnalyticsParam.SecurityMode) : CardSettings( + class SecurityModeChanged(mode: AnalyticsParam.SecurityMode) : CardSettings( event = "Security Mode Changed", params = mapOf("Mode" to mode.value), ) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 6df912853f..13caeece31 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -6,6 +6,8 @@ import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState @@ -107,6 +109,7 @@ class DetailsMiddleware { val result = tangemSdkManager.resetToFactorySettings(card) when (result) { is CompletionResult.Success -> { + Analytics.send(Settings.CardSettings.FactoryResetFinished()) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } is CompletionResult.Failure -> { @@ -133,6 +136,10 @@ class DetailsMiddleware { is DetailsAction.ManageSecurity.OpenSecurity -> { store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity)) } + is DetailsAction.ManageSecurity.SelectOption -> { + val modeParam = AnalyticsParam.SecurityMode.from(action.option) + Analytics.send(Settings.CardSettings.SecurityModeChanged(modeParam)) + } is DetailsAction.ManageSecurity.SaveChanges -> { val cardId = store.state.detailsState.scanResponse?.card?.cardId val selectedOption = @@ -176,7 +183,10 @@ class DetailsMiddleware { is DetailsAction.ManageSecurity.ChangeAccessCode -> { val card = store.state.detailsState.cardSettingsState?.card ?: return scope.launch { - tangemSdkManager.setAccessCode(card.cardId) + when(tangemSdkManager.setAccessCode(card.cardId)){ + is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged()) + is CompletionResult.Failure -> {} + } } } else -> { /* no-op */ diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index b238ffe8f2..44f5cdb8d2 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -2,6 +2,9 @@ package com.tangem.tap.features.details.ui.cardsettings import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.getTwinCardIdForUser +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction @@ -59,12 +62,15 @@ class CardSettingsViewModel(private val store: Store) { private fun handleClickingItem(item: CardInfo) { when (item) { is CardInfo.ChangeAccessCode -> { + Analytics.send(Settings.CardSettings.ButtonChangeUserCode(AnalyticsParam.UserCode.AccessCode)) store.dispatch(DetailsAction.ManageSecurity.ChangeAccessCode) } is CardInfo.ResetToFactorySettings -> { + Analytics.send(Settings.CardSettings.ButtonFactoryReset()) store.dispatch(DetailsAction.ResetToFactory.Start) } is CardInfo.SecurityMode -> { + Analytics.send(Settings.CardSettings.ButtonChangeSecurityMode()) store.dispatch(DetailsAction.ManageSecurity.OpenSecurity) } else -> {} From d7b674ea892ebbf31114df58d944ee1c723b86c6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 14:07:13 +0500 Subject: [PATCH 020/176] Updated on 2026-08-14 --- .../common/analytics/events/AnalyticsParam.kt | 4 +- .../common/analytics/events/ManageTokens.kt | 46 ++++++++-- .../addCustomToken/AddCustomTokenFragment.kt | 28 +++--- .../features/tokens/redux/TokensMiddleware.kt | 4 + .../features/tokens/ui/AddTokensFragment.kt | 54 ++++++----- .../tokens/ui/compose/CurrenciesScreen.kt | 91 +++++++++++++------ 6 files changed, 159 insertions(+), 68 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 5e3f8a81c5..9996f479b7 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -10,7 +10,7 @@ sealed class AnalyticsParam { class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol) } - // Multicurrency or CurrencyType + // MultiCurrency or CurrencyType sealed class CardCurrency(val value: String) { object MultiCurrency : CardCurrency("Multicurrency") class SingleCurrency(type: CurrencyType) : CardCurrency(type.value) @@ -43,7 +43,7 @@ sealed class AnalyticsParam { object LongTap : SecurityMode("Long Tap") companion object { - fun from(option: SecurityOption): SecurityMode = when(option){ + fun from(option: SecurityOption): SecurityMode = when (option) { SecurityOption.AccessCode -> AccessCode SecurityOption.PassCode -> Passcode SecurityOption.LongTap -> LongTap diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt index e5e3fdb96a..9f3ec194b7 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt @@ -1,5 +1,9 @@ package com.tangem.tap.common.analytics.events +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.domain.features.addCustomToken.CustomCurrency +import com.tangem.tap.common.extensions.filterNotNull + /** [REDACTED_AUTHOR] */ @@ -10,7 +14,18 @@ sealed class ManageTokens( class ScreenOpened : ManageTokens("Manage Tokens Screen Opened") class TokenSearched : ManageTokens("Token Searched") - class TokenSwitcherChanged : ManageTokens("Token Switcher Changed") + + class TokenSwitcherChanged( + type: AnalyticsParam.CurrencyType, + state: AnalyticsParam.OnOffState, + ) : ManageTokens( + "Token Switcher Changed", + params = mapOf( + "Token" to type.value, + "State" to state.value, + ), + ) + class ButtonSaveChanges : ManageTokens("Button - Save Changes") class ButtonCustomToken : ManageTokens("Button - Custom Token") @@ -21,13 +36,28 @@ sealed class ManageTokens( class ScreenOpened : ManageTokens("Custom Token Screen Opened") - class TokenWasAdded(symbol: String, network: String, address: String) : ManageTokens( + class TokenWasAdded(customCurrency: CustomCurrency) : ManageTokens( event = "Custom Token Was Added", - params = mapOf( - "Symbol" to symbol, - "Network" to network, - "Address" to address, - ), - ) + params = convertToParam(customCurrency), + ) { + companion object { + private fun convertToParam(customCurrency: CustomCurrency): Map = with(customCurrency) { + return when (this) { + is CustomCurrency.CustomBlockchain -> mapOf( + "Network" to network.toNetworkId(), + "Symbol" to network.currency, + "DerivationPath" to derivationPath?.rawPath, + ).filterNotNull() + is CustomCurrency.CustomToken -> mapOf( + "Network" to network.toNetworkId(), + "Symbol" to token.symbol, + "DerivationPath" to derivationPath?.rawPath, + "Token" to token.id, + "Address" to token.contractAddress, + ).filterNotNull() + } + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt index 2192fd0477..3954882be5 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/AddCustomTokenFragment.kt @@ -13,6 +13,8 @@ import androidx.compose.ui.platform.ComposeView import com.google.accompanist.appcompattheme.AppCompatTheme import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState import com.tangem.domain.redux.domainStore +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.compose.ClosePopupTrigger import com.tangem.tap.features.BaseStoreFragment import com.tangem.tap.features.FragmentOnBackPressedHandler @@ -28,6 +30,11 @@ class AddCustomTokenFragment : BaseStoreFragment(R.layout.view_compose_fragment) private var state: MutableState = mutableStateOf(domainStore.state.addCustomTokensState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Analytics.send(ManageTokens.CustomToken.ScreenOpened()) + } + override fun subscribeToStore() { domainStore.subscribe(this) { state -> state.skipRepeats { oldState, newState -> @@ -45,30 +52,29 @@ class AddCustomTokenFragment : BaseStoreFragment(R.layout.view_compose_fragment) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - view.findViewById(R.id.toolbar)?.let { - it.setTitle(R.string.add_custom_token_title) - } + requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + view.findViewById(R.id.toolbar)?.setTitle(R.string.add_custom_token_title) val closePopupTrigger = initClosingPopupTriggerEvent() view.findViewById(R.id.view_compose)?.setContent { AppCompatTheme(requireContext()) { - Box(modifier = Modifier - .fillMaxSize() + Box( + modifier = Modifier + .fillMaxSize(), ) { AddCustomTokenScreen(state, closePopupTrigger) } } } - - } private fun initClosingPopupTriggerEvent(): ClosePopupTrigger = ClosePopupTrigger().apply { onCloseComplete = ::handleOnBackPressed - addBackPressHandler(object : FragmentOnBackPressedHandler { - override fun handleOnBackPressed() = close() - }) + addBackPressHandler( + object : FragmentOnBackPressedHandler { + override fun handleOnBackPressed() = close() + }, + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 9817b4db49..7bf90e4bfd 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -21,6 +21,8 @@ import com.tangem.domain.redux.domainStore import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.assetReader +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState @@ -52,6 +54,7 @@ class TokensMiddleware { handleAddingCustomToken(action) } is TokensAction.SetSearchInput -> { + Analytics.send(ManageTokens.TokenSearched()) handleLoadCurrencies( scanResponse = store.state.globalState.scanResponse, newSearchInput = action.searchInput @@ -345,6 +348,7 @@ class TokensMiddleware { store.dispatchOnMain(NavigationAction.PopBackTo()) } + Analytics.send(ManageTokens.CustomToken.TokenWasAdded(customCurrency)) val currency = Currency.fromCustomCurrency(customCurrency) val isNeedToDerive = isNeedToDerive(scanResponse, currency) val currencyList = listOf(currency) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 9a9052115c..36b1f82337 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -15,6 +15,8 @@ import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import com.google.accompanist.appcompattheme.AppCompatTheme import com.tangem.blockchain.common.Blockchain +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.extensions.dispatchOnMain @@ -48,12 +50,17 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(TokensAction.ResetState) - } - }) + Analytics.send(ManageTokens.ScreenOpened()) + + activity?.onBackPressedDispatcher?.addCallback( + this, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(NavigationAction.PopBackTo()) + store.dispatch(TokensAction.ResetState) + } + }, + ) val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.slide_right) exitTransition = inflater.inflateTransition(R.transition.fade) @@ -80,6 +87,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), toolbar.setNavigationOnClickListener { activity?.onBackPressed() } val onSaveChanges = { tokens: List, blockchains: List -> + Analytics.send(ManageTokens.ButtonSaveChanges()) store.dispatch(TokensAction.SaveChanges(tokens, blockchains)) } val onNetworkItemClicked = { contractAddress: ContractAddress -> @@ -88,11 +96,11 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), } val onLoadMore = { - store.dispatch( - TokensAction.LoadMore( - scanResponse = store.state.globalState.scanResponse - ) - ) + store.dispatch( + TokensAction.LoadMore( + scanResponse = store.state.globalState.scanResponse, + ), + ) } cvCurrencies.setContent { @@ -101,7 +109,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), tokensState = tokensState, onSaveChanges = onSaveChanges, onNetworkItemClicked = onNetworkItemClicked, - onLoadMore = onLoadMore + onLoadMore = onLoadMore, ) } } @@ -119,6 +127,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), return when (item.itemId) { R.id.menu_search -> true R.id.menu_navigate_add_custom_token -> { + Analytics.send(ManageTokens.ButtonCustomToken()) store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken) true } @@ -154,15 +163,18 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), } fun SearchView.inputtedTextAsFlow(): Flow = callbackFlow { - val watcher = setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String?): Boolean { - return false - } + val watcher = + setOnQueryTextListener( + object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + return false + } - override fun onQueryTextChange(newText: String?): Boolean { - trySend(newText ?: "") - return false - } - }) + override fun onQueryTextChange(newText: String?): Boolean { + trySend(newText ?: "") + return false + } + }, + ) awaitClose { (watcher) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 49886cbf86..7cf23fb44c 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -8,8 +8,16 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material.* -import androidx.compose.runtime.* +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.ExtendedFloatingActionButton +import androidx.compose.material.FabPosition +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -20,6 +28,9 @@ import androidx.compose.ui.unit.dp import com.tangem.blockchain.common.Blockchain import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.compose.Keyboard import com.tangem.tap.common.compose.extensions.addAndNotify import com.tangem.tap.common.compose.extensions.removeAndNotify @@ -41,7 +52,7 @@ fun CurrenciesScreen( tokensState: MutableState = mutableStateOf(store.state.tokensState), onSaveChanges: (List, List) -> Unit, onNetworkItemClicked: (ContractAddress) -> Unit, - onLoadMore: () -> Unit + onLoadMore: () -> Unit, ) { val tokensAddedOnMainScreen = remember { tokensState.value.addedTokens } val blockchainsAddedOnMainScreen = remember { tokensState.value.addedBlockchains } @@ -82,21 +93,21 @@ fun CurrenciesScreen( AnimatedVisibility( visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADING, enter = fadeIn(), - exit = fadeOut() + exit = fadeOut(), ) { Box( contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { CircularProgressIndicator( - color = Color(0xFF1ACE80) + color = Color(0xFF1ACE80), ) } } AnimatedVisibility( visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADED, enter = fadeIn(animationSpec = tween(1000)), - exit = fadeOut(animationSpec = tween(1000)) + exit = fadeOut(animationSpec = tween(1000)), ) { Column { val showHeader = tokensState.value.scanResponse?.card?.useOldStyleDerivation == true @@ -110,7 +121,7 @@ fun CurrenciesScreen( onAddCurrencyToggleClick(currency, token) }, onNetworkItemClicked = onNetworkItemClicked, - onLoadMore = onLoadMore + onLoadMore = onLoadMore, ) } } @@ -122,29 +133,39 @@ private fun toggleBlockchain( blockchain: Blockchain, blockchainsAddedOnMainScreen: List, addedBlockchainsState: MutableState>, - addedTokens: List + addedTokens: List, ) { val isTryingToRemove = addedBlockchainsState.value.contains(blockchain) val isAddedOnMainScreen = blockchainsAddedOnMainScreen.contains(blockchain) val isTokenWithSameBlockchainFound = addedTokens.any { it.blockchain == blockchain } + val analyticsCurrencyTypeParam = AnalyticsParam.CurrencyType.Blockchain(blockchain) if (isTryingToRemove) { if (isTokenWithSameBlockchainFound) { - store.dispatchDialogShow(WalletDialog.TokensAreLinkedDialog( - currencyTitle = blockchain.name, - currencySymbol = blockchain.currency - )) + store.dispatchDialogShow( + WalletDialog.TokensAreLinkedDialog( + currencyTitle = blockchain.name, + currencySymbol = blockchain.currency, + ), + ) } else { if (isAddedOnMainScreen) { - store.dispatchDialogShow(WalletDialog.RemoveWalletDialog( - currencyTitle = blockchain.name, - onOk = { addedBlockchainsState.removeAndNotify(blockchain) } - )) + store.dispatchDialogShow( + WalletDialog.RemoveWalletDialog( + currencyTitle = blockchain.name, + onOk = { + analyticsCurrencyTypeParam.sendOn() + addedBlockchainsState.removeAndNotify(blockchain) + }, + ), + ) } else { + analyticsCurrencyTypeParam.sendOff() addedBlockchainsState.removeAndNotify(blockchain) } } } else { + analyticsCurrencyTypeParam.sendOn() addedBlockchainsState.addAndNotify(blockchain) } } @@ -158,28 +179,46 @@ private fun toggleToken( val isTryingToRemove = addedTokensState.value.contains(token) val isAddedOnMainScreen = tokensAddedOnMainScreen.contains(token) val isUnsupportedToken = !tokensState.canHandleToken(token) + val analyticsCurrencyTypeParam = AnalyticsParam.CurrencyType.Token(token.token) if (isTryingToRemove) { if (isAddedOnMainScreen) { - store.dispatchDialogShow(WalletDialog.RemoveWalletDialog( - currencyTitle = token.token.name, - onOk = { addedTokensState.removeAndNotify(token) } - )) + store.dispatchDialogShow( + WalletDialog.RemoveWalletDialog( + currencyTitle = token.token.name, + onOk = { + analyticsCurrencyTypeParam.sendOff() + addedTokensState.removeAndNotify(token) + }, + ), + ) } else { + analyticsCurrencyTypeParam.sendOff() addedTokensState.removeAndNotify(token) } } else { if (isUnsupportedToken) { - store.dispatchDialogShow(AppDialog.SimpleOkDialogRes( - headerId = R.string.common_warning, - messageId = R.string.alert_manage_tokens_unsupported_message, - )) + store.dispatchDialogShow( + AppDialog.SimpleOkDialogRes( + headerId = R.string.common_warning, + messageId = R.string.alert_manage_tokens_unsupported_message, + ), + ) } else { + analyticsCurrencyTypeParam.sendOn() addedTokensState.addAndNotify(token) } } } +private fun AnalyticsParam.CurrencyType.sendOn() { + Analytics.send(ManageTokens.TokenSwitcherChanged(this, AnalyticsParam.OnOffState.On)) +} + +private fun AnalyticsParam.CurrencyType.sendOff() { + Analytics.send(ManageTokens.TokenSwitcherChanged(this, AnalyticsParam.OnOffState.Off)) +} + @Composable fun SaveChangesButton(keyboardState: Keyboard, onSaveChanges: () -> Unit) { @@ -198,6 +237,6 @@ fun SaveChangesButton(keyboardState: Keyboard, onSaveChanges: () -> Unit) { onClick = onSaveChanges, backgroundColor = colorResource(id = R.color.accent), contentColor = Color.White, - modifier = Modifier.padding(bottom = padding.dp) + modifier = Modifier.padding(bottom = padding.dp), ) } \ No newline at end of file From 7aa118cec40934fbf46e8bc58427e569d6df7d0f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 14:21:12 +0500 Subject: [PATCH 021/176] Updated on 2026-08-14 --- .../tangem/tap/common/analytics/events/ManageTokens.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt index 9f3ec194b7..77cfc78626 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt @@ -44,15 +44,13 @@ sealed class ManageTokens( private fun convertToParam(customCurrency: CustomCurrency): Map = with(customCurrency) { return when (this) { is CustomCurrency.CustomBlockchain -> mapOf( - "Network" to network.toNetworkId(), - "Symbol" to network.currency, + "Token" to network.currency, "DerivationPath" to derivationPath?.rawPath, ).filterNotNull() is CustomCurrency.CustomToken -> mapOf( - "Network" to network.toNetworkId(), - "Symbol" to token.symbol, + "Token" to token.symbol, "DerivationPath" to derivationPath?.rawPath, - "Token" to token.id, + "NetworkId" to network.toNetworkId(), "Address" to token.contractAddress, ).filterNotNull() } From a63f4797fe4cd999b236f0d902227e069956c1f2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 14:29:00 +0500 Subject: [PATCH 022/176] Updated on 2026-08-14 --- .../tangem/tap/features/details/redux/DetailsMiddleware.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 13caeece31..23edb2035c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -136,10 +136,6 @@ class DetailsMiddleware { is DetailsAction.ManageSecurity.OpenSecurity -> { store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity)) } - is DetailsAction.ManageSecurity.SelectOption -> { - val modeParam = AnalyticsParam.SecurityMode.from(action.option) - Analytics.send(Settings.CardSettings.SecurityModeChanged(modeParam)) - } is DetailsAction.ManageSecurity.SaveChanges -> { val cardId = store.state.detailsState.scanResponse?.card?.cardId val selectedOption = @@ -155,6 +151,8 @@ class DetailsMiddleware { when (result) { is CompletionResult.Success -> { selectedOption?.let { + val paramValue = AnalyticsParam.SecurityMode.from(it) + Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue)) store.dispatch(GlobalAction.UpdateSecurityOptions(it)) } store.dispatch(NavigationAction.PopBackTo()) From 37648e0d92c35212ae385e880401c50b2c5e6b2e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 17:02:08 +0500 Subject: [PATCH 023/176] Updated on 2026-08-14 --- .../tangem/tap/common/analytics/events/ManageTokens.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt index 77cfc78626..f944f58d24 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt @@ -45,13 +45,13 @@ sealed class ManageTokens( return when (this) { is CustomCurrency.CustomBlockchain -> mapOf( "Token" to network.currency, - "DerivationPath" to derivationPath?.rawPath, + "Derivation Path" to derivationPath?.rawPath, ).filterNotNull() is CustomCurrency.CustomToken -> mapOf( "Token" to token.symbol, - "DerivationPath" to derivationPath?.rawPath, - "NetworkId" to network.toNetworkId(), - "Address" to token.contractAddress, + "Derivation Path" to derivationPath?.rawPath, + "Network Id" to network.toNetworkId(), + "Contract Address" to token.contractAddress, ).filterNotNull() } } From 8a4a0d77e9bc4b97b05eab4ea973a873618d9e23 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 17:10:55 +0500 Subject: [PATCH 024/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/common/extensions/UI.kt | 3 ++- .../tap/features/wallet/ui/WalletFragment.kt | 3 +++ .../features/wallet/ui/adapters/WalletAdapter.kt | 14 ++++++++------ .../features/wallet/ui/wallet/MultiWalletView.kt | 3 +++ 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt index 4be902ebf7..9e58542ecb 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt @@ -19,6 +19,7 @@ import android.view.inputmethod.InputMethodManager import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.annotation.DrawableRes +import androidx.annotation.PluralsRes import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -57,7 +58,7 @@ fun View.getString(@StringRes id: Int, vararg formatArgs: String): String { return context.getString(id, *formatArgs) } -fun View.getQuantityString(@StringRes id: Int, quantity: Int): String { +fun View.getQuantityString(@PluralsRes id: Int, quantity: Int): String { return context.resources.getQuantityString(id, quantity, quantity) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 87a31563fa..a3d82b0de2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -16,6 +16,8 @@ import coil.load import coil.size.Scale import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Portfolio import com.tangem.tap.common.extensions.show import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.global.GlobalAction @@ -152,6 +154,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber(DiffUtilCallback) { +class WalletAdapter : ListAdapter(DiffUtilCallback) { override fun getItemId(position: Int): Long { return currentList[position].currencyData.currencySymbol?.hashCode()?.toLong() ?: 0 @@ -30,7 +31,7 @@ class WalletAdapter fun submitList( list: List, primaryBlockchain: Blockchain?, - primaryToken: Token? = null + primaryToken: Token? = null, ) { // We used this method to sort the list of currencies. Sorting is disabled for now. super.submitList(list) @@ -38,7 +39,7 @@ class WalletAdapter override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder { val layout = ItemCurrencyWalletBinding.inflate( - LayoutInflater.from(parent.context), parent, false + LayoutInflater.from(parent.context), parent, false, ) return WalletsViewHolder(layout) } @@ -49,11 +50,11 @@ class WalletAdapter object DiffUtilCallback : DiffUtil.ItemCallback() { override fun areContentsTheSame( - oldItem: WalletData, newItem: WalletData + oldItem: WalletData, newItem: WalletData, ) = oldItem == newItem override fun areItemsTheSame( - oldItem: WalletData, newItem: WalletData + oldItem: WalletData, newItem: WalletData, ) = oldItem == newItem } @@ -112,6 +113,7 @@ class WalletAdapter if (wallet.walletAddresses != null) { cardWallet.setOnClickListener { + Analytics.send(Portfolio.TokenTapped()) store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) } } else { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 9d4fe68894..891dba9126 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -6,6 +6,8 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.common.card.Card import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.isTestCard +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Portfolio import com.tangem.tap.common.extensions.animateVisibility import com.tangem.tap.common.extensions.formatAmountAsSpannedString import com.tangem.tap.common.extensions.getQuantityString @@ -85,6 +87,7 @@ class MultiWalletView : WalletView() { binding.btnAddToken.setOnClickListener { val card = store.state.globalState.scanResponse!!.card + Analytics.send(Portfolio.ButtonManageTokens()) store.dispatch( TokensAction.LoadCurrencies( supportedBlockchains = CurrenciesRepository.getBlockchains( From d57d48e68164f8c37ceade0e1ebee386421b3bb9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 18:03:12 +0500 Subject: [PATCH 025/176] Updated on 2026-08-14 --- .../tangem/tap/common/analytics/events/AnalyticsParam.kt | 1 + .../com/tangem/tap/common/analytics/events/MainScreen.kt | 2 +- .../wallet/redux/middlewares/AppCurrencyMiddleware.kt | 4 ++++ .../com/tangem/tap/features/wallet/ui/WalletFragment.kt | 6 ++++-- .../features/wallet/ui/adapters/WarningMessagesAdapter.kt | 5 +++++ .../tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt | 3 +++ 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 9996f479b7..17b2a3484b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -8,6 +8,7 @@ sealed class AnalyticsParam { class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol) class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency) class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol) + class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency): CurrencyType(fiatCurrency.symbol) } // MultiCurrency or CurrencyType diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt index c6825718b7..e19202bf4f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt @@ -19,7 +19,7 @@ sealed class MainScreen( params = mapOf("Currency Type" to currencyType.value), ) - sealed class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen( + class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen( event = "Notice - Rate The App Button Tapped", params = mapOf("Result" to result.value), ) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index f1fb6371cc..40613f791e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -3,6 +3,9 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.tangem.common.services.Result import com.tangem.network.api.tangemTech.CurrenciesResponse import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.redux.global.GlobalAction @@ -61,6 +64,7 @@ class AppCurrencyMiddleware( } private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) { + Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(action.fiatCurrency))) tapWalletManager.rates.clear() fiatCurrenciesPrefStorage.saveAppCurrency(action.fiatCurrency) store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index a3d82b0de2..97fba31222 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -17,6 +17,7 @@ import coil.size.Scale import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Portfolio import com.tangem.tap.common.extensions.show import com.tangem.tap.common.recyclerView.SpaceItemDecoration @@ -54,6 +55,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber Date: Tue, 1 Nov 2022 19:14:20 +0500 Subject: [PATCH 026/176] Updated on 2026-08-14 --- .../tap/common/analytics/events/Token.kt | 32 +++++++++-- .../middlewares/MultiWalletMiddleware.kt | 2 + .../middlewares/TradeCryptoMiddleware.kt | 39 ++++++++----- .../middlewares/WalletDialogsMiddleware.kt | 12 +++- .../redux/middlewares/WalletMiddleware.kt | 2 + .../wallet/ui/WalletDetailsFragment.kt | 56 +++++++++++-------- 6 files changed, 98 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index afe5da529a..b0c9a0db1d 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.analytics.events +import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType + /** [REDACTED_AUTHOR] */ @@ -12,10 +14,30 @@ sealed class Token( class Refreshed : Token("Token", "Refreshed") class ButtonRemoveToken : Token("Token", "Button - Remove Token") class ButtonExplore : Token("Token", "Button - Explore") - class ButtonBuy(token: String) : Token("Token", "Button - Buy", mapOf("Token" to token)) - class ButtonSell(token: String) : Token("Token", "Button - Sell", mapOf("Token" to token)) - class ButtonExchange(token: String) : Token("Token", "Button - Exchange", mapOf("Token" to token)) - class ButtonSend(token: String) : Token("Token", "Button - Send", mapOf("Token" to token)) + + class ButtonBuy(type: CurrencyType) : Token( + category = "Token", + event = "Button - Buy", + params = mapOf("Token" to type.value), + ) + + class ButtonSell(type: CurrencyType) : Token( + category = "Token", + event = "Button - Sell", + params = mapOf("Token" to type.value), + ) + + class ButtonExchange(type: CurrencyType) : Token( + category = "Token", + event = "Button - Exchange", + params = mapOf("Token" to type.value), + ) + + class ButtonSend(type: CurrencyType) : Token( + category = "Token", + event = "Button - Send", + params = mapOf("Token" to type.value), + ) sealed class Recieve( event: String, @@ -37,7 +59,7 @@ sealed class Token( class ButtonQRCode : Send("Button - QR Code") class ButtonSwapCurrency : Send("Button - Swap Currency") - class TransactionSent(token: AnalyticsParam.CurrencyType) : Send( + class TransactionSent(token: CurrencyType) : Send( event = "Transaction Sent", params = mapOf("Token" to token.value), ) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index ba68023190..3433d447d5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.safeUpdate @@ -109,6 +110,7 @@ class MultiWalletMiddleware { WalletDialog.RemoveWalletDialog( currencyTitle = currency.currencyName, onOk = { + Analytics.send(com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken()) store.dispatch( WalletAction.MultiWallet.RemoveWallet( currency = currency, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 319c940a52..182c75c9bd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -2,6 +2,9 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.AmountType +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState @@ -19,7 +22,6 @@ import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.launch - class TradeCryptoMiddleware { fun handle(state: () -> AppState?, action: WalletAction.TradeCryptoAction) { if (DemoHelper.tryHandle(state, action)) return @@ -50,6 +52,7 @@ class TradeCryptoMiddleware { val exchangeManager = store.state.globalState.exchangeManager val appCurrency = store.state.globalState.appCurrency val currency = selectedWalletData.currency + Analytics.send(Token.ButtonBuy(AnalyticsParam.CurrencyType.Currency(currency))) if (currency is Currency.Token && currency.blockchain.isTestnet()) { val walletManager = store.state.walletState.getWalletManager(currency) @@ -62,7 +65,7 @@ class TradeCryptoMiddleware { exchangeManager.buyErc20TestnetTokens( card = card, walletManager = walletManager, - token = currency.token + token = currency.token, ) } return @@ -73,7 +76,7 @@ class TradeCryptoMiddleware { blockchain = currency.blockchain, cryptoCurrencyName = currency.currencySymbol, fiatCurrencyName = appCurrency.code, - walletAddress = addresses[0].address + walletAddress = addresses[0].address, )?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) } } @@ -85,29 +88,37 @@ class TradeCryptoMiddleware { if (addresses.isEmpty()) return val currency = selectedWalletData.currency + Analytics.send(Token.ButtonSell(AnalyticsParam.CurrencyType.Currency(currency))) + store.state.globalState.exchangeManager.getUrl( action = CurrencyExchangeManager.Action.Sell, blockchain = currency.blockchain, cryptoCurrencyName = currency.currencySymbol, fiatCurrencyName = appCurrency.code, - walletAddress = addresses[0].address + walletAddress = addresses[0].address, )?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) } } private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency))) + val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency) - store.dispatchOnMain(PrepareSendScreen( - coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin), - coinRate = selectedWalletData.fiatRate, - walletManager = walletManager - )) - store.dispatchOnMain(SendAction.SendSpecificTransaction( - sendAmount = action.amount, - destinationAddress = action.destinationAddress, - transactionId = action.transactionId - )) + store.dispatchOnMain( + PrepareSendScreen( + coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin), + coinRate = selectedWalletData.fiatRate, + walletManager = walletManager, + ), + ) + store.dispatchOnMain( + SendAction.SendSpecificTransaction( + sendAmount = action.amount, + destinationAddress = action.destinationAddress, + transactionId = action.transactionId, + ), + ) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Send)) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index d53809be9c..64d2122fa2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -1,5 +1,8 @@ package com.tangem.tap.features.wallet.redux.middlewares +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.redux.AppDialog @@ -14,6 +17,9 @@ class WalletDialogsMiddleware { store.dispatchDialogShow(WalletDialog.SignedHashesMultiWalletDialog) } is WalletAction.DialogAction.ChooseTradeActionDialog -> { + store.state.walletState.getSelectedWalletData()?.let { + Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency))) + } store.dispatchDialogShow(WalletDialog.ChooseTradeActionDialog) } is WalletAction.DialogAction.QrCode -> { @@ -21,14 +27,14 @@ class WalletDialogsMiddleware { AppDialog.AddressInfoDialog( currency = action.currency, addressData = action.selectedAddress, - ) + ), ) } is WalletAction.DialogAction.ChooseCurrency -> { store.dispatchDialogShow( WalletDialog.SelectAmountToSendDialog( - amounts = action.amounts - ) + amounts = action.amounts, + ), ) } is WalletAction.DialogAction.RussianCardholdersWarningDialog -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 157c8786b4..a4f353a6f7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -13,6 +13,7 @@ import com.tangem.operations.attestation.Attestation import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld +import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain @@ -247,6 +248,7 @@ class WalletMiddleware { action.context.shareText(action.address) } is WalletAction.ExploreAddress -> { + Analytics.send(Token.ButtonExplore()) store.dispatchOpenUrl(action.exploreUrl) } is WalletAction.Send -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 281d4b3938..9d3dfca291 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -19,6 +19,9 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.TestActions +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.DetailsScreen +import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.appendIfNotNull import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.fitChipsByGroupWidth @@ -59,12 +62,17 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) - activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(WalletAction.MultiWallet.SelectWallet(null)) - store.dispatch(NavigationAction.PopBackTo()) - } - }) + + Analytics.send(DetailsScreen.ScreenOpened()) + activity?.onBackPressedDispatcher?.addCallback( + this, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + store.dispatch(WalletAction.MultiWallet.SelectWallet(null)) + store.dispatch(NavigationAction.PopBackTo()) + } + }, + ) val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.slide_right) exitTransition = inflater.inflateTransition(R.transition.fade) @@ -125,7 +133,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), view?.findViewById(R.id.l_balance)?.let { view -> TestActions.initFor( view = view, - actions = TestWallet.solanaRentExemptWarning() + actions = TestWallet.solanaRentExemptWarning(), ) } } @@ -149,13 +157,15 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), binding.srlWalletDetails.setOnRefreshListener { if (selectedWallet.currencyData.status != BalanceStatus.Loading) { - store.dispatch(WalletAction.LoadWallet( - blockchain = BlockchainNetwork( - selectedWallet.currency.blockchain, - selectedWallet.currency.derivationPath, - emptyList() - ) - ) + Analytics.send(Token.Refreshed()) + store.dispatch( + WalletAction.LoadWallet( + blockchain = BlockchainNetwork( + selectedWallet.currency.blockchain, + selectedWallet.currency.derivationPath, + emptyList(), + ), + ), ) } } @@ -183,7 +193,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), tvCurrencyTitle.text = currencyData.currency tvCurrencySubtitle.text = tvCurrencySubtitle.getString( R.string.wallet_currency_subtitle, - currency.blockchain.fullName + currency.blockchain.fullName, ) } @@ -225,7 +235,6 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), ) } - private fun showPendingTransactionsIfPresent(pendingTransactions: List) { pendingTransactionAdapter.submitList(pendingTransactions) binding.rvPendingTransaction.show(pendingTransactions.isNotEmpty()) @@ -256,8 +265,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), store.dispatch( WalletAction.ExploreAddress( state.walletAddresses.selectedAddress.exploreUrl, - requireContext() - ) + requireContext(), + ), ) } ivQrCode.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode()) @@ -272,7 +281,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), binding.srlWalletDetails.isRefreshing = false (activity as? SnackbarHandler)?.showSnackbar( text = R.string.wallet_notification_no_internet, - buttonTitle = R.string.common_retry + buttonTitle = R.string.common_retry, ) { store.dispatch(WalletAction.LoadData) } } } else { @@ -292,7 +301,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalance.tvStatus.setLoadingStatus(R.string.wallet_balance_loading) } BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress, - BalanceStatus.TransactionInProgress -> { + BalanceStatus.TransactionInProgress, + -> { lBalanceError.root.hide() lBalance.root.show() lBalance.groupBalance.show() @@ -315,7 +325,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalance.tvError.show() lBalance.tvError.setWarningStatus( R.string.wallet_balance_blockchain_unreachable, - data.errorMessage + data.errorMessage, ) } BalanceStatus.NoAccount -> { @@ -325,7 +335,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalanceError.tvErrorDescriptions.text = getString( R.string.wallet_error_no_account_subtitle_format, - data.amountToCreateAccount, data.currencySymbol + data.amountToCreateAccount, data.currencySymbol, ) } } @@ -364,7 +374,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), private fun TextView.setStatus( text: String, @ColorRes color: Int, - @DrawableRes drawable: Int? + @DrawableRes drawable: Int?, ) { this.text = text setTextColor(getColor(color)) From bb19d52b2d9e2b3fc8b8a5c2ca1e0cad14ab6ccd Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 20:43:38 +0500 Subject: [PATCH 027/176] Updated on 2026-08-14 --- .../onboarding/AddressInfoBottomSheetDialog.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt index 50faf24376..4635716436 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -5,6 +5,8 @@ import android.os.Bundle import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchShare @@ -21,7 +23,7 @@ import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding */ class AddressInfoBottomSheetDialog( private val stateDialog: AppDialog.AddressInfoDialog, - context: Context + context: Context, ) : BottomSheetDialog(context) { var binding: DialogOnboardingAddressInfoBinding? = null @@ -40,6 +42,7 @@ class AddressInfoBottomSheetDialog( override fun show() { super.show() + Analytics.send(Token.Recieve.ScreenOpened()) showData(data = stateDialog.addressData) } @@ -51,10 +54,12 @@ class AddressInfoBottomSheetDialog( imvQrCode.setImageBitmap(data.qrCode) tvAddress.text = data.address btnFlCopyAddress.setOnClickListener { + Analytics.send(Token.Recieve.ButtonCopyAddress()) context.copyToClipboard(data.address) store.dispatchToastNotification(R.string.copy_toast_msg) } btnFlShare.setOnClickListener { + Analytics.send(Token.Recieve.ButtonShareAddress()) store.dispatchShare(data.shareUrl) } tvReceiveMessage.text = getQRReceiveMessage(tvReceiveMessage.context, stateDialog.currency) @@ -67,7 +72,7 @@ fun getQRReceiveMessage(context: Context, currency: Currency): String { context.getString( R.string.address_qr_code_message_format, currency.blockchain.fullName, - currency.currencySymbol + currency.currencySymbol, ) } is Currency.Token -> { @@ -75,7 +80,7 @@ fun getQRReceiveMessage(context: Context, currency: Currency): String { R.string.address_qr_code_message_token_format, currency.token.name, currency.currencySymbol, - currency.blockchain.fullName + currency.blockchain.fullName, ) } } From 9a9d7efe656e277f3d634245529f58fcf980026c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 20:49:56 +0500 Subject: [PATCH 028/176] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/common/DialogManager.kt | 3 +-- .../wallet/redux/middlewares/TradeCryptoMiddleware.kt | 10 +++++++--- .../RussianCardholdersWarningBottomSheetDialog.kt | 3 +++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 5ae8d9ce04..ed8d4542b9 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -152,8 +152,7 @@ class DialogManager : StoreSubscriber { primaryButtonRes = state.dialog.primaryButtonRes, primaryButtonAction = state.dialog.onOk, ) - is WalletDialog.RussianCardholdersWarningDialog -> - RussianCardholdersWarningBottomSheetDialog(context) + is WalletDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog(context) else -> null } dialog?.show() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 182c75c9bd..8a951b246c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -7,6 +7,7 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -77,7 +78,10 @@ class TradeCryptoMiddleware { cryptoCurrencyName = currency.currencySymbol, fiatCurrencyName = appCurrency.code, walletAddress = addresses[0].address, - )?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) } + )?.let { + store.dispatchOpenUrl(it) + Analytics.send(Token.Topup.ScreenOpened()) + } } private fun proceedSellAction() { @@ -96,7 +100,7 @@ class TradeCryptoMiddleware { cryptoCurrencyName = currency.currencySymbol, fiatCurrencyName = appCurrency.code, walletAddress = addresses[0].address, - )?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) } + )?.let { store.dispatchOpenUrl(it) } } private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { @@ -127,6 +131,6 @@ class TradeCryptoMiddleware { store.state.globalState.exchangeManager.getSellCryptoReceiptUrl( action = CurrencyExchangeManager.Action.Sell, transactionId = transactionId, - )?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) } + )?.let { store.dispatchOpenUrl(it) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt index a41dce9228..526b991006 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt @@ -4,6 +4,8 @@ import android.content.Context import android.os.Bundle import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.wallet.redux.WalletAction @@ -16,6 +18,7 @@ class RussianCardholdersWarningBottomSheetDialog(context: Context) : BottomSheet override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Analytics.send(Token.Topup.P2PScreenOpened()) binding = DialogRussiansCardholdersWarningBinding .inflate(LayoutInflater.from(context)) .also { setContentView(it.root) } From e761c047d1f0249d7a891f488aa1cf115c2caeca Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 20:51:04 +0500 Subject: [PATCH 029/176] Updated on 2026-08-14 --- .../wallet/redux/middlewares/TradeCryptoMiddleware.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 8a951b246c..a18c14fae9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -100,7 +100,10 @@ class TradeCryptoMiddleware { cryptoCurrencyName = currency.currencySymbol, fiatCurrencyName = appCurrency.code, walletAddress = addresses[0].address, - )?.let { store.dispatchOpenUrl(it) } + )?.let { + store.dispatchOpenUrl(it) + Analytics.send(Token.Withdraw.ScreenOpened()) + } } private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { From bd3ecd6e1ad773cffc94ad662c31174d5b3579b4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 1 Nov 2022 21:02:14 +0500 Subject: [PATCH 030/176] Updated on 2026-08-14 --- .../common/analytics/events/AnalyticsParam.kt | 3 +- .../tap/common/analytics/events/Token.kt | 4 +- .../send/redux/middlewares/SendMiddleware.kt | 3 ++ .../tap/features/send/ui/SendFragment.kt | 40 ++++++++++++++++--- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 17b2a3484b..08cc1d9e5b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -8,7 +8,8 @@ sealed class AnalyticsParam { class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol) class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency) class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol) - class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency): CurrencyType(fiatCurrency.symbol) + class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.symbol) + class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol) } // MultiCurrency or CurrencyType diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index b0c9a0db1d..7648552745 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -59,9 +59,9 @@ sealed class Token( class ButtonQRCode : Send("Button - QR Code") class ButtonSwapCurrency : Send("Button - Swap Currency") - class TransactionSent(token: CurrencyType) : Send( + class TransactionSent(type: CurrencyType) : Send( event = "Transaction Sent", - params = mapOf("Token" to token.value), + params = mapOf("Token" to type.value), ) } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 1e886dd335..c45924449f 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -21,6 +21,8 @@ import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain @@ -222,6 +224,7 @@ private fun sendTransaction( when (sendResult) { is SimpleResult.Success -> { + Analytics.send(Token.Send.TransactionSent(AnalyticsParam.CurrencyType.Amount(amountToSend))) Analytics.handleAnalyticsEvent( event = AnalyticsEventAnOld.TRANSACTION_IS_SENT, card = card, diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 4837aa7449..9fe57ae5bd 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -16,6 +16,8 @@ import com.google.android.material.textfield.TextInputEditText import com.tangem.Message import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard import com.tangem.tap.common.KeyboardObserver +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.tap.common.extensions.setOnImeActionListener @@ -28,10 +30,25 @@ import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget import com.tangem.tap.common.toggleWidget.ViewStateWidget import com.tangem.tap.features.BaseStoreFragment import com.tangem.tap.features.addBackPressHandler -import com.tangem.tap.features.send.redux.* -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* -import com.tangem.tap.features.send.redux.AmountActionUi.* -import com.tangem.tap.features.send.redux.FeeActionUi.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.CheckClipboard +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.PasteAddressPayId +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.SetTruncateHandler +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.TruncateOrRestore +import com.tangem.tap.features.send.redux.AmountAction +import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.AmountActionUi.CheckAmountToSend +import com.tangem.tap.features.send.redux.AmountActionUi.SetMainCurrency +import com.tangem.tap.features.send.redux.AmountActionUi.SetMaxAmount +import com.tangem.tap.features.send.redux.AmountActionUi.ToggleMainCurrency +import com.tangem.tap.features.send.redux.FeeActionUi.ChangeIncludeFee +import com.tangem.tap.features.send.redux.FeeActionUi.ChangeSelectedFee +import com.tangem.tap.features.send.redux.FeeActionUi.ToggleControlsVisibility +import com.tangem.tap.features.send.redux.ReceiptAction +import com.tangem.tap.features.send.redux.ReleaseSendState +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.send.redux.SendActionUi +import com.tangem.tap.features.send.redux.TransactionExtrasAction import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber @@ -43,7 +60,12 @@ import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentSendBinding import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import java.text.DecimalFormatSymbols /** @@ -61,6 +83,11 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { val binding: FragmentSendBinding by viewBinding(FragmentSendBinding::bind) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Analytics.send(Token.Send.ScreenOpened()) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) addBackPressHandler(this) @@ -101,10 +128,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { .launchIn(mainScope) imvPaste.setOnClickListener { + Analytics.send(Token.Send.ButtonPaste()) store.dispatch(PasteAddressPayId(requireContext().getFromClipboard()?.toString() ?: "")) store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused)) } imvQrCode.setOnClickListener { + Analytics.send(Token.Send.ButtonQRCode()) startActivityForResult( Intent(requireContext(), ScanQrCodeActivity::class.java), ScanQrCodeActivity.SCAN_QR_REQUEST_CODE @@ -165,6 +194,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { store.dispatch(AmountAction.SetDecimalSeparator(decimalSeparator)) binding.lSendAmount.tvAmountCurrency.setOnClickListener { + Analytics.send(Token.Send.ButtonSwapCurrency()) store.dispatch(ToggleMainCurrency) store.dispatch(ReceiptAction.RefreshReceipt) store.dispatch(SendAction.ChangeSendButtonState(store.state.sendState.getButtonState())) From c204af29737419436c6da74d78c90c1271236685 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Nov 2022 11:56:53 +0500 Subject: [PATCH 031/176] Updated on 2026-08-14 --- .../tangem/tap/common/analytics/Analytics.kt | 5 ++- .../common/analytics/api/EventHandlerApi.kt | 3 +- .../tap/common/analytics/events/Shop.kt | 34 +++++++++++++-- .../appsFlyer/AppsFlyerAnalyticsHandler.kt | 34 +++------------ .../firebase/FirebaseAnalyticsHandler.kt | 31 +++----------- .../tap/common/shop/TangemShopService.kt | 41 +++++++++---------- .../tap/features/shop/ui/ShopFragment.kt | 4 ++ 7 files changed, 72 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt index 9eaee82a3a..386213f341 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt @@ -11,6 +11,7 @@ import com.tangem.tap.common.analytics.api.ErrorEventLogger import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.extensions.filterNotNull +import com.tangem.tap.common.shop.data.ProductType /** [REDACTED_AUTHOR] @@ -85,9 +86,9 @@ object Analytics : GlobalAnalyticsEventHandler { } } - override fun send(order: Storefront.Order) { + override fun send(order: Storefront.Order, productType: ProductType) { analyticsHandlers.filterIsInstance().forEach { - it.send(order) + it.send(order, productType) } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt index ea77e75dfd..5eda855ec2 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt @@ -13,6 +13,7 @@ import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.extensions.filterNotNull +import com.tangem.tap.common.shop.data.ProductType import com.tangem.tap.domain.configurable.config.Config /** @@ -83,7 +84,7 @@ interface BlockchainSdkErrorEventHandler { } interface ShopifyOrderEventHandler { - fun send(order: Storefront.Order) + fun send(order: Storefront.Order, productType: ProductType) } interface AnalyticsHandlerBuilder { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt index c09bf89ab3..90fdf6e08f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt @@ -1,5 +1,10 @@ package com.tangem.tap.common.analytics.events +import com.shopify.buy3.Storefront +import com.tangem.common.Converter +import com.tangem.tap.common.extensions.filterNotNull +import com.tangem.tap.common.shop.data.ProductType + /** [REDACTED_AUTHOR] */ @@ -10,13 +15,36 @@ sealed class Shop( class ScreenOpened : IntroductionProcess("Shop Screen Opened") - class Purchased(productSku: String) : Shop( + class Purchased(params: Map) : Shop( event = "Purchased", - params = mapOf("SKU" to productSku), + params = params, ) class Redirected(partnerName: String) : Shop( event = "Redirected", params = mapOf("Partner" to partnerName), ) -} \ No newline at end of file +} + +class OrderToParamsConverter : Converter, Map> { + override fun convert(value: Pair): Map { + val order = value.first + val productType = value.second + + val code = (order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code + val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: productType.sku + + val count = when (productType) { + ProductType.WALLET_2_CARDS -> "2" + ProductType.WALLET_3_CARDS -> "3" + } + val amount = "${order.totalPriceV2.amount} ${order.totalPriceV2.currencyCode.name}" + + return mapOf( + "Coupon Code" to code, + "SKU" to sku, + "Count" to count, + "Amount" to amount, + ).filterNotNull() + } +} 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 008b6189b9..8264b4c501 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,12 +1,12 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer -import com.appsflyer.AFInAppEventParameterName -import com.appsflyer.AFInAppEventType import com.shopify.buy3.Storefront -import com.tangem.common.Converter import com.tangem.tap.common.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler +import com.tangem.tap.common.analytics.events.OrderToParamsConverter +import com.tangem.tap.common.analytics.events.Shop +import com.tangem.tap.common.shop.data.ProductType class AppsFlyerAnalyticsHandler( private val client: AppsFlyerAnalyticsClient, @@ -18,12 +18,12 @@ class AppsFlyerAnalyticsHandler( client.logEvent(event, params) } - override fun send(order: Storefront.Order) { - send(ORDER_EVENT, OrderToParamsConverter().convert(order)) + override fun send(order: Storefront.Order, productType: ProductType) { + val eventParams = OrderToParamsConverter().convert(order to productType) + send(Shop.Purchased(eventParams)) } companion object { - const val ORDER_EVENT = AFInAppEventType.PURCHASE const val ID = "AppsFlyer" } @@ -34,26 +34,4 @@ class AppsFlyerAnalyticsHandler( else -> null }?.let { AppsFlyerAnalyticsHandler(it) } } -} - -private class OrderToParamsConverter : Converter> { - - override fun convert(value: Storefront.Order): Map { - val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown" - - val discountCode = - (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code - - val discountParams = if (discountCode != null) { - mapOf(AFInAppEventParameterName.COUPON_CODE to discountCode) - } else { - mapOf() - } - - return mapOf( - AFInAppEventParameterName.CONTENT_ID to sku, - AFInAppEventParameterName.REVENUE to value.totalPriceV2.amount, - AFInAppEventParameterName.CURRENCY to value.currencyCode.name, - ) + discountParams - } } \ 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 65ef31ac8e..1c79942d4f 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,9 +1,7 @@ package com.tangem.tap.common.analytics.handlers.firebase -import com.google.firebase.analytics.FirebaseAnalytics import com.shopify.buy3.Storefront import com.tangem.blockchain.common.BlockchainError -import com.tangem.common.Converter import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError import com.tangem.tap.common.analytics.AnalyticsAnOld @@ -15,6 +13,9 @@ import com.tangem.tap.common.analytics.api.SdkErrorEventHandler import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter +import com.tangem.tap.common.analytics.events.OrderToParamsConverter +import com.tangem.tap.common.analytics.events.Shop +import com.tangem.tap.common.shop.data.ProductType class FirebaseAnalyticsHandler( private val client: FirebaseAnalyticsClient, @@ -57,8 +58,9 @@ class FirebaseAnalyticsHandler( } } - override fun send(order: Storefront.Order) { - send(FirebaseClient.ORDER_EVENT, OrderToParamsConverter().convert(order)) + override fun send(order: Storefront.Order, productType: ProductType) { + val eventParams = OrderToParamsConverter().convert(order to productType) + send(Shop.Purchased(eventParams)) } companion object { @@ -72,25 +74,4 @@ class FirebaseAnalyticsHandler( else -> null }?.let { FirebaseAnalyticsHandler(it) } } -} - -private class OrderToParamsConverter : Converter> { - override fun convert(value: Storefront.Order): Map { - val sku = value.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: "unknown" - - val discountCode = - (value.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code - - val discountParams = if (discountCode != null) { - mapOf(FirebaseAnalytics.Param.DISCOUNT to discountCode) - } else { - mapOf() - } - - return mapOf( - FirebaseAnalytics.Param.ITEM_ID to sku, - FirebaseAnalytics.Param.VALUE to value.totalPriceV2.amount, - FirebaseAnalytics.Param.CURRENCY to value.currencyCode.name, - ) + discountParams - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 87dacd7bc1..3d2c060786 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -22,7 +22,6 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { private val shopifyService = ShopifyService(application, shopifyShop) - private val checkouts = mutableMapOf() private val variants = mutableMapOf() @@ -40,24 +39,27 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { variants.putAll(availableVariants) if (variants.size < SKUS_TO_DISPLAY.size) { - return Result.failure(Exception( - "Shopify: products are missing, " + - "\nproducts available: ${variants.keys.map { it.sku }}")) + return Result.failure( + Exception( + "Shopify: products are missing, " + + "\nproducts available: ${variants.keys.map { it.sku }}", + ), + ) } val twoCardsProduct = TangemProduct( type = ProductType.WALLET_2_CARDS, totalSum = TotalSum( finalValue = variants[ProductType.WALLET_2_CARDS]?.priceV2?.format(), - beforeDiscount = variants[ProductType.WALLET_2_CARDS]?.compareAtPriceV2?.format() - ) + beforeDiscount = variants[ProductType.WALLET_2_CARDS]?.compareAtPriceV2?.format(), + ), ) val threeCardsProduct = TangemProduct( type = ProductType.WALLET_3_CARDS, totalSum = TotalSum( finalValue = variants[ProductType.WALLET_3_CARDS]?.priceV2?.format(), - beforeDiscount = variants[ProductType.WALLET_3_CARDS]?.compareAtPriceV2?.format() - ) + beforeDiscount = variants[ProductType.WALLET_3_CARDS]?.compareAtPriceV2?.format(), + ), ) createCheckouts() return Result.success(listOf(twoCardsProduct, threeCardsProduct)) @@ -85,7 +87,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { val totalPrice = checkouts[productType]!!.totalPriceV2.amount googlePayService.payWithGooglePay( totalPriceCents = totalPrice, currencyCode = checkouts[productType]!!.currencyCode.name, - merchantID = shopifyService.shop.merchantID + merchantID = shopifyService.shop.merchantID, ) } @@ -104,7 +106,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { suspend fun handleGooglePayResult( resultCode: Int, data: Intent?, - productType: ProductType + productType: ProductType, ): Result { val result = googlePayService.handleResponseFromGooglePay(resultCode, data) result.onSuccess { @@ -119,7 +121,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { private suspend fun completeTokenizedPayment( paymentData: PaymentData, - productType: ProductType + productType: ProductType, ): Result { val checkout = checkouts[productType]!! val googlePayResponse = @@ -140,19 +142,18 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { phone = addressGPay.phoneNumber } - val payment = Storefront.TokenizedPaymentInputV3( amount, idempotencyKey, address, paymentData.toJson(), - Storefront.PaymentTokenType.GOOGLE_PAY + Storefront.PaymentTokenType.GOOGLE_PAY, ) .setTest(true) return shopifyService.completeWithTokenizedPayment( payment = payment, - checkoutID = checkout.id + checkoutID = checkout.id, ) } @@ -181,12 +182,12 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { productType, TotalSum( finalValue = it.totalPriceV2.format(), - beforeDiscount = variants[productType]!!.compareAtPriceV2.format() + beforeDiscount = variants[productType]!!.compareAtPriceV2.format(), ), - appliedDiscount = it.getAppliedDiscount() - ) + appliedDiscount = it.getAppliedDiscount(), + ), - ) + ) } return Result.failure(result.exceptionOrNull()!!) } @@ -197,9 +198,7 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { suspend fun waitForCheckout(productType: ProductType) { val result = shopifyService.checkout(true, checkouts[productType]!!.id) - result.onSuccess { - it.order?.let { Analytics.send(it) } - } + result.onSuccess { checkout -> checkout.order?.let { Analytics.send(it, productType) } } } companion object { diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt index 51fe6cb54c..338561267b 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt @@ -12,6 +12,8 @@ import androidx.activity.OnBackPressedCallback import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.tap.common.GlobalLayoutStateHandler import com.tangem.tap.common.KeyboardObserver +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.shop.data.ProductType @@ -41,6 +43,8 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber< override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Analytics.send(Shop.ScreenOpened()) + activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { store.dispatch(NavigationAction.PopBackTo()) From 48343b3c604483811203a922494e278f81db8221 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Nov 2022 12:30:20 +0500 Subject: [PATCH 032/176] Updated on 2026-08-14 --- .../tangem/tap/domain/walletconnect/WalletConnectManager.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index f9ed7f61a5..31e16004cd 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -6,6 +6,7 @@ import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsAnOld +import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.analytics.logWcEvent import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction @@ -224,6 +225,7 @@ class WalletConnectManager { } private fun onSessionClosed(session: WCSession) { + Analytics.send(WalletConnect.SessionDisconnected()) Analytics.logWcEvent( AnalyticsAnOld.WcAnalyticsEvent.Session( AnalyticsAnOld.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url @@ -373,6 +375,7 @@ class WalletConnectManager { ), ) } + Analytics.send(WalletConnect.NewSessionEstablished()) Analytics.logWcEvent( AnalyticsAnOld.WcAnalyticsEvent.Session( AnalyticsAnOld.WcSessionEvent.Connect, peer.url, From dec6afa96a4adfbcfb8fa08fafb7a9731d909854 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Nov 2022 14:57:52 +0500 Subject: [PATCH 033/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/common/analytics/events/Token.kt | 7 ++++++- .../wallet/redux/middlewares/MultiWalletMiddleware.kt | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index 7648552745..9da6abe399 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -12,9 +12,14 @@ sealed class Token( ) : AnalyticsEvent(category, event, params) { class Refreshed : Token("Token", "Refreshed") - class ButtonRemoveToken : Token("Token", "Button - Remove Token") class ButtonExplore : Token("Token", "Button - Explore") + class ButtonRemoveToken(type: CurrencyType) : Token( + "Token", + "Button - Remove Token", + params = mapOf("Token" to type.value), + ) + class ButtonBuy(type: CurrencyType) : Token( category = "Token", event = "Button - Buy", diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 3433d447d5..3fe4e1b3d0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -6,6 +6,8 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.safeUpdate @@ -110,7 +112,7 @@ class MultiWalletMiddleware { WalletDialog.RemoveWalletDialog( currencyTitle = currency.currencyName, onOk = { - Analytics.send(com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken()) + Analytics.send(ButtonRemoveToken(AnalyticsParam.CurrencyType.Currency(currency))) store.dispatch( WalletAction.MultiWallet.RemoveWallet( currency = currency, From c2b57b28b0506ce12bce12741bf46d3a3cca0d87 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Nov 2022 17:52:24 +0500 Subject: [PATCH 034/176] Updated on 2026-08-14 --- .../converters/ShopOrderToEventConverter.kt | 27 ++++++++++++++ .../tap/common/analytics/events/Shop.kt | 37 ++++--------------- .../appsFlyer/AppsFlyerAnalyticsHandler.kt | 8 ++-- .../firebase/FirebaseAnalyticsHandler.kt | 8 ++-- 4 files changed, 43 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt new file mode 100644 index 0000000000..ddbd1683bd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.common.analytics.converters + +import com.shopify.buy3.Storefront +import com.tangem.common.Converter +import com.tangem.tap.common.analytics.events.Shop +import com.tangem.tap.common.shop.data.ProductType + +/** +[REDACTED_AUTHOR] + */ +class ShopOrderToEventConverter : Converter, Shop.Purchased> { + + override fun convert(value: Pair): Shop.Purchased { + val order = value.first + val productType = value.second + + val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: productType.sku + val count = when (productType) { + ProductType.WALLET_2_CARDS -> "2" + ProductType.WALLET_3_CARDS -> "3" + } + val amount = "${order.totalPriceV2.amount} ${order.totalPriceV2.currencyCode.name}" + val code = (order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code + + return Shop.Purchased(sku, count, amount, code) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt index 90fdf6e08f..9634f2929b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt @@ -1,9 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.shopify.buy3.Storefront -import com.tangem.common.Converter import com.tangem.tap.common.extensions.filterNotNull -import com.tangem.tap.common.shop.data.ProductType /** [REDACTED_AUTHOR] @@ -15,36 +12,18 @@ sealed class Shop( class ScreenOpened : IntroductionProcess("Shop Screen Opened") - class Purchased(params: Map) : Shop( + class Purchased(sku: String, count: String, amount: String, couponCode: String?) : Shop( event = "Purchased", - params = params, + params = mapOf( + "SKU" to sku, + "Count" to count, + "Amount" to amount, + "Coupon Code" to couponCode, + ).filterNotNull(), ) class Redirected(partnerName: String) : Shop( event = "Redirected", params = mapOf("Partner" to partnerName), ) -} - -class OrderToParamsConverter : Converter, Map> { - override fun convert(value: Pair): Map { - val order = value.first - val productType = value.second - - val code = (order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code - val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: productType.sku - - val count = when (productType) { - ProductType.WALLET_2_CARDS -> "2" - ProductType.WALLET_3_CARDS -> "3" - } - val amount = "${order.totalPriceV2.amount} ${order.totalPriceV2.currencyCode.name}" - - return mapOf( - "Coupon Code" to code, - "SKU" to sku, - "Count" to count, - "Amount" to amount, - ).filterNotNull() - } -} +} \ 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 8264b4c501..fe23cfd753 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 @@ -4,8 +4,7 @@ import com.shopify.buy3.Storefront import com.tangem.tap.common.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler -import com.tangem.tap.common.analytics.events.OrderToParamsConverter -import com.tangem.tap.common.analytics.events.Shop +import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter import com.tangem.tap.common.shop.data.ProductType class AppsFlyerAnalyticsHandler( @@ -19,8 +18,9 @@ class AppsFlyerAnalyticsHandler( } override fun send(order: Storefront.Order, productType: ProductType) { - val eventParams = OrderToParamsConverter().convert(order to productType) - send(Shop.Purchased(eventParams)) + //TODO: make sending this event through filters + val event = ShopOrderToEventConverter().convert(order to productType) + send(event) } companion object { 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 1c79942d4f..f2abc8ca76 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 @@ -13,8 +13,7 @@ import com.tangem.tap.common.analytics.api.SdkErrorEventHandler import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter -import com.tangem.tap.common.analytics.events.OrderToParamsConverter -import com.tangem.tap.common.analytics.events.Shop +import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter import com.tangem.tap.common.shop.data.ProductType class FirebaseAnalyticsHandler( @@ -59,8 +58,9 @@ class FirebaseAnalyticsHandler( } override fun send(order: Storefront.Order, productType: ProductType) { - val eventParams = OrderToParamsConverter().convert(order to productType) - send(Shop.Purchased(eventParams)) + //TODO: make sending this event through filters + val event = ShopOrderToEventConverter().convert(order to productType) + send(event) } companion object { From 9c3cd6f2d3df061a26a931bdd0fabd2bb22d2878 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Nov 2022 19:26:45 +0500 Subject: [PATCH 035/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 4 +++ .../tangem/tap/common/analytics/Analytics.kt | 30 ++++++++++++------- .../analytics/GlobalAnalyticsEventHandler.kt | 9 ++++-- .../common/analytics/api/EventFilterApi.kt | 12 ++++++++ .../common/analytics/api/EventHandlerApi.kt | 6 ---- .../filters/ShopPurchasedEventFilter.kt | 25 ++++++++++++++++ .../appsFlyer/AppsFlyerAnalyticsHandler.kt | 12 +------- .../firebase/FirebaseAnalyticsHandler.kt | 12 +------- .../tap/common/shop/TangemShopService.kt | 8 ++++- 9 files changed, 76 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 1913ac6e16..5417042486 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -17,6 +17,7 @@ import com.tangem.tap.common.AssetReader import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsHandlersFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder +import com.tangem.tap.common.analytics.filters.ShopPurchasedEventFilter import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler @@ -122,6 +123,9 @@ class TapApplication : Application(), ImageLoaderFactory { factory.build(buildData).forEach { Analytics.addHandler(it.id(), it) } + listOf( + ShopPurchasedEventFilter(), + ).forEach { Analytics.addFilter(it) } } private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt index 386213f341..57617de12b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt @@ -1,23 +1,22 @@ package com.tangem.tap.common.analytics -import com.shopify.buy3.Storefront import com.tangem.blockchain.common.BlockchainError import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.analytics.api.AnalyticsEventFilter import com.tangem.tap.common.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.BlockchainSdkErrorEventHandler import com.tangem.tap.common.analytics.api.CardSdkErrorEventHandler import com.tangem.tap.common.analytics.api.ErrorEventLogger -import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.extensions.filterNotNull -import com.tangem.tap.common.shop.data.ProductType /** [REDACTED_AUTHOR] */ object Analytics : GlobalAnalyticsEventHandler { + private val eventFilters = mutableListOf() private val handlers = mutableMapOf() private val analyticsHandlers: List @@ -35,6 +34,14 @@ object Analytics : GlobalAnalyticsEventHandler { return handlers.remove(name) } + override fun addFilter(filter: AnalyticsEventFilter) { + eventFilters.add(filter) + } + + override fun removeFilter(filter: AnalyticsEventFilter) { + eventFilters.remove(filter) + } + override fun attachToAllEvents(key: String, value: String) { attachToAllEventsParams[key] = value } @@ -44,7 +51,16 @@ object Analytics : GlobalAnalyticsEventHandler { } override fun send(event: AnalyticsEvent, card: Card?, blockchain: String?) { - analyticsHandlers.forEach { it.send(event, card, blockchain) } + val eventFilter = eventFilters.firstOrNull { it.canBeAppliedTo(event) } + + when { + eventFilter == null -> analyticsHandlers.forEach { it.send(event, card, blockchain) } + eventFilter.canBeSent(event) -> { + analyticsHandlers + .filter { eventFilter.canBeConsumedBy(it, event) } + .forEach { it.send(event) } + } + } } override fun handleAnalyticsEvent( @@ -86,12 +102,6 @@ object Analytics : GlobalAnalyticsEventHandler { } } - override fun send(order: Storefront.Order, productType: ProductType) { - analyticsHandlers.filterIsInstance().forEach { - it.send(order, productType) - } - } - override fun prepareParams(card: Card?, blockchain: String?, params: Map): Map { return super.prepareParams(card, blockchain, params).toMutableMap().apply { putAll(attachToAllEventsParams) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt index 139ffa5582..287f991527 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt @@ -1,18 +1,21 @@ package com.tangem.tap.common.analytics +import com.tangem.tap.common.analytics.api.AnalyticsEventFilter import com.tangem.tap.common.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.ErrorEventHandler import com.tangem.tap.common.analytics.api.SdkErrorEventHandler -import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler interface GlobalAnalyticsEventHandler : AnalyticsEventHandler, ErrorEventHandler, - SdkErrorEventHandler, - ShopifyOrderEventHandler { + SdkErrorEventHandler { fun addHandler(name: String, handler: AnalyticsEventHandler) fun removeHandler(name: String): AnalyticsEventHandler? + fun addFilter(filter: AnalyticsEventFilter) + + fun removeFilter(filter: AnalyticsEventFilter) + fun attachToAllEvents(key: String, value: String) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt new file mode 100644 index 0000000000..a4e93fa75b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.common.analytics.api + +import com.tangem.tap.common.analytics.events.AnalyticsEvent + +/** +[REDACTED_AUTHOR] + */ +interface AnalyticsEventFilter { + fun canBeAppliedTo(event: AnalyticsEvent): Boolean + fun canBeSent(event: AnalyticsEvent): Boolean + fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt index 5eda855ec2..494747287e 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt @@ -1,7 +1,6 @@ package com.tangem.tap.common.analytics.api import android.app.Application -import com.shopify.buy3.Storefront import com.tangem.blockchain.common.BlockchainError import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError @@ -13,7 +12,6 @@ import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.extensions.filterNotNull -import com.tangem.tap.common.shop.data.ProductType import com.tangem.tap.domain.configurable.config.Config /** @@ -83,10 +81,6 @@ interface BlockchainSdkErrorEventHandler { ) } -interface ShopifyOrderEventHandler { - fun send(order: Storefront.Order, productType: ProductType) -} - interface AnalyticsHandlerBuilder { fun build(data: Data): AnalyticsEventHandler? diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt new file mode 100644 index 0000000000..5267782ddc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.common.analytics.filters + +import com.tangem.tap.common.analytics.api.AnalyticsEventFilter +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.tap.common.analytics.events.Shop +import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler +import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler + +/** +[REDACTED_AUTHOR] + */ +class ShopPurchasedEventFilter : AnalyticsEventFilter { + + override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is Shop.Purchased + + override fun canBeSent(event: AnalyticsEvent): Boolean = true + + override fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean { + return when (handler) { + is AppsFlyerAnalyticsHandler, is FirebaseAnalyticsHandler -> true + else -> false + } + } +} \ 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 fe23cfd753..b91da358d6 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,15 +1,11 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer -import com.shopify.buy3.Storefront import com.tangem.tap.common.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder -import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler -import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter -import com.tangem.tap.common.shop.data.ProductType class AppsFlyerAnalyticsHandler( private val client: AppsFlyerAnalyticsClient, -) : AnalyticsEventHandler, ShopifyOrderEventHandler { +) : AnalyticsEventHandler { override fun id(): String = ID @@ -17,12 +13,6 @@ class AppsFlyerAnalyticsHandler( client.logEvent(event, params) } - override fun send(order: Storefront.Order, productType: ProductType) { - //TODO: make sending this event through filters - val event = ShopOrderToEventConverter().convert(order to productType) - send(event) - } - companion object { const val ID = "AppsFlyer" } 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 f2abc8ca76..9a4a8bf839 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,6 +1,5 @@ package com.tangem.tap.common.analytics.handlers.firebase -import com.shopify.buy3.Storefront import com.tangem.blockchain.common.BlockchainError import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError @@ -10,15 +9,12 @@ import com.tangem.tap.common.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.api.ErrorEventHandler import com.tangem.tap.common.analytics.api.SdkErrorEventHandler -import com.tangem.tap.common.analytics.api.ShopifyOrderEventHandler import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter -import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter -import com.tangem.tap.common.shop.data.ProductType class FirebaseAnalyticsHandler( private val client: FirebaseAnalyticsClient, -) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler, ShopifyOrderEventHandler { +) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler { override fun id(): String = ID @@ -57,12 +53,6 @@ class FirebaseAnalyticsHandler( } } - override fun send(order: Storefront.Order, productType: ProductType) { - //TODO: make sending this event through filters - val event = ShopOrderToEventConverter().convert(order to productType) - send(event) - } - companion object { const val ID = "Firebase" } diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 3d2c060786..271886dea1 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -5,6 +5,7 @@ import android.content.Intent import com.google.android.gms.wallet.PaymentData import com.shopify.buy3.Storefront import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.common.shop.data.ProductType import com.tangem.tap.common.shop.data.TangemProduct @@ -198,7 +199,12 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { suspend fun waitForCheckout(productType: ProductType) { val result = shopifyService.checkout(true, checkouts[productType]!!.id) - result.onSuccess { checkout -> checkout.order?.let { Analytics.send(it, productType) } } + result.onSuccess { checkout -> + checkout.order?.let { + val event = ShopOrderToEventConverter().convert(it to productType) + Analytics.send(event) + } + } } companion object { From bd0adfea6ffc5d403816862ed99b391900d3f55b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Nov 2022 21:24:22 +0500 Subject: [PATCH 036/176] Updated on 2026-08-14 --- .../onboarding/products/twins/redux/TwinCardsMiddleware.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index bf3cb1296c..aa6de330d6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.onboarding.products.twins.redux +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.extensions.Result import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard @@ -7,6 +8,7 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification @@ -273,6 +275,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } is TwinCardsAction.TopUp -> { val topUpUrl = twinCardsState.walletManager?.getTopUpUrl() ?: return + + val currencyType = AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin) + Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType)) + store.dispatchOpenUrl(topUpUrl) } TwinCardsAction.Done -> { From 7d7b6106a0673e93a8d9c02866b72aeebe450de3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Nov 2022 21:48:54 +0500 Subject: [PATCH 037/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 2 + .../converters/BasicSignInEventConverter.kt | 38 +++++++++++++++++++ .../converters/ParamCardCurrencyConverter.kt | 31 +++++++++++++++ .../common/analytics/events/AnalyticsEvent.kt | 1 + .../tap/common/analytics/events/Basic.kt | 2 +- .../analytics/filters/BasicSignInFilter.kt | 27 +++++++++++++ .../redux/reducers/TotalBalanceOperations.kt | 27 +++++++++---- .../tap/features/wallet/ui/WalletFragment.kt | 14 +++++++ 8 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 5417042486..36fcd97572 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -17,6 +17,7 @@ import com.tangem.tap.common.AssetReader import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsHandlersFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder +import com.tangem.tap.common.analytics.filters.BasicSignInFilter import com.tangem.tap.common.analytics.filters.ShopPurchasedEventFilter import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler @@ -125,6 +126,7 @@ class TapApplication : Application(), ImageLoaderFactory { } listOf( ShopPurchasedEventFilter(), + BasicSignInFilter(), ).forEach { Analytics.addFilter(it) } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt new file mode 100644 index 0000000000..e3d07461d0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt @@ -0,0 +1,38 @@ +package com.tangem.tap.common.analytics.converters + +import com.tangem.common.Converter +import com.tangem.common.extensions.isZero +import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Basic +import com.tangem.tap.domain.tokens.UserWalletId +import com.tangem.tap.features.wallet.redux.ProgressState +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +class BasicSignInEventConverter( + private val scanResponse: ScanResponse, + private val progressState: ProgressState, + private val totalCryptoAmount: BigDecimal, +) : Converter { + + override fun convert(value: Any?): Basic.SignedIn? { + if (progressState == ProgressState.Loading || progressState == ProgressState.Refreshing) return null + val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null + + val cardState = when { + totalCryptoAmount.isZero() -> AnalyticsParam.CardState.Empty + else -> AnalyticsParam.CardState.Full + } + + return Basic.SignedIn( + state = cardState, + currency = cardCurrency, + batch = scanResponse.card.batchId, + ).apply { + filterData = UserWalletId(scanResponse.card.wallets.first().publicKey).stringValue + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt new file mode 100644 index 0000000000..e64b751f4f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.common.analytics.converters + +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.Converter +import com.tangem.domain.common.SaltPayWorkaround +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.domain.extensions.isMultiwalletAllowed + +/** +[REDACTED_AUTHOR] + */ +class ParamCardCurrencyConverter : Converter { + + override fun convert(value: ScanResponse): AnalyticsParam.CardCurrency? { + if (value.card.isMultiwalletAllowed) return AnalyticsParam.CardCurrency.MultiCurrency + + val type = when { + value.isTangemNote() -> { + value.card.getTangemNoteBlockchain()?.let { AnalyticsParam.CurrencyType.Blockchain(it) } + } + value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin) + value.isSaltPay() -> AnalyticsParam.CurrencyType.Token(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay)) + value.getPrimaryToken() != null -> AnalyticsParam.CurrencyType.Token(value.getPrimaryToken()!!) + else -> null + } ?: return null + + return AnalyticsParam.CardCurrency.SingleCurrency(type) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt index 0451fdd68c..8e5cf0e942 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt @@ -7,4 +7,5 @@ sealed class AnalyticsEvent( val category: String, val event: String, val params: Map = mapOf(), + var filterData: Any? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index bb37145672..cba32f8061 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -8,7 +8,7 @@ sealed class Basic( params: Map = mapOf(), ) : AnalyticsEvent("Basic", event, params) { - sealed class SignedIn( + class SignedIn( state: AnalyticsParam.CardState, currency: AnalyticsParam.CardCurrency, batch: String, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt new file mode 100644 index 0000000000..c885cf7ae6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.common.analytics.filters + +import com.tangem.tap.common.analytics.api.AnalyticsEventFilter +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.tap.common.analytics.events.Basic + +/** +[REDACTED_AUTHOR] + */ +class BasicSignInFilter : AnalyticsEventFilter { + + private val alreadySignedIn = mutableSetOf() + + override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is Basic.SignedIn + + override fun canBeSent(event: AnalyticsEvent): Boolean { + val userWalletId = event.filterData as? String ?: return false + + val canBeSent = !alreadySignedIn.contains(userWalletId) + alreadySignedIn.add(userWalletId) + + return canBeSent + } + + override fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt index 15e44ea361..79470dae62 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt @@ -17,6 +17,12 @@ fun List.calculateTotalFiatAmount(): BigDecimal { .reduce(BigDecimal::plus) } +fun List.calculateTotalCryptoAmount(): BigDecimal { + return this + .map { it.currencyData.amount ?: BigDecimal.ZERO } + .reduce(BigDecimal::plus) +} + private fun List.mapToProgressState(): List { return this.map { wallet -> if (wallet.fiatRate == null) ProgressState.Error @@ -25,12 +31,15 @@ private fun List.mapToProgressState(): List { BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress, BalanceStatus.TransactionInProgress, - BalanceStatus.NoAccount -> ProgressState.Done + BalanceStatus.NoAccount, + -> ProgressState.Done BalanceStatus.Unreachable, BalanceStatus.EmptyCard, - BalanceStatus.UnknownBlockchain -> ProgressState.Error + BalanceStatus.UnknownBlockchain, + -> ProgressState.Error BalanceStatus.Loading, - null -> ProgressState.Loading + null, + -> ProgressState.Loading } } } @@ -41,20 +50,24 @@ private infix fun ProgressState.or(newState: ProgressState): ProgressState { ProgressState.Loading, ProgressState.Refreshing, ProgressState.Error, - ProgressState.Done -> this + ProgressState.Done, + -> this } ProgressState.Done, - ProgressState.Error -> when (newState) { + ProgressState.Error, + -> when (newState) { ProgressState.Loading, ProgressState.Refreshing, - ProgressState.Error -> newState + ProgressState.Error, + -> newState ProgressState.Done -> this } ProgressState.Refreshing -> when (newState) { ProgressState.Loading -> this ProgressState.Refreshing, ProgressState.Error, - ProgressState.Done -> newState + ProgressState.Done, + -> newState } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 97fba31222..b44dcebc1d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -17,6 +17,7 @@ import coil.size.Scale import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.converters.BasicSignInEventConverter import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Portfolio import com.tangem.tap.common.extensions.show @@ -33,6 +34,7 @@ import com.tangem.tap.features.wallet.redux.ErrorType import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.reducers.calculateTotalCryptoAmount import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView import com.tangem.tap.features.wallet.ui.wallet.SaltPaySingleWalletView @@ -119,6 +121,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber Date: Thu, 3 Nov 2022 17:42:20 +0500 Subject: [PATCH 038/176] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../common/analytics/converters/BasicSignInEventConverter.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a1658496e7..cf6ea50867 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6 +Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354 diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt index e3d07461d0..083fd47f08 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt @@ -5,7 +5,7 @@ import com.tangem.common.extensions.isZero import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic -import com.tangem.tap.domain.tokens.UserWalletId +import com.tangem.tap.domain.extensions.getUserWalletId import com.tangem.tap.features.wallet.redux.ProgressState import java.math.BigDecimal @@ -32,7 +32,7 @@ class BasicSignInEventConverter( currency = cardCurrency, batch = scanResponse.card.batchId, ).apply { - filterData = UserWalletId(scanResponse.card.wallets.first().publicKey).stringValue + filterData = scanResponse.card.getUserWalletId() } } } \ No newline at end of file From d2d725c4e9927ce8e6b7b9f1334b460cf54ba31e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 3 Nov 2022 17:45:42 +0500 Subject: [PATCH 039/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 2 + .../converters/BasicEventConverter.kt | 76 +++++++++++++++++++ .../converters/BasicSignInEventConverter.kt | 38 ---------- .../common/analytics/events/AnalyticsParam.kt | 7 +- .../tap/common/analytics/events/Basic.kt | 2 +- .../analytics/filters/BasicTopUpFilter.kt | 50 ++++++++++++ .../tap/features/wallet/ui/WalletFragment.kt | 10 +-- .../tap/persistence/PreferencesStorage.kt | 2 + .../tap/persistence/ToppedUpWalletStorage.kt | 57 ++++++++++++++ 9 files changed, 195 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt create mode 100644 app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 36fcd97572..be1837114f 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -18,6 +18,7 @@ import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsHandlersFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.filters.BasicSignInFilter +import com.tangem.tap.common.analytics.filters.BasicTopUpFilter import com.tangem.tap.common.analytics.filters.ShopPurchasedEventFilter import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler @@ -127,6 +128,7 @@ class TapApplication : Application(), ImageLoaderFactory { listOf( ShopPurchasedEventFilter(), BasicSignInFilter(), + BasicTopUpFilter(preferencesStorage.toppedUpWalletStorage), ).forEach { Analytics.addFilter(it) } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt new file mode 100644 index 0000000000..3950f458bc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -0,0 +1,76 @@ +package com.tangem.tap.common.analytics.converters + +import com.tangem.common.Converter +import com.tangem.common.extensions.isZero +import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Basic +import com.tangem.tap.common.analytics.filters.BasicTopUpFilter +import com.tangem.tap.domain.extensions.getUserWalletId +import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.reducers.calculateTotalCryptoAmount + +/** +[REDACTED_AUTHOR] + */ +class BasicSignInEventConverter( + private val scanResponse: ScanResponse, +) : Converter { + + override fun convert(value: WalletState): Basic.SignedIn? { + if (!statesIsReadyToCreateEvent(scanResponse, value)) return null + val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null + + return Basic.SignedIn( + state = AnalyticsParam.CardBalanceState.from(value.walletsData), + currency = cardCurrency, + batch = scanResponse.card.batchId, + ).apply { + filterData = scanResponse.card.getUserWalletId() + } + } +} + +class BasicTopUpEventConverter( + private val scanResponse: ScanResponse, +) : Converter { + + override fun convert(value: WalletState): Basic.ToppedUp? { + if (!statesIsReadyToCreateEvent(scanResponse, value)) return null + val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null + + val data = BasicTopUpFilter.Data( + walletId = scanResponse.card.getUserWalletId(), + cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsData), + ) + + return Basic.ToppedUp(cardCurrency).apply { filterData = data } + } +} + +private fun AnalyticsParam.CardBalanceState.Companion.from(walletsData: List): AnalyticsParam.CardBalanceState { + val totalCryptoAmount = walletsData.calculateTotalCryptoAmount() + return when { + totalCryptoAmount.isZero() -> AnalyticsParam.CardBalanceState.Empty + else -> AnalyticsParam.CardBalanceState.Full + } +} + +private fun statesIsReadyToCreateEvent(scanResponse: ScanResponse, state: WalletState): Boolean { + if (scanResponse.card.isMultiwalletAllowed && state.missingDerivations.isNotEmpty()) return false + if (state.walletsData.isEmpty()) return false + + val totalBalanceState = state.totalBalance?.state ?: return false + if (totalBalanceState == ProgressState.Loading || totalBalanceState == ProgressState.Refreshing) return false + + val balancesCount = state.walletsData.map { + if (it.currencyData.amount == null) 0 else 1 + }.reduce { acc, i -> acc + i } + + if (balancesCount != state.wallets.size) return false + + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt deleted file mode 100644 index 083fd47f08..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicSignInEventConverter.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.tangem.tap.common.analytics.converters - -import com.tangem.common.Converter -import com.tangem.common.extensions.isZero -import com.tangem.domain.common.ScanResponse -import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.common.analytics.events.Basic -import com.tangem.tap.domain.extensions.getUserWalletId -import com.tangem.tap.features.wallet.redux.ProgressState -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -class BasicSignInEventConverter( - private val scanResponse: ScanResponse, - private val progressState: ProgressState, - private val totalCryptoAmount: BigDecimal, -) : Converter { - - override fun convert(value: Any?): Basic.SignedIn? { - if (progressState == ProgressState.Loading || progressState == ProgressState.Refreshing) return null - val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null - - val cardState = when { - totalCryptoAmount.isZero() -> AnalyticsParam.CardState.Empty - else -> AnalyticsParam.CardState.Full - } - - return Basic.SignedIn( - state = cardState, - currency = cardCurrency, - batch = scanResponse.card.batchId, - ).apply { - filterData = scanResponse.card.getUserWalletId() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 08cc1d9e5b..71e6eb6c09 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -18,9 +18,10 @@ sealed class AnalyticsParam { class SingleCurrency(type: CurrencyType) : CardCurrency(type.value) } - sealed class CardState(val value: String) { - object Empty : CardState("Empty") - object Full : CardState("Full") + sealed class CardBalanceState(val value: String) { + object Empty : CardBalanceState("Empty") + object Full : CardBalanceState("Full") + companion object } sealed class RateApp(val value: String) { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index cba32f8061..f1e3927530 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -9,7 +9,7 @@ sealed class Basic( ) : AnalyticsEvent("Basic", event, params) { class SignedIn( - state: AnalyticsParam.CardState, + state: AnalyticsParam.CardBalanceState, currency: AnalyticsParam.CardCurrency, batch: String, ) : Basic( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt new file mode 100644 index 0000000000..5b0638231e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt @@ -0,0 +1,50 @@ +package com.tangem.tap.common.analytics.filters + +import com.tangem.common.extensions.guard +import com.tangem.tap.common.analytics.api.AnalyticsEventFilter +import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Basic +import com.tangem.tap.persistence.ToppedUpWalletStorage + +/** +[REDACTED_AUTHOR] + */ +class BasicTopUpFilter( + private val topupWalletStorage: ToppedUpWalletStorage, +) : AnalyticsEventFilter { + + override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is Basic.ToppedUp + + override fun canBeSent(event: AnalyticsEvent): Boolean { + val data = event.filterData as? Data ?: return false + + val walletInfo = topupWalletStorage.restore(data.walletId).guard { + val newWalletInfo = Data( + walletId = data.walletId, + cardBalanceState = data.cardBalanceState, + ) + topupWalletStorage.save(newWalletInfo) + return false + } + + if (walletInfo.isToppedUp) return false + + return if (!walletInfo.isToppedUp && data.isToppedUp) { + topupWalletStorage.save(walletInfo.copy(cardBalanceState = AnalyticsParam.CardBalanceState.Full)) + true + } else { + false + } + } + + override fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true + + data class Data( + val walletId: String, + val cardBalanceState: AnalyticsParam.CardBalanceState, + ) { + val isToppedUp: Boolean = cardBalanceState == AnalyticsParam.CardBalanceState.Full + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index b44dcebc1d..544d94e6ca 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -18,6 +18,7 @@ import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.converters.BasicSignInEventConverter +import com.tangem.tap.common.analytics.converters.BasicTopUpEventConverter import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Portfolio import com.tangem.tap.common.extensions.show @@ -34,7 +35,6 @@ import com.tangem.tap.features.wallet.redux.ErrorType import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState -import com.tangem.tap.features.wallet.redux.reducers.calculateTotalCryptoAmount import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView import com.tangem.tap.features.wallet.ui.wallet.SaltPaySingleWalletView @@ -231,13 +231,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber = mutableSetOf() + + init { + walletList.addAll(restore()) + } + + fun save(userWalletInfo: BasicTopUpFilter.Data): Boolean { + walletList.removeAll { it.walletId == userWalletInfo.walletId } + walletList.add(userWalletInfo) + return save(walletList) + } + + fun restore(walletId: String): BasicTopUpFilter.Data? { + return walletList.firstOrNull { it.walletId == walletId } + } + + private fun save(userWallets: MutableSet): Boolean { + return try { + val json = jsonConverter.toJson(userWallets) + preferences.edit(true) { putString(KEY, json) } + true + } catch (ex: Exception) { + Timber.e(ex) + false + } + } + + private fun restore(): MutableSet { + val json = preferences.getString(KEY, null) ?: return mutableSetOf() + return try { + jsonConverter.fromJson(json, jsonConverter.typedList(BasicTopUpFilter.Data::class.java))!! + } catch (ex: Exception) { + preferences.edit(true) { remove(KEY) } + mutableSetOf() + } + } + + companion object { + private const val KEY = "userWalletsInfo" + } +} \ No newline at end of file From 431852f30c12e5f97ae3ea7539bf3e3cc5c7dc97 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 8 Nov 2022 14:53:46 +0500 Subject: [PATCH 040/176] Updated on 2026-08-14 --- .../tangem/tap/common/analytics/Analytics.kt | 60 ++++++++++++++----- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt index 57617de12b..6e773bceab 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common.analytics import com.tangem.blockchain.common.BlockchainError import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError +import com.tangem.domain.common.FeatureCoroutineExceptionHandler import com.tangem.tap.common.analytics.api.AnalyticsEventFilter import com.tangem.tap.common.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.BlockchainSdkErrorEventHandler @@ -10,12 +11,20 @@ import com.tangem.tap.common.analytics.api.CardSdkErrorEventHandler import com.tangem.tap.common.analytics.api.ErrorEventLogger import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.extensions.filterNotNull +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.launch +import java.util.concurrent.Executors /** [REDACTED_AUTHOR] */ object Analytics : GlobalAnalyticsEventHandler { + internal val analyticsScope: CoroutineScope by lazy { createScope() } + private val eventFilters = mutableListOf() private val handlers = mutableMapOf() @@ -47,18 +56,22 @@ object Analytics : GlobalAnalyticsEventHandler { } override fun send(event: String, params: Map) { - analyticsHandlers.forEach { it.send(event, params) } + analyticsScope.launch { + analyticsHandlers.forEach { it.send(event, params) } + } } override fun send(event: AnalyticsEvent, card: Card?, blockchain: String?) { - val eventFilter = eventFilters.firstOrNull { it.canBeAppliedTo(event) } + analyticsScope.launch { + val eventFilter = eventFilters.firstOrNull { it.canBeAppliedTo(event) } - when { - eventFilter == null -> analyticsHandlers.forEach { it.send(event, card, blockchain) } - eventFilter.canBeSent(event) -> { - analyticsHandlers - .filter { eventFilter.canBeConsumedBy(it, event) } - .forEach { it.send(event) } + when { + eventFilter == null -> analyticsHandlers.forEach { it.send(event, card, blockchain) } + eventFilter.canBeSent(event) -> { + analyticsHandlers + .filter { eventFilter.canBeConsumedBy(it, event) } + .forEach { it.send(event) } + } } } } @@ -69,14 +82,18 @@ object Analytics : GlobalAnalyticsEventHandler { card: Card?, blockchain: String?, ) { - analyticsHandlers.forEach { - it.handleAnalyticsEvent(event, params, card, blockchain) + analyticsScope.launch { + analyticsHandlers.forEach { + it.handleAnalyticsEvent(event, params, card, blockchain) + } } } override fun send(error: Throwable, params: Map) { - analyticsHandlers.filterIsInstance().forEach { - it.logErrorEvent(error, params) + analyticsScope.launch { + analyticsHandlers.filterIsInstance().forEach { + it.logErrorEvent(error, params) + } } } @@ -86,8 +103,10 @@ object Analytics : GlobalAnalyticsEventHandler { params: Map, card: Card?, ) { - analyticsHandlers.filterIsInstance().forEach { - it.send(error, action, params, card) + analyticsScope.launch { + analyticsHandlers.filterIsInstance().forEach { + it.send(error, action, params, card) + } } } @@ -97,8 +116,10 @@ object Analytics : GlobalAnalyticsEventHandler { params: Map, card: Card?, ) { - analyticsHandlers.filterIsInstance().forEach { - it.send(error, action, params, card) + analyticsScope.launch { + analyticsHandlers.filterIsInstance().forEach { + it.send(error, action, params, card) + } } } @@ -107,6 +128,13 @@ object Analytics : GlobalAnalyticsEventHandler { putAll(attachToAllEventsParams) } } + + private fun createScope(): CoroutineScope { + val name = "Analytics" + val dispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() + val exceptionHandler = FeatureCoroutineExceptionHandler.create(name) + return CoroutineScope(Job() + dispatcher + CoroutineName(name) + exceptionHandler) + } } fun GlobalAnalyticsEventHandler.logWcEvent(event: AnalyticsAnOld.WcAnalyticsEvent) { From de93d0f3503a9fd2c3dc6ed1f755a1ffeba07693 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 8 Nov 2022 18:26:18 +0500 Subject: [PATCH 041/176] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../tangem/tap/common/analytics/Analytics.kt | 67 +++++++++++++------ .../analytics/GlobalAnalyticsEventHandler.kt | 21 ------ .../analytics/api/ErrorEventHandlerApi.kt | 37 ++++++++++ .../common/analytics/api/EventFilterApi.kt | 7 +- .../common/analytics/api/EventHandlerApi.kt | 41 +----------- .../analytics/api/ParamsInterceptorApi.kt | 17 +++++ .../common/analytics/events/AnalyticsParam.kt | 4 +- .../tap/common/analytics/events/Basic.kt | 2 +- .../analytics/filters/BasicSignInFilter.kt | 2 +- .../analytics/filters/BasicTopUpFilter.kt | 2 +- .../filters/ShopPurchasedEventFilter.kt | 2 +- .../BatchIdParamsInterceptor.kt | 21 ++++++ .../tap/features/home/redux/HomeMiddleware.kt | 4 +- 14 files changed, 140 insertions(+), 89 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/api/ErrorEventHandlerApi.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/api/ParamsInterceptorApi.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index cf6ea50867..a1658496e7 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354 +Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6 diff --git a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt index 6e773bceab..3950438219 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt @@ -6,9 +6,15 @@ import com.tangem.common.core.TangemSdkError import com.tangem.domain.common.FeatureCoroutineExceptionHandler import com.tangem.tap.common.analytics.api.AnalyticsEventFilter import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsFilterHolder +import com.tangem.tap.common.analytics.api.AnalyticsHandlerHolder import com.tangem.tap.common.analytics.api.BlockchainSdkErrorEventHandler import com.tangem.tap.common.analytics.api.CardSdkErrorEventHandler +import com.tangem.tap.common.analytics.api.ErrorEventHandler import com.tangem.tap.common.analytics.api.ErrorEventLogger +import com.tangem.tap.common.analytics.api.ParamsInterceptor +import com.tangem.tap.common.analytics.api.ParamsInterceptorHolder +import com.tangem.tap.common.analytics.api.SdkErrorEventHandler import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.extensions.filterNotNull import kotlinx.coroutines.CoroutineName @@ -21,18 +27,27 @@ import java.util.concurrent.Executors /** [REDACTED_AUTHOR] */ +interface GlobalAnalyticsEventHandler : AnalyticsEventHandler, + ErrorEventHandler, + SdkErrorEventHandler, + AnalyticsHandlerHolder, + AnalyticsFilterHolder, + ParamsInterceptorHolder { + + fun send(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null) +} + object Analytics : GlobalAnalyticsEventHandler { - internal val analyticsScope: CoroutineScope by lazy { createScope() } + private val analyticsScope: CoroutineScope by lazy { createScope() } - private val eventFilters = mutableListOf() private val handlers = mutableMapOf() + private val analyticsFilters = mutableSetOf() + private val paramsInterceptors = mutableMapOf() private val analyticsHandlers: List get() = handlers.values.toList() - private val attachToAllEventsParams: MutableMap = mutableMapOf() - override fun id(): String = analyticsHandlers.joinToString(", ") { it.id() } override fun addHandler(name: String, handler: AnalyticsEventHandler) { @@ -44,33 +59,39 @@ object Analytics : GlobalAnalyticsEventHandler { } override fun addFilter(filter: AnalyticsEventFilter) { - eventFilters.add(filter) + analyticsFilters.add(filter) } - override fun removeFilter(filter: AnalyticsEventFilter) { - eventFilters.remove(filter) + override fun removeFilter(filter: AnalyticsEventFilter): Boolean { + return analyticsFilters.remove(filter) } - override fun attachToAllEvents(key: String, value: String) { - attachToAllEventsParams[key] = value + override fun addParamsInterceptor(interceptor: ParamsInterceptor) { + paramsInterceptors[interceptor.id()] = interceptor + } + + override fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor? { + return paramsInterceptors.remove(interceptor.id()) } override fun send(event: String, params: Map) { analyticsScope.launch { - analyticsHandlers.forEach { it.send(event, params) } + analyticsHandlers.forEach { it.send(event, params.interceptParams()) } } } override fun send(event: AnalyticsEvent, card: Card?, blockchain: String?) { analyticsScope.launch { - val eventFilter = eventFilters.firstOrNull { it.canBeAppliedTo(event) } + val eventString = prepareEventString(event.category, event.event) + val eventParams = prepareParams(card, blockchain, event.interceptParams()) + val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) } when { - eventFilter == null -> analyticsHandlers.forEach { it.send(event, card, blockchain) } + eventFilter == null -> analyticsHandlers.forEach { it.send(eventString, eventParams) } eventFilter.canBeSent(event) -> { analyticsHandlers - .filter { eventFilter.canBeConsumedBy(it, event) } - .forEach { it.send(event) } + .filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) } + .forEach { it.send(eventString, eventParams) } } } } @@ -84,7 +105,7 @@ object Analytics : GlobalAnalyticsEventHandler { ) { analyticsScope.launch { analyticsHandlers.forEach { - it.handleAnalyticsEvent(event, params, card, blockchain) + it.handleAnalyticsEvent(event, params.interceptParams(), card, blockchain) } } } @@ -123,17 +144,21 @@ object Analytics : GlobalAnalyticsEventHandler { } } - override fun prepareParams(card: Card?, blockchain: String?, params: Map): Map { - return super.prepareParams(card, blockchain, params).toMutableMap().apply { - putAll(attachToAllEventsParams) - } + private fun AnalyticsEvent.interceptParams(): Map = params.interceptParams() + + private fun Map.interceptParams(): Map { + return this.toMutableMap().apply { interceptParams() } + } + + private fun MutableMap.interceptParams() { + paramsInterceptors.values.forEach { it.intercept(this) } } private fun createScope(): CoroutineScope { val name = "Analytics" val dispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() - val exceptionHandler = FeatureCoroutineExceptionHandler.create(name) - return CoroutineScope(Job() + dispatcher + CoroutineName(name) + exceptionHandler) + val exHandler = FeatureCoroutineExceptionHandler.create(name) + return CoroutineScope(Job() + dispatcher + CoroutineName(name) + exHandler) } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt deleted file mode 100644 index 287f991527..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsEventHandler.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.tap.common.analytics - -import com.tangem.tap.common.analytics.api.AnalyticsEventFilter -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler -import com.tangem.tap.common.analytics.api.ErrorEventHandler -import com.tangem.tap.common.analytics.api.SdkErrorEventHandler - -interface GlobalAnalyticsEventHandler : AnalyticsEventHandler, - ErrorEventHandler, - SdkErrorEventHandler { - - fun addHandler(name: String, handler: AnalyticsEventHandler) - - fun removeHandler(name: String): AnalyticsEventHandler? - - fun addFilter(filter: AnalyticsEventFilter) - - fun removeFilter(filter: AnalyticsEventFilter) - - fun attachToAllEvents(key: String, value: String) -} diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/ErrorEventHandlerApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/ErrorEventHandlerApi.kt new file mode 100644 index 0000000000..3279075ea4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/ErrorEventHandlerApi.kt @@ -0,0 +1,37 @@ +package com.tangem.tap.common.analytics.api + +import com.tangem.blockchain.common.BlockchainError +import com.tangem.common.card.Card +import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.analytics.AnalyticsAnOld +import com.tangem.tap.common.analytics.AnalyticsParamAnOld + +/** +[REDACTED_AUTHOR] + */ +interface ErrorEventHandler { + fun send( + error: Throwable, + params: Map = emptyMap(), + ) +} + +interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler + +interface CardSdkErrorEventHandler { + fun send( + error: TangemSdkError, + action: AnalyticsAnOld.ActionToLog, + params: Map = emptyMap(), + card: Card? = null, + ) +} + +interface BlockchainSdkErrorEventHandler { + fun send( + error: BlockchainError, + action: AnalyticsAnOld.ActionToLog, + params: Map = mapOf(), + card: Card? = null, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt index a4e93fa75b..c86313164b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt @@ -8,5 +8,10 @@ import com.tangem.tap.common.analytics.events.AnalyticsEvent interface AnalyticsEventFilter { fun canBeAppliedTo(event: AnalyticsEvent): Boolean fun canBeSent(event: AnalyticsEvent): Boolean - fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean + fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean +} + +interface AnalyticsFilterHolder { + fun addFilter(filter: AnalyticsEventFilter) + fun removeFilter(filter: AnalyticsEventFilter): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt index 494747287e..5095d83dfa 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt @@ -1,15 +1,10 @@ package com.tangem.tap.common.analytics.api import android.app.Application -import com.tangem.blockchain.common.BlockchainError import com.tangem.common.card.Card -import com.tangem.common.core.TangemSdkError import com.tangem.common.json.MoshiJsonConverter import com.tangem.domain.common.AnalyticsHandlersLogConfig -import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.AnalyticsEventAnOld -import com.tangem.tap.common.analytics.AnalyticsParamAnOld -import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.configurable.config.Config @@ -22,13 +17,6 @@ interface AnalyticsEventHandler { fun send(event: String, params: Map = emptyMap()) - fun send(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null) { - send( - event = prepareEventString(event.category, event.event), - params = prepareParams(card, blockchain, event.params), - ) - } - @Deprecated("Migrate to AnalyticsEvent") fun handleAnalyticsEvent( event: AnalyticsEventAnOld, @@ -45,7 +33,6 @@ interface AnalyticsEventHandler { params: Map = emptyMap(), ): Map = mapOf( AnalyticsParam.Firmware to card?.firmwareVersion?.stringValue, - AnalyticsParam.BatchId to card?.batchId, AnalyticsParam.Blockchain to blockchain, ).filterNotNull() + params @@ -54,31 +41,9 @@ interface AnalyticsEventHandler { } } -interface ErrorEventHandler { - fun send( - error: Throwable, - params: Map = emptyMap(), - ) -} - -interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler - -interface CardSdkErrorEventHandler { - fun send( - error: TangemSdkError, - action: AnalyticsAnOld.ActionToLog, - params: Map = emptyMap(), - card: Card? = null, - ) -} - -interface BlockchainSdkErrorEventHandler { - fun send( - error: BlockchainError, - action: AnalyticsAnOld.ActionToLog, - params: Map = mapOf(), - card: Card? = null, - ) +interface AnalyticsHandlerHolder { + fun addHandler(name: String, handler: AnalyticsEventHandler) + fun removeHandler(name: String): AnalyticsEventHandler? } interface AnalyticsHandlerBuilder { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/ParamsInterceptorApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/ParamsInterceptorApi.kt new file mode 100644 index 0000000000..245ef32da5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/ParamsInterceptorApi.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.common.analytics.api + +import com.tangem.tap.common.analytics.events.AnalyticsEvent + +/** +[REDACTED_AUTHOR] + */ +interface ParamsInterceptor { + fun id(): String + fun canBeAppliedTo(event: AnalyticsEvent): Boolean + fun intercept(params: MutableMap) +} + +interface ParamsInterceptorHolder { + fun addParamsInterceptor(interceptor: ParamsInterceptor) + fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor? +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 71e6eb6c09..0d76d1dd08 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -55,8 +55,10 @@ sealed class AnalyticsParam { } companion object Key { + const val BatchId = "Batch" + + // old keys const val Blockchain = "blockchain" - const val BatchId = "batch_id" const val Firmware = "firmware" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index f1e3927530..229093235d 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -17,7 +17,7 @@ sealed class Basic( params = mapOf( "State" to state.value, "Currency" to currency.value, - "Batch" to batch, + AnalyticsParam.BatchId to batch, ), ) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt index c885cf7ae6..372f2787a3 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt @@ -23,5 +23,5 @@ class BasicSignInFilter : AnalyticsEventFilter { return canBeSent } - override fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true + override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt index 5b0638231e..fea71037d9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt @@ -39,7 +39,7 @@ class BasicTopUpFilter( } } - override fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true + override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true data class Data( val walletId: String, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt index 5267782ddc..945dbefab4 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt @@ -16,7 +16,7 @@ class ShopPurchasedEventFilter : AnalyticsEventFilter { override fun canBeSent(event: AnalyticsEvent): Boolean = true - override fun canBeConsumedBy(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean { + override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean { return when (handler) { is AppsFlyerAnalyticsHandler, is FirebaseAnalyticsHandler -> true else -> false diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt new file mode 100644 index 0000000000..ffe48cf198 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.common.analytics.paramsInterceptor + +import com.tangem.tap.common.analytics.api.ParamsInterceptor +import com.tangem.tap.common.analytics.events.AnalyticsEvent +import com.tangem.tap.common.analytics.events.AnalyticsParam + +/** +[REDACTED_AUTHOR] + */ +class BatchIdParamsInterceptor( + val batchId: String, +) : ParamsInterceptor { + + override fun id(): String = this::class.java.simpleName + + override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true + + override fun intercept(params: MutableMap) { + params[AnalyticsParam.BatchId] = batchId + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 0148a2cbf7..16d6ee9efd 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -13,8 +13,8 @@ import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.analytics.GetCardSourceParamsAnOld -import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.IntroductionProcess +import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOpenUrl @@ -165,7 +165,7 @@ private fun onScanSuccess(scanResponse: ScanResponse) { val tapWalletManager = globalState.tapWalletManager tapWalletManager.updateConfigManager(scanResponse) - Analytics.attachToAllEvents(AnalyticsParam.BatchId, scanResponse.card.batchId) + Analytics.addParamsInterceptor(BatchIdParamsInterceptor(scanResponse.card.batchId)) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) From fec0f149526f4e4818bf7036a482fc7a5b02ce48 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 8 Nov 2022 18:37:46 +0500 Subject: [PATCH 042/176] Updated on 2026-08-14 --- .../com/tangem/tap/common/analytics/Analytics.kt | 4 ++-- .../tap/common/analytics/api/EventFilterApi.kt | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt index 3950438219..276ef9059f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt @@ -87,11 +87,11 @@ object Analytics : GlobalAnalyticsEventHandler { val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) } when { - eventFilter == null -> analyticsHandlers.forEach { it.send(eventString, eventParams) } + eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(eventString, eventParams) } eventFilter.canBeSent(event) -> { analyticsHandlers .filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) } - .forEach { it.send(eventString, eventParams) } + .forEach { handler -> handler.send(eventString, eventParams) } } } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt index c86313164b..eb0f9587ce 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt @@ -4,10 +4,23 @@ import com.tangem.tap.common.analytics.events.AnalyticsEvent /** [REDACTED_AUTHOR] + * The filter is used to separate events to send and determine which handler it can be processed with. */ interface AnalyticsEventFilter { + /** + * Recognizes the event and checks if the given filter can be applied to the event. + */ fun canBeAppliedTo(event: AnalyticsEvent): Boolean + + /** + * An internal filter check that, on external or internal conditions, recognizes the possibility of + * sending an event. + */ fun canBeSent(event: AnalyticsEvent): Boolean + + /** + * Performs a check to see if the event can be dispatched by a specific handler + */ fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean } From 5030c571c5f297b2f963f1bc54517e3f3e6e7625 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 9 Nov 2022 06:08:40 +0500 Subject: [PATCH 043/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 23 ++- .../tangem/tap/common/analytics/Analytics.kt | 145 +++--------------- .../common/analytics/AnalyticsEventAnOld.kt | 71 --------- .../tap/common/analytics/AnalyticsFactory.kt | 37 +++++ .../analytics/AnalyticsHandlersFactory.kt | 23 --- .../analytics/api/ErrorEventHandlerApi.kt | 37 ----- .../common/analytics/api/EventFilterApi.kt | 2 +- .../common/analytics/api/EventHandlerApi.kt | 43 ++---- .../converters/AnalyticsErrorConverter.kt | 70 +++++++++ .../converters/SdkErrorConverters.kt | 88 ----------- .../common/analytics/events/AnalyticsEvent.kt | 3 +- .../common/analytics/events/AnalyticsParam.kt | 13 +- .../tap/common/analytics/events/Basic.kt | 8 +- .../tap/common/analytics/events/Settings.kt | 15 +- .../tap/common/analytics/events/Token.kt | 9 +- .../common/analytics/events/WalletConnect.kt | 6 +- .../analytics/filters/BasicSignInFilter.kt | 4 +- .../analytics/filters/BasicTopUpFilter.kt | 4 +- .../filters/ShopPurchasedEventFilter.kt | 4 +- .../amplitude/AmplitudeAnalyticsHandler.kt | 6 +- .../appsFlyer/AppsFlyerAnalyticsHandler.kt | 6 +- .../firebase/FirebaseAnalyticsHandler.kt | 59 +++---- .../com/tangem/tap/domain/TangemSdkManager.kt | 32 +--- .../tap/domain/twins/TwinCardsManager.kt | 44 +----- .../walletconnect/WalletConnectManager.kt | 52 +++---- .../walletconnect/WalletConnectSdkHelper.kt | 41 ++--- .../details/redux/DetailsMiddleware.kt | 28 +--- .../tap/features/home/redux/HomeMiddleware.kt | 7 - .../send/redux/middlewares/SendMiddleware.kt | 20 +-- .../redux/middlewares/WalletMiddleware.kt | 12 +- .../redux/middlewares/WarningsMiddleware.kt | 34 ++-- .../ui/adapters/WarningMessagesAdapter.kt | 10 +- 32 files changed, 298 insertions(+), 658 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventAnOld.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandlersFactory.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/api/ErrorEventHandlerApi.kt create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index be1837114f..568ca4af60 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -15,7 +15,7 @@ import com.tangem.network.common.MoshiConverter import com.tangem.tap.common.AndroidAssetReader import com.tangem.tap.common.AssetReader import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsHandlersFactory +import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.filters.BasicSignInFilter import com.tangem.tap.common.analytics.filters.BasicTopUpFilter @@ -110,10 +110,14 @@ class TapApplication : Application(), ImageLoaderFactory { } private fun initAnalytics(application: Application, config: Config) { - val factory = AnalyticsHandlersFactory() - factory.addBuilder(AmplitudeAnalyticsHandler.Builder()) - factory.addBuilder(AppsFlyerAnalyticsHandler.Builder()) - factory.addBuilder(FirebaseAnalyticsHandler.Builder()) + val factory = AnalyticsFactory() + factory.addHandlerBuilder(AmplitudeAnalyticsHandler.Builder()) + factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder()) + factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder()) + + factory.addFilter(ShopPurchasedEventFilter()) + factory.addFilter(BasicSignInFilter()) + factory.addFilter(BasicTopUpFilter(preferencesStorage.toppedUpWalletStorage)) val buildData = AnalyticsHandlerBuilder.Data( application = application, @@ -122,14 +126,7 @@ class TapApplication : Application(), ImageLoaderFactory { logConfig = LogConfig.analyticsHandlers, jsonConverter = MoshiJsonConverter.INSTANCE, ) - factory.build(buildData).forEach { - Analytics.addHandler(it.id(), it) - } - listOf( - ShopPurchasedEventFilter(), - BasicSignInFilter(), - BasicTopUpFilter(preferencesStorage.toppedUpWalletStorage), - ).forEach { Analytics.addFilter(it) } + factory.build(Analytics, buildData) } private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt index 276ef9059f..03cb8437bb 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt @@ -1,22 +1,14 @@ package com.tangem.tap.common.analytics -import com.tangem.blockchain.common.BlockchainError -import com.tangem.common.card.Card -import com.tangem.common.core.TangemSdkError import com.tangem.domain.common.FeatureCoroutineExceptionHandler import com.tangem.tap.common.analytics.api.AnalyticsEventFilter import com.tangem.tap.common.analytics.api.AnalyticsEventHandler import com.tangem.tap.common.analytics.api.AnalyticsFilterHolder +import com.tangem.tap.common.analytics.api.AnalyticsHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerHolder -import com.tangem.tap.common.analytics.api.BlockchainSdkErrorEventHandler -import com.tangem.tap.common.analytics.api.CardSdkErrorEventHandler -import com.tangem.tap.common.analytics.api.ErrorEventHandler -import com.tangem.tap.common.analytics.api.ErrorEventLogger import com.tangem.tap.common.analytics.api.ParamsInterceptor import com.tangem.tap.common.analytics.api.ParamsInterceptorHolder -import com.tangem.tap.common.analytics.api.SdkErrorEventHandler import com.tangem.tap.common.analytics.events.AnalyticsEvent -import com.tangem.tap.common.extensions.filterNotNull import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -28,33 +20,26 @@ import java.util.concurrent.Executors [REDACTED_AUTHOR] */ interface GlobalAnalyticsEventHandler : AnalyticsEventHandler, - ErrorEventHandler, - SdkErrorEventHandler, AnalyticsHandlerHolder, AnalyticsFilterHolder, - ParamsInterceptorHolder { - - fun send(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null) -} + ParamsInterceptorHolder object Analytics : GlobalAnalyticsEventHandler { private val analyticsScope: CoroutineScope by lazy { createScope() } - private val handlers = mutableMapOf() - private val analyticsFilters = mutableSetOf() + private val handlers = mutableMapOf() private val paramsInterceptors = mutableMapOf() + private val analyticsFilters = mutableSetOf() - private val analyticsHandlers: List + private val analyticsHandlers: List get() = handlers.values.toList() - override fun id(): String = analyticsHandlers.joinToString(", ") { it.id() } - - override fun addHandler(name: String, handler: AnalyticsEventHandler) { + override fun addHandler(name: String, handler: AnalyticsHandler) { handlers[name] = handler } - override fun removeHandler(name: String): AnalyticsEventHandler? { + override fun removeHandler(name: String): AnalyticsHandler? { return handlers.remove(name) } @@ -74,84 +59,29 @@ object Analytics : GlobalAnalyticsEventHandler { return paramsInterceptors.remove(interceptor.id()) } - override fun send(event: String, params: Map) { + override fun send(event: AnalyticsEvent) { analyticsScope.launch { - analyticsHandlers.forEach { it.send(event, params.interceptParams()) } - } - } - - override fun send(event: AnalyticsEvent, card: Card?, blockchain: String?) { - analyticsScope.launch { - val eventString = prepareEventString(event.category, event.event) - val eventParams = prepareParams(card, blockchain, event.interceptParams()) - + event.params = applyParamsInterceptors(event) val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) } + when { - eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(eventString, eventParams) } + eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(event) } eventFilter.canBeSent(event) -> { analyticsHandlers .filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) } - .forEach { handler -> handler.send(eventString, eventParams) } + .forEach { handler -> handler.send(event) } } } } } - override fun handleAnalyticsEvent( - event: AnalyticsEventAnOld, - params: Map, - card: Card?, - blockchain: String?, - ) { - analyticsScope.launch { - analyticsHandlers.forEach { - it.handleAnalyticsEvent(event, params.interceptParams(), card, blockchain) - } - } - } + private fun applyParamsInterceptors(event: AnalyticsEvent): MutableMap { + val interceptedParams = event.params.toMutableMap() + paramsInterceptors.values + .filter { it.canBeAppliedTo(event) } + .forEach { it.intercept(interceptedParams) } - override fun send(error: Throwable, params: Map) { - analyticsScope.launch { - analyticsHandlers.filterIsInstance().forEach { - it.logErrorEvent(error, params) - } - } - } - - override fun send( - error: TangemSdkError, - action: AnalyticsAnOld.ActionToLog, - params: Map, - card: Card?, - ) { - analyticsScope.launch { - analyticsHandlers.filterIsInstance().forEach { - it.send(error, action, params, card) - } - } - } - - override fun send( - error: BlockchainError, - action: AnalyticsAnOld.ActionToLog, - params: Map, - card: Card?, - ) { - analyticsScope.launch { - analyticsHandlers.filterIsInstance().forEach { - it.send(error, action, params, card) - } - } - } - - private fun AnalyticsEvent.interceptParams(): Map = params.interceptParams() - - private fun Map.interceptParams(): Map { - return this.toMutableMap().apply { interceptParams() } - } - - private fun MutableMap.interceptParams() { - paramsInterceptors.values.forEach { it.intercept(this) } + return interceptedParams } private fun createScope(): CoroutineScope { @@ -160,43 +90,4 @@ object Analytics : GlobalAnalyticsEventHandler { val exHandler = FeatureCoroutineExceptionHandler.create(name) return CoroutineScope(Job() + dispatcher + CoroutineName(name) + exHandler) } -} - -fun GlobalAnalyticsEventHandler.logWcEvent(event: AnalyticsAnOld.WcAnalyticsEvent) { - when (event) { - is AnalyticsAnOld.WcAnalyticsEvent.Action -> { - handleAnalyticsEvent( - event = AnalyticsEventAnOld.WC_SUCCESS_RESPONSE, - params = mapOf( - AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action.name, - ), - ) - } - is AnalyticsAnOld.WcAnalyticsEvent.Error -> { - val params = mapOf( - AnalyticsParamAnOld.WALLET_CONNECT_ACTION.param to event.action?.name, - AnalyticsParamAnOld.ERROR_DESCRIPTION.param to event.error.message, - ).filterNotNull() - send(event.error, params) - } - is AnalyticsAnOld.WcAnalyticsEvent.InvalidRequest -> - handleAnalyticsEvent( - event = AnalyticsEventAnOld.WC_INVALID_REQUEST, - params = mapOf( - AnalyticsParamAnOld.WALLET_CONNECT_REQUEST.param to event.json, - ).filterNotNull(), - ) - is AnalyticsAnOld.WcAnalyticsEvent.Session -> { - val analyticsEvent = when (event.event) { - AnalyticsAnOld.WcSessionEvent.Disconnect -> AnalyticsEventAnOld.WC_SESSION_DISCONNECTED - AnalyticsAnOld.WcSessionEvent.Connect -> AnalyticsEventAnOld.WC_NEW_SESSION - } - handleAnalyticsEvent( - event = analyticsEvent, - params = mapOf( - AnalyticsParamAnOld.WALLET_CONNECT_DAPP_URL.param to event.url, - ).filterNotNull(), - ) - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventAnOld.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventAnOld.kt deleted file mode 100644 index 41cc0d9b96..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEventAnOld.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.tangem.tap.common.analytics - -enum class AnalyticsEventAnOld(val event: String) { - CARD_IS_SCANNED("card_is_scanned"), - TRANSACTION_IS_SENT("transaction_is_sent"), - READY_TO_SCAN("ready_to_scan"), - DEMO_MODE_ACTIVATED("demo_mode_activated"), - - APP_RATING_DISPLAYED("rate_app_warning_displayed"), - APP_RATING_DISMISS("dismiss_rate_app_warning"), - APP_RATING_NEGATIVE("negative_rate_app_feedback"), - APP_RATING_POSITIVE("positive_rate_app_feedback"), - - WC_SUCCESS_RESPONSE("wallet_connect_success_response"), - WC_INVALID_REQUEST("wallet_connect_invalid_request"), - WC_NEW_SESSION("wallet_connect_new_session"), - WC_SESSION_DISCONNECTED("wallet_connect_session_disconnected"), - - GET_CARD("get_card"), -} - -enum class AnalyticsParamAnOld(val param: String) { - BLOCKCHAIN("blockchain"), - CARD_ID("cardId"), - BATCH_ID("batch_id"), - FIRMWARE("firmware"), - ACTION("action"), - ERROR_DESCRIPTION("error_description"), - ERROR_CODE("error_code"), - NEW_SECURITY_OPTION("new_security_option"), - ERROR_KEY("Tangem SDK error key"), - - WALLET_CONNECT_ACTION("wallet_connect_action"), - WALLET_CONNECT_REQUEST("wallet_connect_request"), - WALLET_CONNECT_DAPP_URL("wallet_connect_dapp_url"), - - SOURCE("source"), -} - -enum class GetCardSourceParamsAnOld(val param: String) { - WELCOME("welcome"), - ONBOARDING_BUY_MORE("wallet_onboaring_buy_more_cards"), - ONBOARDING("wallet_onboarding") -} - -object AnalyticsAnOld { - enum class ActionToLog(val key: String) { - Scan("tap_scan_task"), - SendTransaction("send_transaction"), - WalletConnectSign("wallet_connect_personal_sign"), - WalletConnectTransaction("wallet_connect_tx_sign"), - ReadPinSettings("read_pin_settings"), - ChangeSecOptions("change_sec_options"), - CreateWallet("create_wallet"), - PurgeWallet("purge_wallet"), - WriteIssuerData("write_issuer_data"), - } - - enum class WcSessionEvent { Disconnect, Connect } - - enum class WcAction { PersonalSign, SignTransaction, SendTransaction } - - sealed class WcAnalyticsEvent { - data class Error(val error: Throwable, val action: WcAction?) : - WcAnalyticsEvent() - - data class Session(val event: WcSessionEvent, val url: String?) : WcAnalyticsEvent() - data class Action(val action: WcAction) : WcAnalyticsEvent() - data class InvalidRequest(val json: String?) : WcAnalyticsEvent() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt new file mode 100644 index 0000000000..8936adfc3c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsFactory.kt @@ -0,0 +1,37 @@ +package com.tangem.tap.common.analytics + +import com.tangem.tap.common.analytics.api.AnalyticsEventFilter +import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder +import com.tangem.tap.common.analytics.api.ParamsInterceptor + +/** +[REDACTED_AUTHOR] + */ +class AnalyticsFactory { + + private val builders = mutableListOf() + private val filters = mutableListOf() + private val interceptors = mutableListOf() + + fun addHandlerBuilder(builder: AnalyticsHandlerBuilder) { + builders.add(builder) + } + + fun addFilter(filter: AnalyticsEventFilter) { + filters.add(filter) + } + + fun addParamsInterceptor(interceptor: ParamsInterceptor) { + interceptors.add(interceptor) + } + + fun build(analytics: Analytics, data: AnalyticsHandlerBuilder.Data) { + builders.mapNotNull { it.build(data) }.forEach { analytics.addHandler(it.id(), it) } + filters.forEach { analytics.addFilter(it) } + interceptors.forEach { analytics.addParamsInterceptor(it) } + + builders.clear() + filters.clear() + interceptors.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandlersFactory.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandlersFactory.kt deleted file mode 100644 index a287f6fac2..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandlersFactory.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.tap.common.analytics - -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler -import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder - -/** -[REDACTED_AUTHOR] - */ -class AnalyticsHandlersFactory { - - private val builders = mutableListOf() - - fun addBuilder(builder: AnalyticsHandlerBuilder) { - builders.add(builder) - } - - fun build(data: AnalyticsHandlerBuilder.Data): List { - val handlers = builders.mapNotNull { it.build(data) } - builders.clear() - - return handlers - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/ErrorEventHandlerApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/ErrorEventHandlerApi.kt deleted file mode 100644 index 3279075ea4..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/ErrorEventHandlerApi.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.tap.common.analytics.api - -import com.tangem.blockchain.common.BlockchainError -import com.tangem.common.card.Card -import com.tangem.common.core.TangemSdkError -import com.tangem.tap.common.analytics.AnalyticsAnOld -import com.tangem.tap.common.analytics.AnalyticsParamAnOld - -/** -[REDACTED_AUTHOR] - */ -interface ErrorEventHandler { - fun send( - error: Throwable, - params: Map = emptyMap(), - ) -} - -interface SdkErrorEventHandler : CardSdkErrorEventHandler, BlockchainSdkErrorEventHandler - -interface CardSdkErrorEventHandler { - fun send( - error: TangemSdkError, - action: AnalyticsAnOld.ActionToLog, - params: Map = emptyMap(), - card: Card? = null, - ) -} - -interface BlockchainSdkErrorEventHandler { - fun send( - error: BlockchainError, - action: AnalyticsAnOld.ActionToLog, - params: Map = mapOf(), - card: Card? = null, - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt index eb0f9587ce..04d6dbbb01 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventFilterApi.kt @@ -21,7 +21,7 @@ interface AnalyticsEventFilter { /** * Performs a check to see if the event can be dispatched by a specific handler */ - fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean + fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean } interface AnalyticsFilterHolder { diff --git a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt index 5095d83dfa..344402f7f1 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/api/EventHandlerApi.kt @@ -1,53 +1,44 @@ package com.tangem.tap.common.analytics.api import android.app.Application -import com.tangem.common.card.Card import com.tangem.common.json.MoshiJsonConverter import com.tangem.domain.common.AnalyticsHandlersLogConfig -import com.tangem.tap.common.analytics.AnalyticsEventAnOld -import com.tangem.tap.common.analytics.events.AnalyticsParam -import com.tangem.tap.common.extensions.filterNotNull +import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.domain.configurable.config.Config /** [REDACTED_AUTHOR] */ interface AnalyticsEventHandler { + fun send(event: AnalyticsEvent) +} + +interface AnalyticsHandler : AnalyticsEventHandler { fun id(): String fun send(event: String, params: Map = emptyMap()) - @Deprecated("Migrate to AnalyticsEvent") - fun handleAnalyticsEvent( - event: AnalyticsEventAnOld, - params: Map = emptyMap(), - card: Card? = null, - blockchain: String? = null, - ) { - send(event.event, prepareParams(card, blockchain, params)) + override fun send(event: AnalyticsEvent) { + send(prepareEventString(event), event.params) } - fun prepareParams( - card: Card? = null, - blockchain: String? = null, - params: Map = emptyMap(), - ): Map = mapOf( - AnalyticsParam.Firmware to card?.firmwareVersion?.stringValue, - AnalyticsParam.Blockchain to blockchain, - ).filterNotNull() + params + fun prepareEventString(event: AnalyticsEvent): String = "[${event.category}] ${event.event}" +} - fun prepareEventString(category: String, event: String): String { - return "[$category] $event" - } +interface ErrorEventHandler { + fun send( + error: Throwable, + params: Map = emptyMap(), + ) } interface AnalyticsHandlerHolder { - fun addHandler(name: String, handler: AnalyticsEventHandler) - fun removeHandler(name: String): AnalyticsEventHandler? + fun addHandler(name: String, handler: AnalyticsHandler) + fun removeHandler(name: String): AnalyticsHandler? } interface AnalyticsHandlerBuilder { - fun build(data: Data): AnalyticsEventHandler? + fun build(data: Data): AnalyticsHandler? data class Data( val application: Application, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt new file mode 100644 index 0000000000..b6fab47d07 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/AnalyticsErrorConverter.kt @@ -0,0 +1,70 @@ +package com.tangem.tap.common.analytics.converters + +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.common.Converter +import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.features.demo.DemoTransactionSender + +/** +[REDACTED_AUTHOR] + */ +class AnalyticsErrorConverter : Converter> { + fun canBeHandled(error: Throwable): Boolean { + return when (error) { + is TangemSdkError.UserCancelled -> false + is BlockchainSdkError -> !error.customMessage.contains(DemoTransactionSender.ID) + else -> true + } + } + + override fun convert(value: Throwable): Map { + return when (value) { + is BlockchainSdkError -> BlockchainSdkErrorConverter(CardSdkErrorConverter()).convert(value) + is TangemSdkError -> CardSdkErrorConverter().convert(value) + else -> ThrowableErrorConverter().convert(value) + } + } +} + +private class ThrowableErrorConverter : Converter> { + override fun convert(value: Throwable): Map { + val unknown = "unknown" + return mapOf( + AnalyticsParam.ErrorKey to AnalyticsParam.Error.App.value, + AnalyticsParam.ErrorDescription to (value.message ?: value.cause?.message ?: unknown), + ) + } +} + +class CardSdkErrorConverter : Converter> { + + override fun convert(value: TangemSdkError): Map { + if (value is TangemSdkError.UserCancelled) return emptyMap() + + return mapOf( + AnalyticsParam.ErrorKey to AnalyticsParam.Error.CardSdk.value, + AnalyticsParam.ErrorCode to value.code.toString(), + AnalyticsParam.ErrorDescription to value.toString(), + ) + } +} + +private class BlockchainSdkErrorConverter( + private val cardSdkErrorConverter: CardSdkErrorConverter, +) : Converter> { + + override fun convert(value: BlockchainSdkError): Map { + if (value.customMessage.contains(DemoTransactionSender.ID)) return emptyMap() + + if (value is BlockchainSdkError.WrappedTangemError) { + return (value.tangemError as? TangemSdkError)?.let { cardSdkErrorConverter.convert(it) } ?: emptyMap() + } + + return mapOf( + AnalyticsParam.ErrorKey to AnalyticsParam.Error.BlockchainSdk.value, + AnalyticsParam.ErrorCode to value.code.toString(), + AnalyticsParam.ErrorDescription to "${value.javaClass.simpleName}: ${value.customMessage}", + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt deleted file mode 100644 index 5229df8174..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/SdkErrorConverters.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.tangem.tap.common.analytics.converters - -import com.tangem.blockchain.common.BlockchainError -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.common.Converter -import com.tangem.common.core.TangemSdkError -import com.tangem.tap.common.analytics.AnalyticsAnOld -import com.tangem.tap.common.analytics.AnalyticsParamAnOld -import com.tangem.tap.common.analytics.TangemSdk -import com.tangem.tap.features.demo.DemoTransactionSender - -/** -[REDACTED_AUTHOR] - */ -internal data class ErrorEvent( - val throwable: Throwable, - val params: Map, -) - -internal class CardSdkErrorConverter : Converter { - - override fun convert(value: Model): ErrorEvent? { - if (value.error is TangemSdkError.UserCancelled) return null - - val mutableParams = value.params.convertToParamValue().toMutableMap().apply { - putAll(value.cardParams) - } - mutableParams[AnalyticsParamAnOld.ACTION.param] = value.action.key - mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = value.error.code.toString() - mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = value.error.javaClass.simpleName - mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "TangemSdkError" - - return ErrorEvent(TangemSdk.map(value.error), mutableParams) - } - - internal data class Model( - val error: TangemSdkError, - val action: AnalyticsAnOld.ActionToLog, - val params: Map, - val cardParams: Map, - ) -} - -internal class BlockchainSdkErrorConverter( - private val cardSdkErrorConverter: CardSdkErrorConverter, -) : Converter { - - override fun convert(value: Model): ErrorEvent? { - val error = (value.error as? BlockchainSdkError) ?: return null - - if (error is BlockchainSdkError.WrappedTangemError) { - val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return null - - val cardSdkModel = CardSdkErrorConverter.Model( - error = tangemSdkError, - action = value.action, - params = value.params, - cardParams = value.cardParams, - ) - return cardSdkErrorConverter.convert(cardSdkModel) - } - - if (error.customMessage.contains(DemoTransactionSender.ID)) return null - - val mutableParams = value.params.convertToParamValue().toMutableMap() - mutableParams[AnalyticsParamAnOld.ACTION.param] = value.action.key - mutableParams[AnalyticsParamAnOld.ERROR_CODE.param] = error.code.toString() - mutableParams[AnalyticsParamAnOld.ERROR_DESCRIPTION.param] = "${error.javaClass.simpleName}: ${error.customMessage}" - mutableParams[AnalyticsParamAnOld.ERROR_KEY.param] = "BlockchainSdkError" - - return ErrorEvent(value.error, mutableParams) - } - - data class Model( - val error: BlockchainError, - val action: AnalyticsAnOld.ActionToLog, - val params: Map, - val cardParams: Map, - ) -} - -internal fun Map.convertToParamValue(): Map { - return mutableMapOf().also { newMap -> - this.forEach { - newMap[it.key.param] = it.value - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt index 8e5cf0e942..b2db44fe52 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsEvent.kt @@ -6,6 +6,7 @@ package com.tangem.tap.common.analytics.events sealed class AnalyticsEvent( val category: String, val event: String, - val params: Map = mapOf(), + var params: Map = mapOf(), + val error: Throwable? = null, var filterData: Any? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 0d76d1dd08..bad36d3f0d 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -54,11 +54,16 @@ sealed class AnalyticsParam { } } + sealed class Error(val value: String) { + object App : Error("App Error") + object CardSdk : Error("Card Sdk Error") + object BlockchainSdk : Error("Blockchain Sdk Error") + } + companion object Key { const val BatchId = "Batch" - - // old keys - const val Blockchain = "blockchain" - const val Firmware = "firmware" + const val ErrorDescription = "Error Description" + const val ErrorCode = "Error Code" + const val ErrorKey = "Error Key" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index 229093235d..80afe6d3f8 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -6,7 +6,8 @@ package com.tangem.tap.common.analytics.events sealed class Basic( event: String, params: Map = mapOf(), -) : AnalyticsEvent("Basic", event, params) { + error: Throwable? = null, +) : AnalyticsEvent("Basic", event, params, error) { class SignedIn( state: AnalyticsParam.CardBalanceState, @@ -25,4 +26,9 @@ sealed class Basic( event = "Topped up", params = mapOf("Currency" to currency.value), ) + + class ScanError(error: Throwable) : Basic( + event = "Scan", + error = error, + ) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index b24d6ef042..38725048a7 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -9,7 +9,8 @@ sealed class Settings( category: String = "Settings", event: String, params: Map = mapOf(), -) : AnalyticsEvent(category, event, params) { + error: Throwable? = null, +) : AnalyticsEvent(category, event, params, error) { class ScreenOpened : Settings(event = "Settings Screen Opened") class ButtonChat : Settings(event = "Button - Chat") @@ -28,10 +29,15 @@ sealed class Settings( sealed class CardSettings( event: String, params: Map = mapOf(), - ) : Settings("Settings / Card Settings", event, params) { + error: Throwable? = null, + ) : Settings("Settings / Card Settings", event, params, error) { class ButtonFactoryReset : CardSettings("Button - Factory Reset") - class FactoryResetFinished : CardSettings("Factory Reset Finished") + class FactoryResetFinished(error: Throwable? = null) : CardSettings( + event = "Factory Reset Finished", + error = error, + ) + class UserCodeChanged : CardSettings("User Code Changed") class ButtonChangeSecurityMode : CardSettings("Button - Change Security Mode") @@ -40,9 +46,10 @@ sealed class Settings( params = mapOf("Type" to type.value), ) - class SecurityModeChanged(mode: AnalyticsParam.SecurityMode) : CardSettings( + class SecurityModeChanged(mode: AnalyticsParam.SecurityMode, error: Throwable? = null) : CardSettings( event = "Security Mode Changed", params = mapOf("Mode" to mode.value), + error = error, ) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index 9da6abe399..7445e31f33 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -9,7 +9,8 @@ sealed class Token( category: String, event: String, params: Map = mapOf(), -) : AnalyticsEvent(category, event, params) { + error: Throwable? = null, +) : AnalyticsEvent(category, event, params, error) { class Refreshed : Token("Token", "Refreshed") class ButtonExplore : Token("Token", "Button - Explore") @@ -57,16 +58,18 @@ sealed class Token( sealed class Send( event: String, params: Map = mapOf(), - ) : Token("Token / Send", event, params) { + error: Throwable? = null, + ) : Token("Token / Send", event, params, error) { class ScreenOpened : Send("Send Screen Opened") class ButtonPaste : Send("Button - Paste") class ButtonQRCode : Send("Button - QR Code") class ButtonSwapCurrency : Send("Button - Swap Currency") - class TransactionSent(type: CurrencyType) : Send( + class TransactionSent(type: CurrencyType, error: Throwable? = null) : Send( event = "Transaction Sent", params = mapOf("Token" to type.value), + error = error, ) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt index 624145c63d..1ff587b2aa 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt @@ -6,9 +6,13 @@ package com.tangem.tap.common.analytics.events sealed class WalletConnect( event: String, params: Map = mapOf(), -) : AnalyticsEvent("Wallet Connect", event, params) { + error: Throwable? = null, +) : AnalyticsEvent("Wallet Connect", event, params, error) { class NewSessionEstablished : WalletConnect("New Session Established") class SessionDisconnected : WalletConnect("Session Disconnected") class RequestSigned : WalletConnect("Request Signed") + + class SignError(error: Throwable) : WalletConnect("Sign", error = error) + class TransactionError(error: Throwable) : WalletConnect("Transaction", error = error) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt index 372f2787a3..a582ea34f1 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicSignInFilter.kt @@ -1,7 +1,7 @@ package com.tangem.tap.common.analytics.filters import com.tangem.tap.common.analytics.api.AnalyticsEventFilter -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandler import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.analytics.events.Basic @@ -23,5 +23,5 @@ class BasicSignInFilter : AnalyticsEventFilter { return canBeSent } - override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true + override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean = true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt index fea71037d9..be780ccd2d 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/BasicTopUpFilter.kt @@ -2,7 +2,7 @@ package com.tangem.tap.common.analytics.filters import com.tangem.common.extensions.guard import com.tangem.tap.common.analytics.api.AnalyticsEventFilter -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandler import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic @@ -39,7 +39,7 @@ class BasicTopUpFilter( } } - override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean = true + override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean = true data class Data( val walletId: String, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt index 945dbefab4..dffa123240 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt @@ -1,7 +1,7 @@ package com.tangem.tap.common.analytics.filters import com.tangem.tap.common.analytics.api.AnalyticsEventFilter -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandler import com.tangem.tap.common.analytics.events.AnalyticsEvent import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler @@ -16,7 +16,7 @@ class ShopPurchasedEventFilter : AnalyticsEventFilter { override fun canBeSent(event: AnalyticsEvent): Boolean = true - override fun canBeConsumedByHandler(handler: AnalyticsEventHandler, event: AnalyticsEvent): Boolean { + override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean { return when (handler) { is AppsFlyerAnalyticsHandler, is FirebaseAnalyticsHandler -> true else -> false 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 06f77c15b3..c76fe21d38 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 @@ -1,11 +1,11 @@ package com.tangem.tap.common.analytics.handlers.amplitude -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder class AmplitudeAnalyticsHandler( private val client: AmplitudeAnalyticsClient, -) : AnalyticsEventHandler { +) : AnalyticsHandler { override fun id(): String = ID @@ -18,7 +18,7 @@ class AmplitudeAnalyticsHandler( } class Builder : AnalyticsHandlerBuilder { - override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when { + override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when { !data.isDebug -> AmplitudeClient(data.application, data.config.amplitudeApiKey) data.isDebug && data.logConfig.amplitude -> AmplitudeLogClient(data.jsonConverter) else -> null 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 b91da358d6..c3968d9d72 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,11 +1,11 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder class AppsFlyerAnalyticsHandler( private val client: AppsFlyerAnalyticsClient, -) : AnalyticsEventHandler { +) : AnalyticsHandler { override fun id(): String = ID @@ -18,7 +18,7 @@ class AppsFlyerAnalyticsHandler( } class Builder : AnalyticsHandlerBuilder { - override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when { + override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when { !data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerDevKey) data.isDebug && data.logConfig.appsFlyer -> AppsFlyerLogClient(data.jsonConverter) else -> null 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 9a4a8bf839..109e61d0a5 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,20 +1,14 @@ package com.tangem.tap.common.analytics.handlers.firebase -import com.tangem.blockchain.common.BlockchainError -import com.tangem.common.card.Card -import com.tangem.common.core.TangemSdkError -import com.tangem.tap.common.analytics.AnalyticsAnOld -import com.tangem.tap.common.analytics.AnalyticsParamAnOld -import com.tangem.tap.common.analytics.api.AnalyticsEventHandler +import com.tangem.tap.common.analytics.api.AnalyticsHandler import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.api.ErrorEventHandler -import com.tangem.tap.common.analytics.api.SdkErrorEventHandler -import com.tangem.tap.common.analytics.converters.BlockchainSdkErrorConverter -import com.tangem.tap.common.analytics.converters.CardSdkErrorConverter +import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter +import com.tangem.tap.common.analytics.events.AnalyticsEvent class FirebaseAnalyticsHandler( private val client: FirebaseAnalyticsClient, -) : AnalyticsEventHandler, ErrorEventHandler, SdkErrorEventHandler { +) : AnalyticsHandler, ErrorEventHandler { override fun id(): String = ID @@ -22,43 +16,32 @@ class FirebaseAnalyticsHandler( client.logEvent(event, params) } + override fun send(event: AnalyticsEvent) { + when (event.error) { + null -> super.send(event) + else -> { + val errorConverter = AnalyticsErrorConverter() + if (!errorConverter.canBeHandled(event.error)) return + + val errorParams = errorConverter.convert(event.error).toMutableMap() + errorParams["Category"] = event.category + errorParams["Event"] = event.event + errorParams.putAll(event.params) + send(event.error, errorParams) + } + } + } + override fun send(error: Throwable, params: Map) { client.logErrorEvent(error, params) } - override fun send( - error: TangemSdkError, - action: AnalyticsAnOld.ActionToLog, - params: Map, - card: Card?, - ) { - val model = CardSdkErrorConverter.Model(error, action, params, prepareParams(card)) - val converter = CardSdkErrorConverter() - converter.convert(model)?.let { - send(it.throwable, it.params) - } - } - - override fun send( - error: BlockchainError, - action: AnalyticsAnOld.ActionToLog, - params: Map, - card: Card?, - ) { - - val model = BlockchainSdkErrorConverter.Model(error, action, params, prepareParams(card)) - val converter = BlockchainSdkErrorConverter(CardSdkErrorConverter()) - converter.convert(model)?.let { - send(it.throwable, it.params) - } - } - companion object { const val ID = "Firebase" } class Builder : AnalyticsHandlerBuilder { - override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsEventHandler? = when { + override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when { !data.isDebug -> FirebaseClient() data.isDebug && data.logConfig.firebase -> FirebaseLogClient(data.jsonConverter) else -> null diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 194d6d5815..7d6db12a26 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -27,14 +27,11 @@ import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsAnOld -import com.tangem.tap.common.analytics.AnalyticsEventAnOld -import com.tangem.tap.common.analytics.AnalyticsParamAnOld +import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tokens.UserTokensRepository -import com.tangem.tap.features.demo.DemoHelper import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive @@ -49,8 +46,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co additionalBlockchainsToDerive: Collection? = null, messageRes: Int? = null, ): CompletionResult { - Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.READY_TO_SCAN, card = null) - val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) return runTaskAsyncReturnOnMain( runnable = ScanProductTask(null, userTokensRepository, additionalBlockchainsToDerive), @@ -71,29 +66,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co private fun sendScanResultsToAnalytics( result: CompletionResult, ) { - when (result) { - is CompletionResult.Success -> { - Analytics.handleAnalyticsEvent( - event = AnalyticsEventAnOld.CARD_IS_SCANNED, - card = result.data.card, - blockchain = result.data.walletData?.blockchain, - ) - if (DemoHelper.isDemoCard(result.data)) { - Analytics.handleAnalyticsEvent( - event = AnalyticsEventAnOld.DEMO_MODE_ACTIVATED, - params = mapOf(AnalyticsParamAnOld.CARD_ID.param to result.data.card.cardId), - card = result.data.card, - blockchain = result.data.walletData?.blockchain, - ) - } + if (result is CompletionResult.Failure) { + (result.error as? TangemSdkError)?.let { error -> + Analytics.send(Basic.ScanError(error)) } - is CompletionResult.Failure -> - (result.error as? TangemSdkError)?.let { error -> - Analytics.send( - error = error, - action = AnalyticsAnOld.ActionToLog.Scan, - ) - } } } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index fc80f3c5b0..c1f7001ebc 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -7,19 +7,16 @@ import com.tangem.blockchain.extensions.Result import com.tangem.common.CompletionResult import com.tangem.common.KeyPair import com.tangem.common.card.Card -import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.domain.common.ScanResponse import com.tangem.network.common.MoshiConverter import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.common.AssetReader -import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.tangemSdkManager class TwinCardsManager( - private val card: Card, + card: Card, assetReader: AssetReader, ) { @@ -34,15 +31,7 @@ class TwinCardsManager( val response = tangemSdkManager.runTaskAsync(CreateFirstTwinWalletTask(), currentCardId, message) when (response) { is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString() - is CompletionResult.Failure -> { - (response.error as? TangemSdkError)?.let { error -> - Analytics.send( - error, - AnalyticsAnOld.ActionToLog.CreateWallet, - card = card - ) - } - } + is CompletionResult.Failure -> {} } return response } @@ -57,22 +46,14 @@ class TwinCardsManager( firstCardId = currentCardId, issuerKeys = issuerKeyPair, preparingMessage = preparingMessage, - creatingWalletMessage = creatingWalletMessage + creatingWalletMessage = creatingWalletMessage, ) val response = tangemSdkManager.runTaskAsync(task, null, initialMessage) when (response) { is CompletionResult.Success -> { secondCardPublicKey = response.data.wallet.publicKey.toHexString() } - is CompletionResult.Failure -> { - (response.error as? TangemSdkError)?.let { error -> - Analytics.send( - error, - AnalyticsAnOld.ActionToLog.CreateWallet, - card = card - ) - } - } + is CompletionResult.Failure -> {} } return response } @@ -80,20 +61,11 @@ class TwinCardsManager( suspend fun complete(message: Message): Result { val response = tangemSdkManager.runTaskAsync( FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeyPair), - currentCardId, message + currentCardId, message, ) return when (response) { is CompletionResult.Success -> Result.Success(response.data) - is CompletionResult.Failure -> { - (response.error as? TangemSdkError)?.let { error -> - Analytics.send( - error, - AnalyticsAnOld.ActionToLog.WriteIssuerData, - card = card - ) - } - Result.fromTangemSdkError(response.error) - } + is CompletionResult.Failure -> Result.fromTangemSdkError(response.error) } } @@ -102,13 +74,13 @@ class TwinCardsManager( val issuer = getIssuers(reader).first { it.publicKey == publicKey } return KeyPair( publicKey = issuer.publicKey.hexToBytes(), - privateKey = issuer.privateKey.hexToBytes() + privateKey = issuer.privateKey.hexToBytes(), ) } private fun getAdapter(): JsonAdapter> { return MoshiConverter.defaultMoshi().adapter( - Types.newParameterizedType(List::class.java, Issuer::class.java) + Types.newParameterizedType(List::class.java, Issuer::class.java), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 31e16004cd..8b66d5ce12 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -5,9 +5,7 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.events.WalletConnect -import com.tangem.tap.common.analytics.logWcEvent import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapError @@ -226,11 +224,6 @@ class WalletConnectManager { private fun onSessionClosed(session: WCSession) { Analytics.send(WalletConnect.SessionDisconnected()) - Analytics.logWcEvent( - AnalyticsAnOld.WcAnalyticsEvent.Session( - AnalyticsAnOld.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url - ) - ) sessions.remove(session) walletConnectRepository.removeSession(session) store.dispatchOnMain(WalletConnectAction.RemoveSession(session)) @@ -376,11 +369,6 @@ class WalletConnectManager { ) } Analytics.send(WalletConnect.NewSessionEstablished()) - Analytics.logWcEvent( - AnalyticsAnOld.WcAnalyticsEvent.Session( - AnalyticsAnOld.WcSessionEvent.Connect, peer.url, - ), - ) } } } @@ -391,11 +379,11 @@ class WalletConnectManager { } client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction -> Timber.d("onEthSendTransaction: $transaction") - Analytics.logWcEvent( - AnalyticsAnOld.WcAnalyticsEvent.Action( - AnalyticsAnOld.WcAction.SendTransaction - ) - ) + // Analytics.logWcEvent( + // AnalyticsAnOld.WcAnalyticsEvent.Action( + // AnalyticsAnOld.WcAction.SendTransaction + // ) + // ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.HandleTransactionRequest( @@ -409,11 +397,11 @@ class WalletConnectManager { } client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction -> Timber.d("onEthSignTransaction: $transaction") - Analytics.logWcEvent( - AnalyticsAnOld.WcAnalyticsEvent.Action( - AnalyticsAnOld.WcAction.SignTransaction - ) - ) + // Analytics.logWcEvent( + // AnalyticsAnOld.WcAnalyticsEvent.Action( + // AnalyticsAnOld.WcAction.SignTransaction + // ) + // ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.HandleTransactionRequest( @@ -427,11 +415,11 @@ class WalletConnectManager { } client.onEthSign = { id: Long, message: WCEthereumSignMessage -> Timber.d("onEthSign: $message") - Analytics.logWcEvent( - AnalyticsAnOld.WcAnalyticsEvent.Action( - AnalyticsAnOld.WcAction.PersonalSign - ) - ) + // Analytics.logWcEvent( + // AnalyticsAnOld.WcAnalyticsEvent.Action( + // AnalyticsAnOld.WcAction.PersonalSign + // ) + // ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.HandlePersonalSignRequest( @@ -503,11 +491,11 @@ class WalletConnectManager { val message = EthSignHelper.tryToParseEthTypedMessage(request) if (message != null) { Timber.d("onEthSign_v4: $message") - Analytics.logWcEvent( - AnalyticsAnOld.WcAnalyticsEvent.Action( - AnalyticsAnOld.WcAction.PersonalSign - ) - ) + // Analytics.logWcEvent( + // AnalyticsAnOld.WcAnalyticsEvent.Action( + // AnalyticsAnOld.WcAction.PersonalSign + // ) + // ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.HandlePersonalSignRequest( diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index 36e8fc2a1d..a95fd872e9 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -25,7 +25,7 @@ import com.tangem.common.extensions.toHexString import com.tangem.crypto.CryptoUtils import com.tangem.operations.sign.SignHashCommand import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsAnOld +import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.toFormattedString import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession @@ -88,8 +88,8 @@ class WalletConnectSdkHelper { extras = EthereumTransactionExtras( data = transaction.data.removePrefix(HEX_PREFIX).hexToBytes(), gasLimit = gas.toBigInteger(), - nonce = transaction.nonce?.hexToBigDecimal()?.toBigInteger() - ) + nonce = transaction.nonce?.hexToBigDecimal()?.toBigInteger(), + ), ) val dialogData = TransactionRequestDialogData( dAppName = session.peerMeta.name, @@ -101,7 +101,7 @@ class WalletConnectSdkHelper { isEnoughFundsToSend = (balance - total) >= BigDecimal.ZERO, session = session.session, id = id, - type = type + type = type, ) return WcTransactionData( type = type, @@ -109,7 +109,7 @@ class WalletConnectSdkHelper { session = session, id = id, walletManager = walletManager, - dialogData = dialogData + dialogData = dialogData, ) } @@ -123,7 +123,7 @@ class WalletConnectSdkHelper { val blockchain = session.wallet.getBlockchainForSession() return factory.makeWalletManager( blockchain = blockchain, - publicKey = publicKey + publicKey = publicKey, ) } @@ -137,14 +137,14 @@ class WalletConnectSdkHelper { private suspend fun sendTransaction(data: WcTransactionData): String? { val result = (data.walletManager as TransactionSender).send( transactionData = data.transaction, - signer = CommonSigner(tangemSdk) + signer = CommonSigner(tangemSdk), ) return when (result) { SimpleResult.Success -> { HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash } is SimpleResult.Failure -> { - Analytics.send(result.error, AnalyticsAnOld.ActionToLog.WalletConnectTransaction) + (result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.TransactionError(it)) } Timber.e(result.error as BlockchainSdkError) null } @@ -156,13 +156,13 @@ class WalletConnectSdkHelper { transactionData = data.transaction, nonce = null, blockchain = data.walletManager.wallet.blockchain, - gasLimit = null + gasLimit = null, ) ?: return null val command = SignHashCommand( hash = dataToSign.hash, walletPublicKey = data.walletManager.wallet.publicKey.seedKey, - derivationPath = data.walletManager.wallet.publicKey.derivationPath + derivationPath = data.walletManager.wallet.publicKey.derivationPath, ) val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message()) return when (result) { @@ -170,9 +170,7 @@ class WalletConnectSdkHelper { HEX_PREFIX + result.data } is CompletionResult.Failure -> { - (result.error as? TangemSdkError)?.let { error -> - Analytics.send(error, AnalyticsAnOld.ActionToLog.WalletConnectSign) - } + (result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.SignError(it)) } Timber.e(result.error.customMessage) null } @@ -183,7 +181,7 @@ class WalletConnectSdkHelper { val command = SignHashCommand( hash = data, walletPublicKey = session.wallet.walletPublicKey ?: return null, - derivationPath = session.wallet.derivationPath + derivationPath = session.wallet.derivationPath, ) val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message()) return when (result) { @@ -192,13 +190,11 @@ class WalletConnectSdkHelper { ?: session.wallet.walletPublicKey.toDecompressedPublicKey() getBnbResultString( key.toHexString(), - result.data.signature.toHexString() + result.data.signature.toHexString(), ) } is CompletionResult.Failure -> { - (result.error as? TangemSdkError)?.let { error -> - Analytics.send(error, AnalyticsAnOld.ActionToLog.WalletConnectTransaction) - } + (result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.TransactionError(it)) } Timber.e(result.error.customMessage) null } @@ -223,18 +219,17 @@ class WalletConnectSdkHelper { val prefixData = (ETH_MESSAGE_PREFIX + messageData.size.toString()).toByteArray() val hashToSign = (prefixData + messageData).toKeccak() - val dialogData = PersonalSignDialogData( dAppName = session.peerMeta.name, message = messageString, session = session.session, - id = id + id = id, ) return WcPersonalSignData( hash = hashToSign, session = session, id = id, - dialogData = dialogData + dialogData = dialogData, ) } @@ -269,9 +264,7 @@ class WalletConnectSdkHelper { ) } is CompletionResult.Failure -> { - (result.error as? TangemSdkError)?.let { error -> - Analytics.send(error, AnalyticsAnOld.ActionToLog.WalletConnectSign) - } + (result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.SignError(it)) } Timber.e(result.error.customMessage) null } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 835a392ee2..b316098ff4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -4,8 +4,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsAnOld -import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow @@ -129,11 +127,7 @@ class DetailsMiddleware { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - Analytics.send( - error, - AnalyticsAnOld.ActionToLog.PurgeWallet, - card = store.state.detailsState.scanResponse?.card, - ) + Analytics.send(Settings.CardSettings.FactoryResetFinished(error)) } } } @@ -160,30 +154,20 @@ class DetailsMiddleware { SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId) SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId) SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId) - else -> null + else -> return@launch } withContext(Dispatchers.Main) { + val paramValue = AnalyticsParam.SecurityMode.from(selectedOption) when (result) { is CompletionResult.Success -> { - selectedOption?.let { - val paramValue = AnalyticsParam.SecurityMode.from(it) - Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue)) - store.dispatch(GlobalAction.UpdateSecurityOptions(it)) - } + Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue)) + store.dispatch(GlobalAction.UpdateSecurityOptions(selectedOption)) store.dispatch(NavigationAction.PopBackTo()) store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success) } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - Analytics.send( - error = error, - action = AnalyticsAnOld.ActionToLog.ChangeSecOptions, - params = mapOf( - AnalyticsParamAnOld.NEW_SECURITY_OPTION to - (selectedOption?.name ?: ""), - ), - card = store.state.detailsState.scanResponse?.card, - ) + Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue, error)) } store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure) } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 16d6ee9efd..c3c0145ec8 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -10,9 +10,6 @@ import com.tangem.operations.backup.BackupService import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.backupService import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsEventAnOld -import com.tangem.tap.common.analytics.AnalyticsParamAnOld -import com.tangem.tap.common.analytics.GetCardSourceParamsAnOld import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor import com.tangem.tap.common.entities.IndeterminateProgressButton @@ -107,10 +104,6 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL) else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop)) } - Analytics.handleAnalyticsEvent( - event = AnalyticsEventAnOld.GET_CARD, - params = mapOf(AnalyticsParamAnOld.SOURCE.param to GetCardSourceParamsAnOld.WELCOME.param), - ) } } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index c45924449f..da468c2636 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -18,9 +18,6 @@ import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsAnOld -import com.tangem.tap.common.analytics.AnalyticsEventAnOld -import com.tangem.tap.common.analytics.AnalyticsParamAnOld import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogShow @@ -222,14 +219,10 @@ private fun sendTransaction( dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) tangemSdk.config.linkedTerminal = isLinkedTerminal + val currencyType = AnalyticsParam.CurrencyType.Amount(amountToSend) when (sendResult) { is SimpleResult.Success -> { - Analytics.send(Token.Send.TransactionSent(AnalyticsParam.CurrencyType.Amount(amountToSend))) - Analytics.handleAnalyticsEvent( - event = AnalyticsEventAnOld.TRANSACTION_IS_SENT, - card = card, - blockchain = walletManager.wallet.blockchain.currency, - ) + Analytics.send(Token.Send.TransactionSent(currencyType)) dispatch(SendAction.SendSuccess) if (externalTransactionData != null) { @@ -254,15 +247,10 @@ private fun sendTransaction( feeAmount = feeAmount, destinationAddress = destinationAddress, ) - Analytics.send( - error = sendResult.error, - action = AnalyticsAnOld.ActionToLog.SendTransaction, - params = mapOf(AnalyticsParamAnOld.BLOCKCHAIN to walletManager.wallet.blockchain.currency), - card = card, - ) - val error = (sendResult.error as? BlockchainSdkError) ?: return@withMainContext + Analytics.send(Token.Send.TransactionSent(currencyType, error)) + when (error) { is BlockchainSdkError.WrappedTangemError -> { val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return@withMainContext diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 78c837ba71..07d6994a7a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -5,14 +5,12 @@ import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult -import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.isZero import com.tangem.common.services.Result import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.attestation.Attestation import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsAnOld import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -189,15 +187,7 @@ class WalletMiddleware { globalState.scanResponse?.copy(card = result.data) scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) } } - is CompletionResult.Failure -> { - (result.error as? TangemSdkError)?.let { error -> - Analytics.send( - error, - AnalyticsAnOld.ActionToLog.CreateWallet, - card = store.state.detailsState.scanResponse?.card, - ) - } - } + is CompletionResult.Failure -> {} } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index 4a30816169..112c3a0afb 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -8,8 +8,6 @@ import com.tangem.common.card.Card import com.tangem.common.card.FirmwareVersion import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isTestCard -import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.isGreaterThan import com.tangem.tap.common.redux.global.GlobalState @@ -55,14 +53,10 @@ class WarningsMiddleware { if (action.remainingSignatures != null && action.remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING ) { - store.state.globalState.warningManager - ?.removeWarnings( - messageRes = R.string.warning_low_signatures_format - ) + store.state.globalState.warningManager?.removeWarnings(R.string.warning_low_signatures_format) addWarningMessage( - warning = - WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures), - autoUpdate = true + warning = WarningMessagesManager.remainingSignaturesNotEnough(action.remainingSignatures), + autoUpdate = true, ) } } @@ -70,7 +64,8 @@ class WarningsMiddleware { is WalletAction.Warnings.CheckHashesCount, is WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount, is WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline, - is WalletAction.Warnings.Set -> Unit + is WalletAction.Warnings.Set, + -> Unit } } @@ -82,7 +77,6 @@ class WarningsMiddleware { preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds() } if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) { - Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_DISPLAYED) addWarningMessage(WarningMessagesManager.appRatingWarning(), true) } } @@ -119,11 +113,7 @@ class WarningsMiddleware { if (remainingSignatures != null && remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING ) { - addWarningMessage( - WarningMessagesManager.remainingSignaturesNotEnough( - remainingSignatures - ) - ) + addWarningMessage(WarningMessagesManager.remainingSignaturesNotEnough(remainingSignatures)) } } @@ -179,15 +169,9 @@ class WarningsMiddleware { } is SimpleResult.Failure -> if (result.error is BlockchainSdkError.SignatureCountNotMatched) { - addWarningMessage( - WarningMessagesManager.alreadySignedHashesWarning(), - true - ) + addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true) } else if (signedHashes > 0) { - addWarningMessage( - WarningMessagesManager.alreadySignedHashesWarning(), - true - ) + addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true) } null -> Unit } @@ -208,7 +192,7 @@ class WarningsMiddleware { val warningManager = store.state.globalState.warningManager ?: return emptyList() return warningManager.getWarnings( WarningMessage.Location.MainScreen, - store.state.walletState.blockchains + store.state.walletState.blockchains, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index 90abb0d255..dd3bcee91c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt @@ -8,7 +8,6 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.google.android.play.core.review.ReviewManagerFactory import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.AnalyticsEventAnOld import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.extensions.getActivity @@ -29,7 +28,7 @@ class WarningMessagesAdapter : ListAdapter(Dif override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WarningMessageVH { val binding = LayoutWarningCardActionBinding.inflate( - LayoutInflater.from(parent.context), parent, false + LayoutInflater.from(parent.context), parent, false, ) return WarningMessageVH(binding) } @@ -63,7 +62,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi tvMessage.text = getString( resId = warning.messageResId, default = warning.message, - formatArgs = warning.messageFormatArg + formatArgs = warning.messageFormatArg, ) } @@ -101,7 +100,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi } } val buttonTitle = binding.root.getString( - warning.buttonTextId ?: R.string.how_to_got_it_button + warning.buttonTextId ?: R.string.how_to_got_it_button, ) binding.btnGotIt.setOnClickListener(buttonAction) binding.btnGotIt.text = buttonTitle @@ -112,13 +111,11 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi binding.btnClose.show() binding.btnClose.setOnClickListener { Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Closed)) - Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_DISMISS) store.dispatch(GlobalAction.HideWarningMessage(warning)) store.dispatch(WalletAction.Warnings.AppRating.RemindLater) } binding.btnCanBeBetter.setOnClickListener { Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Disliked)) - Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_NEGATIVE) store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) store.dispatch(GlobalAction.HideWarningMessage(warning)) store.dispatch(GlobalAction.SendEmail(RateCanBeBetterEmail())) @@ -127,7 +124,6 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi val activity = binding.root.context.getActivity() ?: return@setOnClickListener Analytics.send(MainScreen.NoticeRateAppButton(AnalyticsParam.RateApp.Liked)) - Analytics.handleAnalyticsEvent(AnalyticsEventAnOld.APP_RATING_POSITIVE) store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) val reviewManager = ReviewManagerFactory.create(activity) val task = reviewManager.requestReviewFlow() From 14a2156262096ec5160cbadad25df1161ec373ae Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 9 Nov 2022 22:17:30 +0500 Subject: [PATCH 044/176] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index cf6ea50867..a1658496e7 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354 +Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6 From a6e04a43443f402f4aafea96ee9824164be28665 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 11 Nov 2022 13:54:04 +0300 Subject: [PATCH 045/176] Updated on 2026-08-14 --- .gitignore | 1 + app/build.gradle | 187 ------------------ app/build.gradle.kts | 182 +++++++++++++++++ app/proguard-rules.pro | 2 +- build.gradle | 31 --- build.gradle.kts | 30 +++ buildSrc/build.gradle.kts | 7 + buildSrc/src/main/java/AppConfig.kt | 8 + buildSrc/src/main/java/ClasspathDependency.kt | 9 + buildSrc/src/main/java/ConfigFieldBuilder.kt | 26 +++ buildSrc/src/main/java/Dependency.kt | 77 ++++++++ buildSrc/src/main/java/Versions.kt | 68 +++++++ common/{build.gradle => build.gradle.kts} | 4 +- dependencies.gradle | 14 -- domain/build.gradle | 88 --------- domain/build.gradle.kts | 98 +++++++++ domain/proguard-rules.pro | 2 +- network/build.gradle | 31 --- network/build.gradle.kts | 26 +++ settings.gradle | 4 - settings.gradle.kts | 4 + 21 files changed, 540 insertions(+), 359 deletions(-) delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/java/AppConfig.kt create mode 100644 buildSrc/src/main/java/ClasspathDependency.kt create mode 100644 buildSrc/src/main/java/ConfigFieldBuilder.kt create mode 100644 buildSrc/src/main/java/Dependency.kt create mode 100644 buildSrc/src/main/java/Versions.kt rename common/{build.gradle => build.gradle.kts} (67%) delete mode 100644 dependencies.gradle delete mode 100644 domain/build.gradle create mode 100644 domain/build.gradle.kts delete mode 100644 network/build.gradle create mode 100644 network/build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/.gitignore b/.gitignore index dd64e70b22..dd33a7dab3 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ local.properties **/fastlane/Preview.html **/fastlane/screenshots **/fastlane/test_output +/buildSrc/build/ diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 70b6edcebd..0000000000 --- a/app/build.gradle +++ /dev/null @@ -1,187 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' -apply plugin: 'com.google.gms.google-services' -apply plugin: 'com.google.firebase.crashlytics' -apply from: '../dependencies.gradle' - -android { - compileSdkVersion 33 - defaultConfig { - applicationId "com.tangem.wallet" - versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 1 - versionName project.hasProperty('versionName') ? project.property('versionName') : "1.0.0.-SNAPSHOT" - minSdkVersion 21 - targetSdkVersion 33 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false' - buildConfigField 'Boolean', environmentConfig.logEnabled, 'false' - } - buildFeatures { - compose true - viewBinding true - } - buildTypes { - release { - debuggable false - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.debug - buildConfigField 'String', environmentConfig.environment, '\"prod\"' - buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false' - buildConfigField 'Boolean', environmentConfig.logEnabled, 'false' - } - debug { - debuggable true - minifyEnabled false - applicationIdSuffix ".dev" - signingConfig signingConfigs.debug - firebaseCrashlytics { - mappingFileUploadEnabled false - } - buildConfigField 'String', environmentConfig.environment, '\"dev\"' - buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'true' - buildConfigField 'Boolean', environmentConfig.logEnabled, 'true' - } - debug_beta { - initWith release - debuggable false - versionNameSuffix "-beta" - applicationIdSuffix ".debug" - buildConfigField 'String', environmentConfig.environment, '\"prod\"' - buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false' - buildConfigField 'Boolean', environmentConfig.logEnabled, 'false' - } - - } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - coreLibraryDesugaringEnabled false - } - composeOptions { - kotlinCompilerExtensionVersion '1.1.0' - } - packagingOptions { - exclude 'lib/x86_64/darwin/libscrypt.dylib' - exclude 'lib/x86_64/freebsd/libscrypt.so' - exclude 'lib/x86_64/linux/libscrypt.so' - } -} - -repositories { - maven { url "https://jitpack.io" } -} - -dependencies { - implementation fileTree(include: ['*.aar'], dir: 'libs') - implementation implementation(project(path: ':domain')) - implementation implementation(project(path: ':network')) - implementation implementation(project(path: ':common')) - - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.appcompat:appcompat:1.5.1' - implementation 'androidx.fragment:fragment-ktx:1.5.3' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'com.google.android.material:material:1.6.1' - implementation 'com.google.android.play:core:1.10.3' - implementation 'com.google.android.play:core-ktx:1.8.1' - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - - implementation "com.tangem:blockchain:${versions.tangem_blockchain_sdk}" - implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tangem_card_sdk}" - implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tangem_card_sdk}" - - // WebView - implementation "androidx.browser:browser:1.4.0" - - implementation 'com.jakewharton.timber:timber:4.7.1' - - //lifecycle - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" - implementation "androidx.lifecycle:lifecycle-common-java8:2.5.1" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" - - implementation "org.rekotlin:rekotlin:1.0.4" - - // QR - implementation 'com.google.zxing:core:3.3.3' - implementation 'me.dm7.barcodescanner:zxing:1.9.8' - - // Camera - implementation 'com.otaliastudios:cameraview:2.6.3' - - // Image Loading - implementation 'io.coil-kt:coil:2.1.0' - implementation 'io.coil-kt:coil-compose:2.1.0' - - // Firebase - implementation platform('com.google.firebase:firebase-bom:26.0.0') - implementation 'com.google.firebase:firebase-analytics-ktx' - implementation 'com.google.firebase:firebase-crashlytics-ktx' - - // Analytics - implementation 'com.appsflyer:af-android-sdk:6.5.1' - implementation 'com.amplitude:android-sdk:2.36.1' - - // WalletConnect - implementation 'com.github.salomonbrys.kotson:kotson:2.5.0' - - // Network and Json - //TODO: refactoring: remove it when all network services moved to the network module - implementation 'com.squareup.retrofit2:retrofit:2.8.1' - implementation 'com.squareup.retrofit2:converter-moshi:2.6.0' - implementation 'com.squareup.moshi:moshi:1.12.0' - implementation "com.squareup.moshi:moshi-kotlin:1.12.0" - - implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) - implementation("com.squareup.okhttp3:okhttp") - implementation("com.squareup.okhttp3:logging-interceptor") - - // Support - implementation("com.zendesk:chat:3.3.5") - implementation("com.zendesk:messaging:5.2.4") - - //Crypto - implementation "com.madgag.spongycastle:core:1.58.0.0" - - // animation - implementation "com.airbnb.android:lottie:3.4.0" - - // Shopify - implementation('com.shopify.mobilebuysdk:buy3:12.0.0') { - exclude group: "com.shopify.graphql.support" - exclude module: 'joda-time' - } - - // Google Pay - implementation 'com.google.android.gms:play-services-wallet:19.1.0' - - //Compose - implementation 'androidx.activity:activity-compose:1.5.0' - implementation 'androidx.compose.material:material:1.1.1' - implementation 'androidx.compose.animation:animation:1.1.1' - implementation 'androidx.compose.foundation:foundation:1.2.0-rc03' - implementation 'androidx.compose.ui:ui:1.1.1' - implementation 'androidx.compose.ui:ui-tooling:1.1.1' - implementation "com.google.accompanist:accompanist-appcompat-theme:0.23.0" - implementation "com.google.accompanist:accompanist-systemuicontroller:0.23.0" - - implementation "com.github.skydoves:androidveil:1.1.2" - - implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6' - - // Shared preferences (encrypted) - implementation 'at.favre.lib:armadillo:0.9.0' - - testImplementation 'junit:junit:4.13.2' - testImplementation "com.google.truth:truth:1.1.3" - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - -} diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000000..218986fc34 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,182 @@ +plugins { + id("com.android.application") + kotlin("android") + kotlin("kapt") + id("com.google.gms.google-services") + id("com.google.firebase.crashlytics") +} + +android { + compileSdk = AppConfig.compileSdkVersion + + defaultConfig { + applicationId = AppConfig.packageName + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + + versionCode = if (project.hasProperty("versionCode")) { + project.property("versionCode") as Int + } else { + AppConfig.versionCode + } + + versionName = if (project.hasProperty("versionName")) { + project.property("versionName") as String + } else { + AppConfig.versionName + } + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildFeatures { + compose = true + viewBinding = true + } + + buildTypes { + release { + isDebuggable = false + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("prod"), + Field.TestActionEnabled(false), + Field.LogEnabled(false), + ), + builder = ::buildConfigField, + ).create() + } + + debug { + isDebuggable = true + isMinifyEnabled = false + applicationIdSuffix = ".dev" + + configure { + // disable mapping file uploads (default=true if minifying) + mappingFileUploadEnabled = false + } + + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("dev"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + + create("debug_beta") { + initWith(getByName("release")) + versionNameSuffix = "-beta" + applicationIdSuffix = ".debug" + } + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } + + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose + } + + packagingOptions { + resources.excludes += "lib/x86_64/darwin/libscrypt.dylib" + resources.excludes += "lib/x86_64/freebsd/libscrypt.so" + resources.excludes += "lib/x86_64/linux/libscrypt.so" + } +} + +repositories { + maven(url = "https://jitpack.io") +} + +dependencies { + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar")))) + implementation(project(":domain")) + implementation(project(":network")) + implementation(project(":common")) + + /** AndroidX libraries */ + implementation(AndroidX.coreKtx) + implementation(AndroidX.appCompat) + implementation(AndroidX.fragmentKtx) + implementation(AndroidX.constraintLayout) + implementation(AndroidX.browser) + implementation(AndroidX.lifecycleRuntimeKtx) + implementation(AndroidX.lifecycleCommonJava8) + implementation(AndroidX.lifecycleViewModelKtx) + implementation(AndroidX.lifecycleLiveDataKtx) + implementation(AndroidX.activityCompose) + + /** Compose libraries */ + implementation(Compose.material) + implementation(Compose.animation) + implementation(Compose.foundation) + implementation(Compose.ui) + implementation(Compose.uiTooling) + implementation(Compose.coil) + + /** Firebase libraries */ + implementation(platform(Firebase.bom)) + implementation(Firebase.firebaseAnalytics) + implementation(Firebase.firebaseCrashlytics) + + /** Tangem libraries */ + implementation(Tangem.blockchain) + implementation(Tangem.cardCore) + implementation(Tangem.cardAndroid) + + /** Other libraries */ + implementation(Library.materialComponent) + implementation(Library.googlePlayCore) + implementation(Library.googlePlayCoreKtx) + coreLibraryDesugaring(Library.desugarJdkLibs) + implementation(Library.timber) + implementation(Library.reKotlin) + implementation(Library.zxingQrCore) + implementation(Library.zxingQrBarcodeScanner) + implementation(Library.otaliastudiosCameraView) + implementation(Library.coil) + implementation(Library.appsflyer) + implementation(Library.amplitude) + implementation(Library.kotsonGsonExt) + //TODO: refactoring: remove it when all network services moved to the datasource module + implementation(Library.retrofit) + implementation(Library.retrofitMoshiConverter) + implementation(Library.moshi) + implementation(Library.moshiKotlin) + implementation(Library.okHttp) + implementation(Library.okHttpLogging) + implementation(Library.zendeskChat) + implementation(Library.zendeskMessaging) + implementation(Library.spongecastleCryptoCore) + implementation(Library.lottie) + implementation(Library.shopifyBuySdk) { + exclude(group = "com.shopify.graphql.support") + exclude(module = "joda-time") + } + implementation(Library.accompanistAppCompatTheme) + implementation(Library.accompanistSystemUiController) + implementation(Library.skydovesVeil) + implementation(Library.viewBindingDelegate) + implementation(Library.armadillo) + implementation(Library.googlePlayServicesWallet) + + /** Testing libraries */ + testImplementation(Test.junit) + testImplementation(Test.truth) + androidTestImplementation(Test.junitAndroidExt) + androidTestImplementation(Test.espresso) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f1b424510d..2f9dc5a47e 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 64f57c2248..0000000000 --- a/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - apply from: 'dependencies.gradle' - - repositories { - google() - jcenter() - maven { url 'https://maven.fabric.io/public' } - } - dependencies { - classpath "com.android.tools.build:gradle:${versions.build_gradle}" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" - classpath "com.github.dcendents:android-maven-gradle-plugin:2.1" - classpath 'com.google.gms:google-services:4.3.10' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1' - } -} - -allprojects { - repositories { - google() - jcenter() - mavenLocal() - maven { url "https://nexus.tangem-tech.com/repository/maven-releases/" } - maven { url 'https://jitpack.io' } - maven { url 'https://zendesk.jfrog.io/zendesk/repo' } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..0cc8e9c419 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,30 @@ +buildscript { + repositories { + google() + mavenCentral() + maven(url = "https://maven.fabric.io/public") + } + + dependencies { + classpath(ClasspathDependency.AndroidGradlePlugin) + classpath(ClasspathDependency.KotlinGradlePlugin) + classpath(ClasspathDependency.AndroidMavenGradlePlugin) + classpath(ClasspathDependency.GoogleServices) + classpath(ClasspathDependency.GoogleFirebaseCrashlytics) + } +} + +allprojects { + repositories { + google() + jcenter() // unable to replace with mavenCentral() due to rekotlin and com.otaliastudios:cameraview + mavenLocal() + maven("https://nexus.tangem-tech.com/repository/maven-releases/") + maven("https://jitpack.io") + maven("https://zendesk.jfrog.io/zendesk/repo") + } +} + +tasks.register("clean", Delete::class) { + delete(rootProject.buildDir) +} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000000..b22ed732fd --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} \ No newline at end of file diff --git a/buildSrc/src/main/java/AppConfig.kt b/buildSrc/src/main/java/AppConfig.kt new file mode 100644 index 0000000000..9a7e91768d --- /dev/null +++ b/buildSrc/src/main/java/AppConfig.kt @@ -0,0 +1,8 @@ +object AppConfig { + const val packageName = "com.tangem.wallet" + const val versionCode = 1 + const val versionName = "1.0.0.-SNAPSHOT" + const val minSdkVersion = 21 + const val targetSdkVersion = 33 + const val compileSdkVersion = 33 +} \ No newline at end of file diff --git a/buildSrc/src/main/java/ClasspathDependency.kt b/buildSrc/src/main/java/ClasspathDependency.kt new file mode 100644 index 0000000000..23fcf71450 --- /dev/null +++ b/buildSrc/src/main/java/ClasspathDependency.kt @@ -0,0 +1,9 @@ +object ClasspathDependency { + const val AndroidGradlePlugin = "com.android.tools.build:gradle:" + Versions.androidGradlePlugin + const val AndroidMavenGradlePlugin = "com.github.dcendents:android-maven-gradle-plugin:" + + Versions.androidMavenGradlePlugin + const val GoogleFirebaseCrashlytics = "com.google.firebase:firebase-crashlytics-gradle:" + + Versions.googleFirebaseCrashlytics + const val GoogleServices = "com.google.gms:google-services:" + Versions.googleServices + const val KotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:" + Versions.kotlin +} \ No newline at end of file diff --git a/buildSrc/src/main/java/ConfigFieldBuilder.kt b/buildSrc/src/main/java/ConfigFieldBuilder.kt new file mode 100644 index 0000000000..42c1f16c56 --- /dev/null +++ b/buildSrc/src/main/java/ConfigFieldBuilder.kt @@ -0,0 +1,26 @@ +typealias ConfigFieldBuilder = (String, String, String) -> Unit + +class BuildConfigFieldFactory( + private val fields: List, + private val builder: ConfigFieldBuilder, +) { + fun create() { + fields.forEach { field -> builder(field.type, field.name, field.value) } + } +} + +/** + * Build config field + * + * @property type field type + * @property name field name + * @property value field value + */ +sealed class Field(val type: String, val name: String, val value: String) { + + class Environment(value: String) : Field("String", "ENVIRONMENT", "\"$value\"") + + class TestActionEnabled(isEnabled: Boolean) : Field("Boolean","TEST_ACTION_ENABLED", isEnabled.toString()) + + class LogEnabled(isEnabled: Boolean) : Field("Boolean","LOG_ENABLED", isEnabled.toString()) +} \ No newline at end of file diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt new file mode 100644 index 0000000000..25c0e3787a --- /dev/null +++ b/buildSrc/src/main/java/Dependency.kt @@ -0,0 +1,77 @@ +object AndroidX { + const val activityCompose = "androidx.activity:activity-compose:" + Versions.androidxActivityCompose + const val appCompat = "androidx.appcompat:appcompat:" + Versions.androidxAppCompat + const val browser = "androidx.browser:browser:" + Versions.androidxBrowser + const val constraintLayout = "androidx.constraintlayout:constraintlayout:" + Versions.androidxConstraintLayout + const val coreKtx = "androidx.core:core-ktx:" + Versions.androidxCore + const val fragmentKtx = "androidx.fragment:fragment-ktx:" + Versions.androidxFragment + const val lifecycleCommonJava8 = "androidx.lifecycle:lifecycle-common-java8:" + Versions.androidxLifecycle + const val lifecycleLiveDataKtx = "androidx.lifecycle:lifecycle-livedata-ktx:" + Versions.androidxLifecycle + const val lifecycleRuntimeKtx = "androidx.lifecycle:lifecycle-runtime-ktx:" + Versions.androidxLifecycle + const val lifecycleViewModelKtx = "androidx.lifecycle:lifecycle-viewmodel-ktx:" + Versions.androidxLifecycle +} + +object Compose { + const val animation = "androidx.compose.animation:animation:" + Versions.compose + const val coil = "io.coil-kt:coil-compose:" + Versions.coil + const val foundation = "androidx.compose.foundation:foundation:" + Versions.composeFoundation + const val material = "androidx.compose.material:material:" + Versions.compose + const val ui = "androidx.compose.ui:ui:" + Versions.compose + const val uiTooling = "androidx.compose.ui:ui-tooling:" + Versions.compose +} + +object Firebase { + const val bom = "com.google.firebase:firebase-bom:" + Versions.firebase + const val firebaseAnalytics = "com.google.firebase:firebase-analytics-ktx" + const val firebaseCrashlytics = "com.google.firebase:firebase-crashlytics-ktx" +} + +object Library { + const val accompanistAppCompatTheme = "com.google.accompanist:accompanist-appcompat-theme:" + Versions.accompanist + const val accompanistSystemUiController = + "com.google.accompanist:accompanist-systemuicontroller:" + Versions.accompanist + const val amplitude = "com.amplitude:android-sdk:" + Versions.amplitude + const val appsflyer = "com.appsflyer:af-android-sdk:" + Versions.appsflyer + const val armadillo = "at.favre.lib:armadillo:" + Versions.armadillo + const val coil = "io.coil-kt:coil:" + Versions.coil + const val coroutine = "org.jetbrains.kotlinx:kotlinx-coroutines-core:" + Versions.coroutine + const val desugarJdkLibs = "com.android.tools:desugar_jdk_libs:" + Versions.desugarJdkLibs + const val googlePlayCore = "com.google.android.play:core:" + Versions.googlePlayCore + const val googlePlayCoreKtx = "com.google.android.play:core-ktx:" + Versions.googlePlayCoreKtx + const val googlePlayServicesWallet = + "com.google.android.gms:play-services-wallet:" + Versions.googlePlayServicesWallet + const val kotsonGsonExt = "com.github.salomonbrys.kotson:kotson:" + Versions.kotsonGsonExt + const val lottie = "com.airbnb.android:lottie:" + Versions.lottie + const val materialComponent = "com.google.android.material:material:" + Versions.googleMaterialComponent + const val moshi = "com.squareup.moshi:moshi:" + Versions.moshi + const val moshiKotlin = "com.squareup.moshi:moshi-kotlin:" + Versions.moshi + const val okHttp = "com.squareup.okhttp3:okhttp:" + Versions.okhttp + const val okHttpLogging = "com.squareup.okhttp3:logging-interceptor:" + Versions.okhttp + const val otaliastudiosCameraView = "com.otaliastudios:cameraview:" + Versions.otaliastudiosCameraView + const val shopifyBuySdk = "com.shopify.mobilebuysdk:buy3:" + Versions.shopifyBuySdk + const val skydovesVeil = "com.github.skydoves:androidveil:" + Versions.skydovesAndroidVeil + const val spongecastleCryptoCore = "com.madgag.spongycastle:core:" + Versions.spongycastleCryptoCore + const val reKotlin = "org.rekotlin:rekotlin:" + Versions.rekotlin + const val retrofit = "com.squareup.retrofit2:retrofit:" + Versions.retrofit + const val retrofitMoshiConverter = "com.squareup.retrofit2:converter-moshi:" + Versions.retrofitMoshiConverter + const val timber = "com.jakewharton.timber:timber:" + Versions.timber + const val viewBindingDelegate = + "com.github.kirich1409:viewbindingpropertydelegate-noreflection:" + Versions.viewBindingDelegate + const val zendeskChat = "com.zendesk:chat:" + Versions.zendeskChat + const val zendeskMessaging = "com.zendesk:messaging:" + Versions.zendeskMessaging + const val zxingQrBarcodeScanner = "me.dm7.barcodescanner:zxing:" + Versions.zxingQrBarcodeScanner + const val zxingQrCore = "com.google.zxing:core:" + Versions.zxingQrCode +} + +object Tangem { + const val blockchain = "com.tangem:blockchain:" + Versions.tangemBlockchainSdk + const val cardAndroid = "com.tangem.tangem-sdk-kotlin:android:" + Versions.tangemCardSgk + const val cardCore = "com.tangem.tangem-sdk-kotlin:core:" + Versions.tangemCardSgk +} + +object Test { + const val espresso = "androidx.test.espresso:espresso-core:" + Versions.espresso + const val junit = "junit:junit:" + Versions.junit + const val junitAndroidExt = "androidx.test.ext:junit:" + Versions.junitAndroidExt + const val truth = "com.google.truth:truth:" + Versions.truth +} \ No newline at end of file diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt new file mode 100644 index 0000000000..d7d89a1fcd --- /dev/null +++ b/buildSrc/src/main/java/Versions.kt @@ -0,0 +1,68 @@ +object Versions { + // region Classpath + const val androidGradlePlugin = "7.1.3" + const val androidMavenGradlePlugin = "2.1" + const val googleFirebaseCrashlytics = "2.7.1" + const val googleServices = "4.3.10" + const val kotlin = "1.6.10" + // endregion Classpath + + // region AndroidX + const val androidxActivityCompose = "1.5.0" + const val androidxAppCompat = "1.5.1" + const val androidxBrowser = "1.4.0" + const val androidxConstraintLayout = "2.1.4" + const val androidxCore = "1.9.0" + const val androidxFragment = "1.5.3" + const val androidxLifecycle = "2.5.1" + // endregion AndroidX + + // region Compose + const val compose = "1.1.0" + const val composeFoundation = "1.2.0-rc03" + // endregion Compose + + // region Other libraries + const val accompanist = "0.23.0" + const val amplitude = "2.36.1" + const val appsflyer = "6.5.1" + const val armadillo = "0.9.0" + const val coil = "2.1.0" + const val coroutine = "1.5.2" + const val desugarJdkLibs = "1.1.5" + const val firebase = "26.0.0" + const val googleMaterialComponent = "1.6.1" + const val googlePlayCore = "1.10.3" + const val googlePlayCoreKtx = "1.8.1" + const val googlePlayServicesWallet = "19.1.0" + const val kotsonGsonExt = "2.5.0" + const val lottie = "3.4.0" + const val moshi = "1.12.0" + const val okhttp = "4.9.3" + const val otaliastudiosCameraView = "2.6.3" + const val rekotlin = "1.0.4" + const val retrofit = "2.8.1" + const val retrofitMoshiConverter = "2.6.0" + const val shopifyBuySdk = "12.0.0" + const val skydovesAndroidVeil = "1.1.2" + const val spongycastleCryptoCore = "1.58.0.0" + const val timber = "4.7.1" + const val viewBindingDelegate = "1.5.6" + const val zendeskChat = "3.3.5" + const val zendeskMessaging = "5.2.4" + const val zxingQrBarcodeScanner = "1.9.8" + const val zxingQrCode = "3.3.3" + // endregion Other libraries + + // region Tangem + const val tangemBlockchainSdk = "develop-133" + const val tangemCardSgk = "develop-165" + // endregion Tangem + + // region Testing + const val espresso = "3.4.0" + const val junit = "4.13.2" + const val truth = "1.1.3" + const val junitAndroidExt = "1.1.3" + // endregion Testing +} \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle.kts similarity index 67% rename from common/build.gradle rename to common/build.gradle.kts index 0412225730..e8259cee84 100644 --- a/common/build.gradle +++ b/common/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id 'java-library' - id 'org.jetbrains.kotlin.jvm' + id("java-library") + id("org.jetbrains.kotlin.jvm") } java { diff --git a/dependencies.gradle b/dependencies.gradle deleted file mode 100644 index ba7e3c39b6..0000000000 --- a/dependencies.gradle +++ /dev/null @@ -1,14 +0,0 @@ -ext.versions = [ - kotlin : '1.6.10', - build_gradle : '7.1.3', - tangem_card_sdk : 'develop-165', - // tangem_card_sdk : '0.0.1', - tangem_blockchain_sdk: 'develop-133', - // tangem_blockchain_sdk: '0.0.1', -] - -ext.environmentConfig = [ - environment : "ENVIRONMENT", - testActionEnabled: "TEST_ACTION_ENABLED", - logEnabled : "LOG_ENABLED", -] diff --git a/domain/build.gradle b/domain/build.gradle deleted file mode 100644 index 83c9fe6ff1..0000000000 --- a/domain/build.gradle +++ /dev/null @@ -1,88 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -apply from: '../dependencies.gradle' - -android { - compileSdk 31 - - defaultConfig { - minSdk 21 - targetSdk 31 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false' - buildConfigField 'Boolean', environmentConfig.logEnabled, 'false' - } - - buildTypes { - release { - debuggable false - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false' - buildConfigField 'Boolean', environmentConfig.logEnabled, 'false' - } - debug { - debuggable true - minifyEnabled false - buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'true' - buildConfigField 'Boolean', environmentConfig.logEnabled, 'true' - } - debug_beta { - initWith release - buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'true' - buildConfigField 'Boolean', environmentConfig.logEnabled, 'true' - } - } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -// composeOptions { -// kotlinCompilerExtensionVersion '1.1.0' -// } - packagingOptions { - exclude 'lib/x86_64/darwin/libscrypt.dylib' - exclude 'lib/x86_64/freebsd/libscrypt.so' - exclude 'lib/x86_64/linux/libscrypt.so' - } -} - -dependencies { - implementation implementation(project(path: ':network')) - implementation implementation(project(path: ':common')) - - implementation "com.tangem:blockchain:${versions.tangem_blockchain_sdk}" - implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tangem_card_sdk}" - implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tangem_card_sdk}" - - // Kotlin coroutines - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' - - // State management - implementation "org.rekotlin:rekotlin:1.0.4" - - // Network - //TODO: it must depends from network module - implementation 'com.squareup.retrofit2:retrofit:2.8.1' - implementation 'com.squareup.retrofit2:converter-moshi:2.6.0' - implementation 'com.squareup.moshi:moshi:1.12.0' - implementation "com.squareup.moshi:moshi-kotlin:1.12.0" - implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' - - // Logs - implementation 'com.jakewharton.timber:timber:4.7.1' - - // Tests - testImplementation 'junit:junit:4.13.2' - testImplementation "com.google.truth:truth:1.1.3" - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} \ No newline at end of file diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts new file mode 100644 index 0000000000..6e1c48864e --- /dev/null +++ b/domain/build.gradle.kts @@ -0,0 +1,98 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + compileSdk = AppConfig.compileSdkVersion + + defaultConfig { + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("prod"), + Field.TestActionEnabled(false), + Field.LogEnabled(false), + ), + builder = ::buildConfigField, + ).create() + } + + debug { + isMinifyEnabled = false + + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("prod"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + packagingOptions { + resources.excludes += "lib/x86_64/darwin/libscrypt.dylib" + resources.excludes += "lib/x86_64/freebsd/libscrypt.so" + resources.excludes += "lib/x86_64/linux/libscrypt.so" + } +} + +dependencies { + implementation(project(":network")) + implementation(project(":common")) + + /** Tangem libraries */ + implementation(Tangem.blockchain) + implementation(Tangem.cardCore) + implementation(Tangem.cardAndroid) + + /** Other libraries */ + implementation(Library.reKotlin) + //TODO: refactoring: remove it when all network services moved to the datasource module + implementation(Library.retrofit) + implementation(Library.retrofitMoshiConverter) + implementation(Library.moshi) + implementation(Library.moshiKotlin) + implementation(Library.okHttpLogging) + implementation(Library.timber) + implementation(Library.coroutine) + + /** Testing libraries */ + testImplementation(Test.junit) + testImplementation(Test.truth) + androidTestImplementation(Test.junitAndroidExt) + androidTestImplementation(Test.espresso) +} \ No newline at end of file diff --git a/domain/proguard-rules.pro b/domain/proguard-rules.pro index 481bb43481..ff59496d81 100644 --- a/domain/proguard-rules.pro +++ b/domain/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/network/build.gradle b/network/build.gradle deleted file mode 100644 index e7fccceda6..0000000000 --- a/network/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -plugins { - id 'java-library' - id 'org.jetbrains.kotlin.jvm' -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -dependencies { - implementation implementation(project(path: ':common')) - - // Tangem sdk's - implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tangem_card_sdk}" - - // Kotlin coroutines - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2' - - // Logs - implementation 'com.jakewharton.timber:timber:4.7.1' - - // Network - implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) - implementation("com.squareup.okhttp3:okhttp") - implementation("com.squareup.okhttp3:logging-interceptor") - implementation 'com.squareup.retrofit2:retrofit:2.8.1' - implementation 'com.squareup.retrofit2:converter-moshi:2.6.0' - implementation 'com.squareup.moshi:moshi:1.13.0' - implementation "com.squareup.moshi:moshi-kotlin:1.13.0" -} \ No newline at end of file diff --git a/network/build.gradle.kts b/network/build.gradle.kts new file mode 100644 index 0000000000..0c82757e05 --- /dev/null +++ b/network/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + id("java-library") + id("org.jetbrains.kotlin.jvm") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + implementation(project(":common")) + + /** Tangem libraries */ + implementation(Tangem.cardCore) + + /** Other libraries */ + implementation(Library.retrofit) + implementation(Library.retrofitMoshiConverter) + implementation(Library.moshi) + implementation(Library.moshiKotlin) + implementation(Library.okHttp) + implementation(Library.okHttpLogging) + implementation(Library.timber) + implementation(Library.coroutine) +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 9b33b9462c..0000000000 --- a/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ -include ':app' -include ':domain' -include ':network' -include ':common' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000..5e820bbbfc --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,4 @@ +include(":app") +include(":domain") +include(":network") +include(":common") \ No newline at end of file From df818fa72a6149c0ca087799a98dc0bee5c57663 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 11 Nov 2022 14:07:23 +0300 Subject: [PATCH 046/176] Updated on 2026-08-14 --- app/build.gradle.kts | 6 +++ .../java/com/tangem/tap/TapApplication.kt | 2 + build.gradle.kts | 4 ++ buildSrc/src/main/java/Dependency.kt | 2 + buildSrc/src/main/java/Versions.kt | 1 + core/datasource/.gitignore | 1 + core/datasource/build.gradle.kts | 46 +++++++++++++++++++ core/datasource/src/main/AndroidManifest.xml | 2 + .../datasource/api/referral/ReferralApi.kt | 7 +++ .../com/tangem/datasource/di/NetworkModule.kt | 44 ++++++++++++++++++ settings.gradle.kts | 3 +- 11 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 core/datasource/.gitignore create mode 100644 core/datasource/build.gradle.kts create mode 100644 core/datasource/src/main/AndroidManifest.xml create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 218986fc34..fe20c4709a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { kotlin("kapt") id("com.google.gms.google-services") id("com.google.firebase.crashlytics") + id("com.google.dagger.hilt.android") } android { @@ -95,6 +96,7 @@ android { resources.excludes += "lib/x86_64/darwin/libscrypt.dylib" resources.excludes += "lib/x86_64/freebsd/libscrypt.so" resources.excludes += "lib/x86_64/linux/libscrypt.so" + resources.excludes += "META-INF/gradle/incremental.annotation.processors" } } @@ -138,6 +140,10 @@ dependencies { implementation(Tangem.cardCore) implementation(Tangem.cardAndroid) + /** DI */ + implementation(Library.hilt) + implementation(Library.hiltKapt) + /** Other libraries */ implementation(Library.materialComponent) implementation(Library.googlePlayCore) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index c0d6e566b4..56e6a36cd4 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -33,6 +33,7 @@ import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.persistence.PreferencesStorage import com.tangem.wallet.BuildConfig import com.zendesk.logger.Logger +import dagger.hilt.android.HiltAndroidApp import org.rekotlin.Store import timber.log.Timber import zendesk.chat.Chat @@ -50,6 +51,7 @@ lateinit var shopService: TangemShopService lateinit var assetReader: AssetReader lateinit var userTokensRepository: UserTokensRepository +@HiltAndroidApp class TapApplication : Application(), ImageLoaderFactory { override fun onCreate() { diff --git a/build.gradle.kts b/build.gradle.kts index 0cc8e9c419..75b1a4e453 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,10 @@ buildscript { } } +plugins { + id("com.google.dagger.hilt.android") version "2.44" apply false +} + allprojects { repositories { google() diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt index 25c0e3787a..bd91e1bdaa 100644 --- a/buildSrc/src/main/java/Dependency.kt +++ b/buildSrc/src/main/java/Dependency.kt @@ -40,6 +40,8 @@ object Library { const val googlePlayCoreKtx = "com.google.android.play:core-ktx:" + Versions.googlePlayCoreKtx const val googlePlayServicesWallet = "com.google.android.gms:play-services-wallet:" + Versions.googlePlayServicesWallet + const val hilt = "com.google.dagger:hilt-android:" + Versions.hilt + const val hiltKapt = "com.google.dagger:hilt-compiler:" + Versions.hilt const val kotsonGsonExt = "com.github.salomonbrys.kotson:kotson:" + Versions.kotsonGsonExt const val lottie = "com.airbnb.android:lottie:" + Versions.lottie const val materialComponent = "com.google.android.material:material:" + Versions.googleMaterialComponent diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index d7d89a1fcd..56e26ff54e 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -35,6 +35,7 @@ object Versions { const val googlePlayCore = "1.10.3" const val googlePlayCoreKtx = "1.8.1" const val googlePlayServicesWallet = "19.1.0" + const val hilt = "2.44" const val kotsonGsonExt = "2.5.0" const val lottie = "3.4.0" const val moshi = "1.12.0" diff --git a/core/datasource/.gitignore b/core/datasource/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/core/datasource/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts new file mode 100644 index 0000000000..2d5eb60c3e --- /dev/null +++ b/core/datasource/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") + id("com.google.dagger.hilt.android") +} + +android { + + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } +} + +dependencies { + + /** DI */ + implementation(Library.hilt) + implementation(Library.hiltKapt) + + /** Coroutines */ + implementation(Library.coroutine) + + /** Logging */ + implementation(Library.timber) + + /** Network */ + implementation(Library.retrofit) + implementation(Library.retrofitMoshiConverter) + implementation(Library.moshi) + implementation(Library.moshiKotlin) + implementation(Library.okHttp) + implementation(Library.okHttpLogging) +} \ No newline at end of file diff --git a/core/datasource/src/main/AndroidManifest.xml b/core/datasource/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..d6401c6174 --- /dev/null +++ b/core/datasource/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt new file mode 100644 index 0000000000..ccaa203e9c --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt @@ -0,0 +1,7 @@ +package com.tangem.datasource.api.referral + +/** + * Api for referral functionality + */ +interface ReferralApi { +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt new file mode 100644 index 0000000000..823d3e63d8 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -0,0 +1,44 @@ +package com.tangem.datasource.di + +import com.tangem.datasource.api.referral.ReferralApi +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class NetworkModule { + + @Provides + @Singleton + fun provideReferralApi(okHttpClient: OkHttpClient): ReferralApi { + return Retrofit.Builder() + .addConverterFactory( + MoshiConverterFactory.create(), + ) + .baseUrl(PROD_REFERRAL_BASE_URL) + .client(okHttpClient) + .build() + .create(ReferralApi::class.java) + } + + @Provides + @Singleton + fun provideOkHttpClient(): OkHttpClient { + return OkHttpClient.Builder() + .addInterceptor( + HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC), + ) + .build() + } + + private companion object { + const val PROD_REFERRAL_BASE_URL = "" + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 5e820bbbfc..658531b8f2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,5 @@ include(":app") include(":domain") include(":network") -include(":common") \ No newline at end of file +include(":common") +include(":core:datasource") \ No newline at end of file From 241f8d217c3ecfac0ae29fb53493c966ee8a00f1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 11 Nov 2022 15:25:06 +0300 Subject: [PATCH 047/176] Updated on 2026-08-14 --- buildSrc/src/main/java/Dependency.kt | 1 + buildSrc/src/main/java/Versions.kt | 1 + core/datasource/build.gradle.kts | 3 ++ .../datasource/api/referral/ReferralApi.kt | 17 +++++++- .../api/referral/models/ReferralResponse.kt | 42 +++++++++++++++++++ .../api/referral/models/StartReferralBody.kt | 11 +++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt index bd91e1bdaa..84f6380c8c 100644 --- a/buildSrc/src/main/java/Dependency.kt +++ b/buildSrc/src/main/java/Dependency.kt @@ -42,6 +42,7 @@ object Library { "com.google.android.gms:play-services-wallet:" + Versions.googlePlayServicesWallet const val hilt = "com.google.dagger:hilt-android:" + Versions.hilt const val hiltKapt = "com.google.dagger:hilt-compiler:" + Versions.hilt + const val jodatime = "net.danlew:android.joda:" + Versions.jodatime const val kotsonGsonExt = "com.github.salomonbrys.kotson:kotson:" + Versions.kotsonGsonExt const val lottie = "com.airbnb.android:lottie:" + Versions.lottie const val materialComponent = "com.google.android.material:material:" + Versions.googleMaterialComponent diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 56e26ff54e..b52ccb4d7b 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -36,6 +36,7 @@ object Versions { const val googlePlayCoreKtx = "1.8.1" const val googlePlayServicesWallet = "19.1.0" const val hilt = "2.44" + const val jodatime = "2.12.1" const val kotsonGsonExt = "2.5.0" const val lottie = "3.4.0" const val moshi = "1.12.0" diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 2d5eb60c3e..ba85cac77f 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -43,4 +43,7 @@ dependencies { implementation(Library.moshiKotlin) implementation(Library.okHttp) implementation(Library.okHttpLogging) + + /** Time */ + implementation(Library.jodatime) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt index ccaa203e9c..42445f931d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt @@ -1,7 +1,22 @@ package com.tangem.datasource.api.referral +import com.tangem.datasource.api.referral.models.ReferralResponse +import com.tangem.datasource.api.referral.models.StartReferralBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Query + /** - * Api for referral functionality + * Api for referral feature */ interface ReferralApi { + + /** Returns referral status by [walletId] */ + @GET("referral/{walletId}") + suspend fun getReferralStatus(@Query("name") walletId: String): ReferralResponse + + /** Make user referral, requires [StartReferralBody] */ + @POST("referral") + suspend fun startReferral(@Body startReferralBody: StartReferralBody): ReferralResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt new file mode 100644 index 0000000000..16c6cd1fa6 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt @@ -0,0 +1,42 @@ +package com.tangem.datasource.api.referral.models + +import com.squareup.moshi.Json +import org.joda.time.DateTime + +/** + * Main response class for referral API + * contains all necessary info about users program status + */ +data class ReferralResponse( + @Json(name = "conditions") val conditions: Conditions, +) + +data class Conditions( + @Json(name = "conditions") val award: Int, + @Json(name = "discount") val discount: Int, + @Json(name = "discountType") val discountType: DiscountType, + @Json(name = "tosLink") val tosLink: String, + @Json(name = "tokens") val tokens: List, + @Json(name = "referral") val referral: Referral, +) + +data class Token( + @Json(name = "id") val id: String, + @Json(name = "name") val name: String, + @Json(name = "symbol") val symbol: String, + @Json(name = "networkId") val networkId: String?, + @Json(name = "contractAddress") val contractAddress: String?, + @Json(name = "decimalCount") val decimalCount: Int, +) + +data class Referral( + @Json(name = "shareLink") val shareLink: String, + @Json(name = "address") val address: String, + @Json(name = "promocode") val promocode: String, + @Json(name = "walletsPurchased") val walletsPurchased: Int, + @Json(name = "termsAcceptedAt") val termsAcceptedAt: DateTime?, +) + +enum class DiscountType { + PERCENTAGE, VALUE +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt new file mode 100644 index 0000000000..98a0a8ab90 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/StartReferralBody.kt @@ -0,0 +1,11 @@ +package com.tangem.datasource.api.referral.models + +import com.squareup.moshi.Json + +/** Body for make user referral */ +data class StartReferralBody( + @Json(name = "walletId") val walletId: String, + @Json(name = "networkId") val networkId: String, + @Json(name = "tokenId") val tokenId: String, + @Json(name = "address") val address: String, +) \ No newline at end of file From ec62375be4a1108b58d8368b554065e930d20c68 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 14 Nov 2022 22:36:50 +0500 Subject: [PATCH 048/176] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/common/analytics/Analytics.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt index 03cb8437bb..8a6ee52f27 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/Analytics.kt @@ -11,7 +11,7 @@ import com.tangem.tap.common.analytics.api.ParamsInterceptorHolder import com.tangem.tap.common.analytics.events.AnalyticsEvent import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.launch import java.util.concurrent.Executors @@ -88,6 +88,6 @@ object Analytics : GlobalAnalyticsEventHandler { val name = "Analytics" val dispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() val exHandler = FeatureCoroutineExceptionHandler.create(name) - return CoroutineScope(Job() + dispatcher + CoroutineName(name) + exHandler) + return CoroutineScope(SupervisorJob() + dispatcher + CoroutineName(name) + exHandler) } } \ No newline at end of file From d4c34f6af90c6420198cf3d54d6d768739654951 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Nov 2022 00:17:49 +0500 Subject: [PATCH 049/176] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/common/DialogManager.kt | 2 +- .../products/note/redux/OnboardingNoteMiddleware.kt | 9 ++++++--- .../tangem/tap/features/wallet/redux/WalletAction.kt | 5 ++++- .../redux/middlewares/WalletDialogsMiddleware.kt | 2 +- .../tap/features/wallet/redux/models/WalletDialog.kt | 5 ++++- .../RussianCardholdersWarningBottomSheetDialog.kt | 12 +++++++++--- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index a1739329b5..4d1c3734ea 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -153,7 +153,7 @@ class DialogManager : StoreSubscriber { primaryButtonAction = state.dialog.onOk, ) is WalletDialog.RussianCardholdersWarningDialog -> - RussianCardholdersWarningBottomSheetDialog(context, state.dialog.topUpUrl) + RussianCardholdersWarningBottomSheetDialog(context, state.dialog.data) else -> null } dialog?.show() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 57940fe421..5ecccaa431 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -13,6 +13,7 @@ import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.getAddressData +import com.tangem.tap.common.extensions.getTopUpUrl import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppDialog @@ -28,6 +29,7 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -174,13 +176,14 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch return } - val topUpUrl = noteState.walletManager.getToUpUrl() ?: return + val topUpUrl = walletManager.getTopUpUrl() ?: return + val blockchain = walletManager.wallet.blockchain if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) { - store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog(topUpUrl)) + val dialogData = WalletDialog.RussianCardholdersWarningDialog.Data(topUpUrl, blockchain) + store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog(dialogData)) return } - val blockchain = walletManager.wallet.blockchain val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain) Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index b9451b117a..02e6073066 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -15,6 +15,7 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal @@ -154,7 +155,9 @@ sealed class WalletAction : Action { object SignedHashesMultiWalletDialog : DialogAction() object ChooseTradeActionDialog : DialogAction() data class ChooseCurrency(val amounts: List?) : DialogAction() - data class RussianCardholdersWarningDialog(val topUpUrl: String? = null) : DialogAction() + data class RussianCardholdersWarningDialog( + val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null + ) : DialogAction() object Hide : DialogAction() } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index 33e8e901f8..f94e6def86 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -38,7 +38,7 @@ class WalletDialogsMiddleware { ) } is WalletAction.DialogAction.RussianCardholdersWarningDialog -> { - store.dispatchDialogShow(WalletDialog.RussianCardholdersWarningDialog(action.topUpUrl)) + store.dispatchDialogShow(WalletDialog.RussianCardholdersWarningDialog(action.dialogData)) } is WalletAction.DialogAction.Hide -> { store.dispatchDialogHide() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt index b4188f8dd8..fc182e2c4f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.wallet.redux.models import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.redux.StateDialog import com.tangem.wallet.R @@ -31,5 +32,7 @@ sealed interface WalletDialog : StateDialog { val titleRes: Int = R.string.token_details_unable_hide_alert_title } - data class RussianCardholdersWarningDialog(val topUpUrl: String?) : WalletDialog + data class RussianCardholdersWarningDialog(val data: Data?) : WalletDialog { + data class Data(val topUpUrl: String, val blockchain: Blockchain) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt index 4073120f64..b8022e2724 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt @@ -5,16 +5,20 @@ import android.os.Bundle import android.view.LayoutInflater import com.google.android.material.bottomsheet.BottomSheetDialog import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.store import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding class RussianCardholdersWarningBottomSheetDialog( - context: Context, private val topUpUrl: String?, + context: Context, + private val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data?, ) : BottomSheetDialog (context) { @@ -36,8 +40,10 @@ class RussianCardholdersWarningBottomSheetDialog( } binding?.btnYes?.setOnClickListener { - if (topUpUrl != null) { - store.dispatchOpenUrl(topUpUrl) + if (dialogData != null) { + val currencyType = AnalyticsParam.CurrencyType.Blockchain(dialogData.blockchain) + Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType)) + store.dispatchOpenUrl(dialogData.topUpUrl) } else { store.dispatch(WalletAction.TradeCryptoAction.Buy(checkUserLocation = false)) } From c70f4c5192f06fd808842d0b42dc784ab3f966d9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Nov 2022 02:27:54 +0500 Subject: [PATCH 050/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 18 ++++++++-- .../com/tangem/tap/common/moshi/Adapters.kt | 16 +++++++++ .../tap/persistence/PreferencesStorage.kt | 8 ++--- .../tap/persistence/ToppedUpWalletStorage.kt | 21 ++++++++++- .../tangem/network/common/MoshiConverter.kt | 35 +++++++------------ 5 files changed, 69 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/moshi/Adapters.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 568ca4af60..5074f09e8d 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -27,6 +27,7 @@ import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.images.createCoilImageLoader import com.tangem.tap.common.log.TangemLogCollector +import com.tangem.tap.common.moshi.BigDecimalAdapter import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.appReducer import com.tangem.tap.common.redux.global.GlobalAction @@ -38,6 +39,7 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.walletconnect.WalletConnectRepository import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.persistence.CardBalanceStateAdapter import com.tangem.tap.persistence.PreferencesStorage import com.tangem.wallet.BuildConfig import com.zendesk.logger.Logger @@ -70,13 +72,14 @@ class TapApplication : Application(), ImageLoaderFactory { foregroundActivityObserver = ForegroundActivityObserver() registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) + initMoshiConverter() DomainLayer.init() NetworkConnectivity.createInstance(store, this) preferencesStorage = PreferencesStorage(this) walletConnectRepository = WalletConnectRepository(this) assetReader = AndroidAssetReader(this) - val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.defaultMoshi()) + val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.INSTANCE.moshi) initConfigManager(configLoader, ::initWithConfigDependency) initWarningMessagesManager() @@ -88,6 +91,17 @@ class TapApplication : Application(), ImageLoaderFactory { ) } + private fun initMoshiConverter() { + fun appAdapters(): List = listOf( + BigDecimalAdapter(), + CardBalanceStateAdapter(), + ) + MoshiConverter.reInitInstance( + adapters = appAdapters() + MoshiJsonConverter.getTangemSdkAdapters(), + typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(), + ) + } + override fun newImageLoader(): ImageLoader { return createCoilImageLoader( context = this, @@ -124,7 +138,7 @@ class TapApplication : Application(), ImageLoaderFactory { config = config, isDebug = BuildConfig.DEBUG, logConfig = LogConfig.analyticsHandlers, - jsonConverter = MoshiJsonConverter.INSTANCE, + jsonConverter = MoshiConverter.INSTANCE, ) factory.build(Analytics, buildData) } diff --git a/app/src/main/java/com/tangem/tap/common/moshi/Adapters.kt b/app/src/main/java/com/tangem/tap/common/moshi/Adapters.kt new file mode 100644 index 0000000000..c0ec09ea9c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/moshi/Adapters.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.common.moshi + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +class BigDecimalAdapter { + @FromJson + fun fromJson(value: String) = BigDecimal(value) + + @ToJson + fun toJson(value: BigDecimal) = value.toString() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 38a03e482d..0e213fc01b 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -4,7 +4,7 @@ import android.app.Application import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit -import com.tangem.common.json.MoshiJsonConverter +import com.tangem.network.common.MoshiConverter import java.util.* class PreferencesStorage(applicationContext: Application) { @@ -21,12 +21,12 @@ class PreferencesStorage(applicationContext: Application) { init { incrementLaunchCounter() appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches()) - usedCardsPrefStorage = UsedCardsPrefStorage(preferences, MoshiJsonConverter.INSTANCE) + usedCardsPrefStorage = UsedCardsPrefStorage(preferences, MoshiConverter.INSTANCE) usedCardsPrefStorage.migrate() - fiatCurrenciesPrefStorage = FiatCurrenciesPrefStorage(preferences, MoshiJsonConverter.INSTANCE) + fiatCurrenciesPrefStorage = FiatCurrenciesPrefStorage(preferences, MoshiConverter.INSTANCE) fiatCurrenciesPrefStorage.migrate() disclaimerPrefStorage = DisclaimerPrefStorage(preferences) - toppedUpWalletStorage = ToppedUpWalletStorage(preferences, MoshiJsonConverter.INSTANCE) + toppedUpWalletStorage = ToppedUpWalletStorage(preferences, MoshiConverter.INSTANCE) } var chatFirstLaunchTime: Long? diff --git a/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt b/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt index 7ec028e8ea..e270b2b820 100644 --- a/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt @@ -2,7 +2,10 @@ package com.tangem.tap.persistence import android.content.SharedPreferences import androidx.core.content.edit +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson import com.tangem.common.json.MoshiJsonConverter +import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.filters.BasicTopUpFilter import timber.log.Timber @@ -44,7 +47,9 @@ class ToppedUpWalletStorage( private fun restore(): MutableSet { val json = preferences.getString(KEY, null) ?: return mutableSetOf() return try { - jsonConverter.fromJson(json, jsonConverter.typedList(BasicTopUpFilter.Data::class.java))!! + val typedList = jsonConverter.typedList(BasicTopUpFilter.Data::class.java) + val listData = jsonConverter.fromJson>(json, typedList)!! + listData.toMutableSet() } catch (ex: Exception) { preferences.edit(true) { remove(KEY) } mutableSetOf() @@ -54,4 +59,18 @@ class ToppedUpWalletStorage( companion object { private const val KEY = "userWalletsInfo" } +} + +class CardBalanceStateAdapter { + @ToJson + fun toJson(src: AnalyticsParam.CardBalanceState): String = src.value + + @FromJson + fun fromJson(json: String): AnalyticsParam.CardBalanceState { + return when (json) { + AnalyticsParam.CardBalanceState.Empty.value -> AnalyticsParam.CardBalanceState.Empty + AnalyticsParam.CardBalanceState.Full.value -> AnalyticsParam.CardBalanceState.Full + else -> throw IllegalArgumentException() + } + } } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/common/MoshiConverter.kt b/network/src/main/java/com/tangem/network/common/MoshiConverter.kt index 1f967032e3..47b5cc4b64 100644 --- a/network/src/main/java/com/tangem/network/common/MoshiConverter.kt +++ b/network/src/main/java/com/tangem/network/common/MoshiConverter.kt @@ -1,38 +1,29 @@ package com.tangem.network.common -import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi -import com.squareup.moshi.ToJson -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.common.json.MoshiJsonConverter -import com.tangem.common.json.TangemSdkAdapter import retrofit2.Converter import retrofit2.converter.moshi.MoshiConverterFactory -import java.math.BigDecimal /** [REDACTED_AUTHOR] */ -class MoshiConverter { +object MoshiConverter { - companion object { - fun createFactory(moshi: Moshi = defaultMoshi()): Converter.Factory = MoshiConverterFactory.create(moshi) + var INSTANCE = MoshiJsonConverter() + private set - fun defaultMoshi(): Moshi = Moshi.Builder() - .add(BigDecimalAdapter) - .add(KotlinJsonAdapterFactory()) - .add(TangemSdkAdapter.DerivationPathAdapter()) - .add(TangemSdkAdapter.DerivationNodeAdapter()) - .build() - - fun sdkMoshi(): Moshi = MoshiJsonConverter.INSTANCE.moshi + fun reInitInstance( + adapters: List = listOf(), + typedAdapters: Map, JsonAdapter<*>> = mapOf(), + ) { + INSTANCE = MoshiJsonConverter(adapters, typedAdapters) } -} -object BigDecimalAdapter { - @FromJson - fun fromJson(string: String) = BigDecimal(string) + fun createFactory(moshi: Moshi = INSTANCE.moshi): Converter.Factory = MoshiConverterFactory.create(moshi) - @ToJson - fun toJson(value: BigDecimal) = value.toString() + fun defaultMoshi(): Moshi = INSTANCE.moshi + + fun sdkMoshi(): Moshi = MoshiJsonConverter.INSTANCE.moshi } \ No newline at end of file From 2925390cb36d6f83c4a804d167e2034a751676a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 11 Nov 2022 17:46:58 +0300 Subject: [PATCH 051/176] Updated on 2026-08-14 --- core/datasource/build.gradle.kts | 2 +- features/referral/data/.gitignore | 1 + features/referral/data/build.gradle.kts | 32 +++++++++++++++++++ .../data/src/main/AndroidManifest.xml | 2 ++ features/referral/domain/.gitignore | 1 + features/referral/domain/build.gradle.kts | 32 +++++++++++++++++++ .../domain/src/main/AndroidManifest.xml | 2 ++ features/referral/presentation/.gitignore | 1 + .../referral/presentation/build.gradle.kts | 32 +++++++++++++++++++ .../presentation/src/main/AndroidManifest.xml | 2 ++ settings.gradle.kts | 5 ++- 11 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 features/referral/data/.gitignore create mode 100644 features/referral/data/build.gradle.kts create mode 100644 features/referral/data/src/main/AndroidManifest.xml create mode 100644 features/referral/domain/.gitignore create mode 100644 features/referral/domain/build.gradle.kts create mode 100644 features/referral/domain/src/main/AndroidManifest.xml create mode 100644 features/referral/presentation/.gitignore create mode 100644 features/referral/presentation/build.gradle.kts create mode 100644 features/referral/presentation/src/main/AndroidManifest.xml diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index ba85cac77f..c85c0841b9 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -28,7 +28,7 @@ dependencies { /** DI */ implementation(Library.hilt) - implementation(Library.hiltKapt) + kapt(Library.hiltKapt) /** Coroutines */ implementation(Library.coroutine) diff --git a/features/referral/data/.gitignore b/features/referral/data/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/referral/data/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/referral/data/build.gradle.kts b/features/referral/data/build.gradle.kts new file mode 100644 index 0000000000..4a5dd97a5c --- /dev/null +++ b/features/referral/data/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") + id("com.google.dagger.hilt.android") +} + +android { + + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } +} + +dependencies { + + /** DI */ + implementation(Library.hilt) + kapt(Library.hiltKapt) +} \ No newline at end of file diff --git a/features/referral/data/src/main/AndroidManifest.xml b/features/referral/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..6f8fe3941b --- /dev/null +++ b/features/referral/data/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/features/referral/domain/.gitignore b/features/referral/domain/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/referral/domain/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/referral/domain/build.gradle.kts b/features/referral/domain/build.gradle.kts new file mode 100644 index 0000000000..4a5dd97a5c --- /dev/null +++ b/features/referral/domain/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") + id("com.google.dagger.hilt.android") +} + +android { + + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } +} + +dependencies { + + /** DI */ + implementation(Library.hilt) + kapt(Library.hiltKapt) +} \ No newline at end of file diff --git a/features/referral/domain/src/main/AndroidManifest.xml b/features/referral/domain/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..ace8d8e355 --- /dev/null +++ b/features/referral/domain/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/features/referral/presentation/.gitignore b/features/referral/presentation/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/referral/presentation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts new file mode 100644 index 0000000000..4a5dd97a5c --- /dev/null +++ b/features/referral/presentation/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") + id("com.google.dagger.hilt.android") +} + +android { + + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } +} + +dependencies { + + /** DI */ + implementation(Library.hilt) + kapt(Library.hiltKapt) +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/AndroidManifest.xml b/features/referral/presentation/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..4c5a0a1f54 --- /dev/null +++ b/features/referral/presentation/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 658531b8f2..878d6a3498 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,4 +2,7 @@ include(":app") include(":domain") include(":network") include(":common") -include(":core:datasource") \ No newline at end of file +include(":core:datasource") +include(":features:referral:data") +include(":features:referral:domain") +include(":features:referral:presentation") \ No newline at end of file From 37054e453d93356a858cfe26b5b957e12a8b78f6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Nov 2022 10:26:15 +0300 Subject: [PATCH 052/176] Updated on 2026-08-14 --- .../api/referral/models/ReferralResponse.kt | 4 +- core/utils/.gitignore | 1 + core/utils/build.gradle.kts | 35 ++++++++++++ core/utils/src/main/AndroidManifest.xml | 2 + .../com/tangem/utils/converter/Converter.kt | 8 +++ .../coroutines/CoroutineDispatcherProvider.kt | 20 +++++++ features/referral/data/build.gradle.kts | 8 +++ .../referral/converters/ReferralConverter.kt | 57 +++++++++++++++++++ .../referral/data/ReferralRepositoryImpl.kt | 43 ++++++++++++++ features/referral/domain/build.gradle.kts | 3 + .../referral/domain/ReferralRepository.kt | 17 ++++++ .../referral/domain/models/ReferralData.kt | 52 +++++++++++++++++ settings.gradle.kts | 1 + 13 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 core/utils/.gitignore create mode 100644 core/utils/build.gradle.kts create mode 100644 core/utils/src/main/AndroidManifest.xml create mode 100644 core/utils/src/main/java/com/tangem/utils/converter/Converter.kt create mode 100644 core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt create mode 100644 features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt create mode 100644 features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt create mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralRepository.kt create mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt index 16c6cd1fa6..ef8823e877 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt @@ -9,15 +9,15 @@ import org.joda.time.DateTime */ data class ReferralResponse( @Json(name = "conditions") val conditions: Conditions, + @Json(name = "referral") val referral: Referral?, ) data class Conditions( - @Json(name = "conditions") val award: Int, + @Json(name = "award") val award: Int, @Json(name = "discount") val discount: Int, @Json(name = "discountType") val discountType: DiscountType, @Json(name = "tosLink") val tosLink: String, @Json(name = "tokens") val tokens: List, - @Json(name = "referral") val referral: Referral, ) data class Token( diff --git a/core/utils/.gitignore b/core/utils/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/core/utils/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/utils/build.gradle.kts b/core/utils/build.gradle.kts new file mode 100644 index 0000000000..917187555f --- /dev/null +++ b/core/utils/build.gradle.kts @@ -0,0 +1,35 @@ +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") + id("com.google.dagger.hilt.android") +} + +android { + + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } +} + +dependencies { + + /** DI */ + implementation(Library.hilt) + kapt(Library.hiltKapt) + + /** Coroutines */ + implementation(Library.coroutine) +} \ No newline at end of file diff --git a/core/utils/src/main/AndroidManifest.xml b/core/utils/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..02f6bf24ad --- /dev/null +++ b/core/utils/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/converter/Converter.kt b/core/utils/src/main/java/com/tangem/utils/converter/Converter.kt new file mode 100644 index 0000000000..6af667ae08 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/converter/Converter.kt @@ -0,0 +1,8 @@ +package com.tangem.utils.converter + +interface Converter { + fun convert(value: I): O + fun convertList(input: List): List { + return input.map { convert(it) } + } +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt new file mode 100644 index 0000000000..952424e215 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt @@ -0,0 +1,20 @@ +package com.tangem.utils.coroutines + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import javax.inject.Inject + +interface CoroutineDispatcherProvider { + val main: CoroutineDispatcher + val io: CoroutineDispatcher +} + +class AppCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcherProvider { + override val main: CoroutineDispatcher = Dispatchers.Main + override val io: CoroutineDispatcher = Dispatchers.IO +} + +class TestingCoroutineDispatcherProvider( + override val main: CoroutineDispatcher = Dispatchers.Unconfined, + override val io: CoroutineDispatcher = Dispatchers.Unconfined, +) : CoroutineDispatcherProvider \ No newline at end of file diff --git a/features/referral/data/build.gradle.kts b/features/referral/data/build.gradle.kts index 4a5dd97a5c..497d0af645 100644 --- a/features/referral/data/build.gradle.kts +++ b/features/referral/data/build.gradle.kts @@ -26,6 +26,14 @@ android { dependencies { + /** Project*/ + implementation(project(":core:datasource")) + implementation(project(":core:utils")) + implementation(project(":features:referral:domain")) + + /** Time */ + implementation(Library.jodatime) + /** DI */ implementation(Library.hilt) kapt(Library.hiltKapt) diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt new file mode 100644 index 0000000000..bc55726c4b --- /dev/null +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt @@ -0,0 +1,57 @@ +package com.tangem.feature.referral.converters + +import com.tangem.datasource.api.referral.models.ReferralResponse +import com.tangem.datasource.api.referral.models.Token +import com.tangem.feature.referral.domain.models.DiscountType +import com.tangem.feature.referral.domain.models.ReferralData +import com.tangem.feature.referral.domain.models.ReferralInfo +import com.tangem.feature.referral.domain.models.TokenData +import com.tangem.utils.converter.Converter +import javax.inject.Inject + +class ReferralConverter @Inject constructor() : Converter { + + override fun convert(value: ReferralResponse): ReferralData { + val conditions = value.conditions + val referral = value.referral + val tokenConverter = TokenConverter() + return if (referral != null) { + ReferralData.ParticipantData( + award = conditions.award, + discount = conditions.discount, + discountType = DiscountType.valueOf(conditions.discountType.name), + tosLink = conditions.tosLink, + tokens = tokenConverter.convertList(conditions.tokens), + referral = ReferralInfo( + shareLink = referral.shareLink, + address = referral.address, + promocode = referral.promocode, + walletsPurchased = referral.walletsPurchased, + termsAcceptedAt = referral.termsAcceptedAt, + ), + ) + } else { + ReferralData.NonParticipantData( + award = conditions.award, + discount = conditions.discount, + discountType = DiscountType.valueOf(conditions.discountType.name), + tosLink = conditions.tosLink, + tokens = tokenConverter.convertList(conditions.tokens), + ) + } + } +} + +private class TokenConverter : Converter { + + override fun convert(value: Token): TokenData { + return TokenData( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = value.networkId, + contractAddress = value.contractAddress, + decimalCount = value.decimalCount, + ) + } +} \ No newline at end of file diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt new file mode 100644 index 0000000000..bf2387f070 --- /dev/null +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt @@ -0,0 +1,43 @@ +package com.tangem.feature.referral.data + +import com.tangem.datasource.api.referral.ReferralApi +import com.tangem.datasource.api.referral.models.StartReferralBody +import com.tangem.feature.referral.converters.ReferralConverter +import com.tangem.feature.referral.domain.ReferralRepository +import com.tangem.feature.referral.domain.models.ReferralData +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import javax.inject.Inject + +internal class ReferralRepositoryImpl @Inject constructor( + private val referralApi: ReferralApi, + private val referralConverter: ReferralConverter, + private val coroutineDispatcher: CoroutineDispatcherProvider, +) : ReferralRepository { + + override suspend fun getReferralStatus(walletId: String): ReferralData { + return withContext(coroutineDispatcher.io) { + referralConverter.convert(referralApi.getReferralStatus(walletId)) + } + } + + override suspend fun startReferral( + walletId: String, + networkId: String, + tokenId: String, + address: String, + ): ReferralData { + return withContext(coroutineDispatcher.io) { + referralConverter.convert( + referralApi.startReferral( + StartReferralBody( + walletId = walletId, + networkId = networkId, + tokenId = tokenId, + address = address, + ), + ), + ) + } + } +} \ No newline at end of file diff --git a/features/referral/domain/build.gradle.kts b/features/referral/domain/build.gradle.kts index 4a5dd97a5c..561cbe2b15 100644 --- a/features/referral/domain/build.gradle.kts +++ b/features/referral/domain/build.gradle.kts @@ -26,6 +26,9 @@ android { dependencies { + /** Time */ + implementation(Library.jodatime) + /** DI */ implementation(Library.hilt) kapt(Library.hiltKapt) diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralRepository.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralRepository.kt new file mode 100644 index 0000000000..a329077747 --- /dev/null +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralRepository.kt @@ -0,0 +1,17 @@ +package com.tangem.feature.referral.domain + +import com.tangem.feature.referral.domain.models.ReferralData + +interface ReferralRepository { + + /** Returns data object of [ReferralData] depends on user program status */ + suspend fun getReferralStatus(walletId: String): ReferralData + + /** Starts user referral program */ + suspend fun startReferral( + walletId: String, + networkId: String, + tokenId: String, + address: String, + ): ReferralData +} \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt new file mode 100644 index 0000000000..ba5f803e59 --- /dev/null +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt @@ -0,0 +1,52 @@ +package com.tangem.feature.referral.domain.models + +import org.joda.time.DateTime + +sealed interface ReferralData { + + val award: Int + val discount: Int + val discountType: DiscountType + val tosLink: String + val tokens: List + + /** Data class that used if user is participant of program */ + data class ParticipantData( + override val award: Int, + override val discount: Int, + override val discountType: DiscountType, + override val tosLink: String, + override val tokens: List, + val referral: ReferralInfo, + ) : ReferralData + + /** Data class that used if user is not participant of program */ + data class NonParticipantData( + override val award: Int, + override val discount: Int, + override val discountType: DiscountType, + override val tosLink: String, + override val tokens: List, + ) : ReferralData +} + +data class TokenData( + val id: String, + val name: String, + val symbol: String, + val networkId: String?, + val contractAddress: String?, + val decimalCount: Int, +) + +data class ReferralInfo( + val shareLink: String, + val address: String, + val promocode: String, + val walletsPurchased: Int, + val termsAcceptedAt: DateTime?, +) + +enum class DiscountType { + PERCENTAGE, VALUE +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 878d6a3498..05eaab6228 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ include(":domain") include(":network") include(":common") include(":core:datasource") +include(":core:utils") include(":features:referral:data") include(":features:referral:domain") include(":features:referral:presentation") \ No newline at end of file From 56b064b1a021c09073b264de21dd030618e7e452 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Nov 2022 17:26:57 +0300 Subject: [PATCH 053/176] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../tap/common/compose/TangemTypography.kt | 8 +- core/ui/.gitignore | 1 + core/ui/build.gradle.kts | 38 ++++ core/ui/src/main/AndroidManifest.xml | 2 + .../com/tangem/core/ui/components/AppBar.kt | 72 ++++++++ .../com/tangem/core/ui/components/Buttons.kt | 164 ++++++++++++++++++ .../com/tangem/core/ui/components/Cards.kt | 79 +++++++++ .../com/tangem/core/ui/res/ButtonColor.kt | 16 ++ .../main/java/com/tangem/core/ui/res/Color.kt | 45 +++++ .../com/tangem/core/ui/res/TangemTheme.kt | 46 +++++ .../tangem/core/ui/res/TangemTypography.kt | 82 +++++++++ .../java/com/tangem/core/ui/res/TextColor.kt | 20 +++ .../ui}/src/main/res/drawable/ic_back.xml | 0 .../ui}/src/main/res/drawable/ic_tangem.xml | 0 .../main/res/drawable/ill_businessman_3d.webp | Bin 0 -> 76206 bytes core/ui/src/main/res/font/roboto_medium.ttf | Bin 0 -> 168644 bytes core/ui/src/main/res/font/roboto_regular.ttf | Bin 0 -> 168260 bytes core/ui/src/main/res/values/dimens.xml | 31 ++++ settings.gradle.kts | 9 +- 20 files changed, 609 insertions(+), 5 deletions(-) create mode 100644 core/ui/.gitignore create mode 100644 core/ui/build.gradle.kts create mode 100644 core/ui/src/main/AndroidManifest.xml create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/Color.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt rename {app => core/ui}/src/main/res/drawable/ic_back.xml (100%) rename {app => core/ui}/src/main/res/drawable/ic_tangem.xml (100%) create mode 100644 core/ui/src/main/res/drawable/ill_businessman_3d.webp create mode 100644 core/ui/src/main/res/font/roboto_medium.ttf create mode 100644 core/ui/src/main/res/font/roboto_regular.ttf create mode 100644 core/ui/src/main/res/values/dimens.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fe20c4709a..847507ba5f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -109,6 +109,7 @@ dependencies { implementation(project(":domain")) implementation(project(":network")) implementation(project(":common")) + implementation(project(":core:ui")) /** AndroidX libraries */ implementation(AndroidX.coreKtx) diff --git a/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt b/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt index 88b088fa6a..2013295f25 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/TangemTypography.kt @@ -9,13 +9,13 @@ import com.tangem.wallet.R object TangemTypography { val body1 = TextStyle( - fontFamily = FontFamily(Font(R.font.roboto)), + fontFamily = FontFamily(Font(R.font.roboto_regular)), fontSize = 16.0.sp, letterSpacing = 0.5.sp, lineHeight = 24.0.sp, ) val body2 = TextStyle( - fontFamily = FontFamily(Font(R.font.roboto)), + fontFamily = FontFamily(Font(R.font.roboto_regular)), fontSize = 14.0.sp, letterSpacing = 0.25.sp, lineHeight = 20.0.sp, @@ -27,13 +27,13 @@ object TangemTypography { lineHeight = 20.0.sp, ) val caption = TextStyle( - fontFamily = FontFamily(Font(R.font.roboto)), + fontFamily = FontFamily(Font(R.font.roboto_regular)), fontSize = 12.0.sp, letterSpacing = 0.4000000059604645.sp, lineHeight = 16.0.sp, ) val headline1 = TextStyle( - fontFamily = FontFamily(Font(R.font.roboto)), + fontFamily = FontFamily(Font(R.font.roboto_regular)), fontSize = 34.0.sp, letterSpacing = 0.0.sp, lineHeight = 44.0.sp, diff --git a/core/ui/.gitignore b/core/ui/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/core/ui/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts new file mode 100644 index 0000000000..ee2eab0e2a --- /dev/null +++ b/core/ui/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + id("com.android.library") + kotlin("android") +} + +android { + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose + } +} + +dependencies { + + /** Compose */ + implementation(Compose.foundation) + implementation(Compose.material) + implementation(Compose.uiTooling) +} \ No newline at end of file diff --git a/core/ui/src/main/AndroidManifest.xml b/core/ui/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..c5a36c7e17 --- /dev/null +++ b/core/ui/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt new file mode 100644 index 0000000000..6810ba11e8 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt @@ -0,0 +1,72 @@ +package com.tangem.core.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemTheme + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun PreviewAppBarWithBackButtonInLightTheme() { + TangemTheme(isDarkTheme = false) { + AppBarWithBackButton(text = "Title", onBackClick = {}) + } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun PreviewAppBarWithBackButtonInDarkTheme() { + TangemTheme(isDarkTheme = true) { + AppBarWithBackButton(text = "Title", onBackClick = {}) + } +} + +/** + * App bar with back button and optional title + * + * @param text optional title + * @param onBackClick the lambda to be invoked when this icon is pressed + * + * @see Figma component + */ +@Composable +fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { + Row( + modifier = Modifier + .background(MaterialTheme.colors.primaryVariant) + .padding(all = dimensionResource(R.dimen.spacing16)), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing16)), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(R.drawable.ic_back), + contentDescription = null, + modifier = Modifier + .size(size = dimensionResource(R.dimen.size24)) + .clickable { onBackClick() }, + tint = MaterialTheme.colors.onPrimary, + ) + if (!text.isNullOrBlank()) { + Text( + text = text, + color = MaterialTheme.colors.onPrimary, + maxLines = 1, + style = MaterialTheme.typography.subtitle1, + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt new file mode 100644 index 0000000000..e9e49c5c71 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -0,0 +1,164 @@ +package com.tangem.core.ui.components + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.res.ButtonColorType +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TextColorType +import com.tangem.core.ui.res.buttonColor +import com.tangem.core.ui.res.textColor + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { + TangemTheme(isDarkTheme = false) { + PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { + TangemTheme(isDarkTheme = false) { + PrimaryStartIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem, + enabled = false, + onClicked = {}, + ) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + PrimaryStartIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem, + enabled = false, + onClicked = {}, + ) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { + TangemTheme(isDarkTheme = false) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { + TangemTheme(isDarkTheme = false) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) + } +} + +/** + * Primary button with an icon at the beginning of the layout + * + * @param text button text + * @param iconResId button icon res id + * @param enabled controls the enabled state of the button + * @param onClicked the lambda to be invoked when this button is pressed + * + * @see Figma component + */ +@Composable +fun PrimaryStartIconButton(text: String, @DrawableRes iconResId: Int, enabled: Boolean = true, onClicked: () -> Unit) { + PrimaryButtonRow(enabled = enabled, onClicked = onClicked) { + Icon( + painter = painterResource(id = iconResId), + contentDescription = null, + ) + Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) + Text(text = text) + } +} + +/** + * Primary button with an icon at the end of the layout + * + * @param text button text + * @param iconResId button icon res id + * @param enabled controls the enabled state of the button + * @param onClicked the lambda to be invoked when this button is pressed + * + * @see Figma component + */ +@Composable +fun PrimaryEndIconButton(text: String, @DrawableRes iconResId: Int, enabled: Boolean = true, onClicked: () -> Unit) { + PrimaryButtonRow(enabled = enabled, onClicked = onClicked) { + Text(text = text) + Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) + Icon( + painter = painterResource(id = iconResId), + contentDescription = null, + ) + } +} + +@Composable +private fun PrimaryButtonRow(enabled: Boolean, onClicked: () -> Unit, content: @Composable (RowScope.() -> Unit)) { + Button( + onClick = onClicked, + modifier = Modifier + .fillMaxWidth() + .height(dimensionResource(R.dimen.size48)), + enabled = enabled, + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY), + contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + disabledBackgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.DISABLED), + disabledContentColor = MaterialTheme.colors.textColor(type = TextColorType.DISABLED), + ), + content = content, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt new file mode 100644 index 0000000000..57a47dff46 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -0,0 +1,79 @@ +package com.tangem.core.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TextColorType +import com.tangem.core.ui.res.textColor + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_SimpleInfoCard_InLightTheme() { + TangemTheme(isDarkTheme = false) { + SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC") + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_SimpleInfoCard_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC") + } +} + +/** + * Small card with text information attached to the edges + * + * @param startText text information attached to the left edge + * @param endText text information attached to the right edge + * + * @see Figma component + */ +@Composable +fun SmallInfoCard(startText: String, endText: String) { + Surface( + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + color = MaterialTheme.colors.secondary, + elevation = dimensionResource(id = R.dimen.elevation2), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(dimensionResource(R.dimen.size48)) + .padding( + horizontal = dimensionResource(id = R.dimen.spacing16), + vertical = dimensionResource(id = R.dimen.spacing12), + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = startText, + color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), + maxLines = 1, + style = MaterialTheme.typography.subtitle2 + ) + Text( + text = endText, + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + maxLines = 1, + style = MaterialTheme.typography.body2 + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt b/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt new file mode 100644 index 0000000000..cb6607c8a3 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt @@ -0,0 +1,16 @@ +package com.tangem.core.ui.res + +import androidx.compose.material.Colors +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +enum class ButtonColorType(val lightColor: Color, val darkColor: Color) { + PRIMARY(lightColor = Dark6, darkColor = Light1), + SECONDARY(lightColor = Light2, darkColor = Dark5), + DISABLED(lightColor = Light2, darkColor = Dark6), + POSITIVE(lightColor = Meadow, darkColor = Meadow), + POSITIVE_DISABLED(lightColor = MagicMint, darkColor = DarkGreen) +} + +@Composable +fun Colors.buttonColor(type: ButtonColorType): Color = if (isSystemInDarkTheme) type.darkColor else type.lightColor \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/Color.kt b/core/ui/src/main/java/com/tangem/core/ui/res/Color.kt new file mode 100644 index 0000000000..4d37ecee45 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/Color.kt @@ -0,0 +1,45 @@ +package com.tangem.core.ui.res + +import androidx.compose.ui.graphics.Color + +// region Base +val Black = Color(0xFF000000) +val White = Color(0xFFFFFFFF) +// endregion Base + +// region Dark +val Dark1 = Color(0xFF919191) +val Dark2 = Color(0xFF656565) +val Dark3 = Color(0xFF494949) +val Dark4 = Color(0xFF3B3B3B) +val Dark5 = Color(0xFF303030) +val Dark6 = Color(0xFF1E1E1E) +// endregion Dark + +// region Light +val Light1 = Color(0xFFF5F5F5) +val Light2 = Color(0xFFEBEBEB) +val Light3 = Color(0xFFD3D3D3) +val Light4 = Color(0xFFC9C9C9) +val Light5 = Color(0xFFB0B0B0) +// endregion Light + +// region Green +val Meadow = Color(0xFF1ACE80) +val MagicMint = Color(0xFFA3EBCC) +val DarkGreen = Color(0xFF06311F) +// endregion Green + +// region Blue +val Cobalt = Color(0xFF0029FF) +val Azure = Color(0xFF007AFF) +// endregion Blue + +// region Red +val Amaranth = Color(0xFFDE1010) +val Flamingo = Color(0xFFED7979) +// endregion Red + +// region Yellow +val Tangerine = Color(0xFFFFB71B) +// endregion Yellow \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt new file mode 100644 index 0000000000..2e065ba6ee --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -0,0 +1,46 @@ +package com.tangem.core.ui.res + +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +internal val isSystemInDarkTheme: Boolean = false // TODO: use isSystemInDarkTheme() for automatic color detection + +@Composable +fun TangemTheme( + isDarkTheme: Boolean = isSystemInDarkTheme, + content: @Composable () -> Unit, +) { + MaterialTheme( + colors = if (isDarkTheme) DarkColors else LightColors, + typography = TangemTypography, + content = content, + ) +} + +private val LightColors = lightColors( + primary = Light1, + primaryVariant = Light1, + secondary = White, + background = Light1, + surface = White, + error = Amaranth, + onPrimary = Dark6, + onSecondary = Dark6, + onBackground = Dark6, + onSurface = Dark6, +) + +private val DarkColors = darkColors( + primary = Black, + primaryVariant = Black, + secondary = Dark6, + background = Black, + surface = Dark6, + error = Amaranth, + onPrimary = White, + onSecondary = White, + onBackground = White, + onSurface = White, +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt new file mode 100644 index 0000000000..89ec283924 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt @@ -0,0 +1,82 @@ +package com.tangem.core.ui.res + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.ExperimentalUnitApi +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType +import androidx.compose.ui.unit.sp +import com.tangem.core.ui.R + +private val RobotoFamily = FontFamily( + Font(R.font.roboto_regular, FontWeight.Normal), + Font(R.font.roboto_medium, FontWeight.Medium), +) + +@OptIn(ExperimentalUnitApi::class) +val TangemTypography = Typography( + defaultFontFamily = RobotoFamily, + h1 = TextStyle( + fontSize = 34.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(0f, TextUnitType.Sp), + lineHeight = TextUnit(44f, TextUnitType.Sp), + ), + h2 = TextStyle( + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + letterSpacing = TextUnit(0.18f, TextUnitType.Sp), + lineHeight = TextUnit(32f, TextUnitType.Sp), + ), + h3 = TextStyle( + fontSize = 20.sp, + fontWeight = FontWeight.Medium, + letterSpacing = TextUnit(0.15f, TextUnitType.Sp), + lineHeight = TextUnit(24f, TextUnitType.Sp), + ), + subtitle1 = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + letterSpacing = TextUnit(0.15f, TextUnitType.Sp), + lineHeight = TextUnit(24f, TextUnitType.Sp), + ), + subtitle2 = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + letterSpacing = TextUnit(0.5f, TextUnitType.Sp), + lineHeight = TextUnit(24f, TextUnitType.Sp), + ), + body1 = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(0.5f, TextUnitType.Sp), + lineHeight = TextUnit(24f, TextUnitType.Sp), + ), + body2 = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(0.25f, TextUnitType.Sp), + lineHeight = TextUnit(20f, TextUnitType.Sp), + ), + button = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + letterSpacing = TextUnit(0.1f, TextUnitType.Sp), + lineHeight = TextUnit(16f, TextUnitType.Sp), + ), + caption = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Normal, + letterSpacing = TextUnit(0.4f, TextUnitType.Sp), + lineHeight = TextUnit(16f, TextUnitType.Sp), + ), + overline = TextStyle( + fontSize = 10.sp, + fontWeight = FontWeight.Medium, + letterSpacing = TextUnit(1.5f, TextUnitType.Sp), + lineHeight = TextUnit(16f, TextUnitType.Sp), + ), +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt new file mode 100644 index 0000000000..44f94e1157 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt @@ -0,0 +1,20 @@ +package com.tangem.core.ui.res + +import androidx.compose.material.Colors +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +enum class TextColorType(val lightColor: Color, val darkColor: Color) { + PRIMARY1(lightColor = Dark6, darkColor = White), + PRIMARY2(lightColor = White, darkColor = Dark6), + SECONDARY(lightColor = Dark2, darkColor = Light5), + TERTIARY(lightColor = Dark1, darkColor = Dark1), + DISABLED(lightColor = Light4, darkColor = Dark3), + ACCENT(lightColor = Meadow, darkColor = Meadow), + WARNING(lightColor = Amaranth, darkColor = Amaranth), + ATTENTION(lightColor = Tangerine, darkColor = Tangerine), + CONSTANT_WHITE(lightColor = White, darkColor = White) +} + +@Composable +fun Colors.textColor(type: TextColorType): Color = if (isSystemInDarkTheme) type.darkColor else type.lightColor \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_back.xml b/core/ui/src/main/res/drawable/ic_back.xml similarity index 100% rename from app/src/main/res/drawable/ic_back.xml rename to core/ui/src/main/res/drawable/ic_back.xml diff --git a/app/src/main/res/drawable/ic_tangem.xml b/core/ui/src/main/res/drawable/ic_tangem.xml similarity index 100% rename from app/src/main/res/drawable/ic_tangem.xml rename to core/ui/src/main/res/drawable/ic_tangem.xml diff --git a/core/ui/src/main/res/drawable/ill_businessman_3d.webp b/core/ui/src/main/res/drawable/ill_businessman_3d.webp new file mode 100644 index 0000000000000000000000000000000000000000..a018c1b5bb6bfd447600ab7422a74239170983c3 GIT binary patch literal 76206 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 X76wnEi(n^v8!x?bp_;lO!DIggn3ZN8 literal 0 HcmV?d00001 diff --git a/core/ui/src/main/res/font/roboto_regular.ttf b/core/ui/src/main/res/font/roboto_regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ddf4bfacb396e97546364ccfeeb9c31dfaea4c25 GIT binary patch literal 168260 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

S&jh1n&JeeMOkU4{=4@cYr6k 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≤FHXCuU9Qce=@vmZ{%9^5??OaVQJ8!4u8-w;yt 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 + + + 1dp + 2dp + 3dp + 4dp + 6dp + 8dp + 12dp + 16dp + 24dp + + + + 12dp + + + + 24dp + 48dp + 200dp + + + + 8dp + 12dp + 16dp + 24dp + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 05eaab6228..5387734770 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,8 +2,15 @@ include(":app") include(":domain") include(":network") include(":common") + +// region Core modules include(":core:datasource") include(":core:utils") +include(":core:ui") +// endregion Core modules + +// region Feature modules include(":features:referral:data") include(":features:referral:domain") -include(":features:referral:presentation") \ No newline at end of file +include(":features:referral:presentation") +// endregion Feature modules \ No newline at end of file From 098f73eb380ecb3b1f4f417f83a3e86e98c0ad4f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 16 Nov 2022 09:51:52 +0300 Subject: [PATCH 054/176] Updated on 2026-08-14 --- .../redux/middlewares/WalletMiddleware.kt | 36 ++++++++++++------- app/src/main/res/values-ru/strings_final.xml | 1 + app/src/main/res/values/strings_final.xml | 1 + 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 30a72f0fd4..ea47afc58a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.wallet.redux.middlewares +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType @@ -17,6 +18,7 @@ import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl +import com.tangem.tap.common.extensions.dispatchToastNotification import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.extensions.shareText import com.tangem.tap.common.extensions.stripZeroPlainString @@ -45,6 +47,7 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.wallet.R import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch @@ -118,10 +121,9 @@ class WalletMiddleware { true, ), ) - store.dispatch(WalletAction.LoadWallet.Success( - action.wallet, - action.blockchain - )) + store.dispatch( + action = WalletAction.LoadWallet.Success(action.wallet, action.blockchain) + ) } } store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) @@ -257,9 +259,17 @@ class WalletMiddleware { return } val newAction = prepareSendAction(action.amount, store.state.walletState) - store.dispatch(newAction) - if (newAction is PrepareSendScreen) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + if (newAction is PrepareSendScreen && newAction.walletManager == null) { + store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home)) + FirebaseCrashlytics.getInstance().recordException( + IllegalStateException("PrepareSendScreen: walletManager is null") + ) + store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found) + } else { + store.dispatch(newAction) + if (newAction is PrepareSendScreen) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + } } } } @@ -366,11 +376,13 @@ class WalletMiddleware { val currency = walletManager.wallet.blockchain.currency if (show) { - dispatchOnMain(WalletAction.SetWalletRent( - wallet = walletManager.wallet, - minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"), - rentExempt = ("${rentExempt.stripZeroPlainString()} $currency") - )) + dispatchOnMain( + WalletAction.SetWalletRent( + wallet = walletManager.wallet, + minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"), + rentExempt = ("${rentExempt.stripZeroPlainString()} $currency") + ) + ) } else { dispatchOnMain(WalletAction.RemoveWalletRent(walletManager.wallet)) } diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index fe86cb0019..ad4f23ca35 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -152,4 +152,5 @@ Вы хотите выйти из процесса активации? В этом случае вам будет необходимо начать процесс заново. Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. + Внутренняя ошибка: не удается найти менеджер кошельков diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index f5e11c6885..70e85346c6 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -154,4 +154,5 @@ Do you want to exit the activation process? In this case, you will need to start from the beginning. You have used a card from another wallet. Tap the card associated with this wallet + Internal error: wallet manager not found From b925625342f8cba4d74c953e78222fbbf4e8082c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 16 Nov 2022 16:28:08 +0300 Subject: [PATCH 055/176] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../tangem/tap/proxy/DerivationManagerImpl.kt | 14 +++++ .../tangem/tap/proxy/UserWalletManagerImpl.kt | 27 ++++++++++ .../api/referral/models/ReferralResponse.kt | 2 +- features/referral/domain/build.gradle.kts | 4 ++ .../referral/domain/ReferralInteractor.kt | 10 ++++ .../referral/domain/ReferralInteractorImpl.kt | 54 +++++++++++++++++++ .../domain/converter/TokensConverter.kt | 19 +++++++ .../referral/domain/models/ReferralData.kt | 2 +- libs/crypto/.gitignore | 1 + libs/crypto/build.gradle.kts | 29 ++++++++++ libs/crypto/src/main/AndroidManifest.xml | 2 + .../tangem/lib/crypto/DerivationManager.kt | 8 +++ .../tangem/lib/crypto/UserWalletManager.kt | 40 ++++++++++++++ .../com/tangem/lib/crypto/models/Token.kt | 10 ++++ settings.gradle.kts | 3 +- 16 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt create mode 100644 app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt create mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractor.kt create mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt create mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt create mode 100644 libs/crypto/.gitignore create mode 100644 libs/crypto/build.gradle.kts create mode 100644 libs/crypto/src/main/AndroidManifest.xml create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fe20c4709a..37328a93f3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -109,6 +109,7 @@ dependencies { implementation(project(":domain")) implementation(project(":network")) implementation(project(":common")) + implementation(project(":libs:crypto")) /** AndroidX libraries */ implementation(AndroidX.coreKtx) diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt new file mode 100644 index 0000000000..a323e29196 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -0,0 +1,14 @@ +package com.tangem.tap.proxy + +import com.tangem.crypto.DerivationManager + +class DerivationManagerImpl : DerivationManager { + + override fun deriveMissingBlockchains(networkId: String?) { + TODO("Not yet implemented") + } + + override fun hasDerivation(): Boolean { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt new file mode 100644 index 0000000000..45843af752 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.proxy + +import com.tangem.crypto.UserWalletManager +import com.tangem.crypto.models.Token + +class UserWalletManagerImpl : UserWalletManager { + + override fun getUserTokens(): List { + TODO("Not yet implemented") + } + + override fun getWalletId(): String { + TODO("Not yet implemented") + } + + override fun isTokenAdded(token: Token): Boolean { + TODO("Not yet implemented") + } + + override fun addToken(token: Token) { + TODO("Not yet implemented") + } + + override fun getWalletAddress(token: Token): String { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt index ef8823e877..061aa55155 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt @@ -26,7 +26,7 @@ data class Token( @Json(name = "symbol") val symbol: String, @Json(name = "networkId") val networkId: String?, @Json(name = "contractAddress") val contractAddress: String?, - @Json(name = "decimalCount") val decimalCount: Int, + @Json(name = "decimalCount") val decimalCount: Int?, ) data class Referral( diff --git a/features/referral/domain/build.gradle.kts b/features/referral/domain/build.gradle.kts index 561cbe2b15..bcee4c51cc 100644 --- a/features/referral/domain/build.gradle.kts +++ b/features/referral/domain/build.gradle.kts @@ -26,6 +26,10 @@ android { dependencies { + /** Libs */ + implementation(project(":core:utils")) + implementation(project(":libs:crypto")) + /** Time */ implementation(Library.jodatime) diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractor.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractor.kt new file mode 100644 index 0000000000..a59b0e3b56 --- /dev/null +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractor.kt @@ -0,0 +1,10 @@ +package com.tangem.feature.referral.domain + +import com.tangem.feature.referral.domain.models.ReferralData + +interface ReferralInteractor { + + suspend fun getReferralStatus(): ReferralData + + suspend fun startReferral(): ReferralData +} \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt new file mode 100644 index 0000000000..3e2dd73ba7 --- /dev/null +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -0,0 +1,54 @@ +package com.tangem.feature.referral.domain + +import com.tangem.crypto.DerivationManager +import com.tangem.crypto.UserWalletManager +import com.tangem.crypto.models.Token +import com.tangem.feature.referral.domain.converter.TokensConverter +import com.tangem.feature.referral.domain.models.ReferralData +import com.tangem.feature.referral.domain.models.TokenData + +internal class ReferralInteractorImpl( + private val repository: ReferralRepository, + private val derivationManager: DerivationManager, + private val userWalletManager: UserWalletManager, + private val tokensConverter: TokensConverter, +) : ReferralInteractor { + + private val tokensForReferral = mutableListOf() + + override suspend fun getReferralStatus(): ReferralData { + val refStatus = repository.getReferralStatus(userWalletManager.getWalletId()) + saveRefTokens(refStatus.tokens) + return refStatus + } + + override suspend fun startReferral(): ReferralData { + if (tokensForReferral.isNotEmpty()) { + val token = tokensConverter.convert(tokensForReferral.first()) + deriveOrAddTokens(token) + val publicAddress = userWalletManager.getWalletAddress(token) + return repository.startReferral( + walletId = userWalletManager.getWalletId(), + networkId = token.networkId ?: "", + tokenId = token.id, + address = publicAddress, + ) + } else { + throw IllegalStateException("tokens for ref is empty") + } + } + + private fun deriveOrAddTokens(token: Token) { + if (!derivationManager.hasDerivation()) { + derivationManager.deriveMissingBlockchains(token.networkId) + } + if (!userWalletManager.isTokenAdded(token)) { + userWalletManager.addToken(token) + } + } + + private fun saveRefTokens(tokens: List) { + tokensForReferral.clear() + tokensForReferral.addAll(tokens) + } +} \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt new file mode 100644 index 0000000000..0a8c565e68 --- /dev/null +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt @@ -0,0 +1,19 @@ +package com.tangem.feature.referral.domain.converter + +import com.tangem.crypto.models.Token +import com.tangem.feature.referral.domain.models.TokenData +import com.tangem.utils.converter.Converter + +class TokensConverter : Converter { + + override fun convert(value: TokenData): Token { + return Token( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = value.networkId, + contractAddress = value.contractAddress, + decimalCount = value.decimalCount, + ) + } +} \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt index ba5f803e59..79216aec7d 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt @@ -36,7 +36,7 @@ data class TokenData( val symbol: String, val networkId: String?, val contractAddress: String?, - val decimalCount: Int, + val decimalCount: Int?, ) data class ReferralInfo( diff --git a/libs/crypto/.gitignore b/libs/crypto/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/libs/crypto/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/libs/crypto/build.gradle.kts b/libs/crypto/build.gradle.kts new file mode 100644 index 0000000000..b6d1313ae0 --- /dev/null +++ b/libs/crypto/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + id("com.android.library") + kotlin("android") +} + +android { + + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } +} + +dependencies { + + /** Coroutines */ + implementation(Library.coroutine) +} \ No newline at end of file diff --git a/libs/crypto/src/main/AndroidManifest.xml b/libs/crypto/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..1fd7238d41 --- /dev/null +++ b/libs/crypto/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt new file mode 100644 index 0000000000..8e77e85480 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt @@ -0,0 +1,8 @@ +package com.tangem.crypto + +interface DerivationManager { + + fun deriveMissingBlockchains(networkId: String?) + + fun hasDerivation(): Boolean +} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt new file mode 100644 index 0000000000..8bb117cd15 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -0,0 +1,40 @@ +package com.tangem.crypto + +import com.tangem.crypto.models.Token + +/** + * Provider for user tokens data + */ +interface UserWalletManager { + + /** + * Returns all user tokens (merged from local and backend) + */ + fun getUserTokens(): List + + /** + * Returns user walletId + */ + fun getWalletId(): String + + /** + * Checks that token added to user wallet + * + * @param token to receive referral payments + */ + fun isTokenAdded(token: Token): Boolean + + /** + * Adds token to wallet if its not + * + * @param token to add to wallet + */ + fun addToken(token: Token) + + /** + * Returns wallet public address for token + * + * @param token for which find address + */ + fun getWalletAddress(token: Token): String +} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt new file mode 100644 index 0000000000..86c314a573 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt @@ -0,0 +1,10 @@ +package com.tangem.crypto.models + +data class Token( + val id: String, + val name: String, + val symbol: String, + val networkId: String?, + val contractAddress: String?, + val decimalCount: Int?, +) \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 05eaab6228..d5d7b47b7d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,4 +6,5 @@ include(":core:datasource") include(":core:utils") include(":features:referral:data") include(":features:referral:domain") -include(":features:referral:presentation") \ No newline at end of file +include(":features:referral:presentation") +include(":libs:crypto") \ No newline at end of file From b47cc52e7054ba1e3e4c78eff734c3f87ac6bed0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 16 Nov 2022 13:03:38 +0300 Subject: [PATCH 056/176] Updated on 2026-08-14 --- app/build.gradle.kts | 2 +- buildSrc/src/main/java/Dependency.kt | 3 +- buildSrc/src/main/java/Versions.kt | 3 +- .../com/tangem/core/ui/components/AppBar.kt | 4 +- .../com/tangem/core/ui/components/Spacers.kt | 19 + .../java/com/tangem/core/ui/res/IconColor.kt | 19 + core/ui/src/main/res/drawable/ic_discount.xml | 9 + core/ui/src/main/res/drawable/ic_tether.xml | 17 + core/ui/src/main/res/values/dimens.xml | 18 + .../referral/presentation/build.gradle.kts | 22 +- .../referral/models/ReferralStateHolder.kt | 37 ++ .../feature/referral/ui/ReferralScreen.kt | 362 ++++++++++++++++++ .../src/main/res/values-ru/string.xml | 20 + .../src/main/res/values/string.xml | 20 + 14 files changed, 546 insertions(+), 9 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt create mode 100644 core/ui/src/main/res/drawable/ic_discount.xml create mode 100644 core/ui/src/main/res/drawable/ic_tether.xml create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt create mode 100644 features/referral/presentation/src/main/res/values-ru/string.xml create mode 100644 features/referral/presentation/src/main/res/values/string.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 847507ba5f..d4505537cb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -176,7 +176,7 @@ dependencies { } implementation(Library.accompanistAppCompatTheme) implementation(Library.accompanistSystemUiController) - implementation(Library.skydovesVeil) + implementation(Library.xmlShimmer) implementation(Library.viewBindingDelegate) implementation(Library.armadillo) implementation(Library.googlePlayServicesWallet) diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt index 84f6380c8c..f93b728008 100644 --- a/buildSrc/src/main/java/Dependency.kt +++ b/buildSrc/src/main/java/Dependency.kt @@ -34,6 +34,7 @@ object Library { const val appsflyer = "com.appsflyer:af-android-sdk:" + Versions.appsflyer const val armadillo = "at.favre.lib:armadillo:" + Versions.armadillo const val coil = "io.coil-kt:coil:" + Versions.coil + const val composeShimmer = "com.valentinilk.shimmer:compose-shimmer:" + Versions.composeShimmer const val coroutine = "org.jetbrains.kotlinx:kotlinx-coroutines-core:" + Versions.coroutine const val desugarJdkLibs = "com.android.tools:desugar_jdk_libs:" + Versions.desugarJdkLibs const val googlePlayCore = "com.google.android.play:core:" + Versions.googlePlayCore @@ -52,7 +53,6 @@ object Library { const val okHttpLogging = "com.squareup.okhttp3:logging-interceptor:" + Versions.okhttp const val otaliastudiosCameraView = "com.otaliastudios:cameraview:" + Versions.otaliastudiosCameraView const val shopifyBuySdk = "com.shopify.mobilebuysdk:buy3:" + Versions.shopifyBuySdk - const val skydovesVeil = "com.github.skydoves:androidveil:" + Versions.skydovesAndroidVeil const val spongecastleCryptoCore = "com.madgag.spongycastle:core:" + Versions.spongycastleCryptoCore const val reKotlin = "org.rekotlin:rekotlin:" + Versions.rekotlin const val retrofit = "com.squareup.retrofit2:retrofit:" + Versions.retrofit @@ -60,6 +60,7 @@ object Library { const val timber = "com.jakewharton.timber:timber:" + Versions.timber const val viewBindingDelegate = "com.github.kirich1409:viewbindingpropertydelegate-noreflection:" + Versions.viewBindingDelegate + const val xmlShimmer = "com.github.skydoves:androidveil:" + Versions.xmlShimmer const val zendeskChat = "com.zendesk:chat:" + Versions.zendeskChat const val zendeskMessaging = "com.zendesk:messaging:" + Versions.zendeskMessaging const val zxingQrBarcodeScanner = "me.dm7.barcodescanner:zxing:" + Versions.zxingQrBarcodeScanner diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index b52ccb4d7b..54b4716dc7 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -28,6 +28,7 @@ object Versions { const val appsflyer = "6.5.1" const val armadillo = "0.9.0" const val coil = "2.1.0" + const val composeShimmer = "1.0.3" const val coroutine = "1.5.2" const val desugarJdkLibs = "1.1.5" const val firebase = "26.0.0" @@ -46,10 +47,10 @@ object Versions { const val retrofit = "2.8.1" const val retrofitMoshiConverter = "2.6.0" const val shopifyBuySdk = "12.0.0" - const val skydovesAndroidVeil = "1.1.2" const val spongycastleCryptoCore = "1.58.0.0" const val timber = "4.7.1" const val viewBindingDelegate = "1.5.6" + const val xmlShimmer = "1.1.2" const val zendeskChat = "3.3.5" const val zendeskMessaging = "5.2.4" const val zxingQrBarcodeScanner = "1.9.8" diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt index 6810ba11e8..6b5e3dc55d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.Icon @@ -48,7 +49,8 @@ fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { Row( modifier = Modifier .background(MaterialTheme.colors.primaryVariant) - .padding(all = dimensionResource(R.dimen.spacing16)), + .padding(all = dimensionResource(R.dimen.spacing16)) + .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing16)), verticalAlignment = Alignment.CenterVertically, ) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt new file mode 100644 index 0000000000..60ce6bf8bc --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -0,0 +1,19 @@ +package com.tangem.core.ui.components + +import androidx.annotation.DimenRes +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource + +@Composable +fun VerticalSpacer(@DimenRes spaceResId: Int) { + Spacer(modifier = Modifier.height(dimensionResource(id = spaceResId))) +} + +@Composable +fun HorizontalSpacer(@DimenRes spaceResId: Int) { + Spacer(modifier = Modifier.width(dimensionResource(id = spaceResId))) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt b/core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt new file mode 100644 index 0000000000..72cc8936bb --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt @@ -0,0 +1,19 @@ +package com.tangem.core.ui.res + +import androidx.compose.material.Colors +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +enum class IconColorType(val lightColor: Color, val darkColor: Color) { + PRIMARY1(lightColor = Black, darkColor = White), + PRIMARY2(lightColor = White, darkColor = Dark6), + SECONDARY(lightColor = Dark2, darkColor = Dark1), + INFORMATIVE(lightColor = Light5, darkColor = Dark2), + INACTIVE(lightColor = Light4, darkColor = Dark4), + ACCENT(lightColor = Meadow, darkColor = Meadow), + WARNING(lightColor = Amaranth, darkColor = Amaranth), + ATTENTION(lightColor = Tangerine, darkColor = Tangerine) +} + +@Composable +fun Colors.iconColor(type: IconColorType): Color = if (isSystemInDarkTheme) type.darkColor else type.lightColor \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_discount.xml b/core/ui/src/main/res/drawable/ic_discount.xml new file mode 100644 index 0000000000..5045eccd5b --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_discount.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_tether.xml b/core/ui/src/main/res/drawable/ic_tether.xml new file mode 100644 index 0000000000..e3f94b5dbb --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_tether.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/core/ui/src/main/res/values/dimens.xml b/core/ui/src/main/res/values/dimens.xml index cd873bfb37..3863794b73 100644 --- a/core/ui/src/main/res/values/dimens.xml +++ b/core/ui/src/main/res/values/dimens.xml @@ -13,19 +13,37 @@ + 6dp 12dp + 16dp + 11dp + 16dp 24dp + 28dp + 40dp 48dp + 56dp + 84dp + 86dp + 102dp + 164dp 200dp + 2dp + 4dp 8dp + 10dp 12dp 16dp 24dp + 32dp + 44dp + 50dp + 54dp \ No newline at end of file diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index 4a5dd97a5c..05a5ad278c 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -1,8 +1,6 @@ plugins { id("com.android.library") kotlin("android") - kotlin("kapt") - id("com.google.dagger.hilt.android") } android { @@ -22,11 +20,25 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 isCoreLibraryDesugaringEnabled = false } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose + } } dependencies { + /** Core modules */ + implementation(project(":core:ui")) - /** DI */ - implementation(Library.hilt) - kapt(Library.hiltKapt) + /** Compose */ + implementation(Compose.foundation) + implementation(Compose.material) + implementation(Compose.uiTooling) + + /** Other libraries */ + implementation(Library.composeShimmer) } \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt new file mode 100644 index 0000000000..4c592299c1 --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt @@ -0,0 +1,37 @@ +package com.tangem.feature.referral.models + +data class ReferralStateHolder( + val headerState: HeaderState, + val referralInfoState: ReferralInfoState, + val effects: Effects, +) { + + data class HeaderState(val onBackClicked: () -> Unit) + + sealed interface ReferralInfoContentState + + sealed interface ReferralInfoState { + + data class ParticipantContent( + val award: String, + val address: String, + val discount: String, + val onAgreementClicked: () -> Unit, + val onParticipateClicked: () -> Unit, + ) : ReferralInfoState, ReferralInfoContentState + + data class NonParticipantContent( + val award: String, + val discount: String, + val purchasedWalletCount: Int, + val code: String, + val onCopyClicked: () -> Unit, + val onShareClicked: () -> Unit, + val onAgreementClicked: () -> Unit, + ) : ReferralInfoState, ReferralInfoContentState + + object Loading : ReferralInfoState + } + + data class Effects(val showErrorToast: Boolean) +} diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt new file mode 100644 index 0000000000..c7894c0bea --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -0,0 +1,362 @@ +package com.tangem.feature.referral.ui + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.AppBarWithBackButton +import com.tangem.core.ui.components.VerticalSpacer +import com.tangem.core.ui.res.ButtonColorType +import com.tangem.core.ui.res.IconColorType +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TextColorType +import com.tangem.core.ui.res.White +import com.tangem.core.ui.res.buttonColor +import com.tangem.core.ui.res.iconColor +import com.tangem.core.ui.res.textColor +import com.tangem.feature.referral.models.ReferralStateHolder +import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState +import com.tangem.feature.referral.presentation.R +import com.valentinilk.shimmer.shimmer + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_Participant_InLightTheme() { + TangemTheme(isDarkTheme = false) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.ParticipantContent( + award = "10 USDT", + address = "ma80...zk8q2", + discount = "10%", + onAgreementClicked = {}, + onParticipateClicked = {}, + ), + effects = ReferralStateHolder.Effects(showErrorToast = false), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_Participant_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.ParticipantContent( + award = "10 USDT", + address = "ma80...zk8q2", + discount = "10%", + onAgreementClicked = {}, + onParticipateClicked = {}, + ), + effects = ReferralStateHolder.Effects(showErrorToast = false), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_NonParticipant_InLightTheme() { + TangemTheme(isDarkTheme = false) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.NonParticipantContent( + award = "10 USDT", + discount = "10%", + purchasedWalletCount = 3, + code = "x4JdK", + onCopyClicked = {}, + onShareClicked = {}, + onAgreementClicked = {}, + ), + effects = ReferralStateHolder.Effects(showErrorToast = false), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.NonParticipantContent( + award = "10 USDT", + discount = "10%", + purchasedWalletCount = 3, + code = "x4JdK", + onCopyClicked = {}, + onShareClicked = {}, + onAgreementClicked = {}, + ), + effects = ReferralStateHolder.Effects(showErrorToast = false), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_Loading_InLightTheme() { + TangemTheme(isDarkTheme = false) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.Loading, + effects = ReferralStateHolder.Effects(showErrorToast = false), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_Loading_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.Loading, + effects = ReferralStateHolder.Effects(showErrorToast = false), + ), + ) + } +} + +/** + * Referral program screen for participant and non-participant + * + * @param stateHolder state holder + */ +@Composable +fun ReferralScreen(stateHolder: ReferralStateHolder) { + Column( + modifier = Modifier + .background(color = MaterialTheme.colors.primary) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Header(stateHolder = stateHolder) + VerticalSpacer(spaceResId = R.dimen.spacing16) + ReferralInfo(stateHolder = stateHolder) + } +} + +@Composable +private fun ColumnScope.Header(stateHolder: ReferralStateHolder) { + AppBar(stateHolder = stateHolder) + Image( + painter = painterResource(R.drawable.ill_businessman_3d), + contentDescription = null, + modifier = Modifier + .padding(horizontal = dimensionResource(id = R.dimen.spacing32)) + .fillMaxWidth() + .height(dimensionResource(R.dimen.size200)), + ) + VerticalSpacer(spaceResId = R.dimen.spacing24) + Text( + text = stringResource(id = R.string.referral_title), + modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.spacing50)), + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + textAlign = TextAlign.Center, + maxLines = 2, + style = MaterialTheme.typography.h2, + ) +} + +@Composable +fun ReferralInfo(stateHolder: ReferralStateHolder) { + when (val state = stateHolder.referralInfoState) { + is ReferralInfoState.ParticipantContent, + is ReferralInfoState.NonParticipantContent -> { + Column(verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing32))) { + ConditionForYou(state = state as ReferralStateHolder.ReferralInfoContentState) + ConditionForYourFriend(state = state) + } + } + is ReferralInfoState.Loading -> { + Column(verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing32))) { + LoadingCondition(iconResId = R.drawable.ic_tether) + LoadingCondition(iconResId = R.drawable.ic_discount) + } + } + } +} + +@Composable +private fun AppBar(stateHolder: ReferralStateHolder) { + AppBarWithBackButton( + text = stringResource(R.string.details_referral_title), + onBackClick = stateHolder.headerState.onBackClicked, + ) +} + +@Composable +private fun ConditionForYou(state: ReferralStateHolder.ReferralInfoContentState) { + Condition(iconResId = R.drawable.ic_tether) { + when (state) { + is ReferralInfoState.ParticipantContent -> InfoForYou(award = state.award, address = state.address) + is ReferralInfoState.NonParticipantContent -> InfoForYou(award = state.award) + } + } +} + +@Composable +private fun ConditionForYourFriend(state: ReferralStateHolder.ReferralInfoContentState) { + Condition(iconResId = R.drawable.ic_discount) { + when (state) { + is ReferralInfoState.ParticipantContent -> InfoForYourFriend(discount = state.discount) + is ReferralInfoState.NonParticipantContent -> InfoForYourFriend(discount = state.discount) + } + } +} + +@Composable +private fun LoadingCondition(@DrawableRes iconResId: Int) { + Condition(iconResId = iconResId) { + ShimmerCondition() + } +} + +@Composable +private fun Condition(@DrawableRes iconResId: Int, infoBlock: @Composable () -> Unit) { + Row( + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.spacing16)), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing12)), + verticalAlignment = Alignment.Top, + ) { + Box( + modifier = Modifier + .background( + color = MaterialTheme.colors.buttonColor(type = ButtonColorType.SECONDARY), + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius16)), + ) + .size(dimensionResource(id = R.dimen.size56)), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = iconResId), + contentDescription = null, + modifier = Modifier.size(dimensionResource(id = R.dimen.size28)), + tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1), + ) + } + infoBlock() + } +} + +@Composable +private fun InfoForYou(award: String, address: String? = null) { + ConditionInfo(title = stringResource(id = R.string.referral_point_currencies_title)) { + Text( + text = buildAnnotatedString { + append(stringResource(id = R.string.referral_point_currencies_description_prefix)) + withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1))) { + append(" $award ") + } + append( + String.format( + stringResource(id = R.string.referral_point_currencies_description_suffix), + if (!address.isNullOrBlank()) address else "", + ), + ) + }, + color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), + style = MaterialTheme.typography.body2, + ) + } +} + +@Composable +private fun InfoForYourFriend(discount: String) { + ConditionInfo(title = stringResource(id = R.string.referral_point_discount_title)) { + Text( + text = buildString { + append(stringResource(id = R.string.referral_point_discount_description_prefix)) + append( + String.format( + stringResource(id = R.string.referral_point_discount_description_value), + " $discount", + ), + ) + append(" ") + append(stringResource(id = R.string.referral_point_discount_description_suffix)) + }, + color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), + style = MaterialTheme.typography.body2, + ) + } +} + +@Composable +private fun ConditionInfo(title: String, subtitleContent: @Composable () -> Unit) { + Column(verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing2))) { + Text( + text = title, + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + style = MaterialTheme.typography.subtitle1, + ) + subtitleContent() + } +} + +@Composable +private fun ShimmerCondition() { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = dimensionResource(id = R.dimen.spacing4)) + .shimmer(), + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing10)), + ) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(dimensionResource(id = R.dimen.radius6))) + .width(dimensionResource(id = R.dimen.size102)) + .height(dimensionResource(id = R.dimen.size16)) + .background(White), + ) + Box( + modifier = Modifier + .clip(RoundedCornerShape(dimensionResource(id = R.dimen.radius6))) + .width(dimensionResource(id = R.dimen.size40)) + .height(dimensionResource(id = R.dimen.size11)) + .background(White), + ) + } +} + diff --git a/features/referral/presentation/src/main/res/values-ru/string.xml b/features/referral/presentation/src/main/res/values-ru/string.xml new file mode 100644 index 0000000000..27e42e7c03 --- /dev/null +++ b/features/referral/presentation/src/main/res/values-ru/string.xml @@ -0,0 +1,20 @@ + + + Реферальная программа + Приведи друга в Tangem + Вы + Получите + за каждый кошелек, который купит ваш друг на ваш адрес в сети Tron %@ + Ваш друг + Получит + %@ скидку + при покупке карточки на сайте tangem.com + Нажимая на эту кнопку вы принимаете + условия участия + в реферальной программе + Участвовать + Вы приняли + Ваш персональный код + %d кошельков + Ваши друзья купили + \ No newline at end of file diff --git a/features/referral/presentation/src/main/res/values/string.xml b/features/referral/presentation/src/main/res/values/string.xml new file mode 100644 index 0000000000..7042ed2411 --- /dev/null +++ b/features/referral/presentation/src/main/res/values/string.xml @@ -0,0 +1,20 @@ + + + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your Tron network address %s + Your friend + Will get a + %s discount + when buying a card on tangem.com + By tapping this button you accept + terms and conditions + of the referral program + Participate + You\'ve accepted + Your personal code + %d wallets + Your friends bought + \ No newline at end of file From 47089f06068ee3dec0ccad5f7f8c60924f3fe2f3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 18 Nov 2022 13:00:15 +0300 Subject: [PATCH 057/176] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Buttons.kt | 31 +++- core/ui/src/main/res/drawable/ic_copy.xml | 9 ++ core/ui/src/main/res/drawable/ic_share.xml | 9 ++ .../src/main/res/values-ru/string_common.xml | 5 + core/ui/src/main/res/values/string_common.xml | 5 + .../feature/referral/ui/AgreementText.kt | 77 ++++++++++ .../referral/ui/NonParticipateBottomBlock.kt | 139 ++++++++++++++++++ .../referral/ui/ParticipateBottomBlock.kt | 50 +++++++ .../feature/referral/ui/ReferralScreen.kt | 55 +++++-- 9 files changed, 362 insertions(+), 18 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_copy.xml create mode 100644 core/ui/src/main/res/drawable/ic_share.xml create mode 100644 core/ui/src/main/res/values-ru/string_common.xml create mode 100644 core/ui/src/main/res/values/string_common.xml create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index e9e49c5c71..65b331e15c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -101,6 +101,7 @@ fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { /** * Primary button with an icon at the beginning of the layout * + * @param modifier button modifier * @param text button text * @param iconResId button icon res id * @param enabled controls the enabled state of the button @@ -110,8 +111,14 @@ fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { * >Figma component */ @Composable -fun PrimaryStartIconButton(text: String, @DrawableRes iconResId: Int, enabled: Boolean = true, onClicked: () -> Unit) { - PrimaryButtonRow(enabled = enabled, onClicked = onClicked) { +fun PrimaryStartIconButton( + modifier: Modifier = Modifier, + text: String, + @DrawableRes iconResId: Int, + enabled: Boolean = true, + onClicked: () -> Unit, +) { + PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { Icon( painter = painterResource(id = iconResId), contentDescription = null, @@ -124,6 +131,7 @@ fun PrimaryStartIconButton(text: String, @DrawableRes iconResId: Int, enabled: B /** * Primary button with an icon at the end of the layout * + * @param modifier button modifier * @param text button text * @param iconResId button icon res id * @param enabled controls the enabled state of the button @@ -133,8 +141,14 @@ fun PrimaryStartIconButton(text: String, @DrawableRes iconResId: Int, enabled: B * >Figma component */ @Composable -fun PrimaryEndIconButton(text: String, @DrawableRes iconResId: Int, enabled: Boolean = true, onClicked: () -> Unit) { - PrimaryButtonRow(enabled = enabled, onClicked = onClicked) { +fun PrimaryEndIconButton( + modifier: Modifier = Modifier, + text: String, + @DrawableRes iconResId: Int, + enabled: Boolean = true, + onClicked: () -> Unit, +) { + PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { Text(text = text) Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) Icon( @@ -145,10 +159,15 @@ fun PrimaryEndIconButton(text: String, @DrawableRes iconResId: Int, enabled: Boo } @Composable -private fun PrimaryButtonRow(enabled: Boolean, onClicked: () -> Unit, content: @Composable (RowScope.() -> Unit)) { +private fun PrimaryButtonRow( + modifier: Modifier, + enabled: Boolean, + onClicked: () -> Unit, + content: @Composable (RowScope.() -> Unit), +) { Button( onClick = onClicked, - modifier = Modifier + modifier = modifier .fillMaxWidth() .height(dimensionResource(R.dimen.size48)), enabled = enabled, diff --git a/core/ui/src/main/res/drawable/ic_copy.xml b/core/ui/src/main/res/drawable/ic_copy.xml new file mode 100644 index 0000000000..d01cdf9823 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_copy.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_share.xml b/core/ui/src/main/res/drawable/ic_share.xml new file mode 100644 index 0000000000..e35c52d223 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_share.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/values-ru/string_common.xml b/core/ui/src/main/res/values-ru/string_common.xml new file mode 100644 index 0000000000..ebb5d6b913 --- /dev/null +++ b/core/ui/src/main/res/values-ru/string_common.xml @@ -0,0 +1,5 @@ + + + Поделиться + Копировать + \ No newline at end of file diff --git a/core/ui/src/main/res/values/string_common.xml b/core/ui/src/main/res/values/string_common.xml new file mode 100644 index 0000000000..ad2cf0d79f --- /dev/null +++ b/core/ui/src/main/res/values/string_common.xml @@ -0,0 +1,5 @@ + + + Share + Copy + \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt new file mode 100644 index 0000000000..133b6b374e --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt @@ -0,0 +1,77 @@ +package com.tangem.feature.referral.ui + +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TextColorType +import com.tangem.core.ui.res.textColor +import com.tangem.feature.referral.presentation.R + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_AgreementText_InLightTheme() { + TangemTheme(isDarkTheme = false) { + Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { + AgreementText(firstPartResId = R.string.referral_tou_not_enroled_prefix, onClicked = {}) + } + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_AgreementText_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { + AgreementText(firstPartResId = R.string.referral_tou_not_enroled_prefix, onClicked = {}) + } + } +} + +@Composable +fun AgreementText(@StringRes firstPartResId: Int, onClicked: () -> Unit) { + val agreementText = annotatedAgreementString(firstPart = stringResource(firstPartResId)) + ClickableText( + text = agreementText, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimensionResource(id = R.dimen.spacing54)), + style = MaterialTheme.typography.caption.copy(textAlign = TextAlign.Center), + maxLines = 2, + onClick = { + val clickableSpanStyle = requireNotNull(agreementText.spanStyles.getOrNull(1)) + if (it in clickableSpanStyle.start..clickableSpanStyle.end) { + onClicked() + } + }, + ) +} + +@Composable +private fun annotatedAgreementString(firstPart: String): AnnotatedString { + return buildAnnotatedString { + withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY))) { + append("$firstPart ") + } + withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.ACCENT))) { + append(stringResource(id = R.string.common_terms_and_conditions)) + } + withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY))) { + append(" ${stringResource(id = R.string.referral_tou_suffix)}") + } + } +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt new file mode 100644 index 0000000000..59b7ab8148 --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -0,0 +1,139 @@ +package com.tangem.feature.referral.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryStartIconButton +import com.tangem.core.ui.components.SmallInfoCard +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TextColorType +import com.tangem.core.ui.res.textColor +import com.tangem.feature.referral.presentation.R + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_NonParticipateBottomBlock_InLightTheme() { + TangemTheme(isDarkTheme = false) { + Column(Modifier.background(MaterialTheme.colors.primary)) { + NonParticipateBottomBlock( + purchasedWalletCount = 3, + code = "x4JdK", + onCopyClicked = {}, + onShareClicked = {}, + onAgreementClicked = {}, + ) + } + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_NonParticipateBottomBlock_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + Column(Modifier.background(MaterialTheme.colors.primary)) { + NonParticipateBottomBlock( + purchasedWalletCount = 3, + code = "x4JdK", + onCopyClicked = {}, + onShareClicked = {}, + onAgreementClicked = {}, + ) + } + } +} + +@Composable +fun NonParticipateBottomBlock( + purchasedWalletCount: Int, + code: String, + onCopyClicked: () -> Unit, + onShareClicked: () -> Unit, + onAgreementClicked: () -> Unit, +) { + Column( + modifier = Modifier + .padding( + top = dimensionResource(id = R.dimen.spacing24), + bottom = dimensionResource(id = R.dimen.spacing16), + ) + .padding(horizontal = dimensionResource(id = R.dimen.spacing16)), + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)), + ) { + SmallInfoCard( + startText = stringResource(id = R.string.referral_friends_bought_title), + endText = String.format( + stringResource(id = R.string.referral_wallets_purchased_count), + purchasedWalletCount, + ), + ) + PersonalCodeCard(code = code) + AdditionalButtons(onCopyClicked = onCopyClicked, onShareClicked = onShareClicked) + AgreementText(firstPartResId = R.string.referral_tou_enroled_prefix, onClicked = onAgreementClicked) + } +} + +@Composable +private fun PersonalCodeCard(code: String) { + Column( + modifier = Modifier + .shadow( + elevation = dimensionResource(id = R.dimen.elevation2), + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + ) + .background( + color = MaterialTheme.colors.secondary, + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + ) + .fillMaxWidth() + .padding(vertical = dimensionResource(id = R.dimen.spacing12)), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing4)), + ) { + Text( + text = stringResource(id = R.string.referral_promo_code_title), + color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), + maxLines = 1, + style = MaterialTheme.typography.subtitle2, + ) + Text( + text = code, + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + maxLines = 1, + style = MaterialTheme.typography.h2, + ) + } +} + +@Composable +private fun AdditionalButtons(onCopyClicked: () -> Unit, onShareClicked: () -> Unit) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)), + ) { + PrimaryStartIconButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.common_copy), + iconResId = R.drawable.ic_copy, + onClicked = onCopyClicked, + ) + PrimaryStartIconButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.common_share), + iconResId = R.drawable.ic_share, + onClicked = onShareClicked, + ) + } +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt new file mode 100644 index 0000000000..7294cbd3f7 --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -0,0 +1,50 @@ +package com.tangem.feature.referral.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryEndIconButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.referral.presentation.R + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ParticipateBottomBlock_InLightTheme() { + TangemTheme(isDarkTheme = false) { + Column(Modifier.background(MaterialTheme.colors.primary)) { + ParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) + } + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ParticipateBottomBlock_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + Column(Modifier.background(MaterialTheme.colors.primary)) { + ParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) + } + } +} + +@Composable +fun ParticipateBottomBlock(onAgreementClicked: () -> Unit, onParticipateClicked: () -> Unit) { + Column { + AgreementText( + firstPartResId = R.string.referral_tou_not_enroled_prefix, + onClicked = onAgreementClicked, + ) + PrimaryEndIconButton( + modifier = Modifier.padding(all = dimensionResource(id = R.dimen.spacing16)), + text = stringResource(id = R.string.referral_button_participate), + iconResId = R.drawable.ic_tangem, + onClicked = onParticipateClicked, + ) + } +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index c7894c0bea..46de8b45c4 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -14,7 +14,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -167,7 +169,8 @@ fun ReferralScreen(stateHolder: ReferralStateHolder) { Column( modifier = Modifier .background(color = MaterialTheme.colors.primary) - .fillMaxSize(), + .fillMaxSize() + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, ) { Header(stateHolder = stateHolder) @@ -201,18 +204,20 @@ private fun ColumnScope.Header(stateHolder: ReferralStateHolder) { @Composable fun ReferralInfo(stateHolder: ReferralStateHolder) { when (val state = stateHolder.referralInfoState) { - is ReferralInfoState.ParticipantContent, + is ReferralInfoState.ParticipantContent -> { + Conditions(state = state) + VerticalSpacer(spaceResId = R.dimen.spacing44) + ParticipantBottomBlock(state = state) + } is ReferralInfoState.NonParticipantContent -> { - Column(verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing32))) { - ConditionForYou(state = state as ReferralStateHolder.ReferralInfoContentState) - ConditionForYourFriend(state = state) - } + Conditions(state = state) + VerticalSpacer(spaceResId = R.dimen.spacing24) + NonParticipantBottomBlock(state = state) } is ReferralInfoState.Loading -> { - Column(verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing32))) { - LoadingCondition(iconResId = R.drawable.ic_tether) - LoadingCondition(iconResId = R.drawable.ic_discount) - } + LoadingCondition(iconResId = R.drawable.ic_tether) + VerticalSpacer(spaceResId = R.dimen.spacing32) + LoadingCondition(iconResId = R.drawable.ic_discount) } } } @@ -225,6 +230,13 @@ private fun AppBar(stateHolder: ReferralStateHolder) { ) } +@Composable +private fun Conditions(state: ReferralStateHolder.ReferralInfoContentState) { + ConditionForYou(state = state) + VerticalSpacer(spaceResId = R.dimen.spacing32) + ConditionForYourFriend(state = state) +} + @Composable private fun ConditionForYou(state: ReferralStateHolder.ReferralInfoContentState) { Condition(iconResId = R.drawable.ic_tether) { @@ -248,7 +260,7 @@ private fun ConditionForYourFriend(state: ReferralStateHolder.ReferralInfoConten @Composable private fun LoadingCondition(@DrawableRes iconResId: Int) { Condition(iconResId = iconResId) { - ShimmerCondition() + ShimmerInfo() } } @@ -335,7 +347,7 @@ private fun ConditionInfo(title: String, subtitleContent: @Composable () -> Unit } @Composable -private fun ShimmerCondition() { +private fun ShimmerInfo() { Column( modifier = Modifier .fillMaxWidth() @@ -360,3 +372,22 @@ private fun ShimmerCondition() { } } +@Composable +private fun ParticipantBottomBlock(state: ReferralInfoState.ParticipantContent) { + ParticipateBottomBlock( + onAgreementClicked = state.onAgreementClicked, + onParticipateClicked = state.onParticipateClicked, + ) +} + +@Composable +private fun NonParticipantBottomBlock(state: ReferralInfoState.NonParticipantContent) { + NonParticipateBottomBlock( + purchasedWalletCount = state.purchasedWalletCount, + code = state.code, + onCopyClicked = state.onCopyClicked, + onShareClicked = state.onShareClicked, + onAgreementClicked = state.onAgreementClicked, + ) +} + From ed03d8530e93f0b89a8700914610a86f56b89d0e Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 19 Nov 2022 17:02:42 +0300 Subject: [PATCH 058/176] Updated on 2026-08-14 --- app/build.gradle.kts | 3 +- .../main/java/com/tangem/tap/MainActivity.kt | 2 + .../main/res/drawable/ic_more_vertical_24.xml | 10 - app/src/main/res/menu/menu_wallet.xml | 10 +- core/ui/build.gradle.kts | 3 + .../appbar/AppBarWithAdditionalButtons.kt | 169 ++++++++++ .../AppBarWithBackButton.kt} | 4 +- .../appbar/models/AdditionalButton.kt | 8 + .../components/atoms/BottomSheetIndicator.kt | 60 ++++ .../com/tangem/core/ui/res/TangemTheme.kt | 7 + .../main/res/drawable/ic_more_vertical.xml | 10 + .../ui}/src/main/res/drawable/ic_scan.xml | 2 +- core/ui/src/main/res/values/dimens.xml | 5 + .../referral/presentation/build.gradle.kts | 3 + .../referral/models/ReferralStateHolder.kt | 5 +- .../ui/AgreementBottomSheetContent.kt | 80 +++++ .../feature/referral/ui/ReferralScreen.kt | 302 +++++++++++------- .../src/main/res/values-ru/string.xml | 4 +- 18 files changed, 542 insertions(+), 145 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_more_vertical_24.xml create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt rename core/ui/src/main/java/com/tangem/core/ui/components/{AppBar.kt => appbar/AppBarWithBackButton.kt} (94%) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/AdditionalButton.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt create mode 100644 core/ui/src/main/res/drawable/ic_more_vertical.xml rename {app => core/ui}/src/main/res/drawable/ic_scan.xml (94%) create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7b8485d96e..dc249a446b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,6 +111,7 @@ dependencies { implementation(project(":common")) implementation(project(":core:ui")) implementation(project(":libs:crypto")) + implementation(project(":features:referral:presentation")) /** AndroidX libraries */ implementation(AndroidX.coreKtx) @@ -144,7 +145,7 @@ dependencies { /** DI */ implementation(Library.hilt) - implementation(Library.hiltKapt) + kapt(Library.hiltKapt) /** Other libraries */ implementation(Library.materialComponent) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index e557e59247..aa9e63c2dc 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -30,6 +30,7 @@ import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.features.shop.redux.ShopAction import com.tangem.wallet.R import com.tangem.wallet.databinding.ActivityMainBinding +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -49,6 +50,7 @@ private val mainCoroutineContext: CoroutineContext get() = Job() + Dispatchers.Main + FeatureCoroutineExceptionHandler.create("mainScope") val mainScope = CoroutineScope(mainCoroutineContext) +@AndroidEntryPoint class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { private var snackbar: Snackbar? = null diff --git a/app/src/main/res/drawable/ic_more_vertical_24.xml b/app/src/main/res/drawable/ic_more_vertical_24.xml deleted file mode 100644 index fd51d1fbf2..0000000000 --- a/app/src/main/res/drawable/ic_more_vertical_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/menu/menu_wallet.xml b/app/src/main/res/menu/menu_wallet.xml index edd88afb93..c5848ecb6a 100644 --- a/app/src/main/res/menu/menu_wallet.xml +++ b/app/src/main/res/menu/menu_wallet.xml @@ -2,9 +2,9 @@

+ android:id="@+id/details_menu" + android:title="@string/details_title" + android:icon="@drawable/ic_more_vertical" + app:iconTint="@color/darkGray6" + app:showAsAction="ifRoom" /> diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index ee2eab0e2a..3c509f8c3f 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -35,4 +35,7 @@ dependencies { implementation(Compose.foundation) implementation(Compose.material) implementation(Compose.uiTooling) + + /** Other libraries */ + implementation(Library.accompanistSystemUiController) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt new file mode 100644 index 0000000000..73cca24307 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt @@ -0,0 +1,169 @@ +package com.tangem.core.ui.components.appbar + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.components.appbar.models.AdditionalButton +import com.tangem.core.ui.res.IconColorType +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TextColorType +import com.tangem.core.ui.res.iconColor +import com.tangem.core.ui.res.textColor + +/** + * App bar with title and two additional buttons + * + * @param startButton button information attached to the left edge + * @param endButton button information attached to the right edge + * + * @see + * Figma Component + */ +@Composable +fun AppBarWithAdditionalButtons( + text: String, + startButton: AdditionalButton? = null, + endButton: AdditionalButton? = null, +) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(dimensionResource(id = R.dimen.size56)) + .padding(all = dimensionResource(id = R.dimen.spacing16)), + ) { + if (startButton != null) { + IconButton(modifier = Modifier.align(Alignment.CenterStart), onClick = startButton.onIconClicked) { + Icon( + painter = painterResource(id = startButton.iconRes), + contentDescription = null, + modifier = Modifier.size(dimensionResource(id = R.dimen.size24)), + tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1), + ) + } + } + + Text( + text = text, + modifier = Modifier.align(Alignment.Center), + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + maxLines = 1, + style = MaterialTheme.typography.subtitle1, + ) + + if (endButton != null) { + IconButton(modifier = Modifier.align(Alignment.CenterEnd), onClick = endButton.onIconClicked) { + Icon( + painter = painterResource(id = endButton.iconRes), + contentDescription = null, + modifier = Modifier.size(dimensionResource(id = R.dimen.size24)), + tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1), + ) + } + } + } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun Preview_AppBarWithAdditionalButtons_InLightTheme() { + TangemTheme(isDarkTheme = false) { + AppBarWithAdditionalButtons( + text = "Tangem", + startButton = AdditionalButton( + iconRes = R.drawable.ic_scan, + onIconClicked = {}, + ), + endButton = AdditionalButton( + iconRes = R.drawable.ic_more_vertical, + onIconClicked = {}, + ), + ) + } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun Preview_AppBarWithAdditionalButtons_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + AppBarWithAdditionalButtons( + text = "Tangem", + startButton = AdditionalButton( + iconRes = R.drawable.ic_scan, + onIconClicked = {}, + ), + endButton = AdditionalButton( + iconRes = R.drawable.ic_more_vertical, + onIconClicked = {}, + ), + ) + } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun Preview_AppBarWithOnlyStartButtons_InLightTheme() { + TangemTheme(isDarkTheme = false) { + AppBarWithAdditionalButtons( + text = "Tangem", + startButton = AdditionalButton( + iconRes = R.drawable.ic_scan, + onIconClicked = {}, + ), + ) + } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun Preview_AppBarWithOnlyStartButtons_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + AppBarWithAdditionalButtons( + text = "Tangem", + startButton = AdditionalButton( + iconRes = R.drawable.ic_scan, + onIconClicked = {}, + ), + ) + } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun Preview_AppBarWithOnlyEndButtons_InLightTheme() { + TangemTheme(isDarkTheme = false) { + AppBarWithAdditionalButtons( + text = "Tangem", + endButton = AdditionalButton( + iconRes = R.drawable.ic_more_vertical, + onIconClicked = {}, + ), + ) + } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun Preview_AppBarWithOnlyEndButtons_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + AppBarWithAdditionalButtons( + text = "Tangem", + endButton = AdditionalButton( + iconRes = R.drawable.ic_more_vertical, + onIconClicked = {}, + ), + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt similarity index 94% rename from core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt rename to core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index 6b5e3dc55d..99d6aa527a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/AppBar.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -1,6 +1,5 @@ -package com.tangem.core.ui.components +package com.tangem.core.ui.components.appbar -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -48,7 +47,6 @@ fun PreviewAppBarWithBackButtonInDarkTheme() { fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { Row( modifier = Modifier - .background(MaterialTheme.colors.primaryVariant) .padding(all = dimensionResource(R.dimen.spacing16)) .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing16)), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/AdditionalButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/AdditionalButton.kt new file mode 100644 index 0000000000..2517f5ef41 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/AdditionalButton.kt @@ -0,0 +1,8 @@ +package com.tangem.core.ui.components.appbar.models + +import androidx.annotation.DrawableRes + +data class AdditionalButton( + @DrawableRes val iconRes: Int, + val onIconClicked: () -> Unit, +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt new file mode 100644 index 0000000000..edf127e645 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt @@ -0,0 +1,60 @@ +package com.tangem.core.ui.components.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.res.IconColorType +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.iconColor + +/** + * Bottom sheet indicator + * + * @see + * Figma Component + */ +@Composable +fun BottomSheetIndicator() { + Box( + modifier = Modifier + .fillMaxWidth() + .height(dimensionResource(id = R.dimen.size20)), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = Modifier + .width(dimensionResource(id = R.dimen.size32)) + .height(dimensionResource(id = R.dimen.size4)) + .background( + color = MaterialTheme.colors.iconColor(type = IconColorType.INACTIVE), + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius2)), + ), + ) + } +} + +@Preview(heightDp = 20, showBackground = true) +@Composable +fun Preview_BottomSheetIndicator_InLightTheme() { + TangemTheme(isDarkTheme = false) { + BottomSheetIndicator() + } +} + +@Preview(heightDp = 20, showBackground = true) +@Composable +fun Preview_BottomSheetIndicator_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + BottomSheetIndicator() + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index 2e065ba6ee..ebd0da2f57 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -4,6 +4,8 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import com.google.accompanist.systemuicontroller.rememberSystemUiController internal val isSystemInDarkTheme: Boolean = false // TODO: use isSystemInDarkTheme() for automatic color detection @@ -12,6 +14,11 @@ fun TangemTheme( isDarkTheme: Boolean = isSystemInDarkTheme, content: @Composable () -> Unit, ) { + val systemUiController = rememberSystemUiController() + SideEffect { + systemUiController.setSystemBarsColor(color = if (isDarkTheme) Black else Light1, darkIcons = !isDarkTheme) + } + MaterialTheme( colors = if (isDarkTheme) DarkColors else LightColors, typography = TangemTypography, diff --git a/core/ui/src/main/res/drawable/ic_more_vertical.xml b/core/ui/src/main/res/drawable/ic_more_vertical.xml new file mode 100644 index 0000000000..9f5887ceb4 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_more_vertical.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_scan.xml b/core/ui/src/main/res/drawable/ic_scan.xml similarity index 94% rename from app/src/main/res/drawable/ic_scan.xml rename to core/ui/src/main/res/drawable/ic_scan.xml index 1e4cd949d6..31f4345be7 100644 --- a/app/src/main/res/drawable/ic_scan.xml +++ b/core/ui/src/main/res/drawable/ic_scan.xml @@ -2,7 +2,7 @@ android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> - + diff --git a/core/ui/src/main/res/values/dimens.xml b/core/ui/src/main/res/values/dimens.xml index 3863794b73..98fbe4b379 100644 --- a/core/ui/src/main/res/values/dimens.xml +++ b/core/ui/src/main/res/values/dimens.xml @@ -19,10 +19,14 @@ + 0dp + 4dp 11dp 16dp + 20dp 24dp 28dp + 32dp 40dp 48dp 56dp @@ -45,5 +49,6 @@ 44dp 50dp 54dp + 2dp \ No newline at end of file diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index 05a5ad278c..73a9372e4b 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -34,6 +34,9 @@ dependencies { /** Core modules */ implementation(project(":core:ui")) + /** AndroidX */ + implementation(AndroidX.appCompat) + /** Compose */ implementation(Compose.foundation) implementation(Compose.material) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt index 4c592299c1..0f9a5666b3 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt @@ -4,6 +4,7 @@ data class ReferralStateHolder( val headerState: HeaderState, val referralInfoState: ReferralInfoState, val effects: Effects, + val agreementBottomSheetState: AgreementBottomSheetState ) { data class HeaderState(val onBackClicked: () -> Unit) @@ -16,7 +17,6 @@ data class ReferralStateHolder( val award: String, val address: String, val discount: String, - val onAgreementClicked: () -> Unit, val onParticipateClicked: () -> Unit, ) : ReferralInfoState, ReferralInfoContentState @@ -27,11 +27,12 @@ data class ReferralStateHolder( val code: String, val onCopyClicked: () -> Unit, val onShareClicked: () -> Unit, - val onAgreementClicked: () -> Unit, ) : ReferralInfoState, ReferralInfoContentState object Loading : ReferralInfoState } data class Effects(val showErrorToast: Boolean) + + data class AgreementBottomSheetState(val url: String) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt new file mode 100644 index 0000000000..a1f8adc86c --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt @@ -0,0 +1,80 @@ +package com.tangem.feature.referral.ui + +import android.view.ViewGroup.LayoutParams +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.tangem.core.ui.components.appbar.AppBarWithAdditionalButtons +import com.tangem.core.ui.components.atoms.BottomSheetIndicator +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.referral.presentation.R + +/** + * Bottom sheet content with referral program terms of use + * + * @param url link to the html page + */ +@Composable +fun AgreementBottomSheetContent(url: String) { + Column( + modifier = Modifier + .background(color = MaterialTheme.colors.secondary) + .fillMaxWidth() + .height(LocalConfiguration.current.screenHeightDp.dp - dimensionResource(id = R.dimen.spacing16)), + ) { + BottomSheetIndicator() + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + AppBarWithAdditionalButtons(text = stringResource(id = R.string.details_referral_title)) + AgreementHtmlView(url = url) + } + } +} + +@Composable +private fun AgreementHtmlView(url: String) { + AndroidView( + modifier = Modifier.background(MaterialTheme.colors.secondary), + factory = { + WebView(it).apply { + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + webViewClient = WebViewClient() + settings.apply { + javaScriptEnabled = false + allowFileAccess = false + } + loadUrl(url) + } + }, + update = { it.loadUrl(url) }, + ) +} + +@Preview +@Composable +fun Preview_AgreementBottomSheet_InLightTheme() { + TangemTheme(isDarkTheme = false) { + AgreementBottomSheetContent(url = "https://tangem.com/en/") + } +} + +@Preview +@Composable +fun Preview_AgreementBottomSheet_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + AgreementBottomSheetContent(url = "https://tangem.com/en/") + } +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 46de8b45c4..a9935a04cc 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -17,10 +17,16 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.BottomSheetScaffold +import androidx.compose.material.BottomSheetState +import androidx.compose.material.BottomSheetValue +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -32,8 +38,8 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.AppBarWithBackButton import com.tangem.core.ui.components.VerticalSpacer +import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.ButtonColorType import com.tangem.core.ui.res.IconColorType import com.tangem.core.ui.res.TangemTheme @@ -46,126 +52,51 @@ import com.tangem.feature.referral.models.ReferralStateHolder import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState import com.tangem.feature.referral.presentation.R import com.valentinilk.shimmer.shimmer - -@Preview(widthDp = 360, showBackground = true) -@Composable -fun Preview_ReferralScreen_Participant_InLightTheme() { - TangemTheme(isDarkTheme = false) { - ReferralScreen( - stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), - referralInfoState = ReferralInfoState.ParticipantContent( - award = "10 USDT", - address = "ma80...zk8q2", - discount = "10%", - onAgreementClicked = {}, - onParticipateClicked = {}, - ), - effects = ReferralStateHolder.Effects(showErrorToast = false), - ), - ) - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -fun Preview_ReferralScreen_Participant_InDarkTheme() { - TangemTheme(isDarkTheme = true) { - ReferralScreen( - stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), - referralInfoState = ReferralInfoState.ParticipantContent( - award = "10 USDT", - address = "ma80...zk8q2", - discount = "10%", - onAgreementClicked = {}, - onParticipateClicked = {}, - ), - effects = ReferralStateHolder.Effects(showErrorToast = false), - ), - ) - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -fun Preview_ReferralScreen_NonParticipant_InLightTheme() { - TangemTheme(isDarkTheme = false) { - ReferralScreen( - stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), - referralInfoState = ReferralInfoState.NonParticipantContent( - award = "10 USDT", - discount = "10%", - purchasedWalletCount = 3, - code = "x4JdK", - onCopyClicked = {}, - onShareClicked = {}, - onAgreementClicked = {}, - ), - effects = ReferralStateHolder.Effects(showErrorToast = false), - ), - ) - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { - TangemTheme(isDarkTheme = true) { - ReferralScreen( - stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), - referralInfoState = ReferralInfoState.NonParticipantContent( - award = "10 USDT", - discount = "10%", - purchasedWalletCount = 3, - code = "x4JdK", - onCopyClicked = {}, - onShareClicked = {}, - onAgreementClicked = {}, - ), - effects = ReferralStateHolder.Effects(showErrorToast = false), - ), - ) - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -fun Preview_ReferralScreen_Loading_InLightTheme() { - TangemTheme(isDarkTheme = false) { - ReferralScreen( - stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), - referralInfoState = ReferralInfoState.Loading, - effects = ReferralStateHolder.Effects(showErrorToast = false), - ), - ) - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -fun Preview_ReferralScreen_Loading_InDarkTheme() { - TangemTheme(isDarkTheme = true) { - ReferralScreen( - stateHolder = ReferralStateHolder( - headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), - referralInfoState = ReferralInfoState.Loading, - effects = ReferralStateHolder.Effects(showErrorToast = false), - ), - ) - } -} +import kotlinx.coroutines.launch /** * Referral program screen for participant and non-participant * * @param stateHolder state holder */ +@OptIn(ExperimentalMaterialApi::class) @Composable fun ReferralScreen(stateHolder: ReferralStateHolder) { + val coroutineScope = rememberCoroutineScope() + val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( + bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed), + ) + + TangemTheme { + BottomSheetScaffold( + sheetContent = { AgreementBottomSheetContent(stateHolder.agreementBottomSheetState.url) }, + scaffoldState = bottomSheetScaffoldState, + sheetShape = RoundedCornerShape( + topStart = dimensionResource(id = R.dimen.radius16), + topEnd = dimensionResource(id = R.dimen.radius16), + ), + sheetElevation = dimensionResource(id = R.dimen.elevation24), + sheetPeekHeight = dimensionResource(id = R.dimen.size0), + content = { + ReferralContent( + stateHolder = stateHolder, + onAgreementClicked = { + coroutineScope.launch { + if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) { + bottomSheetScaffoldState.bottomSheetState.expand() + } else { + bottomSheetScaffoldState.bottomSheetState.collapse() + } + } + }, + ) + }, + ) + } +} + +@Composable +fun ReferralContent(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) { Column( modifier = Modifier .background(color = MaterialTheme.colors.primary) @@ -175,7 +106,7 @@ fun ReferralScreen(stateHolder: ReferralStateHolder) { ) { Header(stateHolder = stateHolder) VerticalSpacer(spaceResId = R.dimen.spacing16) - ReferralInfo(stateHolder = stateHolder) + ReferralInfo(stateHolder = stateHolder, onAgreementClicked = onAgreementClicked) } } @@ -202,17 +133,17 @@ private fun ColumnScope.Header(stateHolder: ReferralStateHolder) { } @Composable -fun ReferralInfo(stateHolder: ReferralStateHolder) { +fun ReferralInfo(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) { when (val state = stateHolder.referralInfoState) { is ReferralInfoState.ParticipantContent -> { Conditions(state = state) VerticalSpacer(spaceResId = R.dimen.spacing44) - ParticipantBottomBlock(state = state) + ParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked) } is ReferralInfoState.NonParticipantContent -> { Conditions(state = state) VerticalSpacer(spaceResId = R.dimen.spacing24) - NonParticipantBottomBlock(state = state) + NonParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked) } is ReferralInfoState.Loading -> { LoadingCondition(iconResId = R.drawable.ic_tether) @@ -373,21 +304,150 @@ private fun ShimmerInfo() { } @Composable -private fun ParticipantBottomBlock(state: ReferralInfoState.ParticipantContent) { +private fun ParticipantBottomBlock(state: ReferralInfoState.ParticipantContent, onAgreementClicked: () -> Unit) { ParticipateBottomBlock( - onAgreementClicked = state.onAgreementClicked, + onAgreementClicked = onAgreementClicked, onParticipateClicked = state.onParticipateClicked, ) } @Composable -private fun NonParticipantBottomBlock(state: ReferralInfoState.NonParticipantContent) { +private fun NonParticipantBottomBlock(state: ReferralInfoState.NonParticipantContent, onAgreementClicked: () -> Unit) { NonParticipateBottomBlock( purchasedWalletCount = state.purchasedWalletCount, code = state.code, onCopyClicked = state.onCopyClicked, onShareClicked = state.onShareClicked, - onAgreementClicked = state.onAgreementClicked, + onAgreementClicked = onAgreementClicked, ) } +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_Participant_InLightTheme() { + TangemTheme(isDarkTheme = false) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.ParticipantContent( + award = "10 USDT", + address = "ma80...zk8q2", + discount = "10%", + onParticipateClicked = {}, + ), + effects = ReferralStateHolder.Effects(showErrorToast = false), + agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( + url = "", + ), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_Participant_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.ParticipantContent( + award = "10 USDT", + address = "ma80...zk8q2", + discount = "10%", + + onParticipateClicked = {}, + ), + effects = ReferralStateHolder.Effects(showErrorToast = false), + agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( + url = "", + ), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_NonParticipant_InLightTheme() { + TangemTheme(isDarkTheme = false) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.NonParticipantContent( + award = "10 USDT", + discount = "10%", + purchasedWalletCount = 3, + code = "x4JdK", + onCopyClicked = {}, + onShareClicked = {}, + + ), + effects = ReferralStateHolder.Effects(showErrorToast = false), + agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( + url = "", + ), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.NonParticipantContent( + award = "10 USDT", + discount = "10%", + purchasedWalletCount = 3, + code = "x4JdK", + onCopyClicked = {}, + onShareClicked = {}, + + ), + effects = ReferralStateHolder.Effects(showErrorToast = false), + agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( + url = "", + ), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_Loading_InLightTheme() { + TangemTheme(isDarkTheme = false) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.Loading, + effects = ReferralStateHolder.Effects(showErrorToast = false), + agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( + url = "", + ), + ), + ) + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_ReferralScreen_Loading_InDarkTheme() { + TangemTheme(isDarkTheme = true) { + ReferralScreen( + stateHolder = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), + referralInfoState = ReferralInfoState.Loading, + effects = ReferralStateHolder.Effects(showErrorToast = false), + agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( + url = "", + ), + ), + ) + } +} + diff --git a/features/referral/presentation/src/main/res/values-ru/string.xml b/features/referral/presentation/src/main/res/values-ru/string.xml index 27e42e7c03..4a6e997f8e 100644 --- a/features/referral/presentation/src/main/res/values-ru/string.xml +++ b/features/referral/presentation/src/main/res/values-ru/string.xml @@ -4,10 +4,10 @@ Приведи друга в Tangem Вы Получите - за каждый кошелек, который купит ваш друг на ваш адрес в сети Tron %@ + за каждый кошелек, который купит ваш друг на ваш адрес в сети Tron %s Ваш друг Получит - %@ скидку + %s скидку при покупке карточки на сайте tangem.com Нажимая на эту кнопку вы принимаете условия участия From 11334b807c042cf3b2c9d11e0856252ca0064b13 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 20 Nov 2022 13:09:31 +0300 Subject: [PATCH 059/176] Updated on 2026-08-14 --- .../datasource/api/referral/ReferralApi.kt | 3 ++- .../api/referral/models/ReferralResponse.kt | 17 +++++++++++++---- .../referral/converters/ReferralConverter.kt | 12 ++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt index 42445f931d..e7eaf31343 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt @@ -10,7 +10,8 @@ import retrofit2.http.Query /** * Api for referral feature */ -interface ReferralApi { +interface +ReferralApi { /** Returns referral status by [walletId] */ @GET("referral/{walletId}") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt index 061aa55155..a21aee2286 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt @@ -14,17 +14,26 @@ data class ReferralResponse( data class Conditions( @Json(name = "award") val award: Int, - @Json(name = "discount") val discount: Int, - @Json(name = "discountType") val discountType: DiscountType, + @Json(name = "discount") val discount: Discount, + @Json(name = "awards") val awards: List, @Json(name = "tosLink") val tosLink: String, - @Json(name = "tokens") val tokens: List, +) + +data class Discount( + @Json(name = "amount") val amount: Int, + @Json(name = "discountType") val discountType: DiscountType, +) + +data class Award( + @Json(name = "amount") val amount: Int, + @Json(name = "token") val token: Token, ) data class Token( @Json(name = "id") val id: String, @Json(name = "name") val name: String, @Json(name = "symbol") val symbol: String, - @Json(name = "networkId") val networkId: String?, + @Json(name = "networkId") val networkId: String, @Json(name = "contractAddress") val contractAddress: String?, @Json(name = "decimalCount") val decimalCount: Int?, ) diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt index bc55726c4b..bbd136e5f2 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt @@ -18,10 +18,10 @@ class ReferralConverter @Inject constructor() : Converter Date: Thu, 17 Nov 2022 21:46:18 +0300 Subject: [PATCH 060/176] Updated on 2026-08-14 --- .../features/tokens/redux/TokensMiddleware.kt | 2 +- .../com/tangem/tap/proxy/CardStateHolder.kt | 13 ++++ .../tangem/tap/proxy/DerivationManagerImpl.kt | 35 +++++++-- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 73 ++++++++++++++++--- .../referral/domain/ReferralInteractorImpl.kt | 6 +- .../domain/converter/TokensConverter.kt | 2 +- .../tangem/lib/crypto/DerivationManager.kt | 6 +- .../tangem/lib/crypto/UserWalletManager.kt | 8 +- .../com/tangem/lib/crypto/models/Token.kt | 4 +- 9 files changed, 119 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/proxy/CardStateHolder.kt diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 6ccf1b9b58..7b2322f79d 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -184,7 +184,7 @@ class TokensMiddleware { } } - private fun deriveMissingBlockchains( + fun deriveMissingBlockchains( scanResponse: ScanResponse, currencyList: List, onSuccess: (ScanResponse) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/proxy/CardStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/CardStateHolder.kt new file mode 100644 index 0000000000..2729f1fc99 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/proxy/CardStateHolder.kt @@ -0,0 +1,13 @@ +package com.tangem.tap.proxy + +import com.tangem.common.card.Card +import com.tangem.domain.common.ScanResponse + +class CardStateHolder { + + val scanResponse: ScanResponse? = null + + fun getActualCard(): Card? { + return scanResponse?.card + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index a323e29196..e3aca88845 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -1,14 +1,37 @@ package com.tangem.tap.proxy -import com.tangem.crypto.DerivationManager +import com.tangem.blockchain.common.Blockchain +import com.tangem.domain.common.TapWorkarounds.derivationStyle +import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.lib.crypto.DerivationManager +import com.tangem.tap.features.tokens.redux.TokensMiddleware -class DerivationManagerImpl : DerivationManager { +class DerivationManagerImpl( + val tokesMiddleware: TokensMiddleware, + val cardStateHolder: CardStateHolder, +) : DerivationManager { - override fun deriveMissingBlockchains(networkId: String?) { - TODO("Not yet implemented") + override suspend fun deriveMissingBlockchains(networkId: String) { + if (cardStateHolder.scanResponse != null) { //todo how to get Currency list from token + tokesMiddleware.deriveMissingBlockchains( + cardStateHolder.scanResponse, emptyList(), + ) { + + } + } } - override fun hasDerivation(): Boolean { - TODO("Not yet implemented") + override fun hasDerivation(networkId: String): Boolean { + val scanResponse = cardStateHolder.scanResponse + val blockchain = Blockchain.fromNetworkId(networkId) + if (scanResponse != null && blockchain != null) { + val derivationPath = blockchain.derivationPath(cardStateHolder.getActualCard()?.derivationStyle)?.rawPath + if (derivationPath.isNullOrEmpty()) return false + return scanResponse.hasDerivation( + blockchain, + derivationPath, + ) + } + return false } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 45843af752..d8e04f93c9 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -1,27 +1,80 @@ package com.tangem.tap.proxy -import com.tangem.crypto.UserWalletManager -import com.tangem.crypto.models.Token +import com.tangem.blockchain.common.Blockchain +import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.lib.crypto.UserWalletManager +import com.tangem.lib.crypto.models.Token +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.domain.extensions.getUserWalletId +import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.features.wallet.redux.WalletAction +import org.rekotlin.Store -class UserWalletManagerImpl : UserWalletManager { +class UserWalletManagerImpl( + private val userTokensRepository: UserTokensRepository, + private val cardStateHolder: CardStateHolder, + private val mainStore: Store, +) : UserWalletManager { - override fun getUserTokens(): List { - TODO("Not yet implemented") + override suspend fun getUserTokens(): List { + val card = cardStateHolder.getActualCard() + if (card != null) { + userTokensRepository.getUserTokens(card) + .filter { it.isToken() } + .map { + Token( + id = it.coinId ?: "", + name = it.currencyName, + symbol = it.currencySymbol, + networkId = it.blockchain.toNetworkId(), + contractAddress = null, + decimalCount = it.decimals, + ) + } + } + return emptyList() } override fun getWalletId(): String { - TODO("Not yet implemented") + return cardStateHolder.getActualCard()?.getUserWalletId() ?: "" } - override fun isTokenAdded(token: Token): Boolean { - TODO("Not yet implemented") + override suspend fun isTokenAdded(token: Token): Boolean { + val card = cardStateHolder.getActualCard() + if (card != null) { + return userTokensRepository.getUserTokens(card) + .any { it.coinId.equals(token.id) } //todo ensure that its the same ids + } + return false } + //todo check is it sync operation override fun addToken(token: Token) { - TODO("Not yet implemented") + val card = cardStateHolder.getActualCard() + val blockchain = Blockchain.fromNetworkId(token.networkId) + if (card != null && blockchain != null) { + val addAction = WalletAction.MultiWallet.AddToken( + token = com.tangem.blockchain.common.Token( + id = token.id, + name = token.name, + symbol = token.symbol, + contractAddress = token.contractAddress + ?: "", //todo clarify where to find that if data from back null + decimals = token.decimalCount ?: 0, //todo clarify where to find that if data from back null + ), + blockchain = BlockchainNetwork( + blockchain, + card, + ), + save = true, + ) + mainStore.dispatch(addAction) + } } override fun getWalletAddress(token: Token): String { - TODO("Not yet implemented") + return "" } } \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index 3e2dd73ba7..69695dd1b8 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -1,8 +1,8 @@ package com.tangem.feature.referral.domain -import com.tangem.crypto.DerivationManager -import com.tangem.crypto.UserWalletManager -import com.tangem.crypto.models.Token +import com.tangem.lib.crypto.DerivationManager +import com.tangem.lib.crypto.UserWalletManager +import com.tangem.lib.crypto.models.Token import com.tangem.feature.referral.domain.converter.TokensConverter import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.TokenData diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt index 0a8c565e68..5a98e0f2a0 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt @@ -1,6 +1,6 @@ package com.tangem.feature.referral.domain.converter -import com.tangem.crypto.models.Token +import com.tangem.lib.crypto.models.Token import com.tangem.feature.referral.domain.models.TokenData import com.tangem.utils.converter.Converter diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt index 8e77e85480..ed5f1ffe3f 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt @@ -1,8 +1,8 @@ -package com.tangem.crypto +package com.tangem.lib.crypto interface DerivationManager { - fun deriveMissingBlockchains(networkId: String?) + suspend fun deriveMissingBlockchains(networkId: String) - fun hasDerivation(): Boolean + fun hasDerivation(networkId: String): Boolean } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt index 8bb117cd15..0ea6a506d3 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -1,6 +1,6 @@ -package com.tangem.crypto +package com.tangem.lib.crypto -import com.tangem.crypto.models.Token +import com.tangem.lib.crypto.models.Token /** * Provider for user tokens data @@ -10,7 +10,7 @@ interface UserWalletManager { /** * Returns all user tokens (merged from local and backend) */ - fun getUserTokens(): List + suspend fun getUserTokens(): List /** * Returns user walletId @@ -22,7 +22,7 @@ interface UserWalletManager { * * @param token to receive referral payments */ - fun isTokenAdded(token: Token): Boolean + suspend fun isTokenAdded(token: Token): Boolean /** * Adds token to wallet if its not diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt index 86c314a573..eb9593843e 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt @@ -1,10 +1,10 @@ -package com.tangem.crypto.models +package com.tangem.lib.crypto.models data class Token( val id: String, val name: String, val symbol: String, - val networkId: String?, + val networkId: String, val contractAddress: String?, val decimalCount: Int?, ) \ No newline at end of file From ebcf2f40e5cc332ee95d4c1afa5e33e6ac50d879 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 18 Nov 2022 18:06:25 +0300 Subject: [PATCH 061/176] Updated on 2026-08-14 --- .../{CardStateHolder.kt => AppStateHolder.kt} | 4 +- .../tangem/tap/proxy/DerivationManagerImpl.kt | 44 +++++-- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 123 ++++++++++++++---- core/datasource/build.gradle.kts | 1 - .../referral/domain/ReferralInteractorImpl.kt | 26 ++-- .../domain/converter/TokensConverter.kt | 35 +++-- .../referral/domain/models/ReferralData.kt | 2 +- .../tangem/lib/crypto/DerivationManager.kt | 12 +- .../tangem/lib/crypto/UserWalletManager.kt | 16 +-- .../com/tangem/lib/crypto/models/Currency.kt | 12 ++ .../tangem/lib/crypto/models/NativeToken.kt | 8 ++ .../lib/crypto/models/NonNativeToken.kt | 10 ++ .../com/tangem/lib/crypto/models/Token.kt | 10 -- 13 files changed, 222 insertions(+), 81 deletions(-) rename app/src/main/java/com/tangem/tap/proxy/{CardStateHolder.kt => AppStateHolder.kt} (65%) create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/NativeToken.kt create mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/NonNativeToken.kt delete mode 100644 libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt diff --git a/app/src/main/java/com/tangem/tap/proxy/CardStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt similarity index 65% rename from app/src/main/java/com/tangem/tap/proxy/CardStateHolder.kt rename to app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 2729f1fc99..130dd64665 100644 --- a/app/src/main/java/com/tangem/tap/proxy/CardStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -2,10 +2,12 @@ package com.tangem.tap.proxy import com.tangem.common.card.Card import com.tangem.domain.common.ScanResponse +import com.tangem.tap.features.wallet.redux.WalletState -class CardStateHolder { +class AppStateHolder { val scanResponse: ScanResponse? = null + val walletState: WalletState? = null fun getActualCard(): Card? { return scanResponse?.card diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index e3aca88845..dd6d2a6e1c 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -1,31 +1,55 @@ package com.tangem.tap.proxy import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.lib.crypto.DerivationManager +import com.tangem.lib.crypto.models.Currency +import com.tangem.lib.crypto.models.NonNativeToken +import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.tokens.redux.TokensMiddleware +import kotlin.coroutines.suspendCoroutine class DerivationManagerImpl( - val tokesMiddleware: TokensMiddleware, - val cardStateHolder: CardStateHolder, + private val tokesMiddleware: TokensMiddleware, + private val appStateHolder: AppStateHolder, ) : DerivationManager { - override suspend fun deriveMissingBlockchains(networkId: String) { - if (cardStateHolder.scanResponse != null) { //todo how to get Currency list from token - tokesMiddleware.deriveMissingBlockchains( - cardStateHolder.scanResponse, emptyList(), - ) { - + override suspend fun deriveMissingBlockchains(currency: Currency) = suspendCoroutine { continuation -> + val blockchain = Blockchain.fromNetworkId(currency.networkId) + val card = appStateHolder.getActualCard() + if (blockchain != null && card != null) { + val appToken = if (currency is NonNativeToken) { + Token( + symbol = currency.symbol, + contractAddress = currency.contractAddress, + decimals = currency.decimalCount, + ) + } else null + val blockchainNetwork = BlockchainNetwork(blockchain, card) + val appCurrency = com.tangem.tap.features.wallet.models.Currency.fromBlockchainNetwork( + blockchainNetwork, + appToken, + ) + if (appStateHolder.scanResponse != null) { + tokesMiddleware.deriveMissingBlockchains( + scanResponse = appStateHolder.scanResponse, + listOf(appCurrency), + ) { + continuation.resumeWith(Result.success(true)) + } } + } else { + continuation.resumeWith(Result.failure(IllegalStateException("no blockchain or card found"))) } } override fun hasDerivation(networkId: String): Boolean { - val scanResponse = cardStateHolder.scanResponse + val scanResponse = appStateHolder.scanResponse val blockchain = Blockchain.fromNetworkId(networkId) if (scanResponse != null && blockchain != null) { - val derivationPath = blockchain.derivationPath(cardStateHolder.getActualCard()?.derivationStyle)?.rawPath + val derivationPath = blockchain.derivationPath(appStateHolder.getActualCard()?.derivationStyle)?.rawPath if (derivationPath.isNullOrEmpty()) return false return scanResponse.hasDerivation( blockchain, diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index d8e04f93c9..cac76b8ab3 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -1,68 +1,140 @@ package com.tangem.tap.proxy import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.DerivationParams +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.common.card.Card +import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toNetworkId import com.tangem.lib.crypto.UserWalletManager -import com.tangem.lib.crypto.models.Token +import com.tangem.lib.crypto.models.Currency +import com.tangem.lib.crypto.models.NativeToken +import com.tangem.lib.crypto.models.NonNativeToken import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.extensions.getUserWalletId +import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction +import org.rekotlin.Action import org.rekotlin.Store class UserWalletManagerImpl( private val userTokensRepository: UserTokensRepository, - private val cardStateHolder: CardStateHolder, + private val appStateHolder: AppStateHolder, private val mainStore: Store, + private val walletManagerFactory: WalletManagerFactory, ) : UserWalletManager { - override suspend fun getUserTokens(): List { - val card = cardStateHolder.getActualCard() + override suspend fun getUserTokens(): List { + val card = appStateHolder.getActualCard() if (card != null) { userTokensRepository.getUserTokens(card) .filter { it.isToken() } .map { - Token( - id = it.coinId ?: "", - name = it.currencyName, - symbol = it.currencySymbol, - networkId = it.blockchain.toNetworkId(), - contractAddress = null, - decimalCount = it.decimals, - ) + if (it is com.tangem.tap.features.wallet.models.Currency.Token) { + NonNativeToken( + id = it.coinId ?: "", + name = it.currencyName, + symbol = it.currencySymbol, + networkId = it.blockchain.toNetworkId(), + contractAddress = it.token.contractAddress, + decimalCount = it.token.decimals, + ) + } else { + NativeToken( + id = it.coinId ?: "", + name = it.currencyName, + symbol = it.currencySymbol, + networkId = it.blockchain.toNetworkId(), + ) + } } } return emptyList() } override fun getWalletId(): String { - return cardStateHolder.getActualCard()?.getUserWalletId() ?: "" + return appStateHolder.getActualCard()?.getUserWalletId() ?: "" } - override suspend fun isTokenAdded(token: Token): Boolean { - val card = cardStateHolder.getActualCard() + override suspend fun isTokenAdded(currency: Currency): Boolean { + val card = appStateHolder.getActualCard() if (card != null) { return userTokensRepository.getUserTokens(card) - .any { it.coinId.equals(token.id) } //todo ensure that its the same ids + .any { it.coinId.equals(currency.id) } //todo ensure that its the same ids } return false } //todo check is it sync operation - override fun addToken(token: Token) { - val card = cardStateHolder.getActualCard() + override fun addToken(currency: Currency) { + val action = if (currency is NativeToken) { + val card = appStateHolder.getActualCard() + if (card != null) { + addNativeTokenToWalletAction(currency, card) + } else { + throw IllegalStateException("card not found") + } + } else { + addNonNativeTokenToWalletAction(currency as NonNativeToken) + } + + mainStore.dispatch(action) + } + + override fun getWalletAddress(currency: Currency): String { + val blockchain = Blockchain.fromNetworkId(currency.networkId) + val card = appStateHolder.getActualCard() + if (blockchain != null && card != null) { + val blockchainNetwork = BlockchainNetwork(blockchain, card) + val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) + if (walletManager != null) { + return walletManager.wallet.address + } else { + throw IllegalStateException("no wallet manager found") + } + } else { + throw IllegalStateException("no blockchain or card found") + } + } + + private fun addNativeTokenToWalletAction(token: NativeToken, card: Card): Action { + val blockchain = Blockchain.fromNetworkId(token.networkId) + val scanResponse = appStateHolder.scanResponse + if (blockchain != null && scanResponse != null) { + val blockchainNetwork = BlockchainNetwork(blockchain, card) + var walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork) + if (walletManager == null) { + walletManager = walletManagerFactory.makeWalletManagerForApp( + scanResponse = scanResponse, + blockchain = blockchain, + derivationParams = createDerivationParams(card.derivationStyle), + ) + } + return WalletAction.MultiWallet.AddBlockchain( + blockchain = blockchainNetwork, + walletManager = walletManager, + save = true, + ) + } else { + throw IllegalStateException("blockchain is not supported") + } + } + + private fun addNonNativeTokenToWalletAction(token: NonNativeToken): Action { + val card = appStateHolder.getActualCard() val blockchain = Blockchain.fromNetworkId(token.networkId) if (card != null && blockchain != null) { - val addAction = WalletAction.MultiWallet.AddToken( + return WalletAction.MultiWallet.AddToken( token = com.tangem.blockchain.common.Token( id = token.id, name = token.name, symbol = token.symbol, - contractAddress = token.contractAddress - ?: "", //todo clarify where to find that if data from back null - decimals = token.decimalCount ?: 0, //todo clarify where to find that if data from back null + contractAddress = token.contractAddress, + decimals = token.decimalCount, ), blockchain = BlockchainNetwork( blockchain, @@ -70,11 +142,12 @@ class UserWalletManagerImpl( ), save = true, ) - mainStore.dispatch(addAction) + } else { + throw IllegalStateException("no card or blockchain found") } } - override fun getWalletAddress(token: Token): String { - return "" + private fun createDerivationParams(derivationStyle: DerivationStyle?): DerivationParams? { + return derivationStyle?.let { DerivationParams.Default(derivationStyle) } //todo clarify if its need to add Custom } } \ No newline at end of file diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index c85c0841b9..7d8f651682 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -6,7 +6,6 @@ plugins { } android { - defaultConfig { compileSdk = AppConfig.compileSdkVersion minSdk = AppConfig.minSdkVersion diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index 69695dd1b8..8b2716f539 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -1,11 +1,11 @@ package com.tangem.feature.referral.domain -import com.tangem.lib.crypto.DerivationManager -import com.tangem.lib.crypto.UserWalletManager -import com.tangem.lib.crypto.models.Token import com.tangem.feature.referral.domain.converter.TokensConverter import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.TokenData +import com.tangem.lib.crypto.DerivationManager +import com.tangem.lib.crypto.UserWalletManager +import com.tangem.lib.crypto.models.Currency internal class ReferralInteractorImpl( private val repository: ReferralRepository, @@ -24,13 +24,13 @@ internal class ReferralInteractorImpl( override suspend fun startReferral(): ReferralData { if (tokensForReferral.isNotEmpty()) { - val token = tokensConverter.convert(tokensForReferral.first()) - deriveOrAddTokens(token) - val publicAddress = userWalletManager.getWalletAddress(token) + val currency = tokensConverter.convert(tokensForReferral.first()) + deriveOrAddTokens(currency) + val publicAddress = userWalletManager.getWalletAddress(currency) return repository.startReferral( walletId = userWalletManager.getWalletId(), - networkId = token.networkId ?: "", - tokenId = token.id, + networkId = currency.networkId, + tokenId = currency.id, address = publicAddress, ) } else { @@ -38,12 +38,12 @@ internal class ReferralInteractorImpl( } } - private fun deriveOrAddTokens(token: Token) { - if (!derivationManager.hasDerivation()) { - derivationManager.deriveMissingBlockchains(token.networkId) + private suspend fun deriveOrAddTokens(currency: Currency) { + if (!derivationManager.hasDerivation(currency.networkId)) { + derivationManager.deriveMissingBlockchains(currency) } - if (!userWalletManager.isTokenAdded(token)) { - userWalletManager.addToken(token) + if (!userWalletManager.isTokenAdded(currency)) { + userWalletManager.addToken(currency) } } diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt index 5a98e0f2a0..4ee8e69395 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt @@ -1,19 +1,32 @@ package com.tangem.feature.referral.domain.converter -import com.tangem.lib.crypto.models.Token import com.tangem.feature.referral.domain.models.TokenData +import com.tangem.lib.crypto.models.Currency +import com.tangem.lib.crypto.models.NativeToken +import com.tangem.lib.crypto.models.NonNativeToken import com.tangem.utils.converter.Converter -class TokensConverter : Converter { +class TokensConverter : Converter { - override fun convert(value: TokenData): Token { - return Token( - id = value.id, - name = value.name, - symbol = value.symbol, - networkId = value.networkId, - contractAddress = value.contractAddress, - decimalCount = value.decimalCount, - ) + override fun convert(value: TokenData): Currency { + return if (value.decimalCount != null && + value.contractAddress != null + ) { + NonNativeToken( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = value.networkId, + contractAddress = value.contractAddress, + decimalCount = value.decimalCount, + ) + } else { + NativeToken( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = value.networkId, + ) + } } } \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt index 79216aec7d..f5cc061cb0 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/models/ReferralData.kt @@ -34,7 +34,7 @@ data class TokenData( val id: String, val name: String, val symbol: String, - val networkId: String?, + val networkId: String, val contractAddress: String?, val decimalCount: Int?, ) diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt index ed5f1ffe3f..b9b2cfecea 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/DerivationManager.kt @@ -1,8 +1,18 @@ package com.tangem.lib.crypto +import com.tangem.lib.crypto.models.Currency + interface DerivationManager { - suspend fun deriveMissingBlockchains(networkId: String) + /** + * Derives missing blockchain and returns flag true if did it successfully + * + * @param currency to derive (Native token or not) + */ + suspend fun deriveMissingBlockchains(currency: Currency): Boolean + /** + * Checks that given [networkId] has derivations + */ fun hasDerivation(networkId: String): Boolean } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt index 0ea6a506d3..6df9887170 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt @@ -1,6 +1,6 @@ package com.tangem.lib.crypto -import com.tangem.lib.crypto.models.Token +import com.tangem.lib.crypto.models.Currency /** * Provider for user tokens data @@ -10,7 +10,7 @@ interface UserWalletManager { /** * Returns all user tokens (merged from local and backend) */ - suspend fun getUserTokens(): List + suspend fun getUserTokens(): List /** * Returns user walletId @@ -20,21 +20,21 @@ interface UserWalletManager { /** * Checks that token added to user wallet * - * @param token to receive referral payments + * @param currency to receive referral payments */ - suspend fun isTokenAdded(token: Token): Boolean + suspend fun isTokenAdded(currency: Currency): Boolean /** * Adds token to wallet if its not * - * @param token to add to wallet + * @param currency to add to wallet */ - fun addToken(token: Token) + fun addToken(currency: Currency) /** * Returns wallet public address for token * - * @param token for which find address + * @param currency for which find address */ - fun getWalletAddress(token: Token): String + fun getWalletAddress(currency: Currency): String } \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt new file mode 100644 index 0000000000..82afac8e25 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt @@ -0,0 +1,12 @@ +package com.tangem.lib.crypto.models + +/** + * Currency data class that can be native blockchain Token + * or custom Token (used in this lib to replace and divide logic Currency from app module) + */ +abstract class Currency( + open val id: String, + open val name: String, + open val symbol: String, + open val networkId: String, +) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NativeToken.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NativeToken.kt new file mode 100644 index 0000000000..f8ab7464d6 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NativeToken.kt @@ -0,0 +1,8 @@ +package com.tangem.lib.crypto.models + +data class NativeToken( + override val id: String, + override val name: String, + override val symbol: String, + override val networkId: String, +) : Currency(id, name, symbol, networkId) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NonNativeToken.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NonNativeToken.kt new file mode 100644 index 0000000000..a2d812cd1b --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/NonNativeToken.kt @@ -0,0 +1,10 @@ +package com.tangem.lib.crypto.models + +class NonNativeToken( + override val id: String, + override val name: String, + override val symbol: String, + override val networkId: String, + val contractAddress: String, + val decimalCount: Int, +) : Currency(id, name, symbol, networkId) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt deleted file mode 100644 index eb9593843e..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Token.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.lib.crypto.models - -data class Token( - val id: String, - val name: String, - val symbol: String, - val networkId: String, - val contractAddress: String?, - val decimalCount: Int?, -) \ No newline at end of file From dca991651ce0b544952e993f67bb8a9fa47deda1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 20 Nov 2022 15:07:49 +0300 Subject: [PATCH 062/176] Updated on 2026-08-14 --- app/build.gradle.kts | 12 +++++- .../com/tangem/tap/proxy/AppStateHolder.kt | 11 +++++ .../tangem/tap/proxy/DerivationManagerImpl.kt | 3 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 11 +++-- .../com/tangem/tap/proxy/di/ProxyModule.kt | 41 +++++++++++++++++++ .../di/CoroutineDispatcherProviderModule.kt | 16 ++++++++ domain/build.gradle.kts | 8 +++- .../referral/data/ReferralRepositoryImpl.kt | 3 +- .../referral/di/ReferralRepositoryModule.kt | 29 +++++++++++++ .../domain/converter/TokensConverter.kt | 3 +- .../domain/di/ReferralDomainModule.kt | 31 ++++++++++++++ .../referral/presentation/build.gradle.kts | 9 ++++ .../presentation/ReferralViewModel.kt | 11 +++++ 13 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt create mode 100644 core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt create mode 100644 features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt create mode 100644 features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/presentation/ReferralViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dc249a446b..e68e3543e7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,7 +111,11 @@ dependencies { implementation(project(":common")) implementation(project(":core:ui")) implementation(project(":libs:crypto")) + + /** Features */ implementation(project(":features:referral:presentation")) + implementation(project(":features:referral:domain")) + implementation(project(":features:referral:data")) /** AndroidX libraries */ implementation(AndroidX.coreKtx) @@ -139,9 +143,13 @@ dependencies { implementation(Firebase.firebaseCrashlytics) /** Tangem libraries */ - implementation(Tangem.blockchain) + implementation(Tangem.blockchain) { + exclude(module = "joda-time") + } implementation(Tangem.cardCore) - implementation(Tangem.cardAndroid) + implementation(Tangem.cardAndroid) { + exclude(module = "joda-time") + } /** DI */ implementation(Library.hilt) diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index 130dd64665..eb4bfba228 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -2,12 +2,23 @@ package com.tangem.tap.proxy import com.tangem.common.card.Card import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.features.tokens.redux.TokensMiddleware import com.tangem.tap.features.wallet.redux.WalletState +import org.rekotlin.Store +/** + * Holds objects from old modules, that missing in DI graph + * Object sets manually to use in new modules and [AppStateHolder] proxies its to DI + */ class AppStateHolder { val scanResponse: ScanResponse? = null val walletState: WalletState? = null + val userTokensRepository: UserTokensRepository? = null + val mainStore: Store? = null + val tokesMiddleware: TokensMiddleware? = null fun getActualCard(): Card? { return scanResponse?.card diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index dd6d2a6e1c..e27bfd5238 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -8,15 +8,14 @@ import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.NonNativeToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.tokens.redux.TokensMiddleware import kotlin.coroutines.suspendCoroutine class DerivationManagerImpl( - private val tokesMiddleware: TokensMiddleware, private val appStateHolder: AppStateHolder, ) : DerivationManager { override suspend fun deriveMissingBlockchains(currency: Currency) = suspendCoroutine { continuation -> + val tokesMiddleware = requireNotNull(appStateHolder.tokesMiddleware) { "tokesMiddleware is null" } val blockchain = Blockchain.fromNetworkId(currency.networkId) val card = appStateHolder.getActualCard() if (blockchain != null && card != null) { diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index cac76b8ab3..1a7f3fbcd5 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -12,24 +12,21 @@ import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.NativeToken import com.tangem.lib.crypto.models.NonNativeToken -import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.extensions.getUserWalletId import com.tangem.tap.domain.extensions.makeWalletManagerForApp -import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import org.rekotlin.Action -import org.rekotlin.Store class UserWalletManagerImpl( - private val userTokensRepository: UserTokensRepository, private val appStateHolder: AppStateHolder, - private val mainStore: Store, private val walletManagerFactory: WalletManagerFactory, ) : UserWalletManager { override suspend fun getUserTokens(): List { val card = appStateHolder.getActualCard() + val userTokensRepository = + requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" } if (card != null) { userTokensRepository.getUserTokens(card) .filter { it.isToken() } @@ -62,6 +59,8 @@ class UserWalletManagerImpl( override suspend fun isTokenAdded(currency: Currency): Boolean { val card = appStateHolder.getActualCard() + val userTokensRepository = + requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" } if (card != null) { return userTokensRepository.getUserTokens(card) .any { it.coinId.equals(currency.id) } //todo ensure that its the same ids @@ -81,7 +80,7 @@ class UserWalletManagerImpl( } else { addNonNativeTokenToWalletAction(currency as NonNativeToken) } - + val mainStore = requireNotNull(appStateHolder.mainStore) { "mainStore is null" } mainStore.dispatch(action) } diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt new file mode 100644 index 0000000000..07d3fb1e8d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -0,0 +1,41 @@ +package com.tangem.tap.proxy.di + +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.lib.crypto.DerivationManager +import com.tangem.lib.crypto.UserWalletManager +import com.tangem.tap.proxy.AppStateHolder +import com.tangem.tap.proxy.DerivationManagerImpl +import com.tangem.tap.proxy.UserWalletManagerImpl +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class ProxyModule { + + @Provides + @Singleton + fun provideAppStateHolder(): AppStateHolder { + return AppStateHolder() + } + + @Provides + @Singleton + fun provideUserWalletManager(appStateHolder: AppStateHolder): UserWalletManager { + return UserWalletManagerImpl( + appStateHolder = appStateHolder, + walletManagerFactory = WalletManagerFactory(), + ) + } + + @Provides + @Singleton + fun provideDerivationManager(appStateHolder: AppStateHolder): DerivationManager { + return DerivationManagerImpl( + appStateHolder = appStateHolder, + ) + } +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt b/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt new file mode 100644 index 0000000000..df85354292 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/di/CoroutineDispatcherProviderModule.kt @@ -0,0 +1,16 @@ +package com.tangem.utils.di + +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +interface CoroutineDispatcherProviderModule { + + @Binds + fun bindCoroutineDispatcherProvider(coroutineDispatcherProvider: AppCoroutineDispatcherProvider): CoroutineDispatcherProvider +} \ No newline at end of file diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 6e1c48864e..6909db2791 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -75,9 +75,13 @@ dependencies { implementation(project(":common")) /** Tangem libraries */ - implementation(Tangem.blockchain) + implementation(Tangem.blockchain) { + exclude(module = "joda-time") + } implementation(Tangem.cardCore) - implementation(Tangem.cardAndroid) + implementation(Tangem.cardAndroid) { + exclude(module = "joda-time") + } /** Other libraries */ implementation(Library.reKotlin) diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt index bf2387f070..789d5d3bc8 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/data/ReferralRepositoryImpl.kt @@ -7,9 +7,8 @@ import com.tangem.feature.referral.domain.ReferralRepository import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext -import javax.inject.Inject -internal class ReferralRepositoryImpl @Inject constructor( +internal class ReferralRepositoryImpl( private val referralApi: ReferralApi, private val referralConverter: ReferralConverter, private val coroutineDispatcher: CoroutineDispatcherProvider, diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt new file mode 100644 index 0000000000..7a88c423cc --- /dev/null +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/di/ReferralRepositoryModule.kt @@ -0,0 +1,29 @@ +package com.tangem.feature.referral.di + +import com.tangem.datasource.api.referral.ReferralApi +import com.tangem.feature.referral.converters.ReferralConverter +import com.tangem.feature.referral.data.ReferralRepositoryImpl +import com.tangem.feature.referral.domain.ReferralRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class ReferralRepositoryModule { + + @Provides + @Singleton + fun provideReferralRepository( + referralApi: ReferralApi, + referralConverter: ReferralConverter, + coroutineDispatcherProvider: CoroutineDispatcherProvider, + ): ReferralRepository { + return ReferralRepositoryImpl( + referralApi, referralConverter, coroutineDispatcherProvider, + ) + } +} \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt index 4ee8e69395..2f756567f1 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/converter/TokensConverter.kt @@ -5,8 +5,9 @@ import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.NativeToken import com.tangem.lib.crypto.models.NonNativeToken import com.tangem.utils.converter.Converter +import javax.inject.Inject -class TokensConverter : Converter { +class TokensConverter @Inject constructor() : Converter { override fun convert(value: TokenData): Currency { return if (value.decimalCount != null && diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt new file mode 100644 index 0000000000..0f0c22b0f0 --- /dev/null +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt @@ -0,0 +1,31 @@ +package com.tangem.feature.referral.domain.di + +import com.tangem.feature.referral.domain.ReferralInteractor +import com.tangem.feature.referral.domain.ReferralInteractorImpl +import com.tangem.feature.referral.domain.ReferralRepository +import com.tangem.feature.referral.domain.converter.TokensConverter +import com.tangem.lib.crypto.DerivationManager +import com.tangem.lib.crypto.UserWalletManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class ReferralDomainModule { + + @Provides + @Singleton + fun provideReferralInteractor( + referralRepository: ReferralRepository, + derivationManager: DerivationManager, + userWalletManager: UserWalletManager, + tokensConverter: TokensConverter, + ): ReferralInteractor { + return ReferralInteractorImpl( + referralRepository, derivationManager, userWalletManager, tokensConverter, + ) + } +} \ No newline at end of file diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index 73a9372e4b..b2763c706c 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -1,6 +1,8 @@ plugins { id("com.android.library") kotlin("android") + kotlin("kapt") + id("com.google.dagger.hilt.android") } android { @@ -42,6 +44,13 @@ dependencies { implementation(Compose.material) implementation(Compose.uiTooling) + /** Domain */ + implementation(project(":features:referral:domain")) + /** Other libraries */ implementation(Library.composeShimmer) + + /** DI */ + implementation(Library.hilt) + kapt(Library.hiltKapt) } \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/presentation/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/presentation/ReferralViewModel.kt new file mode 100644 index 0000000000..e106965a14 --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/presentation/ReferralViewModel.kt @@ -0,0 +1,11 @@ +package com.tangem.feature.referral.presentation + +import androidx.lifecycle.ViewModel +import com.tangem.feature.referral.domain.ReferralInteractor +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class ReferralViewModel @Inject constructor(referralInteractor: ReferralInteractor) : ViewModel() { + +} \ No newline at end of file From 544855747a44d011f8bded8ec2eee9bbca15de46 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 21 Nov 2022 23:17:36 +0300 Subject: [PATCH 063/176] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Buttons.kt | 16 +- .../com/tangem/core/ui/components/Cards.kt | 4 +- .../appbar/AppBarWithAdditionalButtons.kt | 12 +- .../components/appbar/AppBarWithBackButton.kt | 4 +- .../components/atoms/BottomSheetIndicator.kt | 4 +- .../com/tangem/core/ui/res/ButtonColor.kt | 8 + .../main/java/com/tangem/core/ui/res/Color.kt | 45 ---- .../java/com/tangem/core/ui/res/IconColor.kt | 12 + .../tangem/core/ui/res/TangemColorPalette.kt | 48 ++++ .../com/tangem/core/ui/res/TangemColors.kt | 237 ++++++++++++++++++ .../com/tangem/core/ui/res/TangemDimens.kt | 61 +++++ .../com/tangem/core/ui/res/TangemShapes.kt | 22 ++ .../com/tangem/core/ui/res/TangemTheme.kt | 219 +++++++++++++--- .../java/com/tangem/core/ui/res/TextColor.kt | 11 + .../ui/AgreementBottomSheetContent.kt | 4 +- .../feature/referral/ui/AgreementText.kt | 4 +- .../referral/ui/NonParticipateBottomBlock.kt | 4 +- .../referral/ui/ParticipateBottomBlock.kt | 4 +- .../feature/referral/ui/ReferralScreen.kt | 21 +- 19 files changed, 620 insertions(+), 120 deletions(-) delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/Color.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TangemColorPalette.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 65b331e15c..cc09da8dbc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -27,7 +27,7 @@ import com.tangem.core.ui.res.textColor @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) } } @@ -35,7 +35,7 @@ fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) } } @@ -43,7 +43,7 @@ fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() { @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { PrimaryStartIconButton( text = "Manage tokens", iconResId = R.drawable.ic_tangem, @@ -56,7 +56,7 @@ fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { PrimaryStartIconButton( text = "Manage tokens", iconResId = R.drawable.ic_tangem, @@ -69,7 +69,7 @@ fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) } } @@ -77,7 +77,7 @@ fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) } } @@ -85,7 +85,7 @@ fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) } } @@ -93,7 +93,7 @@ fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index 57a47dff46..ef25595ace 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -22,7 +22,7 @@ import com.tangem.core.ui.res.textColor @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_SimpleInfoCard_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC") } } @@ -30,7 +30,7 @@ fun Preview_SimpleInfoCard_InLightTheme() { @Preview(widthDp = 328, heightDp = 48, showBackground = true) @Composable fun Preview_SimpleInfoCard_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC") } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt index 73cca24307..34f25fe87c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt @@ -79,7 +79,7 @@ fun AppBarWithAdditionalButtons( @Preview(widthDp = 360, heightDp = 56, showBackground = true) @Composable fun Preview_AppBarWithAdditionalButtons_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( @@ -97,7 +97,7 @@ fun Preview_AppBarWithAdditionalButtons_InLightTheme() { @Preview(widthDp = 360, heightDp = 56, showBackground = true) @Composable fun Preview_AppBarWithAdditionalButtons_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( @@ -115,7 +115,7 @@ fun Preview_AppBarWithAdditionalButtons_InDarkTheme() { @Preview(widthDp = 360, heightDp = 56, showBackground = true) @Composable fun Preview_AppBarWithOnlyStartButtons_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( @@ -129,7 +129,7 @@ fun Preview_AppBarWithOnlyStartButtons_InLightTheme() { @Preview(widthDp = 360, heightDp = 56, showBackground = true) @Composable fun Preview_AppBarWithOnlyStartButtons_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { AppBarWithAdditionalButtons( text = "Tangem", startButton = AdditionalButton( @@ -143,7 +143,7 @@ fun Preview_AppBarWithOnlyStartButtons_InDarkTheme() { @Preview(widthDp = 360, heightDp = 56, showBackground = true) @Composable fun Preview_AppBarWithOnlyEndButtons_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { AppBarWithAdditionalButtons( text = "Tangem", endButton = AdditionalButton( @@ -157,7 +157,7 @@ fun Preview_AppBarWithOnlyEndButtons_InLightTheme() { @Preview(widthDp = 360, heightDp = 56, showBackground = true) @Composable fun Preview_AppBarWithOnlyEndButtons_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { AppBarWithAdditionalButtons( text = "Tangem", endButton = AdditionalButton( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index 99d6aa527a..629f6f7592 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -21,7 +21,7 @@ import com.tangem.core.ui.res.TangemTheme @Preview(widthDp = 360, heightDp = 56, showBackground = true) @Composable fun PreviewAppBarWithBackButtonInLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { AppBarWithBackButton(text = "Title", onBackClick = {}) } } @@ -29,7 +29,7 @@ fun PreviewAppBarWithBackButtonInLightTheme() { @Preview(widthDp = 360, heightDp = 56, showBackground = true) @Composable fun PreviewAppBarWithBackButtonInDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { AppBarWithBackButton(text = "Title", onBackClick = {}) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt index edf127e645..800b674c51 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt @@ -46,7 +46,7 @@ fun BottomSheetIndicator() { @Preview(heightDp = 20, showBackground = true) @Composable fun Preview_BottomSheetIndicator_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { BottomSheetIndicator() } } @@ -54,7 +54,7 @@ fun Preview_BottomSheetIndicator_InLightTheme() { @Preview(heightDp = 20, showBackground = true) @Composable fun Preview_BottomSheetIndicator_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { BottomSheetIndicator() } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt b/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt index cb6607c8a3..427be16fe5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/ButtonColor.kt @@ -3,7 +3,15 @@ package com.tangem.core.ui.res import androidx.compose.material.Colors import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import com.tangem.core.ui.res.TangemColorPalette.Dark5 +import com.tangem.core.ui.res.TangemColorPalette.Dark6 +import com.tangem.core.ui.res.TangemColorPalette.DarkGreen +import com.tangem.core.ui.res.TangemColorPalette.Light1 +import com.tangem.core.ui.res.TangemColorPalette.Light2 +import com.tangem.core.ui.res.TangemColorPalette.MagicMint +import com.tangem.core.ui.res.TangemColorPalette.Meadow +@Deprecated("Use TangemTheme.colors") enum class ButtonColorType(val lightColor: Color, val darkColor: Color) { PRIMARY(lightColor = Dark6, darkColor = Light1), SECONDARY(lightColor = Light2, darkColor = Dark5), diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/Color.kt b/core/ui/src/main/java/com/tangem/core/ui/res/Color.kt deleted file mode 100644 index 4d37ecee45..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/Color.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tangem.core.ui.res - -import androidx.compose.ui.graphics.Color - -// region Base -val Black = Color(0xFF000000) -val White = Color(0xFFFFFFFF) -// endregion Base - -// region Dark -val Dark1 = Color(0xFF919191) -val Dark2 = Color(0xFF656565) -val Dark3 = Color(0xFF494949) -val Dark4 = Color(0xFF3B3B3B) -val Dark5 = Color(0xFF303030) -val Dark6 = Color(0xFF1E1E1E) -// endregion Dark - -// region Light -val Light1 = Color(0xFFF5F5F5) -val Light2 = Color(0xFFEBEBEB) -val Light3 = Color(0xFFD3D3D3) -val Light4 = Color(0xFFC9C9C9) -val Light5 = Color(0xFFB0B0B0) -// endregion Light - -// region Green -val Meadow = Color(0xFF1ACE80) -val MagicMint = Color(0xFFA3EBCC) -val DarkGreen = Color(0xFF06311F) -// endregion Green - -// region Blue -val Cobalt = Color(0xFF0029FF) -val Azure = Color(0xFF007AFF) -// endregion Blue - -// region Red -val Amaranth = Color(0xFFDE1010) -val Flamingo = Color(0xFFED7979) -// endregion Red - -// region Yellow -val Tangerine = Color(0xFFFFB71B) -// endregion Yellow \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt b/core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt index 72cc8936bb..11dab52a7b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/IconColor.kt @@ -3,7 +3,19 @@ package com.tangem.core.ui.res import androidx.compose.material.Colors import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import com.tangem.core.ui.res.TangemColorPalette.Amaranth +import com.tangem.core.ui.res.TangemColorPalette.Black +import com.tangem.core.ui.res.TangemColorPalette.Dark1 +import com.tangem.core.ui.res.TangemColorPalette.Dark2 +import com.tangem.core.ui.res.TangemColorPalette.Dark4 +import com.tangem.core.ui.res.TangemColorPalette.Dark6 +import com.tangem.core.ui.res.TangemColorPalette.Light4 +import com.tangem.core.ui.res.TangemColorPalette.Light5 +import com.tangem.core.ui.res.TangemColorPalette.Meadow +import com.tangem.core.ui.res.TangemColorPalette.Tangerine +import com.tangem.core.ui.res.TangemColorPalette.White +@Deprecated("Use TangemTheme.colors") enum class IconColorType(val lightColor: Color, val darkColor: Color) { PRIMARY1(lightColor = Black, darkColor = White), PRIMARY2(lightColor = White, darkColor = Dark6), diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColorPalette.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColorPalette.kt new file mode 100644 index 0000000000..5f9617639f --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColorPalette.kt @@ -0,0 +1,48 @@ +package com.tangem.core.ui.res + +import androidx.compose.ui.graphics.Color + +@Suppress("unused") +object TangemColorPalette { + // region Base + val Black = Color(0xFF000000) + val White = Color(0xFFFFFFFF) + // endregion Base + + // region Dark + val Dark1 = Color(0xFF919191) + val Dark2 = Color(0xFF656565) + val Dark3 = Color(0xFF494949) + val Dark4 = Color(0xFF3B3B3B) + val Dark5 = Color(0xFF303030) + val Dark6 = Color(0xFF1E1E1E) + // endregion Dark + + // region Light + val Light1 = Color(0xFFF5F5F5) + val Light2 = Color(0xFFEBEBEB) + val Light3 = Color(0xFFD3D3D3) + val Light4 = Color(0xFFC9C9C9) + val Light5 = Color(0xFFB0B0B0) + // endregion Light + + // region Green + val Meadow = Color(0xFF1ACE80) + val MagicMint = Color(0xFFA3EBCC) + val DarkGreen = Color(0xFF06311F) + // endregion Green + + // region Blue + val Cobalt = Color(0xFF0029FF) + val Azure = Color(0xFF007AFF) + // endregion Blue + + // region Red + val Amaranth = Color(0xFFDE1010) + val Flamingo = Color(0xFFED7979) + // endregion Red + + // region Yellow + val Tangerine = Color(0xFFFFB71B) + // endregion Yellow +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt new file mode 100644 index 0000000000..371e70d22e --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt @@ -0,0 +1,237 @@ +package com.tangem.core.ui.res + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color + +@Suppress("unused", "MemberVisibilityCanBePrivate") +@Stable +class TangemColors internal constructor( + text: Text, + icon: Icon, + button: Button, + background: Background, + control: Control, + stroke: Stroke, + field: Field, +) { + var text by mutableStateOf(text) + private set + var icon by mutableStateOf(icon) + private set + var button by mutableStateOf(button) + private set + var background by mutableStateOf(background) + private set + var control by mutableStateOf(control) + private set + var stroke by mutableStateOf(stroke) + private set + var field by mutableStateOf(field) + private set + + @Stable + class Text internal constructor( + primary1: Color, + primary2: Color, + secondary: Color, + tertiary: Color, + disabled: Color, + accent: Color = TangemColorPalette.Meadow, + warning: Color = TangemColorPalette.Amaranth, + attention: Color = TangemColorPalette.Tangerine, + constantWhite: Color = TangemColorPalette.White, + ) { + var primary1 by mutableStateOf(primary1) + private set + var primary2 by mutableStateOf(primary2) + private set + var secondary by mutableStateOf(secondary) + private set + var tertiary by mutableStateOf(tertiary) + private set + var disabled by mutableStateOf(disabled) + private set + var accent by mutableStateOf(accent) + private set + var warning by mutableStateOf(warning) + private set + var attention by mutableStateOf(attention) + private set + var constantWhite by mutableStateOf(constantWhite) + private set + + fun update(other: Text) { + primary1 = other.primary1 + primary2 = other.primary2 + secondary = other.secondary + tertiary = other.tertiary + disabled = other.disabled + accent = other.accent + } + } + + @Stable + class Icon internal constructor( + primary1: Color, + primary2: Color, + secondary: Color, + informative: Color, + inactive: Color, + accent: Color = TangemColorPalette.Meadow, + warning: Color = TangemColorPalette.Amaranth, + attention: Color = TangemColorPalette.Tangerine, + ) { + var primary1 by mutableStateOf(primary1) + private set + var primary2 by mutableStateOf(primary2) + private set + var secondary by mutableStateOf(secondary) + private set + var informative by mutableStateOf(informative) + private set + var inactive by mutableStateOf(inactive) + private set + var accent by mutableStateOf(accent) + private set + var warning by mutableStateOf(warning) + private set + var attention by mutableStateOf(attention) + private set + + fun update(other: Icon) { + primary1 = other.primary1 + primary2 = other.primary2 + secondary = other.secondary + informative = other.informative + inactive = other.inactive + accent = other.accent + warning = other.warning + attention = other.attention + } + } + + @Stable + class Button internal constructor( + primary: Color, + secondary: Color, + disabled: Color, + positive: Color = TangemColorPalette.Meadow, + positiveDisabled: Color, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var disabled by mutableStateOf(disabled) + private set + var positive by mutableStateOf(positive) + private set + var positiveDisabled by mutableStateOf(positiveDisabled) + private set + + fun update(other: Button) { + primary = other.primary + secondary = other.secondary + disabled = other.disabled + positive = other.positive + positiveDisabled = other.positiveDisabled + } + } + + @Stable + class Background internal constructor( + primary: Color, + secondary: Color, + plain: Color, + action: Color, + fade: Color, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var plain by mutableStateOf(plain) + private set + var action by mutableStateOf(action) + private set + var fade by mutableStateOf(fade) + private set + + fun update(other: Background) { + primary = other.primary + secondary = other.secondary + plain = other.plain + action = other.action + fade = other.fade + } + } + + @Stable + class Control internal constructor( + checked: Color, + unchecked: Color, + key: Color, + ) { + var checked by mutableStateOf(checked) + private set + var unchecked by mutableStateOf(unchecked) + private set + var key by mutableStateOf(key) + private set + + fun update(other: Control) { + checked = other.checked + unchecked = other.unchecked + key = other.key + } + } + + @Stable + class Stroke internal constructor( + primary: Color, + secondary: Color, + transparency: Color, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var transparency by mutableStateOf(transparency) + private set + + fun update(other: Stroke) { + primary = other.primary + secondary = other.secondary + transparency = other.transparency + } + } + + @Stable + class Field internal constructor( + primary: Color, + focused: Color, + ) { + var primary by mutableStateOf(primary) + private set + var focused by mutableStateOf(focused) + private set + + fun update(other: Field) { + primary = other.primary + focused = other.focused + } + } + + fun update(other: TangemColors) { + text.update(other.text) + icon.update(other.icon) + button.update(other.button) + background.update(other.background) + control.update(other.control) + stroke.update(other.stroke) + field.update(other.field) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt new file mode 100644 index 0000000000..441b0bfd52 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -0,0 +1,61 @@ +package com.tangem.core.ui.res + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Immutable +data class TangemDimens internal constructor( + // region Elevation + val elevation0: Dp = 0.dp, + val elevation1: Dp = 1.dp, + val elevation2: Dp = 2.dp, + val elevation3: Dp = 3.dp, + val elevation4: Dp = 4.dp, + val elevation6: Dp = 6.dp, + val elevation8: Dp = 8.dp, + val elevation12: Dp = 12.dp, + val elevation16: Dp = 16.dp, + val elevation24: Dp = 24.dp, + // endregion Elevation + // region Radius + val radius2: Dp = 2.dp, + val radius4: Dp = 4.dp, + val radius6: Dp = 6.dp, + val radius12: Dp = 12.dp, + val radius16: Dp = 16.dp, + val radius28: Dp = 28.dp, + // endregion Radius + // region Size + val size0: Dp = 0.dp, + val size4: Dp = 4.dp, + val size8: Dp = 8.dp, + val size11: Dp = 11.dp, + val size16: Dp = 16.dp, + val size20: Dp = 20.dp, + val size24: Dp = 24.dp, + val size28: Dp = 28.dp, + val size32: Dp = 32.dp, + val size40: Dp = 40.dp, + val size48: Dp = 48.dp, + val size56: Dp = 56.dp, + val size84: Dp = 84.dp, + val size86: Dp = 86.dp, + val size102: Dp = 102.dp, + val size164: Dp = 164.dp, + val size200: Dp = 200.dp, + // endregion Size + // region Spacing + val spacing2: Dp = 2.dp, + val spacing4: Dp = 4.dp, + val spacing8: Dp = 8.dp, + val spacing10: Dp = 10.dp, + val spacing12: Dp = 12.dp, + val spacing16: Dp = 16.dp, + val spacing24: Dp = 24.dp, + val spacing32: Dp = 32.dp, + val spacing44: Dp = 44.dp, + val spacing50: Dp = 50.dp, + val spacing54: Dp = 54.dp, + // endregion Spacing +) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt new file mode 100644 index 0000000000..99a87a701e --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt @@ -0,0 +1,22 @@ +package com.tangem.core.ui.res + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Shape + +@Immutable +data class TangemShapes internal constructor( + val roundedCornersSmall: Shape, + val roundedCornersSmall2: Shape, + val roundedCornersMedium: Shape, + val roundedCornersLarge: Shape, + val bottomSheet: Shape, +) { + constructor(dimens: TangemDimens) : this( + roundedCornersSmall = RoundedCornerShape(size = dimens.radius2), + roundedCornersSmall2 = RoundedCornerShape(size = dimens.radius4), + roundedCornersMedium = RoundedCornerShape(size = dimens.radius12), + roundedCornersLarge = RoundedCornerShape(size = dimens.radius28), + bottomSheet = RoundedCornerShape(size = dimens.radius16), + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index ebd0da2f57..8ff81a2640 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -1,53 +1,200 @@ package com.tangem.core.ui.res +import androidx.compose.material.Colors import androidx.compose.material.MaterialTheme -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors +import androidx.compose.material.ProvideTextStyle +import androidx.compose.material.Typography import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import com.google.accompanist.systemuicontroller.rememberSystemUiController +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.runtime.staticCompositionLocalOf internal val isSystemInDarkTheme: Boolean = false // TODO: use isSystemInDarkTheme() for automatic color detection @Composable fun TangemTheme( - isDarkTheme: Boolean = isSystemInDarkTheme, + isDark: Boolean = false, + typography: Typography = TangemTheme.typography, + dimens: TangemDimens = TangemTheme.dimens, content: @Composable () -> Unit, ) { - val systemUiController = rememberSystemUiController() - SideEffect { - systemUiController.setSystemBarsColor(color = if (isDarkTheme) Black else Light1, darkIcons = !isDarkTheme) - } + val themeColors = if (isDark) darkThemeColors() else lightThemeColors() + val rememberedColors = remember { themeColors } + .also { it.update(themeColors) } + + val shapes = remember { TangemShapes(dimens) } MaterialTheme( - colors = if (isDarkTheme) DarkColors else LightColors, - typography = TangemTypography, - content = content, + colors = materialThemeColors(colors = themeColors, isDark = isDark), + typography = typography, + ) { + CompositionLocalProvider( + LocalTangemColors provides rememberedColors, + LocalTangemTypography provides typography, + LocalTangemDimens provides dimens, + LocalTangemShapes provides shapes, + ) { + ProvideTextStyle( + value = TangemTheme.typography.body1, + content = content, + ) + } + } +} + +object TangemTheme { + val colors: TangemColors + @Composable + @ReadOnlyComposable + get() = LocalTangemColors.current + + val typography: Typography + @Composable + @ReadOnlyComposable + get() = LocalTangemTypography.current + + val dimens: TangemDimens + @Composable + @ReadOnlyComposable + get() = LocalTangemDimens.current + + val shapes: TangemShapes + @Composable + @ReadOnlyComposable + get() = LocalTangemShapes.current +} + +@Stable +@Composable +private fun materialThemeColors( + colors: TangemColors, + isDark: Boolean, +): Colors { + return Colors( + primary = colors.background.primary, + primaryVariant = colors.background.secondary, + secondary = colors.button.primary, + secondaryVariant = colors.text.accent, + background = colors.background.primary, + surface = colors.background.plain, + error = colors.text.warning, + onPrimary = colors.text.primary1, + onSecondary = colors.text.primary1, + onBackground = colors.text.primary1, + onSurface = colors.text.primary1, + onError = colors.text.primary2, + isLight = !isDark, ) } -private val LightColors = lightColors( - primary = Light1, - primaryVariant = Light1, - secondary = White, - background = Light1, - surface = White, - error = Amaranth, - onPrimary = Dark6, - onSecondary = Dark6, - onBackground = Dark6, - onSurface = Dark6, -) +@Composable +private fun lightThemeColors(): TangemColors { + return TangemColors( + text = TangemColors.Text( + primary1 = TangemColorPalette.Dark6, + primary2 = TangemColorPalette.White, + secondary = TangemColorPalette.Dark2, + tertiary = TangemColorPalette.Dark1, + disabled = TangemColorPalette.Light4, + ), + icon = TangemColors.Icon( + primary1 = TangemColorPalette.Black, + primary2 = TangemColorPalette.White, + secondary = TangemColorPalette.Dark2, + informative = TangemColorPalette.Light5, + inactive = TangemColorPalette.Light4, + ), + button = TangemColors.Button( + primary = TangemColorPalette.Dark6, + secondary = TangemColorPalette.Light2, + disabled = TangemColorPalette.Light2, + positiveDisabled = TangemColorPalette.MagicMint, + ), + background = TangemColors.Background( + primary = TangemColorPalette.White, + secondary = TangemColorPalette.Light1, + plain = TangemColorPalette.White, + action = TangemColorPalette.Black, + fade = TangemColorPalette.White, + ), + control = TangemColors.Control( + checked = TangemColorPalette.Meadow, + unchecked = TangemColorPalette.Light2, + key = TangemColorPalette.White, + ), + stroke = TangemColors.Stroke( + primary = TangemColorPalette.Light2, + secondary = TangemColorPalette.Dark4, + transparency = TangemColorPalette.White, + ), + field = TangemColors.Field( + primary = TangemColorPalette.Light1, + focused = TangemColorPalette.Light2, + ), + ) +} -private val DarkColors = darkColors( - primary = Black, - primaryVariant = Black, - secondary = Dark6, - background = Black, - surface = Dark6, - error = Amaranth, - onPrimary = White, - onSecondary = White, - onBackground = White, - onSurface = White, -) \ No newline at end of file +@Composable +private fun darkThemeColors(): TangemColors { + return TangemColors( + text = TangemColors.Text( + primary1 = TangemColorPalette.White, + primary2 = TangemColorPalette.Dark6, + secondary = TangemColorPalette.Light5, + tertiary = TangemColorPalette.Dark1, + disabled = TangemColorPalette.Dark3, + ), + icon = TangemColors.Icon( + primary1 = TangemColorPalette.White, + primary2 = TangemColorPalette.Dark6, + secondary = TangemColorPalette.Dark1, + informative = TangemColorPalette.Dark2, + inactive = TangemColorPalette.Dark4, + ), + button = TangemColors.Button( + primary = TangemColorPalette.Light1, + secondary = TangemColorPalette.Dark5, + disabled = TangemColorPalette.Dark6, + positiveDisabled = TangemColorPalette.DarkGreen, + ), + background = TangemColors.Background( + primary = TangemColorPalette.Dark6, + secondary = TangemColorPalette.Black, + plain = TangemColorPalette.Black, + action = TangemColorPalette.Light4, + fade = TangemColorPalette.Black, + ), + control = TangemColors.Control( + checked = TangemColorPalette.Meadow, + unchecked = TangemColorPalette.Dark4, + key = TangemColorPalette.Light1, + ), + stroke = TangemColors.Stroke( + primary = TangemColorPalette.Dark5, + secondary = TangemColorPalette.Dark1, + transparency = TangemColorPalette.Dark6, + ), + field = TangemColors.Field( + primary = TangemColorPalette.Dark5, + focused = TangemColorPalette.Dark4, + ), + ) +} + +private val LocalTangemColors = staticCompositionLocalOf { + error("No TangemColors provided") +} + +private val LocalTangemTypography = staticCompositionLocalOf { + TangemTypography +} + +private val LocalTangemDimens = staticCompositionLocalOf { + TangemDimens() +} + +private val LocalTangemShapes = staticCompositionLocalOf { + error("No TangemShapes provided") +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt index 44f94e1157..0fce11d9e7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TextColor.kt @@ -3,7 +3,18 @@ package com.tangem.core.ui.res import androidx.compose.material.Colors import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import com.tangem.core.ui.res.TangemColorPalette.Amaranth +import com.tangem.core.ui.res.TangemColorPalette.Dark1 +import com.tangem.core.ui.res.TangemColorPalette.Dark2 +import com.tangem.core.ui.res.TangemColorPalette.Dark3 +import com.tangem.core.ui.res.TangemColorPalette.Dark6 +import com.tangem.core.ui.res.TangemColorPalette.Light4 +import com.tangem.core.ui.res.TangemColorPalette.Light5 +import com.tangem.core.ui.res.TangemColorPalette.Meadow +import com.tangem.core.ui.res.TangemColorPalette.Tangerine +import com.tangem.core.ui.res.TangemColorPalette.White +@Deprecated("Use TangemTheme.colors") enum class TextColorType(val lightColor: Color, val darkColor: Color) { PRIMARY1(lightColor = Dark6, darkColor = White), PRIMARY2(lightColor = White, darkColor = Dark6), diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt index a1f8adc86c..e7d30282c8 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt @@ -66,7 +66,7 @@ private fun AgreementHtmlView(url: String) { @Preview @Composable fun Preview_AgreementBottomSheet_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { AgreementBottomSheetContent(url = "https://tangem.com/en/") } } @@ -74,7 +74,7 @@ fun Preview_AgreementBottomSheet_InLightTheme() { @Preview @Composable fun Preview_AgreementBottomSheet_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { AgreementBottomSheetContent(url = "https://tangem.com/en/") } } \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt index 133b6b374e..cbdcdaeb50 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt @@ -25,7 +25,7 @@ import com.tangem.feature.referral.presentation.R @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_AgreementText_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { AgreementText(firstPartResId = R.string.referral_tou_not_enroled_prefix, onClicked = {}) } @@ -35,7 +35,7 @@ fun Preview_AgreementText_InLightTheme() { @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_AgreementText_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { AgreementText(firstPartResId = R.string.referral_tou_not_enroled_prefix, onClicked = {}) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index 59b7ab8148..2706490417 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -26,7 +26,7 @@ import com.tangem.feature.referral.presentation.R @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_NonParticipateBottomBlock_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { Column(Modifier.background(MaterialTheme.colors.primary)) { NonParticipateBottomBlock( purchasedWalletCount = 3, @@ -42,7 +42,7 @@ fun Preview_NonParticipateBottomBlock_InLightTheme() { @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_NonParticipateBottomBlock_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { Column(Modifier.background(MaterialTheme.colors.primary)) { NonParticipateBottomBlock( purchasedWalletCount = 3, diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 7294cbd3f7..f6c6eced81 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -16,7 +16,7 @@ import com.tangem.feature.referral.presentation.R @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ParticipateBottomBlock_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { Column(Modifier.background(MaterialTheme.colors.primary)) { ParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) } @@ -26,7 +26,7 @@ fun Preview_ParticipateBottomBlock_InLightTheme() { @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ParticipateBottomBlock_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { Column(Modifier.background(MaterialTheme.colors.primary)) { ParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index a9935a04cc..7091674dcd 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -42,9 +42,9 @@ import com.tangem.core.ui.components.VerticalSpacer import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.res.ButtonColorType import com.tangem.core.ui.res.IconColorType +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.White import com.tangem.core.ui.res.buttonColor import com.tangem.core.ui.res.iconColor import com.tangem.core.ui.res.textColor @@ -291,14 +291,14 @@ private fun ShimmerInfo() { .clip(RoundedCornerShape(dimensionResource(id = R.dimen.radius6))) .width(dimensionResource(id = R.dimen.size102)) .height(dimensionResource(id = R.dimen.size16)) - .background(White), + .background(TangemColorPalette.White), ) Box( modifier = Modifier .clip(RoundedCornerShape(dimensionResource(id = R.dimen.radius6))) .width(dimensionResource(id = R.dimen.size40)) .height(dimensionResource(id = R.dimen.size11)) - .background(White), + .background(TangemColorPalette.White), ) } } @@ -325,7 +325,7 @@ private fun NonParticipantBottomBlock(state: ReferralInfoState.NonParticipantCon @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ReferralScreen_Participant_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { ReferralScreen( stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), @@ -347,7 +347,7 @@ fun Preview_ReferralScreen_Participant_InLightTheme() { @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ReferralScreen_Participant_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { ReferralScreen( stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), @@ -370,7 +370,7 @@ fun Preview_ReferralScreen_Participant_InDarkTheme() { @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ReferralScreen_NonParticipant_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { ReferralScreen( stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), @@ -395,7 +395,7 @@ fun Preview_ReferralScreen_NonParticipant_InLightTheme() { @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { ReferralScreen( stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), @@ -420,7 +420,7 @@ fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ReferralScreen_Loading_InLightTheme() { - TangemTheme(isDarkTheme = false) { + TangemTheme(isDark = false) { ReferralScreen( stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), @@ -437,7 +437,7 @@ fun Preview_ReferralScreen_Loading_InLightTheme() { @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ReferralScreen_Loading_InDarkTheme() { - TangemTheme(isDarkTheme = true) { + TangemTheme(isDark = true) { ReferralScreen( stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), @@ -449,5 +449,4 @@ fun Preview_ReferralScreen_Loading_InDarkTheme() { ), ) } -} - +} \ No newline at end of file From 48ce9b4fd271c71cde8cee4aa442516074474ae1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 11 Nov 2022 21:25:42 +0400 Subject: [PATCH 064/176] Updated on 2026-08-14 --- .../tap/common/extensions/Navigation.kt | 16 +- .../redux/navigation/NavigationState.kt | 6 +- core/ui/build.gradle.kts | 3 + .../com/tangem/core/ui/components/Buttons.kt | 681 ++++++++++++++---- .../core/ui/components/ButtonsDeprecated.kt | 185 +++++ .../core/ui/components/SystemBarsEffect.kt | 14 + .../tangem/core/ui/components/TextFields.kt | 414 +++++++++++ .../components/atoms/BottomSheetIndicator.kt | 1 + .../tangem/core/ui/components/atoms/Hand.kt | 72 ++ .../fragments/ComposeBottomSheetFragment.kt | 87 +++ .../core/ui/fragments/ComposeFragment.kt | 57 ++ core/ui/src/main/res/drawable/ic_alert.xml | 10 + core/ui/src/main/res/drawable/ic_close.xml | 10 + core/ui/src/main/res/drawable/ic_plus.xml | 10 + core/ui/src/main/res/values/styles.xml | 12 + 15 files changed, 1429 insertions(+), 149 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeFragment.kt create mode 100644 core/ui/src/main/res/drawable/ic_alert.xml create mode 100644 core/ui/src/main/res/drawable/ic_close.xml create mode 100644 core/ui/src/main/res/drawable/ic_plus.xml create mode 100644 core/ui/src/main/res/values/styles.xml diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index a9504e668a..5e07c484b8 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.extensions import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager @@ -29,12 +30,10 @@ import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.wallet.R import timber.log.Timber -private class Navigation - fun FragmentActivity.openFragment( screen: AppScreen, addToBackstack: Boolean, - fgShareTransition: FragmentShareTransition? = null + fgShareTransition: FragmentShareTransition? = null, ) { val transaction = this.supportFragmentManager.beginTransaction() val fragment = fragmentFactory(screen) @@ -48,9 +47,14 @@ fun FragmentActivity.openFragment( } } } - transaction.replace(R.id.fragment_container, fragment, screen.name) - if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) - transaction.commitAllowingStateLoss() + if (screen.isDialogFragment) { + (fragment as DialogFragment).show(transaction, screen.name) + if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) + } else { + transaction.replace(R.id.fragment_container, fragment, screen.name) + if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) + transaction.commitAllowingStateLoss() + } } fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) { diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index 224e12755b..05ec744393 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -9,7 +9,9 @@ data class NavigationState( val activity: WeakReference? = null, ) : StateType -enum class AppScreen { +enum class AppScreen( + val isDialogFragment: Boolean = false, +) { Home, Shop, Disclaimer, @@ -19,5 +21,5 @@ enum class AppScreen { Details, DetailsSecurity, CardSettings, AppSettings, ResetToFactory, AddTokens, AddCustomToken, WalletConnectSessions, - QrScan + QrScan, } \ No newline at end of file diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 3c509f8c3f..6881f67082 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -30,6 +30,8 @@ android { } dependencies { + /** AndroidX libraries */ + implementation(AndroidX.fragmentKtx) /** Compose */ implementation(Compose.foundation) @@ -38,4 +40,5 @@ dependencies { /** Other libraries */ implementation(Library.accompanistSystemUiController) + implementation(Library.materialComponent) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index cc09da8dbc..11dae7b514 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -1,183 +1,582 @@ package com.tangem.core.ui.components -import androidx.annotation.DrawableRes -import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.Button +import androidx.compose.material.ButtonColors import androidx.compose.material.ButtonDefaults +import androidx.compose.material.ButtonElevation +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Divider import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.State +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import com.tangem.core.ui.R -import com.tangem.core.ui.res.ButtonColorType import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.buttonColor -import com.tangem.core.ui.res.textColor +import com.tangem.core.ui.res.TangemTypography -@Preview(widthDp = 328, heightDp = 48, showBackground = true) +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A103&t=TmfD6UBHPg9uYfev-4) + * */ @Composable -fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { - TangemTheme(isDark = false) { - PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { - TangemTheme(isDark = false) { - PrimaryStartIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem, - enabled = false, - onClicked = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryStartIconButton( - text = "Manage tokens", - iconResId = R.drawable.ic_tangem, - enabled = false, - onClicked = {}, - ) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { - TangemTheme(isDark = false) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { - TangemTheme(isDark = false) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) - } -} - -@Preview(widthDp = 328, heightDp = 48, showBackground = true) -@Composable -fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { - TangemTheme(isDark = true) { - PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) - } +fun TextButton( + modifier: Modifier = Modifier, + text: String, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.None, + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.textButtonColors, + size = TangemButtonSize.Text, + ) } /** - * Primary button with an icon at the beginning of the layout - * - * @param modifier button modifier - * @param text button text - * @param iconResId button icon res id - * @param enabled controls the enabled state of the button - * @param onClicked the lambda to be invoked when this button is pressed - * - * @see Figma component - */ + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A62&t=TmfD6UBHPg9uYfev-4) + * */ @Composable -fun PrimaryStartIconButton( +fun TextButtonIconLeft( modifier: Modifier = Modifier, text: String, - @DrawableRes iconResId: Int, + icon: Painter, enabled: Boolean = true, - onClicked: () -> Unit, + onClick: () -> Unit, ) { - PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { - Icon( - painter = painterResource(id = iconResId), - contentDescription = null, - ) - Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) - Text(text = text) - } + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.textButtonColors, + size = TangemButtonSize.Text, + ) +} + +@Composable +fun PrimaryButton( + modifier: Modifier = Modifier, + text: String, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.None, + onClick = onClick, + colors = TangemButtonsDefaults.primaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) } /** - * Primary button with an icon at the end of the layout - * - * @param modifier button modifier - * @param text button text - * @param iconResId button icon res id - * @param enabled controls the enabled state of the button - * @param onClicked the lambda to be invoked when this button is pressed - * - * @see Figma component - */ + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=68%3A47&t=TmfD6UBHPg9uYfev-4) + * */ @Composable -fun PrimaryEndIconButton( +fun PrimaryButtonIconRight( modifier: Modifier = Modifier, text: String, - @DrawableRes iconResId: Int, + icon: Painter, + showProgress: Boolean = false, enabled: Boolean = true, - onClicked: () -> Unit, + onClick: () -> Unit, ) { - PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { - Text(text = text) - Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) - Icon( - painter = painterResource(id = iconResId), - contentDescription = null, - ) - } + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Right(icon), + onClick = onClick, + colors = TangemButtonsDefaults.primaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) +} + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A258&t=TmfD6UBHPg9uYfev-4) + * */ +@Composable +fun PrimaryButtonIconLeft( + modifier: Modifier = Modifier, + text: String, + icon: Painter, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + colors = TangemButtonsDefaults.primaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) } @Composable -private fun PrimaryButtonRow( - modifier: Modifier, +fun SecondaryButton( + modifier: Modifier = Modifier, + text: String, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.None, + onClick = onClick, + colors = TangemButtonsDefaults.secondaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) +} + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=99%3A50&t=TmfD6UBHPg9uYfev-4) + * */ +@Composable +fun SecondaryButtonIconRight( + modifier: Modifier = Modifier, + text: String, + icon: Painter, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Right(icon), + onClick = onClick, + colors = TangemButtonsDefaults.secondaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) +} + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=233%3A262&t=TmfD6UBHPg9uYfev-4) + * */ +@Composable +fun SecondaryButtonIconLeft( + modifier: Modifier = Modifier, + text: String, + icon: Painter, + showProgress: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + colors = TangemButtonsDefaults.secondaryButtonColors, + enabled = enabled, + showProgress = showProgress, + ) +} + +// region Defaults +@Composable +private fun TangemButton( + modifier: Modifier = Modifier, + text: String, + icon: TangemButtonIcon, + onClick: () -> Unit, + colors: ButtonColors, + showProgress: Boolean, enabled: Boolean, - onClicked: () -> Unit, - content: @Composable (RowScope.() -> Unit), + size: TangemButtonSize = TangemButtonSize.Default, + elevation: ButtonElevation = TangemButtonsDefaults.elevation, ) { Button( - onClick = onClicked, modifier = modifier - .fillMaxWidth() - .height(dimensionResource(R.dimen.size48)), + .width(IntrinsicSize.Min) + .height(IntrinsicSize.Min) + .heightIn(size.toHeightDp()), + onClick = { + if (!showProgress) { + onClick() + } + }, enabled = enabled, - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), - colors = ButtonDefaults.buttonColors( - backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY), - contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), - disabledBackgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.DISABLED), - disabledContentColor = MaterialTheme.colors.textColor(type = TextColorType.DISABLED), - ), - content = content, - ) -} \ No newline at end of file + elevation = elevation, + shape = size.toShape(), + colors = colors, + ) { + ButtonContent( + text = text, + buttonIcon = icon, + colors = colors, + showProgress = showProgress, + enabled = enabled, + ) + } +} + +@Composable +private fun ButtonContent( + text: String, + buttonIcon: TangemButtonIcon, + colors: ButtonColors, + enabled: Boolean, + showProgress: Boolean, +) { + val icon = @Composable { painter: Painter -> + Icon( + modifier = Modifier.size(TangemTheme.dimens.size20), + painter = painter, + tint = colors.contentColor(enabled = enabled).value, + contentDescription = null, + ) + } + + if (showProgress) Box( + modifier = Modifier.wrapContentSize(), + ) { + CircularProgressIndicator( + modifier = Modifier + .align(Alignment.Center) + .size(TangemTheme.dimens.size24), + color = colors.contentColor(enabled = enabled).value, + strokeWidth = TangemTheme.dimens.size4, + ) + } else Row { + if (buttonIcon is TangemButtonIcon.Left) { + icon(buttonIcon.painter) + Spacer(modifier = Modifier.width(TangemTheme.dimens.size8)) + } + Text( + text = text, + style = TangemTypography.button, + color = colors.contentColor(enabled = enabled).value, + ) + if (buttonIcon is TangemButtonIcon.Right) { + Spacer(modifier = Modifier.width(TangemTheme.dimens.size8)) + icon(buttonIcon.painter) + } + } +} + +sealed interface TangemButtonIcon { + val painter: Painter? + + @Immutable + data class Left(override val painter: Painter) : TangemButtonIcon + + @Immutable + data class Right(override val painter: Painter) : TangemButtonIcon + + object None : TangemButtonIcon { + override val painter: Painter? = null + } +} + +enum class TangemButtonSize { + Default, + Text, +} + +@Composable +private fun TangemButtonSize.toHeightDp(): Dp = when (this) { + TangemButtonSize.Default -> TangemTheme.dimens.size48 + TangemButtonSize.Text -> TangemTheme.dimens.size40 +} + +@Composable +private fun TangemButtonSize.toShape(): Shape = when (this) { + TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium + TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall +} + +private object TangemButtonsDefaults { + val elevation: ButtonElevation + @Composable get() = ButtonDefaults + .elevation( + defaultElevation = TangemTheme.dimens.elevation0, + pressedElevation = TangemTheme.dimens.elevation0, + ) + + val primaryButtonColors: ButtonColors + @Composable get() = TangemButtonColors( + backgroundColor = TangemTheme.colors.button.primary, + contentColor = TangemTheme.colors.text.primary2, + disabledBackgroundColor = TangemTheme.colors.button.disabled, + disabledContentColor = TangemTheme.colors.text.disabled, + ) + + val secondaryButtonColors: ButtonColors + @Composable get() = TangemButtonColors( + backgroundColor = TangemTheme.colors.button.secondary, + contentColor = TangemTheme.colors.text.primary1, + disabledBackgroundColor = TangemTheme.colors.button.disabled, + disabledContentColor = TangemTheme.colors.text.disabled, + ) + + val textButtonColors: ButtonColors + @Composable get() = TangemButtonColors( + backgroundColor = Color.Transparent, + contentColor = TangemTheme.colors.text.secondary, + disabledBackgroundColor = Color.Transparent, + disabledContentColor = TangemTheme.colors.text.disabled, + ) +} + +@Immutable +private class TangemButtonColors( + private val backgroundColor: Color, + private val contentColor: Color, + private val disabledBackgroundColor: Color, + private val disabledContentColor: Color, +) : ButtonColors { + @Composable + override fun backgroundColor(enabled: Boolean): State { + return rememberUpdatedState(newValue = if (enabled) backgroundColor else disabledBackgroundColor) + } + + @Composable + override fun contentColor(enabled: Boolean): State { + return rememberUpdatedState(newValue = if (enabled) contentColor else disabledContentColor) + } +} +// endregion Defaults + +// region Preview +@Composable +private fun PrimaryButtonSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + showProgress = true, + text = "Manage tokens", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButtonIconLeft( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + PrimaryButtonIconLeft( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + enabled = false, + onClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun PrimaryButtonPreview_Light() { + TangemTheme { + PrimaryButtonSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun PrimaryButtonPreview_Dark() { + TangemTheme(isDark = true) { + PrimaryButtonSample() + } +} + +@Composable +private fun SecondaryButtonSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + showProgress = true, + text = "Manage tokens", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButtonIconLeft( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + SecondaryButtonIconLeft( + modifier = Modifier.fillMaxWidth(), + text = "Manage tokens", + icon = painterResource(id = R.drawable.ic_tangem), + enabled = false, + onClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun SecondaryButtonPreview_Light() { + TangemTheme { + SecondaryButtonSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun SecondaryButtonPreview_Dark() { + TangemTheme(isDark = true) { + SecondaryButtonSample() + } +} + +@Composable +private fun TextButtonSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + TextButton( + text = "Enabled", + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + TextButtonIconLeft( + text = "Enabled", + icon = painterResource(id = R.drawable.ic_plus), + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + TextButton( + text = "Enabled", + enabled = false, + onClick = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + TextButtonIconLeft( + text = "Enabled", + icon = painterResource(id = R.drawable.ic_plus), + enabled = false, + onClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun TextButtonPreview_Light() { + TangemTheme { + TextButtonSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun TextButtonPreview_Dark() { + TangemTheme(isDark = true) { + TextButtonSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt new file mode 100644 index 0000000000..0ff2ce98a8 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt @@ -0,0 +1,185 @@ +package com.tangem.core.ui.components + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.R +import com.tangem.core.ui.res.ButtonColorType +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TextColorType +import com.tangem.core.ui.res.buttonColor +import com.tangem.core.ui.res.textColor + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Enabled_InLightTheme() { + TangemTheme(isDark = false) { + PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Enabled_InDarkTheme() { + TangemTheme(isDark = true) { + PrimaryStartIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Disabled_InLightTheme() { + TangemTheme(isDark = false) { + PrimaryStartIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem, + enabled = false, + onClicked = {}, + ) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryStartIconButton_Disabled_InDarkTheme() { + TangemTheme(isDark = true) { + PrimaryStartIconButton( + text = "Manage tokens", + iconResId = R.drawable.ic_tangem, + enabled = false, + onClicked = {}, + ) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Enabled_InLightTheme() { + TangemTheme(isDark = false) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Enabled_InDarkTheme() { + TangemTheme(isDark = true) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = true, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Disabled_InLightTheme() { + TangemTheme(isDark = false) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) + } +} + +@Preview(widthDp = 328, heightDp = 48, showBackground = true) +@Composable +fun Preview_PrimaryEndIconButton_Disabled_InDarkTheme() { + TangemTheme(isDark = true) { + PrimaryEndIconButton(text = "Manage tokens", iconResId = R.drawable.ic_tangem, enabled = false, onClicked = {}) + } +} + +/** + * Primary button with an icon at the beginning of the layout + * + * @param modifier button modifier + * @param text button text + * @param iconResId button icon res id + * @param enabled controls the enabled state of the button + * @param onClicked the lambda to be invoked when this button is pressed + * + * @see Figma component + */ +@Deprecated("Use PrimaryButtonIconRight instead") +@Composable +fun PrimaryStartIconButton( + modifier: Modifier = Modifier, + text: String, + @DrawableRes iconResId: Int, + enabled: Boolean = true, + onClicked: () -> Unit, +) { + PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { + Icon( + painter = painterResource(id = iconResId), + contentDescription = null, + ) + Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) + Text(text = text) + } +} + +/** + * Primary button with an icon at the end of the layout + * + * @param modifier button modifier + * @param text button text + * @param iconResId button icon res id + * @param enabled controls the enabled state of the button + * @param onClicked the lambda to be invoked when this button is pressed + * + * @see Figma component + */ +@Deprecated("Use PrimaryButtonIconLeft instead") +@Composable +fun PrimaryEndIconButton( + modifier: Modifier = Modifier, + text: String, + @DrawableRes iconResId: Int, + enabled: Boolean = true, + onClicked: () -> Unit, +) { + PrimaryButtonRow(modifier = modifier, enabled = enabled, onClicked = onClicked) { + Text(text = text) + Spacer(modifier = Modifier.width(dimensionResource(R.dimen.spacing8))) + Icon( + painter = painterResource(id = iconResId), + contentDescription = null, + ) + } +} + +@Composable +private fun PrimaryButtonRow( + modifier: Modifier, + enabled: Boolean, + onClicked: () -> Unit, + content: @Composable (RowScope.() -> Unit), +) { + Button( + onClick = onClicked, + modifier = modifier + .fillMaxWidth() + .height(dimensionResource(R.dimen.size48)), + enabled = enabled, + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + colors = ButtonDefaults.buttonColors( + backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY), + contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + disabledBackgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.DISABLED), + disabledContentColor = MaterialTheme.colors.textColor(type = TextColorType.DISABLED), + ), + content = content, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt new file mode 100644 index 0000000000..34ac768e47 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/SystemBarsEffect.kt @@ -0,0 +1,14 @@ +package com.tangem.core.ui.components + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import com.google.accompanist.systemuicontroller.SystemUiController +import com.google.accompanist.systemuicontroller.rememberSystemUiController + +@Composable +fun SystemBarsEffect( + block: SystemUiController.() -> Unit, +) { + val systemUiController = rememberSystemUiController() + SideEffect { block(systemUiController) } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt new file mode 100644 index 0000000000..f14d87c904 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -0,0 +1,414 @@ +package com.tangem.core.ui.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.material.TextFieldColors +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemTypography + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=213%3A218&t=TmfD6UBHPg9uYfev-4) + * */ +@Composable +fun OutlineTextField( + modifier: Modifier = Modifier, + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + label: String? = null, + placeholder: String? = null, + caption: String? = null, + enabled: Boolean = true, + isError: Boolean = false, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + TangemTextField( + modifier = modifier, + value = value, + onValueChange = onValueChange, + singleLine = true, + label = label, + placeholder = placeholder, + caption = caption, + enabled = enabled, + isError = isError, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + interactionSource = interactionSource, + ) +} + +// region Defaults +@Composable +private fun TangemTextField( + modifier: Modifier = Modifier, + value: TextFieldValue, + singleLine: Boolean, + onValueChange: (TextFieldValue) -> Unit, + label: String? = null, + placeholder: String? = null, + caption: String? = null, + enabled: Boolean = true, + isError: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + colors: TangemTextFieldColors = TangemTextFieldsDefault.defaultTextFieldColors, + size: TangemTextFieldSize = TangemTextFieldSize.Default, + onClear: () -> Unit = { onValueChange(TextFieldValue()) }, +) { + Column(modifier = modifier) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + .heightIn(size.toHeightDp()), + value = value, + textStyle = TangemTypography.body1, + onValueChange = onValueChange, + singleLine = singleLine, + enabled = enabled, + isError = isError, + visualTransformation = visualTransformation, + keyboardActions = keyboardActions, + keyboardOptions = keyboardOptions, + interactionSource = interactionSource, + shape = size.toShape(), + colors = colors, + label = { + if (!label.isNullOrEmpty()) { + Text( + text = label, + style = TangemTypography.caption, + color = colors.labelColor( + enabled = enabled, + error = isError, + interactionSource = interactionSource, + ).value, + ) + } + }, + placeholder = { + if (!placeholder.isNullOrEmpty()) { + Text( + text = placeholder, + style = TangemTypography.body1, + color = colors.placeholderColor(enabled = enabled).value, + ) + } + }, + trailingIcon = { + val iconRes by rememberUpdatedState( + newValue = if (isError) R.drawable.ic_alert else R.drawable.ic_close, + ) + IconButton( + modifier = Modifier.size(32.dp), + onClick = onClear, + enabled = !isError && enabled, + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = iconRes), + tint = colors.trailingIconColor(enabled = enabled, isError = isError).value, + contentDescription = "Clear input", + ) + } + }, + ) + + AnimatedVisibility( + visible = !caption.isNullOrEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + if (caption.isNullOrEmpty()) return@AnimatedVisibility + Column { + Spacer(modifier = Modifier.height(4.dp)) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + text = caption, + style = TangemTypography.body1, + color = colors.captionColor(enabled = enabled, isError = isError).value, + ) + } + } + } +} + +private enum class TangemTextFieldSize { + Default +} + +@Composable +private fun TangemTextFieldSize.toHeightDp(): Dp = when (this) { + TangemTextFieldSize.Default -> TangemTheme.dimens.size56 +} + +@Composable +private fun TangemTextFieldSize.toShape(): Shape = when (this) { + TangemTextFieldSize.Default -> TangemTheme.shapes.roundedCornersSmall2 +} + +private object TangemTextFieldsDefault { + val defaultTextFieldColors: TangemTextFieldColors + @Composable @Stable get() = TangemTextFieldColors( + textColor = TangemTheme.colors.text.primary1, + disabledTextColor = TangemTheme.colors.text.disabled, + backgroundColor = Color.Transparent, + cursorColor = TangemTheme.colors.icon.primary1, + errorCursorColor = TangemTheme.colors.icon.warning, + focusedIndicatorColor = TangemTheme.colors.icon.primary1, + unfocusedIndicatorColor = TangemTheme.colors.stroke.primary, + disabledIndicatorColor = TangemTheme.colors.stroke.primary, + errorIndicatorColor = TangemTheme.colors.icon.warning, + leadingIconColor = TangemTheme.colors.icon.informative, + disabledLeadingIconColor = Color.Transparent, + errorLeadingIconColor = TangemTheme.colors.icon.warning, + trailingIconColor = TangemTheme.colors.icon.informative, + disabledTrailingIconColor = Color.Transparent, + errorTrailingIconColor = TangemTheme.colors.icon.warning, + focusedLabelColor = TangemTheme.colors.text.primary1, + unfocusedLabelColor = TangemTheme.colors.text.secondary, + disabledLabelColor = TangemTheme.colors.text.disabled, + errorLabelColor = TangemTheme.colors.icon.warning, + placeholderColor = TangemTheme.colors.text.secondary, + disabledPlaceholderColor = TangemTheme.colors.text.disabled, + captionColor = TangemTheme.colors.text.tertiary, + disabledCaptionColor = TangemTheme.colors.text.disabled, + errorCaptionColor = TangemTheme.colors.icon.warning, + ) +} + +@Immutable +private data class TangemTextFieldColors( + private val textColor: Color, + private val disabledTextColor: Color, + private val cursorColor: Color, + private val errorCursorColor: Color, + private val focusedIndicatorColor: Color, + private val unfocusedIndicatorColor: Color, + private val errorIndicatorColor: Color, + private val disabledIndicatorColor: Color, + private val leadingIconColor: Color, + private val disabledLeadingIconColor: Color, + private val errorLeadingIconColor: Color, + private val trailingIconColor: Color, + private val disabledTrailingIconColor: Color, + private val errorTrailingIconColor: Color, + private val backgroundColor: Color, + private val focusedLabelColor: Color, + private val unfocusedLabelColor: Color, + private val disabledLabelColor: Color, + private val errorLabelColor: Color, + private val placeholderColor: Color, + private val disabledPlaceholderColor: Color, + private val captionColor: Color, + private val disabledCaptionColor: Color, + private val errorCaptionColor: Color, +) : TextFieldColors { + + @Composable + override fun leadingIconColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + when { + !enabled -> disabledLeadingIconColor + isError -> errorLeadingIconColor + else -> leadingIconColor + }, + ) + } + + @Composable + override fun trailingIconColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + when { + !enabled -> disabledTrailingIconColor + isError -> errorTrailingIconColor + else -> trailingIconColor + }, + ) + } + + @Composable + override fun indicatorColor( + enabled: Boolean, + isError: Boolean, + interactionSource: InteractionSource, + ): State { + val focused by interactionSource.collectIsFocusedAsState() + + val targetValue = when { + !enabled -> disabledIndicatorColor + isError -> errorIndicatorColor + focused -> focusedIndicatorColor + else -> unfocusedIndicatorColor + } + return if (enabled) { + animateColorAsState(targetValue, tween(durationMillis = 120)) + } else { + rememberUpdatedState(targetValue) + } + } + + @Composable + override fun backgroundColor(enabled: Boolean): State { + return rememberUpdatedState(backgroundColor) + } + + @Composable + override fun placeholderColor(enabled: Boolean): State { + return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor) + } + + @Composable + override fun labelColor( + enabled: Boolean, + error: Boolean, + interactionSource: InteractionSource, + ): State { + val focused by interactionSource.collectIsFocusedAsState() + + val targetValue = when { + !enabled -> disabledLabelColor + error -> errorLabelColor + focused -> focusedLabelColor + else -> unfocusedLabelColor + } + return rememberUpdatedState(targetValue) + } + + @Composable + override fun textColor(enabled: Boolean): State { + return rememberUpdatedState(if (enabled) textColor else disabledTextColor) + } + + @Composable + override fun cursorColor(isError: Boolean): State { + return rememberUpdatedState(if (isError) errorCursorColor else cursorColor) + } + + @Composable + fun captionColor(enabled: Boolean, isError: Boolean): State { + return rememberUpdatedState( + newValue = when { + !enabled -> disabledCaptionColor + isError -> errorCaptionColor + else -> captionColor + }, + ) + } +} +// endregion Defaults + +// region Preview +@Composable +private fun OutlineTextFieldSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary) + .padding(horizontal = 16.dp), + ) { + val text = TextFieldValue(text = "Input") + OutlineTextField( + value = text, + onValueChange = { /* no-op */ }, + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + OutlineTextField( + value = text, + onValueChange = { /* no-op */ }, + label = "Default", + placeholder = "Placeholder", + caption = "Supporting text", + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + OutlineTextField( + value = text, + onValueChange = { /* no-op */ }, + enabled = false, + label = "Disabled", + placeholder = "Placeholder", + caption = "Supporting text", + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + OutlineTextField( + value = text, + onValueChange = { /* no-op */ }, + isError = true, + label = "Error", + placeholder = "Placeholder", + caption = "Supporting text", + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + OutlineTextField( + value = TextFieldValue(), + onValueChange = { /* no-op */ }, + label = "Default without value", + placeholder = "Placeholder", + caption = "Supporting text", + ) + Divider(modifier = Modifier.padding(vertical = 8.dp)) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun OutlineTextFieldPreview_Light() { + TangemTheme { + OutlineTextFieldSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun OutlineTextFieldPreview_Dark() { + TangemTheme(isDark = true) { + OutlineTextFieldSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt index 800b674c51..de03ea2174 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt @@ -23,6 +23,7 @@ import com.tangem.core.ui.res.iconColor * @see * Figma Component */ +@Deprecated(message = "Use Hand component instead") @Composable fun BottomSheetIndicator() { Box( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt new file mode 100644 index 0000000000..e3ade6def9 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/Hand.kt @@ -0,0 +1,72 @@ +package com.tangem.core.ui.components.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemTheme + +/** + * [Show in Figma](https://www.figma.com/file/17JyRbuUEZ42DluaFEuGQk/Atoms?node-id=135%3A25&t=Jo68S6ilyewVU8MV-4) + * */ +@Composable +fun Hand( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .padding(vertical = TangemTheme.dimens.spacing8) + .fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = Modifier + .width(TangemTheme.dimens.size32) + .height(TangemTheme.dimens.size4) + .background( + color = TangemTheme.colors.icon.inactive, + shape = TangemTheme.shapes.roundedCornersSmall, + ), + ) + } +} + +// region Preview +@Composable +private fun HandSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary) + .padding(TangemTheme.dimens.spacing12), + verticalArrangement = Arrangement.SpaceBetween, + ) { + Hand() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun HandPreview_Light() { + TangemTheme { + HandSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun HandPreview_Dark() { + TangemTheme(isDark = true) { + HandSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt new file mode 100644 index 0000000000..2a7d4c0864 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt @@ -0,0 +1,87 @@ +package com.tangem.core.ui.fragments + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.FloatRange +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.tangem.core.ui.R +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemTheme + +abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment() { + open val initialBottomSheetState = BottomSheetBehavior.STATE_EXPANDED + + @FloatRange(from = 0.0, to = 1.0) + open val expandedHeightFraction: Float? = null + + override fun getTheme(): Int = R.style.AppTheme_TransparentBottomSheetDialog + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return createComposeView(inflater.context) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + (dialog as BottomSheetDialog).behavior.apply { + state = initialBottomSheetState + skipCollapsed = true + } + + return dialog + } + + private fun createComposeView(context: Context): View { + return ComposeView(context).apply { + setContent { + TangemTheme { + val backgroundColor = TangemTheme.colors.background.plain + SystemBarsEffect { + setSystemBarsColor( + color = backgroundColor, + ) + } + + ScreenContent( + modifier = Modifier + .fillMaxWidth() + .let { + if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it + } + .background( + color = backgroundColor, + shape = TangemTheme.shapes.bottomSheet, + ), + state = provideState().value, + ) + } + } + } + } + + @Composable + protected abstract fun provideState(): State + + @Composable + protected abstract fun ScreenContent( + modifier: Modifier, + state: ScreenState, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeFragment.kt new file mode 100644 index 0000000000..863f22f185 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeFragment.kt @@ -0,0 +1,57 @@ +package com.tangem.core.ui.fragments + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemTheme + +abstract class ComposeFragment : Fragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return createComposeView(inflater.context) + } + + private fun createComposeView(context: Context): View { + return ComposeView(context).apply { + setContent { + TangemTheme { + val backgroundColor = TangemTheme.colors.background.primary + SystemBarsEffect { + setSystemBarsColor( + color = backgroundColor, + ) + } + + ScreenContent( + modifier = Modifier + .fillMaxSize() + .background(color = backgroundColor), + state = provideState().value, + ) + } + } + } + } + + @Composable + protected abstract fun provideState(): State + + @Composable + protected abstract fun ScreenContent( + modifier: Modifier, + state: ScreenState, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_alert.xml b/core/ui/src/main/res/drawable/ic_alert.xml new file mode 100644 index 0000000000..1d855289ee --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_alert.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_close.xml b/core/ui/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000000..ffc64fb7a8 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/drawable/ic_plus.xml b/core/ui/src/main/res/drawable/ic_plus.xml new file mode 100644 index 0000000000..b85852c706 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_plus.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/ui/src/main/res/values/styles.xml b/core/ui/src/main/res/values/styles.xml new file mode 100644 index 0000000000..001815837e --- /dev/null +++ b/core/ui/src/main/res/values/styles.xml @@ -0,0 +1,12 @@ + + + + + + + From a0407a2bb80e061c9087e27ad2b2d60736428903 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Nov 2022 11:54:10 +0300 Subject: [PATCH 065/176] Updated on 2026-08-14 --- {app => core/ui}/src/main/res/values-de/strings_final.xml | 0 .../ui}/src/main/res/values-de/strings_plurals_final.xml | 0 {app => core/ui}/src/main/res/values-fr/strings_final.xml | 0 .../ui}/src/main/res/values-fr/strings_plurals_final.xml | 0 {app => core/ui}/src/main/res/values-it/strings_final.xml | 0 .../ui}/src/main/res/values-it/strings_plurals_final.xml | 0 {app => core/ui}/src/main/res/values-ru/strings_final.xml | 2 +- .../ui}/src/main/res/values-ru/strings_plurals_final.xml | 0 {app => core/ui}/src/main/res/values/strings_final.xml | 2 +- {app => core/ui}/src/main/res/values/strings_plurals_final.xml | 0 10 files changed, 2 insertions(+), 2 deletions(-) rename {app => core/ui}/src/main/res/values-de/strings_final.xml (100%) rename {app => core/ui}/src/main/res/values-de/strings_plurals_final.xml (100%) rename {app => core/ui}/src/main/res/values-fr/strings_final.xml (100%) rename {app => core/ui}/src/main/res/values-fr/strings_plurals_final.xml (100%) rename {app => core/ui}/src/main/res/values-it/strings_final.xml (100%) rename {app => core/ui}/src/main/res/values-it/strings_plurals_final.xml (100%) rename {app => core/ui}/src/main/res/values-ru/strings_final.xml (99%) rename {app => core/ui}/src/main/res/values-ru/strings_plurals_final.xml (100%) rename {app => core/ui}/src/main/res/values/strings_final.xml (99%) rename {app => core/ui}/src/main/res/values/strings_plurals_final.xml (100%) diff --git a/app/src/main/res/values-de/strings_final.xml b/core/ui/src/main/res/values-de/strings_final.xml similarity index 100% rename from app/src/main/res/values-de/strings_final.xml rename to core/ui/src/main/res/values-de/strings_final.xml diff --git a/app/src/main/res/values-de/strings_plurals_final.xml b/core/ui/src/main/res/values-de/strings_plurals_final.xml similarity index 100% rename from app/src/main/res/values-de/strings_plurals_final.xml rename to core/ui/src/main/res/values-de/strings_plurals_final.xml diff --git a/app/src/main/res/values-fr/strings_final.xml b/core/ui/src/main/res/values-fr/strings_final.xml similarity index 100% rename from app/src/main/res/values-fr/strings_final.xml rename to core/ui/src/main/res/values-fr/strings_final.xml diff --git a/app/src/main/res/values-fr/strings_plurals_final.xml b/core/ui/src/main/res/values-fr/strings_plurals_final.xml similarity index 100% rename from app/src/main/res/values-fr/strings_plurals_final.xml rename to core/ui/src/main/res/values-fr/strings_plurals_final.xml diff --git a/app/src/main/res/values-it/strings_final.xml b/core/ui/src/main/res/values-it/strings_final.xml similarity index 100% rename from app/src/main/res/values-it/strings_final.xml rename to core/ui/src/main/res/values-it/strings_final.xml diff --git a/app/src/main/res/values-it/strings_plurals_final.xml b/core/ui/src/main/res/values-it/strings_plurals_final.xml similarity index 100% rename from app/src/main/res/values-it/strings_plurals_final.xml rename to core/ui/src/main/res/values-it/strings_plurals_final.xml diff --git a/app/src/main/res/values-ru/strings_final.xml b/core/ui/src/main/res/values-ru/strings_final.xml similarity index 99% rename from app/src/main/res/values-ru/strings_final.xml rename to core/ui/src/main/res/values-ru/strings_final.xml index ad4f23ca35..dc387d3f71 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/core/ui/src/main/res/values-ru/strings_final.xml @@ -5,7 +5,7 @@ 2 карты Доставка Бесплатно - У меня есть промо-код... + У меня есть промо-код… Итого Другие способы оплаты Купить сейчас diff --git a/app/src/main/res/values-ru/strings_plurals_final.xml b/core/ui/src/main/res/values-ru/strings_plurals_final.xml similarity index 100% rename from app/src/main/res/values-ru/strings_plurals_final.xml rename to core/ui/src/main/res/values-ru/strings_plurals_final.xml diff --git a/app/src/main/res/values/strings_final.xml b/core/ui/src/main/res/values/strings_final.xml similarity index 99% rename from app/src/main/res/values/strings_final.xml rename to core/ui/src/main/res/values/strings_final.xml index 70e85346c6..e434fc002d 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/core/ui/src/main/res/values/strings_final.xml @@ -5,7 +5,7 @@ 2 cards Shipping Free - I have a promo code... + I have a promo code… Total Other payment methods Buy now diff --git a/app/src/main/res/values/strings_plurals_final.xml b/core/ui/src/main/res/values/strings_plurals_final.xml similarity index 100% rename from app/src/main/res/values/strings_plurals_final.xml rename to core/ui/src/main/res/values/strings_plurals_final.xml From 8593df1d85bbfa780ec5af3eea13d70e47f398e0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Nov 2022 12:00:34 +0300 Subject: [PATCH 066/176] Updated on 2026-08-14 --- .../com/tangem/tap/common/compose/Button.kt | 18 ++- .../common/compose/ComposeDialogManager.kt | 5 +- .../com/tangem/tap/common/compose/Spacer.kt | 106 ----------------- .../com/tangem/tap/common/compose/Warning.kt | 1 + .../features/home/compose/content/Content.kt | 10 +- .../compose/content/CurrenciesWeb3Content.kt | 14 +-- .../home/compose/views/HomeButtons.kt | 2 +- .../home/compose/views/StoriesProgressBar.kt | 9 +- .../addCustomToken/compose/FormFieldViews.kt | 15 ++- .../com/tangem/core/ui/components/Spacers.kt | 108 +++++++++++++++++- 10 files changed, 160 insertions(+), 128 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/compose/Spacer.kt diff --git a/app/src/main/java/com/tangem/tap/common/compose/Button.kt b/app/src/main/java/com/tangem/tap/common/compose/Button.kt index 690f35f388..b20f7470b8 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/Button.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/Button.kt @@ -1,7 +1,17 @@ package com.tangem.tap.common.compose -import androidx.compose.foundation.layout.* -import androidx.compose.material.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.material.ripple.LocalRippleTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -16,6 +26,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.tangem.core.ui.components.SpacerH8 import com.tangem.tap.common.compose.extensions.stringResourceDefault import com.tangem.wallet.R @@ -137,8 +148,7 @@ fun ToggledRippleTheme( @Preview @Composable fun ButtonTest() { - Scaffold( - ) { + Scaffold { Column(modifier = Modifier.padding(16.dp)) { PreviewItem("Button") { RectangleButton(text = "Some button") {} diff --git a/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt b/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt index fc496f1d9b..99042c3391 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/ComposeDialogManager.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import com.tangem.core.ui.components.SpacerH16 import com.tangem.domain.DomainDialog import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.global.DomainGlobalAction @@ -105,7 +106,7 @@ fun SimpleDialog( modifier = Modifier.padding(22.dp) ) { DialogTitle(title = title) - LazyColumn() { + LazyColumn { items(items) { item -> Row( modifier = Modifier @@ -154,4 +155,4 @@ fun ErrorDialog( } } ) -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt b/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt deleted file mode 100644 index ce0b190598..0000000000 --- a/app/src/main/java/com/tangem/tap/common/compose/Spacer.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.tangem.tap.common.compose - -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -/** -[REDACTED_AUTHOR] - */ -// ***************************** Horizontal -@Composable -fun SpacerH(height: Dp, modifier: Modifier = Modifier) { - Spacer(modifier = modifier.height(height)) -} - -@Composable -fun SpacerH4(modifier: Modifier = Modifier) { - SpacerH(4.dp, modifier) -} - -@Composable -fun SpacerH8(modifier: Modifier = Modifier) { - SpacerH(8.dp, modifier) -} - -@Composable -fun SpacerH16(modifier: Modifier = Modifier) { - SpacerH(16.dp, modifier) -} - -@Composable -fun SpacerH24(modifier: Modifier = Modifier) { - SpacerH(24.dp, modifier) -} - -@Composable -fun SpacerH32(modifier: Modifier = Modifier) { - SpacerH(32.dp, modifier) -} - -// ***************************** Vertical -@Composable -fun SpacerW(width: Dp, modifier: Modifier = Modifier) { - Spacer(modifier = modifier.width(width)) -} - -@Composable -fun SpacerW4(modifier: Modifier = Modifier) { - SpacerW(4.dp, modifier) -} - -@Composable -fun SpacerW8(modifier: Modifier = Modifier) { - SpacerW(8.dp, modifier) -} - -@Composable -fun SpacerW16(modifier: Modifier = Modifier) { - SpacerW(16.dp, modifier) -} - -@Composable -fun SpacerW24(modifier: Modifier = Modifier) { - SpacerW(24.dp, modifier) -} - -@Composable -fun SpacerW32(modifier: Modifier = Modifier) { - SpacerW(32.dp, modifier) -} - -// ***************************** Size -@Composable -fun SpacerS(size: Dp, modifier: Modifier = Modifier) { - Spacer(modifier = modifier.size(size)) -} - -@Composable -fun SpacerS4(modifier: Modifier = Modifier) { - SpacerS(4.dp, modifier) -} - -@Composable -fun SpacerS8(modifier: Modifier = Modifier) { - SpacerS(8.dp, modifier) -} - -@Composable -fun SpacerS16(modifier: Modifier = Modifier) { - SpacerS(16.dp, modifier) -} - -@Composable -fun SpacerS24(modifier: Modifier = Modifier) { - SpacerS(24.dp, modifier) -} - -@Composable -fun SpacerS32(modifier: Modifier = Modifier) { - SpacerS(32.dp, modifier) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/compose/Warning.kt b/app/src/main/java/com/tangem/tap/common/compose/Warning.kt index 07c82d1384..0d19a8e2ea 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/Warning.kt +++ b/app/src/main/java/com/tangem/tap/common/compose/Warning.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tangem.common.module.ModuleMessage +import com.tangem.core.ui.components.SpacerH8 import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter import com.tangem.wallet.R diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt index 9ee80263c3..e8a6b447f7 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt @@ -2,7 +2,11 @@ package com.tangem.tap.features.home.compose.content import androidx.annotation.DrawableRes import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -20,8 +24,8 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.tangem.tap.common.compose.SpacerH16 -import com.tangem.tap.common.compose.SpacerH32 +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH32 import com.tangem.tap.features.home.compose.StoriesBottomImageAnimation import com.tangem.tap.features.home.compose.StoriesTextAnimation import com.tangem.wallet.R diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt index 198e71d324..85ec581b07 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/CurrenciesWeb3Content.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import com.tangem.tap.common.compose.SpacerH +import com.tangem.core.ui.components.SpacerH12 import com.tangem.tap.common.compose.extensions.dpSize import com.tangem.tap.common.compose.extensions.halfHeight import com.tangem.tap.common.compose.extensions.toPx @@ -64,7 +64,7 @@ fun StoriesCurrenciesContent( targetOffset = animateTo.toPx(), contentDescription = "Currency row", ) - SpacerH(12.dp) + SpacerH12() } } } @@ -118,7 +118,7 @@ fun StoriesWeb3Content( @Composable private fun LightenBox(content: @Composable () -> Unit) { - Box() { + Box { content() Box( modifier = Modifier @@ -130,10 +130,10 @@ private fun LightenBox(content: @Composable () -> Unit) { Color.White.copy(alpha = 0f), Color.White.copy(alpha = 0.75f), Color.White.copy(alpha = 0.95f), - Color.White - ) - ) - ) + Color.White, + ), + ), + ), ) {} } } diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index cb40186a87..1e3cc818b4 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.tangem.tap.common.compose.SpacerS8 +import com.tangem.core.ui.components.SpacerS8 import com.tangem.wallet.R @Composable diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt index 096b87cb0b..3779f57358 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt @@ -4,7 +4,12 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -13,7 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.tap.common.compose.SpacerW4 +import com.tangem.core.ui.components.SpacerW4 @Composable fun StoriesProgressBar( diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt index fd792714be..bc4c0ae9c6 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/FormFieldViews.kt @@ -6,14 +6,25 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.SpacerH8 import com.tangem.domain.common.form.Field import com.tangem.domain.features.addCustomToken.TokenBlockchainField import com.tangem.domain.features.addCustomToken.TokenDerivationPathField import com.tangem.domain.features.addCustomToken.TokenField -import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.* +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenContractAddressChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDecimalsChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenDerivationPathChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNameChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenNetworkChanged +import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction.OnTokenSymbolChanged import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState import com.tangem.domain.redux.domainStore -import com.tangem.tap.common.compose.* +import com.tangem.tap.common.compose.BlockchainSpinner +import com.tangem.tap.common.compose.ClearButton +import com.tangem.tap.common.compose.ClosePopupTrigger +import com.tangem.tap.common.compose.OutlinedTextFieldWidget +import com.tangem.tap.common.compose.PasteButton +import com.tangem.tap.common.compose.TitleSubtitle import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.wallet.R diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt index 60ce6bf8bc..8293073012 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -1,12 +1,17 @@ +@file:Suppress("unused") + package com.tangem.core.ui.components import androidx.annotation.DimenRes import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp @Composable fun VerticalSpacer(@DimenRes spaceResId: Int) { @@ -16,4 +21,105 @@ fun VerticalSpacer(@DimenRes spaceResId: Int) { @Composable fun HorizontalSpacer(@DimenRes spaceResId: Int) { Spacer(modifier = Modifier.width(dimensionResource(id = spaceResId))) -} \ No newline at end of file +} + +// region Horizontal +@Composable +fun SpacerH(height: Dp, modifier: Modifier = Modifier) { + Spacer(modifier = modifier.height(height)) +} + +@Composable +fun SpacerH4(modifier: Modifier = Modifier) { + SpacerH(4.dp, modifier) +} + +@Composable +fun SpacerH8(modifier: Modifier = Modifier) { + SpacerH(8.dp, modifier) +} + +@Composable +fun SpacerH12(modifier: Modifier = Modifier) { + SpacerH(12.dp, modifier) +} + +@Composable +fun SpacerH16(modifier: Modifier = Modifier) { + SpacerH(16.dp, modifier) +} + +@Composable +fun SpacerH24(modifier: Modifier = Modifier) { + SpacerH(24.dp, modifier) +} + +@Composable +fun SpacerH32(modifier: Modifier = Modifier) { + SpacerH(32.dp, modifier) +} +// endregion Horizontal + +// region Vertical +@Composable +fun SpacerW(width: Dp, modifier: Modifier = Modifier) { + Spacer(modifier = modifier.width(width)) +} + +@Composable +fun SpacerW4(modifier: Modifier = Modifier) { + SpacerW(4.dp, modifier) +} + +@Composable +fun SpacerW8(modifier: Modifier = Modifier) { + SpacerW(8.dp, modifier) +} + +@Composable +fun SpacerW16(modifier: Modifier = Modifier) { + SpacerW(16.dp, modifier) +} + +@Composable +fun SpacerW24(modifier: Modifier = Modifier) { + SpacerW(24.dp, modifier) +} + +@Composable +fun SpacerW32(modifier: Modifier = Modifier) { + SpacerW(32.dp, modifier) +} +// endregion Vertical + +// region Size +@Composable +fun SpacerS(size: Dp, modifier: Modifier = Modifier) { + Spacer(modifier = modifier.size(size)) +} + +@Composable +fun SpacerS4(modifier: Modifier = Modifier) { + SpacerS(4.dp, modifier) +} + +@Composable +fun SpacerS8(modifier: Modifier = Modifier) { + SpacerS(8.dp, modifier) +} + +@Composable +fun SpacerS16(modifier: Modifier = Modifier) { + SpacerS(16.dp, modifier) +} + +@Composable +fun SpacerS24(modifier: Modifier = Modifier) { + SpacerS(24.dp, modifier) +} + +@Composable +fun SpacerS32(modifier: Modifier = Modifier) { + SpacerS(32.dp, modifier) +} +// endregion Size \ No newline at end of file From 90bfea9e4cee859a9c595ed47f1478dd04290dae Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 24 Nov 2022 16:21:05 +0300 Subject: [PATCH 067/176] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 12 ----------- .../tap/common/redux/global/GlobalAction.kt | 3 --- .../tap/common/redux/global/GlobalReducer.kt | 20 ++++++++----------- .../tap/common/redux/global/GlobalState.kt | 9 --------- .../details/redux/DetailsMiddleware.kt | 19 ++---------------- .../features/wallet/models/WalletWarning.kt | 2 +- .../tap/features/wallet/redux/WalletState.kt | 2 +- .../wallet/ui/WalletWarningConverter.kt | 7 +++++-- app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 11 +++++----- app/src/main/res/values-ru/strings.xml | 19 +++++++++--------- app/src/main/res/values/strings.xml | 13 ++++++------ .../main/res/values/strings_untranslated.xml | 18 ++++------------- 14 files changed, 41 insertions(+), 96 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index aa9e63c2dc..ad1892a9a5 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -19,8 +19,6 @@ import com.tangem.tap.common.IntentHandler import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.redux.NotificationsHandler -import com.tangem.tap.common.redux.global.AndroidResources -import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.shop.GooglePayService @@ -70,7 +68,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac tangemSdkManager = TangemSdkManager(tangemSdk, this) backupService = BackupService.init(tangemSdk, this) - store.dispatch(GlobalAction.SetResources(getAndroidResources())) store.dispatch( ShopAction.CheckIfGooglePayAvailable( GooglePayService(createPaymentsClient(this), this), @@ -78,15 +75,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ) } - private fun getAndroidResources(): AndroidResources { - return AndroidResources( - AndroidResources.RString( - R.string.copy_toast_msg, - R.string.details_notification_erase_wallet_not_possible, - ), - ) - } - private fun systemActions() { WindowCompat.setDecorFitsSystemWindows(window, false) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index c1e6d85f11..d4c5620fb3 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -29,9 +29,6 @@ sealed class GlobalAction : Action { data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction - // android resources - data class SetResources(val resources: AndroidResources) : GlobalAction() - // dialogs data class ShowDialog(val stateDialog: StateDialog) : GlobalAction() object HideDialog : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 820506ab18..8c04197c58 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -14,9 +14,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState { val globalState = state.globalState return when (action) { - is GlobalAction.SetResources -> { - globalState.copy(resources = action.resources) - } is GlobalAction.Onboarding.Start -> { val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage val onboardingManager = OnboardingManager(action.scanResponse, usedCardsPrefStorage) @@ -34,7 +31,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.ScanFailsCounter.Reset -> { globalState.copy(scanCardFailsCounter = 0) } - is GlobalAction.SaveScanNoteResponse ->{ + is GlobalAction.SaveScanNoteResponse -> { domainStore.dispatch(DomainGlobalAction.SaveScanNoteResponse(action.scanResponse)) globalState.copy(scanResponse = action.scanResponse) } @@ -51,11 +48,12 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.UpdateWalletSignedHashes -> { val card = globalState.scanResponse?.card ?: return globalState val wallet = card.wallet(action.walletPublicKey) ?: return globalState - - val newCardInstance = card.updateWallet(wallet.copy( - totalSignedHashes = action.walletSignedHashes, - remainingSignatures = action.remainingSignatures - )) + val newCardInstance = card.updateWallet( + wallet.copy( + totalSignedHashes = action.walletSignedHashes, + remainingSignatures = action.remainingSignatures, + ), + ) globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance)) } is GlobalAction.SetFeedbackManager -> { @@ -72,9 +70,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState { } is GlobalAction.SetIfCardVerifiedOnline -> globalState.copy(cardVerifiedOnline = action.verified) - is GlobalAction.FetchUserCountry.Success -> globalState.copy( - userCountryCode = action.countryCode - ) + is GlobalAction.FetchUserCountry.Success -> globalState.copy(userCountryCode = action.countryCode) else -> globalState } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 11c84ed856..b897f1b5dc 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -25,18 +25,9 @@ data class GlobalState( val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, val exchangeManager: CurrencyExchangeManager = CurrencyExchangeManager.dummy(), - val resources: AndroidResources = AndroidResources(), val userCountryCode: String? = null, ) : StateType -data class AndroidResources( - val strings: RString = RString(), -) { - data class RString( - val addressWasCopied: Int = -1, - val walletIsNotEmpty: Int = -1, - ) -} typealias CryptoCurrencyName = String data class OnboardingState( diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index b316098ff4..88191c0ee4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -7,7 +7,6 @@ import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState @@ -18,7 +17,6 @@ import com.tangem.tap.domain.extensions.getUserWalletId import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction -import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransactions import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -56,21 +54,8 @@ class DetailsMiddleware { } } is DetailsAction.ReCreateTwinsWallet -> { - val wallet = - store.state.walletState.walletManagers.map { it.wallet }.firstOrNull() - if (wallet == null) { - store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) - store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) - } else { - if (wallet.hasSendableAmountsOrPendingTransactions()) { - val walletIsNotEmpty = - store.state.globalState.resources.strings.walletIsNotEmpty - store.dispatchNotification(walletIsNotEmpty) - } else { - store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) - store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) - } - } + store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) + store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) } is DetailsAction.CreateBackup -> { store.state.detailsState.scanResponse?.let { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt index 3b80a95845..e2709d2bb9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt @@ -8,7 +8,7 @@ sealed class WalletWarning( val edStringValueWithSymbol: String, ) : WalletWarning(1) - object TransactionInProgress : WalletWarning(10) + data class TransactionInProgress(val currencyName: String) : WalletWarning(10) data class BalanceNotEnoughForFee(val blockchainFullName: String) : WalletWarning(30) data class Rent(val walletRent: WalletRent) : WalletWarning(40) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 6219fea552..dedbd21a08 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -350,7 +350,7 @@ data class WalletData( private fun assembleNonTypedWarnings(walletWarnings: MutableList) { if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) { - walletWarnings.add(WalletWarning.TransactionInProgress) + walletWarnings.add(WalletWarning.TransactionInProgress(currency.currencyName)) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt index 624d34da3e..799d029235 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt @@ -27,8 +27,11 @@ class WalletWarningConverter( message.blockchainFullName, message.blockchainFullName, ) } - WalletWarning.TransactionInProgress -> { - context.getString(R.string.wallet_pending_transaction_warning) + is WalletWarning.TransactionInProgress -> { + context.getString( + R.string.token_details_send_blocked_tx_format, + message.currencyName + ) } is WalletWarning.Rent -> { context.getString( diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index cf8a46c64c..4bdd650d4b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -174,7 +174,6 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS Legen Sie die Karte zum Scannen an Legen Sie die Karte an - Ihr Kontostand in dieser Brieftasche ist nicht Null, oder Sie haben unbestätigte Transaktionen. Es ist unmöglich die Funktion der Brieftasche zu löschen. Dies ist die derzeit aktive Wahl diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d39650f3b7..2e751ab41c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -166,7 +166,6 @@ Avertissement Saisissez d\'abord le PayString requis Posez pour scanner Posez la carte - Votre solde sur ce portefeuille n\'est pas nul, ou vous avez des transactions non confirmées. Impossible de supprimer la fonction du portefeuille Cette option est déjà active diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 7eba948ed7..1eecc8a9d4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -28,7 +28,7 @@ Il saldo sta per essere caricato… Blockchain non raggiungibile Conto non creato - Carica %1$s+ %2$s per creare un conto + Carica %s+ %s per creare un conto Carta vuota Crea un portafoglio per iniziare ad utilizzare la tua carta Tangem Questa carta non è supportata @@ -63,10 +63,10 @@ Normale Prioritario Includi commissione - Sarà inviato %1$s %2$s - ≈ %1$s (inc. commissione: %2$s) - Sarà inviato %1$s %2$s e %1$s %2$s - Saldo: %1$s %2$s + Sarà inviato %s %s + ≈ %s (inc. commissione: %s) + Sarà inviato %s %s e %s %s + Saldo: %s %s La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo Requisiti ID carta @@ -174,7 +174,6 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020. Avvicina per scansionare Avvicina la carta - Il tuo saldo su questo portafoglio non è pari a zero o hai transazioni non confermate. Non è possibile eliminare la funzione portafoglio. E\' l\'opzione attualmente attiva diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 41ae2c46a7..bded5827f5 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -30,7 +30,7 @@ Загрузка баланса… Блокчейн недоступен Аккаунт не создан - Загрузите %1$s+ %2$s для создания учетной записи + Загрузите %s+ %s для создания учетной записи Карта пуста Создайте кошелек, чтобы начать пользоваться картой Tangem Эта карта не поддерживается @@ -64,10 +64,10 @@ Нормальный Приоритетный Включить коммисию - %1$s %2$s будет отправлено - ≈ %1$s (включая комиссию: %2$s) - %1$s %2$s и %3$s %4$s будут отправлены - Баланс: %1$s %2$s + %s %s будет отправлено + ≈ %s (включая комиссию: %s) + %s %s и %s %s будут отправлены + Баланс: %s %s Транзакция успешно подписана и отправлена в блокчейн. Баланс кошелька будет обновлен через некоторое время. Подробности ID карты @@ -114,7 +114,6 @@ Сначала введите желаемую PayString Приложите, чтобы отсканировать Приложите карту - Ваш баланс на этом кошельке не равен нулю, или у Вас есть неподтвержденные транзакции Это текущая активная опция Ошибка сканирования карты. Попробуй снова. Купить @@ -206,7 +205,7 @@ Копировать Поделиться + Добавить токены - Пожалуйста, дождитесь завершения транзакции, чтобы иметь возможность отправить средства + Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства Проверка подлинности не удалась Эта карта может быть производственным образцом или подделкой Важная информация о безопасности \u26A0 @@ -319,11 +318,11 @@ Сброс до заводских настроек Код доступа доступен только для карт с резервной копией Сеть Solana взымает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы использовать его бесплатно. - %1s (%2s) + %s (%s) Эта карта не является векселем на предъявителя. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец скрывает автономную подпись, что является проблемой безопасности.\n\nНе принимайте эту карту в качестве физического платежа от кого-то, кому Вы не доверяете.\n\nВо всех остальных отношениях - это совершенно безопасно.\n\nTangem — единственный аппаратный кошелек, предлагающий защиту от подсчета подписей. WalletConnect - Запрос на создание транзакции для %1s\n%2s\n\nСумма: %3s\nКомиссия: %4s\nВсего: %5s\nБаланс: %6s - Запрос на запуск сеанса для %1s\n\nURL: %2s + Запрос на создание транзакции для %s\n%s\n\nСумма: %s\nКомиссия: %s\nВсего: %s\nБаланс: %s + Запрос на запуск сеанса для %s\n\nURL: %s Сессии WalletConnect Сеанс WalletConnect открыт с %s Упс. Нет сессий. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c1ebffd92..b4476d5d6e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,7 +36,7 @@ Blockchain is unreachable Account is not created - Load %1$s+ %2$s to create account + Load %s+ %s to create account Empty card Create wallet to start using Tangem card This card is not supported @@ -74,10 +74,10 @@ Normal Priority Include fee - %1$s %2$s will be sent - ≈ %1$s (inc. fee: %2$s) - %1$s %2$s and %3$s %4$s will be sent - Balance: %1$s %2$s + %s %s will be sent + ≈ %s (inc. fee: %s) + %s %s and %s %s will be sent + Balance: %s %s Transaction has been successfully signed and sent to blockchain node. Wallet balance will be updated in a while Details @@ -168,7 +168,7 @@ \nThis disclaimer was amended for the last time on October 1st, 2020.
Terms of service - No internet connection + No internet connection PayString verification failed PayString unsupported by blockchain @@ -185,6 +185,5 @@ Tap to scan Tap the card - Your balance on this wallet is not zero, or you have unconfirmed transactions This is the currently active option diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 153f80c666..ee8cbaff9d 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -105,7 +105,7 @@ Copy Share + Add tokens - Please wait for the transaction to complete to be able to send funds + Please wait for %s transaction to complete to be able to send funds Authenticity check failed This card might be a production sample or counterfeit @@ -132,21 +132,11 @@ Card: %s Tap card to bind to wallet connect - - Request to create transaction for %1s - \n%2s - - \n\nAmount: %3s - \nFee: %4s - \nTotal: %5s - \nBalance: %6s + Request to create transaction for %s\n%s\n\nAmount: %s\nFee: %s\nTotal: %s\nBalance: %s Can\'t send transaction. Not enough funds. - Request to start a session for %1s\n\n - - URL: %2s - + Request to start a session for %s\n\nURL: %s Requesting to sign a message\n Message for %s:\n%s @@ -309,7 +299,7 @@ Amount to pay: %s
Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. - %1s (%2s) + %s (%s) Type here to search My suggestions From 1836e85084e0354466a02d9c5a63d3be163600dd Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 24 Nov 2022 16:21:35 +0300 Subject: [PATCH 068/176] Updated on 2026-08-14 --- .../features/wallet/ui/WalletDetailsFragment.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 9d3dfca291..f40bf97ea5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -191,10 +191,16 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), private fun setupCurrency(currencyData: BalanceWidgetData, currency: Currency) = with(binding) { tvCurrencyTitle.text = currencyData.currency - tvCurrencySubtitle.text = tvCurrencySubtitle.getString( - R.string.wallet_currency_subtitle, - currency.blockchain.fullName, - ) + + if (currency is Currency.Token) { + tvCurrencySubtitle.text = tvCurrencySubtitle.getString( + R.string.wallet_currency_subtitle, + currency.blockchain.fullName, + ) + tvCurrencySubtitle.show() + } else { + tvCurrencySubtitle.hide() + } } private fun setupButtons(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) = with(binding) { From 2b2a8d2f08a3618af54b01548b58f43265fbe886 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 24 Nov 2022 21:45:39 +0300 Subject: [PATCH 069/176] Updated on 2026-08-14 --- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 22 +++++++++---------- .../main/res/values-ru/strings_blockchain.xml | 6 ++--- app/src/main/res/values/strings.xml | 15 ++++++------- .../main/res/values/strings_blockchain.xml | 6 ++--- .../main/res/values/strings_untranslated.xml | 5 ++--- 6 files changed, 26 insertions(+), 30 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 1eecc8a9d4..b9b5539110 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -67,7 +67,7 @@ ≈ %s (inc. commissione: %s) Sarà inviato %s %s e %s %s Saldo: %s %s - La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo + La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo Requisiti ID carta Emittente diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index bded5827f5..506d97136b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -8,7 +8,6 @@ Отмена Принять Удалить - %s Доступ к камере запрещен Вы не предоставили доступ к своей камере, измените настройки конфиденциальности Настройки @@ -68,7 +67,7 @@ ≈ %s (включая комиссию: %s) %s %s и %s %s будут отправлены Баланс: %s %s - Транзакция успешно подписана и отправлена в блокчейн. Баланс кошелька будет обновлен через некоторое время. + Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время Подробности ID карты Эмитент @@ -84,23 +83,23 @@ Если вы забудете код, Вы потеряете возможность использовать карту. Невозможно восстановить или изменить код, если вы его потеряете. Управление безопасностью Длительное удержание - Этот механизм защищает карту от бесконтактной атаки. Это обеспечит задержку между приемом и выполнением команды. После первой подписанной транзакции этот телефон будет привязан к карте, и транзакции будут подписаны без задержки. + Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. Пароль Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль Код доступа Перед сканированием карты вам нужно будет ввести правильный код доступа У Вас могут возникнуть проблемы с NFC на некоторых iPhone 7/7+ во время извлечения - Эта карта уже пополнялась и подписывала транзакции в прошлом. Рассмотрите возможность немедленного снятия всех средств, если Вы получили эту карту из ненадежного источника. + Эта карта ранее пополнялась и подписывала транзакции. Выведите средства как можно быстрее, если вы получили эту карту из ненадежного источника. Если это ваша карта, то не о чем беспокоиться. Предупреждение Эта карта не предназначена для работы с этим приложением Карта, которую Вы отсканировали, является картой для разработчиков. Не принимайте ее в качестве оплаты. Карты Tangem, выпущенные до сентября 2019 года, в настоящее время не могут быть извлечены с помощью iPhone. Мы усердно работаем с Apple, чтобы сделать это возможным в будущих версиях iOS. Нажмите, чтобы подписать - Приложите карту к телефону, как показано выше - Приложите, чтобы удалить кошелек + Чтобы подписать транзакцию, соедините телефон и карту в точности, как показано выше. + Чтобы сбросить настройки до заводских, соедините телефон и карту в точности, как показано выше. Приложите, чтобы создать кошелек - Приложите, чтобы изменить код доступа - Приложите, чтобы изменить пароль + Чтобы изменить код доступа, соедините телефон и карту в точности, как показано выше. + Чтобы изменить пароль, соедините телефон и карту в точности, как показано выше. Неверный PayString Условия использования Нет соединения с интернетом @@ -127,7 +126,7 @@ Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты Tangem Twin Один кошелек. Две карты. - Одна, которую Вы держите в руках, и вторая карта с номером #%s + Одна, которую Вы держите в руках, и вторая карта с номером #%s. Пересоздать Twin кошелек Условия использования карты Tangem Twin @@ -135,7 +134,6 @@ Приложите Twin карту #%s Подготовка карты Создание кошелька - Процедура создания кошелька состоит из трех шагов. Вы должны пройти его до конца, иначе вам придется начинать сначала. Сканировать карту #%s Вы уже начали процесс пересоздания кошелька. Если Вы прервете его, вы не сможете использовать свои карты, пока не запустите его снова и не завершите пересоздание кошелька. Вы приложили ту же карту. Чтобы создать кошелек, вам нужно приложить другу Twin карту. @@ -230,7 +228,7 @@ Давайте сгенерируем все ключи на вашей карте и создадим безопасный кошелек Создать кошелек Пополните свой кошелек - Чтобы начать, просто пополните карту на любую сумму + Пополните кошелек на любую сумму, чтобы начать пользоваться картой Чтобы начать, просто пополните кошелек более чем на %s %s Купить криптовалюту Показать адрес кошелька @@ -263,7 +261,7 @@ Добавлены две резервные карты Резервная карта #%d Вы можете привязать до трех карт к одному кошельку - Чтобы начать процесс резервного копирования, добавьте еще максимум 2 карты + Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. Подготовьте основную карту с номером %s Подготовьте основную карту Вы можете добавить еще одну карту или завершить процесс резервного копирования diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml index 9ccdfd276e..5baeca3d33 100644 --- a/app/src/main/res/values-ru/strings_blockchain.xml +++ b/app/src/main/res/values-ru/strings_blockchain.xml @@ -11,9 +11,9 @@ Неизвестная ошибка Неверный адрес Недопустимая сумма - Сумма превышает баланс - Общая сумма превышает баланс - Комиссия превышает баланс + Сумма превышает остаток + Отправляемая сумма превышает остаток + Комиссия превышает остаток Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ Уменьшить на %s XTZ Нет, отправить все diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b4476d5d6e..25a90b2276 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,7 +9,6 @@ Cancel Accept Delete - %s Camera access denied You have not given access to your camera, please adjust your privacy settings @@ -78,7 +77,7 @@ ≈ %s (inc. fee: %s) %s %s and %s %s will be sent Balance: %s %s - Transaction has been successfully signed and sent to blockchain node. Wallet balance will be updated in a while + Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while Details Card ID @@ -95,25 +94,25 @@ If you forget the code, you will lose the ability to use the card. There is no way to recover or change your code if you lose it. Manage Security Long Tap - This mechanism protects against proximity attack on a card. It will enforce a delay between reception and execution of a command. After the first signed transaction this phone will be linked with the card and transactions will be signed immediately. + This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. Passcode Before execution any command entailing a change of the card state you will have to enter the passcode. Access code You will have to submit the correct access code before scanning the card You may experience NFC problems with some iPhone 7/7+ during the extraction - This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. + This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about. Warning This card is not designed to work with this app The card you scanned is a development card. Don\'t accept it as a payment Tangem cards manufactured before September 2019 cannot currently be extracted with an iPhone. We\'re working hard with Apple to make it possible in future versions of iOS. Tap to sign - Tap the card to your phone as shown above - Tap to erase wallet + To sign, connect your phone and the card exactly as it shown above + To reset to factory settings, connect your phone and the card exactly as it shown above Tap to create wallet - Tap to change the access code - Tap to change the passcode + To change the access code, connect your phone and the card exactly as it shown above + To change the passcode, connect your phone and the card exactly as it shown above Invalid PayString diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml index 8014c2a199..f19a487b14 100644 --- a/app/src/main/res/values/strings_blockchain.xml +++ b/app/src/main/res/values/strings_blockchain.xml @@ -11,9 +11,9 @@ Unknown error Invalid address Invalid amount - Amount Exceeds Balance - Total Exceeds Balance - Fee Exceeds Balance + Amount exceeds balance + Total amount exceeds balance + Fee exceeds balance To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ Reduce by %s XTZ No, send all diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index ee8cbaff9d..676c8cc848 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -24,7 +24,6 @@ Tap the #%s twin card Preparing card Creating wallet - The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. Scan the card #%s You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet. You\'ve tapped the same card. To create twin wallet you need to tap a second twin card. @@ -176,7 +175,7 @@ Create wallet Top up your wallet - To get started, simply top up the card with any amount + To get started, simply top up the wallet with any amount To get started, simply top up the wallet with more than %s %s. Buy crypto Show the wallet\'s address @@ -218,7 +217,7 @@ Backup card #%d You can backup your keys up to two other blank wallet cards - To start the backup process add up to two cards + To start the backup process add up to two backup cards. Prepare the primary card with number %s Prepare the primary card You can add one more card or finalize the backup process From 7fcb3a8525d1a554c255546ea7f6e64193158568 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Nov 2022 14:59:19 +0300 Subject: [PATCH 070/176] Updated on 2026-08-14 --- app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 506d97136b..2e55d1156d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -8,6 +8,7 @@ Отмена Принять Удалить + %s Доступ к камере запрещен Вы не предоставили доступ к своей камере, измените настройки конфиденциальности Настройки diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 25a90b2276..6ea8e27b1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ Cancel Accept Delete + %s Camera access denied You have not given access to your camera, please adjust your privacy settings From 06afe233fbbea4ec88417774b8962fa2a0515f9a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Nov 2022 14:18:46 +0300 Subject: [PATCH 071/176] Updated on 2026-08-14 --- app/src/main/res/values-de/strings.xml | 30 ------ .../main/res/values-de/strings_blockchain.xml | 1 - app/src/main/res/values-fr/strings.xml | 30 ------ .../main/res/values-fr/strings_blockchain.xml | 1 - app/src/main/res/values-it/strings.xml | 30 ------ .../main/res/values-it/strings_blockchain.xml | 3 +- app/src/main/res/values-ru/strings.xml | 93 +------------------ .../main/res/values-ru/strings_blockchain.xml | 3 +- app/src/main/res/values/strings.xml | 30 ------ .../main/res/values/strings_blockchain.xml | 3 +- .../main/res/values/strings_untranslated.xml | 63 +------------ .../src/main/res/values-de/strings_final.xml | 14 --- .../src/main/res/values-fr/strings_final.xml | 14 --- .../src/main/res/values-it/strings_final.xml | 14 --- .../src/main/res/values-ru/strings_final.xml | 14 --- core/ui/src/main/res/values/strings_final.xml | 16 +--- .../feature/referral/ui/AgreementText.kt | 6 +- .../referral/ui/NonParticipateBottomBlock.kt | 2 +- .../referral/ui/ParticipateBottomBlock.kt | 2 +- .../src/main/res/values-ru/string.xml | 6 +- .../src/main/res/values/string.xml | 6 +- 21 files changed, 17 insertions(+), 364 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4bdd650d4b..967b2ab32f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -8,22 +8,11 @@ Akzeptieren Entfernen - Der Kamerazugriff wurde verweigert Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an - Einstellungen - Willkommen bei Tangem. Haben Sie eine dieser Karten? - Willkommen bei Tangem. Scannen Sie Ihre Karten um zu starten. - Ja! Bitte scannen - Einkaufen - Halten Sie die \ncard hinter dem \nphone bereit und scannen Sie sie. - Eintippen Karte scannen - Scannen Absenden ein Wallet erstellen Tangem - eine Walletoption wählen - Token Tokens Verifizierte Bilanz Die Bilanz wird aufgeladen… @@ -41,14 +30,10 @@ von %s Adresse erkunden PayString erstellen - %s Wallet PayString erstellen - Karte: %s PayString Name $payid.tangem.com - Erstellen PayString sind Ihre einzigartigen Identifikationsdaten wie Telefonnummer, E-Mail-Adresse oder ABN. - Ihre PayString wurde erfolgreich erstellt und zum Clipboard kopiert Fehlerantwort beim Erstellen der PayString Diese PayString existiert bereits. Versuchen Sie mit einer anderen erneut. Adresse oder PayString @@ -64,7 +49,6 @@ Priorität inkl. Gebühr %s %s wird gesendet - ≈ %s (inkl. Gebühr: %s) %s %s und %s %s werden gesendet Bilanz: %s %s Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert @@ -74,34 +58,22 @@ Signiert %s Hasch App Währung - Einstellungen - Karte - Karte validieren - Sicherheit verwalteny - Wallet löschen Dieser Vorgang ist irriversibel. Wenn jemand nach dem Löschen des Wallets Geld überweist, können Sie es nicht mehr abheben. - Sollten Sie den Code verloren haben, haben Sie keinen Zugriff auf die Karte. Sie können Ihren Code nicht wiederherstellen und nicht ändern, wenn Sie ihn verlieren. - Sicherheit verwalten Langes Tippen Dieser Mechanismus schützt vor Annäherungsangriffen auf eine Karte. Es wird eine Verzögerung zwischen dem Empfang und der Ausführung eines Befehls erzwungen. Nach der ersten signierten Transaktion wird dieses Telefon mit der Karte verknüpft und die Transaktionen werden sofort signiert Passcode Bevor Sie einen Befehl ausführen, der eine Änderung des Kartenstatus zur Folge hat, müssen Sie den Passcode eingeben. Zugangscode Sie müssen den richtigen Zugangscode eingeben, bevor Sie die Karte scannen. - Sie können NFC-Probleme mit einigen iPhone 7/7+ während der Extraktion haben Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben. Warnung Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel - Die vor September 2019 ausgestellten Karten können mit iPhone momentan nicht extrahiert werden. Wir arbeiten eng mit Apple zusammen, damit es in den nächsten Versionen von iOS möglich wäre. Tippen um zu signieren - Scannen Sie die Karte mit Ihrem Handy wie oben gezeigt - Tippen Sie um das Wallet zu löschen Tippen Sie um ein Wallet zu erstellen Tippen Sie um den Zugangscode zu ändern Tippen Sie um den Passcode zu ändern - Ungültige PayString @@ -159,7 +131,6 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS Nutzungsbedingungen Keine Internetverbindung - Verifikation der PayString ist fehlgeschlagen PayString wird von der Blockchain nicht unterstützt PayString ist nicht registriert PayString-Anfrage ist fehlgeschlagen @@ -174,6 +145,5 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS Legen Sie die Karte zum Scannen an Legen Sie die Karte an - Dies ist die derzeit aktive Wahl diff --git a/app/src/main/res/values-de/strings_blockchain.xml b/app/src/main/res/values-de/strings_blockchain.xml index 66998f875e..8b9715a9e6 100644 --- a/app/src/main/res/values-de/strings_blockchain.xml +++ b/app/src/main/res/values-de/strings_blockchain.xml @@ -6,7 +6,6 @@ Minimaler Betrag ist %s Restbestand zu klein Das Zielkonto ist nicht erstellt. Der abzusendende Betrag soll %s + Gebühr oder mehr sein - Um ein Konto zu erstellen, senden Sie 1+ XLM an diese Adresse Erhalt der Gebühr fehlgeschlagen Unbekannter Fehler Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 2e751ab41c..0694d102a5 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -9,22 +9,11 @@ J\'accepte Supprimer - L\'accès à la caméra est refusé Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité - Paramètres - Bienvenue à Tangem. Avez-vous une de nos cartes - Encore une fois, bienvenue dans Tangem. \nScannez votre carte pour commencer. - Oui! Scannez-la. - Magasin - Préparez-vous à appuyer votre carte au dos de votre téléphone. - Appuyez Scannez la carte - Scanner Envoyer Créer un portefeuille Tangem - Choisir une variante de portefeuille - Jeton d\'authentification Jetons d\'authentification Solde confirmé Solde est en cours de téléchargement … @@ -42,14 +31,10 @@ de %s Explorer l\'adresse Créer PayString - Portefeuille %s Créer PayString - Carte: %s Dénomination PayString $payid.tangem.com - Créer Votre PayString est une information qui vous est propre, comme un numéro de téléphone, une adresse du courrier électronique ou un ABN. - PayString a été créé avec succès et copié dans le presse-papiers Mauvaise réponse lors de la création de PayString Ce PayString existe déjà. Essayez une autre variante. Adresse ou PayString @@ -65,7 +50,6 @@ Priorité Inclure les commissions Sera envoyé %s %s - ≈ %s (incl. les commissions : %s) Sera envoyé %s %s et %s %s Solde : %s %s La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps @@ -75,33 +59,21 @@ Signé %s hashes Monnaie de l\'application - Paramètres - Carte - Valider la carte - Gestion de la sécurité - Annuler le portefeuille Cette action est irréversible. Si, après avoir annulé le portefeuille, quelqu\'un lui envoie des fonds, vous ne pourrez pas les retirer. - Si vous oubliez le code, vous perdrez la possibilité d\'utiliser la carte. Si vous perdez le code, il sera impossible de le restaurer ou modifier. - Gestion de la sécurité Tenez la carte fermement Ce mécanisme protège contre les attaques sans contact sur la carte. Il y a un délai entre la réception et l\'exécution de la commande. Après la première transaction signée, ce téléphone sera associé à la carte et les transactions seront signées immédiatement. Mot de passe Avant d\'exécuter une commande qui modifie l\'état de la carte, vous devrez entrer un mot de passe. Code d\'accès Vous devrez entrer le mot de passe correct avant de scanner la carte - Vous pouvez rencontrer des problèmes NFC avec certains iPhone 7/7 + lors de l\'extraction Cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable. Attention Cette carte n\'est pas conçue pour fonctionner avec Tangem La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement - Les cartes Tangem émises avant septembre 2019 ne peuvent actuellement pas être extraites à l\'aide de l\'iPhone. Nous travaillons activement avec Apple pour rendre cela possible dans les futures versions d\'iOS. Touchez pour signer - Appuyez votre carte au téléphone comme indiqué ci-dessus - Touchez, pour annuler un portefeuille Touchez, pour créer un portefeuille Touchez, pour modifier le code d\'accès Touchez, pour modifier le mot de passe - PayString incorrect @@ -157,7 +129,6 @@ Avertissement Conditions d\'utilisation Pas de connexion internet - La vérification de PayString a échoué PayString non pris en charge par la blockchain PayString non enregistré La demande de PayString a échoué @@ -166,7 +137,6 @@ Avertissement Saisissez d\'abord le PayString requis Posez pour scanner Posez la carte - Cette option est déjà active diff --git a/app/src/main/res/values-fr/strings_blockchain.xml b/app/src/main/res/values-fr/strings_blockchain.xml index 197f42551c..380232b81b 100644 --- a/app/src/main/res/values-fr/strings_blockchain.xml +++ b/app/src/main/res/values-fr/strings_blockchain.xml @@ -6,7 +6,6 @@ Le montant minimal est de %s Le reste est trop petit Le compte cible n\'a pas été créé. Le montant à envoyer doit être de %s + commissions ou plus - Pour créer un compte, envoyez 1+ XLM à cette adresse Échec de réception des commissions Erreur inconnue Adresse incorrecte diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b9b5539110..28789234b2 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -7,22 +7,11 @@ Fatto Annulla Rimuovere - Accesso alla videocamera negato Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy - Impostazioni - Benvenuti in Tangem. Hai una delle nostre schede?> - Benevenuti di nuovo su Tangem. Scansiona la tua carta per iniziare. - Si! Scansiona carta. - Shop - Preparati ad avvicinare la tua carta sul retro del telefono. - Avvicina Scansiona carta - Scansiona Invia Crea portafoglio Tangem - Scegli l\'opzione del portafoglio - Token Token Saldo verificato Il saldo sta per essere caricato… @@ -40,14 +29,10 @@ da %s Cerca indirizzo Crea PayString - portafoglio %s Crea PayString - Carta: %s Nome PayString $payid.tangem.com - Crea Il tuo PayString – informazioni per te uniche, come il tuo numero di telefono, indirizzo email o ABN. - PayString creato con successo e copiato negli appunti Errore di risposta durante la creazione del PayString Questo PayString esiste già. Scegline un altro. Indirizzo o PayString @@ -64,7 +49,6 @@ Prioritario Includi commissione Sarà inviato %s %s - ≈ %s (inc. commissione: %s) Sarà inviato %s %s e %s %s Saldo: %s %s La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo @@ -74,33 +58,21 @@ Firmato Hash %s Valuta dell\'applicazione - Impostazioni - Carta - Convalidare la carta - Gestione del portafoglio - Cancella il portafoglio Questa azione è irreversibile. Se dopo aver eliminato il portafoglio qualcuno invia dei fondi, non potrai ritirarli. - Se dimentichi il codice, perderai la possibilità di utilizzare la carta. Non è possibile in alcun modo ripristinare o modificare il codice se lo perdi. - Gestione della sicurezza Mantenimento della carta Questo meccanismo protegge dagli avvicinamenti senza contatto sulla carta. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla carta e le transazioni verranno firmate immediatamente. Password Dovrai inserire una password prima di eseguire qualsiasi comando che modifichi lo stato della carta. Codice di accesso Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto - Potresti riscontrare problemi con l\'NFC su alcuni iPhone 7/7 + durante la rimozione Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile. Attenzione Questa carta non è progettata per funzionare con Tangem La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento - Le schede emesse prima di settembre 2019 non possono attualmente essere utilizzate con un iPhone. Stiamo lavorando a stretto contatto con Apple per renderle disponibili nelle future versioni di iOS. Avvicina per firmare - Avvicina la carta al telefono come mostrato sopra - Avvicina per cancellare il portafoglio Avvicina per creare il portafoglio Avvicina per modificare il codice di accesso Avvicina per modificare la password - PayString non valido @@ -159,7 +131,6 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020. Termini del servizio Nessuna connessione a Internet - Verifica del PayString fallita PayString non supportato dalla blockchain PayString non registrato Richiesta PayString fallita @@ -174,6 +145,5 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020. Avvicina per scansionare Avvicina la carta - E\' l\'opzione attualmente attiva diff --git a/app/src/main/res/values-it/strings_blockchain.xml b/app/src/main/res/values-it/strings_blockchain.xml index 92df7b9703..c9842eb6dd 100644 --- a/app/src/main/res/values-it/strings_blockchain.xml +++ b/app/src/main/res/values-it/strings_blockchain.xml @@ -7,9 +7,8 @@ L\'importo residuo è molto basso Errore sconosciuto L\'importo supera il saldo - Per creare un account, invia %s a questo indirizzo + Il conto totale non è stato creato L\'importo da inviare deve essere %s + commissione o maggiore Impossibile ottenere la commissione - Per creare un account, invia 1+ XLM a questo indirizzo Indirizzo non valido Importo non valido Il totale supera il saldo diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2e55d1156d..172380e133 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -9,22 +9,11 @@ Принять Удалить %s - Доступ к камере запрещен Вы не предоставили доступ к своей камере, измените настройки конфиденциальности - Настройки - Добро пожаловать в Tangem.\nУ Вас есть одна из наших карт? - Добро пожаловать обратно в Тангем. \nОтсканируйте карту, чтобы начать. - Магазин - Приготовьтесь приложить свою\nкарту к задней панели\nтелефона - Нажмите на Сканировать карту - Да! Отсканировать. - Сканировать Отправить Создать кошелек Tangem - Выбрите опцию - Токен Токены Подтвержденный баланс Загрузка баланса… @@ -42,14 +31,10 @@ Отправка Проверить адрес Создать PayString - %s кошелек Создать PayString - Карта: %s PayString имя $payid.tangem.com - Создать Ваша PayString — это уникальная для вас информация, такая как номер телефона, электронная почта или ABN - PayString успешно создан и скопирован в буфер обмена Ошибка при создании PayString Такой PayString уже существует. Попробуйте другой. Адрес или PayString @@ -65,7 +50,6 @@ Приоритетный Включить коммисию %s %s будет отправлено - ≈ %s (включая комиссию: %s) %s %s и %s %s будут отправлены Баланс: %s %s Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время @@ -75,36 +59,23 @@ Подписано %s хэшей Валюта приложения - Настройки - Карта - Подтвердить карту - Управление безопасностью - Удалить кошелек Это действие необратимо. Если после удаления кошелька кто-то отправит на него средства, то Вы не сможете их вывести. - Если вы забудете код, Вы потеряете возможность использовать карту. Невозможно восстановить или изменить код, если вы его потеряете. - Управление безопасностью Длительное удержание Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. Пароль Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль Код доступа Перед сканированием карты вам нужно будет ввести правильный код доступа - У Вас могут возникнуть проблемы с NFC на некоторых iPhone 7/7+ во время извлечения Эта карта ранее пополнялась и подписывала транзакции. Выведите средства как можно быстрее, если вы получили эту карту из ненадежного источника. Если это ваша карта, то не о чем беспокоиться. Предупреждение Эта карта не предназначена для работы с этим приложением Карта, которую Вы отсканировали, является картой для разработчиков. Не принимайте ее в качестве оплаты. - Карты Tangem, выпущенные до сентября 2019 года, в настоящее время не могут быть извлечены с помощью iPhone. Мы усердно работаем с Apple, чтобы сделать это возможным в будущих версиях iOS. Нажмите, чтобы подписать - Чтобы подписать транзакцию, соедините телефон и карту в точности, как показано выше. - Чтобы сбросить настройки до заводских, соедините телефон и карту в точности, как показано выше. Приложите, чтобы создать кошелек Чтобы изменить код доступа, соедините телефон и карту в точности, как показано выше. Чтобы изменить пароль, соедините телефон и карту в точности, как показано выше. - Неверный PayString Условия использования Нет соединения с интернетом - Ошибка проверки PayString PayString не поддерживается блокчейном PayString не зарегистрирован Не удалось выполнить запрос PayString @@ -114,21 +85,16 @@ Сначала введите желаемую PayString Приложите, чтобы отсканировать Приложите карту - Это текущая активная опция Ошибка сканирования карты. Попробуй снова. Купить Продать Торговать По умолчанию - Совместимость Устаревший - Создать кошелек Создать кошелек Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты - Tangem Twin Один кошелек. Две карты. Одна, которую Вы держите в руках, и вторая карта с номером #%s. - Пересоздать Twin кошелек Условия использования карты Tangem Twin Это действие необратимо. У Вас не будет доступа к старому кошельку. @@ -136,58 +102,34 @@ Подготовка карты Создание кошелька Сканировать карту #%s - Вы уже начали процесс пересоздания кошелька. Если Вы прервете его, вы не сможете использовать свои карты, пока не запустите его снова и не завершите пересоздание кошелька. Вы приложили ту же карту. Чтобы создать кошелек, вам нужно приложить другу Twin карту. - Адрес кошелька успешно создан Начать - Назад Добавить - Удалить Поиск - Приложение Отправить отзыв - Успешно отправлено - Спасибо за ваш отзыв - Ваши предложения отправлены - Спасибо за ваш отзыв. Мы ответим как можно скорее. - Не удалось отправить электронное письмо - Причина: %s Не могу отправить транзакцию Причина: %s У Вас возникли трудности со сканированием вашей карты? - Пожалуйста, попробуйте приложить карту точно так, как показано на анимации, или обратитесь в поддержку Отмена - Попробуйте снова Обратиться в поддержку Отправить отзыв Очень круто! Может быть лучше Один вопрос Вам нравится приложение Tangem? - Токенов пока нет - Добавленные токены - Удалить токен %s - Вы уверены, что хотите удалить этот токен? Добавить токены - Добавить токен Начните поиск - Управление токенами - Добавить пользовательский - Популярные токены - Добавлен Значок токена Добавить токен Адрес контракта Обязательное поле Пожалуйста, выберите сеть - Пожалуйста, заполните все поля Количество знаков после запятой должно быть корректным числом не больше %d Адрес контракта некорректен Путь деривации некорректен Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить Этот токен/сеть уже находится в вашем списке Сеть - Токен Знаков после запятой Сеть Не выбрано @@ -197,13 +139,8 @@ Символ токена Деривация по BIP44 По-умолчанию - Добавить токен - Блокчейн - Управление токенами - Блокчейны Копировать Поделиться - + Добавить токены Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства Проверка подлинности не удалась Эта карта может быть производственным образцом или подделкой @@ -219,9 +156,6 @@ Это тестовая карта. Не принимайте ее в качестве оплаты. Она должна использоваться только в целях тестирования и разработки. Вы хотите купить или продать криптовалюту? К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений - Добро пожаловать в Tangem - Самый безопасный способ покупать, использовать и\n хранить криптовалюту - Получить новую карту Активация карты Как это работает? Получить криптовалюту @@ -233,7 +167,6 @@ Чтобы начать, просто пополните кошелек более чем на %s %s Купить криптовалюту Показать адрес кошелька - Отсканируйте адрес, чтобы пополнить кошелек Успешно! Ваша криптокарта активирована и готова к использованию Продолжить @@ -242,32 +175,25 @@ Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. Если процесс создания кошелька каким-либо образом прервется, вам придется начать все сначала. Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. - Внутренняя ошибка: не удается создать менеджер кошельков Блокчейн недоступен. Попробуй позже. Хорошо, понял! - Успешно! Начнем Выполнить резервное копирование - Получить резервную карту Оставить на потом Завершить процесс резервного копирования + Добавить резервную карту Сканировать карту #%d Сканировать основную карту Добавить больше карт - Сделайте резервную копию вашего кошелька Нет резервных карт Подготовьте свою карту Добавлена одна резервная карта Добавлены две резервные карты Резервная карта #%d - Вы можете привязать до трех карт к одному кошельку Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. - Подготовьте основную карту с номером %s Подготовьте основную карту Вы можете добавить еще одну карту или завершить процесс резервного копирования Добавлено максимальное количество карт - Если процесс резервного копирования будет прерван каким-либо образом, вам придется начать все сначала Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? Создать код доступа Введите код доступа повторно @@ -282,7 +208,6 @@ Введенный код доступа не соответствует исходному коду доступа Создать Подтвердить - Вы создали 2 резервные карты, и теперь эти карты готовы к использованию Создание резервной копии Процесс резервного копирования частично завершен. Вы не можете выйти из него сейчас. Резервный кошелек @@ -294,13 +219,6 @@ Вы сможете установить код доступа для защиты своих кошельков Код доступа можно восстановить с помощью одной из резервных карт Ваша карта настроена и готова к использованию. - Вы создали 1 резервную карту, и теперь эта карта готова к использованию - Основная карта - Я понимаю - Статус резервного копирования - Активный. Количество резервных карт: %d - Связано. Количество резервных карт: %d - Нет резервной копии У Вас есть прерванное резервное копирование. Хотите возобновить? Да, возобновить Отказаться @@ -315,20 +233,15 @@ Подготовьте резервную карту с номером %s Подготовьте основную карту с номером %s Сброс до заводских настроек - Код доступа доступен только для карт с резервной копией Сеть Solana взымает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы использовать его бесплатно. - %s (%s) Эта карта не является векселем на предъявителя. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец скрывает автономную подпись, что является проблемой безопасности.\n\nНе принимайте эту карту в качестве физического платежа от кого-то, кому Вы не доверяете.\n\nВо всех остальных отношениях - это совершенно безопасно.\n\nTangem — единственный аппаратный кошелек, предлагающий защиту от подсчета подписей. WalletConnect Запрос на создание транзакции для %s\n%s\n\nСумма: %s\nКомиссия: %s\nВсего: %s\nБаланс: %s - Запрос на запуск сеанса для %s\n\nURL: %s + Запрос на открытие сессии для\n%s\n\nСЕТЬ: %s\n\nURL: %s Сессии WalletConnect - Сеанс WalletConnect открыт с %s Упс. Нет сессий. Нет открытых сессий WalletConnect Открытая сессия - Карта: %s - Коснитесь карты, чтобы привязать ее WalletConnect Не удается отправить транзакцию. Недостаточно средств. Просьба подписать сообщение\n Сообщение для %s:\n%s @@ -336,24 +249,20 @@ Вставить из буфера обмена Сканировать новый код Эту карту нельзя использовать для установки сеанса WalletConnect. - Операция не может быть завершена Операция не может быть завершена. \n\nВы уже установили сеанс WalletConnect с этими параметрами. Не удалось установить сеанс WalletConnect: ошибка времени выполнения. Пожалуйста, повторите попытку позже. - Транзакция BNB успешно подписана и отправлена в DApp DApp %s, запрашивает\nподпись транзакции BNB с\n%s Сведения о транзакции:\nОт: %s\nКому: %s\nСумма: %s Торговый ордер на %s\nЦена: %s\nСумма к получению: %s\nСумма к оплате: %s Мои предложения Не могу отсканировать карту Не могу отправить транзакцию - Не могу протолкнуть транзакцию Обращение в поддержку Tangem Обращение в поддержку Расскажите, каких функций Вам не хватает, и мы постараемся Вам помочь Скажите, пожалуйста, какая у Вас карта? Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая маленькая деталь может помочь. Привет, команда поддержки, - Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая деталь может быть полезной. Информация ниже не является обязательной. Вы можете стереть её, если хотите. Нет diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml index 5baeca3d33..f3be360857 100644 --- a/app/src/main/res/values-ru/strings_blockchain.xml +++ b/app/src/main/res/values-ru/strings_blockchain.xml @@ -5,9 +5,8 @@ Неверная комиссия Минимальная сумма: %s Сдача слишком мала - Для создания учетной записи отправьте 1+ XLM на этот адрес Не удалось получить комиссию - Целевая учетная запись не создана. Сумма для отправки должна быть %s + плата за создание или больше + Аккаунт получателя не создан. Сумма для отправки должна быть %s + комиссия или больше Неизвестная ошибка Неверный адрес Недопустимая сумма diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6ea8e27b1c..af231b0713 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,25 +11,14 @@ Delete %s - Camera access denied You have not given access to your camera, please adjust your privacy settings - Settings - Welcome to Tangem.\nDo you have one of our cards? - Welcome back to Tangem. \nScan your card to start. - Shop - Get ready to tap your \ncard to the back of the \nphone. - Tap in Scan card - Yes! Scan it. - Scan Send Create wallet Tangem - Choice wallet option - Token Tokens Verified Balance Balance is loading… @@ -50,15 +39,11 @@ Explore address Create PayString - %s wallet Create PayString - Card: %s PayString name $payid.tangem.com - Create Your PayString is information unique to you, like your phone number, email or ABN. - PayString created successfully and copied to clipboard Error response while creating PayString This PayString already exists. Try a different one. @@ -75,7 +60,6 @@ Priority Include fee %s %s will be sent - ≈ %s (inc. fee: %s) %s %s and %s %s will be sent Balance: %s %s Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while @@ -86,14 +70,7 @@ Signed %s hashes App currency - Settings - Card - Validate card - Manage Security - Erase wallet This action is irreversible. If, after deleting the wallet, someone sends funds to it, then you will not be able to withdraw them. - If you forget the code, you will lose the ability to use the card. There is no way to recover or change your code if you lose it. - Manage Security Long Tap This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. Passcode @@ -101,21 +78,16 @@ Access code You will have to submit the correct access code before scanning the card - You may experience NFC problems with some iPhone 7/7+ during the extraction This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about. Warning This card is not designed to work with this app The card you scanned is a development card. Don\'t accept it as a payment - Tangem cards manufactured before September 2019 cannot currently be extracted with an iPhone. We\'re working hard with Apple to make it possible in future versions of iOS. Tap to sign - To sign, connect your phone and the card exactly as it shown above - To reset to factory settings, connect your phone and the card exactly as it shown above Tap to create wallet To change the access code, connect your phone and the card exactly as it shown above To change the passcode, connect your phone and the card exactly as it shown above - Invalid PayString Legal Disclaimer \n @@ -170,7 +142,6 @@ No internet connection - PayString verification failed PayString unsupported by blockchain PayString not registered PayString request failed @@ -185,5 +156,4 @@ Tap to scan Tap the card - This is the currently active option diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml index f19a487b14..b2ebd2e2d9 100644 --- a/app/src/main/res/values/strings_blockchain.xml +++ b/app/src/main/res/values/strings_blockchain.xml @@ -5,9 +5,8 @@ Invalid Fee Minimum amount is %s Change is too small - To create account send 1+ XLM to this address Failed to get fee - Target account is not created. Amount to send should be %s + fee or more to create + Target account is not created. Amount to send should be %s + fee or more Unknown error Invalid address Invalid amount diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 676c8cc848..f82c05ea0e 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -5,18 +5,14 @@ Sell Trade Default - Compatibility Legacy - Create twin wallet Create twin wallet Generate wallet keys on both cards to start using your Twins - Tangem Twin One wallet. Two cards. This one that you are holding in your hands and the other one with number #%s. - Re-create twin wallet Card terms of use Tangem Twin @@ -25,32 +21,19 @@ Preparing card Creating wallet Scan the card #%s - You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet. You\'ve tapped the same card. To create twin wallet you need to tap a second twin card. - The twin address was successfully created //Common Start - Back Add - Remove Search - App Send feedback - Sent successfully - Thank you for your feedback - Your suggestions were sent - Thank your for your feedback. We will response as soon as possible - Failed to send email - Reason: %s Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - Please try to tap the card exactly as shown in the animation or request support. I\'m okay - Try again Request support Send feedback Really cool! @@ -59,31 +42,20 @@ How do you like Tangem? //Manage tokens - No tokens yet - Added tokens - Remove %s token - Are you sure you want to remove this token? Add Tokens - Add Token - Manage Tokens - Add Custom Custom tokens - Popular tokens - Added Token icon Add Token Contract address Required field Please select the network - Please fill in all the fields Decimal number must be a valid integer, no higher than %d Contract address is invalid Derivation path is invalid Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. This token/network has already been added to your list Network - Token Decimals Network Not selected @@ -93,17 +65,12 @@ Token symbol BIP44 coin type Default - Add token //Details - Blockchain - Manage tokens - Blockchains %s tokens Copy Share - + Add tokens Please wait for %s transaction to complete to be able to send funds Authenticity check failed @@ -124,18 +91,15 @@ //Wallet Connect WalletConnect Wallet Connect Sessions - WalletConnect session opened with %s Ooops. No Sessions. No opened WalletConnect sessions Open session - Card: %s - Tap card to bind to wallet connect Request to create transaction for %s\n%s\n\nAmount: %s\nFee: %s\nTotal: %s\nBalance: %s Can\'t send transaction. Not enough funds. - Request to start a session for %s\n\nURL: %s + Request to start a session for\n%s\n\nNETWORK: %s\n\nURL: %s Requesting to sign a message\n Message for %s:\n%s @@ -150,7 +114,6 @@ Paste from clipboard Scan new code This card can\'t be used to establish WalletConnect session - The operation couldn\'t be completed The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters. Failed to establish WalletConnect session: timeout error. Please, try again later. @@ -162,9 +125,6 @@ Oops, the current version of the application is not ready to work with this card, please check for updates. - Welcome to Tangem - The safest way to buy, use and\n store cryptocurrency - Get a new card Activating card How it works? @@ -179,7 +139,6 @@ To get started, simply top up the wallet with more than %s %s. Buy crypto Show the wallet\'s address - Scan the address to top up your wallet Success! Your crypto card is activated and ready to be used @@ -191,17 +150,14 @@ If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. - Internal error: can\'t create wallet manager Blockchain is unreachable. Try later Ok, Got it! - Success! Getting started Backup now - Get a backup card Skip for later Finalize the backup process + Add backup card @@ -209,20 +165,16 @@ Scan primary card Add more cards - Backup your wallet No backup cards Prepare your card One backup card added Two backup cards added Backup card #%d - You can backup your keys up to two other blank wallet cards To start the backup process add up to two backup cards. - Prepare the primary card with number %s Prepare the primary card You can add one more card or finalize the backup process Max number of cards added. Finalize the backup process - If the process of backup gets interrupted in any way you\'ll have to start over You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it in backup, otherwise you can buy it in shop. Do you like to continue backup process? @@ -240,7 +192,6 @@ Create Submit - You have created 2 backup cards, and now these cards are ready for use. Creating a backup The backup process is partly complete. You can\'t exit it now. @@ -255,14 +206,7 @@ Access code can be restored with one of backup cards. Your wallet card is configured and ready for use. - You have created 1 backup card, and now this card are ready for use. - Primary Card - I understand - Backup status - Active. Backup cards count: %d - Linked. Backup cards count: %d - No backup You have an interrupted backup. Do you want to resume? Yes, resume Discard @@ -278,11 +222,9 @@ Prepare the primary card with number %s Reset to factory settings - Access code is available only for cards with a backup. - The BNB transaction has been successfully signed and sent to the Dapp Dapp %s, requesting to\n sign BNB transaction \n @@ -298,20 +240,17 @@ Amount to pay: %s Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. - %s (%s) Type here to search My suggestions Can\'t scan a card Can\'t send a transaction - Can\'t push a transaction Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. Please tell us what card do you have? Please tell us more about your issue. Every small detail can help. Hi support team, - Please tell us more about your issue. Every small detail can help. Following information is optional. You can erase it if you don’t want to share it. diff --git a/core/ui/src/main/res/values-de/strings_final.xml b/core/ui/src/main/res/values-de/strings_final.xml index b7e64d53df..665f17302a 100644 --- a/core/ui/src/main/res/values-de/strings_final.xml +++ b/core/ui/src/main/res/values-de/strings_final.xml @@ -9,8 +9,6 @@ Total Other payment methods Buy now - Placing your order - Your order Meet\nTangem Buy Store @@ -19,7 +17,6 @@ Exchange Lend Borrow - Stake Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup @@ -33,7 +30,6 @@ The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. Order - Search tokens Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode @@ -41,11 +37,8 @@ Available networks Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. Tokens in Solana network are not supported by this card due to firmware limitation. - Do not transfer your funds to tokens in this blockchain, otherwise it may lead to their irretrievable loss. Contract address copied! If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds - Custom - Notice Attention The server is not available, please try again later Total balance @@ -72,15 +65,12 @@ Tangem Bot Card Settings Security Mode - Selected application protection method Change access code Access code will be changed on this card only Get your card ready! Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. Scan Card App settings - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem app Keep the wallet in the app Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. Save Access Code @@ -90,7 +80,6 @@ WalletConnect Connect to Dapps Link More Cards - You can synchronize up to three cards into one wallet. It can only be done once. Privacy policy Reset to factory settings This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. @@ -107,7 +96,6 @@ Invalid Memo. It won\'t be added to the transaction. Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. - To start working with the card scan the first card and make a backup Attention Tap the card with the visa logo No funds for activation @@ -118,7 +106,6 @@ Connect KYC Verify your identity - In progress Set PIN code Register Verify via Utorg @@ -132,7 +119,6 @@ PIN Code Set PIN code for your SaltPay card Chat with support - This card is not designed to SaltPay backup Please hold the card until the operation complete To start the backup process you have to add Tangem card as your backup No backup card diff --git a/core/ui/src/main/res/values-fr/strings_final.xml b/core/ui/src/main/res/values-fr/strings_final.xml index b7e64d53df..665f17302a 100644 --- a/core/ui/src/main/res/values-fr/strings_final.xml +++ b/core/ui/src/main/res/values-fr/strings_final.xml @@ -9,8 +9,6 @@ Total Other payment methods Buy now - Placing your order - Your order Meet\nTangem Buy Store @@ -19,7 +17,6 @@ Exchange Lend Borrow - Stake Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup @@ -33,7 +30,6 @@ The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. Order - Search tokens Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode @@ -41,11 +37,8 @@ Available networks Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. Tokens in Solana network are not supported by this card due to firmware limitation. - Do not transfer your funds to tokens in this blockchain, otherwise it may lead to their irretrievable loss. Contract address copied! If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds - Custom - Notice Attention The server is not available, please try again later Total balance @@ -72,15 +65,12 @@ Tangem Bot Card Settings Security Mode - Selected application protection method Change access code Access code will be changed on this card only Get your card ready! Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. Scan Card App settings - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem app Keep the wallet in the app Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. Save Access Code @@ -90,7 +80,6 @@ WalletConnect Connect to Dapps Link More Cards - You can synchronize up to three cards into one wallet. It can only be done once. Privacy policy Reset to factory settings This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. @@ -107,7 +96,6 @@ Invalid Memo. It won\'t be added to the transaction. Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. - To start working with the card scan the first card and make a backup Attention Tap the card with the visa logo No funds for activation @@ -118,7 +106,6 @@ Connect KYC Verify your identity - In progress Set PIN code Register Verify via Utorg @@ -132,7 +119,6 @@ PIN Code Set PIN code for your SaltPay card Chat with support - This card is not designed to SaltPay backup Please hold the card until the operation complete To start the backup process you have to add Tangem card as your backup No backup card diff --git a/core/ui/src/main/res/values-it/strings_final.xml b/core/ui/src/main/res/values-it/strings_final.xml index b7e64d53df..665f17302a 100644 --- a/core/ui/src/main/res/values-it/strings_final.xml +++ b/core/ui/src/main/res/values-it/strings_final.xml @@ -9,8 +9,6 @@ Total Other payment methods Buy now - Placing your order - Your order Meet\nTangem Buy Store @@ -19,7 +17,6 @@ Exchange Lend Borrow - Stake Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup @@ -33,7 +30,6 @@ The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. Order - Search tokens Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode @@ -41,11 +37,8 @@ Available networks Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. Tokens in Solana network are not supported by this card due to firmware limitation. - Do not transfer your funds to tokens in this blockchain, otherwise it may lead to their irretrievable loss. Contract address copied! If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds - Custom - Notice Attention The server is not available, please try again later Total balance @@ -72,15 +65,12 @@ Tangem Bot Card Settings Security Mode - Selected application protection method Change access code Access code will be changed on this card only Get your card ready! Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. Scan Card App settings - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem app Keep the wallet in the app Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. Save Access Code @@ -90,7 +80,6 @@ WalletConnect Connect to Dapps Link More Cards - You can synchronize up to three cards into one wallet. It can only be done once. Privacy policy Reset to factory settings This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. @@ -107,7 +96,6 @@ Invalid Memo. It won\'t be added to the transaction. Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. - To start working with the card scan the first card and make a backup Attention Tap the card with the visa logo No funds for activation @@ -118,7 +106,6 @@ Connect KYC Verify your identity - In progress Set PIN code Register Verify via Utorg @@ -132,7 +119,6 @@ PIN Code Set PIN code for your SaltPay card Chat with support - This card is not designed to SaltPay backup Please hold the card until the operation complete To start the backup process you have to add Tangem card as your backup No backup card diff --git a/core/ui/src/main/res/values-ru/strings_final.xml b/core/ui/src/main/res/values-ru/strings_final.xml index dc387d3f71..5b98f5cc07 100644 --- a/core/ui/src/main/res/values-ru/strings_final.xml +++ b/core/ui/src/main/res/values-ru/strings_final.xml @@ -9,8 +9,6 @@ Итого Другие способы оплаты Купить сейчас - Оформление заказа - Ваш заказ Встречайте\nTangem Покупайте Храните @@ -19,7 +17,6 @@ Обменивайте Вкладывайте Занимайте - Стейкайте Революционный аппаратный кошелек Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. Все ключи в безопасности @@ -33,7 +30,6 @@ Кошелек для каждого Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. Купить - Поиск токенов Поиск токенов Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие. Эта функция недоступна в демонстрационном режиме @@ -41,11 +37,8 @@ Доступные сети Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства. Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки. - Не осуществляйте перевод на токены в данной сети, иначе это может привести к их безвозвратной утере. Адрес контракта скопирован! Если вы совершили ошибку с выбором сети при переводе средств с биржи, эта инструкция поможет вам восстановить средства - Пользовательский - Уведомление Внимание Сервер недоступен, повторите попытку позднее Баланс @@ -70,15 +63,12 @@ Tangem Bot Настройки карты Тип безопасности - Выбранный способ защиты приложения Смена кода доступа Код доступа будет изменен только на данной карте Приготовьте свою карту Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. Сканировать Настройки приложения - Включите биометрическую аутентификацию - Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem Cохранение кошелька Если эта опция включена, кошельки всех отсканированных карт будут привязываться к приложению и при открытии приложения будет необходимо пройти биометрическую аутентификацию. Подпись транзакций все так же потребует карту. Сохранение кода доступа @@ -88,7 +78,6 @@ WalletConnect Подключение к Dapps Добавить еще карты - Вы можете объединить до трех карт в одном кошельке. Это можно сделать только один раз. Privacy policy Сброс к заводским настройкам Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа @@ -105,7 +94,6 @@ Недопустимый Tag. Он не будет добавлен в транзакцию. Не удалось установить сессию WalletConnect Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. - Для начала работы с картой сначала отсканируйте первую карту и сделайте бэкап Внимание Приложите карту с логотипом Visa Недостаточно средств для активации @@ -116,7 +104,6 @@ Подключиться Верификация клиента Подтвердите свою личность - В обработке Установить Код доступа Зарегистрироваться Верифицировать (Utorg) @@ -130,7 +117,6 @@ Код доступа Установите Код доступа для вашей SaltPay карты Чат поддержки - Данная карта не подходит для бэкапа SaltPay Пожалуйста, удерживайте карту до завершения операции Для начала процесса бэкапа вам необходимо добавить Tangem карту Бэкап карта не добавлена diff --git a/core/ui/src/main/res/values/strings_final.xml b/core/ui/src/main/res/values/strings_final.xml index e434fc002d..8b0c3fcc01 100644 --- a/core/ui/src/main/res/values/strings_final.xml +++ b/core/ui/src/main/res/values/strings_final.xml @@ -9,8 +9,6 @@ Total Other payment methods Buy now - Placing your order - Your order Meet\nTangem Buy Store @@ -19,7 +17,6 @@ Exchange Lend Borrow - Stake Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup @@ -33,7 +30,6 @@ The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. Order - Search tokens Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode @@ -41,11 +37,8 @@ Available networks Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. Tokens in Solana network are not supported by this card due to firmware limitation. - Do not transfer your funds to tokens in this blockchain, otherwise it may lead to their irretrievable loss. Contract address copied! If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds - Custom - Notice Attention The server is not available, please try again later Total balance @@ -72,15 +65,12 @@ Tangem Bot Card Settings Security Mode - Selected application protection method Change access code Access code will be changed on this card only Get your card ready! Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. Scan Card App settings - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem app Keep the wallet in the app Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. Save Access Code @@ -90,7 +80,6 @@ WalletConnect Connect to Dapps Link More Cards - You can synchronize up to three cards into one wallet. It can only be done once. Privacy policy Reset to factory settings This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. @@ -107,7 +96,6 @@ Invalid Memo. It won\'t be added to the transaction. Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. - To start working with the card scan the first card and make a backup Attention Tap the card with the visa logo No funds for activation @@ -118,7 +106,6 @@ Connect KYC Verify your identity - In progress Set PIN code Register Verify via Utorg @@ -132,7 +119,6 @@ PIN Code Set PIN code for your SaltPay card Chat with support - This card is not designed to SaltPay backup Please hold the card until the operation complete To start the backup process you have to add Tangem card as your backup No backup card @@ -154,5 +140,5 @@ Do you want to exit the activation process? In this case, you will need to start from the beginning. You have used a card from another wallet. Tap the card associated with this wallet - Internal error: wallet manager not found + Internal error: wallet manager not found diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt index cbdcdaeb50..1e2ceefc51 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt @@ -27,7 +27,7 @@ import com.tangem.feature.referral.presentation.R fun Preview_AgreementText_InLightTheme() { TangemTheme(isDark = false) { Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { - AgreementText(firstPartResId = R.string.referral_tou_not_enroled_prefix, onClicked = {}) + AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) } } } @@ -37,7 +37,7 @@ fun Preview_AgreementText_InLightTheme() { fun Preview_AgreementText_InDarkTheme() { TangemTheme(isDark = true) { Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { - AgreementText(firstPartResId = R.string.referral_tou_not_enroled_prefix, onClicked = {}) + AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) } } } @@ -71,7 +71,7 @@ private fun annotatedAgreementString(firstPart: String): AnnotatedString { append(stringResource(id = R.string.common_terms_and_conditions)) } withStyle(SpanStyle(color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY))) { - append(" ${stringResource(id = R.string.referral_tou_suffix)}") + append(" ${stringResource(id = R.string.referral_tos_suffix)}") } } } \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index 2706490417..f202bd4ef6 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -81,7 +81,7 @@ fun NonParticipateBottomBlock( ) PersonalCodeCard(code = code) AdditionalButtons(onCopyClicked = onCopyClicked, onShareClicked = onShareClicked) - AgreementText(firstPartResId = R.string.referral_tou_enroled_prefix, onClicked = onAgreementClicked) + AgreementText(firstPartResId = R.string.referral_tos_enroled_prefix, onClicked = onAgreementClicked) } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index f6c6eced81..d58527d894 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -37,7 +37,7 @@ fun Preview_ParticipateBottomBlock_InDarkTheme() { fun ParticipateBottomBlock(onAgreementClicked: () -> Unit, onParticipateClicked: () -> Unit) { Column { AgreementText( - firstPartResId = R.string.referral_tou_not_enroled_prefix, + firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = onAgreementClicked, ) PrimaryEndIconButton( diff --git a/features/referral/presentation/src/main/res/values-ru/string.xml b/features/referral/presentation/src/main/res/values-ru/string.xml index 4a6e997f8e..5e6ef22607 100644 --- a/features/referral/presentation/src/main/res/values-ru/string.xml +++ b/features/referral/presentation/src/main/res/values-ru/string.xml @@ -9,11 +9,11 @@ Получит %s скидку при покупке карточки на сайте tangem.com - Нажимая на эту кнопку вы принимаете + Нажимая на эту кнопку вы принимаете условия участия - в реферальной программе + в реферальной программе Участвовать - Вы приняли + Вы приняли Ваш персональный код %d кошельков Ваши друзья купили diff --git a/features/referral/presentation/src/main/res/values/string.xml b/features/referral/presentation/src/main/res/values/string.xml index 7042ed2411..e429fe37e1 100644 --- a/features/referral/presentation/src/main/res/values/string.xml +++ b/features/referral/presentation/src/main/res/values/string.xml @@ -9,11 +9,11 @@ Will get a %s discount when buying a card on tangem.com - By tapping this button you accept + By tapping this button you accept terms and conditions - of the referral program + of the referral program Participate - You\'ve accepted + You\'ve accepted Your personal code %d wallets Your friends bought From 9eab9e1a6e83a879b53fb0b130d0a8fb5b9bb010 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Nov 2022 11:29:27 +0300 Subject: [PATCH 072/176] Updated on 2026-08-14 --- .../ui/walletconnect/dialogs/ApproveWcSessionDialog.kt | 4 +++- .../features/send/ui/dialogs/RequestFeeErrorDialog.kt | 2 +- .../com/tangem/tap/features/wallet/ui/BalanceWidget.kt | 2 +- .../tap/features/wallet/ui/WalletDetailsFragment.kt | 2 +- .../wallet/ui/dialogs/SignedHashesWarningDialog.kt | 2 +- .../layout_onboarding_container_bottom.xml | 2 +- app/src/main/res/layout/layout_address.xml | 4 ++-- .../res/layout/layout_onboarding_container_bottom.xml | 2 +- app/src/main/res/layout/layout_wallet_details.xml | 8 ++++---- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-de/strings_blockchain.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-fr/strings_blockchain.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-it/strings_blockchain.xml | 2 +- app/src/main/res/values-ru/strings.xml | 10 ++++------ app/src/main/res/values-ru/strings_blockchain.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/values/strings_blockchain.xml | 2 +- app/src/main/res/values/strings_untranslated.xml | 10 ++++------ core/ui/src/main/res/values-de/strings_final.xml | 4 ++-- core/ui/src/main/res/values-fr/strings_final.xml | 4 ++-- core/ui/src/main/res/values-it/strings_final.xml | 4 ++-- core/ui/src/main/res/values/string_common.xml | 4 ++-- core/ui/src/main/res/values/strings_final.xml | 6 +++--- 25 files changed, 43 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt index ab95ef6ef8..f0917ecf17 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt @@ -12,9 +12,11 @@ import com.tangem.wallet.R class ApproveWcSessionDialog { companion object { fun create(session: WalletConnectSession, networks: List, context: Context): AlertDialog { + val sessionBlockchain = requireNotNull(session.wallet.blockchain) { "session network is null" } val message = context.getString( R.string.wallet_connect_request_session_start, session.peerMeta.name, + sessionBlockchain.fullName, session.peerMeta.url, ) return AlertDialog.Builder(context).apply { @@ -22,7 +24,7 @@ class ApproveWcSessionDialog { setMessage(message) setPositiveButton(context.getText(R.string.common_start)) { _, _ -> store.dispatch(GlobalAction.HideDialog) - store.dispatch(WalletConnectAction.ChooseNetwork(session.wallet.blockchain!!)) + store.dispatch(WalletConnectAction.ChooseNetwork(sessionBlockchain)) } if (networks.size > 1) { setNeutralButton(context.getText(R.string.wallet_connect_select_network)) { _, _ -> diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt index 669ec571f0..353989e76c 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt @@ -18,7 +18,7 @@ class RequestFeeErrorDialog { val errorMessage = dialog.error.customMessage return AlertDialog.Builder(context).apply { - setTitle(R.string.send_error_fee_request_failed) + setTitle(R.string.common_fee_error) setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage)) setNegativeButton(R.string.alert_button_send_feedback) { _, _ -> store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 34ca1f10c1..4b9b5cb18e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -128,7 +128,7 @@ class BalanceWidget( tvErrorTitle.text = fragment.getText(R.string.wallet_error_no_account) tvErrorDescriptions.text = fragment.getString( - R.string.wallet_error_no_account_subtitle_format, + R.string.no_account_generic, data.amountToCreateAccount, data.currencySymbol ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index f40bf97ea5..d938b8966d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -340,7 +340,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalanceError.tvErrorTitle.text = getText(R.string.wallet_error_no_account) lBalanceError.tvErrorDescriptions.text = getString( - R.string.wallet_error_no_account_subtitle_format, + R.string.no_account_generic, data.amountToCreateAccount, data.currencySymbol, ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt index 609537bbaf..c8db3c3d23 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt @@ -14,7 +14,7 @@ class SignedHashesWarningDialog { return AlertDialog.Builder(context).apply { setTitle(context.getString(R.string.warning_important_security_info)) setMessage(R.string.alert_signed_hashes_message) - setPositiveButton(R.string.alert_button_i_understand) { _, _ -> + setPositiveButton(R.string.common_understand) { _, _ -> store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) store.dispatch( GlobalAction.HideWarningMessage( diff --git a/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml index b14f8571de..e0f2965300 100644 --- a/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml +++ b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml @@ -51,7 +51,7 @@ android:id="@+id/chb_understand" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/alert_button_i_understand" + android:text="@string/common_understand" android:textStyle="bold" android:visibility="gone" app:buttonTint="@color/accent" diff --git a/app/src/main/res/layout/layout_address.xml b/app/src/main/res/layout/layout_address.xml index abf039bab7..79341aa867 100644 --- a/app/src/main/res/layout/layout_address.xml +++ b/app/src/main/res/layout/layout_address.xml @@ -43,14 +43,14 @@ style="@style/TapChip" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/wallet_address_chip_default" /> + android:text="@string/address_type_default" /> + android:text="@string/address_type_legacy" /> diff --git a/app/src/main/res/layout/layout_onboarding_container_bottom.xml b/app/src/main/res/layout/layout_onboarding_container_bottom.xml index 9ee7ea6489..6cbb49b0fa 100644 --- a/app/src/main/res/layout/layout_onboarding_container_bottom.xml +++ b/app/src/main/res/layout/layout_onboarding_container_bottom.xml @@ -51,7 +51,7 @@ android:id="@+id/chb_understand" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/alert_button_i_understand" + android:text="@string/common_understand" android:textStyle="bold" android:visibility="gone" app:buttonTint="@color/accent" diff --git a/app/src/main/res/layout/layout_wallet_details.xml b/app/src/main/res/layout/layout_wallet_details.xml index 8e33a5cead..85cb90977c 100644 --- a/app/src/main/res/layout/layout_wallet_details.xml +++ b/app/src/main/res/layout/layout_wallet_details.xml @@ -60,14 +60,14 @@ style="@style/TapChip" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/wallet_address_chip_default" /> + android:text="@string/address_type_default" /> + android:text="@string/address_type_legacy" /> @@ -127,7 +127,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="4dp" android:src="@drawable/ic_inactive" - android:text="@string/wallet_address_button_copy" + android:text="@string/common_copy" android:textAlignment="center" android:textColor="@color/colorSecondary" app:chipBackgroundColor="@color/blue_pale" @@ -148,7 +148,7 @@ android:layout_height="wrap_content" android:layout_marginStart="4dp" android:src="@drawable/ic_inactive" - android:text="@string/wallet_address_button_share" + android:text="@string/common_share" android:textAlignment="center" android:textColor="@color/colorSecondary" app:chipBackgroundColor="@color/blue_pale" diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 967b2ab32f..82b5ef487f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -18,7 +18,7 @@ Die Bilanz wird aufgeladen… Blockchain ist nicht erreichbar Das Konto ist nicht erstellt - %s+ %s aufladen um ein Konto zu erstellen + Laden Sie %s+ %s auf um ein Konto zu erstellen Leere Karte Erstellen Sie ein Wallet um die Tangem-Karte verwenden zu können Diese Karte wird nicht unterstützt diff --git a/app/src/main/res/values-de/strings_blockchain.xml b/app/src/main/res/values-de/strings_blockchain.xml index 8b9715a9e6..910ae90ce2 100644 --- a/app/src/main/res/values-de/strings_blockchain.xml +++ b/app/src/main/res/values-de/strings_blockchain.xml @@ -6,7 +6,7 @@ Minimaler Betrag ist %s Restbestand zu klein Das Zielkonto ist nicht erstellt. Der abzusendende Betrag soll %s + Gebühr oder mehr sein - Erhalt der Gebühr fehlgeschlagen + Erhalt der Gebühr fehlgeschlagen Unbekannter Fehler Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden Um %s XTZ reduzieren diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0694d102a5..8ec21b0632 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -19,7 +19,7 @@ Solde est en cours de téléchargement … Blockchain est inaccessible Compte n\'est pas créé - Téléchargez %s +%s pour créer un compte + Pour créer un compte, téléchargez %s+ %s Carte vide Créez un portefeuille pour commencer à utiliser votre carte Tangem Cette carte n\'est pas prise en charge diff --git a/app/src/main/res/values-fr/strings_blockchain.xml b/app/src/main/res/values-fr/strings_blockchain.xml index 380232b81b..60b04a9489 100644 --- a/app/src/main/res/values-fr/strings_blockchain.xml +++ b/app/src/main/res/values-fr/strings_blockchain.xml @@ -6,7 +6,7 @@ Le montant minimal est de %s Le reste est trop petit Le compte cible n\'a pas été créé. Le montant à envoyer doit être de %s + commissions ou plus - Échec de réception des commissions + Échec de réception des commissions Erreur inconnue Adresse incorrecte Somme incorrecte diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 28789234b2..ba083b4b39 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -17,7 +17,7 @@ Il saldo sta per essere caricato… Blockchain non raggiungibile Conto non creato - Carica %s+ %s per creare un conto + Scarica %s+ %s per creare un account Carta vuota Crea un portafoglio per iniziare ad utilizzare la tua carta Tangem Questa carta non è supportata diff --git a/app/src/main/res/values-it/strings_blockchain.xml b/app/src/main/res/values-it/strings_blockchain.xml index c9842eb6dd..d51ca39197 100644 --- a/app/src/main/res/values-it/strings_blockchain.xml +++ b/app/src/main/res/values-it/strings_blockchain.xml @@ -8,7 +8,7 @@ Errore sconosciuto L\'importo supera il saldo Il conto totale non è stato creato L\'importo da inviare deve essere %s + commissione o maggiore - Impossibile ottenere la commissione + Impossibile ottenere la commissione Indirizzo non valido Importo non valido Il totale supera il saldo diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 172380e133..a709a41b26 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -19,7 +19,7 @@ Загрузка баланса… Блокчейн недоступен Аккаунт не создан - Загрузите %s+ %s для создания учетной записи + Пополните счет на %s+ %s, чтобы создать аккаунт Карта пуста Создайте кошелек, чтобы начать пользоваться картой Tangem Эта карта не поддерживается @@ -89,8 +89,8 @@ Купить Продать Торговать - По умолчанию - Устаревший + По умолчанию + Устаревший Создать кошелек Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты Один кошелек. Две карты. @@ -139,15 +139,13 @@ Символ токена Деривация по BIP44 По-умолчанию - Копировать - Поделиться Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства Проверка подлинности не удалась Эта карта может быть производственным образцом или подделкой Важная информация о безопасности \u26A0 Эта карта ранее подписывала транзакции Узнать подробнее - Я понял + Я понял На этой карте доступно только %s подписей. Вы должны вывести все свои средства. Отключить Подписать diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml index f3be360857..ee2464671d 100644 --- a/app/src/main/res/values-ru/strings_blockchain.xml +++ b/app/src/main/res/values-ru/strings_blockchain.xml @@ -5,7 +5,7 @@ Неверная комиссия Минимальная сумма: %s Сдача слишком мала - Не удалось получить комиссию + Не удалось получить комиссию Аккаунт получателя не создан. Сумма для отправки должна быть %s + комиссия или больше Неизвестная ошибка Неверный адрес diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index af231b0713..ab83f1c625 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,7 +25,7 @@ Blockchain is unreachable Account is not created - Load %s+ %s to create account + Load %s+ %s to create account Empty card Create wallet to start using Tangem card This card is not supported diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml index b2ebd2e2d9..a286c1a5f8 100644 --- a/app/src/main/res/values/strings_blockchain.xml +++ b/app/src/main/res/values/strings_blockchain.xml @@ -5,7 +5,7 @@ Invalid Fee Minimum amount is %s Change is too small - Failed to get fee + Failed to get fee Target account is not created. Amount to send should be %s + fee or more Unknown error Invalid address diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index f82c05ea0e..59a680c908 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -4,8 +4,8 @@ Buy Sell Trade - Default - Legacy + Default + Legacy Create twin wallet Generate wallet keys on both cards to start using your Twins @@ -69,8 +69,6 @@ //Details %s tokens - Copy - Share Please wait for %s transaction to complete to be able to send funds Authenticity check failed @@ -84,7 +82,7 @@ \n\nDo not accept this card as physical payment from someone you don\'t trust. \n\nIt\'s perfectly safe in all other respects. \n\nTangem is the only hardware wallet to offer signature count protection. - I understand + I understand There are only %s signatures available on this card. You must withdraw all of your funds. @@ -251,6 +249,6 @@ Amount to pay: %s Please tell us what card do you have? Please tell us more about your issue. Every small detail can help. Hi support team, - Following information is optional. You can erase it if you don’t want to share it. + Following information is optional. You can erase it if you don\'t want to share it. diff --git a/core/ui/src/main/res/values-de/strings_final.xml b/core/ui/src/main/res/values-de/strings_final.xml index 665f17302a..99b3e6b5b6 100644 --- a/core/ui/src/main/res/values-de/strings_final.xml +++ b/core/ui/src/main/res/values-de/strings_final.xml @@ -26,7 +26,7 @@ Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible - Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. Order @@ -68,7 +68,7 @@ Change access code Access code will be changed on this card only Get your card ready! - Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card App settings Keep the wallet in the app diff --git a/core/ui/src/main/res/values-fr/strings_final.xml b/core/ui/src/main/res/values-fr/strings_final.xml index 665f17302a..99b3e6b5b6 100644 --- a/core/ui/src/main/res/values-fr/strings_final.xml +++ b/core/ui/src/main/res/values-fr/strings_final.xml @@ -26,7 +26,7 @@ Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible - Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. Order @@ -68,7 +68,7 @@ Change access code Access code will be changed on this card only Get your card ready! - Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card App settings Keep the wallet in the app diff --git a/core/ui/src/main/res/values-it/strings_final.xml b/core/ui/src/main/res/values-it/strings_final.xml index 665f17302a..99b3e6b5b6 100644 --- a/core/ui/src/main/res/values-it/strings_final.xml +++ b/core/ui/src/main/res/values-it/strings_final.xml @@ -26,7 +26,7 @@ Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible - Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. Order @@ -68,7 +68,7 @@ Change access code Access code will be changed on this card only Get your card ready! - Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card App settings Keep the wallet in the app diff --git a/core/ui/src/main/res/values/string_common.xml b/core/ui/src/main/res/values/string_common.xml index ad2cf0d79f..85e2e9ef97 100644 --- a/core/ui/src/main/res/values/string_common.xml +++ b/core/ui/src/main/res/values/string_common.xml @@ -1,5 +1,5 @@ - Share - Copy + Share + Copy \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings_final.xml b/core/ui/src/main/res/values/strings_final.xml index 8b0c3fcc01..3460b046ce 100644 --- a/core/ui/src/main/res/values/strings_final.xml +++ b/core/ui/src/main/res/values/strings_final.xml @@ -26,7 +26,7 @@ Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible - Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. Order @@ -60,7 +60,7 @@ Russian bank cards are not accepted at the moment Do you have a bank card of another country or a UnionPay card? Yes - No + No Chat Tangem Bot Card Settings @@ -68,7 +68,7 @@ Change access code Access code will be changed on this card only Get your card ready! - Scan the card to change its settings. The changes will impact only the card you’ve scanned and will not affect other cards tied to your wallet. + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card App settings Keep the wallet in the app From 7f8c5cd23ad952807fc7cfab933ccc6f69c3438e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Nov 2022 16:43:27 +0400 Subject: [PATCH 073/176] Updated on 2026-08-14 --- app/libs/walletconnect-1.5.5.aar | Bin 145525 -> 0 bytes app/libs/walletconnect-1.5.6.aar | Bin 0 -> 140873 bytes .../walletconnect/WalletConnectManager.kt | 34 ------------------ .../walletconnect/WalletConnectSdkHelper.kt | 30 ++++++++++------ 4 files changed, 20 insertions(+), 44 deletions(-) delete mode 100644 app/libs/walletconnect-1.5.5.aar create mode 100644 app/libs/walletconnect-1.5.6.aar diff --git a/app/libs/walletconnect-1.5.5.aar b/app/libs/walletconnect-1.5.5.aar deleted file mode 100644 index 4e2f1b4fb70b95f8fff0e8727a5a73285666b1d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 145525 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 z zpMFRwTrX7WUp>&H@O@Qa+sP64>^8EBsCuYecn2htAdY-+9 zYJ*7bwA}s42K>1Et?8CG_djr=jniv+-4xzNr^vR8Uf1n$wdb-dR;?=$uo#HN^UC(Z zNyhcGvv6P2QOX7fem;~TX5)?ODFF2M9s}{M;7$f;tispfmifu~tomHZ1*}6*_;>B^ zL(fxBtLC-E8?T{eZHU)4I2!L*jvpQ25DUKdyPFv{!c(;q=b&+7Fa;Z&3~jtx#0tRc z0qe%Qi>l%u7GamvDW(Im2-N5|awr_+bgqKuEa#pAipVa}g{z8?Ad4u#jIUJ4TI?eP zA;d-)VXn~oKn*0t$O$Ua^2fi_cQz%?$VDKF=!XIW@d5cz=3Cy2=a4 zBT!yi;fG?P6Qk#p6trur5HCkiwJfYq@=&E15iw~adyvOB&Jo1vPu7-srdMv`O2=y> z$2lJliMJY0=mnVPA~%rdmk$@8Jg<+ATRNVfpX9-As`^g9tMZ9MD;D~!wlmbmQL9VR z=p1?KRy<@xHrYQdlH8zd@W(e5w4h5UAW1u@tJerEfOVZI$?FQX&AHcH(e2( z{FxD(RkP55g_Zj77|p}~m9U`5sDS;{M;bcg>=Ly-HlzH*Pn`Tu@}`CXYx}7*Rz^6? zVMJXYm~v@r77@r3`VXCnat61e`*FA~Gq9O?KR>UGE2QJf6BU>1H|l79^!Xs{(N^Ra zS;KH?-N{!R14E%Gk6Un5i1boz~uzSW;u0No=vKs z0F8{ba&Dt*YFITOBOp`>VsO#Qa;g--2`eW)3D=A(1!ytOW7ilhnDUQ=>gbY*e8?4=$Eu@&jC;N$Vi@FCm zx_>Z5E6S}BrB8S)D#(sz#{~t6$YlSxqKQ$4w&PqWDvswx;NrJ!zQW#1VxEO-qYK1h79~J z#$T7Y5{X$SG&W*N9(+HdgYcx&AcYvb}AU$Nbjk2M?M4WO77QfYr>5MX`K$QaAdvpZHC5+3lx0doK%4& z#Zn-8=GQ_vcnK#z{~9_%Xe2&^H3K#Di&m8drsm=>;B=mmxB zIrww$o?u1!zm-NgM}J-gyd{q7&4K!?VflS;5=kRi{%jtJnKV(D7fCT{Yv+|HYR=p^nziw76Ej5wKch-eXFlYh)VM?%($yBV>bJc+DmBl=QBoD$Q^Q5e3nT}As$o#p2b^aiRld}!{8fPMLI@1 z34nVwU_fXa`K6opX--U=@?H_vrW5d!jib?7B}cX$2cj2t_DKWcnmS!SQ3Xab#h8L5 zH)|?w(*Xu%+LP3Z^aWKhyYd-VwuL4OSOcW#|50@c~5>%P*# zMt~O6?!|`DG9Zb2=%IJ2b%>3lQI0`yA`CYs_g7s+49Uwxx5l(wiDcT=M$X>hnc~c_ zcGYn2zUT9;*|?SoT~Vtjx1f8tqdkko7nMno1W-!YE3}r-MgS%&4?M07ec$=2zT&8q zA9rw4zih5>ah#Cd@Lj2R%fp#YWzt(_9iVEVF~5YdA*rrdCQxvQR@=xRd@Vul+kNg=k-fr%_XJMIm*80(JY|`&J?!YaWs))TB>Xj~tei>OSaomT z)<79#5kXG5yv0ZL)qpo{wl8-aS+uk(GdeGDdy6Rjk9^R)G(durY#ewp+LwiqpNHD%*Vm7dtkN!v5h2Gcs%JN18qpPI;K+1@DP9r-(-G8P>Ln7k5jep1Qpdk1&YwD+NCCaDO z5#3y&(m+?7Y^)*icQ26tuKdOWLXOvjkOoQ!!hc8kqK;xlC?~WOuH4c*LzJ+T<**=SJ}E}-Sc3x%|*~T`exp@u!~tXupEJum}4G1!&WQD1f`dNVEZ8h@Imtz3}m8 z`JvxYA#X
    }N=oceQ#D#Fi-?S@P@t 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

    5oS4I>7)?w_Xs%1iaqlO$J6o`+>mHPde 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 diff --git a/app/libs/walletconnect-1.5.6.aar b/app/libs/walletconnect-1.5.6.aar new file mode 100644 index 0000000000000000000000000000000000000000..30c576fb89d553f3a3a2ac804178fc36783263dd GIT binary patch literal 140873 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

    W#GDQG#Ij0{dqK38heN%l{xK2@V7#|L-7ZWouyk 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 literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index bfd63f82d0..37e488ccf8 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -475,40 +475,6 @@ class WalletConnectManager { store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session)) } } - client.onCustomRequest = { id: Long, data: String -> - Timber.d("Custom Request") - Timber.d(data) - val request = EthSignHelper.parseCustomRequest(data) - - when (request.method) { - WCMethodExtended.ETH_SIGN_TYPE_DATA_V4 -> handleTypedDataV4(request, client, id) - else -> { - Timber.d("WC: unrecognized custom request") - } - } - } - - } - - private fun handleTypedDataV4(request: CustomJsonRpcRequest, client: WCClient, id: Long) { - val message = EthSignHelper.tryToParseEthTypedMessage(request) - if (message != null) { - Timber.d("onEthSign_v4: $message") - // Analytics.logWcEvent( - // AnalyticsAnOld.WcAnalyticsEvent.Action( - // AnalyticsAnOld.WcAction.PersonalSign - // ) - // ) - sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain( - WalletConnectAction.HandlePersonalSignRequest( - message, - sessionData, - id - ) - ) - } - } } companion object { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index cb5fa5b527..def1cb4a92 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -39,6 +39,9 @@ import com.tangem.tap.store import com.tangem.tap.tangemSdk import com.tangem.tap.tangemSdkManager import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage +import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.MESSAGE +import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE +import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.TYPED_MESSAGE import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction import timber.log.Timber import java.math.BigDecimal @@ -206,19 +209,15 @@ class WalletConnectSdkHelper { session: WalletConnectSession, id: Long, ): WcPersonalSignData { - val messageData = - try { - message.data.removePrefix(HEX_PREFIX).hexToBytes() - } catch (exception: Exception) { - message.data.asciiToHex()!!.hexToBytes() - } + val messageData = when (message.type) { + MESSAGE, PERSONAL_MESSAGE -> createMessageData(message) + TYPED_MESSAGE -> EthereumUtils.makeTypedDataHash(message.data) + } + val messageString = message.data.hexToAscii() ?: EthSignHelper.tryToParseEthTypedMessageString(message.data) ?: message.data - val prefixData = (ETH_MESSAGE_PREFIX + messageData.size.toString()).toByteArray() - val hashToSign = (prefixData + messageData).toKeccak() - val dialogData = PersonalSignDialogData( dAppName = session.peerMeta.name, message = messageString, @@ -226,13 +225,24 @@ class WalletConnectSdkHelper { id = id, ) return WcPersonalSignData( - hash = hashToSign, + hash = messageData, session = session, id = id, dialogData = dialogData, ) } + private fun createMessageData(message: WCEthereumSignMessage): ByteArray { + val messageData = try { + message.data.removePrefix(HEX_PREFIX).hexToBytes() + } catch (exception: Exception) { + message.data.asciiToHex()?.hexToBytes() ?: byteArrayOf() + } + + val prefixData = (ETH_MESSAGE_PREFIX + messageData.size.toString()).toByteArray() + return (prefixData + messageData).toKeccak() + } + private fun String.hexToAscii(): String? { return try { removePrefix(HEX_PREFIX).hexToBytes() From 1c7c499166e18dc7b224cf67f649d796eca72976 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Dec 2022 16:25:36 +0300 Subject: [PATCH 074/176] Updated on 2026-08-14 --- .../disclaimer/ui/DisclaimerFragment.kt | 50 +++++++------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index b13e5b588b..633ef10d6a 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -2,13 +2,13 @@ package com.tangem.tap.features.disclaimer.ui import android.os.Bundle import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.tap.common.extensions.configureSettings import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.BaseFragment +import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.disclaimer.redux.DisclaimerState import com.tangem.tap.store @@ -16,32 +16,23 @@ import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentDisclaimerBinding import org.rekotlin.StoreSubscriber -class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer), - StoreSubscriber { +class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubscriber { private val binding: FragmentDisclaimerBinding by viewBinding(FragmentDisclaimerBinding::bind) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - activity?.onBackPressedDispatcher?.addCallback( - this, - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - } - }, - ) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) - exitTransition = inflater.inflateTransition(android.R.transition.slide_top) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + addBackPressHandler(handler = this) + binding.toolbar.setNavigationOnClickListener { handleOnBackPressed() } + binding.webView.configureSettings() } override fun onStart() { super.onStart() - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.disclaimerState == newState.disclaimerState - }.select { it.disclaimerState } + store.subscribe(subscriber = this) { state -> + state + .skipRepeats { oldState, newState -> oldState.disclaimerState == newState.disclaimerState } + .select(AppState::disclaimerState) } } @@ -50,16 +41,11 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer), store.unsubscribe(this) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.toolbar.setNavigationOnClickListener { - store.dispatch(NavigationAction.PopBackTo()) - } - initViews() - } - - private fun initViews() { - binding.webView.configureSettings() + override fun configureTransitions() { + super.configureTransitions() + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) } override fun newState(state: DisclaimerState) = with(binding) { From cf0f05d5b73b61c344c397069d77d5336e9807cc Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 2 Dec 2022 15:40:02 +0400 Subject: [PATCH 075/176] Updated on 2026-08-14 --- buildSrc/src/main/java/Versions.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 54b4716dc7..fda7453ad1 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -58,8 +58,11 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-133" + const val tangemBlockchainSdk = "develop-140" + + // const val tangemBlockchainSdk = "0.0.1" const val tangemCardSgk = "develop-165" + // const val tangemCardSgk = "0.0.1" // endregion Tangem // region Testing From 0ac95e5c2aa5b7a5f3c12e46052c14d1c51dea67 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 5 Dec 2022 13:23:50 +0400 Subject: [PATCH 076/176] Updated on 2026-08-14 --- .../com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt | 3 +++ .../java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt | 1 + 2 files changed, 4 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 97c8a9e41f..632f9f965b 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -11,6 +11,7 @@ import com.tangem.blockchain.common.address.Address import com.tangem.common.card.CardWallet import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.domain.extensions.getUserWalletId class AdditionalFeedbackInfo { class EmailWalletInfo( @@ -29,6 +30,7 @@ class AdditionalFeedbackInfo { var cardFirmwareVersion: String = "" var cardIssuer: String = "" var cardBlockchain: String = "" + var userWalletId: String = "" // wallets internal val walletsInfo = mutableListOf() @@ -52,6 +54,7 @@ class AdditionalFeedbackInfo { cardFirmwareVersion = data.card.firmwareVersion.stringValue cardIssuer = data.card.issuer.name signedHashesCount = formatSignedHashes(data.card.wallets) + userWalletId = data.card.getUserWalletId() } fun setWalletsInfo(walletManagers: List) { diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt index 46cf302fee..4baf39c3e2 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt @@ -22,6 +22,7 @@ class FeedbackDataBuilder( builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain) builder.appendKeyValue("", infoHolder.signedHashesCount) + builder.appendKeyValue("User Wallet ID", infoHolder.userWalletId) return this } From ee722c5f466eaddb209bd3cf1504ac4503427eaf Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 21 Nov 2022 17:21:57 +0300 Subject: [PATCH 077/176] Updated on 2026-08-14 --- .../tap/common/extensions/Navigation.kt | 2 + .../redux/navigation/NavigationState.kt | 1 + .../tangem/utils/coroutines/CoroutineExt.kt | 10 ++ .../referral/presentation/build.gradle.kts | 3 + .../feature/referral/ReferralFragment.kt | 27 ++++ .../referral/models/ReferralStateHolder.kt | 35 +++-- .../presentation/ReferralViewModel.kt | 11 -- .../referral/ui/NonParticipateBottomBlock.kt | 127 +++------------- .../referral/ui/ParticipateBottomBlock.kt | 137 +++++++++++++++--- .../feature/referral/ui/ReferralScreen.kt | 81 +++++------ .../referral/viewmodels/ReferralViewModel.kt | 91 ++++++++++++ 11 files changed, 327 insertions(+), 198 deletions(-) create mode 100644 core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt delete mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/presentation/ReferralViewModel.kt create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 5e07c484b8..97575ecd31 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -6,6 +6,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager import com.tangem.common.extensions.VoidCallback +import com.tangem.feature.referral.ReferralFragment import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment @@ -108,5 +109,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.WalletDetails -> WalletDetailsFragment() AppScreen.WalletConnectSessions -> WalletConnectFragment() AppScreen.QrScan -> QrScanFragment() + AppScreen.ReferralProgram -> ReferralFragment() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index 05ec744393..6d5993cbc0 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -22,4 +22,5 @@ enum class AppScreen( AddTokens, AddCustomToken, WalletConnectSessions, QrScan, + ReferralProgram } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt new file mode 100644 index 0000000000..87097ce482 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -0,0 +1,10 @@ +package com.tangem.utils.coroutines + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +suspend fun runCatching(dispatcher: CoroutineDispatcher, block: suspend () -> R): Result { + return runCatching { + withContext(dispatcher) { block() } + } +} \ No newline at end of file diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index b2763c706c..226a789737 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -34,10 +34,13 @@ android { dependencies { /** Core modules */ + implementation(project(":core:utils")) implementation(project(":core:ui")) /** AndroidX */ implementation(AndroidX.appCompat) + implementation(AndroidX.fragmentKtx) + implementation(AndroidX.lifecycleViewModelKtx) /** Compose */ implementation(Compose.foundation) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt new file mode 100644 index 0000000000..34df965d31 --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt @@ -0,0 +1,27 @@ +package com.tangem.feature.referral + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.tangem.feature.referral.ui.ReferralScreen +import com.tangem.feature.referral.viewmodels.ReferralViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class ReferralFragment : Fragment() { + private val viewModel by viewModels().apply { + // TODO("[REDACTED_JIRA]") this.value.setOnBackClicked() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return ComposeView(inflater.context).apply { + setContent { + ReferralScreen(stateHolder = viewModel.uiState) + } + } + } +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt index 0f9a5666b3..e001315199 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt @@ -3,36 +3,39 @@ package com.tangem.feature.referral.models data class ReferralStateHolder( val headerState: HeaderState, val referralInfoState: ReferralInfoState, - val effects: Effects, - val agreementBottomSheetState: AgreementBottomSheetState + val errorToast: ErrorToast, ) { data class HeaderState(val onBackClicked: () -> Unit) - sealed interface ReferralInfoContentState + sealed interface ReferralInfoContentState { + val award: String + val discount: String + val url: String + } sealed interface ReferralInfoState { - data class ParticipantContent( - val award: String, + override val award: String, val address: String, - val discount: String, - val onParticipateClicked: () -> Unit, + override val discount: String, + val purchasedWalletCount: Int, + val code: String, + override val url: String, ) : ReferralInfoState, ReferralInfoContentState data class NonParticipantContent( - val award: String, - val discount: String, - val purchasedWalletCount: Int, - val code: String, - val onCopyClicked: () -> Unit, - val onShareClicked: () -> Unit, + override val award: String, + override val discount: String, + override val url: String, + val onParticipateClicked: () -> Unit, ) : ReferralInfoState, ReferralInfoContentState object Loading : ReferralInfoState } - data class Effects(val showErrorToast: Boolean) - - data class AgreementBottomSheetState(val url: String) + data class ErrorToast( + val visibility: Boolean, + val changeVisibility: () -> Unit + ) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/presentation/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/presentation/ReferralViewModel.kt deleted file mode 100644 index e106965a14..0000000000 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/presentation/ReferralViewModel.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.feature.referral.presentation - -import androidx.lifecycle.ViewModel -import com.tangem.feature.referral.domain.ReferralInteractor -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class ReferralViewModel @Inject constructor(referralInteractor: ReferralInteractor) : ViewModel() { - -} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index f202bd4ef6..a98b1e8a93 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -1,40 +1,40 @@ package com.tangem.feature.referral.ui import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.PrimaryStartIconButton -import com.tangem.core.ui.components.SmallInfoCard +import com.tangem.core.ui.components.PrimaryEndIconButton import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TextColorType -import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.presentation.R +@Composable +fun NonParticipateBottomBlock(onAgreementClicked: () -> Unit, onParticipateClicked: () -> Unit) { + Column { + AgreementText( + firstPartResId = R.string.referral_tos_not_enroled_prefix, + onClicked = onAgreementClicked, + ) + PrimaryEndIconButton( + modifier = Modifier.padding(all = dimensionResource(id = R.dimen.spacing16)), + text = stringResource(id = R.string.referral_button_participate), + iconResId = R.drawable.ic_tangem, + onClicked = onParticipateClicked, + ) + } +} + @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_NonParticipateBottomBlock_InLightTheme() { TangemTheme(isDark = false) { Column(Modifier.background(MaterialTheme.colors.primary)) { - NonParticipateBottomBlock( - purchasedWalletCount = 3, - code = "x4JdK", - onCopyClicked = {}, - onShareClicked = {}, - onAgreementClicked = {}, - ) + NonParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) } } } @@ -44,96 +44,7 @@ fun Preview_NonParticipateBottomBlock_InLightTheme() { fun Preview_NonParticipateBottomBlock_InDarkTheme() { TangemTheme(isDark = true) { Column(Modifier.background(MaterialTheme.colors.primary)) { - NonParticipateBottomBlock( - purchasedWalletCount = 3, - code = "x4JdK", - onCopyClicked = {}, - onShareClicked = {}, - onAgreementClicked = {}, - ) + NonParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) } } -} - -@Composable -fun NonParticipateBottomBlock( - purchasedWalletCount: Int, - code: String, - onCopyClicked: () -> Unit, - onShareClicked: () -> Unit, - onAgreementClicked: () -> Unit, -) { - Column( - modifier = Modifier - .padding( - top = dimensionResource(id = R.dimen.spacing24), - bottom = dimensionResource(id = R.dimen.spacing16), - ) - .padding(horizontal = dimensionResource(id = R.dimen.spacing16)), - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)), - ) { - SmallInfoCard( - startText = stringResource(id = R.string.referral_friends_bought_title), - endText = String.format( - stringResource(id = R.string.referral_wallets_purchased_count), - purchasedWalletCount, - ), - ) - PersonalCodeCard(code = code) - AdditionalButtons(onCopyClicked = onCopyClicked, onShareClicked = onShareClicked) - AgreementText(firstPartResId = R.string.referral_tos_enroled_prefix, onClicked = onAgreementClicked) - } -} - -@Composable -private fun PersonalCodeCard(code: String) { - Column( - modifier = Modifier - .shadow( - elevation = dimensionResource(id = R.dimen.elevation2), - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), - ) - .background( - color = MaterialTheme.colors.secondary, - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), - ) - .fillMaxWidth() - .padding(vertical = dimensionResource(id = R.dimen.spacing12)), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing4)), - ) { - Text( - text = stringResource(id = R.string.referral_promo_code_title), - color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), - maxLines = 1, - style = MaterialTheme.typography.subtitle2, - ) - Text( - text = code, - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), - maxLines = 1, - style = MaterialTheme.typography.h2, - ) - } -} - -@Composable -private fun AdditionalButtons(onCopyClicked: () -> Unit, onShareClicked: () -> Unit) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)), - ) { - PrimaryStartIconButton( - modifier = Modifier.weight(1f), - text = stringResource(id = R.string.common_copy), - iconResId = R.drawable.ic_copy, - onClicked = onCopyClicked, - ) - PrimaryStartIconButton( - modifier = Modifier.weight(1f), - text = stringResource(id = R.string.common_share), - iconResId = R.drawable.ic_share, - onClicked = onShareClicked, - ) - } } \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index d58527d894..775896aa6c 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -1,24 +1,135 @@ package com.tangem.feature.referral.ui +import android.content.Intent import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.PrimaryEndIconButton +import androidx.core.content.ContextCompat.startActivity +import com.tangem.core.ui.components.PrimaryStartIconButton +import com.tangem.core.ui.components.SmallInfoCard import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TextColorType +import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.presentation.R +@Composable +fun ParticipateBottomBlock( + purchasedWalletCount: Int, + code: String, + onAgreementClicked: () -> Unit, +) { + Column( + modifier = Modifier + .padding( + top = dimensionResource(id = R.dimen.spacing24), + bottom = dimensionResource(id = R.dimen.spacing16), + ) + .padding(horizontal = dimensionResource(id = R.dimen.spacing16)), + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)), + ) { + SmallInfoCard( + startText = stringResource(id = R.string.referral_friends_bought_title), + endText = String.format( + stringResource(id = R.string.referral_wallets_purchased_count), + purchasedWalletCount, + ), + ) + PersonalCodeCard(code = code) + AdditionalButtons(code = code) + AgreementText(firstPartResId = R.string.referral_tos_enroled_prefix, onClicked = onAgreementClicked) + } +} + +@Composable +private fun PersonalCodeCard(code: String) { + Column( + modifier = Modifier + .shadow( + elevation = dimensionResource(id = R.dimen.elevation2), + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + ) + .background( + color = MaterialTheme.colors.secondary, + shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + ) + .fillMaxWidth() + .padding(vertical = dimensionResource(id = R.dimen.spacing12)), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing4)), + ) { + Text( + text = stringResource(id = R.string.referral_promo_code_title), + color = MaterialTheme.colors.textColor(type = TextColorType.TERTIARY), + maxLines = 1, + style = MaterialTheme.typography.subtitle2, + ) + Text( + text = code, + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + maxLines = 1, + style = MaterialTheme.typography.h2, + ) + } +} + +@Composable +private fun AdditionalButtons(code: String) { + val clipboardManager = LocalClipboardManager.current + val context = LocalContext.current + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)), + ) { + PrimaryStartIconButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.common_copy), + iconResId = R.drawable.ic_copy, + onClicked = { clipboardManager.setText(AnnotatedString(code)) }, + ) + PrimaryStartIconButton( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.common_share), + iconResId = R.drawable.ic_share, + onClicked = { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, code) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(context, shareIntent, null) + }, + ) + } +} + @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ParticipateBottomBlock_InLightTheme() { TangemTheme(isDark = false) { Column(Modifier.background(MaterialTheme.colors.primary)) { - ParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) + ParticipateBottomBlock( + purchasedWalletCount = 3, + code = "x4JdK", + onAgreementClicked = {}, + ) } } } @@ -28,23 +139,11 @@ fun Preview_ParticipateBottomBlock_InLightTheme() { fun Preview_ParticipateBottomBlock_InDarkTheme() { TangemTheme(isDark = true) { Column(Modifier.background(MaterialTheme.colors.primary)) { - ParticipateBottomBlock(onAgreementClicked = {}, onParticipateClicked = {}) + ParticipateBottomBlock( + purchasedWalletCount = 3, + code = "x4JdK", + onAgreementClicked = {}, + ) } } -} - -@Composable -fun ParticipateBottomBlock(onAgreementClicked: () -> Unit, onParticipateClicked: () -> Unit) { - Column { - AgreementText( - firstPartResId = R.string.referral_tos_not_enroled_prefix, - onClicked = onAgreementClicked, - ) - PrimaryEndIconButton( - modifier = Modifier.padding(all = dimensionResource(id = R.dimen.spacing16)), - text = stringResource(id = R.string.referral_button_participate), - iconResId = R.drawable.ic_tangem, - onClicked = onParticipateClicked, - ) - } } \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 7091674dcd..708aa9431e 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.referral.ui +import android.widget.Toast import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -26,10 +27,12 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -49,6 +52,7 @@ import com.tangem.core.ui.res.buttonColor import com.tangem.core.ui.res.iconColor import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.models.ReferralStateHolder +import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoContentState import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState import com.tangem.feature.referral.presentation.R import com.valentinilk.shimmer.shimmer @@ -69,7 +73,11 @@ fun ReferralScreen(stateHolder: ReferralStateHolder) { TangemTheme { BottomSheetScaffold( - sheetContent = { AgreementBottomSheetContent(stateHolder.agreementBottomSheetState.url) }, + sheetContent = { + AgreementBottomSheetContent( + url = (stateHolder.referralInfoState as ReferralInfoContentState).url, + ) + }, scaffoldState = bottomSheetScaffoldState, sheetShape = RoundedCornerShape( topStart = dimensionResource(id = R.dimen.radius16), @@ -92,6 +100,15 @@ fun ReferralScreen(stateHolder: ReferralStateHolder) { ) }, ) + + val context = LocalContext.current + SideEffect { + if (stateHolder.errorToast.visibility) { + //TODO("[REDACTED_JIRA]") + Toast.makeText(context, "", Toast.LENGTH_LONG).show() + stateHolder.errorToast.changeVisibility() + } + } } } @@ -137,12 +154,11 @@ fun ReferralInfo(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Uni when (val state = stateHolder.referralInfoState) { is ReferralInfoState.ParticipantContent -> { Conditions(state = state) - VerticalSpacer(spaceResId = R.dimen.spacing44) ParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked) } is ReferralInfoState.NonParticipantContent -> { Conditions(state = state) - VerticalSpacer(spaceResId = R.dimen.spacing24) + VerticalSpacer(spaceResId = R.dimen.spacing44) NonParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked) } is ReferralInfoState.Loading -> { @@ -162,14 +178,14 @@ private fun AppBar(stateHolder: ReferralStateHolder) { } @Composable -private fun Conditions(state: ReferralStateHolder.ReferralInfoContentState) { +private fun Conditions(state: ReferralInfoContentState) { ConditionForYou(state = state) VerticalSpacer(spaceResId = R.dimen.spacing32) ConditionForYourFriend(state = state) } @Composable -private fun ConditionForYou(state: ReferralStateHolder.ReferralInfoContentState) { +private fun ConditionForYou(state: ReferralInfoContentState) { Condition(iconResId = R.drawable.ic_tether) { when (state) { is ReferralInfoState.ParticipantContent -> InfoForYou(award = state.award, address = state.address) @@ -179,7 +195,7 @@ private fun ConditionForYou(state: ReferralStateHolder.ReferralInfoContentState) } @Composable -private fun ConditionForYourFriend(state: ReferralStateHolder.ReferralInfoContentState) { +private fun ConditionForYourFriend(state: ReferralInfoContentState) { Condition(iconResId = R.drawable.ic_discount) { when (state) { is ReferralInfoState.ParticipantContent -> InfoForYourFriend(discount = state.discount) @@ -306,19 +322,17 @@ private fun ShimmerInfo() { @Composable private fun ParticipantBottomBlock(state: ReferralInfoState.ParticipantContent, onAgreementClicked: () -> Unit) { ParticipateBottomBlock( + purchasedWalletCount = state.purchasedWalletCount, + code = state.code, onAgreementClicked = onAgreementClicked, - onParticipateClicked = state.onParticipateClicked, ) } @Composable private fun NonParticipantBottomBlock(state: ReferralInfoState.NonParticipantContent, onAgreementClicked: () -> Unit) { NonParticipateBottomBlock( - purchasedWalletCount = state.purchasedWalletCount, - code = state.code, - onCopyClicked = state.onCopyClicked, - onShareClicked = state.onShareClicked, onAgreementClicked = onAgreementClicked, + onParticipateClicked = state.onParticipateClicked, ) } @@ -333,12 +347,11 @@ fun Preview_ReferralScreen_Participant_InLightTheme() { award = "10 USDT", address = "ma80...zk8q2", discount = "10%", - onParticipateClicked = {}, - ), - effects = ReferralStateHolder.Effects(showErrorToast = false), - agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( + purchasedWalletCount = 3, + code = "x4JdK", url = "", ), + errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), ), ) } @@ -355,13 +368,11 @@ fun Preview_ReferralScreen_Participant_InDarkTheme() { award = "10 USDT", address = "ma80...zk8q2", discount = "10%", - - onParticipateClicked = {}, - ), - effects = ReferralStateHolder.Effects(showErrorToast = false), - agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( + purchasedWalletCount = 3, + code = "x4JdK", url = "", ), + errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), ), ) } @@ -377,16 +388,10 @@ fun Preview_ReferralScreen_NonParticipant_InLightTheme() { referralInfoState = ReferralInfoState.NonParticipantContent( award = "10 USDT", discount = "10%", - purchasedWalletCount = 3, - code = "x4JdK", - onCopyClicked = {}, - onShareClicked = {}, - - ), - effects = ReferralStateHolder.Effects(showErrorToast = false), - agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( url = "", + onParticipateClicked = {}, ), + errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), ), ) } @@ -402,16 +407,10 @@ fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { referralInfoState = ReferralInfoState.NonParticipantContent( award = "10 USDT", discount = "10%", - purchasedWalletCount = 3, - code = "x4JdK", - onCopyClicked = {}, - onShareClicked = {}, - - ), - effects = ReferralStateHolder.Effects(showErrorToast = false), - agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( url = "", + onParticipateClicked = {}, ), + errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), ), ) } @@ -425,10 +424,7 @@ fun Preview_ReferralScreen_Loading_InLightTheme() { stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.Loading, - effects = ReferralStateHolder.Effects(showErrorToast = false), - agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( - url = "", - ), + errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), ), ) } @@ -442,10 +438,7 @@ fun Preview_ReferralScreen_Loading_InDarkTheme() { stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.Loading, - effects = ReferralStateHolder.Effects(showErrorToast = false), - agreementBottomSheetState = ReferralStateHolder.AgreementBottomSheetState( - url = "", - ), + errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), ), ) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt new file mode 100644 index 0000000000..ae29377adf --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt @@ -0,0 +1,91 @@ +package com.tangem.feature.referral.viewmodels + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tangem.feature.referral.domain.ReferralInteractor +import com.tangem.feature.referral.domain.models.DiscountType +import com.tangem.feature.referral.domain.models.ReferralData +import com.tangem.feature.referral.models.ReferralStateHolder +import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class ReferralViewModel @Inject constructor( + private val referralInteractor: ReferralInteractor, + private val dispatchers: CoroutineDispatcherProvider, +) : ViewModel() { + + var uiState by mutableStateOf(createInitiallyUiState()) + private set + + init { + loadReferralData() + } + + fun setOnBackClicked(onBackClicked: () -> Unit) { + uiState = uiState.copy(headerState = ReferralStateHolder.HeaderState(onBackClicked = onBackClicked)) + } + + private fun createInitiallyUiState() = ReferralStateHolder( + headerState = ReferralStateHolder.HeaderState(onBackClicked = { }), + referralInfoState = ReferralInfoState.Loading, + errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = ::cancelErrorToast), + ) + + private fun loadReferralData() { + uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) + viewModelScope.launch(dispatchers.main) { + runCatching(dispatchers.io) { referralInteractor.getReferralStatus() } + .onSuccess { uiState = uiState.copy(referralInfoState = it.convertToReferralInfoState()) } + .onFailure { uiState = uiState.copy(errorToast = uiState.errorToast.copy(visibility = true)) } + } + } + + private fun ReferralData.convertToReferralInfoState(): ReferralInfoState = when (this) { + is ReferralData.ParticipantData -> ReferralInfoState.ParticipantContent( + award = getAwardValue(), + address = referral.address, + discount = getDiscountValue(), + purchasedWalletCount = referral.walletsPurchased, + code = referral.promocode, + url = tosLink, + ) + is ReferralData.NonParticipantData -> ReferralInfoState.NonParticipantContent( + award = getAwardValue(), + discount = getDiscountValue(), + url = tosLink, + onParticipateClicked = ::onParticipateClicked, + ) + } + + private fun ReferralData.getAwardValue(): String = + "$award ${requireNotNull(tokens.firstOrNull()?.symbol) { "Token list is empty" }}" + + private fun ReferralData.getDiscountValue(): String { + val discountSymbol = when (discountType) { + DiscountType.PERCENTAGE -> "%" + DiscountType.VALUE -> throw java.lang.IllegalStateException("Value doesn't support") + } + return "$discount $discountSymbol" + } + + private fun onParticipateClicked() { + uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) + viewModelScope.launch(dispatchers.main) { + runCatching(dispatchers.io) { referralInteractor.startReferral() } + .onSuccess { uiState = uiState.copy(referralInfoState = it.convertToReferralInfoState()) } + .onFailure { uiState = uiState.copy(errorToast = uiState.errorToast.copy(visibility = true)) } + } + } + + private fun cancelErrorToast() { + uiState = uiState.copy(errorToast = uiState.errorToast.copy(visibility = false)) + } +} \ No newline at end of file From 67a245a19c5c1cbf1420932982e2af1cb18cdfc2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 21 Nov 2022 15:59:37 +0300 Subject: [PATCH 078/176] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../main/java/com/tangem/tap/MainActivity.kt | 6 + .../java/com/tangem/tap/TapApplication.kt | 21 +++- .../com/tangem/tap/common/redux/AppReducer.kt | 7 +- .../tap/common/redux/global/GlobalReducer.kt | 8 +- .../wallet/redux/reducers/WalletReducer.kt | 77 ++++++------ .../tap/network/auth/AuthProviderImpl.kt | 12 ++ .../tangem/tap/network/auth/di/AuthModule.kt | 21 ++++ .../com/tangem/tap/proxy/AppStateHolder.kt | 12 +- .../tangem/tap/proxy/DerivationManagerImpl.kt | 118 +++++++++++++++++- core/datasource/build.gradle.kts | 3 + .../datasource/api/AuthHeaderInterceptor.kt | 24 ++++ .../com/tangem/datasource/di/NetworkModule.kt | 7 +- libs/auth/.gitignore | 1 + libs/auth/build.gradle.kts | 23 ++++ libs/auth/src/main/AndroidManifest.xml | 2 + .../java/com/tangem/lib/auth/AuthProvider.kt | 12 ++ settings.gradle.kts | 1 + 18 files changed, 296 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/network/auth/AuthProviderImpl.kt create mode 100644 app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt create mode 100644 libs/auth/.gitignore create mode 100644 libs/auth/build.gradle.kts create mode 100644 libs/auth/src/main/AndroidManifest.xml create mode 100644 libs/auth/src/main/java/com/tangem/lib/auth/AuthProvider.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e68e3543e7..81a98a6e98 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,6 +111,7 @@ dependencies { implementation(project(":common")) implementation(project(":core:ui")) implementation(project(":libs:crypto")) + implementation(project(":libs:auth")) /** Features */ implementation(project(":features:referral:presentation")) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index ad1892a9a5..05e9eed54d 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -26,6 +26,7 @@ import com.tangem.tap.common.shop.GooglePayService.Companion.LOAD_PAYMENT_DATA_R import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.features.shop.redux.ShopAction +import com.tangem.tap.proxy.AppStateHolder import com.tangem.wallet.R import com.tangem.wallet.databinding.ActivityMainBinding import dagger.hilt.android.AndroidEntryPoint @@ -33,6 +34,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import java.lang.ref.WeakReference +import javax.inject.Inject import kotlin.coroutines.CoroutineContext lateinit var tangemSdk: TangemSdk @@ -51,6 +53,9 @@ val mainScope = CoroutineScope(mainCoroutineContext) @AndroidEntryPoint class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { + @Inject + lateinit var appStateHolder: AppStateHolder + private var snackbar: Snackbar? = null private val dialogManager = DialogManager() private val intentHandler = IntentHandler() @@ -66,6 +71,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac tangemSdk = TangemSdk.init(this, TangemSdkManager.config) tangemSdkManager = TangemSdkManager(tangemSdk, this) + appStateHolder.tangemSdkManager = tangemSdkManager backupService = BackupService.init(tangemSdk, this) store.dispatch( diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index d5491a6088..287f2d7a1e 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -41,18 +41,16 @@ import com.tangem.tap.domain.walletconnect.WalletConnectRepository import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.persistence.CardBalanceStateAdapter import com.tangem.tap.persistence.PreferencesStorage +import com.tangem.tap.proxy.AppStateHolder import com.tangem.wallet.BuildConfig import com.zendesk.logger.Logger import dagger.hilt.android.HiltAndroidApp import org.rekotlin.Store import timber.log.Timber import zendesk.chat.Chat +import javax.inject.Inject -val store = Store( - reducer = ::appReducer, - middleware = AppState.getMiddleware(), - state = AppState(), -) +lateinit var store: Store lateinit var foregroundActivityObserver: ForegroundActivityObserver lateinit var preferencesStorage: PreferencesStorage @@ -64,9 +62,21 @@ lateinit var userTokensRepository: UserTokensRepository @HiltAndroidApp class TapApplication : Application(), ImageLoaderFactory { + @Inject + lateinit var appStateHolder: AppStateHolder + override fun onCreate() { super.onCreate() + store = Store( + reducer = { action, state -> + appReducer(action, state, appStateHolder) + }, + middleware = AppState.getMiddleware(), + state = AppState(), + ) + appStateHolder.mainStore = store + if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) } @@ -91,6 +101,7 @@ class TapApplication : Application(), ImageLoaderFactory { context = this, tangemTechService = store.state.domainNetworks.tangemTechService, ) + appStateHolder.userTokensRepository = userTokensRepository } private fun initMoshiConverter() { diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index cbfa2565fb..a14cdb9925 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -14,20 +14,21 @@ import com.tangem.tap.features.send.redux.reducers.SendScreenReducer import com.tangem.tap.features.shop.redux.ShopReducer import com.tangem.tap.features.tokens.redux.TokensReducer import com.tangem.tap.features.wallet.redux.reducers.WalletReducer +import com.tangem.tap.proxy.AppStateHolder import org.rekotlin.Action -fun appReducer(action: Action, state: AppState?): AppState { +fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder): AppState { requireNotNull(state) if (action is AppAction.RestoreState) return action.state return AppState( navigationState = NavigationReducer.reduce(action, state), - globalState = globalReducer(action, state), + globalState = globalReducer(action, state, appStateHolder), homeState = HomeReducer.reduce(action, state), onboardingNoteState = OnboardingNoteReducer.reduce(action, state), onboardingWalletState = OnboardingWalletReducer.reduce(action, state), onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state), - walletState = WalletReducer.reduce(action, state), + walletState = WalletReducer.reduce(action, state, appStateHolder), twinCardsState = TwinCardsReducer.reduce(action, state), sendState = SendScreenReducer.reduce(action, state.sendState), detailsState = DetailsReducer.reduce(action, state), diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 8c04197c58..f8563c233a 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -5,9 +5,10 @@ import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.preferencesStorage +import com.tangem.tap.proxy.AppStateHolder import org.rekotlin.Action -fun globalReducer(action: Action, state: AppState): GlobalState { +fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolder): GlobalState { if (action !is GlobalAction) return state.globalState @@ -32,6 +33,7 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState.copy(scanCardFailsCounter = 0) } is GlobalAction.SaveScanNoteResponse -> { + appStateHolder.scanResponse = action.scanResponse domainStore.dispatch(DomainGlobalAction.SaveScanNoteResponse(action.scanResponse)) globalState.copy(scanResponse = action.scanResponse) } @@ -70,7 +72,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState { } is GlobalAction.SetIfCardVerifiedOnline -> globalState.copy(cardVerifiedOnline = action.verified) - is GlobalAction.FetchUserCountry.Success -> globalState.copy(userCountryCode = action.countryCode) + is GlobalAction.FetchUserCountry.Success -> globalState.copy( + userCountryCode = action.countryCode, + ) else -> globalState } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 3a180040a3..272f328df3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -30,17 +30,19 @@ import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletStore import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.proxy.AppStateHolder import org.rekotlin.Action import timber.log.Timber import java.math.BigDecimal class WalletReducer { companion object { - fun reduce(action: Action, state: AppState): WalletState = internalReduce(action, state) + fun reduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState = + internalReduce(action, state, appStateHolder) } } -private fun internalReduce(action: Action, state: AppState): WalletState { +private fun internalReduce(action: Action, state: AppState, appStateHolder: AppStateHolder): WalletState { val multiWalletReducer = MultiWalletReducer() val onWalletLoadedReducer = OnWalletLoadedReducer() @@ -65,17 +67,17 @@ private fun internalReduce(action: Action, state: AppState): WalletState { blockchainNetwork = BlockchainNetwork( Blockchain.Unknown, null, - emptyList() + emptyList(), ), walletsData = listOf( WalletData( currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), mainButton = WalletMainButton.CreateWalletButton(true), - currency = Currency.Blockchain(Blockchain.Unknown, null) - ) - ) - ) - ) + currency = Currency.Blockchain(Blockchain.Unknown, null), + ), + ), + ), + ), ) } is WalletAction.LoadData.Failure -> { @@ -87,18 +89,18 @@ private fun internalReduce(action: Action, state: AppState): WalletState { walletsData = store.walletsData.map { it.copy( currencyData = it.currencyData.copy( - status = BalanceStatus.Unreachable - ) + status = BalanceStatus.Unreachable, + ), ) - } + }, - ) + ) } newState = newState.copy( state = ProgressState.Error, error = ErrorType.NoInternetConnection, - wallets = wallets + wallets = wallets, ) } is TapError.UnknownBlockchain -> { @@ -110,16 +112,16 @@ private fun internalReduce(action: Action, state: AppState): WalletState { blockchainNetwork = BlockchainNetwork( Blockchain.Unknown, null, - emptyList() + emptyList(), ), walletsData = listOf( WalletData( currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain), - currency = Currency.Blockchain(Blockchain.Unknown, null) - ) - ) - ) - ) + currency = Currency.Blockchain(Blockchain.Unknown, null), + ), + ), + ), + ), ) } else -> { /* no-op */ @@ -190,7 +192,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce( wallet = action.wallet, blockchainNetwork = action.blockchain, - walletState = newState + walletState = newState, ) is WalletAction.LoadWallet.NoAccount -> { val amount = BigDecimal.ZERO @@ -209,10 +211,10 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ), fiatAmount = fiatAmount, fiatAmountFormatted = fiatAmount.toFormattedFiatValue( - fiatCurrencyName = state.globalState.appCurrency.symbol + fiatCurrencyName = state.globalState.appCurrency.symbol, ), amountToCreateAccount = action.amountToCreateAccount, - ) + ), ) } val updatedWalletStore = newState.getWalletStore(action.blockchain) @@ -232,7 +234,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { val newWalletData = walletData?.copy( currencyData = walletData.currencyData.copy( status = BalanceStatus.Unreachable, - errorMessage = message + errorMessage = message, ), ) val tokenWallets = action.wallet.getTokens() @@ -244,8 +246,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState { .map { it.copy( currencyData = it.currencyData.copy( - status = BalanceStatus.Unreachable, errorMessage = message - ) + status = BalanceStatus.Unreachable, errorMessage = message, + ), ) } val updatedWallets = walletStore!!.updateWallets(listOfNotNull(newWalletData) + tokenWallets).walletsData @@ -296,9 +298,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState { selectedWalletData?.copy( walletAddresses = WalletAddresses( address, - walletAddresses.list - ) - ) + walletAddresses.list, + ), + ), ) } is WalletAction.SetWalletRent -> { @@ -320,6 +322,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { else -> { /* no-op */ } } + appStateHolder.walletState = newState return newState } @@ -345,7 +348,7 @@ fun Wallet.createAddressesData(): List { it.value, it.type, getShareUri(it.value), - getExploreUrl(it.value) + getExploreUrl(it.value), ) if (it.type == blockchain.defaultAddressType()) { listOfAddressData.add(0, addressData) @@ -358,27 +361,26 @@ fun Wallet.createAddressesData(): List { private fun handleCheckSignedHashesActions( action: WalletAction.Warnings, - state: WalletState + state: WalletState, ): WalletState { return when (action) { WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount -> state.copy(hashesCountVerified = true) WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline -> state.copy( - hashesCountVerified = false + hashesCountVerified = false, ) is WalletAction.Warnings.Set -> state.copy(mainWarningsList = action.warningList) else -> state } } - private fun setNewFiatRate( fiatRates: Map, appCurrency: FiatCurrency, - state: WalletState + state: WalletState, ): WalletState { val rateFormatter: (BigDecimal) -> String = { rate: BigDecimal -> rate.toFiatRateString( - fiatCurrencyName = appCurrency.symbol + fiatCurrencyName = appCurrency.symbol, ) } @@ -387,7 +389,7 @@ private fun setNewFiatRate( fiatRates = fiatRates.mapNotNullValues { it.value }, rateFormatter = rateFormatter, appCurrency = appCurrency, - state = state + state = state, ) } else { setSingleWalletFiatRates( @@ -403,7 +405,7 @@ private fun setMultiWalletFiatRate( fiatRates: Map, rateFormatter: (BigDecimal) -> String, appCurrency: FiatCurrency, - state: WalletState + state: WalletState, ): WalletState { val newWalletsData = fiatRates.mapNotNull { (currency, rate) -> val walletStore = state.getWalletStore(currency) ?: return@mapNotNull null @@ -488,10 +490,9 @@ private fun setSingleWalletFiatRate( val walletData = state.primaryWallet.copy( currencyData = state.primaryWallet.currencyData.copy(fiatAmountFormatted = fiatAmount), fiatRate = rate, - fiatRateString = rateFormatted + fiatRateString = rateFormatted, ) return state.updateWalletData(walletData) - } else if (currency is Currency.Token && currency.token == token) { Timber.e("Working with token fiat rate") diff --git a/app/src/main/java/com/tangem/tap/network/auth/AuthProviderImpl.kt b/app/src/main/java/com/tangem/tap/network/auth/AuthProviderImpl.kt new file mode 100644 index 0000000000..57156ad5fa --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/auth/AuthProviderImpl.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.network.auth + +import com.tangem.common.extensions.toHexString +import com.tangem.lib.auth.AuthProvider +import com.tangem.tap.proxy.AppStateHolder + +class AuthProviderImpl(private val appStateHolder: AppStateHolder) : AuthProvider { + + override fun getAuthToken(): String { + return appStateHolder.scanResponse?.card?.cardPublicKey?.toHexString() ?: "" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt new file mode 100644 index 0000000000..6e9eabd1f1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.network.auth.di + +import com.tangem.lib.auth.AuthProvider +import com.tangem.tap.network.auth.AuthProviderImpl +import com.tangem.tap.proxy.AppStateHolder +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class AuthModule { + + @Provides + @Singleton + fun provideAuthProvider(appStateHolder: AppStateHolder): AuthProvider { + return AuthProviderImpl(appStateHolder) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index eb4bfba228..c161b935fb 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -3,8 +3,8 @@ package com.tangem.tap.proxy import com.tangem.common.card.Card import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.redux.AppState +import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.tokens.UserTokensRepository -import com.tangem.tap.features.tokens.redux.TokensMiddleware import com.tangem.tap.features.wallet.redux.WalletState import org.rekotlin.Store @@ -14,11 +14,11 @@ import org.rekotlin.Store */ class AppStateHolder { - val scanResponse: ScanResponse? = null - val walletState: WalletState? = null - val userTokensRepository: UserTokensRepository? = null - val mainStore: Store? = null - val tokesMiddleware: TokensMiddleware? = null + var scanResponse: ScanResponse? = null + var walletState: WalletState? = null + var userTokensRepository: UserTokensRepository? = null + var mainStore: Store? = null + var tangemSdkManager: TangemSdkManager? = null fun getActualCard(): Card? { return scanResponse?.card diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index e27bfd5238..48396f67f1 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -2,12 +2,27 @@ package com.tangem.tap.proxy import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.common.CompletionResult +import com.tangem.common.card.EllipticCurve +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.common.extensions.toMapKey +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.NonNativeToken +import com.tangem.operations.derivation.ExtendedPublicKeysMap +import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.TapError import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.scope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlin.coroutines.suspendCoroutine class DerivationManagerImpl( @@ -15,7 +30,6 @@ class DerivationManagerImpl( ) : DerivationManager { override suspend fun deriveMissingBlockchains(currency: Currency) = suspendCoroutine { continuation -> - val tokesMiddleware = requireNotNull(appStateHolder.tokesMiddleware) { "tokesMiddleware is null" } val blockchain = Blockchain.fromNetworkId(currency.networkId) val card = appStateHolder.getActualCard() if (blockchain != null && card != null) { @@ -31,9 +45,10 @@ class DerivationManagerImpl( blockchainNetwork, appToken, ) - if (appStateHolder.scanResponse != null) { - tokesMiddleware.deriveMissingBlockchains( - scanResponse = appStateHolder.scanResponse, + val scanResponse = appStateHolder.scanResponse + if (scanResponse != null) { + deriveMissingBlockchains( + scanResponse = scanResponse, listOf(appCurrency), ) { continuation.resumeWith(Result.success(true)) @@ -57,4 +72,99 @@ class DerivationManagerImpl( } return false } + + private fun deriveMissingBlockchains( + scanResponse: ScanResponse, + currencyList: List, + onSuccess: (ScanResponse) -> Unit, + ) { + val derivationDataList = listOfNotNull( + getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList), + getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList), + ) + val derivations = derivationDataList.associate { it.derivations } + if (derivations.isEmpty()) { + onSuccess(scanResponse) + return + } + + scope.launch { + val result = appStateHolder.tangemSdkManager?.derivePublicKeys( + scanResponse.card.cardId, + derivations, + ) + when (result) { + is CompletionResult.Success -> { + val newDerivedKeys = result.data.entries + val oldDerivedKeys = scanResponse.derivedKeys + + val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet() + + val updatedDerivedKeys = walletKeys.associateWith { walletKey -> + val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap()) + val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap()) + ExtendedPublicKeysMap(oldDerivations + newDerivations) + } + val updatedScanResponse = scanResponse.copy( + derivedKeys = updatedDerivedKeys, + ) + appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) + delay(DELAY_SDK_DIALOG_CLOSE) + onSuccess(updatedScanResponse) + } + is CompletionResult.Failure -> { + appStateHolder.mainStore?.dispatchDebugErrorNotification( + TapError.CustomError( + "Error adding " + + "tokens", + ), + ) + } + else -> { + throw IllegalStateException("result result is null") + } + } + } + } + + private fun getDerivations( + curve: EllipticCurve, + scanResponse: ScanResponse, + currencyList: List, + ): DerivationData? { + val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null + + val manageTokensCandidates = currencyList.map { it.blockchain }.distinct().filter { + it.getSupportedCurves().contains(curve) + }.mapNotNull { + it.derivationPath(scanResponse.card.derivationStyle) + } + + val customTokensCandidates = currencyList.filter { + it.blockchain.getSupportedCurves().contains(curve) + }.mapNotNull { it.derivationPath }.map { DerivationPath(it) } + + val bothCandidates = (manageTokensCandidates + customTokensCandidates).distinct() + if (bothCandidates.isEmpty()) return null + + val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() + val alreadyDerivedKeys: ExtendedPublicKeysMap = + scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap()) + val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList() + + val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) } + if (toDerive.isEmpty()) return null + + return DerivationData( + derivations = mapKeyOfWalletPublicKey to toDerive, + alreadyDerivedKeys = alreadyDerivedKeys, + mapKeyOfWalletPublicKey = mapKeyOfWalletPublicKey, + ) + } + + private class DerivationData( + val derivations: Pair>, + val alreadyDerivedKeys: ExtendedPublicKeysMap, + val mapKeyOfWalletPublicKey: ByteArrayKey, + ) } \ No newline at end of file diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 7d8f651682..20d83b55e0 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -25,6 +25,9 @@ android { dependencies { + /** Project */ + implementation(project(":libs:auth")) + /** DI */ implementation(Library.hilt) kapt(Library.hiltKapt) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt new file mode 100644 index 0000000000..e4d60edceb --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt @@ -0,0 +1,24 @@ +package com.tangem.datasource.api + +import com.tangem.lib.auth.AuthProvider +import okhttp3.Interceptor +import okhttp3.Response + +class AuthHeaderInterceptor( + private val authProvider: AuthProvider, +) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request().newBuilder().apply { + addHeader( + HEADER_CARD_KEY, authProvider.getAuthToken(), + ) + }.build() + + return chain.proceed(request) + } + + companion object { + const val HEADER_CARD_KEY = "card_public_key" + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 823d3e63d8..fd14d8802a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -1,6 +1,8 @@ package com.tangem.datasource.di +import com.tangem.datasource.api.AuthHeaderInterceptor import com.tangem.datasource.api.referral.ReferralApi +import com.tangem.lib.auth.AuthProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -30,15 +32,16 @@ class NetworkModule { @Provides @Singleton - fun provideOkHttpClient(): OkHttpClient { + fun provideOkHttpClient(authProvider: AuthProvider): OkHttpClient { return OkHttpClient.Builder() .addInterceptor( HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC), ) + .addInterceptor(AuthHeaderInterceptor(authProvider)) .build() } private companion object { - const val PROD_REFERRAL_BASE_URL = "" + const val PROD_REFERRAL_BASE_URL = "https://api.tangem-tech.com/v1/" } } \ No newline at end of file diff --git a/libs/auth/.gitignore b/libs/auth/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/libs/auth/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/libs/auth/build.gradle.kts b/libs/auth/build.gradle.kts new file mode 100644 index 0000000000..a1ea108aae --- /dev/null +++ b/libs/auth/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("com.android.library") + kotlin("android") +} + +android { + + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } +} \ No newline at end of file diff --git a/libs/auth/src/main/AndroidManifest.xml b/libs/auth/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e314bdebe7 --- /dev/null +++ b/libs/auth/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/libs/auth/src/main/java/com/tangem/lib/auth/AuthProvider.kt b/libs/auth/src/main/java/com/tangem/lib/auth/AuthProvider.kt new file mode 100644 index 0000000000..1a3836e2d0 --- /dev/null +++ b/libs/auth/src/main/java/com/tangem/lib/auth/AuthProvider.kt @@ -0,0 +1,12 @@ +package com.tangem.lib.auth + +/** + * Provides auth for tangemTech API + */ +interface AuthProvider { + + /** + * Returns authToken for tangem tech api + */ + fun getAuthToken(): String +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index e6a3474eac..76949019de 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,7 @@ include(":core:ui") // region Libs modules include(":libs:crypto") +include(":libs:auth") // endregion Libs modules // region Feature modules From 834a7e40c55749e0a7dc3b3561f3a61aaab8af7e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 21 Nov 2022 20:39:13 +0300 Subject: [PATCH 079/176] Updated on 2026-08-14 --- .../details/ui/details/DetailsScreenState.kt | 4 ++-- .../details/ui/details/DetailsViewModel.kt | 3 +++ .../tap/network/auth/AuthProviderImpl.kt | 6 ++++- app/src/main/res/drawable/ic_add_friends.xml | 9 ++++++++ .../datasource/api/AuthHeaderInterceptor.kt | 11 +++++----- .../datasource/api/referral/ReferralApi.kt | 7 +++--- .../api/referral/models/ReferralResponse.kt | 13 +++++------ .../com/tangem/datasource/di/NetworkModule.kt | 16 +++++++++----- .../main/java/com/tangem/utils/Extensions.kt | 9 ++++++++ .../referral/converters/ReferralConverter.kt | 10 ++++++--- .../feature/referral/ui/ReferralScreen.kt | 2 +- .../referral/viewmodels/ReferralViewModel.kt | 22 ++++++++++++------- .../java/com/tangem/lib/auth/AuthProvider.kt | 4 +++- 13 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 app/src/main/res/drawable/ic_add_friends.xml create mode 100644 core/utils/src/main/java/com/tangem/utils/Extensions.kt diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index a92efc7e2b..470b9b9454 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -20,14 +20,14 @@ enum class SettingsElement( WalletConnect(R.drawable.ic_walletconnect, R.string.wallet_connect_title), Chat(R.drawable.ic_chat, R.string.details_ask_a_question), SendFeedback(R.drawable.ic_comment, R.string.details_row_title_send_feedback), + ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), CardSettings(R.drawable.ic_card_settings, R.string.card_settings_title), AppCurrency(R.drawable.ic_currency, R.string.details_row_title_currency), AppSettings(R.drawable.ic_settings, R.string.app_settings_title), LinkMoreCards(R.drawable.ic_more_cards, R.string.details_row_title_create_backup), TermsOfService(R.drawable.ic_text, R.string.disclaimer_title), // General Terms of Service of the App TermsOfUse(R.drawable.ic_text, R.string.details_row_title_card_tou), // Terms of Use for S2C cards only - PrivacyPolicy(R.drawable.ic_lock, R.string.details_row_privacy_policy), - ; + PrivacyPolicy(R.drawable.ic_lock, R.string.details_row_privacy_policy); } data class SocialNetworkLink( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index c48aeab31d..98777628d8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -95,6 +95,9 @@ class DetailsViewModel(private val store: Store) { SettingsElement.PrivacyPolicy -> { // TODO: To be available later } + SettingsElement.ReferralProgram -> { + store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) + } } } diff --git a/app/src/main/java/com/tangem/tap/network/auth/AuthProviderImpl.kt b/app/src/main/java/com/tangem/tap/network/auth/AuthProviderImpl.kt index 57156ad5fa..d4bbdd61bd 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/AuthProviderImpl.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/AuthProviderImpl.kt @@ -6,7 +6,11 @@ import com.tangem.tap.proxy.AppStateHolder class AuthProviderImpl(private val appStateHolder: AppStateHolder) : AuthProvider { - override fun getAuthToken(): String { + override fun getCardPublicKey(): String { return appStateHolder.scanResponse?.card?.cardPublicKey?.toHexString() ?: "" } + + override fun getCardId(): String { + return appStateHolder.scanResponse?.card?.cardId ?: "" + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add_friends.xml b/app/src/main/res/drawable/ic_add_friends.xml new file mode 100644 index 0000000000..d1457f8210 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_friends.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt index e4d60edceb..5a50c5215e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/AuthHeaderInterceptor.kt @@ -9,16 +9,17 @@ class AuthHeaderInterceptor( ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request().newBuilder().apply { - addHeader( - HEADER_CARD_KEY, authProvider.getAuthToken(), - ) - }.build() + val request = chain.request() + .newBuilder() + .addHeader(HEADER_CARD_KEY, authProvider.getCardPublicKey()) + .addHeader(HEADER_CARD_ID, authProvider.getCardId()) + .build() return chain.proceed(request) } companion object { const val HEADER_CARD_KEY = "card_public_key" + const val HEADER_CARD_ID = "card_id" } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt index e7eaf31343..c3b7ff2200 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/ReferralApi.kt @@ -5,17 +5,16 @@ import com.tangem.datasource.api.referral.models.StartReferralBody import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST -import retrofit2.http.Query +import retrofit2.http.Path /** * Api for referral feature */ -interface -ReferralApi { +interface ReferralApi { /** Returns referral status by [walletId] */ @GET("referral/{walletId}") - suspend fun getReferralStatus(@Query("name") walletId: String): ReferralResponse + suspend fun getReferralStatus(@Path("walletId") walletId: String): ReferralResponse /** Make user referral, requires [StartReferralBody] */ @POST("referral") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt index a21aee2286..3066ab45a7 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/referral/models/ReferralResponse.kt @@ -1,6 +1,7 @@ package com.tangem.datasource.api.referral.models import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass import org.joda.time.DateTime /** @@ -21,7 +22,7 @@ data class Conditions( data class Discount( @Json(name = "amount") val amount: Int, - @Json(name = "discountType") val discountType: DiscountType, + @Json(name = "type") val discountType: String, ) data class Award( @@ -41,11 +42,7 @@ data class Token( data class Referral( @Json(name = "shareLink") val shareLink: String, @Json(name = "address") val address: String, - @Json(name = "promocode") val promocode: String, + @Json(name = "promoCode") val promocode: String, @Json(name = "walletsPurchased") val walletsPurchased: Int, - @Json(name = "termsAcceptedAt") val termsAcceptedAt: DateTime?, -) - -enum class DiscountType { - PERCENTAGE, VALUE -} \ No newline at end of file + @Json(name = "termsAcceptedAt") val termsAcceptedAt: String?, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index fd14d8802a..6fed866ecc 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -1,5 +1,7 @@ package com.tangem.datasource.di +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.datasource.api.AuthHeaderInterceptor import com.tangem.datasource.api.referral.ReferralApi import com.tangem.lib.auth.AuthProvider @@ -22,7 +24,11 @@ class NetworkModule { fun provideReferralApi(okHttpClient: OkHttpClient): ReferralApi { return Retrofit.Builder() .addConverterFactory( - MoshiConverterFactory.create(), + MoshiConverterFactory.create( + Moshi.Builder() + .addLast(KotlinJsonAdapterFactory()) + .build(), + ), ) .baseUrl(PROD_REFERRAL_BASE_URL) .client(okHttpClient) @@ -34,14 +40,14 @@ class NetworkModule { @Singleton fun provideOkHttpClient(authProvider: AuthProvider): OkHttpClient { return OkHttpClient.Builder() - .addInterceptor( - HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC), - ) .addInterceptor(AuthHeaderInterceptor(authProvider)) + .addInterceptor( + HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY), + ) .build() } private companion object { - const val PROD_REFERRAL_BASE_URL = "https://api.tangem-tech.com/v1/" + const val PROD_REFERRAL_BASE_URL = "https://devapi.tangem-tech.com/v1/"//"https://api.tangem-tech.com/v1/" } } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/Extensions.kt b/core/utils/src/main/java/com/tangem/utils/Extensions.kt new file mode 100644 index 0000000000..de72e53d54 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/Extensions.kt @@ -0,0 +1,9 @@ +package com.tangem.utils + +inline fun > safeValueOf(type: String, default: T): T { + return try { + java.lang.Enum.valueOf(T::class.java, type) + } catch (e: IllegalArgumentException) { + default + } +} \ No newline at end of file diff --git a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt index bbd136e5f2..7e468c0500 100644 --- a/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt +++ b/features/referral/data/src/main/java/com/tangem/feature/referral/converters/ReferralConverter.kt @@ -7,6 +7,8 @@ import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.ReferralInfo import com.tangem.feature.referral.domain.models.TokenData import com.tangem.utils.converter.Converter +import com.tangem.utils.safeValueOf +import org.joda.time.format.ISODateTimeFormat import javax.inject.Inject class ReferralConverter @Inject constructor() : Converter { @@ -19,7 +21,7 @@ class ReferralConverter @Inject constructor() : Converter Date: Tue, 22 Nov 2022 11:48:01 +0300 Subject: [PATCH 080/176] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + buildSrc/src/main/java/Dependency.kt | 3 ++- core/utils/build.gradle.kts | 31 ++++++----------------- features/referral/domain/build.gradle.kts | 31 ++++++----------------- libs/crypto/build.gradle.kts | 28 +++++--------------- 5 files changed, 26 insertions(+), 68 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 81a98a6e98..d1e7b40573 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -110,6 +110,7 @@ dependencies { implementation(project(":network")) implementation(project(":common")) implementation(project(":core:ui")) + implementation(project(":core:utils")) implementation(project(":libs:crypto")) implementation(project(":libs:auth")) diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt index f93b728008..c1375a7b76 100644 --- a/buildSrc/src/main/java/Dependency.kt +++ b/buildSrc/src/main/java/Dependency.kt @@ -42,8 +42,9 @@ object Library { const val googlePlayServicesWallet = "com.google.android.gms:play-services-wallet:" + Versions.googlePlayServicesWallet const val hilt = "com.google.dagger:hilt-android:" + Versions.hilt + const val hiltCore = "com.google.dagger:hilt-core:" + Versions.hilt const val hiltKapt = "com.google.dagger:hilt-compiler:" + Versions.hilt - const val jodatime = "net.danlew:android.joda:" + Versions.jodatime + const val jodatime = "joda-time:joda-time:" + Versions.jodatime const val kotsonGsonExt = "com.github.salomonbrys.kotson:kotson:" + Versions.kotsonGsonExt const val lottie = "com.airbnb.android:lottie:" + Versions.lottie const val materialComponent = "com.google.android.material:material:" + Versions.googleMaterialComponent diff --git a/core/utils/build.gradle.kts b/core/utils/build.gradle.kts index 917187555f..9f88778542 100644 --- a/core/utils/build.gradle.kts +++ b/core/utils/build.gradle.kts @@ -1,35 +1,20 @@ plugins { - id("com.android.library") - kotlin("android") + id("java-library") + id("org.jetbrains.kotlin.jvm") kotlin("kapt") - id("com.google.dagger.hilt.android") -} - -android { - - defaultConfig { - compileSdk = AppConfig.compileSdkVersion - minSdk = AppConfig.minSdkVersion - targetSdk = AppConfig.targetSdkVersion - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - isCoreLibraryDesugaringEnabled = false - } } dependencies { /** DI */ - implementation(Library.hilt) + implementation(Library.hiltCore) kapt(Library.hiltKapt) /** Coroutines */ implementation(Library.coroutine) +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } \ No newline at end of file diff --git a/features/referral/domain/build.gradle.kts b/features/referral/domain/build.gradle.kts index bcee4c51cc..fe5dace957 100644 --- a/features/referral/domain/build.gradle.kts +++ b/features/referral/domain/build.gradle.kts @@ -1,27 +1,7 @@ plugins { - id("com.android.library") - kotlin("android") + id("java-library") + id("org.jetbrains.kotlin.jvm") kotlin("kapt") - id("com.google.dagger.hilt.android") -} - -android { - - defaultConfig { - compileSdk = AppConfig.compileSdkVersion - minSdk = AppConfig.minSdkVersion - targetSdk = AppConfig.targetSdkVersion - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - isCoreLibraryDesugaringEnabled = false - } } dependencies { @@ -34,6 +14,11 @@ dependencies { implementation(Library.jodatime) /** DI */ - implementation(Library.hilt) + implementation(Library.hiltCore) kapt(Library.hiltKapt) +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } \ No newline at end of file diff --git a/libs/crypto/build.gradle.kts b/libs/crypto/build.gradle.kts index b6d1313ae0..de1a6876a4 100644 --- a/libs/crypto/build.gradle.kts +++ b/libs/crypto/build.gradle.kts @@ -1,29 +1,15 @@ plugins { - id("com.android.library") - kotlin("android") -} - -android { - - defaultConfig { - compileSdk = AppConfig.compileSdkVersion - minSdk = AppConfig.minSdkVersion - targetSdk = AppConfig.targetSdkVersion - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - isCoreLibraryDesugaringEnabled = false - } + id("java-library") + id("org.jetbrains.kotlin.jvm") } dependencies { /** Coroutines */ implementation(Library.coroutine) +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } \ No newline at end of file From e17ff1b4c28fd83907d60ea08a83892c06bf6845 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Nov 2022 13:49:15 +0300 Subject: [PATCH 081/176] Updated on 2026-08-14 --- app/src/main/res/values-ru/strings.xml | 1 + .../main/res/values/strings_untranslated.xml | 2 +- .../components/appbar/AppBarWithBackButton.kt | 38 +-- .../src/main/res/values-ru/string_common.xml | 1 + core/ui/src/main/res/values-ru/strings.xml | 4 + core/ui/src/main/res/values/dimens.xml | 6 +- core/ui/src/main/res/values/string_common.xml | 1 + core/ui/src/main/res/values/strings.xml | 4 + .../feature/referral/ReferralFragment.kt | 9 +- .../referral/models/ReferralStateHolder.kt | 14 +- .../feature/referral/router/ReferralRouter.kt | 11 + .../ui/AgreementBottomSheetContent.kt | 2 +- .../feature/referral/ui/AgreementText.kt | 42 +-- .../referral/ui/NonParticipateBottomBlock.kt | 2 +- .../referral/ui/ParticipateBottomBlock.kt | 44 ++- .../feature/referral/ui/ReferralScreen.kt | 300 +++++++++++++----- .../referral/viewmodels/ReferralViewModel.kt | 75 +++-- .../src/main/res/values-ru/string.xml | 6 +- .../res/values/{string.xml => strings.xml} | 6 +- 19 files changed, 391 insertions(+), 177 deletions(-) create mode 100644 core/ui/src/main/res/values-ru/strings.xml create mode 100644 core/ui/src/main/res/values/strings.xml create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt rename features/referral/presentation/src/main/res/values/{string.xml => strings.xml} (66%) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a709a41b26..57273eb0aa 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -175,6 +175,7 @@ Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. Блокчейн недоступен. Попробуй позже. Хорошо, понял! + Успешно! Начнем Выполнить резервное копирование Оставить на потом diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 59a680c908..d43c30aab9 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -153,6 +153,7 @@ Ok, Got it! + Success! Getting started Backup now @@ -222,7 +223,6 @@ - Dapp %s, requesting to\n sign BNB transaction \n diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index 629f6f7592..3f4c5efa9e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.appbar +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -18,22 +19,6 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -fun PreviewAppBarWithBackButtonInLightTheme() { - TangemTheme(isDark = false) { - AppBarWithBackButton(text = "Title", onBackClick = {}) - } -} - -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -fun PreviewAppBarWithBackButtonInDarkTheme() { - TangemTheme(isDark = true) { - AppBarWithBackButton(text = "Title", onBackClick = {}) - } -} - /** * App bar with back button and optional title * @@ -47,8 +32,9 @@ fun PreviewAppBarWithBackButtonInDarkTheme() { fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { Row( modifier = Modifier - .padding(all = dimensionResource(R.dimen.spacing16)) - .fillMaxWidth(), + .background(MaterialTheme.colors.primary) + .fillMaxWidth() + .padding(all = dimensionResource(R.dimen.spacing16)), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing16)), verticalAlignment = Alignment.CenterVertically, ) { @@ -69,4 +55,20 @@ fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { ) } } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun PreviewAppBarWithBackButtonInLightTheme() { + TangemTheme(isDark = false) { + AppBarWithBackButton(text = "Title", onBackClick = {}) + } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun PreviewAppBarWithBackButtonInDarkTheme() { + TangemTheme(isDark = true) { + AppBarWithBackButton(text = "Title", onBackClick = {}) + } } \ No newline at end of file diff --git a/core/ui/src/main/res/values-ru/string_common.xml b/core/ui/src/main/res/values-ru/string_common.xml index ebb5d6b913..7df281b0b0 100644 --- a/core/ui/src/main/res/values-ru/string_common.xml +++ b/core/ui/src/main/res/values-ru/string_common.xml @@ -2,4 +2,5 @@ Поделиться Копировать + условия участия \ No newline at end of file diff --git a/core/ui/src/main/res/values-ru/strings.xml b/core/ui/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000000..61522a01ed --- /dev/null +++ b/core/ui/src/main/res/values-ru/strings.xml @@ -0,0 +1,4 @@ + + + Хорошо, понял! + \ No newline at end of file diff --git a/core/ui/src/main/res/values/dimens.xml b/core/ui/src/main/res/values/dimens.xml index 98fbe4b379..60c2e660fb 100644 --- a/core/ui/src/main/res/values/dimens.xml +++ b/core/ui/src/main/res/values/dimens.xml @@ -13,7 +13,9 @@ + 2dp 6dp + 8dp 12dp 16dp @@ -43,12 +45,14 @@ 8dp 10dp 12dp + 14dp 16dp 24dp 32dp 44dp 50dp 54dp - 2dp + 56dp + 92dp \ No newline at end of file diff --git a/core/ui/src/main/res/values/string_common.xml b/core/ui/src/main/res/values/string_common.xml index 85e2e9ef97..b7a15f7fb0 100644 --- a/core/ui/src/main/res/values/string_common.xml +++ b/core/ui/src/main/res/values/string_common.xml @@ -2,4 +2,5 @@ Share Copy + terms and conditions \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml new file mode 100644 index 0000000000..077070e47c --- /dev/null +++ b/core/ui/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Ok, Got it! + \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt index 34df965d31..6ce31cfa5a 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt @@ -5,19 +5,22 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView +import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import com.tangem.feature.referral.router.ReferralRouter import com.tangem.feature.referral.ui.ReferralScreen import com.tangem.feature.referral.viewmodels.ReferralViewModel import dagger.hilt.android.AndroidEntryPoint +import java.lang.ref.WeakReference @AndroidEntryPoint class ReferralFragment : Fragment() { - private val viewModel by viewModels().apply { - // TODO("[REDACTED_JIRA]") this.value.setOnBackClicked() - } + private val viewModel by viewModels() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } + viewModel.setRouter(ReferralRouter(fragmentManager = WeakReference(parentFragmentManager))) return ComposeView(inflater.context).apply { setContent { ReferralScreen(stateHolder = viewModel.uiState) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt index e001315199..11ecb1e075 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt @@ -1,15 +1,16 @@ package com.tangem.feature.referral.models -data class ReferralStateHolder( +internal data class ReferralStateHolder( val headerState: HeaderState, val referralInfoState: ReferralInfoState, - val errorToast: ErrorToast, + val errorSnackbar: ErrorSnackbar? = null, ) { data class HeaderState(val onBackClicked: () -> Unit) sealed interface ReferralInfoContentState { val award: String + val networkName: String val discount: String val url: String } @@ -17,15 +18,18 @@ data class ReferralStateHolder( sealed interface ReferralInfoState { data class ParticipantContent( override val award: String, + override val networkName: String, val address: String, override val discount: String, val purchasedWalletCount: Int, val code: String, + val shareLink: String, override val url: String, ) : ReferralInfoState, ReferralInfoContentState data class NonParticipantContent( override val award: String, + override val networkName: String, override val discount: String, override val url: String, val onParticipateClicked: () -> Unit, @@ -34,8 +38,8 @@ data class ReferralStateHolder( object Loading : ReferralInfoState } - data class ErrorToast( - val visibility: Boolean, - val changeVisibility: () -> Unit + data class ErrorSnackbar( + val throwable: Throwable, + val onOkClicked: () -> Unit, ) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt new file mode 100644 index 0000000000..ecd73795bf --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt @@ -0,0 +1,11 @@ +package com.tangem.feature.referral.router + +import androidx.fragment.app.FragmentManager +import java.lang.ref.WeakReference + +internal class ReferralRouter(private val fragmentManager: WeakReference) { + + fun back() { + fragmentManager.get()?.popBackStack() + } +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt index e7d30282c8..7bd1358fd0 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt @@ -29,7 +29,7 @@ import com.tangem.feature.referral.presentation.R * @param url link to the html page */ @Composable -fun AgreementBottomSheetContent(url: String) { +internal fun AgreementBottomSheetContent(url: String) { Column( modifier = Modifier .background(color = MaterialTheme.colors.secondary) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt index 1e2ceefc51..b77f8c8377 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt @@ -22,28 +22,8 @@ import com.tangem.core.ui.res.TextColorType import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.presentation.R -@Preview(widthDp = 360, showBackground = true) @Composable -fun Preview_AgreementText_InLightTheme() { - TangemTheme(isDark = false) { - Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { - AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) - } - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -fun Preview_AgreementText_InDarkTheme() { - TangemTheme(isDark = true) { - Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { - AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) - } - } -} - -@Composable -fun AgreementText(@StringRes firstPartResId: Int, onClicked: () -> Unit) { +internal fun AgreementText(@StringRes firstPartResId: Int, onClicked: () -> Unit) { val agreementText = annotatedAgreementString(firstPart = stringResource(firstPartResId)) ClickableText( text = agreementText, @@ -74,4 +54,24 @@ private fun annotatedAgreementString(firstPart: String): AnnotatedString { append(" ${stringResource(id = R.string.referral_tos_suffix)}") } } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_AgreementText_InLightTheme() { + TangemTheme(isDark = false) { + Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { + AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) + } + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_AgreementText_InDarkTheme() { + TangemTheme(isDark = true) { + Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { + AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) + } + } } \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index a98b1e8a93..1407434cb7 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -14,7 +14,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.presentation.R @Composable -fun NonParticipateBottomBlock(onAgreementClicked: () -> Unit, onParticipateClicked: () -> Unit) { +internal fun NonParticipateBottomBlock(onAgreementClicked: () -> Unit, onParticipateClicked: () -> Unit) { Column { AgreementText( firstPartResId = R.string.referral_tos_not_enroled_prefix, diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 775896aa6c..987d178626 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -1,5 +1,6 @@ package com.tangem.feature.referral.ui +import android.content.Context import android.content.Intent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -14,8 +15,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -29,10 +32,12 @@ import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.presentation.R @Composable -fun ParticipateBottomBlock( +internal fun ParticipateBottomBlock( purchasedWalletCount: Int, code: String, + shareLink: String, onAgreementClicked: () -> Unit, + showCopySnackbar: () -> Unit, ) { Column( modifier = Modifier @@ -51,7 +56,7 @@ fun ParticipateBottomBlock( ), ) PersonalCodeCard(code = code) - AdditionalButtons(code = code) + AdditionalButtons(shareLink = shareLink, showCopySnackbar = showCopySnackbar) AgreementText(firstPartResId = R.string.referral_tos_enroled_prefix, onClicked = onAgreementClicked) } } @@ -89,9 +94,9 @@ private fun PersonalCodeCard(code: String) { } @Composable -private fun AdditionalButtons(code: String) { +private fun AdditionalButtons(shareLink: String, showCopySnackbar: () -> Unit) { val clipboardManager = LocalClipboardManager.current - val context = LocalContext.current + val hapticFeedback = LocalHapticFeedback.current Row( modifier = Modifier.fillMaxWidth(), @@ -101,25 +106,36 @@ private fun AdditionalButtons(code: String) { modifier = Modifier.weight(1f), text = stringResource(id = R.string.common_copy), iconResId = R.drawable.ic_copy, - onClicked = { clipboardManager.setText(AnnotatedString(code)) }, + onClicked = { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + clipboardManager.setText(AnnotatedString(shareLink)) + showCopySnackbar() + }, ) + + val context = LocalContext.current PrimaryStartIconButton( modifier = Modifier.weight(1f), text = stringResource(id = R.string.common_share), iconResId = R.drawable.ic_share, onClicked = { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, code) - type = "text/plain" - } - val shareIntent = Intent.createChooser(sendIntent, null) - startActivity(context, shareIntent, null) + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + context.shareText(shareLink) }, ) } } +private fun Context.shareText(text: String) { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, text) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(this, shareIntent, null) +} + @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ParticipateBottomBlock_InLightTheme() { @@ -128,7 +144,9 @@ fun Preview_ParticipateBottomBlock_InLightTheme() { ParticipateBottomBlock( purchasedWalletCount = 3, code = "x4JdK", + shareLink = "", onAgreementClicked = {}, + showCopySnackbar = {}, ) } } @@ -142,7 +160,9 @@ fun Preview_ParticipateBottomBlock_InDarkTheme() { ParticipateBottomBlock( purchasedWalletCount = 3, code = "x4JdK", + shareLink = "", onAgreementClicked = {}, + showCopySnackbar = {}, ) } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index e58513e0fb..aab032565f 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -1,38 +1,50 @@ package com.tangem.feature.referral.ui -import android.widget.Toast import androidx.annotation.DrawableRes +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image +import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.BottomSheetScaffold import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.Snackbar +import androidx.compose.material.SnackbarDuration +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState +import androidx.compose.material.SnackbarResult import androidx.compose.material.Text import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.MutableState import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -41,8 +53,10 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.VerticalSpacer import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.res.Black import com.tangem.core.ui.res.ButtonColorType import com.tangem.core.ui.res.IconColorType import com.tangem.core.ui.res.TangemColorPalette @@ -52,6 +66,7 @@ import com.tangem.core.ui.res.buttonColor import com.tangem.core.ui.res.iconColor import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.models.ReferralStateHolder +import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoContentState import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState import com.tangem.feature.referral.presentation.R @@ -65,7 +80,7 @@ import kotlinx.coroutines.launch */ @OptIn(ExperimentalMaterialApi::class) @Composable -fun ReferralScreen(stateHolder: ReferralStateHolder) { +internal fun ReferralScreen(stateHolder: ReferralStateHolder) { val coroutineScope = rememberCoroutineScope() val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed), @@ -75,7 +90,11 @@ fun ReferralScreen(stateHolder: ReferralStateHolder) { BottomSheetScaffold( sheetContent = { AgreementBottomSheetContent( - url = (stateHolder.referralInfoState as? ReferralInfoContentState)?.url ?: "", + url = when (val state = stateHolder.referralInfoState) { + is ReferralInfoState.NonParticipantContent -> state.url + is ReferralInfoState.ParticipantContent -> state.url + is ReferralInfoState.Loading -> "" + }, ) }, scaffoldState = bottomSheetScaffoldState, @@ -100,66 +119,92 @@ fun ReferralScreen(stateHolder: ReferralStateHolder) { ) }, ) + } +} - val context = LocalContext.current - SideEffect { - if (stateHolder.errorToast.visibility) { - //TODO("[REDACTED_JIRA]") - Toast.makeText(context, "", Toast.LENGTH_LONG).show() - stateHolder.errorToast.changeVisibility() +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun ReferralContent(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) { + val isCopyButtonPressed = remember { mutableStateOf(false) } + + Box { + CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + LazyColumn( + modifier = Modifier + .background(color = MaterialTheme.colors.primary) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + stickyHeader { + AppBarWithBackButton( + text = stringResource(R.string.details_referral_title), + onBackClick = stateHolder.headerState.onBackClicked, + ) + } + item { Header() } + item { + ReferralInfo( + stateHolder = stateHolder, + onAgreementClicked = onAgreementClicked, + showCopySnackbar = { isCopyButtonPressed.value = true }, + ) + } } } + + ErrorSnackbarHost(errorSnackbar = stateHolder.errorSnackbar) + CopySnackbarHost(isCopyButtonPressed = isCopyButtonPressed) } } @Composable -fun ReferralContent(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) { - Column( - modifier = Modifier - .background(color = MaterialTheme.colors.primary) - .fillMaxSize() - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Header(stateHolder = stateHolder) +private fun Header() { + Column { + Image( + painter = painterResource(R.drawable.ill_businessman_3d), + contentDescription = null, + modifier = Modifier + .padding(horizontal = dimensionResource(id = R.dimen.spacing32)) + .fillMaxWidth() + .height(dimensionResource(R.dimen.size200)), + ) + VerticalSpacer(spaceResId = R.dimen.spacing24) + Text( + text = stringResource(id = R.string.referral_title), + modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.spacing50)), + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + textAlign = TextAlign.Center, + maxLines = 2, + style = MaterialTheme.typography.h2, + ) VerticalSpacer(spaceResId = R.dimen.spacing16) - ReferralInfo(stateHolder = stateHolder, onAgreementClicked = onAgreementClicked) } } @Composable -private fun ColumnScope.Header(stateHolder: ReferralStateHolder) { - AppBar(stateHolder = stateHolder) - Image( - painter = painterResource(R.drawable.ill_businessman_3d), - contentDescription = null, - modifier = Modifier - .padding(horizontal = dimensionResource(id = R.dimen.spacing32)) - .fillMaxWidth() - .height(dimensionResource(R.dimen.size200)), - ) - VerticalSpacer(spaceResId = R.dimen.spacing24) - Text( - text = stringResource(id = R.string.referral_title), - modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.spacing50)), - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), - textAlign = TextAlign.Center, - maxLines = 2, - style = MaterialTheme.typography.h2, - ) -} - -@Composable -fun ReferralInfo(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) { +private fun ReferralInfo( + stateHolder: ReferralStateHolder, + onAgreementClicked: () -> Unit, + showCopySnackbar: () -> Unit, +) { when (val state = stateHolder.referralInfoState) { is ReferralInfoState.ParticipantContent -> { Conditions(state = state) - ParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked) + ParticipateBottomBlock( + purchasedWalletCount = state.purchasedWalletCount, + code = state.code, + shareLink = state.shareLink, + onAgreementClicked = onAgreementClicked, + showCopySnackbar = showCopySnackbar, + ) } is ReferralInfoState.NonParticipantContent -> { Conditions(state = state) VerticalSpacer(spaceResId = R.dimen.spacing44) - NonParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked) + NonParticipateBottomBlock( + onAgreementClicked = onAgreementClicked, + onParticipateClicked = state.onParticipateClicked, + ) } is ReferralInfoState.Loading -> { LoadingCondition(iconResId = R.drawable.ic_tether) @@ -169,38 +214,34 @@ fun ReferralInfo(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Uni } } -@Composable -private fun AppBar(stateHolder: ReferralStateHolder) { - AppBarWithBackButton( - text = stringResource(R.string.details_referral_title), - onBackClick = stateHolder.headerState.onBackClicked, - ) -} - @Composable private fun Conditions(state: ReferralInfoContentState) { ConditionForYou(state = state) VerticalSpacer(spaceResId = R.dimen.spacing32) - ConditionForYourFriend(state = state) + ConditionForYourFriend(discount = state.discount) } @Composable private fun ConditionForYou(state: ReferralInfoContentState) { Condition(iconResId = R.drawable.ic_tether) { when (state) { - is ReferralInfoState.ParticipantContent -> InfoForYou(award = state.award, address = state.address) - is ReferralInfoState.NonParticipantContent -> InfoForYou(award = state.award) + is ReferralInfoState.ParticipantContent -> InfoForYou( + award = state.award, + networkName = state.networkName, + address = state.address, + ) + is ReferralInfoState.NonParticipantContent -> InfoForYou( + award = state.award, + networkName = state.networkName, + ) } } } @Composable -private fun ConditionForYourFriend(state: ReferralInfoContentState) { +private fun ConditionForYourFriend(discount: String) { Condition(iconResId = R.drawable.ic_discount) { - when (state) { - is ReferralInfoState.ParticipantContent -> InfoForYourFriend(discount = state.discount) - is ReferralInfoState.NonParticipantContent -> InfoForYourFriend(discount = state.discount) - } + InfoForYourFriend(discount = discount) } } @@ -239,7 +280,7 @@ private fun Condition(@DrawableRes iconResId: Int, infoBlock: @Composable () -> } @Composable -private fun InfoForYou(award: String, address: String? = null) { +private fun InfoForYou(award: String, networkName: String, address: String? = null) { ConditionInfo(title = stringResource(id = R.string.referral_point_currencies_title)) { Text( text = buildAnnotatedString { @@ -250,7 +291,8 @@ private fun InfoForYou(award: String, address: String? = null) { append( String.format( stringResource(id = R.string.referral_point_currencies_description_suffix), - if (!address.isNullOrBlank()) address else "", + networkName, + if (!address.isNullOrBlank()) " $address" else "", ), ) }, @@ -319,21 +361,105 @@ private fun ShimmerInfo() { } } +// TODO() Replace component with component from ds @Composable -private fun ParticipantBottomBlock(state: ReferralInfoState.ParticipantContent, onAgreementClicked: () -> Unit) { - ParticipateBottomBlock( - purchasedWalletCount = state.purchasedWalletCount, - code = state.code, - onAgreementClicked = onAgreementClicked, - ) +private fun ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { + if (errorSnackbar != null) { + val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) } + val coroutineScope = rememberCoroutineScope() + + SnackbarHost( + hostState = snackbarHostState, + modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing56)), + snackbar = { + Snackbar( + snackbarData = it, + modifier = Modifier.fillMaxWidth(), + actionOnNewLine = true, + shape = RoundedCornerShape(size = dimensionResource(id = R.dimen.radius8)), + backgroundColor = Black, + contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + actionColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + elevation = dimensionResource(id = R.dimen.elevation3), + ) + }, + ) + + val message = if (errorSnackbar.throwable.cause != null) { + String.format( + format = stringResource(id = R.string.referral_error_failed_to_load_info_with_reason), + errorSnackbar.throwable.cause, + ) + } else { + stringResource(id = R.string.referral_error_failed_to_load_info) + } + val actionLabel = stringResource(id = R.string.warning_button_ok) + + SideEffect { + coroutineScope.launch { + val result = snackbarHostState.showSnackbar( + message = message, + actionLabel = actionLabel, + duration = SnackbarDuration.Indefinite, + ) + if (result == SnackbarResult.ActionPerformed) { + errorSnackbar.onOkClicked() + } + } + } + } } +// TODO() Replace component with component from ds @Composable -private fun NonParticipantBottomBlock(state: ReferralInfoState.NonParticipantContent, onAgreementClicked: () -> Unit) { - NonParticipateBottomBlock( - onAgreementClicked = onAgreementClicked, - onParticipateClicked = state.onParticipateClicked, - ) +private fun CopySnackbarHost(isCopyButtonPressed: MutableState) { + if (isCopyButtonPressed.value) { + val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) } + val coroutineScope = rememberCoroutineScope() + + var snackbarSize by remember { mutableStateOf(0) } + val width = LocalConfiguration.current.screenWidthDp.dp + val snackbarWidth = with(LocalDensity.current) { snackbarSize.toDp() } + + SnackbarHost( + hostState = snackbarHostState, + modifier = Modifier + .padding(top = dimensionResource(id = R.dimen.spacing56), start = (width - snackbarWidth).div(2)) + .fillMaxWidth(), + snackbar = { + Box( + modifier = Modifier + .onSizeChanged { snackbarSize = it.width } + .background(Black, RoundedCornerShape(size = dimensionResource(id = R.dimen.radius8))) + .shadow( + dimensionResource(id = R.dimen.elevation3), + RoundedCornerShape(size = dimensionResource(id = R.dimen.radius8)), + ) + .padding( + horizontal = dimensionResource(id = R.dimen.spacing16), + vertical = dimensionResource(id = R.dimen.spacing14), + ), + ) { + Text( + text = it.message, + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + style = MaterialTheme.typography.body2, + ) + } + }, + ) + + val message = stringResource(id = R.string.referral_promo_code_copied) + SideEffect { + coroutineScope.launch { + snackbarHostState.showSnackbar( + message = message, + duration = SnackbarDuration.Short, + ) + isCopyButtonPressed.value = false + } + } + } } @Preview(widthDp = 360, showBackground = true) @@ -345,13 +471,15 @@ fun Preview_ReferralScreen_Participant_InLightTheme() { headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.ParticipantContent( award = "10 USDT", + networkName = "Tron", address = "ma80...zk8q2", discount = "10%", purchasedWalletCount = 3, code = "x4JdK", + shareLink = "", url = "", ), - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -366,13 +494,15 @@ fun Preview_ReferralScreen_Participant_InDarkTheme() { headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.ParticipantContent( award = "10 USDT", + networkName = "Tron", address = "ma80...zk8q2", discount = "10%", purchasedWalletCount = 3, code = "x4JdK", + shareLink = "", url = "", ), - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -387,11 +517,12 @@ fun Preview_ReferralScreen_NonParticipant_InLightTheme() { headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.NonParticipantContent( award = "10 USDT", + networkName = "Tron", discount = "10%", url = "", onParticipateClicked = {}, ), - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -406,11 +537,12 @@ fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.NonParticipantContent( award = "10 USDT", + networkName = "Tron", discount = "10%", url = "", onParticipateClicked = {}, ), - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -424,7 +556,7 @@ fun Preview_ReferralScreen_Loading_InLightTheme() { stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.Loading, - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -438,7 +570,7 @@ fun Preview_ReferralScreen_Loading_InDarkTheme() { stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.Loading, - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt index 4c62d3ee5a..e0a8546e4b 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt @@ -8,16 +8,20 @@ import androidx.lifecycle.viewModelScope import com.tangem.feature.referral.domain.ReferralInteractor import com.tangem.feature.referral.domain.models.DiscountType import com.tangem.feature.referral.domain.models.ReferralData +import com.tangem.feature.referral.domain.models.ReferralInfo import com.tangem.feature.referral.models.ReferralStateHolder +import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState +import com.tangem.feature.referral.router.ReferralRouter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.properties.Delegates @HiltViewModel -class ReferralViewModel @Inject constructor( +internal class ReferralViewModel @Inject constructor( private val referralInteractor: ReferralInteractor, private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel() { @@ -25,57 +29,80 @@ class ReferralViewModel @Inject constructor( var uiState: ReferralStateHolder by mutableStateOf(createInitiallyUiState()) private set + private var referralRouter: ReferralRouter by Delegates.notNull() + init { loadReferralData() } - fun setOnBackClicked(onBackClicked: () -> Unit) { - uiState = uiState.copy(headerState = ReferralStateHolder.HeaderState(onBackClicked = onBackClicked)) + fun setRouter(router: ReferralRouter) { + referralRouter = router + uiState = uiState.copy(headerState = ReferralStateHolder.HeaderState(onBackClicked = router::back)) } private fun createInitiallyUiState() = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = { }), referralInfoState = ReferralInfoState.Loading, - errorToast = ReferralStateHolder.ErrorToast( - visibility = false, - changeVisibility = { - uiState = uiState.copy( - referralInfoState = uiState.referralInfoState, - errorToast = uiState.errorToast.copy(visibility = false), - ) - }, - ), + errorSnackbar = null, ) private fun loadReferralData() { - val lastUiState = uiState uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) viewModelScope.launch(dispatchers.main) { runCatching(dispatchers.io) { referralInteractor.getReferralStatus() } - .onSuccess { uiState = uiState.copy(referralInfoState = it.convertToReferralInfoState()) } - .onFailure { uiState = lastUiState.copy(errorToast = lastUiState.errorToast.copy(visibility = true)) } + .onSuccess(::showContent) + .onFailure(::showErrorSnackbar) } } + private fun participate() { + uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) + viewModelScope.launch(dispatchers.main) { + runCatching(dispatchers.io) { referralInteractor.startReferral() } + .onSuccess(::showContent) + .onFailure(::showErrorSnackbar) + } + } + + private fun showContent(referralData: ReferralData) { + uiState = uiState.copy(referralInfoState = referralData.convertToReferralInfoState()) + } + + private fun showErrorSnackbar(throwable: Throwable) { + uiState = uiState.copy( + errorSnackbar = ErrorSnackbar(throwable = throwable, onOkClicked = referralRouter::back), + ) + } + private fun ReferralData.convertToReferralInfoState(): ReferralInfoState = when (this) { is ReferralData.ParticipantData -> ReferralInfoState.ParticipantContent( award = getAwardValue(), - address = referral.address, + networkName = getNetworkName(), + address = referral.getAddressValue(), discount = getDiscountValue(), purchasedWalletCount = referral.walletsPurchased, code = referral.promocode, + shareLink = referral.shareLink, url = tosLink, ) is ReferralData.NonParticipantData -> ReferralInfoState.NonParticipantContent( award = getAwardValue(), + networkName = getNetworkName(), discount = getDiscountValue(), url = tosLink, - onParticipateClicked = ::onParticipateClicked, + onParticipateClicked = ::participate, ) } - private fun ReferralData.getAwardValue(): String = - "$award ${requireNotNull(tokens.firstOrNull()?.symbol) { "Token list is empty" }}" + private fun ReferralData.getAwardValue(): String = "$award ${getToken().symbol}" + + private fun ReferralData.getNetworkName(): String = getToken().networkId.replaceFirstChar(Char::uppercase) + + private fun ReferralInfo.getAddressValue(): String { + check(address.length > 5) { "Invalid address" } + return address.substring(startIndex = 0, endIndex = 4) + "..." + + address.substring(startIndex = address.length - 5, endIndex = address.length) + } private fun ReferralData.getDiscountValue(): String { val discountSymbol = when (discountType) { @@ -85,13 +112,5 @@ class ReferralViewModel @Inject constructor( return "$discount $discountSymbol" } - private fun onParticipateClicked() { - val lastUiState = uiState - uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) - viewModelScope.launch(dispatchers.main) { - runCatching(dispatchers.io) { referralInteractor.startReferral() } - .onSuccess { uiState = uiState.copy(referralInfoState = it.convertToReferralInfoState()) } - .onFailure { uiState = lastUiState.copy(errorToast = lastUiState.errorToast.copy(visibility = true)) } - } - } + private fun ReferralData.getToken() = requireNotNull(tokens.firstOrNull()) { "Token list is empty" } } \ No newline at end of file diff --git a/features/referral/presentation/src/main/res/values-ru/string.xml b/features/referral/presentation/src/main/res/values-ru/string.xml index 5e6ef22607..16daf7f17e 100644 --- a/features/referral/presentation/src/main/res/values-ru/string.xml +++ b/features/referral/presentation/src/main/res/values-ru/string.xml @@ -4,7 +4,7 @@ Приведи друга в Tangem Вы Получите - за каждый кошелек, который купит ваш друг на ваш адрес в сети Tron %s + на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг Ваш друг Получит %s скидку @@ -17,4 +17,8 @@ Ваш персональный код %d кошельков Ваши друзья купили + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. + Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. + Персональный код скопирован! \ No newline at end of file diff --git a/features/referral/presentation/src/main/res/values/string.xml b/features/referral/presentation/src/main/res/values/strings.xml similarity index 66% rename from features/referral/presentation/src/main/res/values/string.xml rename to features/referral/presentation/src/main/res/values/strings.xml index e429fe37e1..8e6c0e6912 100644 --- a/features/referral/presentation/src/main/res/values/string.xml +++ b/features/referral/presentation/src/main/res/values/strings.xml @@ -4,7 +4,7 @@ Refer your friends to Tangem You Will get - for each wallet bought by your friend on your Tron network address %s + for each wallet bought by your friend on your %s network address%s Your friend Will get a %s discount @@ -17,4 +17,8 @@ Your personal code %d wallets Your friends bought + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! \ No newline at end of file From b824631eef2dc507ade8321bd011223e164415fc Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 24 Nov 2022 22:13:23 +0300 Subject: [PATCH 082/176] Updated on 2026-08-14 --- .../referral/ui/ParticipateBottomBlock.kt | 8 ++-- .../src/main/res/values-ru/string.xml | 1 + .../src/main/res/values/strings.xml | 43 ++++++++++--------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 987d178626..e34bd7dd8c 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -56,7 +56,7 @@ internal fun ParticipateBottomBlock( ), ) PersonalCodeCard(code = code) - AdditionalButtons(shareLink = shareLink, showCopySnackbar = showCopySnackbar) + AdditionalButtons(code = code, shareLink = shareLink, showCopySnackbar = showCopySnackbar) AgreementText(firstPartResId = R.string.referral_tos_enroled_prefix, onClicked = onAgreementClicked) } } @@ -94,7 +94,7 @@ private fun PersonalCodeCard(code: String) { } @Composable -private fun AdditionalButtons(shareLink: String, showCopySnackbar: () -> Unit) { +private fun AdditionalButtons(code: String, shareLink: String, showCopySnackbar: () -> Unit) { val clipboardManager = LocalClipboardManager.current val hapticFeedback = LocalHapticFeedback.current @@ -108,7 +108,7 @@ private fun AdditionalButtons(shareLink: String, showCopySnackbar: () -> Unit) { iconResId = R.drawable.ic_copy, onClicked = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - clipboardManager.setText(AnnotatedString(shareLink)) + clipboardManager.setText(AnnotatedString(code)) showCopySnackbar() }, ) @@ -120,7 +120,7 @@ private fun AdditionalButtons(shareLink: String, showCopySnackbar: () -> Unit) { iconResId = R.drawable.ic_share, onClicked = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - context.shareText(shareLink) + context.shareText(context.getString(R.string.referral_share_link, shareLink)) }, ) } diff --git a/features/referral/presentation/src/main/res/values-ru/string.xml b/features/referral/presentation/src/main/res/values-ru/string.xml index 16daf7f17e..523d489aee 100644 --- a/features/referral/presentation/src/main/res/values-ru/string.xml +++ b/features/referral/presentation/src/main/res/values-ru/string.xml @@ -21,4 +21,5 @@ Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. Персональный код скопирован! + Купи Tangem Wallet со скидкой!\n%s \ No newline at end of file diff --git a/features/referral/presentation/src/main/res/values/strings.xml b/features/referral/presentation/src/main/res/values/strings.xml index 8e6c0e6912..dfac4843e0 100644 --- a/features/referral/presentation/src/main/res/values/strings.xml +++ b/features/referral/presentation/src/main/res/values/strings.xml @@ -1,24 +1,25 @@ - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - By tapping this button you accept - terms and conditions - of the referral program - Participate - You\'ve accepted - Your personal code - %d wallets - Your friends bought - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + By tapping this button you accept + terms and conditions + of the referral program + Participate + You\'ve accepted + Your personal code + %d wallets + Your friends bought + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s \ No newline at end of file From 849a39f96dce0256c13068bc5b3993712b380d74 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Dec 2022 16:03:00 +0300 Subject: [PATCH 083/176] Updated on 2026-08-14 --- .../features/details/ui/details/DetailsScreenState.kt | 2 +- .../tap/features/details/ui/details/DetailsViewModel.kt | 6 +++--- {app => core/ui}/src/main/res/transition/fade.xml | 0 {app => core/ui}/src/main/res/transition/slide_right.xml | 0 .../java/com/tangem/feature/referral/ReferralFragment.kt | 9 +++++++++ .../com/tangem/feature/referral/ui/ReferralScreen.kt | 2 +- 6 files changed, 14 insertions(+), 5 deletions(-) rename {app => core/ui}/src/main/res/transition/fade.xml (100%) rename {app => core/ui}/src/main/res/transition/slide_right.xml (100%) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index 470b9b9454..6513d28279 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -20,7 +20,7 @@ enum class SettingsElement( WalletConnect(R.drawable.ic_walletconnect, R.string.wallet_connect_title), Chat(R.drawable.ic_chat, R.string.details_ask_a_question), SendFeedback(R.drawable.ic_comment, R.string.details_row_title_send_feedback), - ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), + // ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), CardSettings(R.drawable.ic_card_settings, R.string.card_settings_title), AppCurrency(R.drawable.ic_currency, R.string.details_row_title_currency), AppSettings(R.drawable.ic_settings, R.string.app_settings_title), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 98777628d8..fffc4e625b 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -95,9 +95,9 @@ class DetailsViewModel(private val store: Store) { SettingsElement.PrivacyPolicy -> { // TODO: To be available later } - SettingsElement.ReferralProgram -> { - store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) - } + // SettingsElement.ReferralProgram -> { + // store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram)) + // } } } diff --git a/app/src/main/res/transition/fade.xml b/core/ui/src/main/res/transition/fade.xml similarity index 100% rename from app/src/main/res/transition/fade.xml rename to core/ui/src/main/res/transition/fade.xml diff --git a/app/src/main/res/transition/slide_right.xml b/core/ui/src/main/res/transition/slide_right.xml similarity index 100% rename from app/src/main/res/transition/slide_right.xml rename to core/ui/src/main/res/transition/slide_right.xml diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt index 6ce31cfa5a..6af2cc4e25 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt @@ -1,6 +1,7 @@ package com.tangem.feature.referral import android.os.Bundle +import android.transition.TransitionInflater import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -8,6 +9,7 @@ import androidx.compose.ui.platform.ComposeView import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import com.tangem.feature.referral.presentation.R import com.tangem.feature.referral.router.ReferralRouter import com.tangem.feature.referral.ui.ReferralScreen import com.tangem.feature.referral.viewmodels.ReferralViewModel @@ -18,6 +20,13 @@ import java.lang.ref.WeakReference class ReferralFragment : Fragment() { private val viewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.fade) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } viewModel.setRouter(ReferralRouter(fragmentManager = WeakReference(parentFragmentManager))) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index aab032565f..7b28bf0b50 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -56,7 +56,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.VerticalSpacer import com.tangem.core.ui.components.appbar.AppBarWithBackButton -import com.tangem.core.ui.res.Black +import com.tangem.core.ui.res.TangemColorPalette.Black import com.tangem.core.ui.res.ButtonColorType import com.tangem.core.ui.res.IconColorType import com.tangem.core.ui.res.TangemColorPalette From 141c7d3936467552f28d8d2a13dbad66040a22a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Dec 2022 14:56:00 +0300 Subject: [PATCH 084/176] Updated on 2026-08-14 --- .../tangem/tap/proxy/DerivationManagerImpl.kt | 2 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 8 +-- .../core/ui/components/ButtonsDeprecated.kt | 2 +- .../com/tangem/core/ui/components/Cards.kt | 10 ++-- .../appbar/AppBarWithAdditionalButtons.kt | 8 +-- .../components/atoms/BottomSheetIndicator.kt | 8 +-- .../com/tangem/core/ui/res/TangemDimens.kt | 3 + .../referral/domain/ReferralInteractorImpl.kt | 2 +- .../ui/AgreementBottomSheetContent.kt | 2 +- .../feature/referral/ui/AgreementText.kt | 2 +- .../referral/ui/NonParticipateBottomBlock.kt | 2 +- .../referral/ui/ParticipateBottomBlock.kt | 21 ++++--- .../feature/referral/ui/ReferralScreen.kt | 58 +++++++++---------- .../referral/viewmodels/ReferralViewModel.kt | 2 +- 14 files changed, 66 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index 48396f67f1..2b4d40e0bb 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -121,7 +121,7 @@ class DerivationManagerImpl( ) } else -> { - throw IllegalStateException("result result is null") + error("result result is null") } } } diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 1a7f3fbcd5..2832cd6faf 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -93,10 +93,10 @@ class UserWalletManagerImpl( if (walletManager != null) { return walletManager.wallet.address } else { - throw IllegalStateException("no wallet manager found") + error("no wallet manager found") } } else { - throw IllegalStateException("no blockchain or card found") + error("no blockchain or card found") } } @@ -119,7 +119,7 @@ class UserWalletManagerImpl( save = true, ) } else { - throw IllegalStateException("blockchain is not supported") + error("blockchain is not supported") } } @@ -142,7 +142,7 @@ class UserWalletManagerImpl( save = true, ) } else { - throw IllegalStateException("no card or blockchain found") + error("no card or blockchain found") } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt index 0ff2ce98a8..7fec4a465d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/ButtonsDeprecated.kt @@ -173,7 +173,7 @@ private fun PrimaryButtonRow( .fillMaxWidth() .height(dimensionResource(R.dimen.size48)), enabled = enabled, - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + shape = RoundedCornerShape(TangemTheme.dimens.radius12), colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colors.buttonColor(type = ButtonColorType.PRIMARY), contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index ef25595ace..90d5ee1ce4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -47,17 +47,17 @@ fun Preview_SimpleInfoCard_InDarkTheme() { @Composable fun SmallInfoCard(startText: String, endText: String) { Surface( - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + shape = RoundedCornerShape(TangemTheme.dimens.radius12), color = MaterialTheme.colors.secondary, - elevation = dimensionResource(id = R.dimen.elevation2), + elevation = TangemTheme.dimens.elevation2, ) { Row( modifier = Modifier .fillMaxWidth() - .height(dimensionResource(R.dimen.size48)) + .height(TangemTheme.dimens.size48) .padding( - horizontal = dimensionResource(id = R.dimen.spacing16), - vertical = dimensionResource(id = R.dimen.spacing12), + horizontal = TangemTheme.dimens.spacing16, + vertical = TangemTheme.dimens.spacing12, ), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt index 34f25fe87c..6416a8343f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt @@ -41,15 +41,15 @@ fun AppBarWithAdditionalButtons( Box( modifier = Modifier .fillMaxWidth() - .height(dimensionResource(id = R.dimen.size56)) - .padding(all = dimensionResource(id = R.dimen.spacing16)), + .height(TangemTheme.dimens.size56) + .padding(all = TangemTheme.dimens.spacing16), ) { if (startButton != null) { IconButton(modifier = Modifier.align(Alignment.CenterStart), onClick = startButton.onIconClicked) { Icon( painter = painterResource(id = startButton.iconRes), contentDescription = null, - modifier = Modifier.size(dimensionResource(id = R.dimen.size24)), + modifier = Modifier.size(TangemTheme.dimens.size24), tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1), ) } @@ -68,7 +68,7 @@ fun AppBarWithAdditionalButtons( Icon( painter = painterResource(id = endButton.iconRes), contentDescription = null, - modifier = Modifier.size(dimensionResource(id = R.dimen.size24)), + modifier = Modifier.size(TangemTheme.dimens.size24), tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1), ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt index de03ea2174..907e68d0a2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/BottomSheetIndicator.kt @@ -29,16 +29,16 @@ fun BottomSheetIndicator() { Box( modifier = Modifier .fillMaxWidth() - .height(dimensionResource(id = R.dimen.size20)), + .height(TangemTheme.dimens.size20), contentAlignment = Alignment.Center, ) { Box( modifier = Modifier - .width(dimensionResource(id = R.dimen.size32)) - .height(dimensionResource(id = R.dimen.size4)) + .width(TangemTheme.dimens.size32) + .height(TangemTheme.dimens.size4) .background( color = MaterialTheme.colors.iconColor(type = IconColorType.INACTIVE), - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius2)), + shape = RoundedCornerShape(TangemTheme.dimens.radius2), ), ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 441b0bfd52..4732b620be 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -22,6 +22,7 @@ data class TangemDimens internal constructor( val radius2: Dp = 2.dp, val radius4: Dp = 4.dp, val radius6: Dp = 6.dp, + val radius8: Dp = 8.dp, val radius12: Dp = 12.dp, val radius16: Dp = 16.dp, val radius28: Dp = 28.dp, @@ -51,11 +52,13 @@ data class TangemDimens internal constructor( val spacing8: Dp = 8.dp, val spacing10: Dp = 10.dp, val spacing12: Dp = 12.dp, + val spacing14: Dp = 14.dp, val spacing16: Dp = 16.dp, val spacing24: Dp = 24.dp, val spacing32: Dp = 32.dp, val spacing44: Dp = 44.dp, val spacing50: Dp = 50.dp, val spacing54: Dp = 54.dp, + val spacing56: Dp = 56.dp, // endregion Spacing ) \ No newline at end of file diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index 8b2716f539..bf17471c70 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -34,7 +34,7 @@ internal class ReferralInteractorImpl( address = publicAddress, ) } else { - throw IllegalStateException("tokens for ref is empty") + error("tokens for ref is empty") } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt index 7bd1358fd0..5f15d77781 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt @@ -34,7 +34,7 @@ internal fun AgreementBottomSheetContent(url: String) { modifier = Modifier .background(color = MaterialTheme.colors.secondary) .fillMaxWidth() - .height(LocalConfiguration.current.screenHeightDp.dp - dimensionResource(id = R.dimen.spacing16)), + .height(LocalConfiguration.current.screenHeightDp.dp - TangemTheme.dimens.spacing16), ) { BottomSheetIndicator() Column(modifier = Modifier.verticalScroll(rememberScrollState())) { diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt index b77f8c8377..cb77fb06d7 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt @@ -29,7 +29,7 @@ internal fun AgreementText(@StringRes firstPartResId: Int, onClicked: () -> Unit text = agreementText, modifier = Modifier .fillMaxWidth() - .padding(horizontal = dimensionResource(id = R.dimen.spacing54)), + .padding(horizontal = TangemTheme.dimens.spacing54), style = MaterialTheme.typography.caption.copy(textAlign = TextAlign.Center), maxLines = 2, onClick = { diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index 1407434cb7..29e86058e8 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -21,7 +21,7 @@ internal fun NonParticipateBottomBlock(onAgreementClicked: () -> Unit, onPartici onClicked = onAgreementClicked, ) PrimaryEndIconButton( - modifier = Modifier.padding(all = dimensionResource(id = R.dimen.spacing16)), + modifier = Modifier.padding(all = TangemTheme.dimens.spacing16), text = stringResource(id = R.string.referral_button_participate), iconResId = R.drawable.ic_tangem, onClicked = onParticipateClicked, diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index e34bd7dd8c..a1de2d981f 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview @@ -42,11 +41,11 @@ internal fun ParticipateBottomBlock( Column( modifier = Modifier .padding( - top = dimensionResource(id = R.dimen.spacing24), - bottom = dimensionResource(id = R.dimen.spacing16), + top = TangemTheme.dimens.spacing24, + bottom = TangemTheme.dimens.spacing16, ) - .padding(horizontal = dimensionResource(id = R.dimen.spacing16)), - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)), + .padding(horizontal = TangemTheme.dimens.spacing16), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), ) { SmallInfoCard( startText = stringResource(id = R.string.referral_friends_bought_title), @@ -66,17 +65,17 @@ private fun PersonalCodeCard(code: String) { Column( modifier = Modifier .shadow( - elevation = dimensionResource(id = R.dimen.elevation2), - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + elevation = TangemTheme.dimens.elevation2, + shape = RoundedCornerShape(TangemTheme.dimens.radius12), ) .background( color = MaterialTheme.colors.secondary, - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius12)), + shape = RoundedCornerShape(TangemTheme.dimens.radius12), ) .fillMaxWidth() - .padding(vertical = dimensionResource(id = R.dimen.spacing12)), + .padding(vertical = TangemTheme.dimens.spacing12), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing4)), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), ) { Text( text = stringResource(id = R.string.referral_promo_code_title), @@ -100,7 +99,7 @@ private fun AdditionalButtons(code: String, shareLink: String, showCopySnackbar: Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing16)), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), ) { PrimaryStartIconButton( modifier = Modifier.weight(1f), diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 7b28bf0b50..8c73548fcf 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -56,10 +56,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.VerticalSpacer import com.tangem.core.ui.components.appbar.AppBarWithBackButton -import com.tangem.core.ui.res.TangemColorPalette.Black import com.tangem.core.ui.res.ButtonColorType import com.tangem.core.ui.res.IconColorType import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.core.ui.res.TangemColorPalette.Black import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TextColorType import com.tangem.core.ui.res.buttonColor @@ -99,11 +99,11 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) { }, scaffoldState = bottomSheetScaffoldState, sheetShape = RoundedCornerShape( - topStart = dimensionResource(id = R.dimen.radius16), - topEnd = dimensionResource(id = R.dimen.radius16), + topStart = TangemTheme.dimens.radius16, + topEnd = TangemTheme.dimens.radius16, ), - sheetElevation = dimensionResource(id = R.dimen.elevation24), - sheetPeekHeight = dimensionResource(id = R.dimen.size0), + sheetElevation = TangemTheme.dimens.elevation24, + sheetPeekHeight = TangemTheme.dimens.size0, content = { ReferralContent( stateHolder = stateHolder, @@ -164,7 +164,7 @@ private fun Header() { painter = painterResource(R.drawable.ill_businessman_3d), contentDescription = null, modifier = Modifier - .padding(horizontal = dimensionResource(id = R.dimen.spacing32)) + .padding(horizontal = TangemTheme.dimens.spacing32) .fillMaxWidth() .height(dimensionResource(R.dimen.size200)), ) @@ -255,23 +255,23 @@ private fun LoadingCondition(@DrawableRes iconResId: Int) { @Composable private fun Condition(@DrawableRes iconResId: Int, infoBlock: @Composable () -> Unit) { Row( - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.spacing16)), - horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing12)), + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), verticalAlignment = Alignment.Top, ) { Box( modifier = Modifier .background( color = MaterialTheme.colors.buttonColor(type = ButtonColorType.SECONDARY), - shape = RoundedCornerShape(dimensionResource(id = R.dimen.radius16)), + shape = RoundedCornerShape(TangemTheme.dimens.radius16), ) - .size(dimensionResource(id = R.dimen.size56)), + .size(TangemTheme.dimens.size56), contentAlignment = Alignment.Center, ) { Icon( painter = painterResource(id = iconResId), contentDescription = null, - modifier = Modifier.size(dimensionResource(id = R.dimen.size28)), + modifier = Modifier.size(TangemTheme.dimens.size28), tint = MaterialTheme.colors.iconColor(type = IconColorType.PRIMARY1), ) } @@ -325,7 +325,7 @@ private fun InfoForYourFriend(discount: String) { @Composable private fun ConditionInfo(title: String, subtitleContent: @Composable () -> Unit) { - Column(verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing2))) { + Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2)) { Text( text = title, color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), @@ -340,22 +340,22 @@ private fun ShimmerInfo() { Column( modifier = Modifier .fillMaxWidth() - .padding(top = dimensionResource(id = R.dimen.spacing4)) + .padding(top = TangemTheme.dimens.spacing4) .shimmer(), - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.spacing10)), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing10), ) { Box( modifier = Modifier - .clip(RoundedCornerShape(dimensionResource(id = R.dimen.radius6))) - .width(dimensionResource(id = R.dimen.size102)) - .height(dimensionResource(id = R.dimen.size16)) + .clip(RoundedCornerShape(TangemTheme.dimens.radius6)) + .width(TangemTheme.dimens.size102) + .height(TangemTheme.dimens.size16) .background(TangemColorPalette.White), ) Box( modifier = Modifier - .clip(RoundedCornerShape(dimensionResource(id = R.dimen.radius6))) - .width(dimensionResource(id = R.dimen.size40)) - .height(dimensionResource(id = R.dimen.size11)) + .clip(RoundedCornerShape(TangemTheme.dimens.radius6)) + .width(TangemTheme.dimens.size40) + .height(TangemTheme.dimens.size11) .background(TangemColorPalette.White), ) } @@ -370,17 +370,17 @@ private fun ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { SnackbarHost( hostState = snackbarHostState, - modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing56)), + modifier = Modifier.padding(top = TangemTheme.dimens.spacing56), snackbar = { Snackbar( snackbarData = it, modifier = Modifier.fillMaxWidth(), actionOnNewLine = true, - shape = RoundedCornerShape(size = dimensionResource(id = R.dimen.radius8)), + shape = RoundedCornerShape(size = TangemTheme.dimens.radius8), backgroundColor = Black, contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), actionColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), - elevation = dimensionResource(id = R.dimen.elevation3), + elevation = TangemTheme.dimens.elevation3, ) }, ) @@ -424,20 +424,20 @@ private fun CopySnackbarHost(isCopyButtonPressed: MutableState) { SnackbarHost( hostState = snackbarHostState, modifier = Modifier - .padding(top = dimensionResource(id = R.dimen.spacing56), start = (width - snackbarWidth).div(2)) + .padding(top = TangemTheme.dimens.spacing56, start = (width - snackbarWidth).div(2)) .fillMaxWidth(), snackbar = { Box( modifier = Modifier .onSizeChanged { snackbarSize = it.width } - .background(Black, RoundedCornerShape(size = dimensionResource(id = R.dimen.radius8))) + .background(Black, RoundedCornerShape(size = TangemTheme.dimens.radius8)) .shadow( - dimensionResource(id = R.dimen.elevation3), - RoundedCornerShape(size = dimensionResource(id = R.dimen.radius8)), + TangemTheme.dimens.elevation3, + RoundedCornerShape(size = TangemTheme.dimens.radius8), ) .padding( - horizontal = dimensionResource(id = R.dimen.spacing16), - vertical = dimensionResource(id = R.dimen.spacing14), + horizontal = TangemTheme.dimens.spacing16, + vertical = TangemTheme.dimens.spacing14, ), ) { Text( diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt index e0a8546e4b..21bd4b1057 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt @@ -107,7 +107,7 @@ internal class ReferralViewModel @Inject constructor( private fun ReferralData.getDiscountValue(): String { val discountSymbol = when (discountType) { DiscountType.PERCENTAGE -> "%" - DiscountType.VALUE -> throw java.lang.IllegalStateException("Value doesn't support") + DiscountType.VALUE -> error("Value doesn't support") } return "$discount $discountSymbol" } From 721010b489505ad7be05795599b3497253d356e2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Dec 2022 16:56:59 +0300 Subject: [PATCH 085/176] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 10 +-- .../com/tangem/tap/domain/TangemSdkManager.kt | 2 +- .../domain/twins/CreateFirstTwinWalletTask.kt | 10 ++- .../twins/CreateSecondTwinWalletTask.kt | 24 ++----- .../tap/domain/twins/TwinCardsManager.kt | 1 - .../tangem/tap/domain/twins/WrongTwinCard.kt | 15 ++++ .../dialogs/ApproveWcSessionDialog.kt | 2 +- .../dialogs/BnbTransactionDialog.kt | 16 +++-- .../dialogs/ClipboardOrScanQrDialog.kt | 2 +- .../dialogs/PersonalSignDialog.kt | 15 +--- .../dialogs/TransactionDialog.kt | 4 +- .../home/compose/views/HomeButtons.kt | 2 +- .../products/note/OnboardingNoteFragment.kt | 2 +- .../OnboardingOtherCardsFragment.kt | 6 +- .../products/twins/ui/TwinsCardsFragment.kt | 2 +- .../wallet/ui/OnboardingWalletFragment.kt | 2 +- .../ui/dialogs/AddMoreBackupCardsDialog.kt | 30 ++++---- .../stateSubscribers/SendStateSubscriber.kt | 30 ++++---- .../compose/SelectTokenNetworkDialog.kt | 4 +- .../features/tokens/ui/AddTokensFragment.kt | 4 +- .../tap/features/wallet/ui/WalletFragment.kt | 3 +- .../layout_onboarding_container_bottom.xml | 2 +- .../main/res/layout/fragment_add_tokens.xml | 4 +- .../res/layout/fragment_details_confirm.xml | 6 +- .../main/res/layout/item_popular_token.xml | 1 - .../layout_onboarding_container_bottom.xml | 2 +- app/src/main/res/layout/layout_token.xml | 4 +- app/src/main/res/values-de/strings.xml | 69 ++---------------- app/src/main/res/values-fr/strings.xml | 68 ++---------------- app/src/main/res/values-it/strings.xml | 70 ++---------------- app/src/main/res/values-ru/strings.xml | 50 ++++++------- app/src/main/res/values/strings.xml | 71 +++---------------- .../main/res/values/strings_untranslated.xml | 52 +++++--------- buildSrc/src/main/java/Versions.kt | 2 +- .../src/main/res/values-de/strings_final.xml | 2 +- .../src/main/res/values-fr/strings_final.xml | 2 +- .../src/main/res/values-it/strings_final.xml | 2 +- core/ui/src/main/res/values/strings_final.xml | 2 +- .../com/tangem/domain/common/TwinsHelper.kt | 16 ++--- 39 files changed, 181 insertions(+), 430 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 4d1c3734ea..d1bb43ad95 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -76,13 +76,13 @@ class DialogManager : StoreSubscriber { CreateWalletInterruptDialog.create(state.dialog, context) is WalletConnectDialog.UnsupportedCard -> SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, + titleRes = R.string.wallet_connect_title, messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet, context = context, ) is WalletConnectDialog.AddNetwork -> SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, + titleRes = R.string.wallet_connect_title, message = context.getString( R.string.wallet_connect_network_not_found_format, state.dialog.network, @@ -91,14 +91,14 @@ class DialogManager : StoreSubscriber { ) is WalletConnectDialog.OpeningSessionRejected -> { SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, + titleRes = R.string.wallet_connect_title, messageRes = R.string.wallet_connect_same_wcuri, context = context, ) } is WalletConnectDialog.SessionTimeout -> { SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, + titleRes = R.string.wallet_connect_title, messageRes = R.string.wallet_connect_error_timeout, context = context, ) @@ -123,7 +123,7 @@ class DialogManager : StoreSubscriber { ) is WalletConnectDialog.UnsupportedNetwork -> SimpleAlertDialog.create( - titleRes = R.string.wallet_connect, + titleRes = R.string.wallet_connect_title, messageRes = R.string.wallet_connect_scanner_error_unsupported_network, context = context, ) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 7d6db12a26..4fe7252ac7 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -92,7 +92,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsyncReturnOnMain( ResetToFactorySettingsTask(), card.cardId, - initialMessage = Message(context.getString(R.string.details_row_title_reset_factory_settings)), + initialMessage = Message(context.getString(R.string.card_settings_reset_card_to_factory)), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index 872c130c30..95e8753b3d 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -4,6 +4,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable +import com.tangem.domain.common.TwinCardNumber +import com.tangem.domain.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand @@ -14,8 +16,14 @@ class CreateFirstTwinWalletTask : CardSessionRunnable { session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - val publicKey = session.environment.card?.getSingleWallet()?.publicKey + val card = session.environment.card + val publicKey = card?.getSingleWallet()?.publicKey if (publicKey != null) { + if (TwinsHelper.getTwinCardNumber(card.cardId) == TwinCardNumber.Second) { + callback(CompletionResult.Failure(WrongTwinCard(TwinCardNumber.First))) + return + } + PurgeWalletCommand(publicKey).run(session) { response -> when (response) { is CompletionResult.Success -> { diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 85c03925ff..8780d00055 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -6,28 +6,27 @@ import com.tangem.common.KeyPair import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable -import com.tangem.common.core.TangemError import com.tangem.common.extensions.hexToBytes +import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand import com.tangem.tap.domain.extensions.getSingleWallet -import com.tangem.wallet.R class CreateSecondTwinWalletTask( private val firstPublicKey: String, - private val firstCardId: String, private val issuerKeys: KeyPair, private val preparingMessage: Message, - private val creatingWalletMessage: Message + private val creatingWalletMessage: Message, ) : CardSessionRunnable { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val card = session.environment.card val publicKey = card?.getSingleWallet()?.publicKey if (publicKey != null) { - if (!card.cardId.startsWith(TwinsHelper.getPairCardSeries(firstCardId) ?: "")) { - callback(CompletionResult.Failure(WrongTwinCard())) + if (TwinsHelper.getTwinCardNumber(card.cardId) == TwinCardNumber.First) { + callback(CompletionResult.Failure(WrongTwinCard(TwinCardNumber.Second))) return } @@ -53,13 +52,10 @@ class CreateSecondTwinWalletTask( is CompletionResult.Success -> { session.environment.card = session.environment.card?.updateWallet(result.data.wallet) - WriteProtectedIssuerDataTask( - firstPublicKey.hexToBytes(), issuerKeys - ).run(session) { writeResult -> + WriteProtectedIssuerDataTask(firstPublicKey.hexToBytes(), issuerKeys).run(session) { writeResult -> when (writeResult) { is CompletionResult.Success -> callback(result) - is CompletionResult.Failure -> - callback(CompletionResult.Failure(writeResult.error)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(writeResult.error)) } } } @@ -67,10 +63,4 @@ class CreateSecondTwinWalletTask( } } } - - private class WrongTwinCard : TangemError { - override val code: Int = 50005 - override var customMessage: String = code.toString() - override val messageResId = R.string.twins_wrong_card_error - } } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index c1f7001ebc..ad9df6d287 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -43,7 +43,6 @@ class TwinCardsManager( ): CompletionResult { val task = CreateSecondTwinWalletTask( firstPublicKey = currentCardPublicKey!!, - firstCardId = currentCardId, issuerKeys = issuerKeyPair, preparingMessage = preparingMessage, creatingWalletMessage = creatingWalletMessage, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt b/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt new file mode 100644 index 0000000000..24d032e94f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.domain.twins + +import com.tangem.common.core.TangemError +import com.tangem.domain.common.TwinCardNumber +import com.tangem.tap.tangemSdkManager +import com.tangem.wallet.R + +data class WrongTwinCard(private val twinCardNumber: TwinCardNumber) : TangemError { + override val code: Int = 50005 + override var customMessage: String = tangemSdkManager.getString( + R.string.twin_error_same_card, + twinCardNumber.number, + ) + override val messageResId: Int? = null +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt index f0917ecf17..da8e03c3b8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ApproveWcSessionDialog.kt @@ -20,7 +20,7 @@ class ApproveWcSessionDialog { session.peerMeta.url, ) return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect)) + setTitle(context.getString(R.string.wallet_connect_title)) setMessage(message) setPositiveButton(context.getText(R.string.common_start)) { _, _ -> store.dispatch(GlobalAction.HideDialog) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt index 3d0fb57930..8de826231f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt @@ -26,14 +26,14 @@ class BnbTransactionDialog { it.symbol, it.price, it.quantity, - it.amount + it.amount, ) - }.joinToString ( "\n\n" ) + }.joinToString(separator = "\n\n") is BinanceMessageData.Transfer -> context.getString( R.string.wallet_connect_bnb_transaction_message, data.address, data.outputAddress, - data.amount + data.amount, ) } @@ -44,14 +44,16 @@ class BnbTransactionDialog { val positiveButtonTitle = context.getText(R.string.common_sign) return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect)) + setTitle(context.getString(R.string.wallet_connect_title)) setMessage(fullMessage) setPositiveButton(positiveButtonTitle) { _, _ -> - store.dispatch(WalletConnectAction.BinanceTransaction.Sign( + store.dispatch( + WalletConnectAction.BinanceTransaction.Sign( id = sessionId, data = data.data, - sessionData = session - )) + sessionData = session, + ), + ) } setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> store.dispatch(WalletConnectAction.RejectRequest(session, sessionId)) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt index 809b37cf66..0213971713 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/ClipboardOrScanQrDialog.kt @@ -13,7 +13,7 @@ class ClipboardOrScanQrDialog { companion object { fun create(wcUri: String, context: Context): AlertDialog { return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect)) + setTitle(context.getString(R.string.wallet_connect_title)) setMessage(context.getText(R.string.wallet_connect_clipboard_alert)) setPositiveButton(context.getText(R.string.wallet_connect_paste_from_clipboard)) { _, _ -> store.dispatch(WalletConnectAction.OpenSession(wcUri)) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt index 5d6e001087..c78549f273 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt @@ -10,19 +10,10 @@ import com.trustwallet.walletconnect.models.session.WCSession class PersonalSignDialog { companion object { - fun create( - data: PersonalSignDialogData, - context: Context, - ): AlertDialog { - val message = - context.getString(R.string.wallet_connect_alert_sign_message) + - context.getString( - R.string.wallet_connect_personal_sign_message, - data.dAppName, - data.message, - ) + fun create(data: PersonalSignDialogData, context: Context): AlertDialog { + val message = context.getString(R.string.wallet_connect_alert_sign_message, data.message) return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect)) + setTitle(context.getString(R.string.wallet_connect_title)) setMessage(message) setPositiveButton(context.getText(R.string.common_sign)) { _, _ -> store.dispatch(WalletConnectAction.SignMessage(data.session)) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt index 245cbd2ae0..cd45c1c9be 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt @@ -22,7 +22,7 @@ class TransactionDialog { data.amount, data.gasAmount, data.totalAmount, - data.balance + data.balance, ) val positiveButtonTitle = when (data.type) { @@ -30,7 +30,7 @@ class TransactionDialog { WcTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send) } return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.wallet_connect)) + setTitle(context.getString(R.string.wallet_connect_title)) setMessage(message) setPositiveButton(positiveButtonTitle) { _, _ -> if (data.isEnoughFundsToSend) { diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index 1e3cc818b4..cea593e6fe 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -51,7 +51,7 @@ fun HomeButtons( }, content = { Text( - text = stringResource(id = R.string.home_button_scan), + text = stringResource(id = R.string.welcome_unlock_card), fontWeight = FontWeight.Medium, fontSize = 16.sp, textAlign = TextAlign.Center, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt index 82d575fafe..558c71a9f6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt @@ -178,7 +178,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } private fun setupDoneState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) { - btnMainAction.setText(R.string.onboarding_done_button_continue) + btnMainAction.setText(R.string.common_continue) btnMainAction.setOnClickListener { showConfetti(false) store.dispatch(OnboardingNoteAction.Done) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index f01eb6fbcd..b0b8ae343d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -91,20 +91,20 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment() { } private fun setupDoneState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) { - btnMainAction.setText(R.string.onboarding_done_button_continue) + btnMainAction.setText(R.string.common_continue) btnMainAction.setOnClickListener { store.dispatch(TwinCardsAction.Confetti.Hide) store.dispatch(TwinCardsAction.Done) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 59b784620f..2ca75121a9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -331,7 +331,7 @@ class OnboardingWalletFragment : BaseFragment(R.layout.fragment_onboarding_walle tvBody.text = getString(R.string.onboarding_twins_interrupt_warning) layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_saltpay_button_backup_origin) } else { - tvHeader.text = getText(R.string.onboarding_title_prepare_origin) + tvHeader.text = getText(R.string.common_origin_card) tvBody.text = getString( R.string.onboarding_subtitle_scan_primary_card_format, state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt index 883ce390dc..92d0c3e0c1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt @@ -8,21 +8,21 @@ import com.tangem.tap.store import com.tangem.wallet.R class AddMoreBackupCardsDialog { - companion object { - fun create(context: Context): AlertDialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.alert_title) - setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added) - setPositiveButton(R.string.common_continue) { _, _ -> - store.dispatch(BackupAction.ShowAccessCodeInfoScreen) - } - setNegativeButton(R.string.onboarding_button_add_more_cards) { _, _ -> + companion object { + fun create(context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.alert_title) + setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added) + setPositiveButton(R.string.common_continue) { _, _ -> + store.dispatch(BackupAction.ShowAccessCodeInfoScreen) + } + setNegativeButton(R.string.common_cancel) { _, _ -> - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - }.create() - } + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 4d6d80e6ce..de2fd70ef6 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -230,12 +230,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : val balanceText = when (state.mainCurrency.type) { MainCurrencyType.FIAT -> fg.getString( - R.string.send_balance_subtitle_format, - state.viewBalanceValue, state.mainCurrency.currencySymbol, + R.string.common_balance, + "${state.viewBalanceValue} ${state.mainCurrency.currencySymbol}", ).remove(":") MainCurrencyType.CRYPTO -> fg.getString( - R.string.send_balance_subtitle_format, - state.mainCurrency.currencySymbol, state.viewBalanceValue, + R.string.common_balance, + "${state.mainCurrency.currencySymbol} ${state.viewBalanceValue}", ) } @@ -304,7 +304,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : val willSent = getString( R.string.send_total_subtitle_format, - receipt.willSentCrypto, receipt.symbols.crypto, + "${receipt.willSentCrypto} ${receipt.symbols.crypto}", ) llTotalContainer.tvWillBeSentValue.update(willSent) } @@ -316,15 +316,13 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : tvReceiptAmountValue.update("${receipt.amountCrypto} ${receipt.symbols.crypto}") tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}") llTotalContainer.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}") - - val willSent = SpannableStringBuilder() - .bold { - append(roughOrEmpty(receipt.willSentFiat)).append(" ") - append(receipt.symbols.fiat) - append(" (fee: ${receipt.feeFiat} ") - append(receipt.symbols.fiat).append(")") - } - llTotalContainer.tvWillBeSentValue.update(willSent.toString()) + llTotalContainer.tvWillBeSentValue.update( + getString( + R.string.send_total_subtitle_fiat_format, + "${roughOrEmpty(receipt.willSentFiat)} ${receipt.symbols.fiat}", + "${receipt.feeFiat} ${receipt.symbols.fiat}", + ), + ) } ReceiptLayoutType.TOKEN_FIAT -> { val receipt = state.tokenFiat ?: return @@ -337,8 +335,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : val willSent = getString( R.string.send_total_subtitle_asset_format, - receipt.symbols.token ?: "", receipt.willSentToken, - receipt.symbols.crypto, receipt.willSentFeeCoin, + "${receipt.symbols.token ?: ""} ${receipt.willSentToken}", + "${receipt.symbols.crypto} ${receipt.willSentFeeCoin}", ) llTotalContainer.tvWillBeSentValue.update(willSent) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/SelectTokenNetworkDialog.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/SelectTokenNetworkDialog.kt index 23781967f0..5dc938ed1c 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/SelectTokenNetworkDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/SelectTokenNetworkDialog.kt @@ -13,9 +13,9 @@ import com.tangem.wallet.R @Composable fun SelectTokenNetworkDialog(dialog: DomainDialog.SelectTokenDialog, onDismissRequest: () -> Unit) { SimpleDialog( - title = stringResource(id = R.string.custom_token_type_network), + title = stringResource(id = R.string.custom_token_network_input_title), items = dialog.items, onSelect = dialog.onSelect, - onDismissRequest = onDismissRequest + onDismissRequest = onDismissRequest, ) { network -> TitleSubtitle(dialog.networkIdConverter(network.networkId), network.contractAddress ?: "") } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 36b1f82337..4ba2a2c082 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -118,7 +118,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), override fun newState(state: TokensState) { if (activity == null || view == null) return val toolbarTitle = - if (state.allowToAdd) R.string.add_tokens_title else R.string.search_tokens_title + if (state.allowToAdd) R.string.main_manage_tokens else R.string.search_tokens_title tokensState.value = state binding.toolbar.title = getString(toolbarTitle) } @@ -143,7 +143,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), val menuItem = menu.findItem(R.id.menu_search) val searchView: SearchView = menuItem.actionView as SearchView - searchView.queryHint = searchView.getString(R.string.add_token_search_hint) + searchView.queryHint = searchView.getString(R.string.common_search) searchView.maxWidth = android.R.attr.width searchView.inputtedTextAsFlow() .debounce(800) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 544d94e6ca..17d3d03cca 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -91,8 +91,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber + tools:text="@string/welcome_unlock_card" /> + app:title="@string/main_manage_tokens" /> @@ -30,7 +30,7 @@ android:id="@+id/cv_currencies" android:layout_width="match_parent" android:layout_height="match_parent" - app:layout_behavior="@string/appbar_scrolling_view_behavior"/> + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details_confirm.xml b/app/src/main/res/layout/fragment_details_confirm.xml index 83bbae68c9..8a56bf635b 100644 --- a/app/src/main/res/layout/fragment_details_confirm.xml +++ b/app/src/main/res/layout/fragment_details_confirm.xml @@ -52,7 +52,7 @@ android:textColor="@color/warning" app:layout_constraintTop_toBottomOf="@id/iv_warning" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"/> + app:layout_constraintEnd_toEndOf="parent" /> + app:layout_constraintEnd_toEndOf="parent" /> + tools:text="@string/welcome_unlock_card" /> + app:layout_constraintBottom_toBottomOf="@id/tv_token_fiat_amount" /> Entfernen Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an - Karte scannen + Karte scannen Absenden ein Wallet erstellen Tangem - Tokens + Tokens Verifizierte Bilanz Die Bilanz wird aufgeladen… - Blockchain ist nicht erreichbar Das Konto ist nicht erstellt Laden Sie %s+ %s auf um ein Konto zu erstellen Leere Karte @@ -48,9 +47,9 @@ Normal Priorität inkl. Gebühr - %s %s wird gesendet - %s %s und %s %s werden gesendet - Bilanz: %s %s + %s wird gesendet + %s und %s werden gesendet + Bilanz: %s Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert Details KartenID @@ -58,7 +57,6 @@ Signiert %s Hasch App Währung - Dieser Vorgang ist irriversibel. Wenn jemand nach dem Löschen des Wallets Geld überweist, können Sie es nicht mehr abheben. Langes Tippen Dieser Mechanismus schützt vor Annäherungsangriffen auf eine Karte. Es wird eine Verzögerung zwischen dem Empfang und der Ausführung eines Befehls erzwungen. Nach der ersten signierten Transaktion wird dieses Telefon mit der Karte verknüpft und die Transaktionen werden sofort signiert Passcode @@ -69,65 +67,12 @@ Warnung Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet - Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel + Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel. Tippen um zu signieren - Tippen Sie um ein Wallet zu erstellen Tippen Sie um den Zugangscode zu ändern Tippen Sie um den Passcode zu ändern - -RECHTLICHER HAFTUNGSAUSSCHLUSS -\n\n1.Tangem App (Software) -  -\n\nDie Software ist nur für die Verwendung mit Tangem hardware Wallets (Karten) über NFC-Schnittstelle geeignet. Die Software: -  -\n\na. generieret, speichert, überträgt KEINE privaten (geheimen) kryptografischen Schlüssel für Blockchain- Wallets mit digitalen Assets, einschließlich Kryptowährung, und hat KEINEN Zugriff darauf. -\n\nb. generieret, speichert, überträgt KEINE geheimen Schlüssel, Passwörter, Passphrasen, Wiederherstellungssätze, die verwendet werden können, um private (geheime) Schlüssel in Blockchain- Wallets, die digitale Assets enthalten, wiederherzustellen oder zu kopieren, einschließlich Kryptowährung, und hat KEINEN Zugriff darauf. -\n\nc. erbringt KEINE Wechsel-, Handels- und Investitionsdienstleistungen im Auftrag der Tangem AG. -  -  -\n\n2. Risiken im Zusammenhang mit der Verwendung von Software -  -\n\nTangem haftet nicht für Verluste, Schäden oder Ansprüche aus den Ereignissen, die in den Geltungsbereich von folgenden fünf Kategorien fallen: -  -\n\na. Fehler, die der Benutzer einer kryptowährungsbezogenen Software oder eines kryptowährungsbezogenen Services macht, z.B. vergessene Passwörter, an falsche Adressen gesendete Zahlungen und zufälliges Löschen von Blockchain-Wallets auf Karten. -\n\nb. Probleme mit Software und/oder blockchain- oder kryptowährungsbezogener Software oder Services, z.B. beschädigte Dateien, falsch konstruierte Transaktionen, unsichere kryptografische Bibliotheken, Schadsoftware. -\n\nc. Technische Ausfälle in der Hardware des Benutzers, einschließlich Karten, jeglicher kryptowährungsbezogener Software oder Services, z.B. Datenverlust infolge eines fehlerhaften oder beschädigten Speichers. -\n\nd. Sicherheitsprobleme, die der Benutzer einer kryptowährungsbezogenen Software oder eines kryptowährungsbezogenen Services hat, z.B. unbefugter Zugriff auf Benutzer-Wallets und/oder -Konten. -\n\ne. Handlungen oder Untätigkeit Dritter und/oder Vorkommnisse bei den Dritten, z.B. Insolvenz von Dienstleistungserbringern, Informationssicherheitsattacken bei den Dienstleistungserbringern und Betrug durch Dritte. -  - -\n\n3.  Tradings- und Investitionsrisiken -  -\n\nBei jeder Kryptowährung oder einer anderen digitalen Assetwechsel-Transaktion besteht ein hohes Risiko. Jede Transaktion mit Währungen birgt Risiken, einschließlich, aber nicht beschränkt auf das Potenzial für sich ändernde wirtschaftliche Bedingungen, die den Preis oder die Liquidität einer Währung erheblich beeinflussen können. Investitionen in Kryptowährungsspekulationen können auch anfällig für starke Anstiege und Rückgänge sein, wenn die relevanten Marktwerte schwanken. Aus diesem Grund ist es bei Spekulationen auf solchen Märkten ratsam, nur das Risikokapital zu verwenden. -  - -\n\n4. Risiken im Zusammenhang mit elektronischem Trading -  -\n\nBevor Sie Transaktionen über ein elektronisches System tätigen, sollten Sie die Regeln und Vorschriften der Börsen, die das System anbieten, und/oder die Instrumente, mit denen Sie handeln möchten, sorgfältig überprüfen. Der Online-Handel birgt ein dazugehörendes Risiko aufgrund der Systemreaktion und der Zugriffszeiten, die aufgrund der Marktbedingungen, der Systemleistung und anderer Faktoren variieren können. Sie sollten diese und andere Risiken vor dem Tradingsbeginn berücksichtigen. -  - -\n\n5. Einhaltung der Steuerpflichten -  -\n\nDie Benutzer der Software müssen allein bestimmen, welche Steuern, wenn überhaupt, für Ihre Kryptowährungstransaktionen gelten. Die Eigentümer oder die Mitwirkenden an der Software sind für die Ermittlung der Steuern, die für Kryptowährungstransaktionen gelten, NICHT verantwortlich. -  - -\n\n6. Garantieausschluss - -\n\nDie Software wird „wie gesehen“ bereitgestellt, ohne jegliche Gewährleistung in Bezug auf die Software und/oder Inhalte, Daten, Materialien und/oder Services, die zur Software gehören. -  - -\n\n7. Haftungsbeschränkung -  -\n\nSofern gesetzlich nichts anderes vorgesehen ist, haften die Eigentümer oder die Mitwirkenden an der Software in keinem Fall für Schäden jeglicher Art, einschließlich, aber nicht beschränkt auf Nutzungsausfall, entgangenen Gewinn oder Datenverlust, die aus der Verwendung der Software oder in Zusammenhang mit der jeglichen Verwendung der Software entstehen. Auf keine Weise sind die Eigentümer oder die Mitwirkenden an der Software für die von Ihnen im Vertrauen auf die Software vorgenommenen oder unterlassenen Handlungen, Entscheidungen oder sonstigen Verhaltensweisen verantwortlich. -  -  -\n\n8. Letzte Änderung -  -\n\nDieser Haftungsausschuss wurde am 1. Oktober 2020 zum letzten Mal geändert. - - Nutzungsbedingungen Keine Internetverbindung @@ -145,5 +90,5 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS Legen Sie die Karte zum Scannen an Legen Sie die Karte an - + ≈ %s (inkl. Gebühr: %s) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8ec21b0632..f631f44d40 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -10,14 +10,13 @@ Supprimer Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité - Scannez la carte + Scannez la carte Envoyer Créer un portefeuille Tangem - Jetons d\'authentification + Jetons d\'authentification Solde confirmé Solde est en cours de téléchargement … - Blockchain est inaccessible Compte n\'est pas créé Pour créer un compte, téléchargez %s+ %s Carte vide @@ -49,9 +48,9 @@ Normal Priorité Inclure les commissions - Sera envoyé %s %s - Sera envoyé %s %s et %s %s - Solde : %s %s + Sera envoyé %s + Sera envoyé %s et %s + Solde : %s La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps Référénces ID de la carte @@ -59,7 +58,6 @@ Signé %s hashes Monnaie de l\'application - Cette action est irréversible. Si, après avoir annulé le portefeuille, quelqu\'un lui envoie des fonds, vous ne pourrez pas les retirer. Tenez la carte fermement Ce mécanisme protège contre les attaques sans contact sur la carte. Il y a un délai entre la réception et l\'exécution de la commande. Après la première transaction signée, ce téléphone sera associé à la carte et les transactions seront signées immédiatement. Mot de passe @@ -69,63 +67,11 @@ Cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable. Attention Cette carte n\'est pas conçue pour fonctionner avec Tangem - La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement + La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement. Touchez pour signer - Touchez, pour créer un portefeuille Touchez, pour modifier le code d\'accès Touchez, pour modifier le mot de passe - - -Avertissement -  -\n\n1.Application Tangem (Logiciel) -  -\n\nLe logiciel est destiné à l\'utilisation exclusivement avec les e-portefeuilles Tangem (Cartes) à l\'aide de l\'interface NFC. Le logiciel NE réalise pas ce qui suit : -  -\n\na. Génération, stockage, transmission ou octroi d\'accès aux clés privées (confidentiels) de chiffrement des blockchain-portefeuilles, qui contiennent les avoirs numériques, y compris la crypto-monnaie. -\n\nb. Génération, stockage, transmission ou octroi d\'accès aux clés confidentielles, aux mots de passe, phrases secrètes, phrases de restitution, qui pourront être utilisées pour la restitution ou pour le clonage des clés privées (confidentiels) de chiffrement des blockchain-portefeuilles, contenant les avoirs numériques, y compris la crypto-monnaie. -\n\nc. Prestation des services de bourse, de commerce, d\'investissement du nom de Tangem AG. -  -  -\n\n2. Risques, liés avec l\'utilisation du logiciel -  -\n\nTangem n\'assume aucune responsabilité pour tous les dommages, préjudice ou prétentions, qui peuvent survenir comme résultat des évènements, se rapportant à une de cinq catégories suivantes : -  -  -\n\na. Les erreurs, commises par l\'utilisateur de tout logiciel ou service liés avec la crypto-monnaie, par exemple, les mots de passe oubliés, les paiements envoyés aux adresses incorrectes, et élimination occasionnelle des blockchain-portefeuilles sur les Cartes. -\n\nb. Les problèmes du Logiciel et/ou de tout progiciel ou service liés avec blockchain ou avec la crypto-monnaie, par exemple, les fichiers endommagés, les transaction créées d\'une manière incorrecte, les bibliothèques cryptographiques non sécurisées, les logiciels malveillants. -\n\nc. Les défaillances techniques du hardware de l\'utilisateur, y compris les Cartes, de tout logiciel ou service liés avec la crypto-monnaie, par exemple, la perte des données à cause des périphériques défectives ou endommagées de stockage. -\n\nd. Les problèmes de la sécurité, subis par l\'utilisateur de tout logiciel ou service liés avec la crypto-monnaie, par exemple, l\'accès non autorisé aux portefeuilles et/ou comptes des utilisateurs. -\n\ne. Les actions ou l\'inaction des tierces personnes et/ou les évènements subis par les tierces personnes, par exemple, la faillite du prestataire de services, les attaques sur la sécurité informatique des prestataires de services et la fraude commise par des tierces personnes. -  -\n\n3.  Risques commerciales et d\'investissement -  -\n\nToute échange de crypto-monnaie ou d\'autres avoirs numériques implique les risques importants. Toute transaction liée avec la monnaie implique les risques, qui incluent, entre autres, la possibilité de modification des conditions économiques, qui peuvent influencer d\'une manière significative le prix ou la disponibilité de la monnaie. Les investissements dans les spéculations boursières avec les crypto-monnaies peuvent aussi être exposés aux montées et chutes en flèche lors des fluctuations des valeurs marchandes correspondants. C\'est pour cette raison, que lors de la spéculation sur tels marchés, il est conseillé d\'utiliser seulement le capital-risque. -  - -\n\n4. Risques de commerce électronique -  -\n\nAvant de passer aux transactions en utilisant le système électronique, Vous devez prendre connaissance, d\'une manière bien attentive, des règles et des dispositions stipulées par les bourses qui offrent ce système, et/ou les listes des titres de placement que vous voulez négocier. Le commerce en ligne possède le risque inaliénable lié avec la vitesse de réponse du système et avec le temps d\'accès qui peuvent varier en fonction des conditions de marché, du rendement du système et d\'autres facteurs. Avant de commencer les négoces, vous devez comprendre ces risques aussi que les risques supplémentaires. -  -\n\n5. Respect des obligations fiscales -  -\n\nLes utilisateurs du Logiciel assument toute la responsabilité pour définir, quels impôts sont appliqués à leurs transactions avec la crypto-monnaie, au cas où il y a de tels impôts. Les propriétaires et les participants à l\'élaboration du Logiciel n\'assument pas la responsabilité pour la définition des impôts qui sont appliqués aux transactions avec la crypto-monnaie. -  -\n\n6. Absence de garanties -
 -\n\nLe logiciel est concédé sous les conditions « tel quel », sans aucune garantie à l\'égard du Logiciel et/ou de tout le contenu, données, matériaux et/ou services, présentes dans ce Logiciel. -  -\n\n7. Restriction de la responsabilité -  -\n\nSauf disposition contraire prévue par la législation, les propriétaires et les participants à l\'élaboration du Logiciel n\'assument pas la responsabilité pour aucun dommage, y compris l\'indisponibilité, la perte de profit ou la perte des données, qui surviennent comme résultat de l\'utilisation ou bien qui sont liés d\'une certaine manière avec l\'utilisation du Logiciel. Les propriétaires et les participants à l\'élaboration du Logiciel n\'assument pas la responsabilité pour les actions, les résolutions ou pour une autre conduite que vous manifestez ou pas par l\'intermédiaire du Logiciel. -  -  -\n\n8. Dernière modification -  -\n\nCette avertissement a été modifié, pour la dernière fois, le 1 octobre 2020. - - Conditions d\'utilisation Pas de connexion internet @@ -138,5 +84,5 @@ Avertissement Posez pour scanner Posez la carte - + ≈ %s (incl. les commissions : %s) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ba083b4b39..613ba4dfc2 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -8,14 +8,13 @@ Annulla Rimuovere Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy - Scansiona carta + Scansiona carta Invia Crea portafoglio Tangem - Token + Token Saldo verificato Il saldo sta per essere caricato… - Blockchain non raggiungibile Conto non creato Scarica %s+ %s per creare un account Carta vuota @@ -48,9 +47,9 @@ Normale Prioritario Includi commissione - Sarà inviato %s %s - Sarà inviato %s %s e %s %s - Saldo: %s %s + Sarà inviato %s + Sarà inviato %s e %s + Saldo: %s La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo Requisiti ID carta @@ -58,7 +57,6 @@ Firmato Hash %s Valuta dell\'applicazione - Questa azione è irreversibile. Se dopo aver eliminato il portafoglio qualcuno invia dei fondi, non potrai ritirarli. Mantenimento della carta Questo meccanismo protegge dagli avvicinamenti senza contatto sulla carta. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla carta e le transazioni verranno firmate immediatamente. Password @@ -68,65 +66,11 @@ Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile. Attenzione Questa carta non è progettata per funzionare con Tangem - La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento + La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento. Avvicina per firmare - Avvicina per creare il portafoglio Avvicina per modificare il codice di accesso Avvicina per modificare la password - - - NOTA LEGALE\n -\n -App Tangem (software)\n -\n -1. Il software può essere utilizzato solo con i portafogli hardware Tangem (schede) tramite l\'interfaccia NFC. Il software NON esegue le seguenti operazioni:\n -\n -a) Generazione, archiviazione, trasferimento o accesso a chiavi crittografiche private (segrete) di portafogli blockchain contenenti risorse digitali, inclusa la criptovaluta.\n -\n -b) Generazione, archiviazione, trasferimento o accesso a chiavi segrete, password, passphrase, frasi di ripristino che possono essere utilizzate per recuperare o copiare chiavi private (segrete) su portafogli blockchain contenenti risorse digitali, inclusa la criptovaluta.\n -\n -c) Fornitura di servizi di scambio, negoziazione e investimento per conto di Tandem AG.\n -\n -2. Rischi associati al funzionamento del Software\n -\n -Tandem non sarà ritenuta responsabile per eventuali perdite, danni o reclami derivanti da eventi che rientrano nelle seguenti cinque categorie:\n -\n -a) Errori commessi dall\'utente di qualsiasi software o servizio relativo alla criptovaluta, ad esempio password dimenticate, pagamenti inviati a indirizzi errati e cancellazione accidentale di portafogli blockchain su Schede.\n -\n -b) Problemi con il software e/o qualsiasi software o servizio correlato a blockchain o criptovaluta, come file danneggiati, transazioni generate in modo improprio, librerie crittografiche non sicure, malware.\n -\n -c) Malfunzionamenti tecnici nell\'hardware dell\'utente, incluse le Schede, qualsiasi software o servizio relativo alla criptovaluta, ad esempio, perdita di dati a causa di un dispositivo di archiviazione difettoso o danneggiato.\n -\n -d) Problemi di sicurezza affrontati dall\'utente di qualsiasi software o servizio relativo alla criptovaluta, ad esempio l\'accesso non autorizzato a portafogli e/o account utente.\n -\n -e) Azioni o inattività di terze parti e/o eventi con cui si sono scontrate terze parti, come la bancarotta di fornitori di servizi, attacchi alla sicurezza delle informazioni relative ai fornitori di servizi e frodi perpetrate da terzi.\n -\n -3. Rischi commerciali e di investimento\n -\n -Qualsiasi transazione che coinvolge lo scambio di criptovalute o altre risorse digitali comporta rischi significativi. Qualsiasi transazione che include una valuta comporta dei rischi, inclusa ma non limitata alla possibilità di cambiamenti nelle condizioni economiche che potrebbero influenzare materialmente il valore o la liquidità della valuta. L\'investimento nella speculazione sui cambi di criptovaluta può anche essere soggetto a forti alti e bassi poiché i rispettivi prezzi di mercato fluttuano. È per questo motivo che è consigliabile utilizzare solo capitale di rischio quando si fanno affari in questi mercati.\n -\n -4. Rischi dell\'e-commerce\n -\n -Prima di effettuare transazioni utilizzando un sistema elettronico, è necessario leggere attentamente le norme e i regolamenti delle borse che offrono questo sistema e/o elencano i titoli che si intende negoziare. Il trading online presenta un rischio intrinseco dovuto alla risposta del sistema e ai tempi di accesso, che possono variare a seconda delle condizioni di mercato, delle prestazioni del sistema ed altri fattori. È necessario comprendere questi e altri rischi prima di fare trading.\n -\n -5. Rispetto degli obblighi fiscali\n -\n -Gli utenti del Software sono gli unici responsabili del determinare le eventuali tasse applicate alle loro transazioni in criptovaluta. I proprietari o i contributori del Software NON sono responsabili della determinazione delle tasse che si applicano alle transazioni in criptovaluta.\n -\n -6. Assenza di garanzie\n -\n -Il Software viene fornito "così com\'è" senza garanzia di alcun tipo riguardo al Software e/o a qualsiasi contenuto, dato, materiale e/o servizio fornito con il Software.\n -\n -7. Limitazione di responsabilità\n -\n -Salvo quanto diversamente previsto dalla legge, i proprietari o i contributori del Software non saranno in nessun caso responsabili per eventuali danni di qualsiasi tipo, inclusi, ma non limitati a, perdita di utilizzo, perdita di profitti o perdita di dati derivanti o in qualsiasi modo correlati all\'uso del Software. In nessun modo i proprietari o i contributori del Software sono responsabili delle azioni, decisioni o altri comportamenti presi o non presi da voi in relazione al Software.\n -\n -8. Ultima modifica\n -\n -Questa nota legale è stata modificata l\'ultima volta 01.10.2020. - - Accetta Termini del servizio Nessuna connessione a Internet @@ -145,5 +89,5 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020. Avvicina per scansionare Avvicina la carta - + ≈ %s (inc. commissione: %s) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 57273eb0aa..3b187cf2d3 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -3,21 +3,21 @@ Tangem Сохранить изменения Предупреждение - Повторить попытку + Повторить Готово Отмена Принять Удалить %s Вы не предоставили доступ к своей камере, измените настройки конфиденциальности - Сканировать карту + Сканировать карту Отправить Создать кошелек Tangem - Токены + Токены Подтвержденный баланс Загрузка баланса… - Блокчейн недоступен + Сеть недоступна Аккаунт не создан Пополните счет на %s+ %s, чтобы создать аккаунт Карта пуста @@ -49,9 +49,9 @@ Нормальный Приоритетный Включить коммисию - %s %s будет отправлено - %s %s и %s %s будут отправлены - Баланс: %s %s + %s будет отправлено + %s и %s будет отправлено + Баланс: %s Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время Подробности ID карты @@ -59,7 +59,6 @@ Подписано %s хэшей Валюта приложения - Это действие необратимо. Если после удаления кошелька кто-то отправит на него средства, то Вы не сможете их вывести. Длительное удержание Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. Пароль @@ -71,7 +70,7 @@ Эта карта не предназначена для работы с этим приложением Карта, которую Вы отсканировали, является картой для разработчиков. Не принимайте ее в качестве оплаты. Нажмите, чтобы подписать - Приложите, чтобы создать кошелек + Чтобы создать кошелек, соедините телефон и карту в точности, как показано выше. Чтобы изменить код доступа, соедините телефон и карту в точности, как показано выше. Чтобы изменить пароль, соедините телефон и карту в точности, как показано выше. Условия использования @@ -88,24 +87,24 @@ Ошибка сканирования карты. Попробуй снова. Купить Продать - Торговать + Обменять По умолчанию Устаревший Создать кошелек Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты Один кошелек. Две карты. - Одна, которую Вы держите в руках, и вторая карта с номером #%s. + Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. Условия использования карты Tangem Twin Это действие необратимо. У Вас не будет доступа к старому кошельку. - Приложите Twin карту #%s + Отсканируйте twin-карту #%s Подготовка карты Создание кошелька Сканировать карту #%s - Вы приложили ту же карту. Чтобы создать кошелек, вам нужно приложить другу Twin карту. + Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d Начать Добавить - Поиск + Искать Отправить отзыв Не могу отправить транзакцию Причина: %s @@ -117,9 +116,6 @@ Может быть лучше Один вопрос Вам нравится приложение Tangem? - Добавить токены - Начните поиск - Значок токена Добавить токен Адрес контракта Обязательное поле @@ -129,7 +125,6 @@ Путь деривации некорректен Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить Этот токен/сеть уже находится в вашем списке - Сеть Знаков после запятой Сеть Не выбрано @@ -166,8 +161,8 @@ Купить криптовалюту Показать адрес кошелька Успешно! - Ваша криптокарта активирована и готова к использованию - Продолжить + Ваша карта активирована и готова к использованию + Продолжить Баланс Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. @@ -180,10 +175,9 @@ Выполнить резервное копирование Оставить на потом Завершить процесс резервного копирования - + Добавить резервную карту + Добавить резервную карту Сканировать карту #%d Сканировать основную карту - Добавить больше карт Нет резервных карт Подготовьте свою карту Добавлена одна резервная карта @@ -225,16 +219,14 @@ Если сейчас отменить резервное копирование, то придется вернуть карты до заводских настроек, чтобы начать заново Возобновить резервное копирование Отказаться - Основная карта + Основная карта Сканировать основную карту Перейти к моему кошельку Отправка %s Подготовьте резервную карту с номером %s Подготовьте основную карту с номером %s - Сброс до заводских настроек Сеть Solana взымает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы использовать его бесплатно. - Эта карта не является векселем на предъявителя. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец скрывает автономную подпись, что является проблемой безопасности.\n\nНе принимайте эту карту в качестве физического платежа от кого-то, кому Вы не доверяете.\n\nВо всех остальных отношениях - это совершенно безопасно.\n\nTangem — единственный аппаратный кошелек, предлагающий защиту от подсчета подписей. - WalletConnect + Эта карта не является векселем на предъявителя. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец скрывает автономную подпись, что является проблемой безопасности.\nНе принимайте эту карту в качестве физического платежа от кого-то, кому Вы не доверяете.\nВо всех остальных отношениях - это совершенно безопасно.\nTangem — единственный аппаратный кошелек, предлагающий защиту от подсчета подписей. Запрос на создание транзакции для %s\n%s\n\nСумма: %s\nКомиссия: %s\nВсего: %s\nБаланс: %s Запрос на открытие сессии для\n%s\n\nСЕТЬ: %s\n\nURL: %s Сессии WalletConnect @@ -242,15 +234,14 @@ Нет открытых сессий WalletConnect Открытая сессия Не удается отправить транзакцию. Недостаточно средств. - Просьба подписать сообщение\n - Сообщение для %s:\n%s + Запрос на подпись сообщения.\n\n%s Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код Вставить из буфера обмена Сканировать новый код Эту карту нельзя использовать для установки сеанса WalletConnect. Операция не может быть завершена. \n\nВы уже установили сеанс WalletConnect с этими параметрами. Не удалось установить сеанс WalletConnect: ошибка времени выполнения. Пожалуйста, повторите попытку позже. - DApp %s, запрашивает\nподпись транзакции BNB с\n%s + Dapp %s, запрос на\nподпись транзакции с BNB.\n\n%s Сведения о транзакции:\nОт: %s\nКому: %s\nСумма: %s Торговый ордер на %s\nЦена: %s\nСумма к получению: %s\nСумма к оплате: %s Мои предложения @@ -264,4 +255,5 @@ Привет, команда поддержки, Информация ниже не является обязательной. Вы можете стереть её, если хотите. Нет + ≈ %s (вкл. комиссию: %s) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ab83f1c625..31d6ed2a36 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,16 +13,16 @@ You have not given access to your camera, please adjust your privacy settings - Scan card + Scan card Send Create wallet Tangem - Tokens + Tokens Verified Balance Balance is loading… - Blockchain is unreachable + Network is unreachable Account is not created Load %s+ %s to create account @@ -59,9 +59,9 @@ Normal Priority Include fee - %s %s will be sent - %s %s and %s %s will be sent - Balance: %s %s + %s will be sent + %s and %s will be sent + Balance: %s Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while Details @@ -69,8 +69,7 @@ Issuer Signed %s hashes - App currency - This action is irreversible. If, after deleting the wallet, someone sends funds to it, then you will not be able to withdraw them. + App Currency Long Tap This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. Passcode @@ -81,64 +80,15 @@ This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about. Warning This card is not designed to work with this app - The card you scanned is a development card. Don\'t accept it as a payment + The card you scanned is a development card. Don\'t accept it as a payment. Tap to sign - Tap to create wallet + To create wallet, connect your phone and the card exactly as it shown above To change the access code, connect your phone and the card exactly as it shown above To change the passcode, connect your phone and the card exactly as it shown above - Legal Disclaimer - \n - \n1. Tangem application (Software) - \n - \nThe Software is intended for usage only with Tangem hardware wallets (Cards) via NFC interface. The Software DOES NOT: - \n - \na) Generate, store, transmit, or have access to private (secret) cryptographic keys to blockchain wallets holding digital assets, including crypto-currency. - \n - \nb) Generate, store, transmit, or have access to secret keys, passwords, passphrases, recovery phrases that can be used to restore or to copy private (secret) keys to blockchain wallets holding digital assets, including crypto-currency. - \n - \nc) Provide exchange, trading, investment services on behalf of Tangem AG. - \n - \n2. Risks related to the use of Software - \n - \nTangem will not be responsible for any losses, damages or claims arising from events falling within the scope of the following five categories: - \n - \na) Mistakes made by the user of any cryptocurrency-related software or service, e.g., forgotten passwords, payments sent to wrong addresses, and accidental deletion of blockchain wallets on Cards. - \n - \nb) Problems of Software and/or any blockchain- or cryptocurrency- related software or service, e.g., corrupted files, incorrectly constructed transactions, unsafe cryptographic libraries, malware. - \n - \nc) Technical failures in the hardware of the user, including Cards, of any cryptocurrency-related software or service, e.g., data loss due to a faulty or damaged storage device. - \n - \nd) Security problems experienced by the user of any cryptocurrency-related software or service, e.g., unauthorized access to users\' wallets and/or accounts. - \n - \ne) Actions or inactions of third parties and/or events experienced by third parties, e.g., bankruptcy of service providers, information security attacks on service providers, and fraud conducted by third parties. - \n - \n3. Trading and Investment risks - \n - \nThere is considerable exposure to risk in any crypto-currency or other digital asset exchange transaction. Any transaction involving currencies involves risks including, but not limited to, the potential for changing economic conditions that may substantially affect the price or liquidity of a currency. Investments in crypto-currency exchange speculation may also be susceptible to sharp rises and falls as the relevant market values fluctuate. It is for this reason that when speculating in such markets it is advisable to use only risk capital. - \n - \n4. Electronic Trading Risks - \n - \nBefore you engage in transactions using an electronic system, you should carefully review the rules and regulations of the exchanges offering the system and/or listing the instruments you intend to trade. Online trading has inherent risk due to system response and access times that may vary due to market conditions, system performance, and other factors. You should understand these and additional risks before trading. - \n - \n5. Compliance with tax obligations - \n - \nThe users of the Software are solely responsible to determinate what, if any, taxes apply to their crypto-currency transactions. The owners of, or contributors to, the Software are NOT responsible for determining the taxes that apply to crypto-currency transactions. - \n - \n6. No warranties - \n - \nThe Software is provided on an \“as is\” basis without any warranties of any kind regarding the Software and/or any content, data, materials and/or services provided on the Software. - \n - \n7. Limitation of liability - \n - \nUnless otherwise required by law, in no event shall the owners of, or contributors to, the Software be liable for any damages of any kind, including, but not limited to, loss of use, loss of profits, or loss of data arising out of or in any way connected with the use of the Software. In no way are the owners of, or contributors to, the Software responsible for the actions, decisions, or other behavior taken or not taken by you in reliance upon the Software. - \n - \n8. Last amendment - \n - \nThis disclaimer was amended for the last time on October 1st, 2020. - Terms of service + Terms of Service No internet connection @@ -156,4 +106,5 @@ Tap to scan Tap the card + ≈ %s (inc. fee: %s) diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index d43c30aab9..369b860e27 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -3,7 +3,7 @@ Buy Sell - Trade + Exchange Default Legacy @@ -11,25 +11,24 @@ Generate wallet keys on both cards to start using your Twins One wallet. Two cards. - This one that you are holding in your hands and the other one with number #%s. + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. Card terms of use Tangem Twin This action is irreversible. You will not have access to the old wallet. - Tap the #%s twin card + Scan the #%s twin card Preparing card Creating wallet Scan the card #%s - You\'ve tapped the same card. To create twin wallet you need to tap a second twin card. - + You\'ve scanned same card. To create twin wallet you need to scan card with number %d //Common Start Add Search - Send feedback + Send Feedback Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? @@ -42,9 +41,6 @@ How do you like Tangem? //Manage tokens - Add Tokens - Custom tokens - Token icon Add Token Contract address @@ -55,7 +51,6 @@ Derivation path is invalid Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. This token/network has already been added to your list - Network Decimals Network Not selected @@ -68,7 +63,6 @@ //Details - %s tokens Please wait for %s transaction to complete to be able to send funds Authenticity check failed @@ -78,16 +72,12 @@ Important security information \u26A0 This card has signed transactions in the past Learn more - This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern. - \n\nDo not accept this card as physical payment from someone you don\'t trust. - \n\nIt\'s perfectly safe in all other respects. - \n\nTangem is the only hardware wallet to offer signature count protection. + This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection. I understand There are only %s signatures available on this card. You must withdraw all of your funds. //Wallet Connect - WalletConnect Wallet Connect Sessions Ooops. No Sessions. No opened WalletConnect sessions @@ -99,8 +89,7 @@ Request to start a session for\n%s\n\nNETWORK: %s\n\nURL: %s - Requesting to sign a message\n - Message for %s:\n%s + Requesting to sign a message.\n\n%s Disconnect @@ -134,13 +123,13 @@ Top up your wallet To get started, simply top up the wallet with any amount - To get started, simply top up the wallet with more than %s %s. + To get started, simply top up the wallet with more than %s %s Buy crypto Show the wallet\'s address Success! - Your crypto card is activated and ready to be used - Continue + Your card is activated and ready to be used + Continue Balance Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. @@ -159,10 +148,9 @@ Backup now Skip for later Finalize the backup process - + Add backup card + Add backup card Scan the card #%d Scan primary card - Add more cards No backup cards Prepare your card @@ -173,7 +161,7 @@ To start the backup process add up to two backup cards. Prepare the primary card You can add one more card or finalize the backup process - Max number of cards added. Finalize the backup process + Max number of cards added. Finalize the backup process. You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it in backup, otherwise you can buy it in shop. Do you like to continue backup process? @@ -182,7 +170,7 @@ Protect Personalize Restore - You have to setup a single access code to protect all yor wallets. + You have to setup a single access code to protect all your wallets You can set an individual access code on each card later. The access code can be restored with a linked card, don\'t keep all cards at one place. Choose any word, phrase, or number you want as your access code. @@ -200,7 +188,7 @@ Access code restore You can backup your keys up to two other blank Tangem Wallet cards. - All the backup cards can be used as full-functoinal with the identical keys. + All the backup cards can be used as full-functional with the identical keys. You will be able to set an access code to protect your wallets. Access code can be restored with one of backup cards. @@ -213,20 +201,14 @@ If you will discard the backup now, then you will have to reset the cards to factory settings to start over again Resume backup Discard - Primary card + Primary card Scan the primary card Continue to my wallet Sending %s Prepare the backup card with number %s Prepare the primary card with number %s - Reset to factory settings - - - Dapp %s, requesting to\n -sign BNB transaction -\n -%s + Dapp %s, requesting to\nsign BNB transaction.\n\n%s Transaction details:\n From: %s\n To: %s\n @@ -238,8 +220,6 @@ Amount to pay: %s Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. - Type here to search - My suggestions Can\'t scan a card Can\'t send a transaction diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index fda7453ad1..1a730aa925 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -61,7 +61,7 @@ object Versions { const val tangemBlockchainSdk = "develop-140" // const val tangemBlockchainSdk = "0.0.1" - const val tangemCardSgk = "develop-165" + const val tangemCardSgk = "develop-169" // const val tangemCardSgk = "0.0.1" // endregion Tangem diff --git a/core/ui/src/main/res/values-de/strings_final.xml b/core/ui/src/main/res/values-de/strings_final.xml index 99b3e6b5b6..ca07d2b5c7 100644 --- a/core/ui/src/main/res/values-de/strings_final.xml +++ b/core/ui/src/main/res/values-de/strings_final.xml @@ -70,7 +70,7 @@ Get your card ready! Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card - App settings + App Settings Keep the wallet in the app Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. Save Access Code diff --git a/core/ui/src/main/res/values-fr/strings_final.xml b/core/ui/src/main/res/values-fr/strings_final.xml index 99b3e6b5b6..ca07d2b5c7 100644 --- a/core/ui/src/main/res/values-fr/strings_final.xml +++ b/core/ui/src/main/res/values-fr/strings_final.xml @@ -70,7 +70,7 @@ Get your card ready! Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card - App settings + App Settings Keep the wallet in the app Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. Save Access Code diff --git a/core/ui/src/main/res/values-it/strings_final.xml b/core/ui/src/main/res/values-it/strings_final.xml index 99b3e6b5b6..ca07d2b5c7 100644 --- a/core/ui/src/main/res/values-it/strings_final.xml +++ b/core/ui/src/main/res/values-it/strings_final.xml @@ -70,7 +70,7 @@ Get your card ready! Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card - App settings + App Settings Keep the wallet in the app Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. Save Access Code diff --git a/core/ui/src/main/res/values/strings_final.xml b/core/ui/src/main/res/values/strings_final.xml index 3460b046ce..6f46bdd1be 100644 --- a/core/ui/src/main/res/values/strings_final.xml +++ b/core/ui/src/main/res/values/strings_final.xml @@ -70,7 +70,7 @@ Get your card ready! Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card - App settings + App Settings Keep the wallet in the app Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. Save Access Code diff --git a/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt b/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt index e6f9c23475..23485d8340 100644 --- a/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt +++ b/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt @@ -15,18 +15,10 @@ class TwinsHelper { return CryptoUtils.verify(cardWalletPublicKey, publicKey, signedKey) } - fun getTwinCardNumber(cardId: String): TwinCardNumber? { - return when { - firstCardSeries.map { cardId.startsWith(it) }.contains(true) -> { - TwinCardNumber.First - } - secondCardSeries.map { cardId.startsWith(it) }.contains(true) -> { - TwinCardNumber.Second - } - else -> { - null - } - } + fun getTwinCardNumber(cardId: String): TwinCardNumber? = when { + firstCardSeries.any(cardId::startsWith) -> TwinCardNumber.First + secondCardSeries.any(cardId::startsWith) -> TwinCardNumber.Second + else -> null } fun getPairCardSeries(cardId: String): String? { From 36a3e2f7d67043740bf993eed5f328aecbc70162 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Nov 2022 11:54:10 +0300 Subject: [PATCH 086/176] Updated on 2026-08-14 --- .../tap/common/extensions/Navigation.kt | 2 + .../com/tangem/tap/common/redux/AppState.kt | 5 +- .../redux/navigation/NavigationState.kt | 3 +- .../features/welcome/redux/WelcomeAction.kt | 18 +++ .../features/welcome/redux/WelcomeState.kt | 5 + .../features/welcome/ui/WelcomeFragment.kt | 74 +++++++++++ .../features/welcome/ui/WelcomeScreenState.kt | 11 ++ .../features/welcome/ui/WelcomeViewModel.kt | 49 +++++++ .../ui/components/WelcomeScreenContent.kt | 122 ++++++++++++++++++ .../main/res/drawable/img_tangem_logo_96.xml | 15 +++ .../com/tangem/core/ui/components/Spacers.kt | 7 + .../com/tangem/core/ui/res/TangemDimens.kt | 1 + .../src/main/res/values-ru/strings_final.xml | 4 + core/ui/src/main/res/values/strings_final.xml | 4 + 14 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt create mode 100644 app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt create mode 100644 app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt create mode 100644 app/src/main/res/drawable/img_tangem_logo_96.xml diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 97575ecd31..c33e466944 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -28,6 +28,7 @@ import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment import com.tangem.tap.features.tokens.ui.AddTokensFragment import com.tangem.tap.features.wallet.ui.WalletDetailsFragment import com.tangem.tap.features.wallet.ui.WalletFragment +import com.tangem.tap.features.welcome.ui.WelcomeFragment import com.tangem.wallet.R import timber.log.Timber @@ -110,5 +111,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.WalletConnectSessions -> WalletConnectFragment() AppScreen.QrScan -> QrScanFragment() AppScreen.ReferralProgram -> ReferralFragment() + AppScreen.Welcome -> WelcomeFragment() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index af79cb81e1..db49b1a222 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -33,6 +33,7 @@ import com.tangem.tap.features.tokens.redux.TokensMiddleware import com.tangem.tap.features.tokens.redux.TokensState import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware +import com.tangem.tap.features.welcome.redux.WelcomeState import org.rekotlin.Middleware import org.rekotlin.StateType @@ -51,6 +52,7 @@ data class AppState( val tokensState: TokensState = TokensState(), val walletConnectState: WalletConnectState = WalletConnectState(), val shopState: ShopState = ShopState(), + val welcomeState: WelcomeState = WelcomeState(), ) : StateType { val domainState: DomainState @@ -81,5 +83,4 @@ data class AppState( ) } } -} - +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index 6d5993cbc0..aa3765d085 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -22,5 +22,6 @@ enum class AppScreen( AddTokens, AddCustomToken, WalletConnectSessions, QrScan, - ReferralProgram + ReferralProgram, + Welcome } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt new file mode 100644 index 0000000000..f68e5122d7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.features.welcome.redux + +import com.tangem.common.core.TangemError +import org.rekotlin.Action + +internal sealed interface WelcomeAction : Action { + object ProceedWithBiometrics : WelcomeAction { + object Success : WelcomeAction + data class Error(val error: TangemError) : WelcomeAction + } + + object ProceedWithCard : WelcomeAction { + object Success : WelcomeAction + data class Error(val error: TangemError) : WelcomeAction + } + + object CloseError : WelcomeAction +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt new file mode 100644 index 0000000000..2d89c4d902 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt @@ -0,0 +1,5 @@ +package com.tangem.tap.features.welcome.redux + +import org.rekotlin.StateType + +class WelcomeState : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt new file mode 100644 index 0000000000..b80c8ac31b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt @@ -0,0 +1,74 @@ +package com.tangem.tap.features.welcome.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.fragment.app.viewModels +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.fragments.ComposeFragment +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent +import com.tangem.wallet.R + +internal class WelcomeFragment : ComposeFragment() { + private val viewModel by viewModels() + + @Composable + override fun provideState(): State { + return viewModel.state.collectAsState() + } + + @Composable + override fun ScreenContent( + modifier: Modifier, + state: WelcomeScreenState, + ) { + val snackbarHostState = remember { SnackbarHostState() } + val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage) + + val backgroundColor = colorResource(id = R.color.background_primary) + SystemBarsEffect { + setSystemBarsColor(color = backgroundColor) + } + + Box( + modifier = modifier + .systemBarsPadding(), + ) { + WelcomeScreenContent( + showUnlockProgress = state.showUnlockWithBiometricsProgress, + showScanCardProgress = state.showUnlockWithCardProgress, + onUnlockClick = viewModel::unlockWallets, + onScanCardClick = viewModel::scanCard, + ) + + SnackbarHost( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(vertical = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + hostState = snackbarHostState, + ) + } + + LaunchedEffect(key1 = errorMessage) { + errorMessage?.let { + snackbarHostState.showSnackbar(it) + viewModel.closeError() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt new file mode 100644 index 0000000000..7a741a391f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.features.welcome.ui + +import androidx.compose.runtime.Immutable +import com.tangem.common.core.TangemError + +@Immutable +internal data class WelcomeScreenState( + val showUnlockWithBiometricsProgress: Boolean = false, + val showUnlockWithCardProgress: Boolean = false, + val error: TangemError? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt new file mode 100644 index 0000000000..ab9e8e6673 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt @@ -0,0 +1,49 @@ +package com.tangem.tap.features.welcome.ui + +import androidx.lifecycle.ViewModel +import com.tangem.tap.features.welcome.redux.WelcomeAction +import com.tangem.tap.features.welcome.redux.WelcomeState +import com.tangem.tap.store +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import org.rekotlin.StoreSubscriber + +internal class WelcomeViewModel : ViewModel(), StoreSubscriber { + private val stateInternal = MutableStateFlow(WelcomeScreenState()) + val state: StateFlow = stateInternal + + init { + subscribeToStoreChanges() + } + + fun unlockWallets() { + store.dispatch(WelcomeAction.ProceedWithBiometrics) + } + + fun scanCard() { + store.dispatch(WelcomeAction.ProceedWithCard) + } + + fun closeError() { + store.dispatch(WelcomeAction.CloseError) + } + + override fun newState(state: WelcomeState) { + stateInternal.update { prevState -> + // TODO: Update screen state + prevState + } + } + + override fun onCleared() { + store.unsubscribe(this) + } + + private fun subscribeToStoreChanges() { + store.subscribe(this) { appState -> + appState.skip { old, new -> old.welcomeState == new.welcomeState } + .select { it.welcomeState } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt new file mode 100644 index 0000000000..f9543d5c97 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -0,0 +1,122 @@ +package com.tangem.tap.features.welcome.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryButtonIconRight +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH32 +import com.tangem.core.ui.components.SpacerHMax +import com.tangem.core.ui.res.TangemTheme +import com.tangem.wallet.R + +@Composable +internal fun WelcomeScreenContent( + modifier: Modifier = Modifier, + showUnlockProgress: Boolean, + showScanCardProgress: Boolean, + onUnlockClick: () -> Unit, + onScanCardClick: () -> Unit, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + SpacerHMax() + Icon( + modifier = Modifier.size(TangemTheme.dimens.size96), + painter = painterResource(id = R.drawable.img_tangem_logo_96), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + SpacerH32() + Text( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(R.string.welcome_unlock_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + SpacerH12() + Text( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing44) + .fillMaxWidth(), + text = stringResource(R.string.welcome_unlock_description), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + SpacerHMax() + SecondaryButton( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(R.string.welcome_unlock_face_id), + showProgress = showUnlockProgress, + onClick = onUnlockClick, + ) + SpacerH12() + PrimaryButtonIconRight( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(R.string.welcome_unlock_card), + showProgress = showScanCardProgress, + icon = painterResource(id = R.drawable.ic_tangem), + onClick = onScanCardClick, + ) + SpacerH16() + } +} + +// region Preview +@Composable +private fun WelcomeScreenContentSample( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + WelcomeScreenContent( + showUnlockProgress = false, + showScanCardProgress = false, + onUnlockClick = { /* no-op */ }, + onScanCardClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WelcomeScreenContentPreview_Light() { + TangemTheme { + WelcomeScreenContentSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WelcomeScreenContentPreview_Dark() { + TangemTheme(isDark = true) { + WelcomeScreenContentSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/res/drawable/img_tangem_logo_96.xml b/app/src/main/res/drawable/img_tangem_logo_96.xml new file mode 100644 index 0000000000..93fe8d9c0f --- /dev/null +++ b/app/src/main/res/drawable/img_tangem_logo_96.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt index 8293073012..a6425652bb 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -3,6 +3,7 @@ package com.tangem.core.ui.components import androidx.annotation.DimenRes +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size @@ -58,6 +59,12 @@ fun SpacerH24(modifier: Modifier = Modifier) { fun SpacerH32(modifier: Modifier = Modifier) { SpacerH(32.dp, modifier) } + +// TODO: Refactor with context receivers +@Composable +fun ColumnScope.SpacerHMax(modifier: Modifier = Modifier) { + Spacer(modifier = modifier.weight(1f)) +} // endregion Horizontal // region Vertical diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 4732b620be..a05eb630a4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -42,6 +42,7 @@ data class TangemDimens internal constructor( val size56: Dp = 56.dp, val size84: Dp = 84.dp, val size86: Dp = 86.dp, + val size96: Dp = 96.dp, val size102: Dp = 102.dp, val size164: Dp = 164.dp, val size200: Dp = 200.dp, diff --git a/core/ui/src/main/res/values-ru/strings_final.xml b/core/ui/src/main/res/values-ru/strings_final.xml index 5b98f5cc07..092befb3cc 100644 --- a/core/ui/src/main/res/values-ru/strings_final.xml +++ b/core/ui/src/main/res/values-ru/strings_final.xml @@ -139,4 +139,8 @@ В этом случае вам будет необходимо начать процесс заново. Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. Внутренняя ошибка: не удается найти менеджер кошельков + C возвращением! + Используйте биометрическую аутентификацию или код доступа для входа в приложение + Войти с биометрией + Сканировать карту diff --git a/core/ui/src/main/res/values/strings_final.xml b/core/ui/src/main/res/values/strings_final.xml index 6f46bdd1be..8ded5c6e4a 100644 --- a/core/ui/src/main/res/values/strings_final.xml +++ b/core/ui/src/main/res/values/strings_final.xml @@ -141,4 +141,8 @@ In this case, you will need to start from the beginning. You have used a card from another wallet. Tap the card associated with this wallet Internal error: wallet manager not found + Welcome back! + Use biometric authentication or scan a card to access the app + Log in with biometric + Scan card From 0cc270adc0feca0068e9f1f53a123bec39e0ea20 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 11 Nov 2022 23:26:10 +0400 Subject: [PATCH 087/176] Updated on 2026-08-14 --- .../tap/common/extensions/Navigation.kt | 2 + .../com/tangem/tap/common/redux/AppState.kt | 2 + .../redux/navigation/NavigationState.kt | 3 +- .../saveWallet/redux/SaveWalletAction.kt | 17 ++ .../saveWallet/redux/SaveWalletState.kt | 5 + .../ui/SaveWalletBottomSheetFragment.kt | 83 +++++++++ .../saveWallet/ui/SaveWalletScreenState.kt | 12 ++ .../saveWallet/ui/SaveWalletViewModel.kt | 51 ++++++ .../ui/components/SaveWalletScreenContent.kt | 157 ++++++++++++++++++ .../main/res/drawable/ic_fingerprint_24.xml | 9 + .../dialogs/EnrollBiometricsDialogConent.kt | 97 +++++++++++ .../core/ui/models/EnrollBiometricsDialog.kt | 9 + .../src/main/res/values-ru/strings_final.xml | 9 + core/ui/src/main/res/values/strings_final.xml | 9 + 14 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt create mode 100644 app/src/main/res/drawable/ic_fingerprint_24.xml create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/dialogs/EnrollBiometricsDialogConent.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/models/EnrollBiometricsDialog.kt diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index c33e466944..dae414e733 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -22,6 +22,7 @@ import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment +import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment import com.tangem.tap.features.send.ui.SendFragment import com.tangem.tap.features.shop.ui.ShopFragment import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment @@ -112,5 +113,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.QrScan -> QrScanFragment() AppScreen.ReferralProgram -> ReferralFragment() AppScreen.Welcome -> WelcomeFragment() + AppScreen.SaveWallet -> SaveWalletBottomSheetFragment() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index db49b1a222..d46d70029f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -25,6 +25,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayMiddleware +import com.tangem.tap.features.saveWallet.redux.SaveWalletState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware import com.tangem.tap.features.send.redux.states.SendState import com.tangem.tap.features.shop.redux.ShopMiddleware @@ -53,6 +54,7 @@ data class AppState( val walletConnectState: WalletConnectState = WalletConnectState(), val shopState: ShopState = ShopState(), val welcomeState: WelcomeState = WelcomeState(), + val saveWalletState: SaveWalletState = SaveWalletState(), ) : StateType { val domainState: DomainState diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index aa3765d085..ba861a55dc 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -23,5 +23,6 @@ enum class AppScreen( WalletConnectSessions, QrScan, ReferralProgram, - Welcome + Welcome, + SaveWallet(isDialogFragment = true), } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt new file mode 100644 index 0000000000..3e0bbe4a7e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.features.saveWallet.redux + +import com.tangem.common.core.TangemError +import org.rekotlin.Action + +internal sealed interface SaveWalletAction : Action { + object Save : SaveWalletAction { + object Success : SaveWalletAction + data class Error(val error: TangemError) : SaveWalletAction + } + + object Dismiss : SaveWalletAction + + object CloseError : SaveWalletAction + + object SaveWalletWasShown : SaveWalletAction +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt new file mode 100644 index 0000000000..d43ee7c96b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt @@ -0,0 +1,5 @@ +package com.tangem.tap.features.saveWallet.redux + +import org.rekotlin.StateType + +class SaveWalletState : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt new file mode 100644 index 0000000000..6abc3c607e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -0,0 +1,83 @@ +package com.tangem.tap.features.saveWallet.ui + +import android.content.DialogInterface +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.fragment.app.viewModels +import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent +import com.tangem.core.ui.fragments.ComposeBottomSheetFragment +import com.tangem.core.ui.models.EnrollBiometricsDialog +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent + +internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() { + override val expandedHeightFraction: Float = .95f + + private val viewModel by viewModels() + + override fun onDismiss(dialog: DialogInterface) { + viewModel.dismiss() + super.onDismiss(dialog) + } + + @Composable + override fun provideState(): State { + return viewModel.state.collectAsState() + } + + @Composable + override fun ScreenContent( + modifier: Modifier, + state: SaveWalletScreenState, + ) { + val snackbarHostState = remember { SnackbarHostState() } + val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage) + val enrollBiometricsDialog by rememberUpdatedState(newValue = state.enrollBiometricsDialog) + + Box(modifier = modifier) { + SaveWalletScreenContent( + showProgress = state.showProgress, + onSaveWalletClick = viewModel::saveWallet, + onCloseClick = this@SaveWalletBottomSheetFragment::dismissAllowingStateLoss, + ) + + SnackbarHost( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(vertical = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + hostState = snackbarHostState, + ) + } + + EnrollBiometricsDialog(dialog = enrollBiometricsDialog) + + LaunchedEffect(key1 = errorMessage) { + errorMessage?.let { + snackbarHostState.showSnackbar(it) + viewModel.closeError() + } + } + } + + @Composable + private fun EnrollBiometricsDialog( + modifier: Modifier = Modifier, + dialog: EnrollBiometricsDialog?, + ) { + if (dialog == null) return + EnrollBiometricsDialogContent(modifier, dialog) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt new file mode 100644 index 0000000000..9059d0d97d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.features.saveWallet.ui + +import androidx.compose.runtime.Immutable +import com.tangem.common.core.TangemError +import com.tangem.core.ui.models.EnrollBiometricsDialog + +@Immutable +internal data class SaveWalletScreenState( + val showProgress: Boolean = false, + val enrollBiometricsDialog: EnrollBiometricsDialog? = null, + val error: TangemError? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt new file mode 100644 index 0000000000..24e2ef650c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt @@ -0,0 +1,51 @@ +package com.tangem.tap.features.saveWallet.ui + +import androidx.lifecycle.ViewModel +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.features.saveWallet.redux.SaveWalletAction +import com.tangem.tap.features.saveWallet.redux.SaveWalletState +import com.tangem.tap.store +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import org.rekotlin.StoreSubscriber + +internal class SaveWalletViewModel : ViewModel(), StoreSubscriber { + private val stateInternal = MutableStateFlow(SaveWalletScreenState()) + val state: StateFlow = stateInternal + + init { + subscribeToStoreChanges() + store.dispatchOnMain(SaveWalletAction.SaveWalletWasShown) + } + + fun saveWallet() { + store.dispatch(SaveWalletAction.Save) + } + + fun dismiss() { + store.dispatch(SaveWalletAction.Dismiss) + } + + fun closeError() { + store.dispatch(SaveWalletAction.CloseError) + } + + override fun newState(state: SaveWalletState) { + stateInternal.update { prevState -> + // TODO: Update screen state + prevState + } + } + + override fun onCleared() { + store.unsubscribe(this) + } + + private fun subscribeToStoreChanges() { + store.subscribe(this) { appState -> + appState.skip { old, new -> old.saveWalletState == new.saveWalletState } + .select { it.saveWalletState } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt new file mode 100644 index 0000000000..b2eeba4b72 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt @@ -0,0 +1,157 @@ +package com.tangem.tap.features.saveWallet.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH24 +import com.tangem.core.ui.components.atoms.Hand +import com.tangem.core.ui.res.TangemTheme +import com.tangem.wallet.R + +@Composable +internal fun SaveWalletScreenContent( + modifier: Modifier = Modifier, + showProgress: Boolean, + onSaveWalletClick: () -> Unit, + onCloseClick: () -> Unit, +) { + Column( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.spacing16), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Hand() + IconButton( + modifier = Modifier + .align(Alignment.End) + .padding(all = TangemTheme.dimens.spacing8) + .size(TangemTheme.dimens.size32), + onClick = onCloseClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_close), + tint = TangemTheme.colors.icon.secondary, + contentDescription = stringResource(id = R.string.common_cancel), + ) + } + Spacer(modifier = Modifier.weight(.7f)) + Icon( + modifier = Modifier.size(TangemTheme.dimens.size56), + painter = painterResource(id = R.drawable.ic_fingerprint_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + SpacerH24() + Box( + modifier = Modifier + .background( + color = TangemTheme.colors.text.accent.copy(alpha = .12f), + shape = TangemTheme.shapes.roundedCornersMedium, + ), + ) { + Text( + modifier = Modifier + .padding( + vertical = TangemTheme.dimens.spacing4, + horizontal = TangemTheme.dimens.spacing12, + ), + text = stringResource(R.string.save_user_wallet_agreement_new_feature), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.accent, + textAlign = TextAlign.Center, + ) + } + SpacerH24() + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.save_user_wallet_agreement_header), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + SpacerH12() + Text( + modifier = Modifier.fillMaxWidth(fraction = .87f), + text = stringResource(R.string.save_user_wallet_agreement_description), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.weight(1f)) + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + showProgress = showProgress, + text = stringResource(R.string.save_user_wallet_agreement_allow), + onClick = onSaveWalletClick, + ) + SpacerH16() + Text( + modifier = Modifier.fillMaxWidth(fraction = .7f), + text = stringResource(R.string.save_user_wallet_agreement_notice), + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + ) + SpacerH16() + } +} + +// region Preview +@Composable +private fun SaveWalletScreenContentSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + SaveWalletScreenContent( + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing32) + .fillMaxSize() + .background( + color = TangemTheme.colors.background.plain, + shape = TangemTheme.shapes.bottomSheet, + ), + showProgress = false, + onSaveWalletClick = { /* no-op */ }, + onCloseClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun SaveWalletScreenContentPreview_Light() { + TangemTheme { + SaveWalletScreenContentSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun SaveWalletScreenContentPreview_Dark() { + TangemTheme(isDark = true) { + SaveWalletScreenContentSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_fingerprint_24.xml b/app/src/main/res/drawable/ic_fingerprint_24.xml new file mode 100644 index 0000000000..df5bdf4868 --- /dev/null +++ b/app/src/main/res/drawable/ic_fingerprint_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/dialogs/EnrollBiometricsDialogConent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/dialogs/EnrollBiometricsDialogConent.kt new file mode 100644 index 0000000000..a75943cc04 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/dialogs/EnrollBiometricsDialogConent.kt @@ -0,0 +1,97 @@ +package com.tangem.core.ui.components.dialogs + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.window.Dialog +import com.tangem.core.ui.R +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH24 +import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.components.TextButton +import com.tangem.core.ui.models.EnrollBiometricsDialog +import com.tangem.core.ui.res.TangemTheme + +@Composable +fun EnrollBiometricsDialogContent( + modifier: Modifier = Modifier, + dialog: EnrollBiometricsDialog, +) { + Dialog(onDismissRequest = dialog.onCancel) { + Column( + modifier = modifier + .background( + shape = TangemTheme.shapes.roundedCornersLarge, + color = TangemTheme.colors.background.plain, + ) + .padding(all = TangemTheme.dimens.spacing24), + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_title), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + ) + SpacerH16() + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_description), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + SpacerH24() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + TextButton( + text = stringResource(R.string.common_cancel), + onClick = dialog.onCancel, + ) + SpacerW8() + TextButton( + text = stringResource(R.string.common_enable), + onClick = dialog.onEnroll, + ) + } + } + } +} + +// region Preview +@Composable +private fun EnrollBiometricDialogContentSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + EnrollBiometricsDialogContent(dialog = EnrollBiometricsDialog({}, {})) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun EnrollBiometricDialogContentPreview_Light() { + TangemTheme { + EnrollBiometricDialogContentSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun EnrollBiometricDialogContentPreview_Dark() { + TangemTheme(isDark = true) { + EnrollBiometricDialogContentSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/models/EnrollBiometricsDialog.kt b/core/ui/src/main/java/com/tangem/core/ui/models/EnrollBiometricsDialog.kt new file mode 100644 index 0000000000..a48550d1c1 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/models/EnrollBiometricsDialog.kt @@ -0,0 +1,9 @@ +package com.tangem.core.ui.models + +import androidx.compose.runtime.Immutable + +@Immutable +data class EnrollBiometricsDialog( + val onEnroll: () -> Unit, + val onCancel: () -> Unit, +) \ No newline at end of file diff --git a/core/ui/src/main/res/values-ru/strings_final.xml b/core/ui/src/main/res/values-ru/strings_final.xml index 092befb3cc..20b896eb21 100644 --- a/core/ui/src/main/res/values-ru/strings_final.xml +++ b/core/ui/src/main/res/values-ru/strings_final.xml @@ -143,4 +143,13 @@ Используйте биометрическую аутентификацию или код доступа для входа в приложение Войти с биометрией Сканировать карту + Новый функционал + Сохраните свой кошелек + Функция сохранения кошелька позволяет вам использовать свой кошелек с биометрической авторизацией, не прикладывая карту к телефону для получения доступа. + Разрешить привязку кошелька + Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта + Включите биометрическую авторизацию + Похоже, что у вас отключена биометрическая авторизация, она необходима для сохранения кошельков + Включить + Отмена diff --git a/core/ui/src/main/res/values/strings_final.xml b/core/ui/src/main/res/values/strings_final.xml index 8ded5c6e4a..a6fd518ac9 100644 --- a/core/ui/src/main/res/values/strings_final.xml +++ b/core/ui/src/main/res/values/strings_final.xml @@ -145,4 +145,13 @@ Use biometric authentication or scan a card to access the app Log in with biometric Scan card + New feature + Save your Wallet + Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. + Allow to link wallet + Making a transaction with your funds will still require card tapping + Enable biometric authorization + It looks like you have biometric authorization disabled, it is necessary to save wallets + Enable + Cancel From 47412b1d63c4bff9184de93490504413e73a73a3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 12 Nov 2022 00:36:45 +0400 Subject: [PATCH 088/176] Updated on 2026-08-14 --- .../tap/common/extensions/Navigation.kt | 10 +- .../com/tangem/tap/common/redux/AppState.kt | 2 + .../redux/navigation/NavigationState.kt | 1 + .../redux/WalletSelectorAction.kt | 31 +++ .../redux/WalletSelectorState.kt | 5 + .../ui/WalletSelectorBottomSheetFragment.kt | 80 ++++++ .../ui/WalletSelectorScreenState.kt | 20 ++ .../ui/WalletSelectorViewModel.kt | 143 ++++++++++ .../walletSelector/ui/components/MockData.kt | 30 +++ .../components/RenameWalletDialogContent.kt | 111 ++++++++ .../WallectSelectorScreenContent.kt | 251 ++++++++++++++++++ .../ui/components/WalletItem.kt | 204 ++++++++++++++ .../ui/components/WalletList.kt | 66 +++++ .../ui/model/RenameWalletDialog.kt | 10 + .../walletSelector/ui/model/UserWalletItem.kt | 46 ++++ app/src/main/res/drawable/ic_check_24.xml | 9 + .../main/res/drawable/ic_check_circle_18.xml | 9 + app/src/main/res/drawable/ic_locked_24.xml | 9 + .../res/drawable/ic_pencil_outline_24.xml | 9 + .../com/tangem/core/ui/components/Spacers.kt | 17 ++ .../com/tangem/core/ui/res/TangemDimens.kt | 3 + .../res/values-de/strings_plurals_final.xml | 5 + .../res/values-fr/strings_plurals_final.xml | 6 + .../res/values-it/strings_plurals_final.xml | 5 + .../src/main/res/values-ru/strings_final.xml | 8 + .../res/values-ru/strings_plurals_final.xml | 7 + core/ui/src/main/res/values/strings_final.xml | 8 + .../main/res/values/strings_plurals_final.xml | 7 + 28 files changed, 1110 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorScreenState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletList.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/RenameWalletDialog.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt create mode 100644 app/src/main/res/drawable/ic_check_24.xml create mode 100644 app/src/main/res/drawable/ic_check_circle_18.xml create mode 100644 app/src/main/res/drawable/ic_locked_24.xml create mode 100644 app/src/main/res/drawable/ic_pencil_outline_24.xml diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index dae414e733..3abbbac6d1 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -29,6 +29,7 @@ import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment import com.tangem.tap.features.tokens.ui.AddTokensFragment import com.tangem.tap.features.wallet.ui.WalletDetailsFragment import com.tangem.tap.features.wallet.ui.WalletFragment +import com.tangem.tap.features.walletSelector.ui.WalletSelectorBottomSheetFragment import com.tangem.tap.features.welcome.ui.WelcomeFragment import com.tangem.wallet.R import timber.log.Timber @@ -52,10 +53,14 @@ fun FragmentActivity.openFragment( } if (screen.isDialogFragment) { (fragment as DialogFragment).show(transaction, screen.name) - if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) + if (addToBackstack) { + transaction.addToBackStack(null) + } } else { transaction.replace(R.id.fragment_container, fragment, screen.name) - if (addToBackstack && screen != AppScreen.Home) transaction.addToBackStack(null) + if (addToBackstack && (screen != AppScreen.Home && screen != AppScreen.Welcome)) { + transaction.addToBackStack(null) + } transaction.commitAllowingStateLoss() } } @@ -114,5 +119,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.ReferralProgram -> ReferralFragment() AppScreen.Welcome -> WelcomeFragment() AppScreen.SaveWallet -> SaveWalletBottomSheetFragment() + AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index d46d70029f..a158f2c872 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -35,6 +35,7 @@ import com.tangem.tap.features.tokens.redux.TokensState import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware import com.tangem.tap.features.welcome.redux.WelcomeState +import com.tangem.tap.features.walletSelector.redux.WalletSelectorState import org.rekotlin.Middleware import org.rekotlin.StateType @@ -55,6 +56,7 @@ data class AppState( val shopState: ShopState = ShopState(), val welcomeState: WelcomeState = WelcomeState(), val saveWalletState: SaveWalletState = SaveWalletState(), + val walletSelectorState: WalletSelectorState = WalletSelectorState(), ) : StateType { val domainState: DomainState diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index ba861a55dc..e3d82a292a 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -25,4 +25,5 @@ enum class AppScreen( ReferralProgram, Welcome, SaveWallet(isDialogFragment = true), + WalletSelector(isDialogFragment = true), } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt new file mode 100644 index 0000000000..51a7253075 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.features.walletSelector.redux + +import com.tangem.common.core.TangemError +import org.rekotlin.Action + +internal sealed interface WalletSelectorAction : Action { + object UnlockWithBiometry : WalletSelectorAction { + object Success : WalletSelectorAction + data class Error(val error: TangemError) : WalletSelectorAction + } + + data class SelectWallet( + val walletId: String, + ) : WalletSelectorAction + + data class RenameWallet( + val walletId: String, + val newName: String, + ) : WalletSelectorAction + + data class RemoveWallets( + val walletIdsToRemove: List, + ) : WalletSelectorAction + + object AddWallet : WalletSelectorAction { + object Success : WalletSelectorAction + data class Error(val error: TangemError) : WalletSelectorAction + } + + object CloseError : WalletSelectorAction +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt new file mode 100644 index 0000000000..c84839277a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt @@ -0,0 +1,5 @@ +package com.tangem.tap.features.walletSelector.redux + +import org.rekotlin.StateType + +class WalletSelectorState : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt new file mode 100644 index 0000000000..47c6ca9bf2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -0,0 +1,80 @@ +package com.tangem.tap.features.walletSelector.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.fragment.app.viewModels +import com.tangem.core.ui.fragments.ComposeBottomSheetFragment +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent +import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent +import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog + +internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment() { + private val viewModel by viewModels() + + @Composable + override fun provideState(): State { + return viewModel.state.collectAsState() + } + + @Composable + override fun ScreenContent( + modifier: Modifier, + state: WalletSelectorScreenState, + ) { + val snackbarHostState = remember { SnackbarHostState() } + val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage) + val renameWalletDialog by rememberUpdatedState(newValue = state.renameWalletDialog) + + Box(modifier = modifier) { + WalletSelectorScreenContent( + state = state, + onWalletClick = viewModel::walletClicked, + onWalletLongClick = viewModel::walletLongClicked, + onUnlockClick = viewModel::unlock, + onAddCardClick = viewModel::addWallet, + onClearSelectedClick = viewModel::cancelWalletsEditing, + onEditSelectedWalletClick = viewModel::renameWallet, + onDeleteSelectedWalletsClick = viewModel::deleteWallets, + ) + + SnackbarHost( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(vertical = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + hostState = snackbarHostState, + ) + } + + RenameWalletDialog(dialog = renameWalletDialog) + + LaunchedEffect(key1 = errorMessage) { + errorMessage?.let { + snackbarHostState.showSnackbar(it) + viewModel.closeError() + } + } + } + + @Composable + private fun RenameWalletDialog( + modifier: Modifier = Modifier, + dialog: RenameWalletDialog?, + ) { + if (dialog == null) return + RenameWalletDialogContent(modifier, dialog) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorScreenState.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorScreenState.kt new file mode 100644 index 0000000000..bc3016f8f2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorScreenState.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.features.walletSelector.ui + +import androidx.compose.runtime.Immutable +import com.tangem.common.core.TangemError +import com.tangem.tap.common.entities.FiatCurrency +import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog +import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem + +@Immutable +internal data class WalletSelectorScreenState( + val wallets: List = listOf(), + val selectedWalletId: String? = null, + val fiatCurrency: FiatCurrency = FiatCurrency.Default, + val isLocked: Boolean = true, + val editingWalletsIds: List = listOf(), + val renameWalletDialog: RenameWalletDialog? = null, + val showAddCardProgress: Boolean = false, + val showUnlockProgress: Boolean = false, + val error: TangemError? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt new file mode 100644 index 0000000000..cb6d75b0b0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -0,0 +1,143 @@ +package com.tangem.tap.features.walletSelector.ui + +import androidx.lifecycle.ViewModel +import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction +import com.tangem.tap.features.walletSelector.redux.WalletSelectorState +import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog +import com.tangem.tap.store +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import org.rekotlin.StoreSubscriber + +internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber { + private val stateInternal = MutableStateFlow(WalletSelectorScreenState()) + val state: StateFlow = stateInternal + + init { + subscribeToStoreChanges() + bootstrapWalletListChanges() + bootstrapWalletsStoresChanges() + } + + fun unlock() { + store.dispatch(WalletSelectorAction.UnlockWithBiometry) + } + + fun addWallet() { + store.dispatch(WalletSelectorAction.AddWallet) + } + + fun walletClicked(walletId: String) = with(state.value) { + when { + isLocked -> Unit + editingWalletsIds.isNotEmpty() && !editingWalletsIds.contains(walletId) -> { + editWallet(walletId) + } + editingWalletsIds.isNotEmpty() && editingWalletsIds.contains(walletId) -> { + cancelWalletEditing(walletId) + } + selectedWalletId != walletId -> { + store.dispatch(WalletSelectorAction.SelectWallet(walletId)) + } + } + } + + fun walletLongClicked(walletId: String) = with(state.value) { + if (!isLocked && editingWalletsIds.isEmpty()) { + editWallet(walletId) + } + } + + fun cancelWalletsEditing() { + stateInternal.update { prevState -> + prevState.copy( + editingWalletsIds = emptyList(), + ) + } + } + + fun renameWallet() = with(state.value) { + if (editingWalletsIds.isNotEmpty() && renameWalletDialog == null) { + val editedWalletId = editingWalletsIds.first() + val editedWallet = wallets.find { it.id == editedWalletId } + + if (editedWallet != null) { + val dialog = RenameWalletDialog( + currentName = editedWallet.name, + onApply = { newName -> + store.dispatch(WalletSelectorAction.RenameWallet(editedWalletId, newName)) + stateInternal.update { prevState -> + prevState.copy( + renameWalletDialog = null, + editingWalletsIds = emptyList(), + ) + } + }, + onCancel = { + stateInternal.update { prevState -> + prevState.copy( + renameWalletDialog = null, + ) + } + }, + ) + + stateInternal.update { prevState -> + prevState.copy( + renameWalletDialog = dialog, + ) + } + } + } + } + + fun deleteWallets() = with(state.value) { + if (editingWalletsIds.isNotEmpty()) { + store.dispatch(WalletSelectorAction.RemoveWallets(walletIdsToRemove = editingWalletsIds)) + } + } + + fun closeError() { + store.dispatch(WalletSelectorAction.CloseError) + } + + override fun newState(state: WalletSelectorState) { + // TODO: Update UI state + } + + override fun onCleared() { + store.unsubscribe(this) + } + + private fun editWallet(walletId: String) { + stateInternal.update { prevState -> + prevState.copy( + editingWalletsIds = prevState.editingWalletsIds + walletId, + ) + } + } + + private fun cancelWalletEditing(walletId: String) { + stateInternal.update { prevState -> + prevState.copy( + editingWalletsIds = prevState.editingWalletsIds - walletId, + ) + } + } + + private fun subscribeToStoreChanges() { + store.subscribe(this) { appState -> + appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState } + .select { it.walletSelectorState } + } + } + + private fun bootstrapWalletListChanges() { + // TODO + } + + private fun bootstrapWalletsStoresChanges() { + // TODO + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt new file mode 100644 index 0000000000..7fe1623321 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.features.walletSelector.ui.components + +import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState +import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem + +internal object MockData { + private val userWallet = UserWalletItem( + id = "wallet_1", + balance = UserWalletItem.Balance( + amount = "6781.05 $", + isLoading = false, + ), + name = "Wallet", + imageUrl = "https://app.tangem.com/cards/card_default.png", + type = UserWalletItem.Type.MultiCurrency(tokensCount = 12, cardsInWallet = 3), + ) + + val userWalletList = listOf( + userWallet, + userWallet.copy(id = "wallet_2"), + userWallet.copy(id = "wallet_3", type = UserWalletItem.Type.MultiCurrency(tokensCount = 2, cardsInWallet = 1)), + userWallet.copy(id = "wallet_4", type = UserWalletItem.Type.SingleCurrency(tokenName = "Ethereum")), + ) + + val state = WalletSelectorScreenState( + wallets = userWalletList, + selectedWalletId = userWalletList[0].id, + isLocked = false, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt new file mode 100644 index 0000000000..f6aa55d7a0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt @@ -0,0 +1,111 @@ +package com.tangem.tap.features.walletSelector.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.window.Dialog +import com.tangem.core.ui.components.OutlineTextField +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH24 +import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.components.TextButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog +import com.tangem.wallet.R + +@Composable +internal fun RenameWalletDialogContent( + modifier: Modifier = Modifier, + dialog: RenameWalletDialog, +) { + Dialog(onDismissRequest = dialog.onCancel) { + Column( + modifier = modifier + .background( + shape = TangemTheme.shapes.roundedCornersLarge, + color = TangemTheme.colors.background.plain, + ) + .padding(all = TangemTheme.dimens.spacing24), + ) { + var value by remember { + mutableStateOf(TextFieldValue(text = dialog.currentName)) + } + + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.user_wallet_list_rename_popup_title), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + ) + SpacerH16() + OutlineTextField( + value = value, + label = stringResource(R.string.user_wallet_list_rename_popup_placeholder), + onValueChange = { newValue -> + value = newValue + }, + ) + SpacerH24() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + TextButton( + text = stringResource(id = R.string.common_cancel), + onClick = dialog.onCancel, + ) + SpacerW8() + TextButton( + text = stringResource(id = R.string.common_save), + enabled = value.text.isNotEmpty() && value.text != dialog.currentName, + onClick = { + dialog.onApply(value.text) + }, + ) + } + } + } +} + +// region Preview +@Composable +private fun RenameWalletDialogContentSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + RenameWalletDialogContent(dialog = RenameWalletDialog("", {}, {})) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun RenameWalletDialogContentPreview_Light() { + TangemTheme { + RenameWalletDialogContentSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun RenameWalletDialogContentPreview_Dark() { + TangemTheme(isDark = true) { + RenameWalletDialogContentSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt new file mode 100644 index 0000000000..1fe2d1e173 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -0,0 +1,251 @@ +package com.tangem.tap.features.walletSelector.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButtonIconRight +import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH24 +import com.tangem.core.ui.components.SpacerW16 +import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.components.atoms.Hand +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState +import com.tangem.wallet.R + +@Composable +internal fun WalletSelectorScreenContent( + modifier: Modifier = Modifier, + state: WalletSelectorScreenState, + onWalletClick: (walletId: String) -> Unit, + onWalletLongClick: (walletId: String) -> Unit, + onUnlockClick: () -> Unit, + onAddCardClick: () -> Unit, + onClearSelectedClick: () -> Unit, + onEditSelectedWalletClick: () -> Unit, + onDeleteSelectedWalletsClick: () -> Unit, +) { + val editingWalletsSize by rememberUpdatedState(newValue = state.editingWalletsIds.size) + + Column(modifier = modifier) { + Hand() + SpacerH12() + if (editingWalletsSize > 0) { + EditWalletsBar( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + editingWalletsSize = editingWalletsSize, + onClearSelectedClick = onClearSelectedClick, + onEditSelectedWalletClick = onEditSelectedWalletClick, + onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, + ) + } else { + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.user_wallet_list_title), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + } + SpacerH12() + WalletList( + wallets = state.wallets, + selectedWalletId = state.selectedWalletId, + checkedWalletIds = state.editingWalletsIds, + isLocked = state.isLocked, + onWalletClick = onWalletClick, + onWalletLongClick = onWalletLongClick, + ) + SpacerH24() + if (state.isLocked) { + PrimaryButton( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(R.string.user_wallet_list_unlock_all_face_id), + showProgress = state.showUnlockProgress, + onClick = onUnlockClick, + ) + SpacerH12() + } + SecondaryButtonIconRight( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(R.string.user_wallet_list_add_button), + showProgress = state.showAddCardProgress, + icon = painterResource(id = R.drawable.ic_tangem), + onClick = onAddCardClick, + ) + SpacerH16() + } +} + +@Composable +private fun EditWalletsBar( + modifier: Modifier = Modifier, + editingWalletsSize: Int, + onClearSelectedClick: () -> Unit, + onEditSelectedWalletClick: () -> Unit, + onDeleteSelectedWalletsClick: () -> Unit, +) { + val showEditAction = remember(editingWalletsSize) { editingWalletsSize in 1 until 2 } + Row( + modifier = modifier + .padding(vertical = TangemTheme.dimens.spacing8) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton( + modifier = Modifier.size(TangemTheme.dimens.size32), + onClick = onClearSelectedClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_close), + tint = TangemTheme.colors.icon.primary1, + contentDescription = "Unselect wallets", + ) + } + SpacerW16() + Text( + text = stringResource(id = R.string.user_wallet_list_editing_count, editingWalletsSize), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + SpacerWMax() + if (showEditAction) { + IconButton( + modifier = Modifier.size(TangemTheme.dimens.size32), + onClick = onEditSelectedWalletClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_pencil_outline_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = "Edit wallet name", + ) + } + SpacerW8() + } + IconButton( + modifier = Modifier.size(TangemTheme.dimens.size32), + onClick = onDeleteSelectedWalletsClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_trash), + tint = TangemTheme.colors.icon.warning, + contentDescription = "Delete selected wallets", + ) + } + } +} + +// region Preview +@Composable +private fun WalletSelectorScreenContentSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(color = TangemTheme.colors.background.primary), + ) { + WalletSelectorScreenContent( + modifier = Modifier + .background( + color = TangemTheme.colors.background.plain, + shape = TangemTheme.shapes.bottomSheet, + ), + state = MockData.state.copy(isLocked = true), + onWalletClick = { /* no-op */ }, + onWalletLongClick = { /* no-op */ }, + onUnlockClick = { /* no-op */ }, + onAddCardClick = { /* no-op */ }, + onEditSelectedWalletClick = { /* no-op */ }, + onClearSelectedClick = { /* no-op */ }, + onDeleteSelectedWalletsClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WalletSelectorScreenContentPreview_Light() { + TangemTheme { + WalletSelectorScreenContentSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WalletSelectorScreenContentPreview_Dark() { + TangemTheme(isDark = true) { + WalletSelectorScreenContentSample() + } +} + +@Composable +private fun WalletSelectorScreenContent_EditWallets_Sample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + WalletSelectorScreenContent( + modifier = Modifier + .background( + color = TangemTheme.colors.background.plain, + shape = TangemTheme.shapes.bottomSheet, + ), + state = MockData.state + .copy(editingWalletsIds = listOf(MockData.userWalletList[2].id)), + onWalletClick = { /* no-op */ }, + onWalletLongClick = { /* no-op */ }, + onUnlockClick = { /* no-op */ }, + onAddCardClick = { /* no-op */ }, + onEditSelectedWalletClick = { /* no-op */ }, + onClearSelectedClick = { /* no-op */ }, + onDeleteSelectedWalletsClick = { /* no-op */ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WalletSelectorScreenContent_EditWallets_Preview_Light() { + TangemTheme { + WalletSelectorScreenContent_EditWallets_Sample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun WalletSelectorScreenContent_EditWallets_Preview_Dark() { + TangemTheme(isDark = true) { + WalletSelectorScreenContent_EditWallets_Sample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt new file mode 100644 index 0000000000..6837d47a7f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -0,0 +1,204 @@ +@file:OptIn(ExperimentalComposeUiApi::class) + +package com.tangem.tap.features.walletSelector.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.SpacerH2 +import com.tangem.core.ui.components.SpacerW6 +import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.common.compose.TangemTypography +import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem +import com.tangem.wallet.R + +@Composable +internal fun WalletItem( + modifier: Modifier = Modifier, + wallet: UserWalletItem, + isSelected: Boolean, + isChecked: Boolean, + isLocked: Boolean, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .width(TangemTheme.dimens.size62) + .height(TangemTheme.dimens.size42), + ) { + val cardImageModifier = Modifier + .align(Alignment.Center) + .width(TangemTheme.dimens.size56) + .height(TangemTheme.dimens.size32) + .clip(TangemTheme.shapes.roundedCornersSmall) + SubcomposeAsyncImage( + modifier = cardImageModifier, + model = ImageRequest.Builder(LocalContext.current) + .data(wallet.imageUrl) + .crossfade(true) + .build(), + loading = { CardImagePlaceholder(modifier = cardImageModifier) }, + error = { CardImagePlaceholder(modifier = cardImageModifier) }, + contentDescription = null, + ) + when { + isChecked -> { + Box( + modifier = cardImageModifier + .background( + color = TangemTheme.colors.icon.accent.copy(alpha = .5f), + ), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + tint = TangemTheme.colors.icon.primary2, + contentDescription = null, + ) + } + } + isSelected -> { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .size(TangemTheme.dimens.size18) + .background( + color = Color.White, + shape = CircleShape, + ), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier + .padding(all = (0.5).dp), + painter = painterResource(id = R.drawable.ic_check_circle_18), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + } + } + else -> Unit + } + } + SpacerW8() + Column( + modifier = Modifier.weight(weight = .6f), + verticalArrangement = Arrangement.SpaceAround, + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = wallet.name, + color = if (isSelected) TangemTheme.colors.text.accent else TangemTheme.colors.text.primary1, + style = TangemTypography.subtitle1, + ) + SpacerH2() + Text( + modifier = Modifier.fillMaxWidth(), + text = when (val type = wallet.type) { + is UserWalletItem.Type.MultiCurrency -> pluralStringResource( + id = R.plurals.card_label_card_count, + count = type.cardsInWallet, + type.cardsInWallet, + ) + + is UserWalletItem.Type.SingleCurrency -> type.tokenName + }, + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.tertiary, + ) + } + SpacerW6() + if (isLocked) { + Box( + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing2) + .background( + color = TangemTheme.colors.button.secondary, + shape = TangemTheme.shapes.roundedCornersMedium, + ), + ) { + Icon( + modifier = Modifier + .padding( + vertical = TangemTheme.dimens.spacing8, + horizontal = TangemTheme.dimens.spacing12, + ) + .size(TangemTheme.dimens.size20), + painter = painterResource(id = R.drawable.ic_locked_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + } + } else { + Column( + modifier = Modifier.weight(weight = .4f), + verticalArrangement = Arrangement.SpaceAround, + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = wallet.balance.amount, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.End, + ) + when (val type = wallet.type) { + is UserWalletItem.Type.MultiCurrency -> { + SpacerH2() + Text( + modifier = Modifier.fillMaxWidth(), + text = pluralStringResource( + id = R.plurals.tokens_count, + count = type.tokensCount, + type.tokensCount, + ), + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.End, + ) + } + + is UserWalletItem.Type.SingleCurrency -> Unit + } + } + } + } +} + +@Composable +private fun CardImagePlaceholder( + modifier: Modifier = Modifier, +) { + Image( + modifier = modifier, + painter = painterResource(id = R.drawable.card_placeholder_black), + contentDescription = null, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletList.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletList.kt new file mode 100644 index 0000000000..ab1f4f9c40 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletList.kt @@ -0,0 +1,66 @@ +@file:OptIn(ExperimentalFoundationApi::class) + +package com.tangem.tap.features.walletSelector.ui.components + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.details.ui.cardsettings.resolveReference +import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem + +@Composable +internal fun WalletList( + modifier: Modifier = Modifier, + wallets: List, + selectedWalletId: String?, + checkedWalletIds: List, + isLocked: Boolean, + onWalletClick: (walletId: String) -> Unit, + onWalletLongClick: (walletId: String) -> Unit, +) { + val multiCurrencyWallets = remember(wallets) { + wallets.filter { it.type is UserWalletItem.Type.MultiCurrency } + } + val singleCurrencyWallets = remember(wallets) { + wallets.filter { it.type is UserWalletItem.Type.SingleCurrency } + } + LazyColumn(modifier = modifier) { + val walletSection: (List) -> Unit = { wallets -> + itemsIndexed( + items = wallets, + key = { _, item -> item.id }, + ) { index, wallet -> + if (index == 0) { + Text( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), + text = wallet.type.headerText.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + } + WalletItem( + modifier = Modifier + .combinedClickable( + onClick = { onWalletClick(wallet.id) }, + onLongClick = { onWalletLongClick(wallet.id) }, + ) + .padding(all = TangemTheme.dimens.spacing16), + wallet = wallet, + isSelected = wallet.id == selectedWalletId, + isChecked = wallet.id in checkedWalletIds, + isLocked = isLocked && wallet.id != selectedWalletId, + ) + } + } + + walletSection(multiCurrencyWallets) + walletSection(singleCurrencyWallets) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/RenameWalletDialog.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/RenameWalletDialog.kt new file mode 100644 index 0000000000..e3969634ce --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/RenameWalletDialog.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.features.walletSelector.ui.model + +import androidx.compose.runtime.Immutable + +@Immutable +internal data class RenameWalletDialog( + val currentName: String, + val onApply: (newName: String) -> Unit, + val onCancel: () -> Unit, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt new file mode 100644 index 0000000000..41f0ace5cd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt @@ -0,0 +1,46 @@ +package com.tangem.tap.features.walletSelector.ui.model + +import androidx.compose.runtime.Immutable +import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.wallet.R + +@Immutable +internal data class UserWalletItem( + val id: String, + val name: String, + val imageUrl: String, + val balance: Balance, + val type: Type, +) { + @Immutable + data class Balance( + val amount: String, + val isLoading: Boolean, + ) { + companion object { + val Loading = Balance( + amount = "0.00", + isLoading = true, + ) + } + } + + sealed interface Type { + val headerText: TextReference + + @Immutable + class MultiCurrency( + val tokensCount: Int, + val cardsInWallet: Int, + ) : Type { + override val headerText: TextReference + get() = TextReference.Res(R.string.user_wallet_list_multi_header) + } + + @Immutable + class SingleCurrency(val tokenName: String) : Type { + override val headerText: TextReference + get() = TextReference.Res(R.string.user_wallet_list_single_header) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_check_24.xml b/app/src/main/res/drawable/ic_check_24.xml new file mode 100644 index 0000000000..4291149aff --- /dev/null +++ b/app/src/main/res/drawable/ic_check_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_circle_18.xml b/app/src/main/res/drawable/ic_check_circle_18.xml new file mode 100644 index 0000000000..2c3e494f73 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_circle_18.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_locked_24.xml b/app/src/main/res/drawable/ic_locked_24.xml new file mode 100644 index 0000000000..2afb4d8a14 --- /dev/null +++ b/app/src/main/res/drawable/ic_locked_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pencil_outline_24.xml b/app/src/main/res/drawable/ic_pencil_outline_24.xml new file mode 100644 index 0000000000..039a5d5463 --- /dev/null +++ b/app/src/main/res/drawable/ic_pencil_outline_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt index a6425652bb..7b99b3f27e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -4,6 +4,7 @@ package com.tangem.core.ui.components import androidx.annotation.DimenRes import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size @@ -30,6 +31,11 @@ fun SpacerH(height: Dp, modifier: Modifier = Modifier) { Spacer(modifier = modifier.height(height)) } +@Composable +fun SpacerH2(modifier: Modifier = Modifier) { + SpacerH(2.dp, modifier) +} + @Composable fun SpacerH4(modifier: Modifier = Modifier) { SpacerH(4.dp, modifier) @@ -78,6 +84,11 @@ fun SpacerW4(modifier: Modifier = Modifier) { SpacerW(4.dp, modifier) } +@Composable +fun SpacerW6(modifier: Modifier = Modifier) { + SpacerW(6.dp, modifier) +} + @Composable fun SpacerW8(modifier: Modifier = Modifier) { SpacerW(8.dp, modifier) @@ -97,6 +108,12 @@ fun SpacerW24(modifier: Modifier = Modifier) { fun SpacerW32(modifier: Modifier = Modifier) { SpacerW(32.dp, modifier) } + +// TODO: Refactor with context receivers +@Composable +fun RowScope.SpacerWMax(modifier: Modifier = Modifier) { + Spacer(modifier = modifier.weight(1f)) +} // endregion Vertical // region Size diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index a05eb630a4..731400086c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -33,13 +33,16 @@ data class TangemDimens internal constructor( val size8: Dp = 8.dp, val size11: Dp = 11.dp, val size16: Dp = 16.dp, + val size18: Dp = 18.dp, val size20: Dp = 20.dp, val size24: Dp = 24.dp, val size28: Dp = 28.dp, val size32: Dp = 32.dp, val size40: Dp = 40.dp, + val size42: Dp = 42.dp, val size48: Dp = 48.dp, val size56: Dp = 56.dp, + val size62: Dp = 62.dp, val size84: Dp = 84.dp, val size86: Dp = 86.dp, val size96: Dp = 96.dp, diff --git a/core/ui/src/main/res/values-de/strings_plurals_final.xml b/core/ui/src/main/res/values-de/strings_plurals_final.xml index fa02d656a3..02204c79ec 100644 --- a/core/ui/src/main/res/values-de/strings_plurals_final.xml +++ b/core/ui/src/main/res/values-de/strings_plurals_final.xml @@ -3,4 +3,9 @@ %d card %d cards + + + %d token + %d tokens + diff --git a/core/ui/src/main/res/values-fr/strings_plurals_final.xml b/core/ui/src/main/res/values-fr/strings_plurals_final.xml index f7ce3f895b..e79bd3f0cd 100644 --- a/core/ui/src/main/res/values-fr/strings_plurals_final.xml +++ b/core/ui/src/main/res/values-fr/strings_plurals_final.xml @@ -4,4 +4,10 @@ %d cards %d cards + + + %d token + %d tokens + %d tokens + diff --git a/core/ui/src/main/res/values-it/strings_plurals_final.xml b/core/ui/src/main/res/values-it/strings_plurals_final.xml index fa02d656a3..02204c79ec 100644 --- a/core/ui/src/main/res/values-it/strings_plurals_final.xml +++ b/core/ui/src/main/res/values-it/strings_plurals_final.xml @@ -3,4 +3,9 @@ %d card %d cards + + + %d token + %d tokens + diff --git a/core/ui/src/main/res/values-ru/strings_final.xml b/core/ui/src/main/res/values-ru/strings_final.xml index 20b896eb21..c8db954763 100644 --- a/core/ui/src/main/res/values-ru/strings_final.xml +++ b/core/ui/src/main/res/values-ru/strings_final.xml @@ -152,4 +152,12 @@ Похоже, что у вас отключена биометрическая авторизация, она необходима для сохранения кошельков Включить Отмена + Мультивалютные + Одновалютные + Переименование кошелька + Имя кошелька + Разблокировать все с биометрией + Добавить новый кошелек + %d выбрано + Мои кошельки diff --git a/core/ui/src/main/res/values-ru/strings_plurals_final.xml b/core/ui/src/main/res/values-ru/strings_plurals_final.xml index 53720a4bb6..35486659e4 100644 --- a/core/ui/src/main/res/values-ru/strings_plurals_final.xml +++ b/core/ui/src/main/res/values-ru/strings_plurals_final.xml @@ -5,4 +5,11 @@ %d карт %d карт + + + %d токен + %d токена + %d токенов + %d токенов + diff --git a/core/ui/src/main/res/values/strings_final.xml b/core/ui/src/main/res/values/strings_final.xml index a6fd518ac9..6f508e1d28 100644 --- a/core/ui/src/main/res/values/strings_final.xml +++ b/core/ui/src/main/res/values/strings_final.xml @@ -154,4 +154,12 @@ It looks like you have biometric authorization disabled, it is necessary to save wallets Enable Cancel + Multi-currency + Single-currency + Rename Wallet + Wallet name + Unlock all with biometrics + Add new wallet + %d selected + My Wallets diff --git a/core/ui/src/main/res/values/strings_plurals_final.xml b/core/ui/src/main/res/values/strings_plurals_final.xml index 530ada144e..9fa94fe676 100644 --- a/core/ui/src/main/res/values/strings_plurals_final.xml +++ b/core/ui/src/main/res/values/strings_plurals_final.xml @@ -5,4 +5,11 @@ %d cards %d cards + + + %d tokens + %d token + %d tokens + %d tokens + From 4375e7687f292267199558759820d35efdaa9221 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 12 Nov 2022 03:41:55 +0400 Subject: [PATCH 089/176] Updated on 2026-08-14 --- .../java/com/tangem/domain/common/CardDTO.kt | 300 ++++++++++++++++++ .../domain/common/util/CardExtensions.kt | 12 + .../tangem/domain/common/util/UserWalletId.kt | 37 +++ 3 files changed, 349 insertions(+) create mode 100644 domain/src/main/java/com/tangem/domain/common/CardDTO.kt create mode 100644 domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt create mode 100644 domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt diff --git a/domain/src/main/java/com/tangem/domain/common/CardDTO.kt b/domain/src/main/java/com/tangem/domain/common/CardDTO.kt new file mode 100644 index 0000000000..58df56c687 --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/common/CardDTO.kt @@ -0,0 +1,300 @@ +package com.tangem.domain.common + +import com.squareup.moshi.JsonClass +import com.tangem.common.card.Card +import com.tangem.common.card.CardWallet +import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.EncryptionMode +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.hdWallet.ExtendedPublicKey +import com.tangem.operations.attestation.Attestation +import java.util.* +import com.tangem.common.card.FirmwareVersion as SdkFirmwareVersion + +/** + * [Card] copy + * */ +@JsonClass(generateAdapter = true) +data class CardDTO( + val cardId: String, + val batchId: String, + val cardPublicKey: ByteArray, + val firmwareVersion: FirmwareVersion, + val manufacturer: Manufacturer, + val issuer: Issuer, + val settings: Settings, + val linkedTerminalStatus: LinkedTerminalStatus, + val isAccessCodeSet: Boolean, + val isPasscodeSet: Boolean?, + val supportedCurves: List, + val wallets: List, + val attestation: Attestation, + val backupStatus: BackupStatus?, +) { + constructor(card: Card) : this( + cardId = card.cardId, + batchId = card.batchId, + cardPublicKey = card.cardPublicKey, + firmwareVersion = FirmwareVersion(card.firmwareVersion), + manufacturer = Manufacturer(card.manufacturer), + issuer = Issuer(card.issuer), + settings = Settings(card.settings), + linkedTerminalStatus = LinkedTerminalStatus.fromSdkStatus(card.linkedTerminalStatus), + isAccessCodeSet = card.isAccessCodeSet, + isPasscodeSet = card.isPasscodeSet, + supportedCurves = card.supportedCurves, + wallets = card.wallets.map { Wallet(it) }, + attestation = card.attestation, + backupStatus = BackupStatus.fromSdkStatus(card.backupStatus), + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is CardDTO) return false + + if (cardId != other.cardId) return false + if (batchId != other.batchId) return false + if (!cardPublicKey.contentEquals(other.cardPublicKey)) return false + if (firmwareVersion != other.firmwareVersion) return false + if (manufacturer != other.manufacturer) return false + if (issuer != other.issuer) return false + if (settings != other.settings) return false + if (linkedTerminalStatus != other.linkedTerminalStatus) return false + if (isAccessCodeSet != other.isAccessCodeSet) return false + if (isPasscodeSet != other.isPasscodeSet) return false + if (supportedCurves != other.supportedCurves) return false + if (wallets != other.wallets) return false + if (attestation != other.attestation) return false + + return true + } + + override fun hashCode(): Int { + var result = cardId.hashCode() + result = 31 * result + batchId.hashCode() + result = 31 * result + cardPublicKey.contentHashCode() + result = 31 * result + firmwareVersion.hashCode() + result = 31 * result + manufacturer.hashCode() + result = 31 * result + issuer.hashCode() + result = 31 * result + settings.hashCode() + result = 31 * result + linkedTerminalStatus.hashCode() + result = 31 * result + isAccessCodeSet.hashCode() + result = 31 * result + (isPasscodeSet?.hashCode() ?: 0) + result = 31 * result + supportedCurves.hashCode() + result = 31 * result + wallets.hashCode() + result = 31 * result + attestation.hashCode() + return result + } + + @JsonClass(generateAdapter = true) + data class Settings( + val securityDelay: Int, + val maxWalletsCount: Int, + val isSettingAccessCodeAllowed: Boolean, + val isSettingPasscodeAllowed: Boolean, + val isResettingUserCodesAllowed: Boolean, + val isLinkedTerminalEnabled: Boolean, + val isBackupAllowed: Boolean, + val supportedEncryptionModes: List, + val isFilesAllowed: Boolean, + val isHDWalletAllowed: Boolean, + ) { + constructor(settings: Card.Settings) : this( + securityDelay = settings.securityDelay, + maxWalletsCount = settings.maxWalletsCount, + isSettingAccessCodeAllowed = settings.isSettingAccessCodeAllowed, + isSettingPasscodeAllowed = settings.isSettingPasscodeAllowed, + isResettingUserCodesAllowed = settings.isResettingUserCodesAllowed, + isLinkedTerminalEnabled = settings.isLinkedTerminalEnabled, + isBackupAllowed = settings.isBackupAllowed, + supportedEncryptionModes = settings.supportedEncryptionModes, + isFilesAllowed = settings.isFilesAllowed, + isHDWalletAllowed = settings.isHDWalletAllowed, + ) + } + + @JsonClass(generateAdapter = true) + data class FirmwareVersion( + val major: Int, + val minor: Int, + val patch: Int, + val type: SdkFirmwareVersion.FirmwareType, + ) : Comparable { + constructor(firmwareVersion: SdkFirmwareVersion) : this( + major = firmwareVersion.major, + minor = firmwareVersion.minor, + patch = firmwareVersion.patch, + type = firmwareVersion.type, + ) + + val stringValue: String + get() = StringBuilder() + .append("$major.$minor") + .append(if (patch != 0) ".$patch" else "") + .append(type.rawValue ?: "") + .toString() + + override fun compareTo(other: SdkFirmwareVersion): Int = when { + major != other.major -> major.compareTo(other.major) + minor != other.minor -> minor.compareTo(other.minor) + else -> patch.compareTo(other.patch) + } + } + + @JsonClass(generateAdapter = true) + data class Manufacturer( + val name: String, + val manufactureDate: Date, + val signature: ByteArray?, + ) { + constructor(manufacturer: Card.Manufacturer) : this( + name = manufacturer.name, + manufactureDate = manufacturer.manufactureDate, + signature = manufacturer.signature, + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Manufacturer) return false + + if (name != other.name) return false + if (manufactureDate != other.manufactureDate) return false + if (signature != null) { + if (other.signature == null) return false + if (!signature.contentEquals(other.signature)) return false + } else if (other.signature != null) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + manufactureDate.hashCode() + result = 31 * result + (signature?.contentHashCode() ?: 0) + return result + } + } + + @JsonClass(generateAdapter = true) + data class Issuer( + val name: String, + val publicKey: ByteArray, + ) { + constructor(issuer: Card.Issuer) : this( + name = issuer.name, + publicKey = issuer.publicKey, + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Issuer) return false + + if (name != other.name) return false + if (!publicKey.contentEquals(other.publicKey)) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + publicKey.contentHashCode() + return result + } + } + + @JsonClass(generateAdapter = true) + data class Wallet( + val publicKey: ByteArray, + val chainCode: ByteArray?, + val curve: EllipticCurve, + val settings: CardWallet.Settings, + val totalSignedHashes: Int?, + val remainingSignatures: Int?, + val index: Int, + val hasBackup: Boolean, + val derivedKeys: Map, + val extendedPublicKey: ExtendedPublicKey?, + ) { + constructor(wallet: CardWallet) : this( + publicKey = wallet.publicKey, + chainCode = wallet.chainCode, + curve = wallet.curve, + settings = wallet.settings, + totalSignedHashes = wallet.totalSignedHashes, + remainingSignatures = wallet.remainingSignatures, + index = wallet.index, + hasBackup = wallet.hasBackup, + derivedKeys = wallet.derivedKeys, + extendedPublicKey = wallet.extendedPublicKey, + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Wallet) return false + + if (!publicKey.contentEquals(other.publicKey)) return false + if (chainCode != null) { + if (other.chainCode == null) return false + if (!chainCode.contentEquals(other.chainCode)) return false + } else if (other.chainCode != null) return false + if (curve != other.curve) return false + if (settings != other.settings) return false + if (totalSignedHashes != other.totalSignedHashes) return false + if (remainingSignatures != other.remainingSignatures) return false + if (index != other.index) return false + if (hasBackup != other.hasBackup) return false + + return true + } + + override fun hashCode(): Int { + var result = publicKey.contentHashCode() + result = 31 * result + (chainCode?.contentHashCode() ?: 0) + result = 31 * result + curve.hashCode() + result = 31 * result + settings.hashCode() + result = 31 * result + (totalSignedHashes ?: 0) + result = 31 * result + (remainingSignatures ?: 0) + result = 31 * result + index + result = 31 * result + hasBackup.hashCode() + return result + } + } + + enum class LinkedTerminalStatus { + Current, + Other, + None; + + companion object { + internal fun fromSdkStatus(sdkStatus: Card.LinkedTerminalStatus): LinkedTerminalStatus { + return when (sdkStatus) { + Card.LinkedTerminalStatus.Current -> Current + Card.LinkedTerminalStatus.Other -> Other + Card.LinkedTerminalStatus.None -> None + } + } + } + } + + sealed class BackupStatus { + data class CardLinked(val cardCount: Int) : BackupStatus() + + data class Active(val cardCount: Int) : BackupStatus() + + object NoBackup : BackupStatus() + + val isActive: Boolean + get() = this is Active || this is CardLinked + + companion object { + internal fun fromSdkStatus(sdkStatus: Card.BackupStatus?): BackupStatus? { + return when (sdkStatus) { + is Card.BackupStatus.NoBackup -> NoBackup + is Card.BackupStatus.CardLinked -> CardLinked(sdkStatus.cardCount) + is Card.BackupStatus.Active -> Active(sdkStatus.cardCount) + null -> null + } + } + } + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt b/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt new file mode 100644 index 0000000000..0e516d7b8e --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.common.util + +import com.tangem.domain.common.CardDTO + +val CardDTO.userWalletId: UserWalletId + get() = UserWalletId(findWalletPublicKey(wallets)) + +private fun findWalletPublicKey(wallets: List): ByteArray { + return wallets.firstOrNull() + ?.publicKey + ?: error("Wallet not found") +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt new file mode 100644 index 0000000000..4c8bedcd22 --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt @@ -0,0 +1,37 @@ +package com.tangem.domain.common.util + +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toHexString +import com.tangem.domain.common.extensions.calculateHmacSha256 + +class UserWalletId( + val stringValue: String, +) { + val value = stringValue.hexToBytes() + + constructor(walletPublicKey: ByteArray) : this( + stringValue = calculateUserWalletId(walletPublicKey).toHexString(), + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is UserWalletId) return false + + if (stringValue != other.stringValue) return false + + return true + } + + override fun hashCode(): Int { + return stringValue.hashCode() + } +} + +private fun calculateUserWalletId(publicKey: ByteArray): ByteArray { + val message = MESSAGE_FOR_WALLET_ID.toByteArray() + val keyHash = publicKey.calculateSha256() + return message.calculateHmacSha256(keyHash) +} + +private const val MESSAGE_FOR_WALLET_ID = "UserWalletID" \ No newline at end of file From 0d993b5141832225c2ae0bc4537193f5dd4612af Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 12 Nov 2022 03:46:02 +0400 Subject: [PATCH 090/176] Updated on 2026-08-14 --- .../tap/common/analytics/TangemSdkError.kt | 4 +- .../converters/BasicEventConverter.kt | 6 +- .../common/feedback/AdditionalFeedbackInfo.kt | 4 +- .../tap/common/redux/global/GlobalReducer.kt | 20 ++-- .../com/tangem/tap/domain/TangemSdkManager.kt | 26 +++-- .../com/tangem/tap/domain/TangemSigner.kt | 4 +- .../java/com/tangem/tap/domain/TapErrors.kt | 5 +- .../com/tangem/tap/domain/TapWalletManager.kt | 4 +- .../com/tangem/tap/domain/extensions/Card.kt | 65 ++++-------- .../domain/extensions/WalletManagerFactory.kt | 7 +- .../tasks/product/CreateProductWalletTask.kt | 100 +++++++++++++----- .../tasks/product/ProductCommandProcessor.kt | 4 +- .../domain/tasks/product/ScanProductTask.kt | 83 ++++++++++----- .../tangem/tap/domain/termsOfUse/CardTou.kt | 6 +- .../tap/domain/tokens/CurrenciesRepository.kt | 7 +- .../tap/domain/tokens/UserTokensRepository.kt | 33 +++--- .../domain/tokens/UserTokensStorageService.kt | 4 +- .../domain/tokens/models/BlockchainNetwork.kt | 12 ++- .../domain/twins/CreateFirstTwinWalletTask.kt | 1 - .../twins/CreateSecondTwinWalletTask.kt | 4 +- .../tap/domain/twins/TwinCardsManager.kt | 5 +- .../twins/WriteProtectedIssuerDataTask.kt | 3 +- .../tangem/tap/features/demo/Extentions.kt | 4 +- .../features/details/redux/DetailsAction.kt | 4 +- .../details/redux/DetailsMiddleware.kt | 34 +++--- .../features/details/redux/DetailsReducer.kt | 23 ++-- .../features/details/redux/DetailsState.kt | 10 +- .../walletconnect/WalletConnectMiddleware.kt | 4 +- .../disclaimer/redux/DisclaimerState.kt | 4 +- .../tap/features/home/redux/HomeAction.kt | 2 +- .../tap/features/home/redux/HomeMiddleware.kt | 12 +-- .../features/onboarding/OnboardingHelper.kt | 3 +- .../note/redux/OnboardingNoteMiddleware.kt | 3 +- .../redux/OnboardingOtherCardsMiddleware.kt | 35 ++++-- .../twins/redux/TwinCardsMiddleware.kt | 9 +- .../redux/OnboardingWalletMiddleware.kt | 28 +++-- .../saltPay/SaltPayActivationManager.kt | 6 +- .../send/redux/middlewares/SendMiddleware.kt | 4 +- .../tap/features/wallet/models/Currency.kt | 20 ++-- .../tap/features/wallet/redux/WalletAction.kt | 15 +-- .../redux/middlewares/WarningsMiddleware.kt | 10 +- .../wallet/ui/wallet/MultiWalletView.kt | 6 +- .../exchangeServices/CardExchangeRules.kt | 4 +- .../CurrencyExchangeManager.kt | 5 +- .../com/tangem/tap/proxy/AppStateHolder.kt | 4 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 8 +- buildSrc/src/main/java/Versions.kt | 9 +- .../com/tangem/domain/common/ScanResponse.kt | 18 +--- .../tangem/domain/common/TapWorkarounds.kt | 61 ++++++----- .../com/tangem/domain/common/TwinsHelper.kt | 7 +- .../domain/common/extensions/CardSdk.kt | 11 +- .../tangem/domain/common/util/UserWalletId.kt | 4 +- .../redux/AddCustomTokenState.kt | 12 +-- 53 files changed, 430 insertions(+), 356 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt index 17e31d88c4..491ef8f683 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt @@ -74,7 +74,7 @@ object TangemSdk { is TangemSdkError.UnsupportedWalletConfig -> TangemSdkError.UnsupportedWalletConfig() is TangemSdkError.CryptoUtilsError -> TangemSdkError.CryptoUtilsError(error.customMessage) is TangemSdkError.NetworkError -> TangemSdkError.NetworkError(error.customMessage) - is TangemSdkError.ExceptionError -> TangemSdkError.ExceptionError(error.throwable) + is TangemSdkError.ExceptionError -> TangemSdkError.ExceptionError(error.cause) is TangemSdkError.TooMuchBackupCards -> TangemSdkError.TooMuchBackupCards() is TangemSdkError.BackupCardRequired -> TangemSdkError.BackupCardRequired() is TangemSdkError.CertificateSignatureRequired -> TangemSdkError.CertificateSignatureRequired() @@ -107,6 +107,8 @@ object TangemSdk { is TangemSdkError.BackupFailedFirmware -> TangemSdkError.BackupFailedFirmware() is TangemSdkError.UserForgotTheCode -> TangemSdkError.UserForgotTheCode() is TangemSdkError.BackupFailedIncompatibleBatch -> TangemSdkError.BackupFailedIncompatibleBatch() + is TangemSdkError.BiometricsUnavailable -> error + is TangemSdkError.BiometricsAuthenticationFailed -> error } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 3950f458bc..301ae0cb07 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -3,10 +3,10 @@ package com.tangem.tap.common.analytics.converters import com.tangem.common.Converter import com.tangem.common.extensions.isZero import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.filters.BasicTopUpFilter -import com.tangem.tap.domain.extensions.getUserWalletId import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletData @@ -29,7 +29,7 @@ class BasicSignInEventConverter( currency = cardCurrency, batch = scanResponse.card.batchId, ).apply { - filterData = scanResponse.card.getUserWalletId() + filterData = scanResponse.card.userWalletId.stringValue } } } @@ -43,7 +43,7 @@ class BasicTopUpEventConverter( val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null val data = BasicTopUpFilter.Data( - walletId = scanResponse.card.getUserWalletId(), + walletId = scanResponse.card.userWalletId.stringValue, cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsData), ) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 632f9f965b..48527059ad 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -8,7 +8,7 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.Address -import com.tangem.common.card.CardWallet +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.domain.extensions.getUserWalletId @@ -92,7 +92,7 @@ class AdditionalFeedbackInfo { ) } - private fun formatSignedHashes(wallets: List): String { + private fun formatSignedHashes(wallets: List): String { return wallets.joinToString("\n") { "Signed hashes: ${it.curve.curve} - ${it.totalSignedHashes}" } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index f8563c233a..f4aa80ad03 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.redux.global import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.global.DomainGlobalAction +import com.tangem.tap.common.extensions.replaceBy import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.preferencesStorage @@ -49,12 +50,19 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager) is GlobalAction.UpdateWalletSignedHashes -> { val card = globalState.scanResponse?.card ?: return globalState - val wallet = card.wallet(action.walletPublicKey) ?: return globalState - val newCardInstance = card.updateWallet( - wallet.copy( - totalSignedHashes = action.walletSignedHashes, - remainingSignatures = action.remainingSignatures, - ), + val wallet = card.wallets + .firstOrNull { it.publicKey.contentEquals(action.walletPublicKey) } + ?: return globalState + + val newCardInstance = card.copy( + wallets = card.wallets.toMutableList().also { walletsMutable -> + walletsMutable.replaceBy( + item = wallet.copy( + totalSignedHashes = action.walletSignedHashes, + remainingSignatures = action.remainingSignatures, + ), + ) { it.index == wallet.index } + }, ) globalState.copy(scanResponse = globalState.scanResponse.copy(card = newCardInstance)) } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 4fe7252ac7..66b16d1cad 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -1,7 +1,5 @@ package com.tangem.tap.domain -import CreateProductWalletTask -import CreateProductWalletTaskResponse import android.content.Context import androidx.annotation.StringRes import com.tangem.Message @@ -10,7 +8,6 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.CardFilter import com.tangem.common.CompletionResult import com.tangem.common.SuccessResponse -import com.tangem.common.card.Card import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.CardIdDisplayFormat import com.tangem.common.core.CardSessionRunnable @@ -18,6 +15,8 @@ import com.tangem.common.core.Config import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.map +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.operations.CommandResponse import com.tangem.operations.ScanTask @@ -29,6 +28,8 @@ import com.tangem.operations.pins.SetUserCodeCommand import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask +import com.tangem.tap.domain.tasks.product.CreateProductWalletTask +import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tokens.UserTokensRepository @@ -73,12 +74,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } - suspend fun createWallet(cardId: String?): CompletionResult { + suspend fun createWallet(cardId: String?): CompletionResult { return runTaskAsyncReturnOnMain( CreateWalletAndRescanTask(), cardId, initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)), ) + .map { CardDTO(it) } } suspend fun derivePublicKeys( @@ -88,12 +90,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId) } - suspend fun resetToFactorySettings(card: Card): CompletionResult { + suspend fun resetToFactorySettings(cardId: String): CompletionResult { return runTaskAsyncReturnOnMain( - ResetToFactorySettingsTask(), - card.cardId, + runnable = ResetToFactorySettingsTask(), + cardId = cardId, initialMessage = Message(context.getString(R.string.card_settings_reset_card_to_factory)), ) + .map { CardDTO(it) } } suspend fun setPasscode(cardId: String?): CompletionResult { @@ -128,11 +131,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co ) } - suspend fun scanCard(): CompletionResult { + suspend fun scanCard(): CompletionResult { return runTaskAsyncReturnOnMain( - ScanTask(), + runnable = ScanTask(), initialMessage = Message(context.getString(R.string.initial_message_tap_header)), ) + .map { CardDTO(it) } } suspend fun runTaskAsync( @@ -150,7 +154,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } private suspend fun runTaskAsyncReturnOnMain( - runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, + runnable: CardSessionRunnable, + cardId: String? = null, + initialMessage: Message? = null, ): CompletionResult { val result = runTaskAsync(runnable, cardId, initialMessage) return withContext(Dispatchers.Main) { result } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt index af5cdb6a5c..1979b1df8a 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt @@ -5,13 +5,13 @@ import com.tangem.TangemSdk import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.common.Wallet import com.tangem.common.CompletionResult -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.tap.domain.tasks.SignHashesTask import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class TangemSigner( - private val card: Card, + private val card: CardDTO, private val tangemSdk: TangemSdk, private val initialMessage: Message, private val accessCode: String? = null, diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 5a0cf85668..0bc9a2c283 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -64,8 +64,7 @@ sealed class TapError( ) : TapError(-1), MultiMessageError } -sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemError { - final override val code: Int = 50100 +sealed class TapSdkError(override val messageResId: Int?) : TangemError(code = 50100) { override var customMessage: String = code.toString() object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card) @@ -88,4 +87,4 @@ fun TangemTechError.toTapError(): TapError { } } -class NoDataError(message: String) : TapError.CustomError(customMessage = message) +class NoDataError(message: String) : TapError.CustomError(customMessage = message) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index b15f2a7431..bb07af060e 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -6,8 +6,8 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.common.card.Card import com.tangem.common.services.Result +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTestCard @@ -96,7 +96,7 @@ class TapWalletManager { store.dispatch( WalletAction.MultiWallet.ShowWalletBackupWarning( show = data.card.settings.isBackupAllowed - && data.card.backupStatus == Card.BackupStatus.NoBackup, + && data.card.backupStatus == CardDTO.BackupStatus.NoBackup, ), ) loadData(data) diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index ab2ba5610b..fd46ad2fde 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -1,58 +1,45 @@ package com.tangem.tap.domain.extensions -import com.tangem.common.card.Card -import com.tangem.common.card.CardWallet import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion -import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TwinCardNumber -import com.tangem.domain.common.extensions.calculateHmacSha256 import com.tangem.domain.common.getTwinCardNumber import com.tangem.domain.common.isTangemTwin import com.tangem.operations.attestation.CardVerifyAndGetInfo import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.features.wallet.redux.Artwork -val Card.remainingSignatures: Int? - get() = this.getSingleWallet()?.remainingSignatures +val CardDTO.remainingSignatures: Int? + get() = this.wallets.firstOrNull()?.remainingSignatures -val Card.isWalletDataSupported: Boolean +val CardDTO.isWalletDataSupported: Boolean get() = this.firmwareVersion.major >= 4 -val Card.isMultiwalletAllowed: Boolean +val CardDTO.isMultiwalletAllowed: Boolean get() { - return !isTangemTwin() && !isStart2Coin && !isTangemNote && !isSaltPay - && (firmwareVersion >= FirmwareVersion.MultiWalletAvailable || - getSingleWallet()?.curve == EllipticCurve.Secp256k1) + return !isTangemTwin() && !isStart2Coin && !isTangemNote && !isSaltPay && + (firmwareVersion >= FirmwareVersion.MultiWalletAvailable || + wallets.firstOrNull()?.curve == EllipticCurve.Secp256k1) } -val Card.isHdWalletAllowedByApp: Boolean +val CardDTO.isHdWalletAllowedByApp: Boolean get() = settings.isHDWalletAllowed && !isSaltPay -fun Card.getSingleWallet(): CardWallet? { - return wallets.firstOrNull() +fun CardDTO.hasSignedHashes(): Boolean { + return wallets.any { (it.totalSignedHashes ?: 0) > 0 } } -fun Card.hasWallets(): Boolean = wallets.isNotEmpty() - -fun Card.hasNoWallets(): Boolean = wallets.isEmpty() - -fun Card.hasSingleWallet(): Boolean = wallets.size == 1 - -fun Card.hasSignedHashes(): Boolean { - return wallets.any { it.totalSignedHashes ?: 0 > 0 } +fun CardDTO.signedHashesCount(): Int { + return wallets.sumOf { it.totalSignedHashes ?: 0 } } -fun Card.signedHashesCount(): Int { - return wallets.map { it.totalSignedHashes ?: 0 }.sum() -} - -suspend fun Card.getOrLoadCardArtworkUrl(cardInfo: Result? = null): String { +suspend fun CardDTO.getOrLoadCardArtworkUrl(cardInfo: Result? = null): String { fun ifAnyError(): String { return when { cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL @@ -67,42 +54,28 @@ suspend fun Card.getOrLoadCardArtworkUrl(cardInfo: Result { val artworkId = cardInfoResult.data.artwork?.id - if (artworkId == null || artworkId.isEmpty()) { + if (artworkId.isNullOrEmpty()) { ifAnyError() } else { OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) } } + is Result.Failure -> ifAnyError() } } -fun Card.getArtworkUrl(artworkId: String?): String? { +fun CardDTO.getArtworkUrl(artworkId: String?): String? { return when { artworkId != null -> { OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) } + cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL else -> null } -} - -fun Card.getUserWalletId(): String { - val walletPublicKey = this.wallets.firstOrNull()?.publicKey ?: return "" - return UserWalletId(walletPublicKey).stringValue -} - -class UserWalletId(val walletPublicKey: ByteArray) { - val stringValue: String = calculateUserId(walletPublicKey) - - private fun calculateUserId(walletPublicKey: ByteArray): String { - val message = "UserWalletID".toByteArray() - val keyHash = walletPublicKey.calculateSha256() - return message.calculateHmacSha256(keyHash).toHexString() - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index ec95e13dc6..874e562a2a 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -7,13 +7,12 @@ import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.common.card.Card -import com.tangem.common.card.CardWallet import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.guard import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isTestCard @@ -82,7 +81,7 @@ fun WalletManagerFactory.makeWalletManagerForApp( ) } -private fun getDerivationParams(derivationPath: String?, card: Card): DerivationParams? { +private fun getDerivationParams(derivationPath: String?, card: CardDTO): DerivationParams? { return derivationPath?.let { DerivationParams.Custom( DerivationPath(it), @@ -132,7 +131,7 @@ fun WalletManagerFactory.makePrimaryWalletManager( ) } -private fun selectWallet(wallets: List): CardWallet? { +private fun selectWallet(wallets: List): CardDTO.Wallet? { return when (wallets.size) { 0 -> null 1 -> wallets[0] diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 982ed86857..91006e8dab 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -1,3 +1,5 @@ +package com.tangem.tap.domain.tasks.product + import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.card.Card @@ -9,6 +11,8 @@ import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.guard import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.map +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.KeyWalletPublicKey import com.tangem.domain.common.ProductType import com.tangem.domain.common.TapWorkarounds.derivationStyle @@ -19,19 +23,37 @@ import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.backup.StartPrimaryCardLinkingTask import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.derivation.ExtendedPublicKeysMap -import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask -import com.tangem.tap.domain.tasks.product.CreateWalletsTask -import com.tangem.tap.domain.tasks.product.ProductCommandProcessor -import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets import com.tangem.tap.features.demo.DemoHelper - +import com.tangem.operations.wallet.CreateWalletResponse as SdkCreateWalletResponse data class CreateProductWalletTaskResponse( - val card: Card, + val card: CardDTO, val derivedKeys: Map = mapOf(), - val primaryCard: PrimaryCard? = null -) : CommandResponse + val primaryCard: PrimaryCard? = null, +) : CommandResponse { + constructor( + card: Card, + derivedKeys: Map = mapOf(), + primaryCard: PrimaryCard? = null, + ) : this( + card = CardDTO(card), + derivedKeys = derivedKeys, + primaryCard = primaryCard, + ) +} + +private data class CreateWalletResponse( + val cardId: String, + val wallet: CardDTO.Wallet, +) { + constructor( + sdkResponse: SdkCreateWalletResponse, + ) : this( + cardId = sdkResponse.cardId, + wallet = CardDTO.Wallet(sdkResponse.wallet), + ) +} class CreateProductWalletTask( private val type: ProductType, @@ -39,29 +61,32 @@ class CreateProductWalletTask( override fun run( session: CardSession, - callback: (result: CompletionResult) -> Unit + callback: (result: CompletionResult) -> Unit, ) { val card = session.environment.card.guard { callback(CompletionResult.Failure(TangemSdkError.CardError())) return } + val cardDto = CardDTO(card) val commandProcessor = when (type) { ProductType.Note -> CreateWalletTangemNote() ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet") else -> CreateWalletTangemWallet() } - commandProcessor.proceed(card, session) { + commandProcessor.proceed(cardDto, session) { when (it) { is CompletionResult.Success -> { val result = when (commandProcessor) { is CreateWalletTangemWallet -> { it.data as CreateProductWalletTaskResponse } + else -> CreateProductWalletTaskResponse(card = session.environment.card!!) } callback(CompletionResult.Success(result)) } + is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error)) } } @@ -70,7 +95,7 @@ class CreateProductWalletTask( private class CreateWalletTangemNote : ProductCommandProcessor { override fun proceed( - card: Card, + card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { @@ -94,52 +119,65 @@ private class CreateWalletTangemNote : ProductCommandProcessor + callback(result.map { CreateWalletResponse(it) }) + } } } } private class CreateWalletTangemWallet : ProductCommandProcessor { - private lateinit var card: Card private var primaryCard: PrimaryCard? = null override fun proceed( - card: Card, + card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - this.card = card val curves = card.getCurvesForNonCreatedWallets() if (curves.isEmpty()) { - val createWalletResponses = card.wallets.map { CreateWalletResponse(card.cardId, it) } - proceedWithCreatedWallets(createWalletResponses, session, callback) + val createWalletResponses = card.wallets.map { wallet -> + CreateWalletResponse(card.cardId, wallet) + } + proceedWithCreatedWallets(card, createWalletResponses, session, callback) return } CreateWalletsTask(curves).run(session) { result -> when (result) { is CompletionResult.Success -> { - proceedWithCreatedWallets(result.data.createWalletResponses, session, callback) + proceedWithCreatedWallets( + card = card, + createWalletResponses = result.data.createWalletResponses.map { CreateWalletResponse(it) }, + session = session, + callback = callback, + ) + } + + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } private fun proceedWithCreatedWallets( + card: CardDTO, createWalletResponses: List, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { when { card.settings.isBackupAllowed -> { - linkPrimaryCard(createWalletResponses, session, callback) + linkPrimaryCard(card, createWalletResponses, session, callback) } + card.settings.isHDWalletAllowed -> { - deriveKeys(createWalletResponses, session, callback) + deriveKeys(card, createWalletResponses, session, callback) } + else -> { callback( CompletionResult.Success( @@ -151,6 +189,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor, session: CardSession, callback: (result: CompletionResult) -> Unit, @@ -161,8 +200,9 @@ private class CreateWalletTangemWallet : ProductCommandProcessor { - deriveKeys(createWalletResponse, session, callback) + deriveKeys(card, createWalletResponse, session, callback) } + else -> { callback( CompletionResult.Success( @@ -174,6 +214,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor { callback(CompletionResult.Failure(result.error)) } @@ -182,13 +223,14 @@ private class CreateWalletTangemWallet : ProductCommandProcessor, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { val map = mutableMapOf>() createWalletResponse.forEach { response -> - val blockchainsForCurve = getBlockchains(response.cardId).filter { + val blockchainsForCurve = getBlockchains(response.cardId, card).filter { it.getSupportedCurves().contains(response.wallet.curve) } val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath(card.derivationStyle) } @@ -210,17 +252,21 @@ private class CreateWalletTangemWallet : ProductCommandProcessor callback(CompletionResult.Failure(result.error)) } } } - private fun getBlockchains(cardId: String): List { + private fun getBlockchains( + cardId: String, + card: CardDTO, + ): List { return when { DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.demoBlockchains card.isTestCard -> listOf(Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt index c0810ff887..8b9d8d9d2f 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt @@ -1,15 +1,15 @@ package com.tangem.tap.domain.tasks.product import com.tangem.common.CompletionResult -import com.tangem.common.card.Card import com.tangem.common.core.CardSession +import com.tangem.domain.common.CardDTO /** [REDACTED_AUTHOR] */ interface ProductCommandProcessor { fun proceed( - card: Card, + card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 459191b260..575f5fc05f 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -13,6 +13,7 @@ import com.tangem.common.extensions.guard import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isExcluded @@ -31,7 +32,6 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand import com.tangem.tap.domain.TapSdkError import com.tangem.tap.domain.extensions.getPrimaryCurve -import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork @@ -53,19 +53,20 @@ class ScanProductTask( callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) return } + val cardDto = CardDTO(card) - val error = getErrorIfExcludedCard(card) + val error = getErrorIfExcludedCard(cardDto) if (error != null) { callback(CompletionResult.Failure(error)) return } val commandProcessor = when { - card.isTangemNote -> ScanNoteProcessor() - card.isTangemTwins -> ScanTwinProcessor() + cardDto.isTangemNote -> ScanNoteProcessor() + cardDto.isTangemTwins -> ScanTwinProcessor() else -> ScanWalletProcessor(userTokensRepository, additionalBlockchainsToDerive) } - commandProcessor.proceed(card, session) { processorResult -> + commandProcessor.proceed(cardDto, session) { processorResult -> when (processorResult) { is CompletionResult.Success -> ScanTask().run(session) { scanTaskResult -> when (scanTaskResult) { @@ -73,7 +74,7 @@ class ScanProductTask( // it need because processorResult.data.card doesn't contains attestation result // and CardWallet.derivedKeys val processorScanResponseWithNewCard = processorResult.data.copy( - card = scanTaskResult.data, + card = CardDTO(scanTaskResult.data), ) callback(CompletionResult.Success(processorScanResponseWithNewCard)) } @@ -85,7 +86,7 @@ class ScanProductTask( } } - private fun getErrorIfExcludedCard(card: Card): TangemError? { + private fun getErrorIfExcludedCard(card: CardDTO): TangemError? { if (card.isExcluded) return TapSdkError.CardForDifferentApp if (card.isNotSupportedInThatRelease) return TapSdkError.CardNotSupportedByRelease return null @@ -94,7 +95,7 @@ class ScanProductTask( private class ScanNoteProcessor : ProductCommandProcessor { override fun proceed( - card: Card, + card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { @@ -117,7 +118,7 @@ private class ScanWalletProcessor( var primaryCard: PrimaryCard? = null override fun proceed( - card: Card, + card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { @@ -125,7 +126,7 @@ private class ScanWalletProcessor( } private fun createMissingWalletsIfNeeded( - card: Card, + card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { @@ -159,13 +160,13 @@ private class ScanWalletProcessor( } private fun startLinkingForBackupIfNeeded( - card: Card, + card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { val activationInProgress = preferencesStorage.usedCardsPrefStorage.isActivationInProgress(card.cardId) - if ((card.backupStatus == Card.BackupStatus.NoBackup && card.wallets.isNotEmpty()) + if ((card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty()) && (activationInProgress || card.isSaltPay) ) { StartPrimaryCardLinkingTask().run(session) { linkingResult -> @@ -185,7 +186,7 @@ private class ScanWalletProcessor( } private fun deriveKeysIfNeeded( - card: Card, + card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { @@ -227,25 +228,46 @@ private class ScanWalletProcessor( } } - private suspend fun getBlockchainsToDerive(card: Card): List { + private suspend fun getBlockchainsToDerive(card: CardDTO): List { val userTokensRepository = userTokensRepository ?: return emptyList() - val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(card).toMutableList().ifEmpty { - mutableListOf( - BlockchainNetwork(Blockchain.Bitcoin, card), - BlockchainNetwork(Blockchain.Ethereum, card), - ) - } + val blockchainsToDerive = userTokensRepository.loadBlockchainsToDerive(card) + .toMutableList() + .ifEmpty { + mutableListOf( + BlockchainNetwork( + blockchain = Blockchain.Bitcoin, + card = card, + ), + BlockchainNetwork( + blockchain = Blockchain.Ethereum, + card = card, + ), + ) + } if (card.settings.isHDWalletAllowed) { blockchainsToDerive.addAll( listOf( - BlockchainNetwork(Blockchain.Ethereum, card), - BlockchainNetwork(Blockchain.EthereumTestnet, card), + BlockchainNetwork( + blockchain = Blockchain.Ethereum, + card = card, + ), + BlockchainNetwork( + blockchain = Blockchain.EthereumTestnet, + card = card, + ), ), ) } if (additionalBlockchainsToDerive != null) { - blockchainsToDerive.addAll(additionalBlockchainsToDerive.map { BlockchainNetwork(it, card) }) + blockchainsToDerive.addAll( + additionalBlockchainsToDerive.map { + BlockchainNetwork( + blockchain = it, + card = card, + ) + }, + ) } if (!card.useOldStyleDerivation) { blockchainsToDerive.removeAll( @@ -255,13 +277,18 @@ private class ScanWalletProcessor( Blockchain.RSK, Blockchain.Fantom, Blockchain.FantomTestnet, Blockchain.Avalanche, Blockchain.AvalancheTestnet, - ).map { BlockchainNetwork(it, card) }, + ).map { + BlockchainNetwork( + blockchain = it, + card = card, + ) + }, ) } return blockchainsToDerive.distinct() } - private suspend fun collectDerivations(card: Card): Map> { + private suspend fun collectDerivations(card: CardDTO): Map> { val blockchains = getBlockchainsToDerive(card) val derivations = mutableMapOf>() @@ -287,14 +314,14 @@ private class ScanWalletProcessor( private class ScanTwinProcessor : ProductCommandProcessor { override fun proceed( - card: Card, + card: CardDTO, session: CardSession, callback: (result: CompletionResult) -> Unit, ) { ReadIssuerDataCommand().run(session) { readDataResult -> when (readDataResult) { is CompletionResult.Success -> { - val publicKey = card.getSingleWallet()?.publicKey + val publicKey = card.wallets.firstOrNull()?.publicKey if (publicKey == null) { val response = ScanResponse( card = card, @@ -332,7 +359,7 @@ private class ScanTwinProcessor : ProductCommandProcessor { } } -fun Card.getCurvesForNonCreatedWallets(): List { +fun CardDTO.getCurvesForNonCreatedWallets(): List { val curvesPresent = wallets.map { it.curve }.toSet() val curvesForNonCreatedWallets = supportedCurves.subtract(curvesPresent + EllipticCurve.Secp256r1) return curvesForNonCreatedWallets.toList() diff --git a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt b/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt index f05065bc8d..6644362bf5 100644 --- a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt +++ b/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt @@ -3,8 +3,8 @@ package com.tangem.tap.domain.termsOfUse import android.content.res.Resources import android.net.Uri import androidx.core.os.ConfigurationCompat -import com.tangem.common.card.Card -import java.util.Locale +import com.tangem.domain.common.CardDTO +import java.util.* /** [REDACTED_AUTHOR] @@ -13,7 +13,7 @@ class CardTou { private val locale: Locale = ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)!! - fun getUrl(card: Card): Uri? { + fun getUrl(card: CardDTO): Uri? { val issuerName = card.issuer.name if (issuerName.lowercase(Locale.getDefault()) != "start2coin") return null diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt index d3d5836701..28bcdc9c35 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt @@ -2,10 +2,11 @@ package com.tangem.tap.domain.tokens import com.tangem.blockchain.common.Blockchain import com.tangem.common.card.FirmwareVersion +import com.tangem.domain.common.CardDTO object CurrenciesRepository { fun getBlockchains( - cardFirmware: FirmwareVersion, + cardFirmware: CardDTO.FirmwareVersion, isTestNet: Boolean = false, ): List { val blockchains = if (cardFirmware < FirmwareVersion.MultiWalletAvailable) { @@ -26,6 +27,4 @@ object CurrenciesRepository { ) } } -} - - +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 40b8389049..4d6646ed32 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -2,13 +2,13 @@ package com.tangem.tap.domain.tokens import android.content.Context import com.tangem.blockchain.common.DerivationStyle -import com.tangem.common.card.Card import com.tangem.common.services.Result +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.util.userWalletId import com.tangem.network.api.tangemTech.TangemTechService import com.tangem.network.api.tangemTech.UserTokensResponse import com.tangem.tap.common.AndroidFileReader import com.tangem.tap.domain.NoDataError -import com.tangem.tap.domain.extensions.getUserWalletId import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.models.Currency @@ -23,8 +23,8 @@ class UserTokensRepository( private val storageService: UserTokensStorageService, private val networkService: UserTokensNetworkService, ) { - suspend fun getUserTokens(card: Card): List { - val userId = card.getUserWalletId() + suspend fun getUserTokens(card: CardDTO): List { + val userId = card.userWalletId.stringValue if (DemoHelper.isDemoCardId(card.cardId)) { return loadTokensOffline(card, userId).ifEmpty { loadDemoCurrencies() } } @@ -36,25 +36,28 @@ class UserTokensRepository( return when (val networkResult = networkService.getUserTokens(userId)) { is Result.Success -> { val tokens = networkResult.data.tokens.mapNotNull { Currency.fromTokenResponse(it) } - storageService.saveUserTokens(card.getUserWalletId(), tokens.toUserTokensResponse()) + storageService.saveUserTokens(userId, tokens.toUserTokensResponse()) tokens.distinct() } + is Result.Failure -> { handleGetUserTokensFailure(card = card, userId = userId, error = networkResult.error) } } } - suspend fun saveUserTokens(card: Card, tokens: List) { + suspend fun saveUserTokens(card: CardDTO, tokens: List) { + val userId = card.userWalletId.stringValue val userTokens = tokens.toUserTokensResponse() - networkService.saveUserTokens(card.getUserWalletId(), userTokens) - storageService.saveUserTokens(card.getUserWalletId(), userTokens) + networkService.saveUserTokens(userId, userTokens) + storageService.saveUserTokens(userId, userTokens) } - suspend fun removeUserTokens(card: Card) { + suspend fun removeUserTokens(card: CardDTO) { + val userId = card.userWalletId.stringValue val userTokens = emptyList().toUserTokensResponse() - networkService.saveUserTokens(card.getUserWalletId(), userTokens) - storageService.saveUserTokens(card.getUserWalletId(), userTokens) + networkService.saveUserTokens(userId, userTokens) + storageService.saveUserTokens(userId, userTokens) } private fun List.toUserTokensResponse(): UserTokensResponse { @@ -66,8 +69,8 @@ class UserTokensRepository( ) } - suspend fun loadBlockchainsToDerive(card: Card): List { - val userId = card.getUserWalletId() + suspend fun loadBlockchainsToDerive(card: CardDTO): List { + val userId = card.userWalletId.stringValue val blockchainNetworks = loadTokensOffline(card, userId).toBlockchainNetworks() if (DemoHelper.isDemoCardId(card.cardId)) { @@ -89,7 +92,7 @@ class UserTokensRepository( } private suspend fun handleGetUserTokensFailure( - card: Card, + card: CardDTO, userId: String, error: Throwable, ): List { @@ -107,7 +110,7 @@ class UserTokensRepository( } } - private suspend fun loadTokensOffline(card: Card, userId: String): List { + private suspend fun loadTokensOffline(card: CardDTO, userId: String): List { return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt index c71e5afa53..5f36f9bd49 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -2,7 +2,7 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonAdapter import com.tangem.Log -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.network.api.tangemTech.UserTokensResponse import com.tangem.network.common.MoshiConverter import com.tangem.tap.common.FileReader @@ -28,7 +28,7 @@ class UserTokensStorageService( } @Deprecated("") - suspend fun getUserTokens(card: Card): List { + suspend fun getUserTokens(card: CardDTO): List { val blockchainNetworks = oldUserTokensRepository.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed) return blockchainNetworks.flatMap { it.toCurrencies() } diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt index 36e376a08b..3b9558c303 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/models/BlockchainNetwork.kt @@ -4,8 +4,8 @@ import com.squareup.moshi.JsonClass import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager -import com.tangem.common.card.Card import com.tangem.common.extensions.calculateHashCode +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle @JsonClass(generateAdapter = true) @@ -15,16 +15,18 @@ data class BlockchainNetwork( val tokens: List ) { - constructor(blockchain: Blockchain, card: Card) : this( + constructor( + blockchain: Blockchain, + card: CardDTO, + ) : this( blockchain = blockchain, derivationPath = if (card.settings.isHDWalletAllowed) blockchain.derivationPath(card.derivationStyle)?.rawPath else null, - tokens = emptyList() + tokens = emptyList(), ) - fun updateTokens(tokens: List): BlockchainNetwork { return copy( - tokens = (this.tokens + tokens).distinct() + tokens = (this.tokens + tokens).distinct(), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index 95e8753b3d..3860732b7d 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -9,7 +9,6 @@ import com.tangem.domain.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand -import com.tangem.tap.domain.extensions.getSingleWallet class CreateFirstTwinWalletTask : CardSessionRunnable { override fun run( diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 8780d00055..cca9da3551 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -23,7 +23,7 @@ class CreateSecondTwinWalletTask( override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val card = session.environment.card - val publicKey = card?.getSingleWallet()?.publicKey + val publicKey = card?.wallets?.firstOrNull()?.publicKey if (publicKey != null) { if (TwinsHelper.getTwinCardNumber(card.cardId) == TwinCardNumber.First) { callback(CompletionResult.Failure(WrongTwinCard(TwinCardNumber.Second))) @@ -63,4 +63,4 @@ class CreateSecondTwinWalletTask( } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index ad9df6d287..c29d226df4 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -6,9 +6,9 @@ import com.tangem.Message import com.tangem.blockchain.extensions.Result import com.tangem.common.CompletionResult import com.tangem.common.KeyPair -import com.tangem.common.card.Card import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.network.common.MoshiConverter import com.tangem.operations.wallet.CreateWalletResponse @@ -16,7 +16,7 @@ import com.tangem.tap.common.AssetReader import com.tangem.tap.tangemSdkManager class TwinCardsManager( - card: Card, + card: CardDTO, assetReader: AssetReader, ) { @@ -91,7 +91,6 @@ class TwinCardsManager( } private class Issuer( - val id: String, val privateKey: String, val publicKey: String, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt index e4ae0109bd..31d35c4a06 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt @@ -13,7 +13,6 @@ import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand import com.tangem.operations.issuerAndUserData.ReadIssuerDataResponse import com.tangem.operations.issuerAndUserData.WriteIssuerDataCommand import com.tangem.operations.sign.SignHashCommand -import com.tangem.tap.domain.extensions.getSingleWallet class WriteProtectedIssuerDataTask( private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair, @@ -25,7 +24,7 @@ class WriteProtectedIssuerDataTask( ) { SignHashCommand( twinPublicKey.calculateSha256(), - session.environment.card!!.getSingleWallet()!!.publicKey + session.environment.card!!.wallets.first().publicKey, ) .run(session) { signResult -> when (signResult) { diff --git a/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt b/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt index 66d09bdbf0..1e97beb6e0 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt @@ -1,10 +1,10 @@ package com.tangem.tap.features.demo -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse /** [REDACTED_AUTHOR] */ fun ScanResponse.isDemoCard(): Boolean = DemoHelper.isDemoCardId(card.cardId) -fun Card.isDemoCard(): Boolean = DemoHelper.isDemoCardId(cardId) \ No newline at end of file +fun CardDTO.isDemoCard(): Boolean = DemoHelper.isDemoCardId(cardId) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index b421b69730..787e92f332 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -1,7 +1,7 @@ package com.tangem.tap.features.details.redux import com.tangem.blockchain.common.Wallet -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.domain.termsOfUse.CardTou @@ -30,7 +30,7 @@ sealed class DetailsAction : Action { object ScanCard : DetailsAction() - data class PrepareCardSettingsData(val card: Card) : DetailsAction() + data class PrepareCardSettingsData(val card: CardDTO) : DetailsAction() object ResetCardSettingsData : DetailsAction() sealed class ManageSecurity : DetailsAction() { diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 88191c0ee4..4c2d72d2fe 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.details.redux import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.domain.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings @@ -13,7 +14,6 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.extensions.getUserWalletId import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -67,11 +67,12 @@ class DetailsMiddleware { scope.launch { when (val result = tangemSdkManager.scanCard()) { is CompletionResult.Success -> { - val card = result.data - if (card.getUserWalletId() == - store.state.globalState.scanResponse?.card?.getUserWalletId() - ) { - store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) + val scannedCard = result.data + val currentCardId = store.state.globalState.scanResponse?.card + ?.userWalletId + ?.stringValue + if (scannedCard.userWalletId.stringValue == currentCardId) { + store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(scannedCard)) } else { store.dispatchDialogShow( AppDialog.SimpleOkDialogRes( @@ -81,8 +82,7 @@ class DetailsMiddleware { ) } } - is CompletionResult.Failure -> { - } + is CompletionResult.Failure -> Unit } } } @@ -104,22 +104,20 @@ class DetailsMiddleware { is DetailsAction.ResetToFactory.Proceed -> { val card = store.state.detailsState.cardSettingsState?.card ?: return scope.launch { - val result = tangemSdkManager.resetToFactorySettings(card) - when (result) { + when (val result = tangemSdkManager.resetToFactorySettings(card.cardId)) { is CompletionResult.Success -> { Analytics.send(Settings.CardSettings.FactoryResetFinished()) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } + is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - Analytics.send(Settings.CardSettings.FactoryResetFinished(error)) - } + Analytics.send(Settings.CardSettings.FactoryResetFinished(error)) } } } } } - else -> { /* no-op */ - } + else -> Unit } } } @@ -156,8 +154,7 @@ class DetailsMiddleware { } store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure) } - else -> { /* no-op */ - } + else -> Unit } } } @@ -171,8 +168,7 @@ class DetailsMiddleware { } } } - else -> { /* no-op */ - } + else -> Unit } } } @@ -186,4 +182,4 @@ class DetailsMiddleware { } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index ef4a0aa975..225505dfde 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.details.redux -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTangemNote @@ -51,12 +51,12 @@ private fun handlePrepareScreen( scanResponse = action.scanResponse, wallets = action.wallets, cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card), - createBackupAllowed = action.scanResponse.card.backupStatus == Card.BackupStatus.NoBackup, + createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appCurrency = store.state.globalState.appCurrency, ) } -private fun handlePrepareCardSettingsScreen(card: Card, state: DetailsState): DetailsState { +private fun handlePrepareCardSettingsScreen(card: CardDTO, state: DetailsState): DetailsState { val cardSettingsState = CardSettingsState( cardInfo = card.toCardInfo(), manageSecurityState = prepareSecurityOptions(card), @@ -66,14 +66,16 @@ private fun handlePrepareCardSettingsScreen(card: Card, state: DetailsState): De return state.copy(cardSettingsState = cardSettingsState) } -private fun prepareSecurityOptions(card: Card): ManageSecurityState { +private fun prepareSecurityOptions(card: CardDTO): ManageSecurityState { val securityOption = when { card.isAccessCodeSet -> { SecurityOption.AccessCode } + card.isPasscodeSet == true -> { SecurityOption.PassCode } + else -> { SecurityOption.LongTap } @@ -94,7 +96,7 @@ private fun prepareSecurityOptions(card: Card): ManageSecurityState { ) } -private fun isResetToFactoryAllowedByCard(card: Card): Boolean { +private fun isResetToFactoryAllowedByCard(card: CardDTO): Boolean { val notAllowedByAnyWallet = card.wallets.any { it.settings.isPermanent } val notAllowedByCard = notAllowedByAnyWallet || (card.isWalletDataSupported && (!card.isTangemNote && !card.settings.isBackupAllowed)) || @@ -114,7 +116,8 @@ private fun handleEraseWallet( } private fun handleSecurityAction( - action: DetailsAction.ManageSecurity, state: DetailsState, + action: DetailsAction.ManageSecurity, + state: DetailsState, ): DetailsState { return when (action) { is DetailsAction.ManageSecurity.SelectOption -> { @@ -144,7 +147,8 @@ private fun handleSecurityAction( } private fun handlePrivacyAction( - action: DetailsAction.AppSettings, state: DetailsState, + action: DetailsAction.AppSettings, + state: DetailsState, ): DetailsState { return when (action) { is DetailsAction.AppSettings.SwitchPrivacySetting -> { @@ -157,7 +161,8 @@ private fun handlePrivacyAction( } private fun prepareAllowedSecurityOptions( - card: Card?, currentSecurityOption: SecurityOption?, + card: CardDTO?, + currentSecurityOption: SecurityOption?, ): EnumSet { val allowedSecurityOptions = EnumSet.of(SecurityOption.LongTap) @@ -173,7 +178,7 @@ private fun prepareAllowedSecurityOptions( return allowedSecurityOptions } -private fun Card.toCardInfo(): CardInfo { +private fun CardDTO.toCardInfo(): CardInfo { val cardId = this.cardId.chunked(4).joinToString(separator = " ") val issuer = this.issuer.name val signedHashes = this.signedHashesCount() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 8e02856097..07c0847213 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -2,7 +2,7 @@ package com.tangem.tap.features.details.redux import android.net.Uri import com.tangem.blockchain.common.Wallet -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.entities.Button import com.tangem.tap.common.entities.FiatCurrency @@ -20,13 +20,13 @@ data class DetailsState( val privacyPolicyUrl: String? = null, val createBackupAllowed: Boolean = false, val appCurrency: FiatCurrency = FiatCurrency.Default, - val saveWallets: Boolean = true, - val saveAccessCodes: Boolean = true, + val saveWallets: Boolean = false, + val saveAccessCodes: Boolean = false, ) : StateType { // if you do not delegate - the application crashes on startup, // because twinCardsState has not been created yet - val twinCardsState: TwinCardsState by ReadOnlyProperty { thisRef, property -> + val twinCardsState: TwinCardsState by ReadOnlyProperty { _, _ -> store.state.twinCardsState } @@ -42,7 +42,7 @@ data class CardInfo( data class CardSettingsState( val cardInfo: CardInfo, - val card: Card, + val card: CardDTO, val manageSecurityState: ManageSecurityState?, val resetCardAllowed: Boolean, val resetConfirmed: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index ed2c68c8d6..7818f367b3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -3,8 +3,8 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.WalletManager -import com.tangem.common.card.Card import com.tangem.common.extensions.guard +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.withMainContext @@ -240,7 +240,7 @@ class WalletConnectMiddleware { handleScanResponse(scanResponse = scanResponse, session = session, blockchain = blockchain) } - private fun getAvailableBlockchains(card: Card, walletState: WalletState): List { + private fun getAvailableBlockchains(card: CardDTO, walletState: WalletState): List { return walletState.currencies.filter { it.isBlockchain() && !it.isCustomCurrency(card.derivationStyle) && it.blockchain.isEvm() }.map { it.blockchain } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt index 530a4afb78..ee0700f5b5 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt @@ -1,8 +1,8 @@ package com.tangem.tap.features.disclaimer.redux import android.net.Uri -import com.tangem.common.card.Card import com.tangem.common.extensions.VoidCallback +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isSaltPay import org.rekotlin.StateType @@ -22,7 +22,7 @@ sealed class DisclaimerType( companion object { fun get(scanResponse: ScanResponse): DisclaimerType = get(scanResponse.card) - fun get(card: Card): DisclaimerType = when { + fun get(card: CardDTO): DisclaimerType = when { card.isSaltPay -> SaltPay else -> Tangem } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index 0a276dff4f..6b71aee0f7 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -14,4 +14,4 @@ sealed class HomeAction : Action { data class GoToShop(val userCountryCode: String?) : HomeAction() data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction() -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index c3c0145ec8..1d94184e98 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -1,8 +1,8 @@ package com.tangem.tap.features.home.redux -import com.tangem.common.card.Card import com.tangem.common.core.TangemError import com.tangem.common.services.Result +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withIOContext import com.tangem.domain.common.extensions.withMainContext @@ -45,7 +45,6 @@ import com.tangem.wallet.R import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action -import org.rekotlin.DispatchFunction import org.rekotlin.Middleware class HomeMiddleware { @@ -56,22 +55,23 @@ class HomeMiddleware { } } -private val homeMiddleware: Middleware = { dispatch, state -> +private val homeMiddleware: Middleware = { _, _ -> { next -> { action -> - handleHomeAction(state, action, dispatch) + handleHomeAction(action) next(action) } } } -private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch: DispatchFunction) { +private fun handleHomeAction(action: Action) { when (action) { is HomeAction.Init -> { store.dispatch(GlobalAction.RestoreAppCurrency) store.dispatch(GlobalAction.ExchangeManager.Init) store.dispatch(GlobalAction.FetchUserCountry) } + is HomeAction.ShouldScanCardOnResume -> { if (action.shouldScanCard) { store.dispatch(HomeAction.ShouldScanCardOnResume(false)) @@ -123,7 +123,7 @@ private fun checkForUnfinishedBackupForSaltPay( return } - fun isTheSamePrimaryCard(card: Card): Boolean { + fun isTheSamePrimaryCard(card: CardDTO): Boolean { return backupService.primaryCardId?.let { it == card.cardId } ?: false } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 2917a052fe..f8996b9ea9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -3,7 +3,6 @@ package com.tangem.tap.features.onboarding import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.extensions.hasWallets import com.tangem.tap.preferencesStorage /** @@ -23,7 +22,7 @@ class OnboardingHelper { cardInfoStorage.isActivationInProgress(cardId) } } - response.card.hasWallets() -> cardInfoStorage.isActivationInProgress(cardId) + response.card.wallets.isNotEmpty() -> cardInfoStorage.isActivationInProgress(cardId) else -> true } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 5ecccaa431..6f17c03078 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -22,7 +22,6 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.extensions.hasWallets import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE @@ -79,7 +78,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch } is OnboardingNoteAction.DetermineStepOfScreen -> { val step = when { - !card.hasWallets() -> OnboardingNoteStep.CreateWallet + card.wallets.isEmpty() -> OnboardingNoteStep.CreateWallet noteState.walletBalance.balanceIsToppedUp() -> OnboardingNoteStep.Done else -> OnboardingNoteStep.TopUpWallet } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index 45c4a48fc8..acd4013518 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -12,7 +12,6 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.extensions.hasWallets import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.scope @@ -21,7 +20,6 @@ import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action -import org.rekotlin.DispatchFunction import org.rekotlin.Middleware class OnboardingOtherCardsMiddleware { @@ -30,16 +28,16 @@ class OnboardingOtherCardsMiddleware { } } -private val onboardingOtherCardsMiddleware: Middleware = { dispatch, state -> +private val onboardingOtherCardsMiddleware: Middleware = { dispatch, _ -> { next -> { action -> - handleOtherCardsAction(action, dispatch) + handleOtherCardsAction(action) next(action) } } } -private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { +private fun handleOtherCardsAction(action: Action) { if (action !is OnboardingOtherCardsAction) return val globalState = store.state.globalState val onboardingManager = globalState.onboardingState.onboardingManager ?: return @@ -60,7 +58,7 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { } is OnboardingOtherCardsAction.DetermineStepOfScreen -> { val step = when { - !card.hasWallets() -> OnboardingOtherCardsStep.CreateWallet + card.wallets.isEmpty() -> OnboardingOtherCardsStep.CreateWallet else -> OnboardingOtherCardsStep.Done } store.dispatch((OnboardingOtherCardsAction.SetStepOfScreen(step))) @@ -87,21 +85,32 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { val updatedResponse = onboardingManager.scanResponse.copy( card = result.data.card, ) + val updatedCard = updatedResponse.card onboardingManager.scanResponse = updatedResponse - onboardingManager.activationStarted(updatedResponse.card.cardId) + onboardingManager.activationStarted(updatedCard.cardId) val primaryBlockchain = updatedResponse.getBlockchain() val blockchainNetworks = if (primaryBlockchain != Blockchain.Unknown) { val primaryToken = updatedResponse.getPrimaryToken() val blockchainNetwork = - BlockchainNetwork(primaryBlockchain, updatedResponse.card).updateTokens( - listOfNotNull(primaryToken), + BlockchainNetwork( + blockchain = primaryBlockchain, + card = updatedCard, ) + .updateTokens( + listOfNotNull(primaryToken), + ) listOf(blockchainNetwork) } else { listOf( - BlockchainNetwork(Blockchain.Bitcoin, updatedResponse.card), - BlockchainNetwork(Blockchain.Ethereum, updatedResponse.card), + BlockchainNetwork( + blockchain = Blockchain.Bitcoin, + card = updatedCard, + ), + BlockchainNetwork( + blockchain = Blockchain.Ethereum, + card = updatedCard, + ), ) } @@ -126,5 +135,9 @@ private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { withMainContext { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } } } + is OnboardingOtherCardsAction.Confetti.Hide, + is OnboardingOtherCardsAction.SetArtworkUrl, + is OnboardingOtherCardsAction.Confetti.Show, + -> Unit } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index aa6de330d6..7233956925 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -150,6 +150,13 @@ private fun handle(action: Action, dispatch: DispatchFunction) { finishCardActivation() postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) } } + + TwinCardsStep.None, + TwinCardsStep.Warning, + TwinCardsStep.CreateFirstWallet, + TwinCardsStep.CreateSecondWallet, + TwinCardsStep.CreateThirdWallet, + -> Unit } } is TwinCardsAction.Wallet.LaunchFirstStep -> { @@ -299,4 +306,4 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index dfe53282ed..31b85f86e6 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -2,8 +2,8 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult -import com.tangem.common.card.Card import com.tangem.common.extensions.ifNotNull +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext @@ -18,7 +18,6 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.extensions.hasWallets import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction @@ -76,19 +75,23 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: // it's possible when found unfinished backup for standard Wallet cards store.dispatch(OnboardingWalletAction.ResumeBackup) } - card.hasWallets() && card.backupStatus == Card.BackupStatus.NoBackup -> { + + card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> { store.dispatch(OnboardingWalletAction.ResumeBackup) } - card.hasWallets() && card.backupStatus?.isActive == true -> { + + card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> { when { // check for unfinished backup for saltPay cards. See more scanResponse.isSaltPay() && backupService.hasIncompletedBackup -> { store.dispatch(OnboardingWalletAction.ResumeBackup) } + scanResponse.isSaltPay() -> { store.dispatch(OnboardingWalletAction.GetToSaltPayStep) store.dispatch(BackupAction.FinishBackup) } + else -> store.dispatch(BackupAction.FinishBackup) } } @@ -125,8 +128,14 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: ) onboardingManager.scanResponse = updatedResponse val blockchainNetworks = listOf( - BlockchainNetwork(Blockchain.Bitcoin, result.data.card), - BlockchainNetwork(Blockchain.Ethereum, result.data.card), + BlockchainNetwork( + blockchain = Blockchain.Bitcoin, + card = result.data.card, + ), + BlockchainNetwork( + blockchain = Blockchain.Ethereum, + card = result.data.card, + ), ) store.dispatch( WalletAction.MultiWallet.SaveCurrencies( @@ -193,7 +202,7 @@ private fun updateScanResponseAfterBackup( val card = if (backupState.backupCardsNumber > 0) { val cardsCount = backupState.backupCardsNumber scanResponse.card.copy( - backupStatus = Card.BackupStatus.Active(cardCount = cardsCount), + backupStatus = CardDTO.BackupStatus.Active(cardCount = cardsCount), isAccessCodeSet = true, ) } else { @@ -366,9 +375,10 @@ private fun startCardActivation(scanResponse: ScanResponse) { * Standard Wallet cards finish activation at BackupAction.FinishBackup * SaltPay cards finish activation at OnboardingWalletAction.FinishOnboarding */ -internal fun finishCardActivation(backupState: BackupState, card: Card?) { +internal fun finishCardActivation(backupState: BackupState, card: CardDTO?) { (listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds) - .distinct().filterNotNull() + .distinct() + .filterNotNull() .forEach { cardId -> preferencesStorage.usedCardsPrefStorage.activationFinished(cardId) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt index 4ef4b407d2..04933d0a46 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt @@ -11,6 +11,7 @@ import com.tangem.common.extensions.guard import com.tangem.common.extensions.isZero import com.tangem.common.services.Result import com.tangem.domain.common.extensions.successOr +import com.tangem.domain.common.util.UserWalletId import com.tangem.network.api.paymentology.AttestationResponse import com.tangem.network.api.paymentology.PaymentologyApiService import com.tangem.network.api.paymentology.RegisterKYCRequest @@ -20,7 +21,6 @@ import com.tangem.network.api.paymentology.RegistrationResponse import com.tangem.network.api.paymentology.tryExtractError import com.tangem.operations.attestation.AttestWalletKeyResponse import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.domain.extensions.UserWalletId import com.tangem.tap.domain.getFirstToken import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import java.math.BigDecimal @@ -123,7 +123,7 @@ class SaltPayActivationManager( } suspend fun claim(amountToClaim: BigDecimal, signer: TransactionSigner): Result { - val result = gnosisRegistrator.transferFrom(amountToClaim, signer).successOr { + gnosisRegistrator.transferFrom(amountToClaim, signer).successOr { return Result.Failure(SaltPayActivationError.ClaimTransactionFailed) } return Result.Success(Unit) @@ -192,4 +192,4 @@ class KYCUrlProvider( .appendQueryParameter(kycProvider.externalIdParameterKey, kycRefId) .build().toString() } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index da468c2636..13811d5ed0 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -11,9 +11,9 @@ import com.tangem.blockchain.common.TransactionError import com.tangem.blockchain.common.TransactionSender import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE @@ -150,7 +150,7 @@ private fun sendTransaction( feeAmount: Amount, destinationAddress: String, transactionExtras: TransactionExtrasState, - card: Card, + card: CardDTO, externalTransactionData: ExternalTransactionData?, dispatch: (Action) -> Unit, ) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 06e3cbae64..4c5dd07560 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -1,8 +1,6 @@ package com.tangem.tap.features.wallet.models -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle -import com.tangem.blockchain.common.Token import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.common.extensions.toNetworkId @@ -11,6 +9,8 @@ import com.tangem.network.api.tangemTech.TokenResponse import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.tokens.redux.TokenWithBlockchain +import com.tangem.blockchain.common.Blockchain as SdkBlockchain +import com.tangem.blockchain.common.Token as SdkToken sealed interface Currency { val coinId: String? @@ -18,7 +18,7 @@ sealed interface Currency { is Blockchain -> blockchain.toCoinId() is Token -> token.id } - val blockchain: com.tangem.blockchain.common.Blockchain + val blockchain: SdkBlockchain val currencySymbol: CryptoCurrencyName val derivationPath: String? val currencyName: String @@ -33,15 +33,15 @@ sealed interface Currency { } data class Token( - val token: com.tangem.blockchain.common.Token, - override val blockchain: com.tangem.blockchain.common.Blockchain, + val token: SdkToken, + override val blockchain: SdkBlockchain, override val derivationPath: String?, ) : Currency { override val currencySymbol = token.symbol } data class Blockchain( - override val blockchain: com.tangem.blockchain.common.Blockchain, + override val blockchain: SdkBlockchain, override val derivationPath: String?, ) : Currency { override val currencySymbol: CryptoCurrencyName = blockchain.currency @@ -72,7 +72,7 @@ sealed interface Currency { companion object { fun fromBlockchainNetwork( blockchainNetwork: BlockchainNetwork, - token: com.tangem.blockchain.common.Token? = null, + token: SdkToken? = null, ): Currency { return if (token != null) { Token( @@ -115,7 +115,7 @@ sealed interface Currency { ?: return null return when { tokenResponse.contractAddress != null -> Token( - com.tangem.blockchain.common.Token( + token = SdkToken( name = tokenResponse.name, symbol = tokenResponse.symbol, contractAddress = tokenResponse.contractAddress!!, @@ -148,8 +148,8 @@ fun List.toBlockchainNetworks(): List { return this.filter { it.isBlockchain() }.map { BlockchainNetwork(it.blockchain, it.derivationPath, getTokens(it)) } } -private fun List.getTokens(currency: Currency): List { +fun List.getTokens(currency: Currency): List { return this .filter { it.isToken() && it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath } .mapNotNull { if (it is Currency.Token) it.token else null } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 02e6073066..fc947d90fd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -7,9 +7,8 @@ import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage @@ -70,7 +69,7 @@ sealed class WalletAction : Action { data class AddToken(val token: Token, val blockchain: BlockchainNetwork, val save: Boolean) : MultiWallet() data class SaveCurrencies( - val blockchainNetworks: List, val card: Card? = null, + val blockchainNetworks: List, val card: CardDTO? = null, ) : MultiWallet() data class TokenLoaded( @@ -123,9 +122,9 @@ sealed class WalletAction : Action { object Failure : WalletAction() } - class LoadCardInfo(val card: Card) : WalletAction() + class LoadCardInfo(val card: CardDTO) : WalletAction() - data class LoadArtwork(val card: Card, val artworkId: String?) : WalletAction() { + data class LoadArtwork(val card: CardDTO, val artworkId: String?) : WalletAction() { data class Success(val artwork: Artwork) : WalletAction() object Failure : WalletAction() } @@ -134,10 +133,6 @@ sealed class WalletAction : Action { data class Send(val amount: Amount? = null) : WalletAction() - object EmptyField : WalletAction(), ErrorAction { - override val error = TapError.PayIdEmptyField - } - data class CopyAddress(val address: String, val context: Context) : WalletAction() { object Success : WalletAction(), NotificationAction { override val messageResource = R.string.wallet_notification_address_copied @@ -186,7 +181,7 @@ sealed class WalletAction : Action { data class SetWalletRent( val wallet: Wallet, val minRent: String, - val rentExempt: String + val rentExempt: String, ) : WalletAction() data class RemoveWalletRent(val wallet: Wallet) : WalletAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index 112c3a0afb..109d44ad31 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -4,8 +4,8 @@ import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.SignatureCountValidator import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.common.card.Card import com.tangem.common.card.FirmwareVersion +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.tap.common.extensions.dispatchOnMain @@ -13,10 +13,8 @@ import com.tangem.tap.common.extensions.isGreaterThan import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager -import com.tangem.tap.domain.extensions.getSingleWallet import com.tangem.tap.domain.extensions.hasSignedHashes import com.tangem.tap.domain.extensions.isMultiwalletAllowed -import com.tangem.tap.domain.extensions.remainingSignatures import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity @@ -108,8 +106,8 @@ class WarningsMiddleware { } } - private fun showWarningLowRemainingSignaturesIfNeeded(card: Card) { - val remainingSignatures = card.remainingSignatures + private fun showWarningLowRemainingSignaturesIfNeeded(card: CardDTO) { + val remainingSignatures = card.wallets.firstOrNull()?.remainingSignatures if (remainingSignatures != null && remainingSignatures <= WarningMessagesManager.REMAINING_SIGNATURES_WARNING ) { @@ -159,7 +157,7 @@ class WarningsMiddleware { val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator scope.launch { - val signedHashes = card.getSingleWallet()?.totalSignedHashes ?: 0 + val signedHashes = card.wallets.firstOrNull()?.totalSignedHashes ?: 0 val result = validator?.validateSignatureCount(signedHashes) withContext(Dispatchers.Main) { when (result) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index d4172e31ef..956fe058dc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -3,7 +3,7 @@ package com.tangem.tap.features.wallet.ui.wallet import android.widget.Button import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.tap.common.analytics.Analytics @@ -54,8 +54,8 @@ class MultiWalletView : WalletView() { private fun setupWalletCardNumber(binding: FragmentWalletBinding) = with(binding) { val card = store.state.globalState.scanResponse?.card - if (card?.backupStatus is Card.BackupStatus.Active) { - val cardCount = (card.backupStatus as Card.BackupStatus.Active).cardCount + 1 + if (card?.backupStatus is CardDTO.BackupStatus.Active) { + val cardCount = (card.backupStatus as CardDTO.BackupStatus.Active).cardCount + 1 tvTwinCardNumber.show() tvTwinCardNumber.text = tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, cardCount) } else { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt index a7f7b9aec2..939121bda7 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CardExchangeRules.kt @@ -1,6 +1,6 @@ package com.tangem.tap.network.exchangeServices -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.wallet.models.Currency @@ -9,7 +9,7 @@ import com.tangem.tap.features.wallet.models.Currency [REDACTED_AUTHOR] */ class CardExchangeRules( - val cardProvider: () -> Card?, + val cardProvider: () -> CardDTO?, ) : ExchangeRules { override fun featureIsSwitchedOn(): Boolean { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt index 04c92fc542..3f61d82801 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -7,7 +7,7 @@ import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.extensions.Result -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.common.redux.global.GlobalAction @@ -86,8 +86,9 @@ class CurrencyExchangeManager( } } +@Suppress("unused") suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( - card: Card, + card: CardDTO, walletManager: EthereumWalletManager, token: Token, ) { diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt index c161b935fb..11cfdee61f 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -1,6 +1,6 @@ package com.tangem.tap.proxy -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TangemSdkManager @@ -20,7 +20,7 @@ class AppStateHolder { var mainStore: Store? = null var tangemSdkManager: TangemSdkManager? = null - fun getActualCard(): Card? { + fun getActualCard(): CardDTO? { return scanResponse?.card } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 2832cd6faf..8856cdc80d 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -4,15 +4,15 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationParams import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.common.card.Card +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toNetworkId +import com.tangem.domain.common.util.userWalletId import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.NativeToken import com.tangem.lib.crypto.models.NonNativeToken -import com.tangem.tap.domain.extensions.getUserWalletId import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction @@ -54,7 +54,7 @@ class UserWalletManagerImpl( } override fun getWalletId(): String { - return appStateHolder.getActualCard()?.getUserWalletId() ?: "" + return appStateHolder.getActualCard()?.userWalletId?.stringValue ?: "" } override suspend fun isTokenAdded(currency: Currency): Boolean { @@ -100,7 +100,7 @@ class UserWalletManagerImpl( } } - private fun addNativeTokenToWalletAction(token: NativeToken, card: Card): Action { + private fun addNativeTokenToWalletAction(token: NativeToken, card: CardDTO): Action { val blockchain = Blockchain.fromNetworkId(token.networkId) val scanResponse = appStateHolder.scanResponse if (blockchain != null && scanResponse != null) { diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 1a730aa925..f9ac9274ec 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -58,11 +58,10 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-140" - - // const val tangemBlockchainSdk = "0.0.1" - const val tangemCardSgk = "develop-169" - // const val tangemCardSgk = "0.0.1" + const val tangemBlockchainSdk = "0.0.1" + // const val tangemBlockchainSdk = "develop-140" + const val tangemCardSgk = "0.0.1" + // const val tangemCardSgk = "develop-165" // endregion Tangem // region Testing diff --git a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt index 56c6979d5e..ad0538d7a5 100644 --- a/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt +++ b/domain/src/main/java/com/tangem/domain/common/ScanResponse.kt @@ -2,7 +2,6 @@ package com.tangem.domain.common import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token -import com.tangem.common.card.Card import com.tangem.common.card.EllipticCurve import com.tangem.common.card.WalletData import com.tangem.common.extensions.ByteArrayKey @@ -12,7 +11,6 @@ import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isSaltPayVisa import com.tangem.domain.common.TapWorkarounds.isSaltPayWallet -import com.tangem.domain.common.TapWorkarounds.isTangemNote import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.operations.CommandResponse @@ -23,7 +21,7 @@ import com.tangem.operations.derivation.ExtendedPublicKeysMap [REDACTED_AUTHOR] */ data class ScanResponse( - val card: Card, + val card: CardDTO, val productType: ProductType, val walletData: WalletData?, val secondTwinPublicKey: String? = null, @@ -67,20 +65,22 @@ data class ScanResponse( return hasDerivation(blockchain, DerivationPath(rawDerivationPath)) } - fun hasDerivation(blockchain: Blockchain, derivationPath: DerivationPath): Boolean { + private fun hasDerivation(blockchain: Blockchain, derivationPath: DerivationPath): Boolean { val isTestnet = card.isTestCard || blockchain.isTestnet() return when { Blockchain.secp256k1Blockchains(isTestnet).contains(blockchain) -> { hasDerivation(EllipticCurve.Secp256k1, derivationPath) } + Blockchain.ed25519OnlyBlockchains(isTestnet).contains(blockchain) -> { hasDerivation(EllipticCurve.Ed25519, derivationPath) } + else -> false } } - fun hasDerivation(curve: EllipticCurve, derivationPath: DerivationPath): Boolean { + private fun hasDerivation(curve: EllipticCurve, derivationPath: DerivationPath): Boolean { val foundWallet = card.wallets.firstOrNull { it.curve == curve } ?: return false val extendedPublicKeysMap = derivedKeys[foundWallet.publicKey.toMapKey()] ?: return false @@ -93,12 +93,4 @@ enum class ProductType { Note, Twins, Wallet, SaltPay } -val Card.productType: ProductType - get() = when { - isTangemTwins -> ProductType.Twins - isTangemNote -> ProductType.Note - isSaltPay -> ProductType.SaltPay - else -> ProductType.Wallet - } - typealias KeyWalletPublicKey = ByteArrayKey \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt index 69ec26308b..21fa7e169f 100644 --- a/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt +++ b/domain/src/main/java/com/tangem/domain/common/TapWorkarounds.kt @@ -17,18 +17,32 @@ object TapWorkarounds { return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER } - val Card.isStart2Coin: Boolean + val CardDTO.isTangemTwins: Boolean + get() = TwinsHelper.getTwinCardNumber(cardId) != null + + //TODO: replace by reading files from a card + val CardDTO.isTangemNote: Boolean + get() = tangemNoteBatches.contains(batchId) + + val CardDTO.isStart2Coin: Boolean get() = isStart2CoinIssuer(issuer.name) - val Card.isTestCard: Boolean + val CardDTO.isSaltPay: Boolean + get() = isSaltPayVisa || isSaltPayWallet + + val CardDTO.isSaltPayVisa: Boolean + get() = SaltPayWorkaround.isVisaBatchId(batchId) + + val CardDTO.isSaltPayWallet: Boolean + get() = SaltPayWorkaround.isWalletCardId(cardId) + + val CardDTO.isTestCard: Boolean get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH) - val Card.useOldStyleDerivation: Boolean + val CardDTO.useOldStyleDerivation: Boolean get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95" - - val Card.derivationStyle: DerivationStyle? + val CardDTO.derivationStyle: DerivationStyle? get() = if (!settings.isHDWalletAllowed) { - Int.MAX_VALUE null } else if (useOldStyleDerivation) { DerivationStyle.LEGACY @@ -36,15 +50,18 @@ object TapWorkarounds { DerivationStyle.NEW } - val Card.isNotSupportedInThatRelease: Boolean + val CardDTO.isExcluded: Boolean + get() { + val excludedBatch = excludedBatches.contains(batchId) + val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT)) + return excludedBatch || excludedIssuerName + } + + val CardDTO.isNotSupportedInThatRelease: Boolean get() = false - val Card.isTangemTwins: Boolean - get() = TwinsHelper.getTwinCardNumber(cardId) != null - - //TODO: replace by reading files from a card - val Card.isTangemNote: Boolean - get() = tangemNoteBatches.contains(batchId) + fun CardDTO.getTangemNoteBlockchain(): Blockchain? = + tangemNoteBatches[batchId] ?: if (isSaltPay) Blockchain.Gnosis else null private val tangemNoteBatches = mapOf( "AB01" to Blockchain.Bitcoin, @@ -63,24 +80,6 @@ object TapWorkarounds { fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId] ?: null - - val Card.isSaltPay: Boolean - get() = isSaltPayVisa || isSaltPayWallet - - val Card.isSaltPayVisa: Boolean - get() = SaltPayWorkaround.isVisaBatchId(batchId) - - val Card.isSaltPayWallet: Boolean - get() = SaltPayWorkaround.isWalletCardId(cardId) - - - val Card.isExcluded: Boolean - get() { - val excludedBatch = excludedBatches.contains(batchId) - val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT)) - return excludedBatch || excludedIssuerName - } - private val excludedBatches = listOf( "0027", "0030", diff --git a/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt b/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt index 23485d8340..a1ded44422 100644 --- a/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt +++ b/domain/src/main/java/com/tangem/domain/common/TwinsHelper.kt @@ -1,6 +1,5 @@ package com.tangem.domain.common -import com.tangem.common.card.Card import com.tangem.crypto.CryptoUtils class TwinsHelper { @@ -57,14 +56,14 @@ enum class TwinCardNumber(val number: Int) { } @Deprecated("Use ScanResponse.isTangemTwin") -fun Card.isTangemTwin(): Boolean { +fun CardDTO.isTangemTwin(): Boolean { return TwinsHelper.getTwinCardNumber(cardId) != null } -fun Card.getTwinCardNumber(): TwinCardNumber? { +fun CardDTO.getTwinCardNumber(): TwinCardNumber? { return TwinsHelper.getTwinCardNumber(this.cardId) } -fun Card.getTwinCardIdForUser(): String { +fun CardDTO.getTwinCardIdForUser(): String { return TwinsHelper.getTwinCardIdForUser(this.cardId) } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt b/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt index acc0fc7bf0..bd7a3d7643 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/CardSdk.kt @@ -1,9 +1,9 @@ package com.tangem.domain.common.extensions import com.tangem.blockchain.common.Blockchain -import com.tangem.common.card.Card import com.tangem.common.card.EllipticCurve import com.tangem.common.card.FirmwareVersion +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isTestCard /** @@ -12,11 +12,12 @@ import com.tangem.domain.common.TapWorkarounds.isTestCard val FirmwareVersion.Companion.SolanaTokensAvailable get() = FirmwareVersion(4, 52) -fun Card.supportedBlockchains(): List { +fun CardDTO.supportedBlockchains(): List { val supportedBlockchains = when { firmwareVersion < FirmwareVersion.MultiWalletAvailable -> { Blockchain.fromCurve(EllipticCurve.Secp256k1) } + else -> { (Blockchain.fromCurve(EllipticCurve.Secp256k1) + Blockchain.fromCurve(EllipticCurve.Ed25519)).distinct() } @@ -26,7 +27,7 @@ fun Card.supportedBlockchains(): List { .filter { it.isSupportedInApp() } } -fun Card.supportedTokens(): List { +fun CardDTO.supportedTokens(): List { val tokensSupportedByBlockchain = supportedBlockchains().filter { it.canHandleTokens() }.toMutableList() val tokensSupportedByCard = when { firmwareVersion >= FirmwareVersion.SolanaTokensAvailable -> tokensSupportedByBlockchain @@ -41,10 +42,10 @@ fun Card.supportedTokens(): List { return filtered } -fun Card.canHandleBlockchain(blockchain: Blockchain): Boolean { +fun CardDTO.canHandleBlockchain(blockchain: Blockchain): Boolean { return this.supportedBlockchains().contains(blockchain) } -fun Card.canHandleToken(blockchain: Blockchain): Boolean { +fun CardDTO.canHandleToken(blockchain: Blockchain): Boolean { return this.supportedTokens().contains(blockchain) } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt index 4c8bedcd22..1d51a46d2c 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt +++ b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt @@ -10,8 +10,8 @@ class UserWalletId( ) { val value = stringValue.hexToBytes() - constructor(walletPublicKey: ByteArray) : this( - stringValue = calculateUserWalletId(walletPublicKey).toHexString(), + constructor(walletPublicKey: ByteArray?) : this( + stringValue = walletPublicKey?.let { calculateUserWalletId(it).toHexString() } ?: "", ) override fun equals(other: Any?): Boolean { diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt index 28b821f2ab..6dde5b01c6 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt @@ -2,10 +2,10 @@ package com.tangem.domain.features.addCustomToken.redux import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationStyle -import com.tangem.common.card.Card import com.tangem.common.json.MoshiJsonConverter import com.tangem.domain.AddCustomTokenError import com.tangem.domain.DomainWrapped +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.extensions.isSupportedInApp import com.tangem.domain.common.extensions.supportedBlockchains @@ -133,7 +133,7 @@ data class AddCustomTokenState( null } - fun reset(card: Card): AddCustomTokenState { + fun reset(card: CardDTO): AddCustomTokenState { return this.copy( appSavedCurrencies = null, onTokenAddCallback = null, @@ -159,7 +159,7 @@ data class AddCustomTokenState( .getConvertedData() } - fun getNetworks(card: Card, type: CustomTokenType): List { + fun getNetworks(card: CardDTO, type: CustomTokenType): List { return getNetworksList(card, type) } @@ -189,7 +189,7 @@ data class AddCustomTokenState( }.derivationPath(derivationStyleToUse) } - internal fun createFormFields(card: Card, type: CustomTokenType): List> { + internal fun createFormFields(card: CardDTO, type: CustomTokenType): List> { return listOf( TokenField(ContractAddress), TokenBlockchainField(Network, getNetworksList(card, type)), @@ -204,7 +204,7 @@ data class AddCustomTokenState( * Serves to determine the networks (blockchains & tokens) that can be selected by Form.Networks. * Blockchain.Unknown - is the default selection */ - private fun getNetworksList(card: Card, type: CustomTokenType): List { + private fun getNetworksList(card: CardDTO, type: CustomTokenType): List { val evmBlockchains = Blockchain.values() .filter { it.isEvm() } .filter { card.isTestCard == it.isTestnet() } @@ -239,7 +239,7 @@ data class AddCustomTokenState( ) } - private fun getSupportedDerivations(card: Card): List { + private fun getSupportedDerivations(card: CardDTO): List { val evmBlockchains = Blockchain.values() .filter { card.isTestCard == it.isTestnet() && it.isEvm() } .filter { it.isSupportedInApp() } From e72bf276da8ecbd7129d3b3a440b0519f4387679 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Nov 2022 18:08:46 +0300 Subject: [PATCH 091/176] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 10 +- .../com/tangem/tap/common/extensions/Store.kt | 6 + .../com/tangem/tap/common/redux/AppReducer.kt | 2 + .../com/tangem/tap/common/redux/AppState.kt | 4 +- .../redux/navigation/NavigationMiddleware.kt | 33 ++- .../redux/navigation/NavigationState.kt | 2 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 15 ++ .../com/tangem/tap/domain/model/UserWallet.kt | 34 +++ .../tap/domain/scanCard/ScanCardProcessor.kt | 233 ++++++++++++++++++ .../userWalletList/UserWalletsListManager.kt | 29 +++ .../di/UserWalletsListProvider.kt | 8 + .../DummyUserWalletsListManager.kt | 58 +++++ .../welcome/redux/WelcomeMiddleware.kt | 90 +++++++ .../features/welcome/redux/WelcomeReducer.kt | 30 +++ .../features/welcome/redux/WelcomeState.kt | 7 +- .../features/welcome/ui/WelcomeFragment.kt | 3 +- .../features/welcome/ui/WelcomeScreenState.kt | 4 +- .../features/welcome/ui/WelcomeViewModel.kt | 21 +- 18 files changed, 568 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListProvider.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt create mode 100644 app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 05e9eed54d..f89ccbeb0a 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -18,6 +18,7 @@ import com.tangem.tap.common.DialogManager import com.tangem.tap.common.IntentHandler import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -25,6 +26,8 @@ import com.tangem.tap.common.shop.GooglePayService import com.tangem.tap.common.shop.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import com.tangem.tap.domain.userWalletList.di.provideDummyImplementation import com.tangem.tap.features.shop.redux.ShopAction import com.tangem.tap.proxy.AppStateHolder import com.tangem.wallet.R @@ -40,6 +43,7 @@ import kotlin.coroutines.CoroutineContext lateinit var tangemSdk: TangemSdk lateinit var tangemSdkManager: TangemSdkManager lateinit var backupService: BackupService +lateinit var userWalletsListManager: UserWalletsListManager var notificationsHandler: NotificationsHandler? = null private val coroutineContext: CoroutineContext @@ -73,6 +77,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac tangemSdkManager = TangemSdkManager(tangemSdk, this) appStateHolder.tangemSdkManager = tangemSdkManager backupService = BackupService.init(tangemSdk, this) + userWalletsListManager = UserWalletsListManager.provideDummyImplementation() store.dispatch( ShopAction.CheckIfGooglePayAvailable( @@ -82,7 +87,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } private fun systemActions() { - WindowCompat.setDecorFitsSystemWindows(window, false) val windowInsetsController = WindowInsetsControllerCompat(window, binding.root) @@ -106,7 +110,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted val shopOpened = store.state.shopState.total != null if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) + val navigateTo = if (userWalletsListManager.hasSavedUserWallets) AppScreen.Welcome else AppScreen.Home + + store.dispatchOnMain(NavigationAction.NavigateTo(navigateTo)) } intentHandler.handleIntent(intent) } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index d88560669e..1919cd3dbf 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -6,6 +6,7 @@ import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.Dispatchers @@ -26,6 +27,11 @@ fun Store<*>.dispatchNotification(resId: Int) { dispatchOnMain(GlobalAction.ShowNotification(resId)) } +@Suppress("unused") // receiver type +fun Store<*>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) { + // TODO: Load tokens for selected user wallet. Will be created in further MRs +} + fun Store<*>.dispatchToastNotification(resId: Int) { dispatchOnMain(GlobalAction.ShowToastNotification(resId)) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index a14cdb9925..6e55404733 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -15,6 +15,7 @@ import com.tangem.tap.features.shop.redux.ShopReducer import com.tangem.tap.features.tokens.redux.TokensReducer import com.tangem.tap.features.wallet.redux.reducers.WalletReducer import com.tangem.tap.proxy.AppStateHolder +import com.tangem.tap.features.welcome.redux.WelcomeReducer import org.rekotlin.Action fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder): AppState { @@ -36,6 +37,7 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder) tokensState = TokensReducer.reduce(action, state), walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState), shopState = ShopReducer.reduce(action, state.shopState), + welcomeState = WelcomeReducer.reduce(action, state), ) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index a158f2c872..b8b4bf689a 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -34,8 +34,9 @@ import com.tangem.tap.features.tokens.redux.TokensMiddleware import com.tangem.tap.features.tokens.redux.TokensState import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware -import com.tangem.tap.features.welcome.redux.WelcomeState import com.tangem.tap.features.walletSelector.redux.WalletSelectorState +import com.tangem.tap.features.welcome.redux.WelcomeMiddleware +import com.tangem.tap.features.welcome.redux.WelcomeState import org.rekotlin.Middleware import org.rekotlin.StateType @@ -84,6 +85,7 @@ data class AppState( WalletConnectMiddleware().walletConnectMiddleware, BackupMiddleware().backupMiddleware, ShopMiddleware().shopMiddleware, + WelcomeMiddleware().middleware, ) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 8a2f5c5ac1..97d4c27134 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -2,13 +2,15 @@ package com.tangem.tap.common.redux.navigation import android.content.Intent import com.tangem.tap.common.CustomTabsManager +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.openFragment import com.tangem.tap.common.extensions.popBackTo import com.tangem.tap.common.extensions.shareText import com.tangem.tap.common.redux.AppState +import com.tangem.tap.store import org.rekotlin.Middleware -val navigationMiddleware: Middleware = { dispatch, state -> +val navigationMiddleware: Middleware = { _, state -> { next -> { action -> if (action is NavigationAction) { @@ -16,16 +18,24 @@ val navigationMiddleware: Middleware = { dispatch, state -> when (action) { is NavigationAction.NavigateTo -> { navState?.activity?.get()?.openFragment( - action.screen, - action.addToBackstack, - action.fragmentShareTransition + screen = action.screen, + addToBackstack = action.addToBackstack, + fgShareTransition = action.fragmentShareTransition, ) } is NavigationAction.PopBackTo -> { - if (action.screen == AppScreen.Home) { - navState?.activity?.get()?.popBackTo(null, true) - } else { - navState?.activity?.get()?.popBackTo(action.screen) + when (val screen = action.screen) { + AppScreen.Home, + AppScreen.Welcome, + -> { + navState?.activity?.get()?.popBackTo(screen = null, inclusive = true) + if (navState?.backStack?.contains(screen) != true) { + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + } + } + else -> { + navState?.activity?.get()?.popBackTo(screen = action.screen) + } } } is NavigationAction.OpenUrl -> { @@ -39,10 +49,11 @@ val navigationMiddleware: Middleware = { dispatch, state -> navState?.activity?.get()?.startActivity(intent) } is NavigationAction.Share -> { - navState?.activity?.get()?.let { - it.shareText(action.data) - } + navState?.activity?.get()?.shareText(action.data) } + is NavigationAction.ActivityCreated, + is NavigationAction.ActivityDestroyed, + -> Unit } } next(action) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt index e3d82a292a..cf60dc79f6 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationState.kt @@ -5,7 +5,7 @@ import org.rekotlin.StateType import java.lang.ref.WeakReference data class NavigationState( - val backStack: List = listOf(AppScreen.Home), + val backStack: List = emptyList(), val activity: WeakReference? = null, ) : StateType diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 66b16d1cad..b1f24b354d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -8,11 +8,13 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.CardFilter import com.tangem.common.CompletionResult import com.tangem.common.SuccessResponse +import com.tangem.common.UserCodeType import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.CardIdDisplayFormat import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.Config import com.tangem.common.core.TangemSdkError +import com.tangem.common.core.UserCodeRequestPolicy import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.hdWallet.DerivationPath import com.tangem.common.map @@ -46,7 +48,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co userTokensRepository: UserTokensRepository, additionalBlockchainsToDerive: Collection? = null, messageRes: Int? = null, + useBiometricsForAccessCode: Boolean = false, ): CompletionResult { + setAccessCodeRequestPolicy(useBiometricsForAccessCode) + val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) return runTaskAsyncReturnOnMain( runnable = ScanProductTask(null, userTokensRepository, additionalBlockchainsToDerive), @@ -175,6 +180,16 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return context.getString(stringResId, formatArgs) } + fun setAccessCodeRequestPolicy( + useBiometricsForAccessCode: Boolean, + ) { + tangemSdk.config.userCodeRequestPolicy = if (useBiometricsForAccessCode) { + UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode) + } else { + UserCodeRequestPolicy.Default + } + } + companion object { val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt new file mode 100644 index 0000000000..f649f4919f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -0,0 +1,34 @@ +package com.tangem.tap.domain.model + +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.util.UserWalletId +import com.tangem.domain.common.util.userWalletId +import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl + +data class UserWallet( + val name: String, + val walletId: UserWalletId, + val artworkUrl: String, + val cardsInWallet: Set, + val scanResponse: ScanResponse, +) { + val cardId: String + get() = scanResponse.card.cardId + + companion object { + suspend operator fun invoke( + scanResponse: ScanResponse, + backupCardsIds: Set? = null, + ): UserWallet { + return with(scanResponse) { + UserWallet( + walletId = card.userWalletId, + name = productType.name, + artworkUrl = card.getOrLoadCardArtworkUrl(), + cardsInWallet = backupCardsIds?.plus(card.cardId) ?: setOf(card.cardId), + scanResponse = this, + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt new file mode 100644 index 0000000000..954888733e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -0,0 +1,233 @@ +package com.tangem.tap.domain.scanCard + +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.core.TangemError +import com.tangem.common.core.TangemSdkError +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.services.Result +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.extensions.withMainContext +import com.tangem.operations.backup.BackupService +import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.backupService +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.IntroductionProcess +import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor +import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa +import com.tangem.tap.common.redux.AppDialog +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.disclaimer.redux.DisclaimerAction +import com.tangem.tap.features.disclaimer.redux.DisclaimerType +import com.tangem.tap.features.disclaimer.redux.isAccepted +import com.tangem.tap.features.onboarding.OnboardingHelper +import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep +import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler +import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction +import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userTokensRepository +import com.tangem.wallet.R +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +// TODO: Create repository for that +object ScanCardProcessor { + suspend fun scan( + useBiometricsForAccessCode: Boolean = false, + additionalBlockchainsToDerive: Collection? = null, + onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {}, + onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {}, + onWalletNotCreated: suspend (() -> Unit) = {}, + onFailure: suspend (error: TangemError) -> Unit = {}, + onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {}, + ) = withMainContext { + onProgressStateChange(true) + onScanStateChange(true) + tangemSdkManager.scanProduct( + userTokensRepository = userTokensRepository, + additionalBlockchainsToDerive = additionalBlockchainsToDerive, + useBiometricsForAccessCode = useBiometricsForAccessCode, + ) + .doOnFailure { error -> + onProgressStateChange(false) + onScanStateChange(false) + onFailure(error) + } + .doOnSuccess { scanResponse -> + onScanStateChange(false) + checkForUnfinishedBackupForSaltPay( + backupService = backupService, + scanResponse = scanResponse, + onProgressStateChange = { onProgressStateChange(it) }, + nextHandler = { + showDisclaimerIfNeed( + scanResponse = scanResponse, + nextHandler = { + onScanSuccess( + scanResponse = scanResponse, + onProgressStateChange = onProgressStateChange, + onSuccess = onSuccess, + onWalletNotCreated = onWalletNotCreated, + onFailure = onFailure, + ) + }, + ) + }, + ) + + } + } + + /** + * It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards + * see BackupAction.CheckForUnfinishedBackup + * If user touches card other than Visa SaltPay - show dialog and block next processing + */ + private inline fun checkForUnfinishedBackupForSaltPay( + backupService: BackupService, + scanResponse: ScanResponse, + onProgressStateChange: (showProgress: Boolean) -> Unit, + nextHandler: (ScanResponse) -> Unit, + ) { + if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { + nextHandler(scanResponse) + return + } + + val isTheSamePrimaryCard = backupService.primaryCardId + ?.let { it == scanResponse.card.cardId } + ?: false + + if (scanResponse.isSaltPayWallet() || !isTheSamePrimaryCard) { + onProgressStateChange(false) + showSaltPayTapVisaLogoCardDialog() + } else { + nextHandler(scanResponse) + } + } + + private suspend inline fun showDisclaimerIfNeed( + scanResponse: ScanResponse, + crossinline nextHandler: suspend (ScanResponse) -> Unit, + ) { + val disclaimerType = DisclaimerType.get(scanResponse) + store.dispatch(DisclaimerAction.SetDisclaimerType(disclaimerType)) + + if (disclaimerType.isAccepted()) { + nextHandler((scanResponse)) + } else scope.launch(Dispatchers.Main) { + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatch( + DisclaimerAction.Show { + scope.launch { + nextHandler(scanResponse) + } + }, + ) + } + } + + private suspend inline fun onScanSuccess( + scanResponse: ScanResponse, + crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + crossinline onWalletNotCreated: suspend () -> Unit, + crossinline onSuccess: suspend (ScanResponse) -> Unit, + crossinline onFailure: suspend (error: TangemError) -> Unit, + ) { + Analytics.send(IntroductionProcess.CardWasScanned()) + + val globalState = store.state.globalState + val tapWalletManager = globalState.tapWalletManager + tapWalletManager.updateConfigManager(scanResponse) + + Analytics.addParamsInterceptor(BatchIdParamsInterceptor(scanResponse.card.batchId)) + + store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) + + if (scanResponse.isSaltPay()) { + if (scanResponse.isSaltPayVisa()) { + val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) + val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) + delay(500) + withMainContext { + when (result) { + is Result.Success -> { + val isOnboardingCase = result.data + if (isOnboardingCase) { + onWalletNotCreated() + store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) + store.dispatch(OnboardingSaltPayAction.SetDependencies(manager, config)) + store.dispatch(OnboardingSaltPayAction.Update) + navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + onSuccess(scanResponse) + onProgressStateChange(false) + } + } + is Result.Failure -> { + SaltPayExceptionHandler.handle(result.error) + delay(DELAY_SDK_DIALOG_CLOSE) + onFailure(TangemSdkError.ExceptionError(result.error)) + onProgressStateChange(false) + } + } + } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + if (scanResponse.card.backupStatus?.isActive == false) { + showSaltPayTapVisaLogoCardDialog() + } else { + onSuccess(scanResponse) + } + onProgressStateChange(false) + } + } else { + if (OnboardingHelper.isOnboardingCase(scanResponse)) { + onWalletNotCreated() + store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) + val appScreen = OnboardingHelper.whereToNavigate(scanResponse) + navigateTo(appScreen) { onProgressStateChange(it) } + } else { + if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { + onWalletNotCreated() + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly)) + navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + onSuccess(scanResponse) + onProgressStateChange(false) + } + } + } + } + + private fun showSaltPayTapVisaLogoCardDialog() { + store.dispatchDialogShow( + AppDialog.SimpleOkDialogRes( + headerId = R.string.saltpay_error_empty_backup_title, + messageId = R.string.saltpay_error_empty_backup_message, + ), + ) + } + + private suspend inline fun navigateTo( + screen: AppScreen, + onProgressStateChange: (showProgress: Boolean) -> Unit, + ) { + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + onProgressStateChange(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt new file mode 100644 index 0000000000..05efbdf35f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -0,0 +1,29 @@ +package com.tangem.tap.domain.userWalletList + +import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.UserWallet +import kotlinx.coroutines.flow.Flow + +interface UserWalletsListManager { + val userWallets: Flow> + val selectedUserWallet: Flow + val selectedUserWalletSync: UserWallet? + val isLocked: Flow + val isLockedSync: Boolean + val hasSavedUserWallets: Boolean + + suspend fun unlockWithBiometry(): CompletionResult + suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult + fun lock() + + suspend fun selectWallet(walletId: UserWalletId): CompletionResult + + suspend fun save(userWallet: UserWallet): CompletionResult + suspend fun update(userWallet: UserWallet): CompletionResult + + suspend fun delete(walletIds: List): CompletionResult + suspend fun clear(): CompletionResult + + companion object +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListProvider.kt new file mode 100644 index 0000000000..f60c7f3de0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListProvider.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.domain.userWalletList.di + +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import com.tangem.tap.domain.userWalletList.implementation.DummyUserWalletsListManager + +fun UserWalletsListManager.Companion.provideDummyImplementation(): UserWalletsListManager { + return DummyUserWalletsListManager() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt new file mode 100644 index 0000000000..bcc6c05b7f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt @@ -0,0 +1,58 @@ +package com.tangem.tap.domain.userWalletList.implementation + +import com.tangem.common.CompletionResult +import com.tangem.common.catching +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +class DummyUserWalletsListManager : UserWalletsListManager { + override val userWallets: Flow> + get() = flowOf(emptyList()) + override val selectedUserWallet: Flow + get() = flowOf() + override val selectedUserWalletSync: UserWallet? + get() = null + override val isLocked: Flow + get() = flowOf(true) + override val isLockedSync: Boolean + get() = true + override val hasSavedUserWallets: Boolean + get() = false + + override suspend fun unlockWithBiometry(): CompletionResult { + return CompletionResult.Success(null) + } + + override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { + return CompletionResult.Success(Unit) + } + + override fun lock() { + /* no-op */ + } + + override suspend fun selectWallet(walletId: UserWalletId): CompletionResult { + return catching { + error("Not implemented") + } + } + + override suspend fun save(userWallet: UserWallet): CompletionResult { + return CompletionResult.Success(Unit) + } + + override suspend fun update(userWallet: UserWallet): CompletionResult { + return CompletionResult.Success(Unit) + } + + override suspend fun delete(walletIds: List): CompletionResult { + return CompletionResult.Success(Unit) + } + + override suspend fun clear(): CompletionResult { + return CompletionResult.Success(Unit) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt new file mode 100644 index 0000000000..eb7fca3d46 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -0,0 +1,90 @@ +package com.tangem.tap.features.welcome.redux + +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.onUserWalletSelected +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager +import kotlinx.coroutines.launch +import org.rekotlin.Middleware + +internal class WelcomeMiddleware { + val middleware: Middleware = { _, _ -> + { next -> + { action -> + if (action is WelcomeAction) { + handleAction(action) + } + next(action) + } + } + } + + private fun handleAction(action: WelcomeAction) { + when (action) { + is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometry() + is WelcomeAction.ProceedWithCard -> proceedWithCard() + is WelcomeAction.ProceedWithBiometrics.Error -> Unit + is WelcomeAction.ProceedWithCard.Error -> Unit + is WelcomeAction.ProceedWithBiometrics.Success -> Unit + is WelcomeAction.ProceedWithCard.Success -> Unit + is WelcomeAction.CloseError -> Unit + } + } + + private fun proceedWithBiometry() { + scope.launch { + userWalletsListManager.unlockWithBiometry() + .doOnFailure { error -> + store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Error(error)) + } + .doOnSuccess { selectedUserWallet -> + if (selectedUserWallet != null) { + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) + store.onUserWalletSelected(selectedUserWallet) + } + } + } + } + + private fun proceedWithCard() = scope.launch { + scanCardInternal { scanResponse -> + val userWallet = UserWallet(scanResponse) + + userWalletsListManager.unlockWithCard(userWallet) + .doOnFailure { error -> + store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error)) + } + .doOnSuccess { + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) + store.onUserWalletSelected(userWallet) + } + } + } + + private suspend inline fun scanCardInternal( + crossinline onCardScanned: suspend (ScanResponse) -> Unit, + ) { + ScanCardProcessor.scan( + onSuccess = { scanResponse -> + scope.launch { onCardScanned(scanResponse) } + }, + onFailure = { + store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(it)) + }, + onWalletNotCreated = { + store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) + }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt new file mode 100644 index 0000000000..d5f3e0276b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.features.welcome.redux + +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Action + +internal object WelcomeReducer { + fun reduce(action: Action, state: AppState): WelcomeState { + return if (action is WelcomeAction) { + internalReduce(action, state.welcomeState) + } else state.welcomeState + } + + private fun internalReduce(action: WelcomeAction, state: WelcomeState): WelcomeState { + return when (action) { + is WelcomeAction.ProceedWithBiometrics -> state.copy(isUnlockWithBiometricsInProgress = true) + is WelcomeAction.ProceedWithCard -> state.copy(isUnlockWithCardInProgress = true) + is WelcomeAction.ProceedWithBiometrics.Error -> state.copy( + error = action.error, + isUnlockWithBiometricsInProgress = false, + ) + is WelcomeAction.ProceedWithCard.Error -> state.copy( + error = action.error, + isUnlockWithCardInProgress = false, + ) + is WelcomeAction.ProceedWithBiometrics.Success -> state.copy(isUnlockWithBiometricsInProgress = false) + is WelcomeAction.ProceedWithCard.Success -> state.copy(isUnlockWithCardInProgress = false) + is WelcomeAction.CloseError -> state.copy(error = null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt index 2d89c4d902..a4fce23f93 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt @@ -1,5 +1,10 @@ package com.tangem.tap.features.welcome.redux +import com.tangem.common.core.TangemError import org.rekotlin.StateType -class WelcomeState : StateType \ No newline at end of file +data class WelcomeState( + val isUnlockWithBiometricsInProgress: Boolean = false, + val isUnlockWithCardInProgress: Boolean = false, + val error: TangemError? = null, +) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt index b80c8ac31b..07c9af833c 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt @@ -20,6 +20,7 @@ import androidx.fragment.app.viewModels import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.fragments.ComposeFragment import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent import com.tangem.wallet.R @@ -37,7 +38,7 @@ internal class WelcomeFragment : ComposeFragment() { state: WelcomeScreenState, ) { val snackbarHostState = remember { SnackbarHostState() } - val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage) + val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference()) val backgroundColor = colorResource(id = R.color.background_primary) SystemBarsEffect { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt index 7a741a391f..d0d883148a 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeScreenState.kt @@ -1,11 +1,11 @@ package com.tangem.tap.features.welcome.ui import androidx.compose.runtime.Immutable -import com.tangem.common.core.TangemError +import com.tangem.tap.features.details.ui.cardsettings.TextReference @Immutable internal data class WelcomeScreenState( val showUnlockWithBiometricsProgress: Boolean = false, val showUnlockWithCardProgress: Boolean = false, - val error: TangemError? = null, + val error: TextReference? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt index ab9e8e6673..a047d8518e 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt @@ -1,6 +1,8 @@ package com.tangem.tap.features.welcome.ui import androidx.lifecycle.ViewModel +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.tap.features.welcome.redux.WelcomeAction import com.tangem.tap.features.welcome.redux.WelcomeState import com.tangem.tap.store @@ -15,6 +17,7 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber { init { subscribeToStoreChanges() + initGlobalState() } fun unlockWallets() { @@ -31,8 +34,16 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber { override fun newState(state: WelcomeState) { stateInternal.update { prevState -> - // TODO: Update screen state - prevState + prevState.copy( + showUnlockWithBiometricsProgress = state.isUnlockWithBiometricsInProgress, + showUnlockWithCardProgress = state.isUnlockWithCardInProgress, + error = state.error + ?.takeUnless { it.silent } + ?.let { error -> + error.messageResId?.let { TextReference.Res(it) } + ?: TextReference.Str(error.customMessage) + }, + ) } } @@ -46,4 +57,10 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber { .select { it.welcomeState } } } + + private fun initGlobalState() { + store.dispatch(GlobalAction.RestoreAppCurrency) + store.dispatch(GlobalAction.ExchangeManager.Init) + store.dispatch(GlobalAction.FetchUserCountry) + } } \ No newline at end of file From 41dea25009771d79c85aaa408509760d5c8e17c9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Nov 2022 18:26:31 +0300 Subject: [PATCH 092/176] Updated on 2026-08-14 --- .../com/tangem/tap/ActivityResultCaller.kt | 8 ++ .../tangem/tap/ForegroundActivityObserver.kt | 32 ++++- .../java/com/tangem/tap/TapApplication.kt | 2 + .../com/tangem/tap/common/redux/AppReducer.kt | 2 + .../com/tangem/tap/common/redux/AppState.kt | 2 + .../redux/navigation/NavigationAction.kt | 2 + .../redux/navigation/NavigationMiddleware.kt | 26 ++++ .../com/tangem/tap/domain/TangemSdkManager.kt | 42 ++++-- .../com/tangem/tap/domain/model/UserWallet.kt | 3 + .../saveWallet/redux/SaveWalletAction.kt | 11 ++ .../saveWallet/redux/SaveWalletMiddleware.kt | 124 ++++++++++++++++++ .../saveWallet/redux/SaveWalletReducer.kt | 51 +++++++ .../saveWallet/redux/SaveWalletState.kt | 15 ++- .../ui/SaveWalletBottomSheetFragment.kt | 3 +- .../saveWallet/ui/SaveWalletScreenState.kt | 4 +- .../saveWallet/ui/SaveWalletViewModel.kt | 23 +++- .../tap/persistence/PreferencesStorage.kt | 21 +++ 17 files changed, 348 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/ActivityResultCaller.kt create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt diff --git a/app/src/main/java/com/tangem/tap/ActivityResultCaller.kt b/app/src/main/java/com/tangem/tap/ActivityResultCaller.kt new file mode 100644 index 0000000000..16a05117ac --- /dev/null +++ b/app/src/main/java/com/tangem/tap/ActivityResultCaller.kt @@ -0,0 +1,8 @@ +package com.tangem.tap + +import android.content.Intent +import androidx.activity.result.ActivityResultLauncher + +interface ActivityResultCaller { + val activityResultLauncher: ActivityResultLauncher? +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt index 806769df5f..9664f38966 100644 --- a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt +++ b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt @@ -2,11 +2,18 @@ package com.tangem.tap import android.app.Activity import android.app.Application.ActivityLifecycleCallbacks +import android.content.Intent import android.os.Bundle -import java.util.WeakHashMap +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import java.util.* import kotlin.reflect.KClass -class ForegroundActivityObserver { +class ForegroundActivityObserver : ActivityResultCaller { + override var activityResultLauncher: ActivityResultLauncher? = null + private set + private val activities = WeakHashMap, Activity>() val foregroundActivity: Activity? @@ -15,25 +22,38 @@ class ForegroundActivityObserver { .firstOrNull() ?.value - val callbacks get() = Callbacks() + internal val callbacks: ActivityLifecycleCallbacks + get() = Callbacks() + + internal inner class Callbacks : ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + activityResultLauncher = (activity as? AppCompatActivity)?.registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ) { + /* no-op */ + } + } - inner class Callbacks : ActivityLifecycleCallbacks { override fun onActivityResumed(activity: Activity) { activities[activity::class] = activity } override fun onActivityDestroyed(activity: Activity) { activities.remove(activity::class) + if (activities.isEmpty()) { + activityResultLauncher = null + } } - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - } override fun onActivityStarted(activity: Activity) { } + override fun onActivityPaused(activity: Activity) { } + override fun onActivityStopped(activity: Activity) { } + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { } } diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 287f2d7a1e..4b801de36a 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -53,6 +53,7 @@ import javax.inject.Inject lateinit var store: Store lateinit var foregroundActivityObserver: ForegroundActivityObserver +lateinit var activityResultCaller: ActivityResultCaller lateinit var preferencesStorage: PreferencesStorage lateinit var walletConnectRepository: WalletConnectRepository lateinit var shopService: TangemShopService @@ -82,6 +83,7 @@ class TapApplication : Application(), ImageLoaderFactory { } foregroundActivityObserver = ForegroundActivityObserver() + activityResultCaller = foregroundActivityObserver registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) initMoshiConverter() diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index 6e55404733..6d2bf6af62 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -10,6 +10,7 @@ import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteRedu import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer +import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer import com.tangem.tap.features.send.redux.reducers.SendScreenReducer import com.tangem.tap.features.shop.redux.ShopReducer import com.tangem.tap.features.tokens.redux.TokensReducer @@ -38,6 +39,7 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder) walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState), shopState = ShopReducer.reduce(action, state.shopState), welcomeState = WelcomeReducer.reduce(action, state), + saveWalletState = SaveWalletReducer.reduce(action, state), ) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index b8b4bf689a..d85b69df46 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -25,6 +25,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayMiddleware +import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware import com.tangem.tap.features.saveWallet.redux.SaveWalletState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware import com.tangem.tap.features.send.redux.states.SendState @@ -86,6 +87,7 @@ data class AppState( BackupMiddleware().backupMiddleware, ShopMiddleware().shopMiddleware, WelcomeMiddleware().middleware, + SaveWalletMiddleware().middleware, ) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt index e397d78d8a..df26b85edc 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt @@ -18,6 +18,8 @@ sealed class NavigationAction : Action { data class OpenDocument(val url: Uri) : NavigationAction() + object OpenBiometricsSettings : NavigationAction() + data class Share(val data: String) : NavigationAction() data class ActivityCreated(val activity: WeakReference) : NavigationAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 97d4c27134..da9430b1e1 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -1,6 +1,10 @@ package com.tangem.tap.common.redux.navigation import android.content.Intent +import android.hardware.biometrics.BiometricManager +import android.os.Build +import android.provider.Settings +import com.tangem.tap.activityResultCaller import com.tangem.tap.common.CustomTabsManager import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.openFragment @@ -48,6 +52,28 @@ val navigationMiddleware: Middleware = { _, state -> intent.data = action.url navState?.activity?.get()?.startActivity(intent) } + is NavigationAction.OpenBiometricsSettings -> { + val settingsAction = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { + Settings.ACTION_BIOMETRIC_ENROLL + } + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> { + Settings.ACTION_FINGERPRINT_ENROLL + } + else -> { + Settings.ACTION_SETTINGS + } + } + val intent = Intent(settingsAction).apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + putExtra( + Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, + BiometricManager.Authenticators.BIOMETRIC_STRONG, + ) + } + } + activityResultCaller.activityResultLauncher?.launch(intent) + } is NavigationAction.Share -> { navState?.activity?.get()?.shareText(action.data) } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index b1f24b354d..b7a54c7d6d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -5,19 +5,12 @@ import androidx.annotation.StringRes import com.tangem.Message import com.tangem.TangemSdk import com.tangem.blockchain.common.Blockchain -import com.tangem.common.CardFilter -import com.tangem.common.CompletionResult -import com.tangem.common.SuccessResponse -import com.tangem.common.UserCodeType +import com.tangem.common.* import com.tangem.common.card.FirmwareVersion -import com.tangem.common.core.CardIdDisplayFormat -import com.tangem.common.core.CardSessionRunnable -import com.tangem.common.core.Config -import com.tangem.common.core.TangemSdkError -import com.tangem.common.core.UserCodeRequestPolicy +import com.tangem.common.core.* import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.hdWallet.DerivationPath -import com.tangem.common.map +import com.tangem.common.usersCode.UserCodeRepository import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.operations.CommandResponse @@ -43,6 +36,11 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { + val canUseBiometry: Boolean + get() = tangemSdk.biometricManager.canAuthenticate || canEnrollBiometrics + + val canEnrollBiometrics: Boolean + get() = tangemSdk.biometricManager.canEnrollBiometrics suspend fun scanProduct( userTokensRepository: UserTokensRepository, @@ -104,6 +102,16 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co .map { CardDTO(it) } } + suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { + return createUserCodeRepository().save( + cardIds = cardsIds, + userCode = UserCode( + type = UserCodeType.AccessCode, + stringValue = accessCode, + ), + ) + } + suspend fun setPasscode(cardId: String?): CompletionResult { return runTaskAsyncReturnOnMain( SetUserCodeCommand.changePasscode(null), @@ -136,9 +144,14 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co ) } - suspend fun scanCard(): CompletionResult { + suspend fun scanCard( + cardId: String? = null, + useBiometricsForAccessCode: Boolean = false, + ): CompletionResult { + setAccessCodeRequestPolicy(useBiometricsForAccessCode) return runTaskAsyncReturnOnMain( runnable = ScanTask(), + cardId = cardId, initialMessage = Message(context.getString(R.string.initial_message_tap_header)), ) .map { CardDTO(it) } @@ -190,6 +203,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } + private fun createUserCodeRepository() = with(tangemSdk) { + UserCodeRepository( + biometricManager = biometricManager, + secureStorage = secureStorage, + ) + } + companion object { val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index f649f4919f..6ce60fae85 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -15,6 +15,9 @@ data class UserWallet( val cardId: String get() = scanResponse.card.cardId + val hasAccessCode: Boolean + get() = scanResponse.card.isAccessCodeSet + companion object { suspend operator fun invoke( scanResponse: ScanResponse, diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt index 3e0bbe4a7e..46581d4432 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt @@ -1,9 +1,16 @@ package com.tangem.tap.features.saveWallet.redux import com.tangem.common.core.TangemError +import com.tangem.domain.common.ScanResponse import org.rekotlin.Action internal sealed interface SaveWalletAction : Action { + data class ProvideAdditionalInfo( + val scanResponse: ScanResponse, + val accessCode: String?, + val backupCardsIds: Set?, + ) : SaveWalletAction + object Save : SaveWalletAction { object Success : SaveWalletAction data class Error(val error: TangemError) : SaveWalletAction @@ -12,6 +19,10 @@ internal sealed interface SaveWalletAction : Action { object Dismiss : SaveWalletAction object CloseError : SaveWalletAction + object EnrollBiometrics : SaveWalletAction { + object Enroll : SaveWalletAction + object Cancel : SaveWalletAction + } object SaveWalletWasShown : SaveWalletAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt new file mode 100644 index 0000000000..844b55b115 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -0,0 +1,124 @@ +package com.tangem.tap.features.saveWallet.redux + +import com.tangem.common.CompletionResult +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.flatMap +import com.tangem.common.map +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.onUserWalletSelected +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager +import kotlinx.coroutines.launch +import org.rekotlin.Middleware + +internal class SaveWalletMiddleware { + val middleware: Middleware = { _, stateProvider -> + { next -> + { action -> + val state = stateProvider() + if (action is SaveWalletAction && state != null) { + handleAction(action, state.saveWalletState) + } + next(action) + } + } + } + + private fun handleAction(action: SaveWalletAction, state: SaveWalletState) { + when (action) { + is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state) + is SaveWalletAction.Save.Success -> popBack() + is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics() + is SaveWalletAction.SaveWalletWasShown -> saveWalletWasShown() + is SaveWalletAction.ProvideAdditionalInfo, + is SaveWalletAction.Dismiss, + is SaveWalletAction.CloseError, + is SaveWalletAction.Save.Error, + is SaveWalletAction.EnrollBiometrics, + is SaveWalletAction.EnrollBiometrics.Cancel, + -> Unit + } + } + + private fun enrollBiometrics() { + store.dispatchOnMain(NavigationAction.OpenBiometricsSettings) + } + + private fun saveWalletIfBiometricsEnrolled(state: SaveWalletState) { + if (tangemSdkManager.canEnrollBiometrics) { + store.dispatchOnMain(SaveWalletAction.EnrollBiometrics) + } else { + saveWallet(state) + } + } + + private fun saveWallet(state: SaveWalletState) { + val scanResponse = state.additionalInfo?.scanResponse + ?: store.state.globalState.scanResponse + ?: return + + scope.launch { + val userWallet = UserWallet( + scanResponse = scanResponse, + backupCardsIds = state.additionalInfo?.backupCardsIds, + ) + + userWalletsListManager.save(userWallet) + .flatMap { + trySaveAccessCode( + userWallet = userWallet, + additionalInfo = state.additionalInfo, + ) + } + .doOnFailure { error -> + store.dispatchOnMain(SaveWalletAction.Save.Error(error)) + } + .doOnSuccess { + preferencesStorage.shouldSaveUserWallets = true + preferencesStorage.shouldSaveAccessCodes = true + store.dispatchOnMain(SaveWalletAction.Save.Success) + store.onUserWalletSelected(userWallet) + } + } + } + + private fun popBack() { + store.dispatchOnMain(NavigationAction.PopBackTo()) + } + + private fun saveWalletWasShown() { + preferencesStorage.shouldShowSaveWallet = false + } + + private suspend fun trySaveAccessCode( + userWallet: UserWallet, + additionalInfo: SaveWalletState.WalletAdditionalInfo?, + ): CompletionResult { + return when { + additionalInfo?.accessCode != null -> { + tangemSdkManager.saveAccessCode( + accessCode = additionalInfo.accessCode, + cardsIds = userWallet.cardsInWallet, + ) + } + userWallet.hasAccessCode -> { + tangemSdkManager + .scanCard( + cardId = userWallet.cardId, + useBiometricsForAccessCode = true, + ) + .map { /* no-op */ } + } + else -> { + CompletionResult.Success(Unit) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt new file mode 100644 index 0000000000..9907306ea6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt @@ -0,0 +1,51 @@ +package com.tangem.tap.features.saveWallet.redux + +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Action + +internal object SaveWalletReducer { + fun reduce(action: Action, state: AppState): SaveWalletState { + return if (action is SaveWalletAction) { + internalReduce(action, state.saveWalletState) + } else state.saveWalletState + } + + private fun internalReduce(action: SaveWalletAction, state: SaveWalletState): SaveWalletState { + return when (action) { + is SaveWalletAction.ProvideAdditionalInfo -> state.copy( + additionalInfo = SaveWalletState.WalletAdditionalInfo( + scanResponse = action.scanResponse, + accessCode = action.accessCode, + backupCardsIds = action.backupCardsIds, + ), + ) + is SaveWalletAction.Save -> state.copy( + isSaveInProgress = true, + ) + is SaveWalletAction.Save.Error -> state.copy( + error = action.error, + isSaveInProgress = false, + ) + is SaveWalletAction.Save.Success -> state.copy( + additionalInfo = null, + isSaveInProgress = false, + ) + is SaveWalletAction.Dismiss -> state.copy( + additionalInfo = null, + ) + is SaveWalletAction.CloseError -> state.copy( + error = null, + ) + is SaveWalletAction.EnrollBiometrics -> state.copy( + needEnrollBiometrics = true, + ) + is SaveWalletAction.EnrollBiometrics.Enroll, + is SaveWalletAction.EnrollBiometrics.Cancel, + -> state.copy( + needEnrollBiometrics = false, + isSaveInProgress = false, + ) + is SaveWalletAction.SaveWalletWasShown -> state + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt index d43ee7c96b..50cb237ff2 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt @@ -1,5 +1,18 @@ package com.tangem.tap.features.saveWallet.redux +import com.tangem.common.core.TangemError +import com.tangem.domain.common.ScanResponse import org.rekotlin.StateType -class SaveWalletState : StateType \ No newline at end of file +data class SaveWalletState( + val additionalInfo: WalletAdditionalInfo? = null, + val isSaveInProgress: Boolean = false, + val needEnrollBiometrics: Boolean = false, + val error: TangemError? = null, +) : StateType { + data class WalletAdditionalInfo( + val scanResponse: ScanResponse, + val accessCode: String?, + val backupCardsIds: Set?, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt index 6abc3c607e..65da3e8dc5 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -20,6 +20,7 @@ import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent import com.tangem.core.ui.fragments.ComposeBottomSheetFragment import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() { @@ -43,7 +44,7 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment - // TODO: Update screen state - prevState + prevState.copy( + showProgress = state.isSaveInProgress, + enrollBiometricsDialog = if (state.needEnrollBiometrics) createEnrollBiometricsDialog() else null, + error = state.error + ?.takeUnless { it.silent } + ?.let { error -> + error.messageResId?.let { TextReference.Res(it) } + ?: TextReference.Str(error.customMessage) + }, + ) } } @@ -48,4 +58,13 @@ internal class SaveWalletViewModel : ViewModel(), StoreSubscriber Date: Thu, 1 Dec 2022 14:54:01 +0300 Subject: [PATCH 093/176] Updated on 2026-08-14 --- .../ui/WalletSelectorBottomSheetFragment.kt | 17 +-- .../ui/WalletSelectorScreenState.kt | 10 +- .../ui/WalletSelectorViewModel.kt | 3 +- .../walletSelector/ui/components/MockData.kt | 30 +++-- .../WallectSelectorScreenContent.kt | 122 ++++++++++++------ .../ui/components/WalletList.kt | 66 ---------- .../walletSelector/ui/model/UserWalletItem.kt | 62 +++++---- 7 files changed, 148 insertions(+), 162 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletList.kt diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index 47c6ca9bf2..3637f2ea5e 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -5,15 +5,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.* import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.fragment.app.viewModels import com.tangem.core.ui.fragments.ComposeBottomSheetFragment import com.tangem.core.ui.res.TangemTheme @@ -29,6 +26,7 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment = listOf(), + val multiCurrencyWallets: List = emptyList(), + val singleCurrencyWallets: List = emptyList(), val selectedWalletId: String? = null, - val fiatCurrency: FiatCurrency = FiatCurrency.Default, - val isLocked: Boolean = true, + val isLocked: Boolean = false, val editingWalletsIds: List = listOf(), val renameWalletDialog: RenameWalletDialog? = null, val showAddCardProgress: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index cb6d75b0b0..fcb8f32c7e 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -60,7 +60,8 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber, + singleCurrencyWallets: List, + selectedWalletId: String?, + checkedWalletIds: List, + isLocked: Boolean, + onWalletClick: (walletId: String) -> Unit, + onWalletLongClick: (walletId: String) -> Unit, +) { + Column(modifier = modifier) { + val walletsSection = @Composable { wallets: List -> + wallets.forEachIndexed { index, wallet -> + if (index == 0) { + Text( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), + text = wallet.headerText.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + } + WalletItem( + modifier = Modifier + .combinedClickable( + onClick = { onWalletClick(wallet.id) }, + onLongClick = { onWalletLongClick(wallet.id) }, + ) + .padding(all = TangemTheme.dimens.spacing16), + wallet = wallet, + isSelected = wallet.id == selectedWalletId, + isChecked = wallet.id in checkedWalletIds, + isLocked = isLocked && wallet.id != selectedWalletId, + ) + } + } + + walletsSection(multiCurrencyWallets) + walletsSection(singleCurrencyWallets) + } +} + // region Preview @Composable private fun WalletSelectorScreenContentSample( @@ -221,7 +263,7 @@ private fun WalletSelectorScreenContent_EditWallets_Sample( shape = TangemTheme.shapes.bottomSheet, ), state = MockData.state - .copy(editingWalletsIds = listOf(MockData.userWalletList[2].id)), + .copy(editingWalletsIds = listOf(MockData.state.multiCurrencyWallets[2].id)), onWalletClick = { /* no-op */ }, onWalletLongClick = { /* no-op */ }, onUnlockClick = { /* no-op */ }, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletList.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletList.kt deleted file mode 100644 index ab1f4f9c40..0000000000 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletList.kt +++ /dev/null @@ -1,66 +0,0 @@ -@file:OptIn(ExperimentalFoundationApi::class) - -package com.tangem.tap.features.walletSelector.ui.components - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.features.details.ui.cardsettings.resolveReference -import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem - -@Composable -internal fun WalletList( - modifier: Modifier = Modifier, - wallets: List, - selectedWalletId: String?, - checkedWalletIds: List, - isLocked: Boolean, - onWalletClick: (walletId: String) -> Unit, - onWalletLongClick: (walletId: String) -> Unit, -) { - val multiCurrencyWallets = remember(wallets) { - wallets.filter { it.type is UserWalletItem.Type.MultiCurrency } - } - val singleCurrencyWallets = remember(wallets) { - wallets.filter { it.type is UserWalletItem.Type.SingleCurrency } - } - LazyColumn(modifier = modifier) { - val walletSection: (List) -> Unit = { wallets -> - itemsIndexed( - items = wallets, - key = { _, item -> item.id }, - ) { index, wallet -> - if (index == 0) { - Text( - modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), - text = wallet.type.headerText.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - ) - } - WalletItem( - modifier = Modifier - .combinedClickable( - onClick = { onWalletClick(wallet.id) }, - onLongClick = { onWalletLongClick(wallet.id) }, - ) - .padding(all = TangemTheme.dimens.spacing16), - wallet = wallet, - isSelected = wallet.id == selectedWalletId, - isChecked = wallet.id in checkedWalletIds, - isLocked = isLocked && wallet.id != selectedWalletId, - ) - } - } - - walletSection(multiCurrencyWallets) - walletSection(singleCurrencyWallets) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt index 41f0ace5cd..cadbde80db 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt @@ -5,42 +5,38 @@ import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R @Immutable -internal data class UserWalletItem( - val id: String, - val name: String, - val imageUrl: String, - val balance: Balance, - val type: Type, -) { +internal sealed interface UserWalletItem { + val id: String + val name: String + val imageUrl: String + val balance: Balance + + val headerText: TextReference + get() = when (this) { + is MultiCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_multi_header) + is SingleCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_single_header) + } + @Immutable data class Balance( val amount: String, val isLoading: Boolean, - ) { - companion object { - val Loading = Balance( - amount = "0.00", - isLoading = true, - ) - } - } + ) +} - sealed interface Type { - val headerText: TextReference +internal data class MultiCurrencyUserWalletItem( + override val id: String, + override val name: String, + override val imageUrl: String, + override val balance: UserWalletItem.Balance, + val tokensCount: Int, + val cardsInWallet: Int, +) : UserWalletItem - @Immutable - class MultiCurrency( - val tokensCount: Int, - val cardsInWallet: Int, - ) : Type { - override val headerText: TextReference - get() = TextReference.Res(R.string.user_wallet_list_multi_header) - } - - @Immutable - class SingleCurrency(val tokenName: String) : Type { - override val headerText: TextReference - get() = TextReference.Res(R.string.user_wallet_list_single_header) - } - } -} \ No newline at end of file +internal data class SingleCurrencyUserWalletItem( + override val id: String, + override val name: String, + override val imageUrl: String, + override val balance: UserWalletItem.Balance, + val tokenName: String, +) : UserWalletItem \ No newline at end of file From b1a901d127b739ccaf965d90a5b0becb53bfef13 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Dec 2022 14:55:03 +0300 Subject: [PATCH 094/176] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../ui/components/WalletItem.kt | 404 +++++++++++------- .../com/tangem/core/ui/res/TangemDimens.kt | 2 +- 3 files changed, 260 insertions(+), 147 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d1e7b40573..37bc6d5ed2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -192,6 +192,7 @@ dependencies { implementation(Library.viewBindingDelegate) implementation(Library.armadillo) implementation(Library.googlePlayServicesWallet) + implementation(Library.composeShimmer) /** Testing libraries */ testImplementation(Test.junit) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index 6837d47a7f..f33664bbfb 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -1,18 +1,8 @@ -@file:OptIn(ExperimentalComposeUiApi::class) - package com.tangem.tap.features.walletSelector.ui.components import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.Text @@ -34,8 +24,11 @@ import com.tangem.core.ui.components.SpacerW6 import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.compose.TangemTypography +import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem +import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem import com.tangem.wallet.R +import com.valentinilk.shimmer.shimmer @Composable internal fun WalletItem( @@ -48,157 +41,276 @@ internal fun WalletItem( Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically, + ) { + WalletCardImage( + cardImageUrl = wallet.imageUrl, + isChecked = isChecked, + isSelected = isSelected, + ) + SpacerW8() + WalletInfo( + modifier = Modifier.weight(weight = .6f), + wallet = wallet, + isSelected = isSelected, + ) + SpacerW6() + TokensInfo( + modifier = Modifier.weight(weight = .4f), + isLocked = isLocked, + balance = wallet.balance, + tokensCount = (wallet as? MultiCurrencyUserWalletItem)?.tokensCount, + ) + } +} + +@Composable +private fun WalletCardImage( + modifier: Modifier = Modifier, + cardImageUrl: String, + isChecked: Boolean, + isSelected: Boolean, +) { + Box( + modifier = modifier + .width(TangemTheme.dimens.size62) + .height(TangemTheme.dimens.size42), + ) { + val cardImageModifier = Modifier + .align(Alignment.Center) + .width(TangemTheme.dimens.size56) + .height(TangemTheme.dimens.size32) + .clip(TangemTheme.shapes.roundedCornersSmall) + + SubcomposeAsyncImage( + modifier = cardImageModifier, + model = ImageRequest.Builder(LocalContext.current) + .data(cardImageUrl) + .crossfade(true) + .build(), + loading = { CardImageShimmer(modifier = cardImageModifier) }, + error = { CardImagePlaceholder(modifier = cardImageModifier) }, + contentDescription = null, + ) + + when { + isChecked -> { + Box( + modifier = cardImageModifier + .background( + color = TangemTheme.colors.icon.accent.copy(alpha = .5f), + ), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + tint = TangemTheme.colors.icon.primary2, + contentDescription = null, + ) + } + } + isSelected -> { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .size(TangemTheme.dimens.size18) + .background( + color = Color.White, + shape = CircleShape, + ), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier + .padding(all = (0.5).dp), + painter = painterResource(id = R.drawable.ic_check_circle_18), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + } + } + else -> Unit + } + } +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +private fun WalletInfo( + modifier: Modifier = Modifier, + wallet: UserWalletItem, + isSelected: Boolean, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.SpaceAround, + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = wallet.name, + color = if (isSelected) TangemTheme.colors.text.accent else TangemTheme.colors.text.primary1, + style = TangemTypography.subtitle1, + ) + SpacerH2() + Text( + modifier = Modifier.fillMaxWidth(), + text = when (wallet) { + is MultiCurrencyUserWalletItem -> pluralStringResource( + id = R.plurals.card_label_card_count, + count = wallet.cardsInWallet, + wallet.cardsInWallet, + ) + is SingleCurrencyUserWalletItem -> wallet.tokenName + }, + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.tertiary, + ) + } +} + +@Composable +private fun TokensInfo( + modifier: Modifier = Modifier, + isLocked: Boolean, + balance: UserWalletItem.Balance, + tokensCount: Int?, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.SpaceAround, + horizontalAlignment = Alignment.End, + ) { + if (isLocked) { + LockedPlaceholder() + } else { + if (balance.isLoading) { + LoadingTokensInfo( + isMultiCurrencyWallet = tokensCount != null, + ) + } else { + LoadedTokensInfo( + balanceAmount = balance.amount, + tokensCount = tokensCount, + ) + } + } + } +} + +@Composable +private fun LoadingTokensInfo( + modifier: Modifier = Modifier, + isMultiCurrencyWallet: Boolean, +) { + Column( + modifier = modifier.shimmer(), + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.SpaceAround, ) { Box( modifier = Modifier .width(TangemTheme.dimens.size62) - .height(TangemTheme.dimens.size42), - ) { - val cardImageModifier = Modifier - .align(Alignment.Center) - .width(TangemTheme.dimens.size56) - .height(TangemTheme.dimens.size32) - .clip(TangemTheme.shapes.roundedCornersSmall) - SubcomposeAsyncImage( - modifier = cardImageModifier, - model = ImageRequest.Builder(LocalContext.current) - .data(wallet.imageUrl) - .crossfade(true) - .build(), - loading = { CardImagePlaceholder(modifier = cardImageModifier) }, - error = { CardImagePlaceholder(modifier = cardImageModifier) }, - contentDescription = null, - ) - when { - isChecked -> { - Box( - modifier = cardImageModifier - .background( - color = TangemTheme.colors.icon.accent.copy(alpha = .5f), - ), - contentAlignment = Alignment.Center, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_check_24), - tint = TangemTheme.colors.icon.primary2, - contentDescription = null, - ) - } - } - isSelected -> { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .size(TangemTheme.dimens.size18) - .background( - color = Color.White, - shape = CircleShape, - ), - contentAlignment = Alignment.Center, - ) { - Icon( - modifier = Modifier - .padding(all = (0.5).dp), - painter = painterResource(id = R.drawable.ic_check_circle_18), - tint = TangemTheme.colors.icon.accent, - contentDescription = null, - ) - } - } - else -> Unit - } - } - SpacerW8() - Column( - modifier = Modifier.weight(weight = .6f), - verticalArrangement = Arrangement.SpaceAround, - ) { - Text( - modifier = Modifier.fillMaxWidth(), - text = wallet.name, - color = if (isSelected) TangemTheme.colors.text.accent else TangemTheme.colors.text.primary1, - style = TangemTypography.subtitle1, - ) + .height(TangemTheme.dimens.size16) + .background( + color = TangemTheme.colors.button.secondary, + shape = TangemTheme.shapes.roundedCornersSmall2, + ), + ) + if (isMultiCurrencyWallet) { SpacerH2() - Text( - modifier = Modifier.fillMaxWidth(), - text = when (val type = wallet.type) { - is UserWalletItem.Type.MultiCurrency -> pluralStringResource( - id = R.plurals.card_label_card_count, - count = type.cardsInWallet, - type.cardsInWallet, - ) - - is UserWalletItem.Type.SingleCurrency -> type.tokenName - }, - style = TangemTheme.typography.caption, - color = TangemTheme.colors.text.tertiary, - ) - } - SpacerW6() - if (isLocked) { Box( modifier = Modifier - .padding(vertical = TangemTheme.dimens.spacing2) + .width(TangemTheme.dimens.size48) + .height(TangemTheme.dimens.size12) .background( color = TangemTheme.colors.button.secondary, - shape = TangemTheme.shapes.roundedCornersMedium, + shape = TangemTheme.shapes.roundedCornersSmall2, ), - ) { - Icon( - modifier = Modifier - .padding( - vertical = TangemTheme.dimens.spacing8, - horizontal = TangemTheme.dimens.spacing12, - ) - .size(TangemTheme.dimens.size20), - painter = painterResource(id = R.drawable.ic_locked_24), - tint = TangemTheme.colors.icon.primary1, - contentDescription = null, - ) - } - } else { - Column( - modifier = Modifier.weight(weight = .4f), - verticalArrangement = Arrangement.SpaceAround, - ) { - Text( - modifier = Modifier.fillMaxWidth(), - text = wallet.balance.amount, - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.End, - ) - when (val type = wallet.type) { - is UserWalletItem.Type.MultiCurrency -> { - SpacerH2() - Text( - modifier = Modifier.fillMaxWidth(), - text = pluralStringResource( - id = R.plurals.tokens_count, - count = type.tokensCount, - type.tokensCount, - ), - style = TangemTheme.typography.caption, - color = TangemTheme.colors.text.tertiary, - textAlign = TextAlign.End, - ) - } - - is UserWalletItem.Type.SingleCurrency -> Unit - } - } + ) } } } +@OptIn(ExperimentalComposeUiApi::class) +@Composable +private fun LoadedTokensInfo( + modifier: Modifier = Modifier, + balanceAmount: String, + tokensCount: Int?, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.End, + ) { + Text( + text = balanceAmount, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.End, + ) + if (tokensCount != null) { + SpacerH2() + Text( + text = pluralStringResource( + id = R.plurals.tokens_count, + count = tokensCount, + tokensCount, + ), + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.End, + ) + } + } +} + +@Composable +private fun LockedPlaceholder( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .padding(vertical = TangemTheme.dimens.spacing2) + .background( + color = TangemTheme.colors.button.secondary, + shape = TangemTheme.shapes.roundedCornersMedium, + ), + ) { + Icon( + modifier = Modifier + .padding( + vertical = TangemTheme.dimens.spacing8, + horizontal = TangemTheme.dimens.spacing12, + ) + .size(TangemTheme.dimens.size20), + painter = painterResource(id = R.drawable.ic_locked_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + } +} + @Composable private fun CardImagePlaceholder( modifier: Modifier = Modifier, ) { - Image( - modifier = modifier, - painter = painterResource(id = R.drawable.card_placeholder_black), - contentDescription = null, - ) + Box(modifier = modifier) { + Image( + modifier = Modifier.matchParentSize(), + painter = painterResource(id = R.drawable.card_placeholder_black), + contentDescription = null, + ) + } +} + +@Composable +private fun CardImageShimmer( + modifier: Modifier = Modifier, +) { + Box(modifier = modifier.shimmer()) { + Box( + modifier = Modifier + .matchParentSize() + .background(TangemTheme.colors.button.secondary), + ) + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 731400086c..5b111b5e96 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -31,7 +31,7 @@ data class TangemDimens internal constructor( val size0: Dp = 0.dp, val size4: Dp = 4.dp, val size8: Dp = 8.dp, - val size11: Dp = 11.dp, + val size12: Dp = 12.dp, val size16: Dp = 16.dp, val size18: Dp = 18.dp, val size20: Dp = 20.dp, From d3e3f5cfd5459a15802d22f9a7059ec6026bfc86 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Dec 2022 21:02:04 +0300 Subject: [PATCH 095/176] Updated on 2026-08-14 --- .../ui/components/WalletItem.kt | 69 +++++++++---------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index f33664bbfb..c95d59deec 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -7,11 +7,15 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource @@ -79,7 +83,16 @@ private fun WalletCardImage( .align(Alignment.Center) .width(TangemTheme.dimens.size56) .height(TangemTheme.dimens.size32) - .clip(TangemTheme.shapes.roundedCornersSmall) + .clip(TangemTheme.shapes.roundedCornersSmall2) + + val checkedColorFilter by rememberUpdatedState( + newValue = if (isChecked) { + ColorFilter.tint( + color = TangemTheme.colors.icon.accent.copy(alpha = .6f), + blendMode = BlendMode.SrcOver, + ) + } else null, + ) SubcomposeAsyncImage( modifier = cardImageModifier, @@ -89,46 +102,28 @@ private fun WalletCardImage( .build(), loading = { CardImageShimmer(modifier = cardImageModifier) }, error = { CardImagePlaceholder(modifier = cardImageModifier) }, + colorFilter = checkedColorFilter, contentDescription = null, ) - when { - isChecked -> { - Box( - modifier = cardImageModifier - .background( - color = TangemTheme.colors.icon.accent.copy(alpha = .5f), - ), - contentAlignment = Alignment.Center, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_check_24), - tint = TangemTheme.colors.icon.primary2, - contentDescription = null, - ) - } + if (isSelected) { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .size(TangemTheme.dimens.size18) + .background( + color = Color.White, + shape = CircleShape, + ), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.padding(all = (0.5).dp), + painter = painterResource(id = R.drawable.ic_check_circle_18), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) } - isSelected -> { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .size(TangemTheme.dimens.size18) - .background( - color = Color.White, - shape = CircleShape, - ), - contentAlignment = Alignment.Center, - ) { - Icon( - modifier = Modifier - .padding(all = (0.5).dp), - painter = painterResource(id = R.drawable.ic_check_circle_18), - tint = TangemTheme.colors.icon.accent, - contentDescription = null, - ) - } - } - else -> Unit } } } From a0f1a6591cc687aab4d44238ca4861374fd9dd99 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Dec 2022 14:40:31 +0300 Subject: [PATCH 096/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 12 + .../com/tangem/tap/common/redux/AppReducer.kt | 2 + .../com/tangem/tap/common/redux/AppState.kt | 2 + .../tap/domain/model/TotalFiatBalance.kt | 25 +++ .../com/tangem/tap/domain/model/UserWallet.kt | 32 +-- .../tap/domain/model/WalletDataModel.kt | 79 +++++++ .../tap/domain/model/WalletStoreModel.kt | 36 +++ .../model/builders/UserWalletBuilder.kt | 30 +++ .../TotalFiatBalanceCalculator.kt | 14 ++ .../di/TotalFiatBalanceCalculatorProvider.kt | 8 + .../DefaultTotalFiatBalanceCalculator.kt | 102 +++++++++ .../userWalletList/UserWalletsListManager.kt | 2 + ...r.kt => UserWalletsListManagerProvider.kt} | 0 .../DummyUserWalletsListManager.kt | 6 + .../walletStores/WalletStoresManager.kt | 27 +++ .../di/WalletsStoresManagerProvider.kt | 8 + .../DummyWalletStoresManager.kt | 35 +++ .../saveWallet/redux/SaveWalletMiddleware.kt | 17 +- .../walletSelector/redux/UserWalletModel.kt | 22 ++ .../redux/WalletSelectorAction.kt | 29 +++ .../redux/WalletSelectorMiddleware.kt | 212 ++++++++++++++++++ .../redux/WalletSelectorReducer.kt | 107 +++++++++ .../redux/WalletSelectorState.kt | 12 +- .../tap/features/walletSelector/ui/Mapper.kt | 76 +++++++ .../ui/WalletSelectorBottomSheetFragment.kt | 3 +- .../ui/WalletSelectorScreenState.kt | 4 +- .../ui/WalletSelectorViewModel.kt | 43 +++- .../welcome/redux/WelcomeMiddleware.kt | 4 +- 28 files changed, 905 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/totalBalance/TotalFiatBalanceCalculator.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/totalBalance/di/TotalFiatBalanceCalculatorProvider.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt rename app/src/main/java/com/tangem/tap/domain/userWalletList/di/{UserWalletsListProvider.kt => UserWalletsListManagerProvider.kt} (100%) create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/di/WalletsStoresManagerProvider.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt create mode 100644 app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 4b801de36a..82e2081d78 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -37,6 +37,10 @@ import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.config.FeaturesLocalLoader import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator +import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation +import com.tangem.tap.domain.walletStores.WalletStoresManager +import com.tangem.tap.domain.walletStores.di.provideDummyImplementation import com.tangem.tap.domain.walletconnect.WalletConnectRepository import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.persistence.CardBalanceStateAdapter @@ -60,6 +64,14 @@ lateinit var shopService: TangemShopService lateinit var assetReader: AssetReader lateinit var userTokensRepository: UserTokensRepository +val walletStoresManager by lazy { + WalletStoresManager.provideDummyImplementation() +} + +val totalFiatBalanceCalculator by lazy { + TotalFiatBalanceCalculator.provideDefaultImplementation() +} + @HiltAndroidApp class TapApplication : Application(), ImageLoaderFactory { diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index 6d2bf6af62..f03dfc70ca 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -16,6 +16,7 @@ import com.tangem.tap.features.shop.redux.ShopReducer import com.tangem.tap.features.tokens.redux.TokensReducer import com.tangem.tap.features.wallet.redux.reducers.WalletReducer import com.tangem.tap.proxy.AppStateHolder +import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer import com.tangem.tap.features.welcome.redux.WelcomeReducer import org.rekotlin.Action @@ -40,6 +41,7 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder) shopState = ShopReducer.reduce(action, state.shopState), welcomeState = WelcomeReducer.reduce(action, state), saveWalletState = SaveWalletReducer.reduce(action, state), + walletSelectorState = WalletSelectorReducer.reduce(action, state), ) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index d85b69df46..c89e7c0133 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -35,6 +35,7 @@ import com.tangem.tap.features.tokens.redux.TokensMiddleware import com.tangem.tap.features.tokens.redux.TokensState import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware +import com.tangem.tap.features.walletSelector.redux.WalletSelectorMiddleware import com.tangem.tap.features.walletSelector.redux.WalletSelectorState import com.tangem.tap.features.welcome.redux.WelcomeMiddleware import com.tangem.tap.features.welcome.redux.WelcomeState @@ -88,6 +89,7 @@ data class AppState( ShopMiddleware().shopMiddleware, WelcomeMiddleware().middleware, SaveWalletMiddleware().middleware, + WalletSelectorMiddleware().middleware, ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt new file mode 100644 index 0000000000..6bdca4b2de --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.domain.model + +import java.math.BigDecimal + +/** + * Represents fiat balance of [WalletStoreModel] list + * @property amount Amount of the total balance + * */ +sealed class TotalFiatBalance { + open val amount: BigDecimal = BigDecimal.ZERO + + object Loading : TotalFiatBalance() + + class Refreshing( + override val amount: BigDecimal, + ) : TotalFiatBalance() + + class Error( + override val amount: BigDecimal, + ) : TotalFiatBalance() + + class Loaded( + override val amount: BigDecimal, + ) : TotalFiatBalance() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 6ce60fae85..4cb6fbfdd8 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -2,9 +2,17 @@ package com.tangem.tap.domain.model import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.userWalletId -import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl +/** + * Represents user's wallet which stored in app persistence + * @param name User's wallet name + * @param walletId User's wallet [UserWalletId] + * @param artworkUrl User wallet card artwork URL + * @param cardsInWallet List of cards IDs assigned with this user's wallet + * @param scanResponse [ScanResponse] of primary user's wallet card. + * TODO: Replace with [com.tangem.domain.common.CardDTO] + * @property cardId ID of user's wallet primary card + * */ data class UserWallet( val name: String, val walletId: UserWalletId, @@ -14,24 +22,4 @@ data class UserWallet( ) { val cardId: String get() = scanResponse.card.cardId - - val hasAccessCode: Boolean - get() = scanResponse.card.isAccessCodeSet - - companion object { - suspend operator fun invoke( - scanResponse: ScanResponse, - backupCardsIds: Set? = null, - ): UserWallet { - return with(scanResponse) { - UserWallet( - walletId = card.userWalletId, - name = productType.name, - artworkUrl = card.getOrLoadCardArtworkUrl(), - cardsInWallet = backupCardsIds?.plus(card.cardId) ?: setOf(card.cardId), - scanResponse = this, - ) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt new file mode 100644 index 0000000000..173ed53786 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt @@ -0,0 +1,79 @@ +package com.tangem.tap.domain.model + +import com.tangem.tap.domain.model.WalletDataModel.Status +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.PendingTransaction +import com.tangem.tap.features.wallet.redux.AddressData +import java.math.BigDecimal + +/** + * Contains info about wallet store currency + * @param currency Wallet's [Currency] + * @param status Wallet's [Status], represents current status of that currency + * @param walletAddresses List of wallet data [AddressData] + * @param existentialDeposit Amount that must be held on currency's balance, if balance is below that amount all + * founds will be destroyed. Null if currency don't have existential deposit + * @param fiatRate Wallet's fiat rate, used to calculate fiat balance. Null if not provided + * */ +data class WalletDataModel( + val currency: Currency, + val status: Status, + val walletAddresses: List, + val existentialDeposit: BigDecimal?, + val fiatRate: BigDecimal?, +) { + + /** + * Represent current status of currency + * @property amount Currency amount + * @property pendingTransactions List of currency [PendingTransaction] sent in currency's blockchain + * @property errorMessage Status error message, null if not provided + * @property isErrorStatus true if current status is error status, false otherwise + * */ + sealed class Status { + open val amount: BigDecimal = BigDecimal.ZERO + open val pendingTransactions: List = emptyList() + open val errorMessage: String? = null + open val isErrorStatus: Boolean = false + } + + object Loading : Status() + + data class VerifiedOnline( + override val amount: BigDecimal, + ) : Status() + + data class TransactionInProgress( + override val amount: BigDecimal, + override val pendingTransactions: List, + ) : Status() + + data class SameCurrencyTransactionInProgress( + override val amount: BigDecimal, + override val pendingTransactions: List, + ) : Status() + + data class NoAccount( + val amountToCreateAccount: BigDecimal?, + ) : Status() { + override val isErrorStatus: Boolean = true + } + + data class Unreachable( + override val errorMessage: String?, + ) : Status() { + override val isErrorStatus: Boolean = true + } + + object MissedDerivation : Status() { + override val isErrorStatus: Boolean = true + } + + data class Refreshing( + override val amount: BigDecimal, + override val pendingTransactions: List, + override val errorMessage: String?, + ) : Status() { + override val isErrorStatus: Boolean = errorMessage != null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt new file mode 100644 index 0000000000..1753b9b0ba --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.domain.model + +import com.tangem.blockchain.common.WalletManager +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.WalletStoreModel.WalletRent +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import java.math.BigDecimal + +/** + * Contains info about the blockchain and its currencies + * @param userWalletId ID of the [UserWallet] which uses that store + * @param blockchainNetwork Store's [BlockchainNetwork] + * @param walletManager Store's [WalletManager], may be null if it fails to create this manager + * @param walletsData List of [WalletDataModel] which represents store's blockchain currency and tokens currencies + * @param walletRent Store's [WalletRent], null if store has no rent or currency balance is greater then + * [WalletRent.exemptionAmount] + * */ +data class WalletStoreModel( + val userWalletId: UserWalletId, + val blockchainNetwork: BlockchainNetwork, + val walletManager: WalletManager?, + val walletsData: List, + val walletRent: WalletRent?, +) { + + /** + * Represents wallet blockchain rent + * @param rent Amount that will be charged in overtime if the blockchain does not have an amount greater than + * the [WalletRent.exemptionAmount] + * @param exemptionAmount Amount that should be on the blockchain balance not to pay rent + * */ + data class WalletRent( + val rent: BigDecimal, + val exemptionAmount: BigDecimal, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt new file mode 100644 index 0000000000..4ea9db1491 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.domain.model.builders + +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.util.userWalletId +import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl +import com.tangem.tap.domain.model.UserWallet + +class UserWalletBuilder( + private val scanResponse: ScanResponse, +) { + private var backupCardsIds: Set = emptySet() + + fun setBackupCardsIds(backupCardsIds: Set?) = this.apply { + if (backupCardsIds != null) { + this.backupCardsIds = backupCardsIds + } + } + + suspend fun build(): UserWallet { + return with(scanResponse) { + UserWallet( + walletId = card.userWalletId, + name = productType.name, + artworkUrl = card.getOrLoadCardArtworkUrl(), + cardsInWallet = backupCardsIds.plus(card.cardId), + scanResponse = this, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/TotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/TotalFiatBalanceCalculator.kt new file mode 100644 index 0000000000..d9b586d3e6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/TotalFiatBalanceCalculator.kt @@ -0,0 +1,14 @@ +package com.tangem.tap.domain.totalBalance + +import com.tangem.tap.domain.model.TotalFiatBalance +import com.tangem.tap.domain.model.WalletStoreModel +import java.math.BigDecimal + +interface TotalFiatBalanceCalculator { + suspend fun calculate( + prevAmount: BigDecimal, + walletStores: List, + ): TotalFiatBalance + + companion object +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/di/TotalFiatBalanceCalculatorProvider.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/di/TotalFiatBalanceCalculatorProvider.kt new file mode 100644 index 0000000000..ea285cc404 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/di/TotalFiatBalanceCalculatorProvider.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.domain.totalBalance.di + +import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator +import com.tangem.tap.domain.totalBalance.implementation.DefaultTotalFiatBalanceCalculator + +fun TotalFiatBalanceCalculator.Companion.provideDefaultImplementation(): TotalFiatBalanceCalculator { + return DefaultTotalFiatBalanceCalculator() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt new file mode 100644 index 0000000000..be318667dd --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt @@ -0,0 +1,102 @@ +package com.tangem.tap.domain.totalBalance.implementation + +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.domain.model.TotalFiatBalance +import com.tangem.tap.domain.model.WalletDataModel +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.math.BigDecimal + +internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { + override suspend fun calculate( + prevAmount: BigDecimal, + walletStores: List, + ): TotalFiatBalance { + return if (walletStores.isEmpty()) { + TotalFiatBalance.Loading + } else { + withContext(Dispatchers.Default) { + val walletsData = walletStores + .asSequence() + .flatMap { it.walletsData } + val calculateAmount = { walletsData.calculateTotalFiatAmount() } + + when (walletsData.findStatus()) { + TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading + TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing(prevAmount) + TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount()) + TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(calculateAmount()) + } + } + } + } + + private fun Sequence.findStatus(): TotalFiatBalanceStatus { + return this + .mapToStatus() + .reduce { prevStatus, newStatus -> + getCurrentStatus(prevStatus, newStatus) + } + } + + private fun Sequence.mapToStatus(): Sequence { + return this.map { walletData -> + when (walletData.status) { + is WalletDataModel.Refreshing -> TotalFiatBalanceStatus.Refreshing + is WalletDataModel.VerifiedOnline, + is WalletDataModel.SameCurrencyTransactionInProgress, + is WalletDataModel.TransactionInProgress, + is WalletDataModel.NoAccount, + -> TotalFiatBalanceStatus.Loaded + is WalletDataModel.Unreachable, + is WalletDataModel.MissedDerivation, + -> TotalFiatBalanceStatus.Error + is WalletDataModel.Loading -> TotalFiatBalanceStatus.Loading + } + } + } + + private fun Sequence.calculateTotalFiatAmount(): BigDecimal { + return this + .map { walletData -> + walletData.fiatRate + ?.let { walletData.status.amount.toFiatValue(it) } + ?: BigDecimal.ZERO + } + .reduce(BigDecimal::plus) + } + + private fun getCurrentStatus( + prevStatus: TotalFiatBalanceStatus, + newStatus: TotalFiatBalanceStatus, + ): TotalFiatBalanceStatus { + return when (prevStatus) { + TotalFiatBalanceStatus.Loading -> prevStatus + TotalFiatBalanceStatus.Refreshing -> when (newStatus) { + TotalFiatBalanceStatus.Loading -> prevStatus + TotalFiatBalanceStatus.Refreshing, + TotalFiatBalanceStatus.Error, + TotalFiatBalanceStatus.Loaded, + -> newStatus + } + TotalFiatBalanceStatus.Loaded, + TotalFiatBalanceStatus.Error, + -> when (newStatus) { + TotalFiatBalanceStatus.Loading, + TotalFiatBalanceStatus.Refreshing, + TotalFiatBalanceStatus.Error, + -> newStatus + TotalFiatBalanceStatus.Loaded -> prevStatus + } + } + } + + private enum class TotalFiatBalanceStatus { + Loading, + Refreshing, + Error, + Loaded, + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index 05efbdf35f..3dc337ae61 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -25,5 +25,7 @@ interface UserWalletsListManager { suspend fun delete(walletIds: List): CompletionResult suspend fun clear(): CompletionResult + suspend fun get(walletId: UserWalletId): CompletionResult + companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt similarity index 100% rename from app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListProvider.kt rename to app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt index bcc6c05b7f..5135966db1 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt @@ -55,4 +55,10 @@ class DummyUserWalletsListManager : UserWalletsListManager { override suspend fun clear(): CompletionResult { return CompletionResult.Success(Unit) } + + override suspend fun get(walletId: UserWalletId): CompletionResult { + return catching { + error("Not implemented") + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt new file mode 100644 index 0000000000..346a6108e8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.domain.walletStores + +import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.WalletStoreModel +import kotlinx.coroutines.flow.Flow + +interface WalletStoresManager { + fun getAll(): Flow>> + fun get(userWalletId: UserWalletId): Flow> + + suspend fun delete(userWalletsIds: List): CompletionResult + suspend fun clear(): CompletionResult + + suspend fun fetch( + userWallet: UserWallet, + refresh: Boolean = false, + ): CompletionResult + + suspend fun fetch( + userWallets: List, + refresh: Boolean = false, + ): CompletionResult + + companion object +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/di/WalletsStoresManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/di/WalletsStoresManagerProvider.kt new file mode 100644 index 0000000000..fdc5187641 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/di/WalletsStoresManagerProvider.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.domain.walletStores.di + +import com.tangem.tap.domain.walletStores.WalletStoresManager +import com.tangem.tap.domain.walletStores.implementation.DummyWalletStoresManager + +fun WalletStoresManager.Companion.provideDummyImplementation(): WalletStoresManager { + return DummyWalletStoresManager() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt new file mode 100644 index 0000000000..93c5c780c1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt @@ -0,0 +1,35 @@ +package com.tangem.tap.domain.walletStores.implementation + +import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.domain.walletStores.WalletStoresManager +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +internal class DummyWalletStoresManager : WalletStoresManager { + override fun getAll(): Flow>> { + return emptyFlow() + } + + override fun get(userWalletId: UserWalletId): Flow> { + return emptyFlow() + } + + override suspend fun delete(userWalletsIds: List): CompletionResult { + return CompletionResult.Success(Unit) + } + + override suspend fun clear(): CompletionResult { + return CompletionResult.Success(Unit) + } + + override suspend fun fetch(userWallet: UserWallet, refresh: Boolean): CompletionResult { + return CompletionResult.Success(Unit) + } + + override suspend fun fetch(userWallets: List, refresh: Boolean): CompletionResult { + return CompletionResult.Success(Unit) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 844b55b115..500201104f 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -4,12 +4,12 @@ import com.tangem.common.CompletionResult import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap -import com.tangem.common.map import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store @@ -65,10 +65,9 @@ internal class SaveWalletMiddleware { ?: return scope.launch { - val userWallet = UserWallet( - scanResponse = scanResponse, - backupCardsIds = state.additionalInfo?.backupCardsIds, - ) + val userWallet = UserWalletBuilder(scanResponse) + .setBackupCardsIds(backupCardsIds = state.additionalInfo?.backupCardsIds) + .build() userWalletsListManager.save(userWallet) .flatMap { @@ -108,14 +107,6 @@ internal class SaveWalletMiddleware { cardsIds = userWallet.cardsInWallet, ) } - userWallet.hasAccessCode -> { - tangemSdkManager - .scanCard( - cardId = userWallet.cardId, - useBiometricsForAccessCode = true, - ) - .map { /* no-op */ } - } else -> { CompletionResult.Success(Unit) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt new file mode 100644 index 0000000000..a1dc438e52 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt @@ -0,0 +1,22 @@ +package com.tangem.tap.features.walletSelector.redux + +import com.tangem.tap.domain.model.TotalFiatBalance + +data class UserWalletModel( + val id: String, + val name: String, + val artworkUrl: String, + val type: Type, + val fiatBalance: TotalFiatBalance, +) { + sealed interface Type { + data class SingleCurrency( + val blockchainName: String?, + ) : Type + + data class MultiCurrency( + val tokensCount: Int, + val cardsInWallet: Int, + ) : Type + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index 51a7253075..d18cf44dec 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -1,9 +1,33 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.common.core.TangemError +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.common.entities.FiatCurrency +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.WalletStoreModel import org.rekotlin.Action internal sealed interface WalletSelectorAction : Action { + data class UserWalletsLoaded( + val userWallets: List, + ) : WalletSelectorAction + + data class SelectedWalletChanged( + val selectedWallet: UserWallet, + ) : WalletSelectorAction + + data class IsLockedChanged( + val isLocked: Boolean, + ) : WalletSelectorAction + + data class WalletStoresChanged( + val walletsStores: Map>, + ) : WalletSelectorAction + + data class BalanceLoaded( + val userWalletModel: UserWalletModel, + ) : WalletSelectorAction + object UnlockWithBiometry : WalletSelectorAction { object Success : WalletSelectorAction data class Error(val error: TangemError) : WalletSelectorAction @@ -27,5 +51,10 @@ internal sealed interface WalletSelectorAction : Action { data class Error(val error: TangemError) : WalletSelectorAction } + data class ChangeAppCurrency( + val fiatCurrency: FiatCurrency, + ) : WalletSelectorAction + + data class HandleError(val error: TangemError) : WalletSelectorAction object CloseError : WalletSelectorAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt new file mode 100644 index 0000000000..00f5b77e25 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -0,0 +1,212 @@ +package com.tangem.tap.features.walletSelector.redux + +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.flatMap +import com.tangem.common.map +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.* +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.onUserWalletSelected +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.domain.model.builders.UserWalletBuilder +import com.tangem.tap.domain.scanCard.ScanCardProcessor +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import org.rekotlin.Middleware +import timber.log.Timber + +internal class WalletSelectorMiddleware { + val middleware: Middleware = { _, appStateProvider -> + { next -> + { action -> + val appState = appStateProvider() + if (action is WalletSelectorAction && appState != null) { + handleAction(action, appState.walletSelectorState) + } + next(action) + } + } + } + + private fun handleAction(action: WalletSelectorAction, state: WalletSelectorState) { + when (action) { + is WalletSelectorAction.UserWalletsLoaded -> { + fetchWalletStores(action.userWallets) + } + is WalletSelectorAction.WalletStoresChanged -> { + updateBalances(action.walletsStores, state) + } + is WalletSelectorAction.UnlockWithBiometry -> { + unlockWalletsWithBiometry() + } + is WalletSelectorAction.AddWallet -> { + addWallet() + } + is WalletSelectorAction.SelectWallet -> { + selectWallet(action.walletId) + } + is WalletSelectorAction.RemoveWallets -> { + removeWallets(action.walletIdsToRemove, state) + } + is WalletSelectorAction.RenameWallet -> { + renameWallet(action.walletId, action.newName) + } + is WalletSelectorAction.ChangeAppCurrency, + is WalletSelectorAction.AddWallet.Success, + is WalletSelectorAction.AddWallet.Error, + is WalletSelectorAction.SelectedWalletChanged, + is WalletSelectorAction.UnlockWithBiometry.Error, + is WalletSelectorAction.UnlockWithBiometry.Success, + is WalletSelectorAction.BalanceLoaded, + is WalletSelectorAction.IsLockedChanged, + is WalletSelectorAction.HandleError, + is WalletSelectorAction.CloseError, + -> Unit + } + } + + private fun fetchWalletStores(userWallets: List) { + scope.launch { + walletStoresManager.fetch(userWallets) + .doOnFailure { error -> + Timber.e(error, "Unable to fetch wallet stores") + store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + } + } + } + + private fun updateBalances(walletStores: Map>, state: WalletSelectorState) { + walletStores.forEach { (walletId, walletStores) -> + scope.launch { + val updatedWallet = state.wallets + .find { it.id == walletId.stringValue } + ?.let { wallet -> + wallet.copy( + type = when (val type = wallet.type) { + is UserWalletModel.Type.MultiCurrency -> type.copy( + tokensCount = walletStores.flatMap { it.walletsData }.size, + ) + is UserWalletModel.Type.SingleCurrency -> type.copy( + blockchainName = walletStores + .firstOrNull() + ?.blockchainNetwork + ?.blockchain + ?.fullName, + ) + }, + fiatBalance = totalFiatBalanceCalculator.calculate( + prevAmount = wallet.fiatBalance.amount, + walletStores = walletStores, + ), + ) + } + + if (updatedWallet != null) { + store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) + } + } + } + } + + private fun unlockWalletsWithBiometry() { + scope.launch { + userWalletsListManager.unlockWithBiometry() + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.UnlockWithBiometry.Error(error)) + } + .doOnSuccess { + store.dispatchOnMain(WalletSelectorAction.UnlockWithBiometry.Success) + } + } + } + + private fun addWallet() = scope.launch { + scanCardInternal { scanResponse -> + val userWallet = UserWalletBuilder(scanResponse).build() + + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + } + .doOnSuccess { + val selectedWallet = userWalletsListManager.selectedUserWallet.first() + val isSavedWalletSelected = userWallet == selectedWallet + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + if (isSavedWalletSelected) { + store.dispatchOnMain(NavigationAction.PopBackTo()) + store.onUserWalletSelected(selectedWallet) + } + } + } + } + + private fun selectWallet(id: String) { + scope.launch { + userWalletsListManager.selectWallet(UserWalletId(id)) + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + } + .doOnSuccess { selectedWallet -> + store.dispatchOnMain(NavigationAction.PopBackTo()) + store.onUserWalletSelected(selectedWallet) + } + } + } + + private fun removeWallets(walletIdsToRemove: List, state: WalletSelectorState) { + scope.launch { + when (walletIdsToRemove.size) { + state.wallets.size -> { // Removing all wallets + userWalletsListManager.clear() + .flatMap { walletStoresManager.clear() } + .doOnSuccess { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + + else -> { + userWalletsListManager.delete(walletIdsToRemove.map { UserWalletId(it) }) + .flatMap { walletStoresManager.delete(walletIdsToRemove) } + } + } + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + } + } + } + + private fun renameWallet(walletId: String, newName: String) { + scope.launch { + userWalletsListManager.get(walletId = UserWalletId(walletId)) + .map { it.copy(name = newName) } + .flatMap { userWalletsListManager.update(it) } + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + } + } + } + + private suspend inline fun scanCardInternal( + crossinline onCardScanned: suspend (ScanResponse) -> Unit, + ) { + ScanCardProcessor.scan( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + onSuccess = { + onCardScanned(it) + }, + onFailure = { error -> + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + }, + onWalletNotCreated = { + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo()) + }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt new file mode 100644 index 0000000000..82fe296fd4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -0,0 +1,107 @@ +package com.tangem.tap.features.walletSelector.redux + +import com.tangem.domain.common.CardDTO +import com.tangem.tap.common.extensions.replaceByOrAdd +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.model.TotalFiatBalance +import com.tangem.tap.domain.model.UserWallet +import org.rekotlin.Action + +internal object WalletSelectorReducer { + fun reduce(action: Action, state: AppState): WalletSelectorState { + return if (action is WalletSelectorAction) { + internalReduce(action, state.walletSelectorState) + } else state.walletSelectorState + } + + private fun internalReduce(action: WalletSelectorAction, state: WalletSelectorState): WalletSelectorState { + return when (action) { + is WalletSelectorAction.UserWalletsLoaded -> state.copy( + wallets = action.userWallets.updateWalletsModels(state.wallets), + ) + is WalletSelectorAction.SelectedWalletChanged -> state.copy( + selectedWalletId = action.selectedWallet.walletId.stringValue, + ) + is WalletSelectorAction.IsLockedChanged -> state.copy( + isLocked = action.isLocked, + ) + is WalletSelectorAction.BalanceLoaded -> state.copy( + wallets = state.wallets.updateWithBalance(action.userWalletModel), + ) + is WalletSelectorAction.HandleError -> state.copy(error = action.error) + is WalletSelectorAction.CloseError -> state.copy(error = null) + is WalletSelectorAction.UnlockWithBiometry -> state.copy( + isUnlockInProgress = true, + ) + is WalletSelectorAction.UnlockWithBiometry.Error -> state.copy( + isUnlockInProgress = false, + error = action.error, + ) + is WalletSelectorAction.UnlockWithBiometry.Success -> state.copy( + isUnlockInProgress = false, + ) + is WalletSelectorAction.AddWallet -> state.copy( + isCardSavingInProgress = true, + ) + is WalletSelectorAction.AddWallet.Error -> state.copy( + isCardSavingInProgress = false, + error = action.error, + ) + is WalletSelectorAction.AddWallet.Success -> state.copy( + isCardSavingInProgress = false, + ) + is WalletSelectorAction.ChangeAppCurrency -> state.copy( + fiatCurrency = action.fiatCurrency, + ) + is WalletSelectorAction.WalletStoresChanged, + is WalletSelectorAction.SelectWallet, + is WalletSelectorAction.RemoveWallets, + is WalletSelectorAction.RenameWallet, + -> state + } + } + + private fun List.updateWalletsModels(prevWallets: List): List { + return this.map { userWallet -> + prevWallets + .find { it.id == userWallet.walletId.stringValue } + ?.copy( + name = userWallet.name, + artworkUrl = userWallet.artworkUrl, + ) + ?: with(userWallet) { + UserWalletModel( + id = walletId.stringValue, + name = name, + artworkUrl = artworkUrl, + type = getType(), + fiatBalance = TotalFiatBalance.Loading, + ) + } + } + } + + private fun List.updateWithBalance( + userWalletModel: UserWalletModel, + ): List { + return ArrayList(this).apply { + replaceByOrAdd(userWalletModel) { it.id == userWalletModel.id } + } + } + + private fun UserWallet.getType(): UserWalletModel.Type { + return if (scanResponse.card.isMultiwalletAllowed) { + UserWalletModel.Type.MultiCurrency( + tokensCount = 0, + cardsInWallet = (scanResponse.card.backupStatus as? CardDTO.BackupStatus.Active) + ?.cardCount?.inc() + ?: 1, + ) + } else { + UserWalletModel.Type.SingleCurrency( + blockchainName = null, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt index c84839277a..df6ec0867d 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt @@ -1,5 +1,15 @@ package com.tangem.tap.features.walletSelector.redux +import com.tangem.common.core.TangemError +import com.tangem.tap.common.entities.FiatCurrency import org.rekotlin.StateType -class WalletSelectorState : StateType \ No newline at end of file +data class WalletSelectorState( + val wallets: List = emptyList(), + val selectedWalletId: String? = null, + val isLocked: Boolean = false, + val fiatCurrency: FiatCurrency = FiatCurrency.Default, + val isCardSavingInProgress: Boolean = false, + val isUnlockInProgress: Boolean = false, + val error: TangemError? = null, +) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt new file mode 100644 index 0000000000..34ce8d3ef9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt @@ -0,0 +1,76 @@ +package com.tangem.tap.features.walletSelector.ui + +import com.tangem.tap.common.entities.FiatCurrency +import com.tangem.tap.common.extensions.toFormattedFiatValue +import com.tangem.tap.domain.model.TotalFiatBalance +import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.tap.features.walletSelector.redux.UserWalletModel +import com.tangem.tap.features.walletSelector.redux.WalletSelectorState +import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem +import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem +import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem + +internal fun WalletSelectorScreenState.updateWithNewState( + newState: WalletSelectorState, +): WalletSelectorScreenState { + val walletsUi = newState.wallets.toUiModels(newState.fiatCurrency) + val walletsIds = walletsUi.map { it.id } + val multiCurrencyWallets = arrayListOf() + val singleCurrencyWallets = arrayListOf() + walletsUi.forEach { wallet -> + when (wallet) { + is MultiCurrencyUserWalletItem -> { + multiCurrencyWallets.add(wallet) + } + is SingleCurrencyUserWalletItem -> { + singleCurrencyWallets.add(wallet) + } + } + } + + return this.copy( + multiCurrencyWallets = multiCurrencyWallets, + singleCurrencyWallets = singleCurrencyWallets, + selectedWalletId = newState.selectedWalletId, + editingWalletsIds = editingWalletsIds.filter { it in walletsIds }, + isLocked = newState.isLocked, + showUnlockProgress = newState.isUnlockInProgress, + showAddCardProgress = newState.isCardSavingInProgress, + error = newState.error + ?.takeUnless { it.silent } + ?.let { error -> + error.messageResId?.let { TextReference.Res(it) } + ?: TextReference.Str(error.customMessage) + }, + ) +} + +private fun List.toUiModels( + appCurrency: FiatCurrency, +): Sequence { + return this.asSequence().map { userWalletModel -> + with(userWalletModel) { + val balance = UserWalletItem.Balance( + amount = fiatBalance.amount.toFormattedFiatValue(appCurrency.symbol), + isLoading = fiatBalance is TotalFiatBalance.Loading, + ) + when (type) { + is UserWalletModel.Type.MultiCurrency -> MultiCurrencyUserWalletItem( + id = id, + name = name, + imageUrl = artworkUrl, + balance = balance, + cardsInWallet = type.cardsInWallet, + tokensCount = type.tokensCount, + ) + is UserWalletModel.Type.SingleCurrency -> SingleCurrencyUserWalletItem( + id = id, + name = name, + imageUrl = artworkUrl, + balance = balance, + tokenName = type.blockchainName ?: "—", + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index 3637f2ea5e..50ee972e7d 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.fragment.app.viewModels import com.tangem.core.ui.fragments.ComposeBottomSheetFragment import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog @@ -33,7 +34,7 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment + prevState.updateWithNewState(state) + } } override fun onCleared() { @@ -135,10 +144,38 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber + store.dispatchOnMain(WalletSelectorAction.WalletStoresChanged(walletStores)) + } + .launchIn(viewModelScope) + } + + private fun bootstrapAppFiatCurrency() { + store.dispatch( + WalletSelectorAction.ChangeAppCurrency( + fiatCurrency = store.state.globalState.appCurrency, + ), + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index eb7fca3d46..73556a2d6f 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -8,7 +8,7 @@ import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.scope import com.tangem.tap.store @@ -58,7 +58,7 @@ internal class WelcomeMiddleware { private fun proceedWithCard() = scope.launch { scanCardInternal { scanResponse -> - val userWallet = UserWallet(scanResponse) + val userWallet = UserWalletBuilder(scanResponse).build() userWalletsListManager.unlockWithCard(userWallet) .doOnFailure { error -> From e993dc064760a7e6f3d16f456bead3c3ea246a95 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Dec 2022 14:40:31 +0300 Subject: [PATCH 097/176] Updated on 2026-08-14 --- .idea/codeStyles/Project.xml | 3 +- .idea/codeStyles/codeStyleConfig.xml | 2 +- .idea/inspectionProfiles/Project_Default.xml | 4 +- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 6 +- app/src/main/assets/tangem-app-config | 2 +- .../com/tangem/tap/LockUserWalletsTimer.kt | 115 +++++ .../main/java/com/tangem/tap/MainActivity.kt | 19 +- .../java/com/tangem/tap/TapApplication.kt | 46 +- .../com/tangem/tap/common/extensions/Store.kt | 5 +- .../com/tangem/tap/common/redux/AppState.kt | 5 +- .../redux/LockUserWalletsTimerMiddleware.kt | 15 + .../common/redux/global/GlobalMidlleware.kt | 6 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 54 ++- .../com/tangem/tap/domain/TapWalletManager.kt | 74 ++- .../configurable/config/ConfigManager.kt | 2 +- .../com/tangem/tap/domain/model/UserWallet.kt | 2 + .../tap/domain/model/WalletDataModel.kt | 6 + .../tap/domain/model/WalletStoreModel.kt | 4 +- .../model/builders/WalletStoreBuilder.kt | 77 ++++ .../tap/domain/scanCard/ScanCardProcessor.kt | 4 +- .../tasks/product/CreateProductWalletTask.kt | 2 + .../domain/tasks/product/ScanProductTask.kt | 3 + .../tap/domain/twins/TwinCardsManager.kt | 20 +- .../userWalletList/UserWalletListError.kt | 40 ++ .../di/UserWalletsListManagerProvider.kt | 68 ++- .../BiometricUserWalletsListManager.kt | 303 +++++++++++++ .../model/UserWalletEncryptionKey.kt | 33 ++ .../model/UserWalletInformation.kt | 20 + .../SelectedUserWalletRepository.kt | 8 + .../repository/UserWalletsKeysRepository.kt | 12 + .../UserWalletsPublicInformationRepository.kt | 15 + ...erWalletsSensitiveInformationRepository.kt | 16 + .../BiometricUserWalletsKeysRepository.kt | 106 +++++ .../DefaultSelectedUserWalletRepository.kt | 30 ++ ...tUserWalletsPublicInformationRepository.kt | 84 ++++ ...erWalletsSensitiveInformationRepository.kt | 142 ++++++ .../utils/ByteArrayListExtensions.kt | 9 + .../tap/domain/userWalletList/utils/Mapper.kt | 58 +++ .../utils/json/ByteArrayKeyAdapter.kt | 29 ++ .../utils/json/CardBackupStatusAdapter.kt | 57 +++ .../json/ExtendedPublicKeysMapAdapter.kt | 57 +++ .../json/ScanResponseDerivedKeysMapAdapter.kt | 52 +++ .../utils/json/WalletDerivedKeysMapAdapter.kt | 56 +++ .../WalletCurrenciesManager.kt | 30 ++ .../di/WalletCurrenciesManagerProvider.kt | 25 + .../DefaultWalletCurrenciesManager.kt | 205 +++++++++ .../domain/walletStores/WalletStoresError.kt | 39 ++ .../di/WalletsStoresManagerProvider.kt | 22 + .../DefaultWalletStoresManager.kt | 174 +++++++ .../utils/CompletitionsResultOperations.kt | 27 ++ .../repository/WalletAmountsRepository.kt | 26 ++ .../repository/WalletManagersRepository.kt | 25 + .../repository/WalletStoresRepository.kt | 30 ++ .../repository/di/RepositoryProvider.kt | 26 ++ .../DefaultWalletAmountsRepository.kt | 427 ++++++++++++++++++ .../DefaultWalletManagersRepository.kt | 192 ++++++++ .../DefaultWalletStoresRepository.kt | 91 ++++ .../utils/WalletDataOperations.kt | 181 ++++++++ .../utils/WalletStoreOperations.kt | 93 ++++ .../storage/WalletManagerStorage.kt | 36 ++ .../storage/WalletStoresStorage.kt | 51 +++ .../features/details/redux/DetailsAction.kt | 16 +- .../details/redux/DetailsMiddleware.kt | 161 ++++++- .../features/details/redux/DetailsReducer.kt | 19 +- .../features/details/redux/DetailsState.kt | 2 + .../ui/appsettings/AppSettingsScreen.kt | 29 +- .../ui/appsettings/AppSettingsScreenState.kt | 6 +- .../ui/appsettings/AppSettingsViewModel.kt | 13 + .../details/ui/details/DetailsScreenState.kt | 4 + .../details/ui/details/DetailsViewModel.kt | 6 +- .../tangem/tap/features/home/HomeFragment.kt | 10 +- .../tap/features/home/redux/HomeMiddleware.kt | 207 ++------- .../features/onboarding/OnboardingHelper.kt | 47 ++ .../note/redux/OnboardingNoteMiddleware.kt | 9 +- .../redux/OnboardingOtherCardsMiddleware.kt | 9 +- .../products/twins/redux/TwinCardsAction.kt | 5 +- .../twins/redux/TwinCardsMiddleware.kt | 36 +- .../products/twins/redux/TwinCardsState.kt | 17 +- .../products/twins/ui/TwinsCardsFragment.kt | 11 +- .../redux/OnboardingWalletMiddleware.kt | 12 +- .../saveWallet/redux/SaveWalletAction.kt | 2 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 51 ++- .../saveWallet/redux/SaveWalletReducer.kt | 8 +- .../saveWallet/redux/SaveWalletState.kt | 4 +- .../ui/SaveWalletBottomSheetFragment.kt | 8 +- .../send/redux/middlewares/SendMiddleware.kt | 47 +- .../tap/features/send/ui/SendFragment.kt | 30 +- .../features/tokens/redux/TokensMiddleware.kt | 137 +++--- .../tap/features/wallet/redux/WalletAction.kt | 38 +- .../tap/features/wallet/redux/WalletState.kt | 16 +- .../middlewares/AppCurrencyMiddleware.kt | 14 +- .../middlewares/MultiWalletMiddleware.kt | 68 ++- .../redux/middlewares/WalletMiddleware.kt | 125 +++-- .../redux/middlewares/WarningsMiddleware.kt | 14 +- .../redux/reducers/MultiWalletReducer.kt | 20 +- .../wallet/redux/reducers/WalletReducer.kt | 189 +++++++- .../tap/features/wallet/ui/BalanceWidget.kt | 2 +- .../wallet/ui/WalletDetailsFragment.kt | 45 +- .../tap/features/wallet/ui/WalletFragment.kt | 24 +- .../tap/features/wallet/ui/WalletViewModel.kt | 39 ++ .../wallet/ui/adapters/WalletAdapter.kt | 25 +- .../wallet/ui/images/CurrencyIconView.kt | 2 + .../wallet/ui/wallet/MultiWalletView.kt | 16 +- .../wallet/ui/wallet/SingleWalletView.kt | 12 +- .../features/welcome/ui/WelcomeFragment.kt | 8 +- app/src/main/res/drawable/ic_tap_card_24.xml | 27 ++ .../drawable/shape_rectangle_rounded_100.xml | 2 +- .../res/layout/card_total_balance_shimmer.xml | 12 +- .../res/layout/layout_card_total_balance.xml | 132 +++--- app/src/main/res/values/styles.xml | 9 + buildSrc/src/main/java/Versions.kt | 2 +- .../com/tangem/core/ui/res/TangemShapes.kt | 5 +- .../res/values-it/strings_plurals_final.xml | 2 + .../src/main/res/values-ru/strings_final.xml | 2 + core/ui/src/main/res/values/strings_final.xml | 1 + .../java/com/tangem/domain/common/CardInfo.kt | 14 + .../domain/common/util/CardExtensions.kt | 20 +- .../tangem/domain/common/util/UserWalletId.kt | 6 + 119 files changed, 4449 insertions(+), 720 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt create mode 100644 app/src/main/java/com/tangem/tap/common/redux/LockUserWalletsTimerMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletListError.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/repository/SelectedUserWalletRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/utils/ByteArrayListExtensions.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ByteArrayKeyAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/CardBackupStatusAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ExtendedPublicKeysMapAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ScanResponseDerivedKeysMapAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/WalletDerivedKeysMapAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletCurrencies/di/WalletCurrenciesManagerProvider.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt create mode 100644 app/src/main/res/drawable/ic_tap_card_24.xml create mode 100644 domain/src/main/java/com/tangem/domain/common/CardInfo.kt diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 76a59bafa3..6527894333 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -12,6 +12,7 @@ diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a1658496e7..bfc2bf8157 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6 +Subproject commit bfc2bf8157089bce6b44779bdae66df2c920de70 diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt new file mode 100644 index 0000000000..c4b3d48e73 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -0,0 +1,115 @@ +package com.tangem.tap + +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import kotlinx.coroutines.* +import timber.log.Timber +import kotlin.time.Duration + +internal class LockUserWalletsTimer( + owner: LifecycleOwner, + private val duration: Duration = with(Duration) { 5.minutes }, +) : LifecycleOwner by owner, + DefaultLifecycleObserver { + + private var delayJob: Job? = null + set(value) { + field?.cancel() + field = value + } + private var isStopped = false + private var openWelcomeScreenWhenResumed = false + + init { + lifecycle.addObserver(this) + } + + override fun onResume(owner: LifecycleOwner) { + Timber.d( + """ + Owner resumed + |- Was stopped: $isStopped + |- Need to open welcome screen: $openWelcomeScreenWhenResumed + """.trimIndent(), + ) + isStopped = false + start() + if (openWelcomeScreenWhenResumed) { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + openWelcomeScreenWhenResumed = false + } + } + + override fun onStop(owner: LifecycleOwner) { + Timber.d("Owner stopped") + isStopped = true + } + + override fun onDestroy(owner: LifecycleOwner) { + Timber.d("Owner destroyed") + stop() + } + + fun restart() { + if (delayJob == null) return + Timber.d( + """ + Timer restart + |- Duration millis: ${duration.inWholeMilliseconds} + """.trimIndent(), + ) + start(log = false) + } + + private fun start(log: Boolean = true) { + if (log) { + Timber.d( + """ + Timer start + |- Duration millis: ${duration.inWholeMilliseconds} + """.trimIndent(), + ) + } + delayJob = createDelayJob() + } + + private fun stop(log: Boolean = true) { + if (log) { + Timber.d( + """ + Timer stop + |- Was started: ${delayJob?.isActive ?: false} + """.trimIndent(), + ) + } + delayJob = null + } + + private fun createDelayJob(): Job = lifecycleScope.launch(Dispatchers.Default) { + val startTime = System.currentTimeMillis() + delay(duration) + if (isActive) { + val userWalletsListManager = userWalletsListManagerSafe ?: return@launch + if (userWalletsListManager.hasSavedUserWallets) { + val currentTime = System.currentTimeMillis() + Timber.d( + """ + Finished + |- App is stopped: $isStopped + |- Millis passed: ${currentTime - startTime} + """.trimIndent(), + ) + userWalletsListManager.lock() + if (isStopped) { + openWelcomeScreenWhenResumed = true + } else { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index f89ccbeb0a..b7c9ad608a 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -27,7 +27,7 @@ import com.tangem.tap.common.shop.GooglePayService.Companion.LOAD_PAYMENT_DATA_R import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager -import com.tangem.tap.domain.userWalletList.di.provideDummyImplementation +import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.features.shop.redux.ShopAction import com.tangem.tap.proxy.AppStateHolder import com.tangem.wallet.R @@ -44,6 +44,10 @@ lateinit var tangemSdk: TangemSdk lateinit var tangemSdkManager: TangemSdkManager lateinit var backupService: BackupService lateinit var userWalletsListManager: UserWalletsListManager +internal var lockUserWalletsTimer: LockUserWalletsTimer? = null + private set +var userWalletsListManagerSafe: UserWalletsListManager? = null + private set var notificationsHandler: NotificationsHandler? = null private val coroutineContext: CoroutineContext @@ -77,7 +81,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac tangemSdkManager = TangemSdkManager(tangemSdk, this) appStateHolder.tangemSdkManager = tangemSdkManager backupService = BackupService.init(tangemSdk, this) - userWalletsListManager = UserWalletsListManager.provideDummyImplementation() + userWalletsListManager = UserWalletsListManager.provideBiometricImplementation( + context = applicationContext, + tangemSdkManager = tangemSdkManager, + ) + userWalletsListManagerSafe = userWalletsListManager + lockUserWalletsTimer = LockUserWalletsTimer(owner = this) store.dispatch( ShopAction.CheckIfGooglePayAvailable( @@ -176,4 +185,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac override fun removeOnActivityResultCallback(callback: OnActivityResultCallback) { onActivityResultCallbacks.remove(callback) } + + override fun onUserInteraction() { + super.onUserInteraction() + + lockUserWalletsTimer?.restart() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 82e2081d78..41b4fb59bb 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -7,6 +7,8 @@ import coil.ImageLoader import coil.ImageLoaderFactory import com.tangem.Log import com.tangem.LogFormat +import com.tangem.blockchain.common.BlockchainSdkConfig +import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.common.json.MoshiJsonConverter import com.tangem.domain.DomainLayer @@ -39,8 +41,14 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation +import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager +import com.tangem.tap.domain.walletCurrencies.di.provideDefaultImplementation import com.tangem.tap.domain.walletStores.WalletStoresManager -import com.tangem.tap.domain.walletStores.di.provideDummyImplementation +import com.tangem.tap.domain.walletStores.di.provideDefaultImplementation +import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository +import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository +import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository +import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation import com.tangem.tap.domain.walletconnect.WalletConnectRepository import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.persistence.CardBalanceStateAdapter @@ -64,10 +72,40 @@ lateinit var shopService: TangemShopService lateinit var assetReader: AssetReader lateinit var userTokensRepository: UserTokensRepository -val walletStoresManager by lazy { - WalletStoresManager.provideDummyImplementation() +private val walletStoresRepository by lazy { WalletStoresRepository.provideDefaultImplementation() } +private val walletManagersRepository by lazy { + WalletManagersRepository.provideDefaultImplementation( + walletManagerFactory = WalletManagerFactory( + blockchainSdkConfig = store.state.globalState.configManager + ?.config + ?.blockchainSdkConfig + ?: BlockchainSdkConfig(), + ), + ) +} +private val walletAmountsRepository by lazy { + WalletAmountsRepository.provideDefaultImplementation( + tangemTechService = store.state.domainNetworks.tangemTechService, + ) +} +val walletStoresManager by lazy { + WalletStoresManager.provideDefaultImplementation( + userTokensRepository = userTokensRepository, + walletStoresRepository = walletStoresRepository, + walletManagersRepository = walletManagersRepository, + walletAmountsRepository = walletAmountsRepository, + appCurrencyProvider = { store.state.globalState.appCurrency }, + ) +} +val walletCurrenciesManager by lazy { + WalletCurrenciesManager.provideDefaultImplementation( + userTokensRepository = userTokensRepository, + walletStoresRepository = walletStoresRepository, + walletManagersRepository = walletManagersRepository, + walletAmountsRepository = walletAmountsRepository, + appCurrencyProvider = { store.state.globalState.appCurrency }, + ) } - val totalFiatBalanceCalculator by lazy { TotalFiatBalanceCalculator.provideDefaultImplementation() } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 1919cd3dbf..ed66f4c74b 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -28,8 +28,8 @@ fun Store<*>.dispatchNotification(resId: Int) { } @Suppress("unused") // receiver type -fun Store<*>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) { - // TODO: Load tokens for selected user wallet. Will be created in further MRs +suspend fun Store<*>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) { + store.state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh) } fun Store<*>.dispatchToastNotification(resId: Int) { @@ -77,7 +77,6 @@ suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) { fun Store<*>.dispatchOpenUrl(url: String) { store.dispatch(NavigationAction.OpenUrl(url)) } - fun Store<*>.dispatchShare(url: String) { store.dispatch(NavigationAction.Share(url)) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index c89e7c0133..fad0770f65 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -71,7 +71,9 @@ data class AppState( companion object { fun getMiddleware(): List> { return listOf( - logMiddleware, navigationMiddleware, notificationsMiddleware, + logMiddleware, + navigationMiddleware, + notificationsMiddleware, GlobalMiddleware.handler, HomeMiddleware.handler, OnboardingNoteMiddleware.handler, @@ -90,6 +92,7 @@ data class AppState( WelcomeMiddleware().middleware, SaveWalletMiddleware().middleware, WalletSelectorMiddleware().middleware, + LockUserWalletsTimerMiddleware().middleware, ) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/LockUserWalletsTimerMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/LockUserWalletsTimerMiddleware.kt new file mode 100644 index 0000000000..67bb53a5b5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/LockUserWalletsTimerMiddleware.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.common.redux + +import com.tangem.tap.lockUserWalletsTimer +import org.rekotlin.Middleware + +class LockUserWalletsTimerMiddleware { + val middleware: Middleware = { _, _ -> + { nextDispatch -> + { action -> + lockUserWalletsTimer?.restart() + nextDispatch(action) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index 0b13471f65..dfcdf942bf 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -167,9 +167,9 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di scope.launch { tangemSdkManager.changeDisplayedCardIdNumbersCount(null) val result = tangemSdkManager.scanProduct( - userTokensRepository, - action.additionalBlockchainsToDerive, - action.messageResId, + userTokensRepository = userTokensRepository, + additionalBlockchainsToDerive = action.additionalBlockchainsToDerive, + messageRes = action.messageResId, ) withMainContext { store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index b7a54c7d6d..4d58f2467d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -5,11 +5,21 @@ import androidx.annotation.StringRes import com.tangem.Message import com.tangem.TangemSdk import com.tangem.blockchain.common.Blockchain -import com.tangem.common.* +import com.tangem.common.CardFilter +import com.tangem.common.CompletionResult +import com.tangem.common.SuccessResponse +import com.tangem.common.UserCode +import com.tangem.common.UserCodeType +import com.tangem.common.biometric.BiometricManager import com.tangem.common.card.FirmwareVersion -import com.tangem.common.core.* +import com.tangem.common.core.CardIdDisplayFormat +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.Config +import com.tangem.common.core.TangemSdkError +import com.tangem.common.core.UserCodeRequestPolicy import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.map import com.tangem.common.usersCode.UserCodeRepository import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse @@ -28,6 +38,7 @@ import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.domain.userWalletList.di.USER_WALLETS_BIOMETRIC_KEY_NAME import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive @@ -42,8 +53,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co val canEnrollBiometrics: Boolean get() = tangemSdk.biometricManager.canEnrollBiometrics + val biometricManager: BiometricManager + get() = tangemSdk.biometricManager + suspend fun scanProduct( userTokensRepository: UserTokensRepository, + cardId: String? = null, additionalBlockchainsToDerive: Collection? = null, messageRes: Int? = null, useBiometricsForAccessCode: Boolean = false, @@ -52,8 +67,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) return runTaskAsyncReturnOnMain( - runnable = ScanProductTask(null, userTokensRepository, additionalBlockchainsToDerive), - cardId = null, initialMessage = message, + runnable = ScanProductTask( + card = null, + userTokensRepository = userTokensRepository, + additionalBlockchainsToDerive = additionalBlockchainsToDerive, + ), + cardId = cardId, + initialMessage = message, ).also { sendScanResultsToAnalytics(it) } } @@ -89,7 +109,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun derivePublicKeys( cardId: String, derivations: Map>, + useBiometricsForAccessCode: Boolean = false, ): CompletionResult { + setAccessCodeRequestPolicy(useBiometricsForAccessCode) return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId) } @@ -102,6 +124,16 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co .map { CardDTO(it) } } + suspend fun unlockBiometricKeys(): CompletionResult { + return biometricManager.authenticate( + mode = BiometricManager.AuthenticationMode.Keys( + USER_WALLETS_BIOMETRIC_KEY_NAME, + tangemSdk.config.userCodesBiometricKeyName, + ), + ) + .map { /* no-op */ } + } + suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { return createUserCodeRepository().save( cardIds = cardsIds, @@ -110,6 +142,20 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co stringValue = accessCode, ), ) + .map { + biometricManager.unauthenticate( + keyName = tangemSdk.config.userCodesBiometricKeyName, + ) + } + } + + suspend fun clearSavedUserCodes(): CompletionResult { + return createUserCodeRepository().clear() + .map { + biometricManager.unauthenticate( + keyName = tangemSdk.config.userCodesBiometricKeyName, + ) + } } suspend fun setPasscode(cardId: String?): CompletionResult { diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index bb07af060e..368ed91c74 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -1,11 +1,8 @@ package com.tangem.tap.domain -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkConfig -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.blockchain.common.* +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse @@ -13,6 +10,7 @@ import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.ThrottlerWithValues import com.tangem.domain.common.extensions.withMainContext +import com.tangem.operations.attestation.Attestation import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalAction @@ -20,16 +18,21 @@ import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.domain.extensions.makeWalletManagersForApp +import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.domain.walletStores.WalletStoresError import com.tangem.tap.features.demo.isDemoCard import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.store import com.tangem.tap.userTokensRepository +import com.tangem.tap.walletStoresManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import timber.log.Timber class TapWalletManager { val walletManagerFactory: WalletManagerFactory @@ -81,6 +84,60 @@ class TapWalletManager { } } + suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean) { + val scanResponse = userWallet.scanResponse + val card = scanResponse.card + val attestationFailed = card.attestation.status == Attestation.Status.Failed + + store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) + updateConfigManager(scanResponse) + + withMainContext { + store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) + store.dispatch(WalletConnectAction.ResetState) + store.dispatch(GlobalAction.SaveScanNoteResponse(scanResponse)) + store.dispatch(WalletConnectAction.RestoreSessions(scanResponse)) + store.dispatch(WalletAction.UserWalletChanged(userWallet)) + store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) + store.dispatch(WalletAction.Warnings.CheckIfNeeded) + + if (refresh) { + loadData(userWallet, refresh = true) + } + } + } + + suspend fun loadData(userWallet: UserWallet, refresh: Boolean = false) { + walletStoresManager.fetch(userWallet, refresh) + .doOnSuccess { + Timber.d("Wallet stores fetched for ${userWallet.walletId}") + store.dispatchOnMain(WalletAction.LoadData.Success) + } + .doOnFailure { error -> + val errorAction = when (error) { + is WalletStoresError -> when (error) { + is WalletStoresError.FetchFiatRatesError, + is WalletStoresError.UpdateWalletManagerError, + -> WalletAction.LoadData.Failure(error = null) + is WalletStoresError.WalletManagerNotCreated -> WalletAction.LoadData.Failure( + error = TapError.WalletManager.CreationError, + ) + is WalletStoresError.UnknownBlockchain -> WalletAction.LoadData.Failure( + error = TapError.UnknownBlockchain, + ) + is WalletStoresError.NoInternetConnection -> WalletAction.LoadData.Failure( + error = TapError.NoInternetConnection, + ) + } + else -> WalletAction.LoadData.Failure(error = null) + } + + Timber.e(error, "Wallet stores fetching failed for ${userWallet.walletId}") + + store.dispatchOnMain(errorAction) + } + } + suspend fun onCardScanned(data: ScanResponse) { walletManagersThrottler.clear() store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data) @@ -143,7 +200,7 @@ class TapWalletManager { private fun checkIfDerivationsAreMissing(blockchainNetworks: List, scanResponse: ScanResponse) { blockchainNetworks.map { if (it.tokens.isNotEmpty()) { - WalletAction.MultiWallet.AddTokens(it.tokens, it, false) + WalletAction.MultiWallet.AddTokens(it.tokens, it) } } val missingDerivations = blockchainNetworks @@ -171,7 +228,6 @@ class TapWalletManager { WalletAction.MultiWallet.AddBlockchains( blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)), walletManagers = listOf(primaryWalletManager), - save = false, ), WalletAction.LoadFiatRate(), ) @@ -187,7 +243,6 @@ class TapWalletManager { WalletAction.MultiWallet.AddBlockchains( blockchains = blockchainNetworks, walletManagers = walletManagers, - save = false, ), ) @@ -197,7 +252,6 @@ class TapWalletManager { WalletAction.MultiWallet.AddTokens( tokens = it.tokens, blockchain = it, - save = false, ), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index bd530b0796..b95d540bf0 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -106,7 +106,7 @@ class ConfigManager { blockchairApiKey = values.blockchairApiKey, blockchairAuthorizationToken = values.blockchairAuthorizationToken, blockcypherTokens = values.blockcypherTokens, - infuraProjectId = values.infuraProjectId + infuraProjectId = values.infuraProjectId, ), appsFlyerDevKey = values.appsFlyerDevKey, amplitudeApiKey = values.amplitudeApiKey, diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 4cb6fbfdd8..a4433cb32a 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -22,4 +22,6 @@ data class UserWallet( ) { val cardId: String get() = scanResponse.card.cardId + + internal var isSaved: Boolean = true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt index 173ed53786..439b900c39 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt @@ -35,6 +35,12 @@ data class WalletDataModel( open val pendingTransactions: List = emptyList() open val errorMessage: String? = null open val isErrorStatus: Boolean = false + + fun asRefreshing() = Refreshing( + amount = amount, + pendingTransactions = pendingTransactions, + errorMessage = errorMessage, + ) } object Loading : Status() diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt index 1753b9b0ba..cba89801a3 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt @@ -10,7 +10,8 @@ import java.math.BigDecimal * Contains info about the blockchain and its currencies * @param userWalletId ID of the [UserWallet] which uses that store * @param blockchainNetwork Store's [BlockchainNetwork] - * @param walletManager Store's [WalletManager], may be null if it fails to create this manager + * @param walletManager Store's [WalletManager], may be null if it fails to create this manager. TODO: Remove after + * WalletMiddleware refactoring * @param walletsData List of [WalletDataModel] which represents store's blockchain currency and tokens currencies * @param walletRent Store's [WalletRent], null if store has no rent or currency balance is greater then * [WalletRent.exemptionAmount] @@ -18,6 +19,7 @@ import java.math.BigDecimal data class WalletStoreModel( val userWalletId: UserWalletId, val blockchainNetwork: BlockchainNetwork, + @Deprecated("Don't use it, will be removed") val walletManager: WalletManager?, val walletsData: List, val walletRent: WalletRent?, diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt new file mode 100644 index 0000000000..004184aacf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -0,0 +1,77 @@ +package com.tangem.tap.domain.model.builders + +import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider +import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.WalletDataModel +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.redux.reducers.createAddressesData +import java.math.BigDecimal + +class WalletStoreBuilder( + private val userWallet: UserWallet, +) { + private var walletManager: WalletManager? = null + private var blockchainNetwork: BlockchainNetwork? = null + + fun setWalletManager(walletManager: WalletManager?) = this.apply { + this.walletManager = walletManager + } + + fun setBlockchainNetwork(blockchainNetwork: BlockchainNetwork?) = this.apply { + this.blockchainNetwork = blockchainNetwork + } + + fun build(): WalletStoreModel { + val blockchainNetwork = this.blockchainNetwork + ?: walletManager?.let(BlockchainNetwork::fromWalletManager) + ?: error("Blockchain network and wallet manager must not be null") + + val blockchainWalletData = blockchainNetwork.getBlockchainWalletData(walletManager) + val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager) + + return WalletStoreModel( + userWalletId = userWallet.walletId, + blockchainNetwork = blockchainNetwork, + walletManager = walletManager, + walletsData = (listOf(blockchainWalletData) + tokensWalletsData), + walletRent = null, + ) + } +} + +private fun BlockchainNetwork.getBlockchainWalletData(walletManager: WalletManager?): WalletDataModel { + return WalletDataModel( + currency = Currency.Blockchain( + blockchain = blockchain, + derivationPath = derivationPath, + ), + status = WalletDataModel.Loading, + walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(), + existentialDeposit = getExistentialDeposit(walletManager), + fiatRate = null, + ) +} + +private fun BlockchainNetwork.getTokensWalletsData(walletManager: WalletManager?): List { + return this.tokens + .map { token -> + WalletDataModel( + currency = Currency.Token( + token = token, + blockchain = blockchain, + derivationPath = derivationPath, + ), + status = WalletDataModel.Loading, + walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(), + existentialDeposit = getExistentialDeposit(walletManager), + fiatRate = null, + ) + } +} + +private fun getExistentialDeposit(walletManager: WalletManager?): BigDecimal? { + return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 954888733e..eef2f2afb5 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -46,6 +46,7 @@ object ScanCardProcessor { suspend fun scan( useBiometricsForAccessCode: Boolean = false, additionalBlockchainsToDerive: Collection? = null, + cardId: String? = null, onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {}, onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {}, onWalletNotCreated: suspend (() -> Unit) = {}, @@ -56,6 +57,7 @@ object ScanCardProcessor { onScanStateChange(true) tangemSdkManager.scanProduct( userTokensRepository = userTokensRepository, + cardId = cardId, additionalBlockchainsToDerive = additionalBlockchainsToDerive, useBiometricsForAccessCode = useBiometricsForAccessCode, ) @@ -202,7 +204,7 @@ object ScanCardProcessor { } else { if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { onWalletNotCreated() - store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly)) + store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } } else { delay(DELAY_SDK_DIALOG_CLOSE) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 91006e8dab..a219814de1 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -59,6 +59,8 @@ class CreateProductWalletTask( private val type: ProductType, ) : CardSessionRunnable { + override val allowsAccessCodeFromRepository: Boolean = false + override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 575f5fc05f..537500d0b4 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -45,6 +45,9 @@ class ScanProductTask( private val additionalBlockchainsToDerive: Collection? = null, ) : CardSessionRunnable { + override val allowsAccessCodeFromRepository: Boolean + get() = !additionalBlockchainsToDerive.isNullOrEmpty() + override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index c29d226df4..2e2e0f9642 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -19,16 +19,21 @@ class TwinCardsManager( card: CardDTO, assetReader: AssetReader, ) { - - private val currentCardId: String = card.cardId + private val firstCardId: String = card.cardId + private var secondCardId: String? = null private var currentCardPublicKey: String? = null - private var secondCardPublicKey: String? = null + var secondCardPublicKey: String? = null + private set private val issuerKeyPair: KeyPair = getIssuerKeys(assetReader, card.issuer.publicKey.toHexString()) suspend fun createFirstWallet(message: Message): CompletionResult { - val response = tangemSdkManager.runTaskAsync(CreateFirstTwinWalletTask(), currentCardId, message) + val response = tangemSdkManager.runTaskAsync( + runnable = CreateFirstTwinWalletTask(), + cardId = firstCardId, + initialMessage = message, + ) when (response) { is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString() is CompletionResult.Failure -> {} @@ -43,6 +48,7 @@ class TwinCardsManager( ): CompletionResult { val task = CreateSecondTwinWalletTask( firstPublicKey = currentCardPublicKey!!, + firstCardId = firstCardId, issuerKeys = issuerKeyPair, preparingMessage = preparingMessage, creatingWalletMessage = creatingWalletMessage, @@ -51,6 +57,7 @@ class TwinCardsManager( when (response) { is CompletionResult.Success -> { secondCardPublicKey = response.data.wallet.publicKey.toHexString() + secondCardId = response.data.cardId } is CompletionResult.Failure -> {} } @@ -59,8 +66,9 @@ class TwinCardsManager( suspend fun complete(message: Message): Result { val response = tangemSdkManager.runTaskAsync( - FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeyPair), - currentCardId, message, + runnable = FinalizeTwinTask(secondCardPublicKey!!.hexToBytes(), issuerKeyPair), + cardId = firstCardId, + initialMessage = message, ) return when (response) { is CompletionResult.Success -> Result.Success(response.data) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletListError.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletListError.kt new file mode 100644 index 0000000000..6291f7ae4d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletListError.kt @@ -0,0 +1,40 @@ +package com.tangem.tap.domain.userWalletList + +import com.tangem.common.core.TangemError +import com.tangem.wallet.R + +sealed class UserWalletListError(code: Int) : TangemError(code) { + override val silent: Boolean + get() = (cause as? TangemError)?.silent == true + + override val messageResId: Int? = null + + object WalletAlreadySaved : UserWalletListError(code = 60001) { + override var customMessage: String = "This wallet has already been saved, you can add another one" + override val messageResId: Int = R.string.user_wallet_list_error_wallet_already_saved + } + + class SaveEncryptionKeysError( + override val cause: Throwable, + ) : UserWalletListError(code = 60001) { + override var customMessage: String = "Encryption keys could not be saved: ${cause.localizedMessage}" + } + + class ReceiveEncryptionKeysError( + override val cause: Throwable, + ) : UserWalletListError(code = 60002) { + override var customMessage: String = "Encryption keys could not be received: ${cause.localizedMessage}" + } + + class SaveSensitiveInformationError( + override val cause: Throwable, + ) : UserWalletListError(code = 60003) { + override var customMessage: String = "Sensitive information could not be saved: ${cause.localizedMessage}" + } + + class ReceiveSensitiveInformationError( + override val cause: Throwable, + ) : UserWalletListError(code = 60004) { + override var customMessage: String = "Sensitive information could not be received: ${cause.localizedMessage}" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt index f60c7f3de0..28475b75fa 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt @@ -1,8 +1,70 @@ package com.tangem.tap.domain.userWalletList.di +import android.content.Context +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tangem.common.json.TangemSdkAdapter +import com.tangem.common.services.secure.SecureStorage +import com.tangem.tangem_sdk_new.storage.AndroidSecureStorage +import com.tangem.tangem_sdk_new.storage.createEncryptedSharedPreferences +import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager -import com.tangem.tap.domain.userWalletList.implementation.DummyUserWalletsListManager +import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager +import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository +import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository +import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository +import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.json.* -fun UserWalletsListManager.Companion.provideDummyImplementation(): UserWalletsListManager { - return DummyUserWalletsListManager() +const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage" +const val USER_WALLETS_BIOMETRIC_KEY_NAME = "user_wallets" + +fun UserWalletsListManager.Companion.provideBiometricImplementation( + context: Context, + tangemSdkManager: TangemSdkManager, +): UserWalletsListManager { + val moshi = Moshi.Builder() + .add(WalletDerivedKeysMapAdapter()) + .add(ScanResponseDerivedKeysMapAdapter()) + .add(ByteArrayKeyAdapter()) + .add(ExtendedPublicKeysMapAdapter()) + .add(CardBackupStatusAdapter()) + .add(TangemSdkAdapter.DateAdapter()) + .add(TangemSdkAdapter.DerivationPathAdapter()) + .add(TangemSdkAdapter.DerivationNodeAdapter()) + .add(KotlinJsonAdapterFactory()) + .build() + + val secureStorage = AndroidSecureStorage( + preferences = SecureStorage.createEncryptedSharedPreferences( + context = context, + storageName = USER_WALLETS_STORAGE_NAME, + ), + ) + + val keysRepository = BiometricUserWalletsKeysRepository( + biometricKeyName = USER_WALLETS_BIOMETRIC_KEY_NAME, + moshi = moshi, + secureStorage = secureStorage, + biometricManager = tangemSdkManager.biometricManager, + ) + val publicInformationRepository = DefaultUserWalletsPublicInformationRepository( + moshi = moshi, + secureStorage = secureStorage, + ) + val sensitiveInformationRepository = DefaultUserWalletsSensitiveInformationRepository( + moshi = moshi, + secureStorage = secureStorage, + ) + val selectedUserWalletRepository = DefaultSelectedUserWalletRepository( + secureStorage = secureStorage, + ) + + return BiometricUserWalletsListManager( + tangemSdkManager = tangemSdkManager, + keysRepository = keysRepository, + publicInformationRepository = publicInformationRepository, + sensitiveInformationRepository = sensitiveInformationRepository, + selectedUserWalletRepository = selectedUserWalletRepository, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt new file mode 100644 index 0000000000..d7ab7b396d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -0,0 +1,303 @@ +package com.tangem.tap.domain.userWalletList.implementation + +import com.tangem.common.* +import com.tangem.domain.common.util.UserWalletId +import com.tangem.domain.common.util.encryptionKey +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.UserWalletListError +import com.tangem.tap.domain.userWalletList.UserWalletsListManager +import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey +import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository +import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository +import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository +import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.toUserWallets +import com.tangem.tap.domain.userWalletList.utils.updateWith +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.* +import timber.log.Timber + +@OptIn(ExperimentalCoroutinesApi::class) +internal class BiometricUserWalletsListManager( + private val tangemSdkManager: TangemSdkManager, + private val keysRepository: UserWalletsKeysRepository, + private val publicInformationRepository: UserWalletsPublicInformationRepository, + private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository, + private val selectedUserWalletRepository: SelectedUserWalletRepository, +) : UserWalletsListManager { + private val state = MutableStateFlow(State()) + + override val userWallets: Flow> + get() = state + .mapLatest { it.wallets } + .distinctUntilChanged() + + override val selectedUserWallet: Flow + get() = state + .mapLatest { state -> + state.wallets.find { + it.walletId == state.selectedWalletId + } + } + .filterNotNull() + .distinctUntilChanged() + + override val selectedUserWalletSync: UserWallet? + get() = findSelectedWallet() + + override val isLocked: Flow + get() = state + .mapLatest { it.isLocked } + .distinctUntilChanged() + + override val isLockedSync: Boolean + get() = state.value.isLocked + + override val hasSavedUserWallets: Boolean + get() = selectedUserWalletRepository.get() != null + + override suspend fun unlockWithBiometry(): CompletionResult { + return unlockWithBiometryInternal() + .map { selectedUserWalletSync } + } + + override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { + state.update { prevState -> + userWallet.isSaved = false + prevState.copy( + encryptionKeys = listOf(UserWalletEncryptionKey(userWallet)), + wallets = listOf(userWallet), + ) + } + + return loadModels() + .map { + state.update { prevState -> + prevState.copy( + selectedWalletId = userWallet.walletId, + isLocked = prevState.wallets.size != 1, + ) + } + } + } + + override fun lock() { + tangemSdkManager.biometricManager.unauthenticate() + state.update { prevState -> + prevState.copy( + encryptionKeys = emptyList(), + isLocked = true, + ) + } + } + + override suspend fun selectWallet(walletId: UserWalletId): CompletionResult = catching { + if (state.value.selectedWalletId == walletId) { + return@catching findSelectedWallet()!! + } + + if (!state.value.isLocked) { + selectedUserWalletRepository.set(walletId) + + state.update { prevState -> + prevState.copy( + selectedWalletId = walletId, + ) + } + } + + findSelectedWallet()!! + } + + override suspend fun save(userWallet: UserWallet): CompletionResult { + return saveInternal(userWallet, override = false) + } + + override suspend fun update(userWallet: UserWallet): CompletionResult { + return saveInternal(userWallet, override = true) + } + + override suspend fun delete(walletIds: List): CompletionResult { + if (state.value.isLocked) { + return CompletionResult.Success(Unit) + } + + val walletIdsToRemove = state.value.wallets + .map { it.walletId } + .filter { it in walletIds } + + changeSelectedWalletIfNeeded(walletIdsToRemove) + + return sensitiveInformationRepository.delete(walletIdsToRemove) + .flatMap { publicInformationRepository.delete(walletIdsToRemove) } + .flatMap { keysRepository.delete(walletIdsToRemove) } + .map { keys -> + state.update { prevState -> + prevState.copy( + encryptionKeys = keys, + wallets = prevState.wallets.filter { it.walletId !in walletIdsToRemove }, + ) + } + } + .flatMap { loadModels() } + } + + override suspend fun clear(): CompletionResult { + return sensitiveInformationRepository.delete( + walletIds = state.value.wallets.map { it.walletId }, + ) + .flatMap { publicInformationRepository.clear() } + .flatMap { keysRepository.clear() } + .map { + selectedUserWalletRepository.set(null) + tangemSdkManager.biometricManager.unauthenticate() + state.update { State() } + } + } + + override suspend fun get(walletId: UserWalletId): CompletionResult = withUnlock { + return catching { + state.value.wallets.first { it.walletId == walletId } + } + } + + private suspend fun saveInternal( + userWallet: UserWallet, + override: Boolean, + ): CompletionResult = withUnlock { + val isWalletSaved = state.value.wallets + .filter { it.isSaved } + .flatMap(UserWallet::cardsInWallet) + .contains(userWallet.cardId) + + if (isWalletSaved && !override) { + CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) + } else { + keysRepository.save( + walletId = userWallet.walletId, + encryptionKey = userWallet.scanResponse.card.encryptionKey, + ) + .doOnSuccess { keys -> + state.update { prevState -> + prevState.copy( + encryptionKeys = (keys + prevState.encryptionKeys).distinctBy { it.walletId }, + ) + } + } + .flatMap { publicInformationRepository.save(userWallet) } + .flatMap { sensitiveInformationRepository.save(userWallet) } + .flatMap { loadModels() } + .doOnSuccess { + userWallet.isSaved = true + } + } + } + + private suspend inline fun withUnlock( + block: () -> CompletionResult, + ): CompletionResult { + return (if (state.value.isLocked) unlockWithBiometryInternal() else CompletionResult.Success(Unit)) + .flatMap { block() } + } + + private suspend fun unlockWithBiometryInternal(): CompletionResult { + return keysRepository.getAll() + .map { keys -> + state.update { prevState -> + prevState.copy( + encryptionKeys = (keys + prevState.encryptionKeys).distinctBy { it.walletId }, + ) + } + } + .flatMap { loadModels() } + .map { + state.update { prevState -> + prevState.copy( + isLocked = false, + ) + } + } + } + + private suspend fun loadModels(): CompletionResult { + return getSavedUserWallets() + .map { userWallets -> + if (userWallets.isNotEmpty()) state.update { prevState -> + val wallets = (userWallets + prevState.wallets).distinctBy { it.walletId } + + prevState.copy( + wallets = wallets, + selectedWalletId = findOrSetSelectedWallet(prevState.selectedWalletId, wallets), + ) + } + } + .doOnFailure { error -> + Timber.e(error, "Unable to load user wallets") + } + } + + private suspend fun getSavedUserWallets(): CompletionResult> { + return publicInformationRepository.getAll() + .map { it.toUserWallets() } + .flatMap { userWallets -> + sensitiveInformationRepository.getAll(state.value.encryptionKeys) + .map { walletIdToSensitiveInformation -> + userWallets.updateWith(walletIdToSensitiveInformation) + } + } + } + + private fun findOrSetSelectedWallet( + prevSelectedWalletId: UserWalletId?, + userWallets: List, + ): UserWalletId? { + return prevSelectedWalletId + ?: (selectedUserWalletRepository.get() + ?: (userWallets.firstOrNull()?.walletId + ?.also { selectedUserWalletRepository.set(it) })) + } + + private fun changeSelectedWalletIfNeeded( + walletsIdsToRemove: List, + ) { + val remainingWallets = state.value.wallets.filter { + it.walletId !in walletsIdsToRemove + } + val selectedWallet = findSelectedWallet() + when { + remainingWallets.isEmpty() -> { + state.update { prevState -> + prevState.copy( + selectedWalletId = null, + ) + } + selectedUserWalletRepository.set(null) + } + !remainingWallets.contains(selectedWallet) -> { + val newSelectedWallet = remainingWallets.first() + state.update { prevState -> + prevState.copy( + selectedWalletId = newSelectedWallet.walletId, + ) + } + selectedUserWalletRepository.set(newSelectedWallet.walletId) + } + } + } + + private fun findSelectedWallet(): UserWallet? { + return with(state.value) { + wallets.find { + it.walletId == selectedWalletId + } + } + } + + private data class State( + val encryptionKeys: List = emptyList(), + val wallets: List = emptyList(), + val selectedWalletId: UserWalletId? = null, + val isLocked: Boolean = true, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt new file mode 100644 index 0000000000..cdf1edbdfb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.domain.userWalletList.model + +import com.squareup.moshi.JsonClass +import com.tangem.domain.common.util.UserWalletId +import com.tangem.domain.common.util.encryptionKey +import com.tangem.tap.domain.model.UserWallet + +@JsonClass(generateAdapter = true) +internal data class UserWalletEncryptionKey( + val walletId: UserWalletId, + val encryptionKey: ByteArray, +) { + constructor(userWallet: UserWallet) : this( + walletId = userWallet.walletId, + encryptionKey = userWallet.scanResponse.card.encryptionKey, + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is UserWalletEncryptionKey) return false + + if (walletId != other.walletId) return false + if (!encryptionKey.contentEquals(other.encryptionKey)) return false + + return true + } + + override fun hashCode(): Int { + var result = walletId.hashCode() + result = 31 * result + encryptionKey.contentHashCode() + return result + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt new file mode 100644 index 0000000000..b2308343ff --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt @@ -0,0 +1,20 @@ +package com.tangem.tap.domain.userWalletList.model + +import com.squareup.moshi.JsonClass +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.util.UserWalletId + +@JsonClass(generateAdapter = true) +internal data class UserWalletSensitiveInformation( + val wallets: List, +) + +@JsonClass(generateAdapter = true) +internal data class UserWalletPublicInformation( + val name: String, + val walletId: UserWalletId, + val artworkUrl: String, + val cardsInWallet: Set, + val scanResponse: ScanResponse, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/SelectedUserWalletRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/SelectedUserWalletRepository.kt new file mode 100644 index 0000000000..aee7766d49 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/SelectedUserWalletRepository.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.domain.userWalletList.repository + +import com.tangem.domain.common.util.UserWalletId + +internal interface SelectedUserWalletRepository { + fun get(): UserWalletId? + fun set(walletId: UserWalletId?) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt new file mode 100644 index 0000000000..9fc67b367b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.domain.userWalletList.repository + +import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey + +internal interface UserWalletsKeysRepository { + suspend fun getAll(): CompletionResult> + suspend fun save(walletId: UserWalletId, encryptionKey: ByteArray): CompletionResult> + suspend fun delete(walletIds: List): CompletionResult> + suspend fun clear(): CompletionResult +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt new file mode 100644 index 0000000000..8765a6b804 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.domain.userWalletList.repository + +import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation + +internal interface UserWalletsPublicInformationRepository { + suspend fun save(userWallet: UserWallet): CompletionResult + + suspend fun getAll(): CompletionResult> + + suspend fun delete(walletIds: List): CompletionResult + suspend fun clear(): CompletionResult +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt new file mode 100644 index 0000000000..dcc5677814 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.domain.userWalletList.repository + +import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey +import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation + +internal interface UserWalletsSensitiveInformationRepository { + suspend fun save(userWallet: UserWallet): CompletionResult + suspend fun getAll( + encryptionKeys: List, + ): CompletionResult> + + suspend fun delete(walletIds: List): CompletionResult +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt new file mode 100644 index 0000000000..e1a2ae7329 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -0,0 +1,106 @@ +package com.tangem.tap.domain.userWalletList.repository.implementation + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.tangem.common.CompletionResult +import com.tangem.common.biometric.BiometricManager +import com.tangem.common.biometric.BiometricStorage +import com.tangem.common.flatMap +import com.tangem.common.map +import com.tangem.common.mapFailure +import com.tangem.common.services.secure.SecureStorage +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.common.extensions.replaceByOrAdd +import com.tangem.tap.domain.userWalletList.UserWalletListError +import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey +import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository + +internal class BiometricUserWalletsKeysRepository( + biometricKeyName: String, + moshi: Moshi, + secureStorage: SecureStorage, + biometricManager: BiometricManager, +) : UserWalletsKeysRepository { + private val biometricStorage = BiometricStorage( + biometricKeyName = biometricKeyName, + biometricManager = biometricManager, + secureStorage = secureStorage, + ) + private val walletsKeysAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, UserWalletEncryptionKey::class.java), + ) + + override suspend fun getAll(): CompletionResult> { + return biometricStorage.get(key = StorageKey.WalletEncryptionKeys.name) + .map { encryptionKeys -> + encryptionKeys.decodeToKeys() + } + .mapFailure { error -> + UserWalletListError.ReceiveEncryptionKeysError(error.cause ?: error) + } + } + + override suspend fun save( + walletId: UserWalletId, + encryptionKey: ByteArray, + ): CompletionResult> { + return getAll() + .flatMap { keys -> + if (keys.any { it.walletId == walletId }) { + return@flatMap CompletionResult.Success(Unit) + } + + val encodedKeys = keys.toMutableList() + .apply { + replaceByOrAdd(UserWalletEncryptionKey(walletId, encryptionKey)) { + it.walletId == walletId + } + } + .encode() + + biometricStorage.store( + key = StorageKey.WalletEncryptionKeys.name, + data = encodedKeys, + ) + } + .flatMap { getAll() } + .mapFailure { error -> + UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) + } + } + + override suspend fun delete(walletIds: List): CompletionResult> { + return getAll() + .map { keys -> + val keysToRemove = keys.filter { it.walletId in walletIds }.toSet() + (keys - keysToRemove).encode() + } + .flatMap { encodedKeys -> + biometricStorage.store( + key = StorageKey.WalletEncryptionKeys.name, + data = encodedKeys, + ) + } + .flatMap { getAll() } + } + + override suspend fun clear(): CompletionResult { + return biometricStorage.delete(key = StorageKey.WalletEncryptionKeys.name) + } + + private fun List.encode(): ByteArray { + return this.let(walletsKeysAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + + private fun ByteArray?.decodeToKeys(): List { + return this?.decodeToString(throwOnInvalidSequence = true) + ?.let(walletsKeysAdapter::fromJson) + .orEmpty() + } + + private enum class StorageKey { + WalletEncryptionKeys + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt new file mode 100644 index 0000000000..ec44dbd914 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultSelectedUserWalletRepository.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.domain.userWalletList.repository.implementation + +import com.tangem.common.services.secure.SecureStorage +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository + +internal class DefaultSelectedUserWalletRepository( + private val secureStorage: SecureStorage, +) : SelectedUserWalletRepository { + override fun get(): UserWalletId? { + return secureStorage.get(StorageKey.SelectedWalletId.name) + ?.decodeToString(throwOnInvalidSequence = true) + ?.let { UserWalletId(it) } + } + + override fun set(walletId: UserWalletId?) { + if (walletId == null) { + secureStorage.delete(StorageKey.SelectedWalletId.name) + } else { + secureStorage.store( + data = walletId.stringValue.encodeToByteArray(throwOnInvalidSequence = true), + account = StorageKey.SelectedWalletId.name, + ) + } + } + + private enum class StorageKey { + SelectedWalletId + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt new file mode 100644 index 0000000000..46d0fc073b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -0,0 +1,84 @@ +package com.tangem.tap.domain.userWalletList.repository.implementation + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.tangem.common.CompletionResult +import com.tangem.common.catching +import com.tangem.common.flatMap +import com.tangem.common.services.secure.SecureStorage +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.common.extensions.replaceByOrAdd +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation +import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository +import com.tangem.tap.domain.userWalletList.utils.publicInformation +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +internal class DefaultUserWalletsPublicInformationRepository( + moshi: Moshi, + private val secureStorage: SecureStorage, +) : UserWalletsPublicInformationRepository { + private val publicInformationAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, UserWalletPublicInformation::class.java), + ) + + override suspend fun save(userWallet: UserWallet): CompletionResult { + return getAll() + .flatMap { savedInformation -> + val infoToSave = withContext(Dispatchers.Default) { + savedInformation.toMutableList() + .apply { + replaceByOrAdd(userWallet.publicInformation) { + userWallet.walletId == it.walletId + } + } + } + + save(infoToSave) + } + } + + override suspend fun getAll(): CompletionResult> = catching { + withContext(Dispatchers.IO) { + secureStorage.get(StorageKey.UserWalletPublicInformation.name) + ?.decodeToString() + ?.let(publicInformationAdapter::fromJson) + .orEmpty() + } + } + + override suspend fun delete(walletIds: List): CompletionResult { + return getAll() + .flatMap { publicInformation -> + val infoToRemove = publicInformation + .filter { it.walletId in walletIds } + .toSet() + + save( + publicInformation = publicInformation - infoToRemove, + ) + } + } + + override suspend fun clear(): CompletionResult = catching { + secureStorage.delete(StorageKey.UserWalletPublicInformation.name) + } + + @JvmName("saveWithPublicInformation") + private suspend fun save( + publicInformation: List, + ): CompletionResult = catching { + withContext(Dispatchers.IO) { + publicInformation + .let(publicInformationAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + .also { secureStorage.store(it, StorageKey.UserWalletPublicInformation.name) } + } + } + + private enum class StorageKey { + UserWalletPublicInformation + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt new file mode 100644 index 0000000000..c338d17c67 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -0,0 +1,142 @@ +package com.tangem.tap.domain.userWalletList.repository.implementation + +import android.security.keystore.KeyProperties +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.tangem.common.CompletionResult +import com.tangem.common.catching +import com.tangem.common.mapFailure +import com.tangem.common.services.secure.SecureStorage +import com.tangem.domain.common.util.UserWalletId +import com.tangem.domain.common.util.encryptionKey +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.UserWalletListError +import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey +import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation +import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.sensitiveInformation +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +internal class DefaultUserWalletsSensitiveInformationRepository( + moshi: Moshi, + private val secureStorage: SecureStorage, +) : UserWalletsSensitiveInformationRepository { + private val sensitiveInformationAdapter: JsonAdapter = moshi.adapter( + UserWalletSensitiveInformation::class.java, + ) + private val cipher: Cipher by lazy { + Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") + } + + override suspend fun save(userWallet: UserWallet): CompletionResult { + return catching { + withContext(Dispatchers.Default) { + userWallet.sensitiveInformation + .let(sensitiveInformationAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + .encryptAndStoreIv( + walletId = userWallet.walletId, + encryptionKey = userWallet.scanResponse.card.encryptionKey, + ) + .let { encryptedInformation -> + withContext(Dispatchers.IO) { + secureStorage.store( + data = encryptedInformation, + account = StorageKey.SensitiveInformation(userWallet.walletId).name, + ) + } + } + } + } + .mapFailure { error -> + UserWalletListError.SaveSensitiveInformationError(error.cause ?: error) + } + } + + override suspend fun getAll( + encryptionKeys: List, + ): CompletionResult> { + return catching { + if (encryptionKeys.isEmpty()) { + return@catching emptyMap() + } + + val keyToEncryptedInformation = withContext(Dispatchers.IO) { + encryptionKeys.associateWith { encryptionKey -> + secureStorage.get(StorageKey.SensitiveInformation(encryptionKey.walletId).name) + } + } + + withContext(Dispatchers.Default) { + val keyToInformation = + mutableMapOf() + + keyToEncryptedInformation.forEach { (key, encryptedInformation) -> + val information = encryptedInformation + ?.getIvAndDecrypt( + walletId = key.walletId, + encryptionKey = key.encryptionKey, + ) + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(sensitiveInformationAdapter::fromJson) + + if (information != null) { + keyToInformation[key.walletId] = information + } + } + + keyToInformation + } + } + .mapFailure { error -> + UserWalletListError.ReceiveSensitiveInformationError(error.cause ?: error) + } + } + + override suspend fun delete(walletIds: List): CompletionResult = catching { + walletIds + .forEach { walletId -> + secureStorage.delete(StorageKey.SensitiveInformation(walletId).name) + } + } + + private fun ByteArray.encryptAndStoreIv(walletId: UserWalletId, encryptionKey: ByteArray): ByteArray { + val secretKey = SecretKeySpec(encryptionKey, algorithm) + cipher.init(Cipher.ENCRYPT_MODE, secretKey) + val encryptedData = cipher.doFinal(this) + secureStorage.store(data = cipher.iv, account = StorageKey.SensitiveInformationIv(walletId).name) + return encryptedData + } + + private fun ByteArray.getIvAndDecrypt(walletId: UserWalletId, encryptionKey: ByteArray): ByteArray? { + val iv = secureStorage.get(StorageKey.SensitiveInformationIv(walletId).name) + ?: error("IV not found") + val ivParam = IvParameterSpec(iv) + val secretKeySpec = SecretKeySpec(encryptionKey, algorithm) + return cipher + .also { it.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParam) } + .doFinal(this) + } + + private sealed interface StorageKey { + val name: String + + class SensitiveInformation(walletId: UserWalletId) : StorageKey { + override val name: String = "user_wallet_sensitive_information_${walletId.stringValue}" + } + + class SensitiveInformationIv(walletId: UserWalletId) : StorageKey { + override val name: String = "user_wallet_sensitive_information_iv_${walletId.stringValue}" + } + } + + companion object { + private const val algorithm = KeyProperties.KEY_ALGORITHM_AES + private const val blockMode = KeyProperties.BLOCK_MODE_CBC + private const val encryptionPadding = KeyProperties.ENCRYPTION_PADDING_PKCS7 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/ByteArrayListExtensions.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/ByteArrayListExtensions.kt new file mode 100644 index 0000000000..8a150ac79e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/ByteArrayListExtensions.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.domain.userWalletList.utils + +internal fun List.containsBA(element: ByteArray?): Boolean { + this.forEach { + if (it.contentEquals(element)) return true + } + + return false +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt new file mode 100644 index 0000000000..810ba463f6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -0,0 +1,58 @@ +package com.tangem.tap.domain.userWalletList.utils + +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation +import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation + +internal val UserWallet.sensitiveInformation: UserWalletSensitiveInformation + get() = UserWalletSensitiveInformation(scanResponse.card.wallets) + +internal val UserWallet.publicInformation: UserWalletPublicInformation + get() = UserWalletPublicInformation( + name = name, + walletId = walletId, + artworkUrl = artworkUrl, + cardsInWallet = cardsInWallet, + scanResponse = scanResponse.copy( + card = scanResponse.card.copy( + wallets = emptyList(), + ), + ), + ) + +internal fun UserWalletPublicInformation.toUserWallet(): UserWallet { + return UserWallet( + name = name, + walletId = walletId, + artworkUrl = artworkUrl, + cardsInWallet = cardsInWallet, + scanResponse = scanResponse, + ) +} + +internal fun List.toUserWallets(): List { + return this.map { it.toUserWallet() } +} + +internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInformation): UserWallet { + return copy( + scanResponse = scanResponse.copy( + card = scanResponse.card.copy( + wallets = sensitiveInformation.wallets, + ), + ), + ) +} + +internal fun List.updateWith( + walletIdToSensitiveInformation: Map, +): List { + return if (walletIdToSensitiveInformation.isEmpty()) this else { + this.map { wallet -> + walletIdToSensitiveInformation[wallet.walletId] + ?.let(wallet::updateWith) + ?: wallet + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ByteArrayKeyAdapter.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ByteArrayKeyAdapter.kt new file mode 100644 index 0000000000..889438c346 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ByteArrayKeyAdapter.kt @@ -0,0 +1,29 @@ +package com.tangem.tap.domain.userWalletList.utils.json + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson +import com.tangem.common.extensions.ByteArrayKey + +internal class ByteArrayKeyAdapter { + @ToJson + fun toJson( + writer: JsonWriter, + src: ByteArrayKey, + byteArrayAdapter: JsonAdapter, + ) { + byteArrayAdapter.toJson(writer, src.bytes) + } + + @FromJson + fun fromJson( + reader: JsonReader, + byteArrayAdapter: JsonAdapter, + ): ByteArrayKey? { + return byteArrayAdapter.fromJson(reader)?.let { + ByteArrayKey(bytes = it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/CardBackupStatusAdapter.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/CardBackupStatusAdapter.kt new file mode 100644 index 0000000000..419fd22dbf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/CardBackupStatusAdapter.kt @@ -0,0 +1,57 @@ +package com.tangem.tap.domain.userWalletList.utils.json + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson +import com.tangem.domain.common.CardDTO + +internal class CardBackupStatusAdapter { + @ToJson + fun toJson( + writer: JsonWriter, + src: CardDTO.BackupStatus?, + mapAdapter: JsonAdapter>, + ) { + val jsonMap = mutableMapOf() + + when (src) { + is CardDTO.BackupStatus.Active -> { + jsonMap["status"] = "active" + jsonMap["cardCount"] = src.cardCount.toString() + } + is CardDTO.BackupStatus.CardLinked -> { + jsonMap["status"] = "card_linked" + jsonMap["cardCount"] = src.cardCount.toString() + } + is CardDTO.BackupStatus.NoBackup -> { + jsonMap["status"] = "no_backup" + } + null -> { + jsonMap["status"] = "null" + } + } + + mapAdapter.toJson(writer, jsonMap) + } + + @FromJson + fun fromJson( + reader: JsonReader, + mapAdapter: JsonAdapter>, + ): CardDTO.BackupStatus? { + val map = mapAdapter.fromJson(reader) ?: return null + + return when (map["status"]) { + "active" -> CardDTO.BackupStatus.Active( + cardCount = map["cardCount"]?.toInt() ?: 0, + ) + "card_linked" -> CardDTO.BackupStatus.CardLinked( + cardCount = map["cardCount"]?.toInt() ?: 0, + ) + "no_backup" -> CardDTO.BackupStatus.NoBackup + else -> null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ExtendedPublicKeysMapAdapter.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ExtendedPublicKeysMapAdapter.kt new file mode 100644 index 0000000000..9399f55b79 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ExtendedPublicKeysMapAdapter.kt @@ -0,0 +1,57 @@ +package com.tangem.tap.domain.userWalletList.utils.json + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toHexString +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.hdWallet.ExtendedPublicKey +import com.tangem.operations.derivation.ExtendedPublicKeysMap + +internal class ExtendedPublicKeysMapAdapter { + @ToJson + fun toJson( + writer: JsonWriter, + src: ExtendedPublicKeysMap, + mapAdapter: JsonAdapter>, + derivationPathAdapter: JsonAdapter, + extendedPublicKeyAdapter: JsonAdapter, + ) { + val jsonMap = mutableMapOf() + + src.forEach { (derivationPath, extendedPublicKey) -> + val derivationPathJson = derivationPathAdapter.toJson(derivationPath) + val derivationPathEncoded = derivationPathJson.encodeToByteArray().toHexString() + val extendedPublicKeyJson = extendedPublicKeyAdapter.toJson(extendedPublicKey) + + jsonMap[derivationPathEncoded] = extendedPublicKeyJson + } + + mapAdapter.toJson(writer, jsonMap) + } + + @FromJson + fun fromJson( + reader: JsonReader, + mapAdapter: JsonAdapter>, + derivationPathAdapter: JsonAdapter, + extendedPublicKeyAdapter: JsonAdapter, + ): ExtendedPublicKeysMap { + val map = mutableMapOf() + + mapAdapter.fromJson(reader)?.forEach { (derivationPathEncoded, extendedPublicKeyJson) -> + val derivationPathJson = derivationPathEncoded.hexToBytes().decodeToString() + val derivationPath = derivationPathAdapter.fromJson(derivationPathJson) + val extendedPublicKey = extendedPublicKeyAdapter.fromJson(extendedPublicKeyJson) + + if (derivationPath != null && extendedPublicKey != null) { + map[derivationPath] = extendedPublicKey + } + } + + return ExtendedPublicKeysMap(map) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ScanResponseDerivedKeysMapAdapter.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ScanResponseDerivedKeysMapAdapter.kt new file mode 100644 index 0000000000..0aada43b6a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/ScanResponseDerivedKeysMapAdapter.kt @@ -0,0 +1,52 @@ +package com.tangem.tap.domain.userWalletList.utils.json + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.operations.derivation.ExtendedPublicKeysMap + +internal class ScanResponseDerivedKeysMapAdapter { + @ToJson + fun toJson( + writer: JsonWriter, + src: Map, + mapAdapter: JsonAdapter>, + byteArrayKeyAdapter: JsonAdapter, + extendedPublicKeysMapAdapter: JsonAdapter, + ) { + val jsonMap = mutableMapOf() + + src.forEach { (key, extendedPublicKeysMap) -> + val keyJson = byteArrayKeyAdapter.toJson(key) + val extendedPublicKeysMapJson = extendedPublicKeysMapAdapter.toJson(extendedPublicKeysMap) + + jsonMap[keyJson] = extendedPublicKeysMapJson + } + + mapAdapter.toJson(writer, jsonMap) + } + + @FromJson + fun fromJson( + reader: JsonReader, + mapAdapter: JsonAdapter>, + byteArrayKeyAdapter: JsonAdapter, + extendedPublicKeysMapAdapter: JsonAdapter, + ): Map { + val map = mutableMapOf() + + mapAdapter.fromJson(reader)?.forEach { (keyJson, extendedPublicKeysMapJson) -> + val key = byteArrayKeyAdapter.fromJson(keyJson) + val extendedPublicKeysMap = extendedPublicKeysMapAdapter.fromJson(extendedPublicKeysMapJson) + + if (key != null && extendedPublicKeysMap != null) { + map[key] = extendedPublicKeysMap + } + } + + return map + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/WalletDerivedKeysMapAdapter.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/WalletDerivedKeysMapAdapter.kt new file mode 100644 index 0000000000..e4ba0dd0db --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/WalletDerivedKeysMapAdapter.kt @@ -0,0 +1,56 @@ +package com.tangem.tap.domain.userWalletList.utils.json + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson +import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toHexString +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.hdWallet.ExtendedPublicKey + +internal class WalletDerivedKeysMapAdapter { + @ToJson + fun toJson( + writer: JsonWriter, + src: Map, + mapAdapter: JsonAdapter>, + derivationPathAdapter: JsonAdapter, + extendedPublicKeyAdapter: JsonAdapter, + ) { + val jsonMap = mutableMapOf() + + src.forEach { (derivationPath, extendedPublicKey) -> + val derivationPathJson = derivationPathAdapter.toJson(derivationPath) + val derivationPathEncoded = derivationPathJson.encodeToByteArray().toHexString() + val extendedPublicKeyJson = extendedPublicKeyAdapter.toJson(extendedPublicKey) + + jsonMap[derivationPathEncoded] = extendedPublicKeyJson + } + + mapAdapter.toJson(writer, jsonMap) + } + + @FromJson + fun fromJson( + reader: JsonReader, + mapAdapter: JsonAdapter>, + derivationPathAdapter: JsonAdapter, + extendedPublicKeyAdapter: JsonAdapter, + ): Map { + val map = mutableMapOf() + + mapAdapter.fromJson(reader)?.forEach { (derivationPathEncoded, extendedPublicKeyJson) -> + val derivationPathJson = derivationPathEncoded.hexToBytes().decodeToString() + val derivationPath = derivationPathAdapter.fromJson(derivationPathJson) + val extendedPublicKey = extendedPublicKeyAdapter.fromJson(extendedPublicKeyJson) + + if (derivationPath != null && extendedPublicKey != null) { + map[derivationPath] = extendedPublicKey + } + } + + return map + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt new file mode 100644 index 0000000000..f1115ee3ef --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.domain.walletCurrencies + +import com.tangem.common.CompletionResult +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.features.wallet.models.Currency + +interface WalletCurrenciesManager { + suspend fun update( + userWallet: UserWallet, + blockchainNetwork: BlockchainNetwork, + ): CompletionResult + + suspend fun addCurrencies( + userWallet: UserWallet, + currenciesToAdd: List, + ): CompletionResult + + suspend fun removeCurrency( + userWallet: UserWallet, + currencyToRemove: Currency, + ): CompletionResult + + suspend fun removeCurrencies( + userWallet: UserWallet, + currenciesToRemove: List, + ): CompletionResult + + companion object +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/di/WalletCurrenciesManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/di/WalletCurrenciesManagerProvider.kt new file mode 100644 index 0000000000..0c6bfccf02 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/di/WalletCurrenciesManagerProvider.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.domain.walletCurrencies.di + +import com.tangem.tap.common.entities.FiatCurrency +import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager +import com.tangem.tap.domain.walletCurrencies.implementation.DefaultWalletCurrenciesManager +import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository +import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository +import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository + +fun WalletCurrenciesManager.Companion.provideDefaultImplementation( + userTokensRepository: UserTokensRepository, + walletStoresRepository: WalletStoresRepository, + walletAmountsRepository: WalletAmountsRepository, + walletManagersRepository: WalletManagersRepository, + appCurrencyProvider: () -> FiatCurrency, +): WalletCurrenciesManager { + return DefaultWalletCurrenciesManager( + userTokensRepository = userTokensRepository, + walletStoresRepository = walletStoresRepository, + walletAmountsRepository = walletAmountsRepository, + walletManagersRepository = walletManagersRepository, + appCurrencyProvider = appCurrencyProvider, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt new file mode 100644 index 0000000000..6a63e8f5de --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -0,0 +1,205 @@ +package com.tangem.tap.domain.walletCurrencies.implementation + +import com.tangem.common.CompletionResult +import com.tangem.common.catching +import com.tangem.common.flatMap +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.TapWorkarounds.derivationStyle +import com.tangem.tap.common.entities.FiatCurrency +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.builders.WalletStoreBuilder +import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager +import com.tangem.tap.domain.walletStores.implementation.utils.fold +import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository +import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository +import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.getTokens +import com.tangem.tap.features.wallet.models.toCurrencies +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext + +internal class DefaultWalletCurrenciesManager( + private val userTokensRepository: UserTokensRepository, + private val walletStoresRepository: WalletStoresRepository, + private val walletAmountsRepository: WalletAmountsRepository, + private val walletManagersRepository: WalletManagersRepository, + private val appCurrencyProvider: () -> FiatCurrency, +) : WalletCurrenciesManager { + override suspend fun update( + userWallet: UserWallet, + blockchainNetwork: BlockchainNetwork, + ): CompletionResult { + val walletStore = walletStoresRepository.get(userWallet.walletId).first() + .find { + it.blockchainNetwork.blockchain == blockchainNetwork.blockchain + && it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath + } + + return if (walletStore != null) { + walletAmountsRepository.update( + userWallet = userWallet, + walletStore = walletStore, + fiatCurrency = appCurrencyProvider(), + ) + } else CompletionResult.Success(Unit) + } + + override suspend fun addCurrencies( + userWallet: UserWallet, + currenciesToAdd: List, + ): CompletionResult = withContext(Dispatchers.Default) { + var newBlockchainNetworks = listOf() + catching { + val card = userWallet.scanResponse.card + val savedCurrencies = withContext(Dispatchers.IO) { + userTokensRepository.getUserTokens(card) + } + newBlockchainNetworks = (savedCurrencies + currenciesToAdd) + .toBlockchainNetworks(userWallet.scanResponse.card) + val newCurrencies = newBlockchainNetworks.toCurrencies() + + withContext(Dispatchers.IO) { + userTokensRepository.saveUserTokens( + card = card, + tokens = newCurrencies, + ) + } + } + .flatMap { + newBlockchainNetworks.updateWalletStores(userWallet) + } + } + + override suspend fun removeCurrency( + userWallet: UserWallet, + currencyToRemove: Currency, + ): CompletionResult { + return removeCurrencies(userWallet, listOf(currencyToRemove)) + } + + override suspend fun removeCurrencies( + userWallet: UserWallet, + currenciesToRemove: List, + ): CompletionResult = withContext(Dispatchers.Default) { + var remainingBlockchainsNetworks = emptyList() + catching { + val card = userWallet.scanResponse.card + val savedCurrencies = withContext(Dispatchers.IO) { + userTokensRepository.getUserTokens(card) + } + + val remainingCurrencies = arrayListOf() + savedCurrencies.forEach { savedCurrency -> + if (savedCurrency !in currenciesToRemove) { + remainingCurrencies.add(savedCurrency) + } + } + + remainingBlockchainsNetworks = remainingCurrencies.toBlockchainNetworks(userWallet.scanResponse.card) + + withContext(Dispatchers.IO) { + userTokensRepository.saveUserTokens( + card = card, + tokens = remainingCurrencies, + ) + } + } + .flatMap { + remainingBlockchainsNetworks.updateWalletStores(userWallet) + } + } + + private fun List.toBlockchainNetworks(card: CardDTO): List { + val blockchainNetworks = arrayListOf() + val findDerivationPath: (currency: Currency) -> String? = { currency -> + currency.derivationPath + ?: currency.blockchain.derivationPath(card.derivationStyle) + ?.rawPath + } + + for (currency in this.sortedByDescending { it.isBlockchain() }) { + when (currency) { + is Currency.Blockchain -> { + val blockchainNetwork = BlockchainNetwork( + blockchain = currency.blockchain, + derivationPath = findDerivationPath(currency), + tokens = getTokens(currency), + ) + + blockchainNetworks.add(blockchainNetwork) + } + is Currency.Token -> { + val tokenBlockchainNetworkIndex = blockchainNetworks + .indexOfFirst { + it.blockchain == currency.blockchain && + it.derivationPath == currency.derivationPath + } + + if (tokenBlockchainNetworkIndex == -1) { + blockchainNetworks.add( + BlockchainNetwork( + blockchain = currency.blockchain, + derivationPath = findDerivationPath(currency), + tokens = listOf(currency.token), + ), + ) + } else { + val tokenBlockchainNetwork = blockchainNetworks[tokenBlockchainNetworkIndex] + if (currency.token in tokenBlockchainNetwork.tokens) { + continue + } else { + blockchainNetworks.add( + tokenBlockchainNetworkIndex, + tokenBlockchainNetwork.copy( + tokens = tokenBlockchainNetwork.tokens + currency.token, + ), + ) + } + } + } + } + } + + return blockchainNetworks + } + + private suspend fun List.updateWalletStores( + userWallet: UserWallet, + ): CompletionResult { + val userWalletId = userWallet.walletId + return this + .also { blockchainNetworks -> + walletStoresRepository.deleteDifference( + userWalletId = userWalletId, + currentBlockchains = blockchainNetworks.map { it.blockchain }, + ) + } + .map { blockchainNetwork -> + walletManagersRepository.findOrMake( + userWallet = userWallet, + blockchainNetwork = blockchainNetwork, + refresh = true, + ) + .flatMap { walletManager -> + walletStoresRepository.storeOrUpdate( + userWalletId = userWalletId, + walletStore = WalletStoreBuilder(userWallet) + .setWalletManager(walletManager) + .setBlockchainNetwork(blockchainNetwork) + .build(), + ) + } + } + .fold() + .flatMap { + walletAmountsRepository.update( + userWallet = userWallet, + fiatCurrency = appCurrencyProvider(), + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt new file mode 100644 index 0000000000..de8c07b6d2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresError.kt @@ -0,0 +1,39 @@ +package com.tangem.tap.domain.walletStores + +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.core.TangemError + +sealed class WalletStoresError(code: Int) : TangemError(code) { + override val silent: Boolean + get() = (cause as? TangemError)?.silent == true + + override val messageResId: Int? = null + override val message: String? + get() = customMessage + + class FetchFiatRatesError( + currencies: List, + override val cause: Throwable?, + ) : WalletStoresError(60011) { + override var customMessage: String = "Failed to fetch fiat rates for currencies $currencies" + } + + class UnknownBlockchain : WalletStoresError(60012) { + override var customMessage: String = "Unknown blockchain" + } + + object NoInternetConnection : WalletStoresError(60013) { + override var customMessage: String = "No internet connection" + } + + class WalletManagerNotCreated(blockchain: Blockchain) : WalletStoresError(60014) { + override var customMessage: String = "Wallet manager can not be created for $blockchain" + } + + class UpdateWalletManagerError( + blockchain: Blockchain, + override val cause: Throwable, + ) : WalletStoresError(600015) { + override var customMessage: String = "Unable to update wallet manager for currency $blockchain: $cause" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/di/WalletsStoresManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/di/WalletsStoresManagerProvider.kt index fdc5187641..d2a4789fb0 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/di/WalletsStoresManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/di/WalletsStoresManagerProvider.kt @@ -2,7 +2,29 @@ package com.tangem.tap.domain.walletStores.di import com.tangem.tap.domain.walletStores.WalletStoresManager import com.tangem.tap.domain.walletStores.implementation.DummyWalletStoresManager +import com.tangem.tap.common.entities.FiatCurrency +import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.domain.walletStores.implementation.DefaultWalletStoresManager +import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository +import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository +import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository fun WalletStoresManager.Companion.provideDummyImplementation(): WalletStoresManager { return DummyWalletStoresManager() +} + +fun WalletStoresManager.Companion.provideDefaultImplementation( + userTokensRepository: UserTokensRepository, + walletStoresRepository: WalletStoresRepository, + walletAmountsRepository: WalletAmountsRepository, + walletManagersRepository: WalletManagersRepository, + appCurrencyProvider: () -> FiatCurrency, +): WalletStoresManager { + return DefaultWalletStoresManager( + userTokensRepository = userTokensRepository, + walletStoresRepository = walletStoresRepository, + walletAmountsRepository = walletAmountsRepository, + walletManagersRepository = walletManagersRepository, + appCurrencyProvider = appCurrencyProvider, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt new file mode 100644 index 0000000000..442ac5bd45 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -0,0 +1,174 @@ +package com.tangem.tap.domain.walletStores.implementation + +import com.tangem.blockchain.common.WalletManager +import com.tangem.common.CompletionResult +import com.tangem.common.flatMap +import com.tangem.common.flatMapOnFailure +import com.tangem.common.map +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.common.entities.FiatCurrency +import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.domain.model.builders.WalletStoreBuilder +import com.tangem.tap.domain.tokens.UserTokensRepository +import com.tangem.tap.domain.walletStores.WalletStoresError +import com.tangem.tap.domain.walletStores.WalletStoresManager +import com.tangem.tap.domain.walletStores.implementation.utils.fold +import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository +import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository +import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository +import com.tangem.tap.features.wallet.models.toBlockchainNetworks +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.withContext + +internal class DefaultWalletStoresManager( + private val userTokensRepository: UserTokensRepository, + private val walletStoresRepository: WalletStoresRepository, + private val walletAmountsRepository: WalletAmountsRepository, + private val walletManagersRepository: WalletManagersRepository, + private val appCurrencyProvider: () -> FiatCurrency, +) : WalletStoresManager { + private val state = MutableStateFlow(State()) + + override fun getAll(): Flow>> { + return walletStoresRepository.getAll() + } + + override fun get(userWalletId: UserWalletId): Flow> { + return walletStoresRepository.get(userWalletId) + } + + override suspend fun delete(userWalletsIds: List): CompletionResult { + val walletIds = userWalletsIds.map { UserWalletId(it) } + return walletStoresRepository.delete(walletIds) + .flatMap { walletManagersRepository.delete(walletIds) } + } + + override suspend fun clear(): CompletionResult { + return walletStoresRepository.clear() + } + + override suspend fun fetch( + userWallets: List, + refresh: Boolean, + ): CompletionResult { + val fiatCurrency = appCurrencyProvider.invoke() + val isFiatCurrencyChanged = state.value.fiatCurrency != fiatCurrency + + state.update { prevState -> + prevState.copy( + fiatCurrency = fiatCurrency, + ) + } + + return userWallets + .mapNotNull { userWallet -> + val hasNotWalletStoresForUserWallet = !walletStoresRepository.contains(userWallet.walletId) + if (refresh || hasNotWalletStoresForUserWallet || isFiatCurrencyChanged) { + fetchWalletsIfNeeded(userWallet, refresh) + } else null + } + .fold(initial = arrayListOf()) { acc, data -> + acc.apply { add(data) } + } + .flatMap { + walletAmountsRepository.update(it, fiatCurrency) + } + } + + override suspend fun fetch( + userWallet: UserWallet, + refresh: Boolean, + ): CompletionResult { + return fetch(listOf(userWallet), refresh) + } + + private suspend fun fetchWalletsIfNeeded( + userWallet: UserWallet, + refresh: Boolean, + ): CompletionResult { + return if (userWallet.scanResponse.card.isMultiwalletAllowed) { + fetchMultiWallets(userWallet, refresh) + } else { + fetchSingleWallet(userWallet, refresh) + } + .map { userWallet } + } + + private suspend fun fetchMultiWallets( + userWallet: UserWallet, + refresh: Boolean, + ): CompletionResult { + val scanResponse = userWallet.scanResponse + val userTokens = withContext(Dispatchers.IO) { + userTokensRepository.getUserTokens(scanResponse.card) + } + val userWalletId = userWallet.walletId + + return withContext(Dispatchers.Default) { + userTokens.toBlockchainNetworks() + .also { blockchainNetworks -> + walletStoresRepository.deleteDifference( + userWalletId = userWalletId, + currentBlockchains = blockchainNetworks.map { it.blockchain }, + ) + } + .map { blockchainNetwork -> + val storeWalletStore: suspend (WalletManager?) -> CompletionResult = + { walletManager -> + walletStoresRepository.storeOrUpdate( + userWalletId = userWalletId, + walletStore = WalletStoreBuilder(userWallet) + .setWalletManager(walletManager) + .setBlockchainNetwork(blockchainNetwork) + .build(), + ) + } + + walletManagersRepository.findOrMake( + userWallet = userWallet, + blockchainNetwork = blockchainNetwork, + refresh = refresh, + ) + .flatMap { walletManager -> + storeWalletStore(walletManager) + } + .flatMapOnFailure { error -> + when (error) { + is WalletStoresError.WalletManagerNotCreated, + is WalletStoresError.UpdateWalletManagerError, + -> storeWalletStore(null) + else -> CompletionResult.Failure(error) + } + } + } + .fold() + } + } + + private suspend fun fetchSingleWallet( + userWallet: UserWallet, + refresh: Boolean, + ): CompletionResult { + return walletManagersRepository.findOrMake( + userWallet = userWallet, + refresh = refresh, + ) + .flatMap { walletManager -> + walletStoresRepository.storeOrUpdate( + userWalletId = userWallet.walletId, + walletStore = WalletStoreBuilder(userWallet) + .setWalletManager(walletManager) + .build(), + ) + } + } + + internal data class State( + val fiatCurrency: FiatCurrency? = null, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt new file mode 100644 index 0000000000..3c94784ec8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.domain.walletStores.implementation.utils + +import com.tangem.common.CompletionResult + +internal fun List>.fold(): CompletionResult { + return fold(Unit) { _, _ -> Unit } +} + +@Suppress("UNCHECKED_CAST") +internal inline fun List>.fold( + initial: R, + operation: (acc: R, data: D) -> R, +): CompletionResult { + var resultData = initial + for (result in this) { + when (result) { + is CompletionResult.Success -> { + resultData = operation(resultData, result.data) + } + is CompletionResult.Failure -> { + return result as CompletionResult.Failure + } + } + } + + return CompletionResult.Success(resultData) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt new file mode 100644 index 0000000000..f46a552dfe --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.domain.walletStores.repository + +import com.tangem.common.CompletionResult +import com.tangem.tap.common.entities.FiatCurrency +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.WalletStoreModel + +interface WalletAmountsRepository { + suspend fun update( + userWallets: List, + fiatCurrency: FiatCurrency, + ): CompletionResult + + suspend fun update( + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult + + suspend fun update( + userWallet: UserWallet, + walletStore: WalletStoreModel, + fiatCurrency: FiatCurrency, + ): CompletionResult + + companion object +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt new file mode 100644 index 0000000000..9c8f9b1c13 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.domain.walletStores.repository + +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.WalletManager +import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.tokens.models.BlockchainNetwork + +interface WalletManagersRepository { + suspend fun findOrMake( + userWallet: UserWallet, + blockchainNetwork: BlockchainNetwork? = null, + refresh: Boolean = false, + ): CompletionResult + + suspend fun delete(userWalletIds: List): CompletionResult + + suspend fun delete( + userWalletId: UserWalletId, + blockchain: Blockchain, + ): CompletionResult + + companion object +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt new file mode 100644 index 0000000000..a5184fd40f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.domain.walletStores.repository + +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.WalletStoreModel +import kotlinx.coroutines.flow.Flow + +interface WalletStoresRepository { + fun getAll(): Flow>> + fun get(userWalletId: UserWalletId): Flow> + + suspend fun contains(userWalletId: UserWalletId): Boolean + + suspend fun delete(userWalletsIds: List): CompletionResult + + suspend fun deleteDifference( + userWalletId: UserWalletId, + currentBlockchains: List, + ): CompletionResult + + suspend fun clear(): CompletionResult + + suspend fun storeOrUpdate( + userWalletId: UserWalletId, + walletStore: WalletStoreModel, + ): CompletionResult + + companion object +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt new file mode 100644 index 0000000000..315606c659 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.domain.walletStores.repository.di + +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository +import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository +import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository +import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletAmountsRepository +import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletManagersRepository +import com.tangem.tap.domain.walletStores.repository.implementation.DefaultWalletStoresRepository + +fun WalletStoresRepository.Companion.provideDefaultImplementation(): WalletStoresRepository { + return DefaultWalletStoresRepository() +} + +fun WalletManagersRepository.Companion.provideDefaultImplementation( + walletManagerFactory: WalletManagerFactory, +): WalletManagersRepository { + return DefaultWalletManagersRepository(walletManagerFactory) +} + +fun WalletAmountsRepository.Companion.provideDefaultImplementation( + tangemTechService: TangemTechService, +): WalletAmountsRepository { + return DefaultWalletAmountsRepository(tangemTechService) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt new file mode 100644 index 0000000000..df28ac9d4c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -0,0 +1,427 @@ +package com.tangem.tap.domain.walletStores.repository.implementation + +import com.tangem.blockchain.blockchains.solana.RentProvider +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.extensions.Result.Failure +import com.tangem.blockchain.extensions.Result.Success +import com.tangem.common.CompletionResult +import com.tangem.common.catching +import com.tangem.common.doOnSuccess +import com.tangem.common.flatMap +import com.tangem.common.flatMapOnFailure +import com.tangem.common.map +import com.tangem.common.services.Result +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.util.UserWalletId +import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.tap.common.entities.FiatCurrency +import com.tangem.tap.common.extensions.replaceByOrAdd +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.domain.walletStores.WalletStoresError +import com.tangem.tap.domain.walletStores.implementation.utils.fold +import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository +import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore +import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithAmounts +import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithError +import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates +import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithMissedDerivation +import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithRent +import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithUnreachable +import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage +import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage +import com.tangem.tap.features.wallet.models.PendingTransactionType +import com.tangem.tap.features.wallet.models.filterByCoin +import com.tangem.tap.features.wallet.models.getPendingTransactions +import com.tangem.tap.network.NetworkConnectivity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext +import timber.log.Timber +import java.math.BigDecimal + +internal class DefaultWalletAmountsRepository( + private val tangemTechService: TangemTechService, +) : WalletAmountsRepository { + private val walletStoresStorage = WalletStoresStorage + private val walletManagersStorage = WalletManagerStorage + + override suspend fun update( + userWallets: List, + fiatCurrency: FiatCurrency, + ): CompletionResult { + return if (userWallets.isEmpty()) CompletionResult.Success(Unit) + else withContext(Dispatchers.Default) { + awaitAll( + async { fetchAmounts(userWallets) }, + async { fetchFiatRates(userWallets, fiatCurrency) }, + ) + .fold() + } + } + + override suspend fun update( + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult { + return update(listOf(userWallet), fiatCurrency) + } + + override suspend fun update( + userWallet: UserWallet, + walletStore: WalletStoreModel, + fiatCurrency: FiatCurrency, + ): CompletionResult = withContext(Dispatchers.Default) { + val walletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + + awaitAll( + async { + // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] + val walletManager = walletStore.walletManager + fetchAmounts(walletId, scanResponse, walletStore, walletManager) + .flatMap { fetchRentIfNeeded(walletStore, walletManager) } + }, + async { fetchFiatRates(listOf(userWallet), fiatCurrency) }, + ) + .fold() + } + + private suspend fun fetchAmounts( + userWallets: List, + ): CompletionResult = coroutineScope { + userWallets.map { userWallet -> + val walletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + val walletStores = walletStoresStorage.getSync(walletId) + + walletStores.map { walletStore -> + async { + // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] + val walletManager = walletStore.walletManager + fetchAmounts(walletId, scanResponse, walletStore, walletManager) + .flatMap { fetchRentIfNeeded(walletStore, walletManager) } + } + } + .awaitAll() + .fold() + } + .fold() + } + + private suspend fun fetchFiatRates( + userWallets: List, + fiatCurrency: FiatCurrency, + ): CompletionResult { + val walletsIds = userWallets.map { it.walletId } + val walletStores = walletsIds + .flatMap { walletStoresStorage.getSync(it) } + + val currencies = walletStores + .asSequence() + .flatMap { it.walletsData } + .map { it.currency } + + val coinsIds = currencies.mapNotNull { it.coinId }.distinct().toList() + + val fiatRatesResult = withContext(Dispatchers.IO) { + tangemTechService.rates( + currency = fiatCurrency.code, + ids = coinsIds, + ) + } + + return when (fiatRatesResult) { + is Result.Success -> { + Timber.d( + """ + Fetched fiat rates + |- User wallets ids: $walletsIds + |- Coins ids: $coinsIds + """.trimIndent(), + ) + + walletStores.forEach { walletStore -> + updateWithFiatRates( + walletStore = walletStore, + fiatRates = fiatRatesResult.data.rates, + ) + } + + CompletionResult.Success(Unit) + } + is Result.Failure -> { + val error = WalletStoresError.FetchFiatRatesError( + currencies = currencies.map { it.currencySymbol }.toList(), + cause = fiatRatesResult.error, + ) + + Timber.e( + error, + """ + Unable to fetch fiat rates + |- User wallets ids: $walletsIds + |- Coins ids: $coinsIds + """.trimIndent(), + ) + + CompletionResult.Failure(error) + } + } + } + + private suspend fun fetchAmounts( + walletId: UserWalletId, + scanResponse: ScanResponse, + walletStore: WalletStoreModel, + walletManager: WalletManager?, + ): CompletionResult { + val hasMissedDerivations = with(walletStore.blockchainNetwork) { + derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath) + } + val blockchain = walletStore.blockchainNetwork.blockchain + val tokens = walletStore.blockchainNetwork.tokens.map { it.name } + + return when { + hasMissedDerivations -> { + Timber.e( + """ + Missed derivation + |- User wallet id: $walletId + |- Blockchain: $blockchain + """.trimIndent(), + ) + + updateWithMissedDerivation( + walletStore = walletStore, + ) + + CompletionResult.Success(Unit) + } + walletManager == null -> { + Timber.e( + """ + Wallet manager is null + |- User wallet id: $walletId + |- Blockchain: $blockchain + """.trimIndent(), + ) + + updateWithUnreachable( + walletStore = walletStore, + ) + + CompletionResult.Success(Unit) + } + else -> { + withInternetConnection { walletManager.update() } + .map { updateWalletManagerWithAmounts(walletId, walletManager) } + .doOnSuccess { + Timber.d( + """ + Fetched amounts + |- User wallet id: $walletId + |- Blockchain: $blockchain + |- Tokens: $tokens + """.trimIndent(), + ) + + updateWithAmounts( + walletStore = walletStore, + wallet = walletManager.wallet, + ) + } + .flatMapOnFailure { error -> + Timber.e( + error, + """ + Unable to fetch amounts + |- User wallet id: $walletId + |- Blockchain: $blockchain + |- Tokens: $tokens + """.trimIndent(), + ) + + if (error is BlockchainSdkError) { + updateWithError( + walletStore = walletStore, + wallet = walletManager.wallet, + error = error, + ) + CompletionResult.Success(Unit) + } else { + CompletionResult.Failure(error) + } + } + } + } + } + + private suspend fun fetchRentIfNeeded( + walletStore: WalletStoreModel, + walletManager: WalletManager?, + ): CompletionResult { + val rentProvider = walletManager as? RentProvider + + if (walletManager == null || rentProvider == null) { + return CompletionResult.Success(Unit) + } + + when (val result = rentProvider.minimalBalanceForRentExemption()) { + is Success -> { + val balance = walletManager.wallet.fundsAvailable(AmountType.Coin) + val outgoingTxs = walletManager.wallet.getPendingTransactions( + PendingTransactionType.Outgoing, + ).filterByCoin() + + val rentExempt = result.data + val setRent = if (outgoingTxs.isEmpty()) { + balance < rentExempt + } else { + val outgoingAmount = outgoingTxs.sumOf { it.amountValue ?: BigDecimal.ZERO } + val rest = balance.minus(outgoingAmount) + balance < rest + } + + updateWithRent( + walletStore = walletStore, + rent = if (setRent) { + WalletStoreModel.WalletRent( + rent = rentProvider.rentAmount(), + exemptionAmount = rentExempt, + ) + } else null, + ) + } + is Failure -> Unit + } + + return CompletionResult.Success(Unit) + } + + private suspend inline fun withInternetConnection(crossinline block: suspend () -> Unit): CompletionResult { + return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + val error = WalletStoresError.NoInternetConnection + Timber.e(error) + CompletionResult.Failure(error) + } else withContext(Dispatchers.IO) { + catching { block() } + } + } + + private suspend fun updateWalletManagerWithAmounts( + walletId: UserWalletId, + walletManager: WalletManager, + ) = withContext(Dispatchers.Default) { + walletManagersStorage.update { prevManagers -> + val newManagersForUserWallet = prevManagers[walletId].orEmpty() + .toMutableList() + .apply { + replaceByOrAdd(walletManager) { + it.wallet.blockchain == it.wallet.blockchain + } + } + + prevManagers.apply { + set(walletId, newManagersForUserWallet) + } + } + } + + private suspend fun updateWithError( + walletStore: WalletStoreModel, + wallet: Wallet, + error: BlockchainSdkError, + ) = withContext(Dispatchers.Default) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletId = walletStore.userWalletId, + walletStore = walletStore, + update = { + it.updateWithError( + wallet = wallet, + error = error, + ) + }, + ) + } + } + + private suspend fun updateWithAmounts( + walletStore: WalletStoreModel, + wallet: Wallet, + ) = withContext(Dispatchers.Default) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletId = walletStore.userWalletId, + walletStore = walletStore, + update = { + it.updateWithAmounts(wallet = wallet) + }, + ) + } + } + + private suspend fun updateWithMissedDerivation( + walletStore: WalletStoreModel, + ) = withContext(Dispatchers.Default) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletId = walletStore.userWalletId, + walletStore = walletStore, + update = { + it.updateWithMissedDerivation() + }, + ) + } + } + + private suspend fun updateWithUnreachable( + walletStore: WalletStoreModel, + ) = withContext(Dispatchers.Default) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletId = walletStore.userWalletId, + walletStore = walletStore, + update = { + it.updateWithUnreachable() + }, + ) + } + } + + private suspend fun updateWithFiatRates( + walletStore: WalletStoreModel, + fiatRates: Map, + ) = withContext(Dispatchers.Default) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletId = walletStore.userWalletId, + walletStore = walletStore, + update = { + it.updateWithFiatRates(rates = fiatRates) + }, + ) + } + } + + private suspend fun updateWithRent( + walletStore: WalletStoreModel, + rent: WalletStoreModel.WalletRent?, + ) = withContext(Dispatchers.Default) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletId = walletStore.userWalletId, + walletStore = walletStore, + update = { + it.updateWithRent(rent) + }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt new file mode 100644 index 0000000000..600120ad42 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -0,0 +1,192 @@ +package com.tangem.tap.domain.walletStores.repository.implementation + +import com.tangem.blockchain.common.* +import com.tangem.common.CompletionResult +import com.tangem.common.catching +import com.tangem.common.hdWallet.DerivationPath +import com.tangem.common.map +import com.tangem.common.mapFailure +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isTestCard +import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.extensions.makeWalletManagerForApp +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.domain.walletStores.WalletStoresError +import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository +import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import timber.log.Timber + +internal class DefaultWalletManagersRepository( + private val walletManagerFactory: WalletManagerFactory, +) : WalletManagersRepository { + private val walletManagersStorage = WalletManagerStorage + + override suspend fun findOrMake( + userWallet: UserWallet, + blockchainNetwork: BlockchainNetwork?, + refresh: Boolean, + ): CompletionResult = withContext(Dispatchers.Default) { + if (refresh) { + deleteInternal(userWallet.walletId, blockchainNetwork?.blockchain) + makeAndStore(userWallet, blockchainNetwork) + } else { + val foundWalletManager = findWalletManager( + userWalletId = userWallet.walletId, + blockchain = blockchainNetwork?.blockchain, + ) + + foundWalletManager?.updateTokens( + scanResponse = userWallet.scanResponse, + blockchainNetwork = blockchainNetwork, + ) + ?: makeAndStore(userWallet, blockchainNetwork) + } + } + + private suspend fun makeAndStore( + userWallet: UserWallet, + blockchainNetwork: BlockchainNetwork?, + ): CompletionResult { + val scanResponse = userWallet.scanResponse + val blockchain = blockchainNetwork?.blockchain + ?: scanResponse.getBlockchain().let { blockchain -> + if (scanResponse.card.isTestCard) blockchain.getTestnetVersion() else blockchain + } + val derivationParams = getDerivationParams( + derivationPath = blockchainNetwork?.derivationPath, + card = scanResponse.card, + ) + + val walletManager = blockchain?.let { + walletManagerFactory.makeWalletManagerForApp( + scanResponse = userWallet.scanResponse, + blockchain = blockchain, + derivationParams = derivationParams, + ) + } + + return when { + blockchain == Blockchain.Unknown || blockchain == null -> { + val error = WalletStoresError.UnknownBlockchain() + Timber.e(error) + CompletionResult.Failure(error) + } + walletManager != null -> { + walletManager.updateTokens( + scanResponse = scanResponse, + blockchainNetwork = blockchainNetwork, + ) + .map { updatedWalletManager -> + store(userWallet.walletId, updatedWalletManager) + updatedWalletManager + } + } + else -> { + val error = WalletStoresError.WalletManagerNotCreated(blockchain) + Timber.e(error) + CompletionResult.Failure(error) + } + } + } + + override suspend fun delete(userWalletIds: List): CompletionResult = catching { + walletManagersStorage.update { prevManagers -> + prevManagers.apply { + userWalletIds.forEach { userWalletId -> + remove(userWalletId) + } + } + } + } + + override suspend fun delete( + userWalletId: UserWalletId, + blockchain: Blockchain, + ): CompletionResult = catching { + deleteInternal(userWalletId, blockchain) + } + + private suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager) { + walletManagersStorage.update { prevManagers -> + prevManagers.apply { + set( + key = userWalletId, + value = this[userWalletId].orEmpty() + walletManager, + ) + } + } + } + + private suspend fun deleteInternal(userWalletId: UserWalletId, blockchain: Blockchain?) { + walletManagersStorage.update { prevManagers -> + prevManagers.apply { + if (blockchain == null) { + set( + key = userWalletId, + value = emptyList(), + ) + } else { + set( + key = userWalletId, + value = this[userWalletId] + ?.filter { it.wallet.blockchain == blockchain } + .orEmpty(), + ) + } + } + } + } + + private fun WalletManager.updateTokens( + scanResponse: ScanResponse, + blockchainNetwork: BlockchainNetwork?, + ): CompletionResult { + val walletManager = this + return catching { + val tokens = blockchainNetwork?.tokens ?: listOfNotNull(scanResponse.getPrimaryToken()) + + if (tokens.isNotEmpty()) { + walletManager.addTokens(tokens) + } + + walletManager + } + .mapFailure { + val error = WalletStoresError.UpdateWalletManagerError( + blockchain = walletManager.wallet.blockchain, + cause = it, + ) + Timber.e(error) + error + } + } + + private suspend fun findWalletManager( + userWalletId: UserWalletId, + blockchain: Blockchain?, + ): WalletManager? { + return walletManagersStorage.getAllSync()[userWalletId]?.let { userWalletManagers -> + if (blockchain == null) userWalletManagers.firstOrNull() + else userWalletManagers.find { it.wallet.blockchain == blockchain } + } + } + + private fun getDerivationParams(derivationPath: String?, card: CardDTO): DerivationParams? { + return derivationPath?.let { + DerivationParams.Custom( + path = DerivationPath(it), + ) + } ?: if (!card.settings.isHDWalletAllowed) { + null + } else if (card.useOldStyleDerivation) { + DerivationParams.Default(DerivationStyle.LEGACY) + } else { + DerivationParams.Default(DerivationStyle.NEW) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt new file mode 100644 index 0000000000..f12cd945cf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt @@ -0,0 +1,91 @@ +package com.tangem.tap.domain.walletStores.repository.implementation + +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.CompletionResult +import com.tangem.common.catching +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository +import com.tangem.tap.domain.walletStores.repository.implementation.utils.isSameWalletStore +import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore +import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithSelf +import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext + +internal class DefaultWalletStoresRepository : WalletStoresRepository { + private val walletStoresStorage = WalletStoresStorage + + override fun getAll(): Flow>> { + return walletStoresStorage.getAll() + } + + override fun get(userWalletId: UserWalletId): Flow> { + return walletStoresStorage.get(userWalletId) + } + + override suspend fun contains(userWalletId: UserWalletId): Boolean { + return walletStoresStorage.getSync(userWalletId).isNotEmpty() + } + + override suspend fun delete(userWalletsIds: List): CompletionResult = catching { + walletStoresStorage.update { prevStores -> + prevStores.filterKeys { it !in userWalletsIds } as HashMap> + } + } + + override suspend fun deleteDifference( + userWalletId: UserWalletId, + currentBlockchains: List, + ): CompletionResult = catching { + walletStoresStorage.update { prevStores -> + prevStores.apply { + this[userWalletId] = this[userWalletId] + ?.filter { it.blockchainNetwork.blockchain in currentBlockchains } + .orEmpty() + } + } + } + + override suspend fun clear(): CompletionResult = catching { + walletStoresStorage.update { hashMapOf() } + } + + override suspend fun storeOrUpdate( + userWalletId: UserWalletId, + walletStore: WalletStoreModel, + ): CompletionResult = catching { + walletStoresStorage.update { prevStores -> + prevStores.addOrUpdate(userWalletId, walletStore) + } + } + + private suspend fun HashMap>.addOrUpdate( + userWalletId: UserWalletId, + walletStore: WalletStoreModel, + ): HashMap> = withContext(Dispatchers.Default) { + val prevStores = this@addOrUpdate + val walletStores = prevStores[userWalletId] + + if (walletStores.isNullOrEmpty()) { + prevStores.apply { + set(userWalletId, listOf(walletStore)) + } + } else { + val oldWalletStore = walletStores.find(walletStore::isSameWalletStore) + + if (oldWalletStore == null) { + prevStores.apply { + set(userWalletId, walletStores + walletStore) + } + } else { + prevStores.replaceWalletStore( + walletId = userWalletId, + walletStore = oldWalletStore, + update = { it.updateWithSelf(walletStore) }, + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt new file mode 100644 index 0000000000..39d4adfadc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt @@ -0,0 +1,181 @@ +package com.tangem.tap.domain.walletStores.repository.implementation.utils + +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.Wallet +import com.tangem.common.core.TangemError +import com.tangem.tap.domain.extensions.amountToCreateAccount +import com.tangem.tap.domain.getFirstToken +import com.tangem.tap.domain.model.WalletDataModel +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.getPendingTransactions +import java.math.BigDecimal + +internal fun WalletDataModel.updateWithFiatRate( + fiatRate: BigDecimal?, +): WalletDataModel { + return this.copy( + fiatRate = fiatRate, + ) +} + +internal fun List.updateWithFiatRates( + fiatRates: Map, +): List { + return this.map { walletData -> + val rate = fiatRates[walletData.currency.coinId]?.toBigDecimal() + walletData.updateWithFiatRate(rate) + } +} + +internal fun WalletDataModel.updateWithAmount(wallet: Wallet): WalletDataModel { + val pendingTransactions = wallet.getPendingTransactions() + return this.copy( + status = when (val currency = this.currency) { + is Currency.Blockchain -> { + val amount = wallet.fundsAvailable(AmountType.Coin) + if (pendingTransactions.isEmpty()) { + WalletDataModel.VerifiedOnline( + amount = amount, + ) + } else { + WalletDataModel.TransactionInProgress( + amount = amount, + pendingTransactions = pendingTransactions, + ) + } + } + is Currency.Token -> { + val token = currency.token + val amount = wallet.fundsAvailable(AmountType.Token(token)) + val hasTokenPendingTransactions = pendingTransactions + .any { it.transactionData.amount.currencySymbol == token.symbol } + when { + hasTokenPendingTransactions -> { + WalletDataModel.TransactionInProgress( + amount = amount, + pendingTransactions = pendingTransactions, + ) + } + pendingTransactions.isNotEmpty() -> { + WalletDataModel.SameCurrencyTransactionInProgress( + amount = amount, + pendingTransactions = pendingTransactions, + ) + } + else -> { + WalletDataModel.VerifiedOnline( + amount = amount, + ) + } + } + } + }, + ) +} + +internal fun List.updateWithAmounts(wallet: Wallet): List { + return this.map { walletData -> + walletData.updateWithAmount(wallet) + } +} + +internal fun WalletDataModel.updateWithError( + wallet: Wallet, + error: TangemError, +): WalletDataModel { + return this.copy( + status = when (error) { + is BlockchainSdkError.AccountNotFound -> { + val amountToCreateAccount = wallet.blockchain + .amountToCreateAccount(wallet.getFirstToken()) + + if (amountToCreateAccount != null) { + WalletDataModel.NoAccount( + amountToCreateAccount = amountToCreateAccount, + ) + } else { + WalletDataModel.Unreachable( + errorMessage = error.customMessage, + ) + } + } + else -> WalletDataModel.Unreachable( + errorMessage = error.customMessage, + ) + }, + ) +} + +internal fun List.updateWithError( + wallet: Wallet, + error: TangemError, +): List { + return this.map { walletData -> + walletData.updateWithError(wallet, error) + } +} + +internal fun WalletDataModel.updateWithSelf( + newWalletData: WalletDataModel, +): WalletDataModel { + val oldWalletData = this + val oldStatus = oldWalletData.status + return oldWalletData.copy( + status = when (val newStatus = newWalletData.status) { + is WalletDataModel.Loading -> when (oldStatus) { + is WalletDataModel.MissedDerivation -> WalletDataModel.Loading + else -> oldStatus.asRefreshing() + } + is WalletDataModel.MissedDerivation, + is WalletDataModel.Refreshing, + is WalletDataModel.NoAccount, + is WalletDataModel.Unreachable, + is WalletDataModel.SameCurrencyTransactionInProgress, + is WalletDataModel.TransactionInProgress, + is WalletDataModel.VerifiedOnline, + -> newStatus + }, + walletAddresses = newWalletData.walletAddresses, + existentialDeposit = newWalletData.existentialDeposit, + fiatRate = newWalletData.fiatRate ?: oldWalletData.fiatRate, + ) +} + +internal fun List.updateWithMissedDerivation(): List { + return this.map { walletData -> + walletData.copy( + status = WalletDataModel.MissedDerivation, + ) + } +} + +internal fun List.updateWithUnreachable(): List { + return this.map { walletData -> + walletData.copy( + status = WalletDataModel.Unreachable(errorMessage = null), + ) + } +} + +internal fun List.updateWithSelf( + walletsData: List, +): List { + val oldWalletsData = this + val updatedWalletsData = arrayListOf() + + walletsData.forEach { newWalletData -> + val walletDataToUpdate = oldWalletsData.find(newWalletData::isSameWalletData) + if (walletDataToUpdate != null) { + updatedWalletsData.add(walletDataToUpdate.updateWithSelf(newWalletData)) + } else { + updatedWalletsData.add(newWalletData) + } + } + + return updatedWalletsData +} + +internal fun WalletDataModel.isSameWalletData(other: WalletDataModel): Boolean { + return currency == other.currency +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt new file mode 100644 index 0000000000..8d29c62825 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -0,0 +1,93 @@ +package com.tangem.tap.domain.walletStores.repository.implementation.utils + +import com.tangem.blockchain.common.Wallet +import com.tangem.common.core.TangemError +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.WalletStoreModel + +internal inline fun HashMap>.replaceWalletStore( + walletId: UserWalletId, + walletStore: WalletStoreModel, + update: (walletStore: WalletStoreModel) -> WalletStoreModel, +): HashMap> { + return this.apply { + this[walletId] = this[walletId] + ?.replaceWalletStore(walletStore, update) + .orEmpty() + } +} + +internal fun WalletStoreModel.updateWithError( + wallet: Wallet, + error: TangemError, +): WalletStoreModel { + return this.copy( + walletsData = walletsData.updateWithError( + wallet = wallet, + error = error, + ), + ) +} + +internal fun WalletStoreModel.updateWithAmounts( + wallet: Wallet, +): WalletStoreModel { + return this.copy( + walletsData = walletsData.updateWithAmounts(wallet = wallet), + ) +} + +internal fun WalletStoreModel.updateWithFiatRates( + rates: Map, +): WalletStoreModel { + return this.copy( + walletsData = walletsData.updateWithFiatRates(rates), + ) +} + +internal fun WalletStoreModel.updateWithSelf( + newWalletStore: WalletStoreModel, +): WalletStoreModel { + val oldStore = this + return oldStore.copy( + walletManager = newWalletStore.walletManager, + walletRent = newWalletStore.walletRent, + walletsData = oldStore.walletsData.updateWithSelf(newWalletStore.walletsData), + ) +} + +internal fun WalletStoreModel.updateWithMissedDerivation(): WalletStoreModel { + return this.copy( + walletsData = walletsData.updateWithMissedDerivation(), + ) +} + +internal fun WalletStoreModel.updateWithUnreachable(): WalletStoreModel { + return this.copy( + walletsData = walletsData.updateWithUnreachable(), + ) +} + +internal fun WalletStoreModel.updateWithRent(rent: WalletStoreModel.WalletRent?): WalletStoreModel { + return this.copy( + walletRent = rent, + ) +} + +internal inline fun List.replaceWalletStore( + newWalletStore: WalletStoreModel, + update: (walletStore: WalletStoreModel) -> WalletStoreModel, +): List { + val mutableStores = ArrayList(this) + for ((index, walletStore) in this.withIndex()) { + if (walletStore.isSameWalletStore(newWalletStore)) { + mutableStores[index] = update(walletStore) + break + } + } + return mutableStores +} + +internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean { + return blockchainNetwork == other.blockchainNetwork +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt new file mode 100644 index 0000000000..1dae6db10e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.domain.walletStores.storage + +import com.tangem.blockchain.common.WalletManager +import com.tangem.domain.common.util.UserWalletId +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +internal object WalletManagerStorage { + private val managers = + MutableSharedFlow>>(replay = 1) + + init { + managers.tryEmit(hashMapOf()) + } + + suspend fun getAllSync(): Map> { + return managers.first() + } + + private val mutex = Mutex() + suspend fun update( + f: suspend (HashMap>) -> HashMap>, + ) { + while (mutex.isLocked) { + delay(timeMillis = 60) + } + + mutex.withLock { + val prevState = managers.first() + managers.emit(f(prevState)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt new file mode 100644 index 0000000000..d4b6061dfc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt @@ -0,0 +1,51 @@ +package com.tangem.tap.domain.walletStores.storage + +import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.domain.model.WalletStoreModel +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +internal object WalletStoresStorage { + private val stores = + MutableSharedFlow>>(replay = 1) + + init { + stores.tryEmit(hashMapOf()) + } + + fun getAll(): Flow>> { + return stores + } + + @OptIn(ExperimentalCoroutinesApi::class) + fun get(userWalletId: UserWalletId): Flow> { + return stores + .mapLatest { stores -> + stores[userWalletId].orEmpty() + } + } + + suspend fun getSync(userWalletId: UserWalletId): List { + return stores.first().getOrElse(userWalletId) { emptyList() } + } + + private val mutex = Mutex() + suspend fun update( + f: suspend (HashMap>) -> HashMap>, + ) { + while (mutex.isLocked) { + delay(timeMillis = 60) + } + + mutex.withLock { + val prevState = stores.first() + stores.emit(f(prevState)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index 787e92f332..bf8afa7f53 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -45,8 +45,20 @@ sealed class DetailsAction : Action { } sealed class AppSettings : DetailsAction() { - data class SwitchPrivacySetting(val enable: Boolean, val setting: PrivacySetting) : - AppSettings() + data class SwitchPrivacySetting( + val enable: Boolean, + val setting: PrivacySetting, + ) : AppSettings() { + data class Success( + val enable: Boolean, + val setting: PrivacySetting, + ) : AppSettings() + } + + object EnrollBiometrics : AppSettings() { + object Enroll : AppSettings() + object Cancel : AppSettings() + } } data class ChangeAppCurrency(val fiatCurrency: FiatCurrency) : DetailsAction() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 4c2d72d2fe..11cd4bb5f4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -2,6 +2,9 @@ package com.tangem.tap.features.details.redux import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.common.flatMap import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.Analytics @@ -9,44 +12,53 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction +import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletStoresManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.rekotlin.Action import org.rekotlin.Middleware +import timber.log.Timber class DetailsMiddleware { private val eraseWalletMiddleware = EraseWalletMiddleware() private val manageSecurityMiddleware = ManageSecurityMiddleware() private val managePrivacyMiddleware = ManagePrivacyMiddleware() - val detailsMiddleware: Middleware = { _, state -> + val detailsMiddleware: Middleware = { _, stateProvider -> { next -> { action -> - handleAction(state, action) + if (!DemoHelper.tryHandle(stateProvider, action)) { + val detailsState = stateProvider()?.detailsState + if (detailsState != null) { + handleAction(detailsState, action) + } + } next(action) } } } - private fun handleAction(state: () -> AppState?, action: Action) { - if (DemoHelper.tryHandle(state, action)) return - + private fun handleAction(state: DetailsState, action: Action) { when (action) { is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action) is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) - is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(action) + is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(state, action) is DetailsAction.ShowDisclaimer -> { val uri = store.state.detailsState.cardTermsOfUseUrl if (uri != null) { @@ -65,14 +77,17 @@ class DetailsMiddleware { } DetailsAction.ScanCard -> { scope.launch { - when (val result = tangemSdkManager.scanCard()) { - is CompletionResult.Success -> { - val scannedCard = result.data + tangemSdkManager.scanCard( + cardId = state.scanResponse?.card?.cardId, + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + state.scanResponse?.card?.isAccessCodeSet == true, + ) + .doOnSuccess { card -> val currentCardId = store.state.globalState.scanResponse?.card ?.userWalletId ?.stringValue - if (scannedCard.userWalletId.stringValue == currentCardId) { - store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(scannedCard)) + if (card.userWalletId.stringValue == currentCardId) { + store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) } else { store.dispatchDialogShow( AppDialog.SimpleOkDialogRes( @@ -82,8 +97,6 @@ class DetailsMiddleware { ) } } - is CompletionResult.Failure -> Unit - } } } } @@ -104,17 +117,22 @@ class DetailsMiddleware { is DetailsAction.ResetToFactory.Proceed -> { val card = store.state.detailsState.cardSettingsState?.card ?: return scope.launch { - when (val result = tangemSdkManager.resetToFactorySettings(card.cardId)) { - is CompletionResult.Success -> { + tangemSdkManager.resetToFactorySettings(card.cardId) + .flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) } + .doOnSuccess { Analytics.send(Settings.CardSettings.FactoryResetFinished()) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + val screen = if (userWalletsListManager.hasSavedUserWallets) { + AppScreen.Welcome + } else { + AppScreen.Home + } + store.dispatchOnMain(NavigationAction.PopBackTo(screen)) } - - is CompletionResult.Failure -> { - (result.error as? TangemSdkError)?.let { error -> - Analytics.send(Settings.CardSettings.FactoryResetFinished(error)) } + .doOnFailure { error -> + (error as? TangemSdkError)?.let { sdkError -> + Analytics.send(Settings.CardSettings.FactoryResetFinished(sdkError)) + } } - } } } else -> Unit @@ -174,12 +192,109 @@ class DetailsMiddleware { } class ManagePrivacyMiddleware { - fun handle(action: DetailsAction.AppSettings) { + fun handle(state: DetailsState, action: DetailsAction.AppSettings) { when (action) { is DetailsAction.AppSettings.SwitchPrivacySetting -> { -// TODO() + if (tangemSdkManager.canEnrollBiometrics) { + store.dispatch(DetailsAction.AppSettings.EnrollBiometrics) + } + when (action.setting) { + PrivacySetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable) + PrivacySetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable) + } + } + is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> Unit + is DetailsAction.AppSettings.EnrollBiometrics -> Unit + is DetailsAction.AppSettings.EnrollBiometrics.Enroll -> enrollBiometrics() + is DetailsAction.AppSettings.EnrollBiometrics.Cancel -> Unit + } + } + + private fun enrollBiometrics() { + store.dispatchOnMain(NavigationAction.OpenBiometricsSettings) + } + + private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch { + if (state.saveWallets == enable) return@launch + if (enable) { + saveCurrentWallet() + } else { + deleteSavedWallets() + if (state.saveAccessCodes) { + deleteSavedAccessCodes() } } } + + private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch { + if (state.saveAccessCodes == enable) return@launch + if (enable) { + if (!state.saveWallets) { + saveCurrentWallet() + } + saveAccessCodes() + } else { + deleteSavedAccessCodes() + } + } + + private suspend fun saveCurrentWallet() { + val scanResponse = store.state.detailsState.scanResponse ?: return + val userWallet = UserWalletBuilder(scanResponse).build() + + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Wallet saving failed") + } + .doOnSuccess { + preferencesStorage.shouldShowSaveWallet = false + preferencesStorage.shouldSaveUserWallets = true + store.dispatchOnMain( + DetailsAction.AppSettings.SwitchPrivacySetting.Success( + setting = PrivacySetting.SaveWallets, + enable = true, + ), + ) + store.onUserWalletSelected(userWallet) + } + } + + private suspend fun deleteSavedWallets() { + userWalletsListManager.clear() + .flatMap { walletStoresManager.clear() } + .doOnSuccess { + preferencesStorage.shouldSaveUserWallets = false + store.dispatchOnMain( + DetailsAction.AppSettings.SwitchPrivacySetting.Success( + setting = PrivacySetting.SaveWallets, + enable = false, + ), + ) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + + private fun saveAccessCodes() { + preferencesStorage.shouldSaveAccessCodes = true + store.dispatchOnMain( + DetailsAction.AppSettings.SwitchPrivacySetting.Success( + setting = PrivacySetting.SaveAccessCode, + enable = true, + ), + ) + } + + private suspend fun deleteSavedAccessCodes() { + tangemSdkManager.clearSavedUserCodes() + .doOnSuccess { + preferencesStorage.shouldSaveAccessCodes = false + store.dispatchOnMain( + DetailsAction.AppSettings.SwitchPrivacySetting.Success( + setting = PrivacySetting.SaveAccessCode, + enable = false, + ), + ) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 225505dfde..28d1b87cb8 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -8,7 +8,10 @@ import com.tangem.domain.common.isTangemTwin import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.extensions.isWalletDataSupported import com.tangem.tap.domain.extensions.signedHashesCount +import com.tangem.tap.preferencesStorage import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager import org.rekotlin.Action import java.util.* @@ -53,6 +56,9 @@ private fun handlePrepareScreen( cardTermsOfUseUrl = action.cardTou.getUrl(action.scanResponse.card), createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appCurrency = store.state.globalState.appCurrency, + isBiometricsAvailable = tangemSdkManager.canUseBiometry, + saveWallets = userWalletsListManager.hasSavedUserWallets, + saveAccessCodes = preferencesStorage.shouldSaveAccessCodes, ) } @@ -151,12 +157,15 @@ private fun handlePrivacyAction( state: DetailsState, ): DetailsState { return when (action) { - is DetailsAction.AppSettings.SwitchPrivacySetting -> { - when (action.setting) { - PrivacySetting.SaveWallets -> state.copy(saveWallets = action.enable) - PrivacySetting.SaveAccessCode -> state.copy(saveAccessCodes = action.enable) - } + is DetailsAction.AppSettings.SwitchPrivacySetting -> state + is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> when (action.setting) { + PrivacySetting.SaveWallets -> state.copy(saveWallets = action.enable) + PrivacySetting.SaveAccessCode -> state.copy(saveAccessCodes = action.enable) } + is DetailsAction.AppSettings.EnrollBiometrics -> state.copy(needEnrollBiometrics = true) + is DetailsAction.AppSettings.EnrollBiometrics.Enroll, + is DetailsAction.AppSettings.EnrollBiometrics.Cancel, + -> state.copy(needEnrollBiometrics = false) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt index 07c0847213..3cb211b7c5 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt @@ -22,6 +22,8 @@ data class DetailsState( val appCurrency: FiatCurrency = FiatCurrency.Default, val saveWallets: Boolean = false, val saveAccessCodes: Boolean = false, + val isBiometricsAvailable: Boolean = false, + val needEnrollBiometrics: Boolean = false, ) : StateType { // if you do not delegate - the application crashes on startup, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index 34018707cf..afdd0de6e1 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -1,23 +1,12 @@ package com.tangem.tap.features.details.ui.appsettings -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CornerSize import androidx.compose.material.AlertDialog import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.colorResource @@ -26,6 +15,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent +import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.details.redux.PrivacySetting import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -64,6 +55,8 @@ private fun AppSettings( ) } + EnrollBiometricsDialog(dialog = state.enrollBiometricsDialog) + Column( modifier = modifier .fillMaxSize(), @@ -84,6 +77,15 @@ private fun AppSettings( } } +@Composable +private fun EnrollBiometricsDialog( + modifier: Modifier = Modifier, + dialog: EnrollBiometricsDialog?, +) { + if (dialog == null) return + EnrollBiometricsDialogContent(modifier, dialog) +} + @Composable private fun AppSettingsElement( state: AppSettingsScreenState, @@ -224,6 +226,7 @@ fun AppSettingsScreenPreview() { PrivacySetting.SaveAccessCode to false, ), onSettingToggled = { _, _ -> }, + enrollBiometricsDialog = null, ), onBackPressed = { }, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt index 043423b3d3..1e4c1abeee 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt @@ -1,10 +1,10 @@ package com.tangem.tap.features.details.ui.appsettings +import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.tap.features.details.redux.PrivacySetting data class AppSettingsScreenState( val settings: Map, + val enrollBiometricsDialog: EnrollBiometricsDialog?, val onSettingToggled: (PrivacySetting, Boolean) -> Unit, -) - - +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt index 65dd12eaeb..16a6ebdbba 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt @@ -1,5 +1,7 @@ package com.tangem.tap.features.details.ui.appsettings +import com.tangem.core.ui.models.EnrollBiometricsDialog +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState @@ -11,12 +13,14 @@ import org.rekotlin.Store class AppSettingsViewModel(private val store: Store) { private val _uiState = MutableStateFlow(updateState(store.state.detailsState)) val uiState: StateFlow = _uiState + fun updateState(state: DetailsState): AppSettingsScreenState { return AppSettingsScreenState( settings = mapOf( PrivacySetting.SaveWallets to state.saveWallets, PrivacySetting.SaveAccessCode to state.saveAccessCodes, ), + enrollBiometricsDialog = if (state.needEnrollBiometrics) createEnrollBiometricsDialog() else null, onSettingToggled = { privacySetting, enabled -> onSettingsToggled(privacySetting, enabled) }, ) } @@ -24,4 +28,13 @@ class AppSettingsViewModel(private val store: Store) { private fun onSettingsToggled(setting: PrivacySetting, enable: Boolean) { store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting)) } + + private fun createEnrollBiometricsDialog() = EnrollBiometricsDialog( + onCancel = { + store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics.Cancel) + }, + onEnroll = { + store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics.Enroll) + }, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index 6513d28279..2120746a74 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -1,7 +1,9 @@ package com.tangem.tap.features.details.ui.details +import androidx.compose.runtime.Immutable import com.tangem.wallet.R +@Immutable data class DetailsScreenState( val elements: List, val tangemLinks: List, @@ -13,6 +15,7 @@ data class DetailsScreenState( val appNameRes: Int = R.string.app_name } +@Immutable enum class SettingsElement( val iconRes: Int, val titleRes: Int, @@ -30,6 +33,7 @@ enum class SettingsElement( PrivacyPolicy(R.drawable.ic_lock, R.string.details_row_privacy_policy); } +@Immutable data class SocialNetworkLink( val network: SocialNetwork, val url: String, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index fffc4e625b..319524eccd 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -36,7 +36,7 @@ class DetailsViewModel(private val store: Store) { SettingsElement.PrivacyPolicy -> { if (state.privacyPolicyUrl != null) it else null } - SettingsElement.AppSettings -> null // TODO: until we implement settings from this screen + SettingsElement.AppSettings -> if (state.isBiometricsAvailable) it else null SettingsElement.AppCurrency -> if (state.scanResponse?.card?.isMultiwalletAllowed != true) it else null SettingsElement.TermsOfUse -> if (state.scanResponse?.card?.isStart2Coin == true) it else null else -> it @@ -44,7 +44,7 @@ class DetailsViewModel(private val store: Store) { } return DetailsScreenState( - settings, + elements = settings, tangemLinks = getSocialLinks(), tangemVersion = getTangemAppVersion(), appCurrency = state.appCurrency.name, @@ -80,7 +80,7 @@ class DetailsViewModel(private val store: Store) { } SettingsElement.AppSettings -> { Analytics.send(Settings.ButtonAppSettings()) - store.dispatch(NavigationAction.NavigateTo(AppScreen.AppSettings)) //TODO: To be available later + store.dispatch(NavigationAction.NavigateTo(AppScreen.AppSettings)) } SettingsElement.LinkMoreCards -> { Analytics.send(Settings.ButtonCreateBackup()) diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index c06f921c2f..88a3e28b3c 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -26,7 +26,6 @@ import org.rekotlin.StoreSubscriber class HomeFragment : Fragment(), StoreSubscriber { private var homeState: MutableState = mutableStateOf(store.state.homeState) - private var composeView: ComposeView? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -38,20 +37,16 @@ class HomeFragment : Fragment(), StoreSubscriber { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - val context = container?.context ?: return null - + ): View { store.dispatch(BackupAction.CheckForUnfinishedBackup) - composeView = ComposeView(context).apply { + return ComposeView(inflater.context).apply { setContent { AppCompatTheme { ScreenContent() } } } - - return composeView } override fun onStart() { @@ -71,7 +66,6 @@ class HomeFragment : Fragment(), StoreSubscriber { override fun onDestroyView() { super.onDestroyView() rollbackStatusBarIconsColor() - composeView = null } override fun newState(state: HomeState) { diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 1d94184e98..c33b6d9a9b 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -1,51 +1,33 @@ package com.tangem.tap.features.home.redux -import com.tangem.common.core.TangemError -import com.tangem.common.services.Result -import com.tangem.domain.common.CardDTO -import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.extensions.withIOContext -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.operations.backup.BackupService -import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.backupService -import com.tangem.tap.common.analytics.Analytics -import com.tangem.tap.common.analytics.events.IntroductionProcess -import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor +import com.tangem.common.doOnFailure +import com.tangem.common.doOnResult +import com.tangem.common.doOnSuccess import com.tangem.tap.common.entities.IndeterminateProgressButton -import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.onCardScanned -import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa +import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.postUiDelayBg -import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.disclaimer.redux.DisclaimerAction -import com.tangem.tap.features.disclaimer.redux.DisclaimerType -import com.tangem.tap.features.disclaimer.redux.isAccepted +import com.tangem.tap.domain.model.builders.UserWalletBuilder +import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.features.home.BELARUS_COUNTRY_CODE import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.redux.HomeMiddleware.Companion.BUY_WALLET_URL -import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction -import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store -import com.tangem.wallet.R +import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware +import timber.log.Timber class HomeMiddleware { companion object { @@ -71,34 +53,13 @@ private fun handleHomeAction(action: Action) { store.dispatch(GlobalAction.ExchangeManager.Init) store.dispatch(GlobalAction.FetchUserCountry) } - is HomeAction.ShouldScanCardOnResume -> { if (action.shouldScanCard) { store.dispatch(HomeAction.ShouldScanCardOnResume(false)) postUiDelayBg(700) { store.dispatch(HomeAction.ReadCard) } } } - is HomeAction.ReadCard -> { - changeButtonState(ButtonState.PROGRESS) - val scanCardAction = GlobalAction.ScanCard( - onSuccess = { scanResponse -> - store.dispatch(HomeAction.ScanInProgress(false)) - checkForUnfinishedBackupForSaltPay( - backupService = backupService, - scanResponse = scanResponse, - nextHandler = { - showDisclaimerIfNeed( - scanResponse = scanResponse, - nextHandler = ::onScanSuccess, - ) - }, - ) - }, - onFailure = ::onScanFailure, - ) - store.dispatch(HomeAction.ScanInProgress(true)) - postUiDelayBg(300) { store.dispatch(scanCardAction) } - } + is HomeAction.ReadCard -> readCard() is HomeAction.GoToShop -> { when (action.userCountryCode) { RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL) @@ -108,134 +69,44 @@ private fun handleHomeAction(action: Action) { } } -/** - * It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards - * see BackupAction.CheckForUnfinishedBackup - * If user touches card other than Visa SaltPay - show dialog and block next processing - */ -private fun checkForUnfinishedBackupForSaltPay( - backupService: BackupService, - scanResponse: ScanResponse, - nextHandler: (ScanResponse) -> Unit, -) { - if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { - nextHandler(scanResponse) - return - } - - fun isTheSamePrimaryCard(card: CardDTO): Boolean { - return backupService.primaryCardId?.let { it == card.cardId } ?: false - } - - if (scanResponse.isSaltPayWallet() || !isTheSamePrimaryCard(scanResponse.card)) { - changeButtonState(ButtonState.ENABLED) - showSaltPayTapVisaLogoCardDialog() - } else { - nextHandler(scanResponse) - } -} - -private fun showDisclaimerIfNeed(scanResponse: ScanResponse, nextHandler: (ScanResponse) -> Unit) { - val disclaimerType = DisclaimerType.get(scanResponse) - store.dispatch(DisclaimerAction.SetDisclaimerType(disclaimerType)) - - if (disclaimerType.isAccepted()) { - nextHandler((scanResponse)) - } else { - changeButtonState(ButtonState.ENABLED) - store.dispatch( - DisclaimerAction.Show { +private fun readCard() = scope.launch { + delay(timeMillis = 200) + ScanCardProcessor.scan( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + onProgressStateChange = { showProgress -> + if (showProgress) { changeButtonState(ButtonState.PROGRESS) - nextHandler(scanResponse) - }, - ) - } -} - -private fun onScanSuccess(scanResponse: ScanResponse) { - Analytics.send(IntroductionProcess.CardWasScanned()) - val globalState = store.state.globalState - val tapWalletManager = globalState.tapWalletManager - tapWalletManager.updateConfigManager(scanResponse) - - Analytics.addParamsInterceptor(BatchIdParamsInterceptor(scanResponse.card.batchId)) - - store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) - - if (scanResponse.isSaltPay()) { - if (scanResponse.isSaltPayVisa()) { + } else { + changeButtonState(ButtonState.ENABLED) + } + }, + onScanStateChange = { scanInProgress -> + store.dispatch(HomeAction.ScanInProgress(scanInProgress)) + }, + onSuccess = { scanResponse -> scope.launch { - val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) - val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) - delay(500) - withMainContext { - when (result) { - is Result.Success -> { - val isOnboardingCase = result.data - if (isOnboardingCase) { - store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) - store.dispatch(OnboardingSaltPayAction.SetDependencies(manager, config)) - store.dispatch(OnboardingSaltPayAction.Update) - navigateTo(AppScreen.OnboardingWallet) - } else { - navigateTo(AppScreen.Wallet) - withIOContext { store.onCardScanned(scanResponse) } - } + if (preferencesStorage.shouldSaveUserWallets) { + val userWallet = UserWalletBuilder(scanResponse).build() + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + store.onCardScanned(scanResponse) } - is Result.Failure -> { - changeButtonState(ButtonState.ENABLED) - SaltPayExceptionHandler.handle(result.error) + .doOnSuccess { + store.onUserWalletSelected(userWallet) } - } + .doOnResult { + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + } + } else { + store.onCardScanned(scanResponse) + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } } - } else { - if (scanResponse.card.backupStatus?.isActive == false) { - changeButtonState(ButtonState.ENABLED) - showSaltPayTapVisaLogoCardDialog() - } else { - navigateTo(AppScreen.Wallet, null) - scope.launch { store.onCardScanned(scanResponse) } - } - } - } else { - if (OnboardingHelper.isOnboardingCase(scanResponse)) { - store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) - val appScreen = OnboardingHelper.whereToNavigate(scanResponse) - navigateTo(appScreen) - } else { - if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { - store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly)) - navigateTo(AppScreen.OnboardingTwins) - } else { - navigateTo(AppScreen.Wallet, null) - } - scope.launch { store.onCardScanned(scanResponse) } - } - } -} - -private fun showSaltPayTapVisaLogoCardDialog() { - store.dispatchDialogShow( - AppDialog.SimpleOkDialogRes( - headerId = R.string.saltpay_error_empty_backup_title, - messageId = R.string.saltpay_error_empty_backup_message, - ), + }, ) } -private fun onScanFailure(error: TangemError) { - store.dispatch(HomeAction.ScanInProgress(false)) - changeButtonState(ButtonState.ENABLED) -} - private fun changeButtonState(state: ButtonState) { store.dispatch(HomeAction.ChangeScanCardButtonState(IndeterminateProgressButton(state))) -} - -private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition? = null) { - postUiDelayBg(DELAY_SDK_DIALOG_CLOSE) { - changeButtonState(ButtonState.ENABLED) - store.dispatch(NavigationAction.NavigateTo(screen, transition)) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index f8996b9ea9..2fe2d59e78 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -2,8 +2,18 @@ package com.tangem.tap.features.onboarding import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] @@ -39,5 +49,42 @@ class OnboardingHelper { ProductType.SaltPay -> AppScreen.OnboardingWallet } } + + fun trySaveWalletAndNavigateToWalletScreen( + scanResponse: ScanResponse, + accessCode: String? = null, + backupCardsIds: List? = null, + ) { + when { + userWalletsListManager.hasSavedUserWallets -> scope.launch { + delay(timeMillis = 1_200) + store.dispatchOnMain( + SaveWalletAction.ProvideBackupInfo( + scanResponse = scanResponse, + accessCode = accessCode, + backupCardsIds = backupCardsIds?.toSet(), + ), + ) + store.dispatchOnMain(SaveWalletAction.Save) + } + tangemSdkManager.canUseBiometry && + preferencesStorage.shouldShowSaveWallet -> scope.launch { + delay(timeMillis = 1_200) + store.dispatchOnMain( + SaveWalletAction.ProvideBackupInfo( + scanResponse = scanResponse, + accessCode = accessCode, + backupCardsIds = backupCardsIds?.toSet(), + ), + ) + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) + } + else -> scope.launch { + store.onCardScanned(scanResponse) + } + } + + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index 6f17c03078..7119dfaf0d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -14,17 +14,15 @@ import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.getAddressData import com.tangem.tap.common.extensions.getTopUpUrl -import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE +import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction @@ -190,10 +188,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch } OnboardingNoteAction.Done -> { store.dispatch(GlobalAction.Onboarding.Stop) - scope.launch { - store.onCardScanned(scanResponse) - withMainContext { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } - } + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt index acd4013518..91536792a9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt @@ -6,13 +6,11 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding -import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.scope import com.tangem.tap.store @@ -130,10 +128,7 @@ private fun handleOtherCardsAction(action: Action) { } OnboardingOtherCardsAction.Done -> { store.dispatch(GlobalAction.Onboarding.Stop) - scope.launch { - store.onCardScanned(onboardingManager.scanResponse) - withMainContext { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } - } + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(onboardingManager.scanResponse) } is OnboardingOtherCardsAction.Confetti.Hide, is OnboardingOtherCardsAction.SetArtworkUrl, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt index 5c915f9cb0..8b25bf8e00 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt @@ -45,7 +45,6 @@ sealed class TwinCardsAction : Action { data class LaunchThirdStep(val message: Message) : TwinCardsAction() } - // for the onboarding data class SetPairCardId(val cardId: String) : TwinCardsAction() object TopUp : TwinCardsAction() @@ -53,6 +52,10 @@ sealed class TwinCardsAction : Action { data class SetWalletManager(val walletManager: WalletManager) : TwinCardsAction() object Done : TwinCardsAction() + data class SaveScannedTwinCardAndNavigateToWallet( + val scanResponse: ScanResponse, + ) : TwinCardsAction() + sealed class Balance { object Update : TwinCardsAction() data class Set(val balance: OnboardingWalletBalance) : TwinCardsAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 7233956925..8853e2766e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -12,10 +12,10 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.getAddressData import com.tangem.tap.common.extensions.getTopUpUrl -import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState @@ -25,6 +25,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.domain.twins.TwinCardsManager +import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.preferencesStorage @@ -88,7 +89,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { when (action) { is TwinCardsAction.Init -> { - if (twinCardsState.currentStep == TwinCardsStep.WelcomeOnly) return + if (twinCardsState.currentStep is TwinCardsStep.WelcomeOnly) return val scanResponse = getScanResponse() onboardingManager?.apply { @@ -141,7 +142,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } is TwinCardsAction.SetStepOfScreen -> { when (action.step) { - TwinCardsStep.WelcomeOnly, TwinCardsStep.Welcome -> { + is TwinCardsStep.WelcomeOnly, TwinCardsStep.Welcome -> { Analytics.send(Onboarding.Twins.ScreenOpened()) preferencesStorage.saveTwinsOnboardingShown() } @@ -289,21 +290,24 @@ private fun handle(action: Action, dispatch: DispatchFunction) { store.dispatchOpenUrl(topUpUrl) } TwinCardsAction.Done -> { - scope.launch { - val scanResponse = getScanResponse() - store.onCardScanned(scanResponse) - withMainContext { - when (twinCardsState.mode) { - CreateTwinWalletMode.CreateWallet -> { - store.dispatch(GlobalAction.Onboarding.Stop) - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) - } - CreateTwinWalletMode.RecreateWallet -> { - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - } - } + val scanResponse = getScanResponse() + when (twinCardsState.mode) { + CreateTwinWalletMode.CreateWallet -> { + store.dispatchOnMain(GlobalAction.Onboarding.Stop) + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen( + scanResponse = scanResponse, + backupCardsIds = listOfNotNull(twinCardsState.twinCardsManager?.secondCardPublicKey), + ) + } + CreateTwinWalletMode.RecreateWallet -> { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } } } + is TwinCardsAction.SaveScannedTwinCardAndNavigateToWallet -> { + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen( + scanResponse = action.scanResponse, + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt index 0b2e4b4d1a..0e1078584e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.twins.redux import com.tangem.blockchain.common.WalletManager +import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.domain.TapError import com.tangem.tap.domain.twins.TwinCardsManager @@ -62,9 +63,19 @@ data class TwinCardsState( enum class CreateTwinWalletMode { CreateWallet, RecreateWallet } -enum class TwinCardsStep { - None, WelcomeOnly, Welcome, Warning, CreateFirstWallet, CreateSecondWallet, CreateThirdWallet, +sealed class TwinCardsStep { + object None : TwinCardsStep() + data class WelcomeOnly( + val scanResponse: ScanResponse, + ) : TwinCardsStep() + + object Welcome : TwinCardsStep() + object Warning : TwinCardsStep() + object CreateFirstWallet : TwinCardsStep() + object CreateSecondWallet : TwinCardsStep() + object CreateThirdWallet : TwinCardsStep() // for the onboarding - TopUpWallet, Done + object TopUpWallet : TwinCardsStep() + object Done : TwinCardsStep() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index 8d434a53b8..9511376155 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -13,6 +13,7 @@ import coil.load import com.tangem.Message import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback +import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TwinCardNumber import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.AndroidAssetReader @@ -23,8 +24,6 @@ import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.common.extensions.stripZeroPlainString -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.redux.navigation.ShareElement import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget import com.tangem.tap.common.transitions.InternalNoteLayoutTransition @@ -43,7 +42,7 @@ import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding class TwinsCardsFragment : BaseOnboardingFragment() { private val mainBinding by lazy { binding.vMain } - private var previousStep = TwinCardsStep.None + private var previousStep: TwinCardsStep = TwinCardsStep.None private lateinit var twinsWidget: TwinsCardWidget private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget @@ -127,7 +126,7 @@ class TwinsCardsFragment : BaseOnboardingFragment() { pbBinding.pbState.progress = state.progress when (state.currentStep) { - TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state) + is TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state, state.currentStep.scanResponse) TwinCardsStep.Welcome -> setupWelcomeState(state) TwinCardsStep.Warning -> setupWarningState(state) TwinCardsStep.CreateFirstWallet -> setupCreateFirstWalletState(state) @@ -157,9 +156,9 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } } - private fun setupWelcomeOnlyState(state: TwinCardsState) { + private fun setupWelcomeOnlyState(state: TwinCardsState, scanResponse: ScanResponse) { setupWelcomeState(state) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + store.dispatch(TwinCardsAction.SaveScannedTwinCardAndNavigateToWallet(scanResponse)) store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.None)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 31b85f86e6..761bdd1a53 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -21,6 +21,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction +import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState @@ -69,17 +70,14 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: Analytics.send(Onboarding.Started()) } } - when { card == null -> { // it's possible when found unfinished backup for standard Wallet cards store.dispatch(OnboardingWalletAction.ResumeBackup) } - card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> { store.dispatch(OnboardingWalletAction.ResumeBackup) } - card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> { when { // check for unfinished backup for saltPay cards. See more @@ -162,8 +160,11 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } else { val backupState = store.state.onboardingWalletState.backupState val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState) - scope.launch { globalState.tapWalletManager.onCardScanned(updatedScanResponse) } - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen( + scanResponse = updatedScanResponse, + accessCode = backupState.accessCode, + backupCardsIds = backupState.backupCardIds, + ) } } is OnboardingWalletAction.ResumeBackup -> { @@ -233,6 +234,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) when (action) { is BackupAction.StartBackup -> { + tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) Analytics.send(Onboarding.Backup.Started()) backupService.discardSavedBackup() val primaryCard = scanResponse?.primaryCard diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt index 46581d4432..30ebdd258f 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt @@ -5,7 +5,7 @@ import com.tangem.domain.common.ScanResponse import org.rekotlin.Action internal sealed interface SaveWalletAction : Action { - data class ProvideAdditionalInfo( + data class ProvideBackupInfo( val scanResponse: ScanResponse, val accessCode: String?, val backupCardsIds: Set?, diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 500201104f..6c4c0e4288 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -7,6 +7,7 @@ import com.tangem.common.flatMap import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.builders.UserWalletBuilder @@ -34,10 +35,10 @@ internal class SaveWalletMiddleware { private fun handleAction(action: SaveWalletAction, state: SaveWalletState) { when (action) { is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state) - is SaveWalletAction.Save.Success -> popBack() is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics() is SaveWalletAction.SaveWalletWasShown -> saveWalletWasShown() - is SaveWalletAction.ProvideAdditionalInfo, + is SaveWalletAction.Save.Success, + is SaveWalletAction.ProvideBackupInfo, is SaveWalletAction.Dismiss, is SaveWalletAction.CloseError, is SaveWalletAction.Save.Error, @@ -60,52 +61,56 @@ internal class SaveWalletMiddleware { } private fun saveWallet(state: SaveWalletState) { - val scanResponse = state.additionalInfo?.scanResponse + val scanResponse = state.backupInfo?.scanResponse ?: store.state.globalState.scanResponse ?: return scope.launch { val userWallet = UserWalletBuilder(scanResponse) - .setBackupCardsIds(backupCardsIds = state.additionalInfo?.backupCardsIds) + .setBackupCardsIds(backupCardsIds = state.backupInfo?.backupCardsIds) .build() - userWalletsListManager.save(userWallet) - .flatMap { - trySaveAccessCode( - userWallet = userWallet, - additionalInfo = state.additionalInfo, - ) - } + saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) + .flatMap { userWalletsListManager.save(userWallet) } .doOnFailure { error -> store.dispatchOnMain(SaveWalletAction.Save.Error(error)) } .doOnSuccess { preferencesStorage.shouldSaveUserWallets = true preferencesStorage.shouldSaveAccessCodes = true + + val isSavedWalletSelected = + userWalletsListManager.selectedUserWalletSync?.walletId == userWallet.walletId + + if (isSavedWalletSelected) { + store.dispatchOnMain(NavigationAction.PopBackTo()) + } else { + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) + } + store.dispatchOnMain(SaveWalletAction.Save.Success) store.onUserWalletSelected(userWallet) } } } - private fun popBack() { - store.dispatchOnMain(NavigationAction.PopBackTo()) - } - private fun saveWalletWasShown() { preferencesStorage.shouldShowSaveWallet = false } - private suspend fun trySaveAccessCode( - userWallet: UserWallet, - additionalInfo: SaveWalletState.WalletAdditionalInfo?, + private suspend fun saveAccessCodeIfNeeded( + accessCode: String?, + cardsInWallet: Set, ): CompletionResult { return when { - additionalInfo?.accessCode != null -> { - tangemSdkManager.saveAccessCode( - accessCode = additionalInfo.accessCode, - cardsIds = userWallet.cardsInWallet, - ) + accessCode != null -> { + tangemSdkManager.unlockBiometricKeys() + .flatMap { + tangemSdkManager.saveAccessCode( + accessCode = accessCode, + cardsIds = cardsInWallet, + ) + } } else -> { CompletionResult.Success(Unit) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt index 9907306ea6..89eefd41db 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt @@ -12,8 +12,8 @@ internal object SaveWalletReducer { private fun internalReduce(action: SaveWalletAction, state: SaveWalletState): SaveWalletState { return when (action) { - is SaveWalletAction.ProvideAdditionalInfo -> state.copy( - additionalInfo = SaveWalletState.WalletAdditionalInfo( + is SaveWalletAction.ProvideBackupInfo -> state.copy( + backupInfo = SaveWalletState.WalletBackupInfo( scanResponse = action.scanResponse, accessCode = action.accessCode, backupCardsIds = action.backupCardsIds, @@ -27,11 +27,11 @@ internal object SaveWalletReducer { isSaveInProgress = false, ) is SaveWalletAction.Save.Success -> state.copy( - additionalInfo = null, + backupInfo = null, isSaveInProgress = false, ) is SaveWalletAction.Dismiss -> state.copy( - additionalInfo = null, + backupInfo = null, ) is SaveWalletAction.CloseError -> state.copy( error = null, diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt index 50cb237ff2..38af8d0b6f 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt @@ -5,12 +5,12 @@ import com.tangem.domain.common.ScanResponse import org.rekotlin.StateType data class SaveWalletState( - val additionalInfo: WalletAdditionalInfo? = null, + val backupInfo: WalletBackupInfo? = null, val isSaveInProgress: Boolean = false, val needEnrollBiometrics: Boolean = false, val error: TangemError? = null, ) : StateType { - data class WalletAdditionalInfo( + data class WalletBackupInfo( val scanResponse: ScanResponse, val accessCode: String?, val backupCardsIds: Set?, diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt index 65da3e8dc5..f3d0d97747 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -6,13 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 13811d5ed0..22d6c9138d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -5,26 +5,18 @@ import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.TransactionError -import com.tangem.blockchain.common.TransactionSender -import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.* import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.extensions.dispatchErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -36,27 +28,17 @@ import com.tangem.tap.domain.extensions.minimalAmount import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard -import com.tangem.tap.features.send.redux.AddressPayIdActionUi -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction -import com.tangem.tap.features.send.redux.AmountAction -import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.FeeAction.RequestFee -import com.tangem.tap.features.send.redux.PrepareSendScreen -import com.tangem.tap.features.send.redux.SendAction -import com.tangem.tap.features.send.redux.SendActionUi import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.ExternalTransactionData import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.redux.states.TransactionExtrasState import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdk import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.rekotlin.Action import org.rekotlin.Middleware import java.util.* @@ -231,13 +213,9 @@ private fun sendTransaction( dispatch(NavigationAction.PopBackTo()) } scope.launch(Dispatchers.IO) { - withContext(Dispatchers.Main) { - dispatch(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager))) - } + updateWallet(walletManager) delay(11000) // more than 10000 to avoid throttling - withContext(Dispatchers.Main) { - dispatch(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager))) - } + updateWallet(walletManager) } } is SimpleResult.Failure -> { @@ -327,4 +305,17 @@ private fun updateWarnings(dispatch: (Action) -> Unit) { val warnings = warningsManager.getWarnings(WarningMessage.Location.SendScreen, listOf(blockchain)) dispatch(SendAction.Warnings.Set(warnings)) +} + +private suspend fun updateWallet(walletManager: WalletManager) { + val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) { + walletCurrenciesManager.update( + userWallet = selectedUserWallet, + blockchainNetwork = blockchainNetwork, + ) + } else { + store.dispatchOnMain(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager))) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 9fe57ae5bd..a4f93f7949 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -30,25 +30,10 @@ import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget import com.tangem.tap.common.toggleWidget.ViewStateWidget import com.tangem.tap.features.BaseStoreFragment import com.tangem.tap.features.addBackPressHandler -import com.tangem.tap.features.send.redux.AddressPayIdActionUi -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.CheckClipboard -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.PasteAddressPayId -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.SetTruncateHandler -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.TruncateOrRestore -import com.tangem.tap.features.send.redux.AmountAction -import com.tangem.tap.features.send.redux.AmountActionUi -import com.tangem.tap.features.send.redux.AmountActionUi.CheckAmountToSend -import com.tangem.tap.features.send.redux.AmountActionUi.SetMainCurrency -import com.tangem.tap.features.send.redux.AmountActionUi.SetMaxAmount -import com.tangem.tap.features.send.redux.AmountActionUi.ToggleMainCurrency -import com.tangem.tap.features.send.redux.FeeActionUi.ChangeIncludeFee -import com.tangem.tap.features.send.redux.FeeActionUi.ChangeSelectedFee -import com.tangem.tap.features.send.redux.FeeActionUi.ToggleControlsVisibility -import com.tangem.tap.features.send.redux.ReceiptAction -import com.tangem.tap.features.send.redux.ReleaseSendState -import com.tangem.tap.features.send.redux.SendAction -import com.tangem.tap.features.send.redux.SendActionUi -import com.tangem.tap.features.send.redux.TransactionExtrasAction +import com.tangem.tap.features.send.redux.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* +import com.tangem.tap.features.send.redux.AmountActionUi.* +import com.tangem.tap.features.send.redux.FeeActionUi.* import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber @@ -60,12 +45,7 @@ import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentSendBinding import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import java.text.DecimalFormatSymbols /** diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 7b2322f79d..7b53918da2 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -7,6 +7,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.toMapKey +import com.tangem.common.flatMap import com.tangem.common.hdWallet.DerivationPath import com.tangem.common.services.Result import com.tangem.domain.DomainWrapped @@ -18,8 +19,7 @@ import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction import com.tangem.domain.redux.domainStore import com.tangem.operations.derivation.ExtendedPublicKeysMap -import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.assetReader +import com.tangem.tap.* import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -34,9 +34,6 @@ import com.tangem.tap.domain.tokens.LoadAvailableCoinsService import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -49,14 +46,12 @@ class TokensMiddleware { when (action) { is TokensAction.LoadCurrencies -> handleLoadCurrencies(action.scanResponse) is TokensAction.SaveChanges -> handleSaveChanges(action) - is TokensAction.PrepareAndNavigateToAddCustomToken -> { - handleAddingCustomToken(action) - } + is TokensAction.PrepareAndNavigateToAddCustomToken -> handleAddingCustomToken() is TokensAction.SetSearchInput -> { Analytics.send(ManageTokens.TokenSearched()) handleLoadCurrencies( scanResponse = store.state.globalState.scanResponse, - newSearchInput = action.searchInput + newSearchInput = action.searchInput, ) } is TokensAction.LoadMore -> { @@ -106,8 +101,8 @@ class TokensMiddleware { .filter(supportedBlockchains.toSet()) store.dispatchOnMain( TokensAction.LoadCurrencies.Success( - currencies, loadCoinsResult.data.moreAvailable - ) + currencies, loadCoinsResult.data.moreAvailable, + ), ) } is Result.Failure -> store.dispatchOnMain(TokensAction.LoadCurrencies.Failure) @@ -116,14 +111,14 @@ class TokensMiddleware { } } - private fun handleSaveChanges(action: TokensAction.SaveChanges) { - val scanResponse = store.state.globalState.scanResponse ?: return + private fun handleSaveChanges(action: TokensAction.SaveChanges) = scope.launch { + val scanResponse = store.state.globalState.scanResponse ?: return@launch val currentTokens = store.state.tokensState.addedWallets.toNonCustomTokensWithBlockchains( - scanResponse.card.derivationStyle + scanResponse.card.derivationStyle, ) val currentBlockchains = store.state.tokensState.addedWallets.toNonCustomBlockchains( - scanResponse.card.derivationStyle + scanResponse.card.derivationStyle, ) val blockchainsToAdd = action.addedBlockchains.filter { !currentBlockchains.contains(it) } @@ -149,7 +144,7 @@ class TokensMiddleware { ) { store.dispatchDebugErrorNotification("Nothing to save") store.dispatch(NavigationAction.PopBackTo()) - return + return@launch } val currencyList = convertToCurrencies( @@ -201,8 +196,9 @@ class TokensMiddleware { scope.launch { val result = tangemSdkManager.derivePublicKeys( - scanResponse.card.cardId, - derivations + cardId = scanResponse.card.cardId, + derivations = derivations, + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) when (result) { is CompletionResult.Success -> { @@ -221,6 +217,7 @@ class TokensMiddleware { ) store.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) delay(DELAY_SDK_DIALOG_CLOSE) + onSuccess(updatedScanResponse) } is CompletionResult.Failure -> { @@ -275,50 +272,78 @@ class TokensMiddleware { scanResponse: ScanResponse, currencyList: List, ) { - val factory = store.state.globalState.tapWalletManager.walletManagerFactory - val derivationStyle = scanResponse.card.derivationStyle + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) { + val updatedUserWallet = selectedUserWallet.copy( + scanResponse = scanResponse, + ) - val addActions = currencyList.mapIndexedNotNull { index, currency -> - when (currency) { - is Currency.Blockchain -> { - val derivationPath = currency.derivationPath?.let { DerivationPath(it) } - - val derivationParams = derivationStyle?.let { - when (derivationPath) { - null -> DerivationParams.Default(derivationStyle) - else -> DerivationParams.Custom(derivationPath) - } + scope.launch { + userWalletsListManager.update(updatedUserWallet) + .flatMap { + walletCurrenciesManager.addCurrencies( + userWallet = updatedUserWallet, + currenciesToAdd = currencyList, + ) + } + } + } else { + val factory = store.state.globalState.tapWalletManager.walletManagerFactory + val derivationStyle = scanResponse.card.derivationStyle + + val addActions = currencyList.mapIndexedNotNull { index, currency -> + when (currency) { + is Currency.Blockchain -> { + val derivationPath = currency.derivationPath?.let { DerivationPath(it) } + + val derivationParams = derivationStyle?.let { + when (derivationPath) { + null -> DerivationParams.Default(derivationStyle) + else -> DerivationParams.Custom(derivationPath) + } + } + val walletManager = factory.makeWalletManagerForApp( + scanResponse = scanResponse, + blockchain = currency.blockchain, + derivationParams = derivationParams, + ) ?: return@mapIndexedNotNull null + val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) + WalletAction.MultiWallet.AddBlockchain( + blockchain = blockchainNetwork, + walletManager = walletManager, + save = index == currencyList.lastIndex, + ) + } + is Currency.Token -> { + val rawDerivationPath = currency.derivationPath + ?: currency.blockchain.derivationPath(derivationStyle)?.rawPath + val blockchainNetwork = + BlockchainNetwork(currency.blockchain, rawDerivationPath, listOf(currency.token)) + WalletAction.MultiWallet.AddToken( + token = currency.token, + blockchain = blockchainNetwork, + save = index == currencyList.lastIndex, + ) } - val walletManager = factory.makeWalletManagerForApp( - scanResponse = scanResponse, - blockchain = currency.blockchain, - derivationParams = derivationParams, - ) ?: return@mapIndexedNotNull null - val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) - WalletAction.MultiWallet.AddBlockchain( - blockchain = blockchainNetwork, - walletManager = walletManager, - save = index == currencyList.lastIndex, - ) - } - is Currency.Token -> { - val rawDerivationPath = currency.derivationPath - ?: currency.blockchain.derivationPath(derivationStyle)?.rawPath - val blockchainNetwork = - BlockchainNetwork(currency.blockchain, rawDerivationPath, listOf(currency.token)) - WalletAction.MultiWallet.AddToken( - token = currency.token, - blockchain = blockchainNetwork, - save = index == currencyList.lastIndex, - ) } } + addActions.forEach { store.dispatchOnMain(it) } } - addActions.forEach { store.dispatchOnMain(it) } } - private fun removeCurrenciesIfNeeded(currencies: List) { - if (currencies.isNotEmpty()) store.dispatch(WalletAction.MultiWallet.RemoveWallets(currencies)) + private suspend fun removeCurrenciesIfNeeded(currencies: List) { + when { + currencies.isEmpty() -> Unit + userWalletsListManager.hasSavedUserWallets -> { + walletCurrenciesManager.removeCurrencies( + userWallet = userWalletsListManager.selectedUserWalletSync!!, + currenciesToRemove = currencies, + ) + } + else -> { + store.dispatch(WalletAction.MultiWallet.RemoveWallets(currencies)) + } + } } private fun isNeedToDerive(scanResponse: ScanResponse, currency: Currency): Boolean { @@ -327,7 +352,7 @@ class TokensMiddleware { } ?: false } - private fun handleAddingCustomToken(action: TokensAction.PrepareAndNavigateToAddCustomToken) { + private fun handleAddingCustomToken() = scope.launch { val onAddCustomToken = fun(customCurrency: CustomCurrency) { val scanResponse = store.state.globalState.scanResponse ?: return diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index fc947d90fd..1505f4bd2a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -1,17 +1,16 @@ package com.tangem.tap.features.wallet.redux import android.content.Context -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.AddressType import com.tangem.domain.common.CardDTO import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage +import com.tangem.tap.domain.model.TotalFiatBalance +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.models.WalletDialog @@ -27,7 +26,8 @@ sealed class WalletAction : Action { object LoadData : WalletAction() { object Refresh : WalletAction() - data class Failure(val error: TapError) : WalletAction() + object Success : WalletAction() + data class Failure(val error: TapError?) : WalletAction() } data class LoadWallet( @@ -54,20 +54,28 @@ sealed class WalletAction : Action { sealed class MultiWallet : WalletAction() { data class SetIsMultiwalletAllowed(val isMultiwalletAllowed: Boolean) : MultiWallet() + data class AddBlockchains( + val blockchains: List, + val walletManagers: List, + ) : MultiWallet() + + data class AddTokens( + val tokens: List, + val blockchain: BlockchainNetwork, + ) : MultiWallet() + data class AddBlockchain( val blockchain: BlockchainNetwork, val walletManager: WalletManager?, val save: Boolean, ) : MultiWallet() - data class AddBlockchains( - val blockchains: List, val walletManagers: List, val save: Boolean, + data class AddToken( + val token: Token, + val blockchain: BlockchainNetwork, + val save: Boolean, ) : MultiWallet() - data class AddTokens(val tokens: List, val blockchain: BlockchainNetwork, val save: Boolean) : - MultiWallet() - - data class AddToken(val token: Token, val blockchain: BlockchainNetwork, val save: Boolean) : MultiWallet() data class SaveCurrencies( val blockchainNetworks: List, val card: CardDTO? = null, ) : MultiWallet() @@ -161,6 +169,8 @@ sealed class WalletAction : Action { object CreateWallet : WalletAction() object EmptyWallet : WalletAction() + object ChangeWallet : WalletAction() + object ShowSaveWalletIfNeeded : WalletAction() sealed class TradeCryptoAction : WalletAction() { object Sell : TradeCryptoAction() @@ -190,4 +200,8 @@ sealed class WalletAction : Action { object ChooseAppCurrency : AppCurrencyAction() data class SelectAppCurrency(val fiatCurrency: FiatCurrency) : AppCurrencyAction() } + + data class UserWalletChanged(val userWallet: UserWallet) : WalletAction() + data class WalletStoresChanged(val walletStores: List) : WalletAction() + data class TotalFiatBalanceChanged(val balance: TotalFiatBalance) : WalletAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index dedbd21a08..cffbcc23bf 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -14,16 +14,13 @@ import com.tangem.tap.common.toggleWidget.WidgetState import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState -import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.PendingTransaction -import com.tangem.tap.features.wallet.models.TotalBalance -import com.tangem.tap.features.wallet.models.WalletRent -import com.tangem.tap.features.wallet.models.WalletWarning +import com.tangem.tap.features.wallet.models.* import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount import com.tangem.tap.features.wallet.redux.reducers.findProgressState import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager import org.rekotlin.StateType import java.math.BigDecimal import kotlin.properties.ReadOnlyProperty @@ -80,6 +77,9 @@ data class WalletState( primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard && primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain + val hasSavedWallets: Boolean + get() = userWalletsListManager.hasSavedUserWallets + fun getWalletManager(currency: Currency?): WalletManager? { if (currency?.blockchain == null) return null return getWalletStore(currency)?.walletManager @@ -242,11 +242,11 @@ data class WalletState( totalBalance = TotalBalance( state = walletsData.findProgressState(), fiatAmount = walletsData.calculateTotalFiatAmount(), - fiatCurrency = store.state.globalState.appCurrency - ) + fiatCurrency = store.state.globalState.appCurrency, + ), ) } else this.copy( - totalBalance = null + totalBalance = null, ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index 40613f791e..d0720a000c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -13,9 +13,11 @@ import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.models.WalletDialog +import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction import com.tangem.tap.persistence.FiatCurrenciesPrefStorage import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch class AppCurrencyMiddleware( @@ -65,11 +67,19 @@ class AppCurrencyMiddleware( private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) { Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(action.fiatCurrency))) - tapWalletManager.rates.clear() fiatCurrenciesPrefStorage.saveAppCurrency(action.fiatCurrency) store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency)) store.dispatch(DetailsAction.ChangeAppCurrency(action.fiatCurrency)) - store.dispatch(WalletAction.LoadFiatRate()) + store.dispatch(WalletSelectorAction.ChangeAppCurrency(action.fiatCurrency)) + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) { + scope.launch { + tapWalletManager.loadData(selectedUserWallet, refresh = true) + } + } else { + tapWalletManager.rates.clear() + store.dispatch(WalletAction.LoadFiatRate()) + } } private fun List.mapToUiModel(): List { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index ca702cc439..927205c938 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -3,8 +3,10 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager +import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext +import com.tangem.tap.* import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken @@ -17,6 +19,8 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makeWalletManagerForApp +import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.demo.isDemoCard @@ -26,9 +30,6 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.features.wallet.redux.reducers.toWallet -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.userTokensRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -53,7 +54,7 @@ class MultiWalletMiddleware { addTokens(listOf(action.token), action.blockchain, walletState, globalState, action.save) } is WalletAction.MultiWallet.AddTokens -> { - addTokens(action.tokens, action.blockchain, walletState, globalState, action.save) + addTokens(action.tokens, action.blockchain, walletState, globalState, save = false) } is WalletAction.MultiWallet.AddBlockchain -> { action.walletManager?.let { @@ -121,19 +122,27 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.RemoveWallet -> { - val currency = action.currency - val card = globalState.scanResponse?.card.guard { - store.dispatchErrorNotification(TapError.UnsupportedState("card is NULL")) - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - return + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) scope.launch { + walletCurrenciesManager.removeCurrency( + userWallet = selectedUserWallet, + currencyToRemove = action.currency, + ) + } else { + val currency = action.currency + val card = globalState.scanResponse?.card.guard { + store.dispatchErrorNotification(TapError.UnsupportedState("card is NULL")) + store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) + return + } + var currencies = walletState?.currencies ?: emptyList() + currencies = currencies.filterNot { it == currency } + if (currency.isBlockchain()) { + currencies + .filter { it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath } + } + scope.launch { userTokensRepository.saveUserTokens(card, currencies) } } - var currencies = walletState?.currencies ?: emptyList() - currencies = currencies.filterNot { it == currency } - if (currency.isBlockchain()) { - currencies - .filter { it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath } - } - scope.launch { userTokensRepository.saveUserTokens(card, currencies) } } is WalletAction.MultiWallet.RemoveWallets -> { val card = globalState.scanResponse?.card.guard { @@ -154,11 +163,36 @@ class MultiWalletMiddleware { } } is WalletAction.MultiWallet.ScanToGetDerivations -> { - store.dispatch(WalletAction.Scan) + val selectedWallet = userWalletsListManager.selectedUserWalletSync + if (selectedWallet != null) { + scanAndUpdateCard(selectedWallet, walletState) + } else { + store.dispatch(WalletAction.Scan) + } } } } + private fun scanAndUpdateCard( + selectedWallet: UserWallet, + state: WalletState?, + ) = scope.launch { + ScanCardProcessor.scan( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + cardId = selectedWallet.cardId, + additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, + ) { scanResponse -> + val userWallet = selectedWallet.copy( + scanResponse = scanResponse, + ) + + userWalletsListManager.update(userWallet) + .doOnSuccess { + store.state.globalState.tapWalletManager.loadData(userWallet, refresh = true) + } + } + } + private fun addDummyBalances(walletManagers: List) { walletManagers.forEach { if (it.wallet.fundsAvailable(AmountType.Coin) == BigDecimal.ZERO) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 5e245d4038..1bb5cd9b42 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -11,17 +11,10 @@ import com.tangem.common.services.Result import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.attestation.Attestation import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.* import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.copyToClipboard -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.dispatchErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.dispatchOpenUrl -import com.tangem.tap.common.extensions.dispatchToastNotification -import com.tangem.tap.common.extensions.onCardScanned -import com.tangem.tap.common.extensions.shareText -import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -29,25 +22,20 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.failedRates import com.tangem.tap.domain.loadedRates +import com.tangem.tap.domain.model.WalletDataModel +import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen -import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.PendingTransactionType -import com.tangem.tap.features.wallet.models.filterByCoin -import com.tangem.tap.features.wallet.models.getPendingTransactions -import com.tangem.tap.features.wallet.models.getSendableAmounts +import com.tangem.tap.features.wallet.models.* import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletStore import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.network.NetworkStateChanged -import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager import com.tangem.wallet.R +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch @@ -219,20 +207,34 @@ class WalletMiddleware { } } is WalletAction.LoadData, - is WalletAction.LoadData.Refresh -> { + is WalletAction.LoadData.Refresh, + -> { + val selectedWallet = userWalletsListManager.selectedUserWalletSync scope.launch { - val scanNoteResponse = globalState.scanResponse ?: return@launch - if (walletState.walletsData.isNotEmpty()) { - globalState.tapWalletManager.reloadData(scanNoteResponse) + if (selectedWallet != null) { + globalState.tapWalletManager.loadData( + userWallet = selectedWallet, + refresh = action is WalletAction.LoadData.Refresh, + ) } else { - globalState.tapWalletManager.loadData(scanNoteResponse) + val scanNoteResponse = globalState.scanResponse ?: return@launch + if (walletState.walletsData.isNotEmpty()) { + globalState.tapWalletManager.reloadData(scanNoteResponse) + } else { + globalState.tapWalletManager.loadData(scanNoteResponse) + } } } } is NetworkStateChanged -> { - globalState.scanResponse?.let { scanNoteResponse -> - store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) - scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) } + store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) + val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync + if (selectedUserWallet != null) scope.launch { + globalState.tapWalletManager.loadData(selectedUserWallet) + } else { + globalState.scanResponse?.let { scanNoteResponse -> + scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) } + } } } is WalletAction.CopyAddress -> { @@ -255,7 +257,7 @@ class WalletMiddleware { if (newAction is PrepareSendScreen && newAction.walletManager == null) { store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home)) FirebaseCrashlytics.getInstance().recordException( - IllegalStateException("PrepareSendScreen: walletManager is null") + IllegalStateException("PrepareSendScreen: walletManager is null"), ) store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found) } else { @@ -265,6 +267,75 @@ class WalletMiddleware { } } } + is WalletAction.ShowSaveWalletIfNeeded -> { + showSaveWalletIfNeeded() + } + is WalletAction.ChangeWallet -> { + changeWallet() + } + is WalletAction.UserWalletChanged -> { + scope.launch { + globalState.tapWalletManager.loadData(action.userWallet) + } + } + is WalletAction.WalletStoresChanged -> { + scope.launch(Dispatchers.Default) { + fetchTotalFiatBalance(action.walletStores, walletState) + findMissedDerivations(action.walletStores) + tryToShowAppRatingWarning(action.walletStores) + } + } + is WalletAction.TotalFiatBalanceChanged -> Unit + } + } + + private fun fetchTotalFiatBalance(walletStores: List, state: WalletState) { + scope.launch { + val totalFiatBalance = totalFiatBalanceCalculator.calculate( + prevAmount = state.totalBalance?.fiatAmount ?: BigDecimal.ZERO, + walletStores = walletStores, + ) + store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance)) + } + } + + private fun findMissedDerivations(wallStores: List) { + scope.launch { + val missedDerivations = wallStores + .filter { store -> + store.walletsData.any { it.status is WalletDataModel.MissedDerivation } + } + .map { it.blockchainNetwork } + + store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(missedDerivations)) + } + } + + private fun tryToShowAppRatingWarning(walletStores: List) { + warningsMiddleware.tryToShowAppRatingWarning( + hasNonZeroWallets = walletStores + .flatMap { it.walletsData } + .any { it.status.amount.isGreaterThan(BigDecimal.ZERO) }, + ) + } + + private fun showSaveWalletIfNeeded() { + if (preferencesStorage.shouldShowSaveWallet + && tangemSdkManager.canUseBiometry + && store.state.navigationState.backStack.lastOrNull() == AppScreen.Wallet + ) { + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) + } + } + + private fun changeWallet() { + when { + userWalletsListManager.hasSavedUserWallets -> { + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) + } + else -> { + store.dispatch(WalletAction.Scan) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index 109d44ad31..e911c48e3c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -67,11 +67,8 @@ class WarningsMiddleware { } } - fun tryToShowAppRatingWarning(wallet: Wallet) { - val nonZeroWalletsCount = wallet.amounts.filter { - it.value.value?.isGreaterThan(BigDecimal.ZERO) ?: false - }.size - if (nonZeroWalletsCount > 0) { + fun tryToShowAppRatingWarning(hasNonZeroWallets: Boolean) { + if (hasNonZeroWallets) { preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds() } if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) { @@ -79,6 +76,13 @@ class WarningsMiddleware { } } + fun tryToShowAppRatingWarning(wallet: Wallet) { + val nonZeroWalletsCount = wallet.amounts.filter { + it.value.value?.isGreaterThan(BigDecimal.ZERO) ?: false + }.size + tryToShowAppRatingWarning(hasNonZeroWallets = nonZeroWalletsCount > 0) + } + private fun showCardWarningsIfNeeded(globalState: GlobalState?) { globalState?.scanResponse?.let { scanResponse -> val card = scanResponse.card diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index b75ed1ff6c..3b174c915f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -9,17 +9,9 @@ import com.tangem.tap.common.extensions.toFiatString import com.tangem.tap.common.extensions.toFormattedCurrencyString import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.WalletRent -import com.tangem.tap.features.wallet.models.filterByToken -import com.tangem.tap.features.wallet.models.getPendingTransactions -import com.tangem.tap.features.wallet.models.removeUnknownTransactions -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletMainButton -import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.models.* +import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN -import com.tangem.tap.features.wallet.redux.WalletStore import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData @@ -174,9 +166,13 @@ class MultiWalletReducer { is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy( showBackupWarning = action.show, ) - is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(missingDerivations = action.blockchains) + is WalletAction.MultiWallet.AddMissingDerivations -> state.copy( + missingDerivations = action.blockchains, + ) is WalletAction.MultiWallet.BackupWallet -> state - is WalletAction.MultiWallet.ScanToGetDerivations -> state + is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy( + state = ProgressState.Loading, + ) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 272f328df3..54833f79e2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -3,34 +3,31 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet +import com.tangem.common.extensions.isZero import com.tangem.common.extensions.mapNotNullValues +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.extensions.toFiatRateString -import com.tangem.tap.common.extensions.toFiatString -import com.tangem.tap.common.extensions.toFiatValue -import com.tangem.tap.common.extensions.toFormattedCurrencyString -import com.tangem.tap.common.extensions.toFormattedFiatValue +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.getArtworkUrl +import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.getFirstToken +import com.tangem.tap.domain.model.TotalFiatBalance +import com.tangem.tap.domain.model.WalletDataModel +import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent -import com.tangem.tap.features.wallet.redux.AddressData -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.features.wallet.redux.ErrorType -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletAddresses -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletMainButton -import com.tangem.tap.features.wallet.redux.WalletState -import com.tangem.tap.features.wallet.redux.WalletStore +import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.proxy.AppStateHolder +import com.tangem.tap.features.wallet.ui.TokenData +import com.tangem.tap.store import org.rekotlin.Action import timber.log.Timber import java.math.BigDecimal @@ -124,7 +121,10 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ), ) } - else -> { /* no-op */ + else -> { + newState = newState.copy( + state = ProgressState.Error, + ) } } } @@ -297,8 +297,8 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS newState = newState.updateWalletData( selectedWalletData?.copy( walletAddresses = WalletAddresses( - address, - walletAddresses.list, + selectedAddress = address, + list = walletAddresses.list, ), ), ) @@ -319,13 +319,164 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS } is WalletAction.UserTokens.Loading -> newState = newState.copy(loadingUserTokens = true) is WalletAction.UserTokens.Loaded -> newState = newState.copy(loadingUserTokens = false) - else -> { /* no-op */ + is WalletAction.UserWalletChanged -> with(action.userWallet) { + val card = scanResponse.card + newState = WalletState( + cardId = card.cardId, + isMultiwalletAllowed = card.isMultiwalletAllowed, + cardImage = Artwork( + artworkId = artworkUrl, + ), + isTestnet = card.isTestCard, + state = ProgressState.Loading, + wallets = newState.wallets, + showBackupWarning = card.isMultiwalletAllowed && + card.settings.isBackupAllowed && + card.backupStatus == CardDTO.BackupStatus.NoBackup, + ) } + is WalletAction.WalletStoresChanged -> { + newState = newState.copy( + wallets = action.walletStores.mapToReduxModel(newState.isMultiwalletAllowed), + ) + } + is WalletAction.TotalFiatBalanceChanged -> { + newState = newState.copy( + totalBalance = action.balance.mapToReduxModel(), + ) + } + is WalletAction.LoadData.Success -> { + val selectedCurrency = if (!newState.isMultiwalletAllowed) { + newState.wallets.firstOrNull() + ?.walletsData + ?.firstOrNull() + ?.currency + } else { + newState.selectedCurrency + } + + newState = newState.copy( + state = ProgressState.Done, + selectedCurrency = selectedCurrency, + ) + } + else -> Unit } appStateHolder.walletState = newState return newState } +@JvmName("walletStoreModelToReduxModel") +private fun List.mapToReduxModel( + isMultiWalletAllowed: Boolean, +): List { + return this.map { walletStoreModel -> + with(walletStoreModel) { + WalletStore( + walletManager = walletManager, + blockchainNetwork = blockchainNetwork, + walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletStoreModel.walletRent), + ) + } + } +} + +@JvmName("walletDataModelToReduxModel") +private fun List.mapToReduxModel( + isMultiWalletAllowed: Boolean, + walletRent: WalletStoreModel.WalletRent?, +): List { + return this.map { walletDataModel -> + with(walletDataModel) { + val amount = status.amount + val amountFormatted = amount.toFormattedCurrencyString( + decimals = currency.decimals, + currency = currency.currencySymbol, + ) + val appCurrency = store.state.globalState.appCurrency + val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } + val fiatAmountFormatted = fiatAmount + ?.takeIf { !status.isErrorStatus } + ?.toFormattedFiatValue(appCurrency.symbol) + val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol) + + WalletData( + currency = currency, + walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress -> + WalletAddresses( + selectedAddress = selectedAddress, + list = walletAddresses, + ) + }, + existentialDepositString = existentialDeposit?.toPlainString(), + fiatRate = fiatRate, + fiatRateString = fiatRateFormatted, + pendingTransactions = status.pendingTransactions, + mainButton = WalletMainButton.SendButton( + enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(), + ), + walletRent = walletRent?.let { + WalletRent( + minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", + rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}", + ) + }, + currencyData = BalanceWidgetData( + status = when (status) { + is WalletDataModel.Loading -> BalanceStatus.Loading + is WalletDataModel.NoAccount -> BalanceStatus.NoAccount + is WalletDataModel.Refreshing -> BalanceStatus.Refreshing + is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress + is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress + is WalletDataModel.Unreachable, + is WalletDataModel.MissedDerivation, + -> BalanceStatus.Unreachable + is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline + }, + currency = currency.currencyName, + currencySymbol = currency.currencySymbol, + blockchainAmount = status.amount, + amount = amount, + amountFormatted = amountFormatted, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmountFormatted, + token = when { + !isMultiWalletAllowed && currency is Currency.Token -> { + TokenData( + amount = amount, + amountFormatted = amountFormatted, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmountFormatted, + tokenSymbol = currency.currencySymbol, + fiatRate = fiatRate, + fiatRateString = fiatRateFormatted, + ) + } + else -> null + }, + amountToCreateAccount = (status as? WalletDataModel.NoAccount) + ?.amountToCreateAccount + ?.toString(), + errorMessage = status.errorMessage, + ), + ) + } + } +} + +private fun TotalFiatBalance.mapToReduxModel(): TotalBalance { + return TotalBalance( + state = when (this) { + is TotalFiatBalance.Loading -> ProgressState.Loading + is TotalFiatBalance.Refreshing -> ProgressState.Refreshing + is TotalFiatBalance.Error -> ProgressState.Error + is TotalFiatBalance.Loaded -> ProgressState.Done + }, + fiatAmount = amount, + fiatCurrency = store.state.globalState.appCurrency, + ) +} + fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? { if (wallet == null) return null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 4b9b5cb18e..71bdbb7b8c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -34,7 +34,7 @@ data class BalanceWidgetData( ) data class TokenData( - val amountFormatted: String, + val amountFormatted: String?, val amount: BigDecimal? = null, val tokenSymbol: String, val fiatAmountFormatted: String? = null, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index d938b8966d..2339b0a219 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -1,11 +1,7 @@ package com.tangem.tap.features.wallet.ui import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup +import android.view.* import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.annotation.ColorRes @@ -22,33 +18,26 @@ import com.tangem.tap.common.TestActions import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.DetailsScreen import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.appendIfNotNull -import com.tangem.tap.common.extensions.beginDelayedTransition -import com.tangem.tap.common.extensions.fitChipsByGroupWidth -import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.common.extensions.getString -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.extensions.toQrCode +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction -import com.tangem.tap.features.wallet.redux.ErrorType -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter import com.tangem.tap.features.wallet.ui.images.load import com.tangem.tap.features.wallet.ui.test.TestWallet +import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.userWalletsListManagerSafe +import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletDetailsBinding +import kotlinx.coroutines.launch import org.rekotlin.StoreSubscriber class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), @@ -158,15 +147,19 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), binding.srlWalletDetails.setOnRefreshListener { if (selectedWallet.currencyData.status != BalanceStatus.Loading) { Analytics.send(Token.Refreshed()) - store.dispatch( - WalletAction.LoadWallet( - blockchain = BlockchainNetwork( - selectedWallet.currency.blockchain, - selectedWallet.currency.derivationPath, - emptyList(), - ), - ), + val blockchainNetwork = BlockchainNetwork( + blockchain = selectedWallet.currency.blockchain, + derivationPath = selectedWallet.currency.derivationPath, + tokens = emptyList(), ) + val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync + if (selectedUserWallet != null) scope.launch { + walletCurrenciesManager.update(selectedUserWallet, blockchainNetwork) + } else { + store.dispatch( + WalletAction.LoadWallet(blockchainNetwork), + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 17d3d03cca..962c7cd390 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -8,6 +8,7 @@ import android.view.View import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.transition.TransitionInflater @@ -41,6 +42,7 @@ import com.tangem.tap.features.wallet.ui.wallet.SaltPaySingleWalletView import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView import com.tangem.tap.features.wallet.ui.wallet.WalletView import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager import com.tangem.wallet.BuildConfig import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletBinding @@ -54,6 +56,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -63,7 +67,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber) { @@ -211,9 +225,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber store.dispatch( DetailsAction.PrepareScreen( - scanNoteResponse, - store.state.walletState.walletManagers.map { it.wallet }, - CardTou(), + scanResponse = scanNoteResponse, + wallets = store.state.walletState.walletManagers.map { it.wallet }, + cardTou = CardTou(), ), ) store.dispatch(NavigationAction.NavigateTo(AppScreen.Details)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt new file mode 100644 index 0000000000..df0b0537ed --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt @@ -0,0 +1,39 @@ +package com.tangem.tap.features.wallet.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletStoresManager +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +internal class WalletViewModel : ViewModel() { + fun launch() { + bootstrapSelectedWalletStoresChanges() + bootstrapShowSaveWalletIfNeeded() + } + + private fun bootstrapSelectedWalletStoresChanges() { + userWalletsListManager.selectedUserWallet + .flatMapLatest { selectedWallet -> + walletStoresManager.get(selectedWallet.walletId) + } + .onEach { walletStores -> + store.dispatch(WalletAction.WalletStoresChanged(walletStores)) + } + .launchIn(viewModelScope) + } + + private fun bootstrapShowSaveWalletIfNeeded() { + viewModelScope.launch { + delay(timeMillis = 1_800) + store.dispatchOnMain(WalletAction.ShowSaveWalletIfNeeded) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index 4130b02f73..8ade97afa3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -6,8 +6,6 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Portfolio @@ -28,15 +26,6 @@ class WalletAdapter : ListAdapter(D return currentList[position].currencyData.currencySymbol?.hashCode()?.toLong() ?: 0 } - fun submitList( - list: List, - primaryBlockchain: Blockchain?, - primaryToken: Token? = null, - ) { - // We used this method to sort the list of currencies. Sorting is disabled for now. - super.submitList(list) - } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WalletsViewHolder { val layout = ItemCurrencyWalletBinding.inflate( LayoutInflater.from(parent.context), parent, false, @@ -73,15 +62,7 @@ class WalletAdapter : ListAdapter(D BalanceStatus.Unreachable -> { root.getString(R.string.wallet_balance_blockchain_unreachable) } - BalanceStatus.NoAccount, - BalanceStatus.VerifiedOnline, - BalanceStatus.SameCurrencyTransactionInProgress, - BalanceStatus.EmptyCard, - BalanceStatus.UnknownBlockchain, - BalanceStatus.Loading, - BalanceStatus.Refreshing, - null, - -> null + else -> null } if (status == null || status == BalanceStatus.Loading) { @@ -101,8 +82,8 @@ class WalletAdapter : ListAdapter(D ) lContent.tvCurrency.text = wallet.currencyData.currency - lContent.tvAmountFiat.text = wallet.currencyData.fiatAmountFormatted - lContent.tvAmount.text = wallet.currencyData.amountFormatted + lContent.tvAmountFiat.text = wallet.currencyData.fiatAmountFormatted ?: "—" + lContent.tvAmount.text = wallet.currencyData.amountFormatted ?: "—" lContent.tvStatus.isVisible = statusMessage != null lContent.tvStatus.text = statusMessage diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt index cd5241fb86..b6d34873df 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt @@ -70,5 +70,7 @@ fun CurrencyIconView.load( blockchain = currency.blockchain, getLocalImage = true, ).load() + } else { + isBlockchainBadgeVisible = false } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 956fe058dc..6703abd9a9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -9,11 +9,7 @@ import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Portfolio -import com.tangem.tap.common.extensions.animateVisibility -import com.tangem.tap.common.extensions.formatAmountAsSpannedString -import com.tangem.tap.common.extensions.getQuantityString -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.CurrenciesRepository @@ -73,16 +69,17 @@ class MultiWalletView : WalletView() { walletsAdapter.setHasStableIds(true) binding?.rvMultiwallet?.layoutManager = LinearLayoutManager(fragment.requireContext()) binding?.rvMultiwallet?.adapter = walletsAdapter + binding?.rvMultiwallet?.itemAnimator = null } override fun onNewState(state: WalletState) { val fragment = fragment ?: return val binding = binding ?: return - handleTotalBalance(binding, state.totalBalance) + handleTotalBalance(binding, state.totalBalance, state.state) handleBackupWarning(binding, state.showBackupWarning) handleRescanWarning(binding, state.missingDerivations.isNotEmpty()) - walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken) + walletsAdapter.submitList(state.walletsData) binding.pbLoadingUserTokens.show(state.loadingUserTokens) @@ -135,11 +132,14 @@ class MultiWalletView : WalletView() { private fun handleTotalBalance( binding: FragmentWalletBinding, totalBalance: TotalBalance?, + progressState: ProgressState, ) = with(binding.lCardTotalBalance) { root.isVisible = totalBalance != null if (totalBalance != null) { // Skip changes when on refreshing state - if (totalBalance.state == ProgressState.Refreshing) return@with + if (totalBalance.state == ProgressState.Refreshing || + progressState == ProgressState.Refreshing + ) return@with if (totalBalance.state == ProgressState.Loading) { veilBalance.veil() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index a0126391a3..7307439224 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -3,12 +3,7 @@ package com.tangem.tap.features.wallet.ui.wallet import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager -import com.tangem.domain.common.TwinCardNumber -import com.tangem.tap.common.extensions.beginDelayedTransition -import com.tangem.tap.common.extensions.fitChipsByGroupWidth -import com.tangem.tap.common.extensions.getQuantityString -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.extensions.* import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction @@ -38,9 +33,12 @@ class SingleWalletView : WalletView() { btnAddToken.hide() rvPendingTransaction.hide() pbLoadingUserTokens.hide() + lCardTotalBalance.root.hide() + lSingleWalletBalance.root.hide() + lWalletRescanWarning.root.hide() lCardBalance.root.show() lAddress.root.show() - lSingleWalletBalance.root.hide() + rowButtons.show() } override fun onViewCreated() { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt index 07c9af833c..8ccaffcc44 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt @@ -6,13 +6,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.colorResource diff --git a/app/src/main/res/drawable/ic_tap_card_24.xml b/app/src/main/res/drawable/ic_tap_card_24.xml new file mode 100644 index 0000000000..dba54e1396 --- /dev/null +++ b/app/src/main/res/drawable/ic_tap_card_24.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/shape_rectangle_rounded_100.xml b/app/src/main/res/drawable/shape_rectangle_rounded_100.xml index 7f26fb8be5..23424858db 100644 --- a/app/src/main/res/drawable/shape_rectangle_rounded_100.xml +++ b/app/src/main/res/drawable/shape_rectangle_rounded_100.xml @@ -5,4 +5,4 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/card_total_balance_shimmer.xml b/app/src/main/res/layout/card_total_balance_shimmer.xml index 5bacc23c4c..76dfc5e5a3 100644 --- a/app/src/main/res/layout/card_total_balance_shimmer.xml +++ b/app/src/main/res/layout/card_total_balance_shimmer.xml @@ -1,11 +1,13 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:layout_width="124dp" + android:layout_height="22dp" + android:layout_marginTop="8dp" + tools:background="@color/background_secondary" /> diff --git a/app/src/main/res/layout/layout_card_total_balance.xml b/app/src/main/res/layout/layout_card_total_balance.xml index fe3484f373..c8dc2dd7bd 100644 --- a/app/src/main/res/layout/layout_card_total_balance.xml +++ b/app/src/main/res/layout/layout_card_total_balance.xml @@ -1,86 +1,84 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/card_balance" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="18dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="18dp" + android:animateLayoutChanges="true"> + android:id="@+id/tv_title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:text="@string/main_page_balance" + android:textColor="@color/iconGray" + android:textSize="14sp" + app:layout_constraintEnd_toStartOf="@id/tv_currency_name" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:id="@+id/veil_balance" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + app:layout_constraintBottom_toTopOf="@id/tv_processing" + app:layout_constraintTop_toBottomOf="@id/tv_title" + app:veilLayout_baseColor="@color/lightGray0" + app:veilLayout_highlightColor="@color/lightGray1" + app:veilLayout_layout="@layout/card_total_balance_shimmer" + app:veilLayout_radius="4dp" + app:veilLayout_shimmerEnable="true" + app:veilLayout_veiled="true" + tools:veilLayout_veiled="false"> + android:id="@+id/tv_balance" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:minWidth="152dp" + android:textColor="@color/darkGray6" + android:textSize="26sp" + android:textStyle="bold" + tools:text="22 325.40 $" /> + android:id="@+id/tv_processing" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:text="@string/main_processing_full_amount" + android:textColor="@color/warning" + android:textSize="12sp" + android:visibility="gone" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/veil_balance" + tools:visibility="visible" /> + android:id="@+id/tv_currency_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/darkGray1" + android:textSize="16sp" + app:drawableEndCompat="@drawable/ic_arrow_angle_down" + app:drawableTint="@color/darkGray1" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="USD" /> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index dad17e5962..2bd13d5482 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -171,6 +171,15 @@ + + + + diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index f9ac9274ec..ef909db8cc 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -50,7 +50,7 @@ object Versions { const val spongycastleCryptoCore = "1.58.0.0" const val timber = "4.7.1" const val viewBindingDelegate = "1.5.6" - const val xmlShimmer = "1.1.2" + const val xmlShimmer = "1.1.3" const val zendeskChat = "3.3.5" const val zendeskMessaging = "5.2.4" const val zxingQrBarcodeScanner = "1.9.8" diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt index 99a87a701e..57bd37658f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt @@ -17,6 +17,9 @@ data class TangemShapes internal constructor( roundedCornersSmall2 = RoundedCornerShape(size = dimens.radius4), roundedCornersMedium = RoundedCornerShape(size = dimens.radius12), roundedCornersLarge = RoundedCornerShape(size = dimens.radius28), - bottomSheet = RoundedCornerShape(size = dimens.radius16), + bottomSheet = RoundedCornerShape( + topStart = dimens.radius16, + topEnd = dimens.radius16, + ), ) } \ No newline at end of file diff --git a/core/ui/src/main/res/values-it/strings_plurals_final.xml b/core/ui/src/main/res/values-it/strings_plurals_final.xml index 02204c79ec..e79bd3f0cd 100644 --- a/core/ui/src/main/res/values-it/strings_plurals_final.xml +++ b/core/ui/src/main/res/values-it/strings_plurals_final.xml @@ -1,11 +1,13 @@ %d card + %d cards %d cards %d token + %d tokens %d tokens diff --git a/core/ui/src/main/res/values-ru/strings_final.xml b/core/ui/src/main/res/values-ru/strings_final.xml index c8db954763..f111ad038a 100644 --- a/core/ui/src/main/res/values-ru/strings_final.xml +++ b/core/ui/src/main/res/values-ru/strings_final.xml @@ -59,6 +59,7 @@ Карты банков РФ в данный момент не принимаются У вас есть карта банка другой страны или платежной системы UnionPay? Да + Нет Чат Tangem Bot Настройки карты @@ -160,4 +161,5 @@ Добавить новый кошелек %d выбрано Мои кошельки + Этот кошелек уже был сохранен, вы можете добавить другой diff --git a/core/ui/src/main/res/values/strings_final.xml b/core/ui/src/main/res/values/strings_final.xml index 6f508e1d28..450d76907c 100644 --- a/core/ui/src/main/res/values/strings_final.xml +++ b/core/ui/src/main/res/values/strings_final.xml @@ -162,4 +162,5 @@ Add new wallet %d selected My Wallets + This wallet has already been saved, you can add another one diff --git a/domain/src/main/java/com/tangem/domain/common/CardInfo.kt b/domain/src/main/java/com/tangem/domain/common/CardInfo.kt new file mode 100644 index 0000000000..7953c475ac --- /dev/null +++ b/domain/src/main/java/com/tangem/domain/common/CardInfo.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.common + +import com.tangem.common.card.WalletData +import com.tangem.operations.backup.PrimaryCard +import com.tangem.operations.derivation.ExtendedPublicKeysMap + +data class CardInfo( + val card: CardDTO, + val productType: ProductType, + val walletData: WalletData?, + val secondTwinPublicKey: String?, + val derivedKeys: Map, + val primaryCard: PrimaryCard?, +) \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt b/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt index 0e516d7b8e..4dc70fd2bd 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt +++ b/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt @@ -1,12 +1,26 @@ package com.tangem.domain.common.util +import com.tangem.common.extensions.calculateSha256 import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.extensions.calculateHmacSha256 val CardDTO.userWalletId: UserWalletId get() = UserWalletId(findWalletPublicKey(wallets)) -private fun findWalletPublicKey(wallets: List): ByteArray { +val CardDTO.encryptionKey: ByteArray + get() = findWalletPublicKey(wallets) + ?.let { calculateEncryptionKey(it) } + ?: error("Wallet ID not found") + +private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { + val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() + val keyHash = publicKey.calculateSha256() + return message.calculateHmacSha256(keyHash) +} + +private fun findWalletPublicKey(wallets: List): ByteArray? { return wallets.firstOrNull() ?.publicKey - ?: error("Wallet not found") -} \ No newline at end of file +} + +private const val MESSAGE_FOR_ENCRYPTION_KEY = "UserWalletEncryptionKey" \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt index 1d51a46d2c..4e83c913ca 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt +++ b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt @@ -26,6 +26,12 @@ class UserWalletId( override fun hashCode(): Int { return stringValue.hashCode() } + + override fun toString(): String { + return with(stringValue) { + "UserWalletId(${take(3)}...${takeLast(3)})" + } + } } private fun calculateUserWalletId(publicKey: ByteArray): ByteArray { From 3d8aba391f066251f33ce94cb7e4c31d6ac975d0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Dec 2022 16:07:33 +0300 Subject: [PATCH 098/176] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../main/java/com/tangem/feature/referral/ui/ReferralScreen.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index bfc2bf8157..a1658496e7 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit bfc2bf8157089bce6b44779bdae66df2c920de70 +Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6 diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 8c73548fcf..39d5296a12 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -355,7 +355,7 @@ private fun ShimmerInfo() { modifier = Modifier .clip(RoundedCornerShape(TangemTheme.dimens.radius6)) .width(TangemTheme.dimens.size40) - .height(TangemTheme.dimens.size11) + .height(TangemTheme.dimens.size12) .background(TangemColorPalette.White), ) } From 922b302cb1c67cdeda76d8caee7cf8df3acd5db3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Dec 2022 16:45:43 +0300 Subject: [PATCH 099/176] Updated on 2026-08-14 --- .../common/feedback/AdditionalFeedbackInfo.kt | 4 +- .../tap/domain/model/TotalFiatBalance.kt | 6 +- .../model/builders/UserWalletBuilder.kt | 2 +- .../model/builders/WalletStoreBuilder.kt | 69 +++++++++++++++---- .../DefaultWalletCurrenciesManager.kt | 5 +- .../DefaultWalletStoresManager.kt | 8 +-- .../saveWallet/redux/SaveWalletMiddleware.kt | 3 +- .../walletSelector/redux/UserWalletModel.kt | 4 +- .../redux/WalletSelectorMiddleware.kt | 52 ++++++++------ .../redux/WalletSelectorReducer.kt | 5 +- core/datasource/build.gradle.kts | 14 ++++ core/ui/build.gradle.kts | 14 ++++ features/referral/data/build.gradle.kts | 14 ++++ .../referral/presentation/build.gradle.kts | 14 ++++ 14 files changed, 159 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 48527059ad..16e1bb559a 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -10,8 +10,8 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.Address import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.extensions.stripZeroPlainString -import com.tangem.tap.domain.extensions.getUserWalletId class AdditionalFeedbackInfo { class EmailWalletInfo( @@ -54,7 +54,7 @@ class AdditionalFeedbackInfo { cardFirmwareVersion = data.card.firmwareVersion.stringValue cardIssuer = data.card.issuer.name signedHashesCount = formatSignedHashes(data.card.wallets) - userWalletId = data.card.getUserWalletId() + userWalletId = data.card.userWalletId.stringValue } fun setWalletsInfo(walletManagers: List) { diff --git a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt index 6bdca4b2de..ac3ac6d56a 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt @@ -11,15 +11,15 @@ sealed class TotalFiatBalance { object Loading : TotalFiatBalance() - class Refreshing( + data class Refreshing( override val amount: BigDecimal, ) : TotalFiatBalance() - class Error( + data class Error( override val amount: BigDecimal, ) : TotalFiatBalance() - class Loaded( + data class Loaded( override val amount: BigDecimal, ) : TotalFiatBalance() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 4ea9db1491..377b8cf8c6 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -10,7 +10,7 @@ class UserWalletBuilder( ) { private var backupCardsIds: Set = emptySet() - fun setBackupCardsIds(backupCardsIds: Set?) = this.apply { + fun backupCardsIds(backupCardsIds: Set?) = this.apply { if (backupCardsIds != null) { this.backupCardsIds = backupCardsIds } diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt index 004184aacf..003c85904a 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -10,25 +10,70 @@ import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.reducers.createAddressesData import java.math.BigDecimal -class WalletStoreBuilder( - private val userWallet: UserWallet, -) { - private var walletManager: WalletManager? = null - private var blockchainNetwork: BlockchainNetwork? = null +interface WalletStoreBuilder { + fun build(): WalletStoreModel - fun setWalletManager(walletManager: WalletManager?) = this.apply { + interface BlockchainNetworkWalletStoreBuilder : WalletStoreBuilder { + fun walletManager(walletManager: WalletManager?): WalletStoreBuilder + } + + interface WalletMangerWalletStoreBuilder : WalletStoreBuilder { + fun blockchainNetwork(blockchainNetwork: BlockchainNetwork?): WalletStoreBuilder + } + + companion object { + operator fun invoke( + userWallet: UserWallet, + blockchainNetwork: BlockchainNetwork, + ): BlockchainNetworkWalletStoreBuilder { + return BlockchainNetworkWalletStoreBuilderImpl(userWallet, blockchainNetwork) + } + + operator fun invoke( + userWallet: UserWallet, + walletManager: WalletManager, + ): WalletMangerWalletStoreBuilder { + return WalletMangerWalletStoreBuilderImpl(userWallet, walletManager) + } + } +} + +private class BlockchainNetworkWalletStoreBuilderImpl( + private val userWallet: UserWallet, + private val blockchainNetwork: BlockchainNetwork, +) : WalletStoreBuilder.BlockchainNetworkWalletStoreBuilder { + private var walletManager: WalletManager? = null + + override fun walletManager(walletManager: WalletManager?) = this.apply { this.walletManager = walletManager } - fun setBlockchainNetwork(blockchainNetwork: BlockchainNetwork?) = this.apply { + override fun build(): WalletStoreModel { + val blockchainWalletData = blockchainNetwork.getBlockchainWalletData(walletManager) + val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager) + + return WalletStoreModel( + userWalletId = userWallet.walletId, + blockchainNetwork = blockchainNetwork, + walletManager = walletManager, + walletsData = (listOf(blockchainWalletData) + tokensWalletsData), + walletRent = null, + ) + } +} + +private class WalletMangerWalletStoreBuilderImpl( + private val userWallet: UserWallet, + private val walletManager: WalletManager, +) : WalletStoreBuilder.WalletMangerWalletStoreBuilder { + private var blockchainNetwork: BlockchainNetwork? = null + + override fun blockchainNetwork(blockchainNetwork: BlockchainNetwork?) = this.apply { this.blockchainNetwork = blockchainNetwork } - fun build(): WalletStoreModel { - val blockchainNetwork = this.blockchainNetwork - ?: walletManager?.let(BlockchainNetwork::fromWalletManager) - ?: error("Blockchain network and wallet manager must not be null") - + override fun build(): WalletStoreModel { + val blockchainNetwork = this.blockchainNetwork ?: BlockchainNetwork.fromWalletManager(walletManager) val blockchainWalletData = blockchainNetwork.getBlockchainWalletData(walletManager) val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager) diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index 6a63e8f5de..c664c46c0a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -187,9 +187,8 @@ internal class DefaultWalletCurrenciesManager( .flatMap { walletManager -> walletStoresRepository.storeOrUpdate( userWalletId = userWalletId, - walletStore = WalletStoreBuilder(userWallet) - .setWalletManager(walletManager) - .setBlockchainNetwork(blockchainNetwork) + walletStore = WalletStoreBuilder(userWallet, walletManager) + .blockchainNetwork(blockchainNetwork) .build(), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt index 442ac5bd45..52224a9f13 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -122,9 +122,8 @@ internal class DefaultWalletStoresManager( { walletManager -> walletStoresRepository.storeOrUpdate( userWalletId = userWalletId, - walletStore = WalletStoreBuilder(userWallet) - .setWalletManager(walletManager) - .setBlockchainNetwork(blockchainNetwork) + walletStore = WalletStoreBuilder(userWallet, blockchainNetwork) + .walletManager(walletManager) .build(), ) } @@ -161,8 +160,7 @@ internal class DefaultWalletStoresManager( .flatMap { walletManager -> walletStoresRepository.storeOrUpdate( userWalletId = userWallet.walletId, - walletStore = WalletStoreBuilder(userWallet) - .setWalletManager(walletManager) + walletStore = WalletStoreBuilder(userWallet, walletManager) .build(), ) } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 6c4c0e4288..286b00e96d 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -9,7 +9,6 @@ import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.preferencesStorage import com.tangem.tap.scope @@ -67,7 +66,7 @@ internal class SaveWalletMiddleware { scope.launch { val userWallet = UserWalletBuilder(scanResponse) - .setBackupCardsIds(backupCardsIds = state.backupInfo?.backupCardsIds) + .backupCardsIds(state.backupInfo?.backupCardsIds) .build() saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt index a1dc438e52..3c61afc62c 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt @@ -11,12 +11,12 @@ data class UserWalletModel( ) { sealed interface Type { data class SingleCurrency( - val blockchainName: String?, + val blockchainName: String? = null, ) : Type data class MultiCurrency( - val tokensCount: Int, val cardsInWallet: Int, + val tokensCount: Int = 0, ) : Type } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 00f5b77e25..00540c19f2 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -6,7 +6,6 @@ import com.tangem.common.flatMap import com.tangem.common.map import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.* import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState @@ -16,6 +15,12 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.totalFiatBalanceCalculator +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletStoresManager import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -86,26 +91,7 @@ internal class WalletSelectorMiddleware { scope.launch { val updatedWallet = state.wallets .find { it.id == walletId.stringValue } - ?.let { wallet -> - wallet.copy( - type = when (val type = wallet.type) { - is UserWalletModel.Type.MultiCurrency -> type.copy( - tokensCount = walletStores.flatMap { it.walletsData }.size, - ) - is UserWalletModel.Type.SingleCurrency -> type.copy( - blockchainName = walletStores - .firstOrNull() - ?.blockchainNetwork - ?.blockchain - ?.fullName, - ) - }, - fiatBalance = totalFiatBalanceCalculator.calculate( - prevAmount = wallet.fiatBalance.amount, - walletStores = walletStores, - ), - ) - } + ?.updateWalletStoresAndCalculateFiatBalance(walletStores) if (updatedWallet != null) { store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) @@ -138,6 +124,7 @@ internal class WalletSelectorMiddleware { val selectedWallet = userWalletsListManager.selectedUserWallet.first() val isSavedWalletSelected = userWallet == selectedWallet store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + if (isSavedWalletSelected) { store.dispatchOnMain(NavigationAction.PopBackTo()) store.onUserWalletSelected(selectedWallet) @@ -209,4 +196,27 @@ internal class WalletSelectorMiddleware { }, ) } + + private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( + walletStores: List, + ): UserWalletModel { + return this.copy( + type = when (type) { + is UserWalletModel.Type.MultiCurrency -> type.copy( + tokensCount = walletStores.flatMap { it.walletsData }.size, + ) + is UserWalletModel.Type.SingleCurrency -> type.copy( + blockchainName = walletStores + .firstOrNull() + ?.blockchainNetwork + ?.blockchain + ?.fullName, + ) + }, + fiatBalance = totalFiatBalanceCalculator.calculate( + prevAmount = fiatBalance.amount, + walletStores = walletStores, + ), + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index 82fe296fd4..b9f2ca6c70 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -93,15 +93,12 @@ internal object WalletSelectorReducer { private fun UserWallet.getType(): UserWalletModel.Type { return if (scanResponse.card.isMultiwalletAllowed) { UserWalletModel.Type.MultiCurrency( - tokensCount = 0, cardsInWallet = (scanResponse.card.backupStatus as? CardDTO.BackupStatus.Active) ?.cardCount?.inc() ?: 1, ) } else { - UserWalletModel.Type.SingleCurrency( - blockchainName = null, - ) + UserWalletModel.Type.SingleCurrency() } } } \ No newline at end of file diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 20d83b55e0..df394557fd 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -21,6 +21,20 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 isCoreLibraryDesugaringEnabled = false } + + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } } dependencies { diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 6881f67082..929960a637 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -27,6 +27,20 @@ android { composeOptions { kotlinCompilerExtensionVersion = Versions.compose } + + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } } dependencies { diff --git a/features/referral/data/build.gradle.kts b/features/referral/data/build.gradle.kts index 497d0af645..510d84817c 100644 --- a/features/referral/data/build.gradle.kts +++ b/features/referral/data/build.gradle.kts @@ -22,6 +22,20 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 isCoreLibraryDesugaringEnabled = false } + + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } } dependencies { diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index 226a789737..19a4cc8c13 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -30,6 +30,20 @@ android { composeOptions { kotlinCompilerExtensionVersion = Versions.compose } + + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } } dependencies { From 3b33b9753b72331f5800ada0825f52c0b617343e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 10:27:29 +0300 Subject: [PATCH 100/176] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- buildSrc/src/main/java/Versions.kt | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a1658496e7..bfc2bf8157 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6 +Subproject commit bfc2bf8157089bce6b44779bdae66df2c920de70 diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index ef909db8cc..e43bcc7e0d 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -58,10 +58,8 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "0.0.1" - // const val tangemBlockchainSdk = "develop-140" - const val tangemCardSgk = "0.0.1" - // const val tangemCardSgk = "develop-165" + const val tangemBlockchainSdk = "develop-141" + const val tangemCardSgk = "develop-171" // endregion Tangem // region Testing From 76a9691e0d668dd6f79ddd78caa9110f5e2b0307 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 10:54:38 +0300 Subject: [PATCH 101/176] Updated on 2026-08-14 --- .../com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt | 2 +- .../com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt | 1 - .../main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt | 1 - app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt | 3 +-- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index 3860732b7d..d812507524 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -16,7 +16,7 @@ class CreateFirstTwinWalletTask : CardSessionRunnable { callback: (result: CompletionResult) -> Unit, ) { val card = session.environment.card - val publicKey = card?.getSingleWallet()?.publicKey + val publicKey = card?.wallets?.firstOrNull()?.publicKey if (publicKey != null) { if (TwinsHelper.getTwinCardNumber(card.cardId) == TwinCardNumber.Second) { callback(CompletionResult.Failure(WrongTwinCard(TwinCardNumber.First))) diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index cca9da3551..8950bc0a02 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -12,7 +12,6 @@ import com.tangem.domain.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand -import com.tangem.tap.domain.extensions.getSingleWallet class CreateSecondTwinWalletTask( private val firstPublicKey: String, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 2e2e0f9642..9aed6f968c 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -48,7 +48,6 @@ class TwinCardsManager( ): CompletionResult { val task = CreateSecondTwinWalletTask( firstPublicKey = currentCardPublicKey!!, - firstCardId = firstCardId, issuerKeys = issuerKeyPair, preparingMessage = preparingMessage, creatingWalletMessage = creatingWalletMessage, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt b/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt index 24d032e94f..d05d28217e 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/WrongTwinCard.kt @@ -5,8 +5,7 @@ import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.tangemSdkManager import com.tangem.wallet.R -data class WrongTwinCard(private val twinCardNumber: TwinCardNumber) : TangemError { - override val code: Int = 50005 +data class WrongTwinCard(private val twinCardNumber: TwinCardNumber) : TangemError(code = 50005) { override var customMessage: String = tangemSdkManager.getString( R.string.twin_error_same_card, twinCardNumber.number, From b5af62f06b802f27d9b13517c68a50355f179a88 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Nov 2022 13:19:57 +0300 Subject: [PATCH 102/176] Updated on 2026-08-14 --- app/build.gradle.kts | 3 + features/swap/data/.gitignore | 1 + features/swap/data/build.gradle.kts | 37 ++++++++++++ .../swap/data/src/main/AndroidManifest.xml | 2 + features/swap/domain/.gitignore | 1 + features/swap/domain/build.gradle.kts | 9 +++ features/swap/presentation/.gitignore | 1 + features/swap/presentation/build.gradle.kts | 59 +++++++++++++++++++ .../presentation/src/main/AndroidManifest.xml | 2 + settings.gradle.kts | 3 + 10 files changed, 118 insertions(+) create mode 100644 features/swap/data/.gitignore create mode 100644 features/swap/data/build.gradle.kts create mode 100644 features/swap/data/src/main/AndroidManifest.xml create mode 100644 features/swap/domain/.gitignore create mode 100644 features/swap/domain/build.gradle.kts create mode 100644 features/swap/presentation/.gitignore create mode 100644 features/swap/presentation/build.gradle.kts create mode 100644 features/swap/presentation/src/main/AndroidManifest.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8dd72df3e7..e57fabb13b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -119,6 +119,9 @@ dependencies { implementation(project(":features:referral:presentation")) implementation(project(":features:referral:domain")) implementation(project(":features:referral:data")) + implementation(project(":features:swap:presentation")) + implementation(project(":features:swap:domain")) + implementation(project(":features:swap:data")) /** AndroidX libraries */ implementation(AndroidX.coreKtx) diff --git a/features/swap/data/.gitignore b/features/swap/data/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/swap/data/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts new file mode 100644 index 0000000000..298948e089 --- /dev/null +++ b/features/swap/data/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") + id("com.google.dagger.hilt.android") +} + +android { + + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } +} + +dependencies { + + /** Project*/ + implementation(project(":core:datasource")) + implementation(project(":core:utils")) + implementation(project(":features:swap:domain")) + + /** DI */ + implementation(Library.hilt) + kapt(Library.hiltKapt) +} \ No newline at end of file diff --git a/features/swap/data/src/main/AndroidManifest.xml b/features/swap/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..5981b0aeee --- /dev/null +++ b/features/swap/data/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/features/swap/domain/.gitignore b/features/swap/domain/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/swap/domain/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts new file mode 100644 index 0000000000..e8259cee84 --- /dev/null +++ b/features/swap/domain/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("java-library") + id("org.jetbrains.kotlin.jvm") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/features/swap/presentation/.gitignore b/features/swap/presentation/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/swap/presentation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts new file mode 100644 index 0000000000..8eac586d41 --- /dev/null +++ b/features/swap/presentation/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + id("com.android.library") + kotlin("android") + kotlin("kapt") + id("com.google.dagger.hilt.android") +} + +android { + + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + isCoreLibraryDesugaringEnabled = false + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = Versions.compose + } +} + +dependencies { + /** Core modules */ + implementation(project(":core:utils")) + implementation(project(":core:ui")) + + /** AndroidX */ + implementation(AndroidX.appCompat) + implementation(AndroidX.fragmentKtx) + implementation(AndroidX.lifecycleViewModelKtx) + + /** Compose */ + implementation(Compose.foundation) + implementation(Compose.material) + implementation(Compose.uiTooling) + + /** Domain */ + implementation(project(":features:swap:domain")) + + /** Other libraries */ + implementation(Library.composeShimmer) + + /** DI */ + implementation(Library.hilt) + kapt(Library.hiltKapt) +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/AndroidManifest.xml b/features/swap/presentation/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..927ad53a71 --- /dev/null +++ b/features/swap/presentation/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 76949019de..a0a79551bb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,4 +18,7 @@ include(":libs:auth") include(":features:referral:data") include(":features:referral:domain") include(":features:referral:presentation") +include(":features:swap:data") +include(":features:swap:domain") +include(":features:swap:presentation") // endregion Feature modules \ No newline at end of file From 20e6ff8ef0a8f798fd32d5d7798ef70287f05448 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Nov 2022 10:13:16 +0300 Subject: [PATCH 103/176] Updated on 2026-08-14 --- .../datasource/api/oneinch/OneInchApi.kt | 200 ++++++++++++++++++ .../api/oneinch/models/AllowanceResponse.kt | 7 + .../oneinch/models/ApproveCalldataResponse.kt | 18 ++ .../oneinch/models/ApproveSpenderResponse.kt | 12 ++ .../api/oneinch/models/PathViewDto.kt | 13 ++ .../api/oneinch/models/ProtocolsResponse.kt | 27 +++ .../api/oneinch/models/QuoteResponse.kt | 22 ++ .../api/oneinch/models/StatusResponse.kt | 8 + .../api/oneinch/models/SwapErrorDto.kt | 42 ++++ .../api/oneinch/models/SwapResponse.kt | 12 ++ .../api/oneinch/models/TokenOneInchDto.kt | 20 ++ .../api/oneinch/models/TokensResponse.kt | 12 ++ .../api/oneinch/models/TransactionDto.kt | 23 ++ .../com/tangem/datasource/di/NetworkModule.kt | 17 ++ .../di/qualifiers/OneInchApiQualifiers.kt | 15 ++ 15 files changed, 448 insertions(+) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/AllowanceResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ApproveCalldataResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ApproveSpenderResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/PathViewDto.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ProtocolsResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/QuoteResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/StatusResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapErrorDto.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TokenOneInchDto.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TokensResponse.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TransactionDto.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/qualifiers/OneInchApiQualifiers.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt new file mode 100644 index 0000000000..5ce908280b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt @@ -0,0 +1,200 @@ +package com.tangem.datasource.api.oneinch + +import com.tangem.datasource.api.oneinch.models.AllowanceResponse +import com.tangem.datasource.api.oneinch.models.ApproveCalldataResponse +import com.tangem.datasource.api.oneinch.models.ApproveSpenderResponse +import com.tangem.datasource.api.oneinch.models.ProtocolsResponse +import com.tangem.datasource.api.oneinch.models.QuoteResponse +import com.tangem.datasource.api.oneinch.models.StatusResponse +import com.tangem.datasource.api.oneinch.models.SwapResponse +import com.tangem.datasource.api.oneinch.models.TokensResponse +import retrofit2.http.GET +import retrofit2.http.Query + +interface OneInchApi { + + /** + * Healthcheck return 200 if service is available + * + * @return [StatusResponse] + */ + @GET("healthcheck") + suspend fun healthcheck(): StatusResponse + + //region Approve + /** + * Address of the 1inch router that must be trusted to spend funds for the exchange + * + * @return [ApproveSpenderResponse] + */ + @GET("approve/spender") + suspend fun approveSpender(): ApproveSpenderResponse + + /** + * Generate data for calling the contract in order to allow the 1inch router to spend funds + * + * @param tokenAddress Token address you want to exchange + * @param amount The number of tokens that the 1inch router is allowed to spend. + * If not specified, it will be allowed to spend an infinite amount of tokens. + * + * @return [ApproveCalldataResponse] Transaction body to allow the exchange with the 1inch router + */ + @GET("approve/transaction") + suspend fun approveTransaction( + @Query("tokenAddress") tokenAddress: String, + @Query("amount") amount: String?, + ): ApproveCalldataResponse + + /** + * Get the number of tokens that the 1inch router is allowed to spend + * + * @param tokenAddress Token address you want to exchange + * @param walletAddress Wallet address for which you want to check + * + * @return [AllowanceResponse] + */ + @GET("approve/allowance") + suspend fun approveAllowance( + @Query("tokenAddress") tokenAddress: String, + @Query("walletAddress") walletAddress: String, + ): AllowanceResponse + //endregion Approve + + //region Info + /** + * List of tokens that are available for swap in the 1inch Aggregation protocol + * + * @return [TokensResponse] + */ + @GET("tokens") + suspend fun tokensAvailable(): TokensResponse + + /** + * List of liquidity sources that are available for routing in the 1inch Aggregation protocol + * + * @return + */ + @GET("liquidity-sources") + suspend fun liquiditySources(): ProtocolsResponse + //endregion Info + + //region Swap + /** + * Find the best quote to exchange via 1inch router + * + * @param fromTokenAddress Example : 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE + * @param toTokenAddress Example : 0x111111111117dc0aa78b770fa6a738034120c302 + * @param amount amount of a token to sell, set in minimal divisible units e.g.: + * 1.00 DAI set as 1000000000000000000 + * 51.03 USDC set as 51030000 + * + * @param protocols default: all + * @param fee this percentage of fromTokenAddress token amount will be sent to referrerAddress, + * the rest will be used as input for a swap + * Min: 0; max: 3; Max: 0; max: 3; default: 0; !should be the same for quote and swap! + * + * @param gasLimit maximum amount of gas for a swap; + * @param connectorTokens token-connectors can be specified via this parameter. + * The more is set — the longer route estimation will take. + * If not set, default token-connectors will be usedmax: 5; !should be the same for quote and swap! + * + * @param complexityLevel maximum number of token-connectors to be used in a transaction. + * The more is used — the longer route estimation will take + * min: 0; max: 3; default: 2; !should be the same for quote and swap! + * + * @param mainRouteParts default: 10; max: 50 !should be the same for quote and swap! + * @param parts limit maximum number of parts each main route parts can be split into; + * should be the same for a quote and swap + * default: 20; max: 100 + * + * @param gasPrice 1inch takes in account gas expenses to determine exchange route. + * It is important to use the same gas price on the quote and swap methods. + * Gas price set in wei: 12.5 GWEI set as 12500000000 + * default: fast from network + * + * @return [QuoteResponse] + */ + suspend fun quote( + @Query("fromTokenAddress") fromTokenAddress: String, + @Query("toTokenAddress") toTokenAddress: String, + @Query("amount") amount: String, + @Query("protocols") protocols: String?, + @Query("fee") fee: String?, + @Query("gasLimit") gasLimit: String?, + @Query("connectorTokens") connectorTokens: String?, + @Query("complexityLevel") complexityLevel: String?, + @Query("mainRouteParts") mainRouteParts: String?, + @Query("parts") parts: String?, + @Query("gasPrice") gasPrice: String?, + ): QuoteResponse + + /** + * Generate data for calling the 1inch router for exchange + * + * @param fromTokenAddress Example : 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE + * @param toTokenAddress Example : 0x111111111117dc0aa78b770fa6a738034120c302 + * @param amount amount of a token to sell, set in minimal divisible units e.g.: + * 1.00 DAI set as 1000000000000000000 + * 51.03 USDC set as 51030000 + * + * @param fromAddress The address that calls the 1inch contract + * @param slippage limit of price slippage you are willing to accept in percentage, may be set with decimals. + * &slippage=0.5 means 0.5% slippage is acceptable. Low values increase chances that transaction will fail, + * high values increase chances of front running. min: 0; max: 50; + * + * @param protocols default: all + * @param destinationAddress Receiver of destination currency. default: fromAddress + * @param fee this percentage of fromTokenAddress token amount will be sent to referrerAddress, + * the rest will be used as input for a swap + * Min: 0; max: 3; Max: 0; max: 3; default: 0; !should be the same for quote and swap! + * + * @param permit https://eips.ethereum.org/EIPS/eip-2612 + * @param compatibilityMode Allows to build calldata without optimized routers + * @param burnChi If true, CHI will be burned from fromAddress to compensate gas. + * Check CHI balance and allowance before turning that on. CHI should be approved for the spender address + * + * @param connectorTokens token-connectors can be specified via this parameter. + * The more is set — the longer route estimation will take. + * If not set, default token-connectors will be usedmax: 5; !should be the same for quote and swap! + * + * @param complexityLevel maximum number of token-connectors to be used in a transaction. + * The more is used — the longer route estimation will take + * min: 0; max: 3; default: 2; !should be the same for quote and swap! + * + * @param mainRouteParts default: 10; max: 50 !should be the same for quote and swap! + * @param parts limit maximum number of parts each main route parts can be split into; + * should be the same for a quote and swap + * default: 20; max: 100 + * + * @param gasLimit maximum amount of gas for a swap; + * @param gasPrice 1inch takes in account gas expenses to determine exchange route. + * It is important to use the same gas price on the quote and swap methods. + * Gas price set in wei: 12.5 GWEI set as 12500000000 + * default: fast from network + * + * @return [SwapResponse] + */ + suspend fun swap( + @Query("fromTokenAddress") fromTokenAddress: String, + @Query("toTokenAddress") toTokenAddress: String, + @Query("amount") amount: String, + @Query("fromAddress") fromAddress: String, + @Query("slippage") slippage: Int, + @Query("protocols") protocols: String?, + @Query("destReceiver") destinationAddress: String?, + @Query("referrerAddress") referrerAddress: String?, + @Query("fee") fee: String?, + @Query("disableEstimate") disableEstimate: Boolean?, + @Query("permit") permit: String?, + @Query("compatibilityMode") compatibilityMode: Boolean?, + @Query("burnChi") burnChi: Boolean?, + @Query("allowPartialFill") allowPartialFill: Boolean?, + @Query("parts") parts: String?, + @Query("mainRouteParts") mainRouteParts: String?, + @Query("connectorTokens") connectorTokens: String?, + @Query("complexityLevel") complexityLevel: String?, + @Query("gasLimit") gasLimit: String?, + @Query("gasPrice") gasPrice: String?, + ): SwapResponse + //endregion Swap +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/AllowanceResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/AllowanceResponse.kt new file mode 100644 index 0000000000..16956f907e --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/AllowanceResponse.kt @@ -0,0 +1,7 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +data class AllowanceResponse( + @Json(name = "allowance") val allowance: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ApproveCalldataResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ApproveCalldataResponse.kt new file mode 100644 index 0000000000..f57fab94fd --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ApproveCalldataResponse.kt @@ -0,0 +1,18 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +/** + * Approve calldata response + * + * @property data The encoded data to call the approve method on the swapped token contract + * @property gasPrice Gas price for fast transaction processing + * @property toAddress Token address that will be allowed to exchange through 1inch router + * @property value Native token value in WEI (for approve is always 0) + */ +data class ApproveCalldataResponse( + @Json(name = "data") val data: String, + @Json(name = "gasPrice") val gasPrice: String, + @Json(name = "to") val toAddress: String, + @Json(name = "value") val value: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ApproveSpenderResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ApproveSpenderResponse.kt new file mode 100644 index 0000000000..0ee9d5b2ac --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ApproveSpenderResponse.kt @@ -0,0 +1,12 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +/** + * Approve spender response + * + * @property address Address of the 1inch router that must be trusted to spend funds for the exchange + */ +data class ApproveSpenderResponse( + @Json(name = "address") val address: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/PathViewDto.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/PathViewDto.kt new file mode 100644 index 0000000000..03c23155ff --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/PathViewDto.kt @@ -0,0 +1,13 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +/** + * Path view dto + */ +data class PathViewDto( + @Json(name = "name") val name: String, + @Json(name = "part") val part: Int, + @Json(name = "fromTokenAddress") val fromTokenAddress: String, + @Json(name = "toTokenAddress") val toTokenAddress: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ProtocolsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ProtocolsResponse.kt new file mode 100644 index 0000000000..b083cd1ce8 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/ProtocolsResponse.kt @@ -0,0 +1,27 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +/** + * Protocols response + * + * @property protocols List of protocols that are available for routing in the 1inch Aggregation protocol + */ +data class ProtocolsResponse( + @Json(name = "protocols") val protocols: List, +) + +/** + * Protocol image + * + * @property id Protocol id + * @property title Protocol title + * @property image Protocol logo image + * @property imageColor Protocol logo image in color + */ +data class ProtocolImageDto( + @Json(name = "id") val id: String, + @Json(name = "title") val title: String, + @Json(name = "img") val image: String, + @Json(name = "img_color") val imageColor: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/QuoteResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/QuoteResponse.kt new file mode 100644 index 0000000000..dca5642f12 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/QuoteResponse.kt @@ -0,0 +1,22 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +/** + * Quote response + * + * @property fromToken Source token info + * @property toToken Destination token info + * @property toTokenAmount Expected amount of destination token + * @property fromTokenAmount Amount of source token + * @property protocols Selected protocols in a path + * @property estimatedGas gas fee + */ +data class QuoteResponse( + @Json(name = "fromToken") val fromToken: TokenOneInchDto, + @Json(name = "toToken") val toToken: TokenOneInchDto, + @Json(name = "toTokenAmount") val toTokenAmount: String, + @Json(name = "fromTokenAmount") val fromTokenAmount: String, + @Json(name = "protocols") val protocols: List, + @Json(name = "estimatedGas") val estimatedGas: Int, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/StatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/StatusResponse.kt new file mode 100644 index 0000000000..576851a3b5 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/StatusResponse.kt @@ -0,0 +1,8 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +data class StatusResponse( + @Json(name = "status") val status: String, + @Json(name = "provider") val provider: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapErrorDto.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapErrorDto.kt new file mode 100644 index 0000000000..fe342de231 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapErrorDto.kt @@ -0,0 +1,42 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +/** + * Swap error dto + * + * One of the following errors: + * + * -Insufficient liquidity + * -Cannot estimate + * -You may not have enough ETH balance for gas fee + * -FromTokenAddress cannot be equals to toTokenAddress + * -Cannot estimate. Don't forget about miner fee. Try to leave the buffer of ETH for gas + * -Not enough balance + * -Not enough allowance + * + * @property statusCode HTTP code + * @property error Error code description + * @property description Error description (one of the following) + * @property requestId Request id + * @property meta Meta information + * @constructor Create empty Swap error dto + */ +data class SwapErrorDto( + @Json(name = "statusCode") val statusCode: Int, + @Json(name = "error") val error: String, + @Json(name = "description") val description: String, + @Json(name = "requestId") val requestId: String, + @Json(name = "meta") val meta: List, +) + +/** + * Nest error meta + * + * @property type Type of field + * @property value Value of field + */ +data class NestErrorMeta( + @Json(name = "type") val type: String, + @Json(name = "value") val value: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapResponse.kt new file mode 100644 index 0000000000..1f25a391e0 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/SwapResponse.kt @@ -0,0 +1,12 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +data class SwapResponse( + @Json(name = "fromToken") val fromToken: TokenOneInchDto, + @Json(name = "toToken") val toToken: TokenOneInchDto, + @Json(name = "toTokenAmount") val toTokenAmount: String, + @Json(name = "fromTokenAmount") val fromTokenAmount: String, + @Json(name = "protocols") val protocols: List, + @Json(name = "transaction") val transaction: TransactionDto, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TokenOneInchDto.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TokenOneInchDto.kt new file mode 100644 index 0000000000..0c06f15d84 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TokenOneInchDto.kt @@ -0,0 +1,20 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +/** + * Token one inch + * + * @property symbol token symbol + * @property name token name + * @property address token address + * @property decimals token decimals + * @property logoURI token logo image url + */ +data class TokenOneInchDto( + @Json(name = "symbol") val symbol: String, + @Json(name = "name") val name: String, + @Json(name = "address") val address: String, + @Json(name = "decimals") val decimals: Int, + @Json(name = "logoURI") val logoURI: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TokensResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TokensResponse.kt new file mode 100644 index 0000000000..ca1b369949 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TokensResponse.kt @@ -0,0 +1,12 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +/** + * Tokens response + * + * @property tokens List of supported tokens + */ +data class TokensResponse( + @Json(name = "tokens") val tokens: Map, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TransactionDto.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TransactionDto.kt new file mode 100644 index 0000000000..bd71ce74db --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TransactionDto.kt @@ -0,0 +1,23 @@ +package com.tangem.datasource.api.oneinch.models + +import com.squareup.moshi.Json + +/** + * Transaction dto + * + * @property fromAddress transactions will be sent from this address + * @property toAddress transactions will be sent to our(1inch) contract address + * @property data The encoded data to call the approve method on the swapped token contract + * @property value Native token value in WEI (for approve is always 0) + * @property gasPrice The encoded data to call the approve method on the swapped token contract + * @property gas estimated amount of the gas limit, increase this value by 25% + * @constructor Create empty Transaction dto + */ +data class TransactionDto( + @Json(name = "from") val fromAddress: String, + @Json(name = "to") val toAddress: String, + @Json(name = "data") val data: String, + @Json(name = "value") val value: String, + @Json(name = "gasPrice") val gasPrice: String, + @Json(name = "gas") val gas: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 6fed866ecc..0e88861b6d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -3,7 +3,9 @@ package com.tangem.datasource.di import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.datasource.api.AuthHeaderInterceptor +import com.tangem.datasource.api.oneinch.OneInchApi import com.tangem.datasource.api.referral.ReferralApi +import com.tangem.datasource.di.qualifiers.OneInchEthereum import com.tangem.lib.auth.AuthProvider import dagger.Module import dagger.Provides @@ -19,6 +21,20 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) class NetworkModule { + @Provides + @Singleton + @OneInchEthereum + fun provideOneInchEthereumApi(): OneInchApi { + return Retrofit.Builder() + .addConverterFactory( + MoshiConverterFactory.create(), + ) + .baseUrl(ONE_INCH_ETHER_BASE_URL) + .client(OkHttpClient()) + .build() + .create(OneInchApi::class.java) + } + @Provides @Singleton fun provideReferralApi(okHttpClient: OkHttpClient): ReferralApi { @@ -49,5 +65,6 @@ class NetworkModule { private companion object { const val PROD_REFERRAL_BASE_URL = "https://devapi.tangem-tech.com/v1/"//"https://api.tangem-tech.com/v1/" + const val ONE_INCH_ETHER_BASE_URL = "" } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/qualifiers/OneInchApiQualifiers.kt b/core/datasource/src/main/java/com/tangem/datasource/di/qualifiers/OneInchApiQualifiers.kt new file mode 100644 index 0000000000..f312541fde --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/qualifiers/OneInchApiQualifiers.kt @@ -0,0 +1,15 @@ +package com.tangem.datasource.di.qualifiers + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class OneInchEthereum + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class OneInchBinance + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class OneInchOptimism \ No newline at end of file From 03e48b09296df20fff37d869692dd042cd869795 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Nov 2022 13:38:14 +0300 Subject: [PATCH 104/176] Updated on 2026-08-14 --- app/build.gradle.kts | 2 +- app/src/main/assets/tangem-app-config | 2 +- .../java/com/tangem/tap/TapApplication.kt | 3 +- .../com/tangem/tap/domain/RatesRepository.kt | 2 +- .../java/com/tangem/tap/domain/TapErrors.kt | 2 +- .../com/tangem/tap/domain/tokens/Currency.kt | 2 +- .../tokens/LoadAvailableCoinsService.kt | 6 +-- .../domain/tokens/OldUserTokensRepository.kt | 4 +- .../domain/tokens/UserTokensNetworkService.kt | 4 +- .../tap/domain/tokens/UserTokensRepository.kt | 4 +- .../domain/tokens/UserTokensStorageService.kt | 4 +- .../tap/domain/twins/TwinCardsManager.kt | 4 +- .../walletconnect/WalletConnectRepository.kt | 9 ++-- .../saltPay/SaltPayActivationManager.kt | 33 +++++++------- .../redux/OnboardingSaltPayMiddleware.kt | 42 +++++++++--------- .../saltPay/redux/OnboardingSaltPayState.kt | 2 +- .../tap/features/wallet/models/Currency.kt | 2 +- .../middlewares/AppCurrencyMiddleware.kt | 4 +- .../mercuryo/MercuryoService.kt | 2 +- .../moonpay/MoonPayService.kt | 2 +- .../tangem/tap/network/payid/PayIdService.kt | 2 +- .../tap/network/payid/PayIdVerifyService.kt | 2 +- .../persistence/FiatCurrenciesPrefStorage.kt | 2 +- .../tap/persistence/PreferencesStorage.kt | 2 +- core/datasource/build.gradle.kts | 3 ++ .../datasource/api}/common/Interceptors.kt | 2 +- .../datasource/api}/common/MoshiConverter.kt | 6 ++- .../datasource/api/common/NetworError.kt | 19 ++++++++ .../tangem/datasource/api}/common/Retrofit.kt | 2 +- .../api/paymentology/PaymentologyApi.kt | 2 +- .../paymentology/PaymentologyApiService.kt | 6 +-- .../datasource}/api/paymentology/Requests.kt | 2 +- .../datasource}/api/paymentology/Responses.kt | 2 +- .../datasource}/api/tangemTech/Responses.kt | 2 +- .../api/tangemTech/TangemTechApi.kt | 2 +- .../api/tangemTech/TangemTechService.kt | 8 ++-- domain/build.gradle.kts | 2 +- .../java/com/tangem/domain/DomainDialog.kt | 2 +- .../common/extensions/TangemTechService.kt | 4 +- .../addCustomToken/AddCustomTokenService.kt | 4 +- .../redux/AddCustomTokenAction.kt | 2 +- .../addCustomToken/redux/AddCustomTokenHub.kt | 8 ++-- .../redux/AddCustomTokenState.kt | 4 +- .../domain/redux/global/DomainGlobalHub.kt | 2 +- .../domain/redux/global/DomainGlobalState.kt | 10 +++-- network/.gitignore | 1 - network/build.gradle.kts | 26 ----------- .../results.bin | 1 + .../results.bin | 1 + .../transformed/jetified-network.jar | Bin 0 -> 99352 bytes .../results.bin | 1 + .../results.bin | 1 + .../transformed/jetified-network/classes.dex | Bin 0 -> 93256 bytes .../results.bin | 1 + .../transformed/jetified-network/classes.dex | Bin 0 -> 93256 bytes .../results.bin | 1 + .../transformed/jetified-network.jar | Bin 0 -> 99352 bytes .../results.bin | 1 + .../transformed/jetified-network.jar | Bin 0 -> 99352 bytes .../results.bin | 1 + .../results.bin | 0 .../results.bin | 1 + .../transformed/jetified-network.jar | Bin 0 -> 99352 bytes .../results.bin | 0 .../results.bin | 1 + .../results.bin | 1 + .../results.bin | 1 + .../results.bin | 0 .../results.bin | 0 .../main/META-INF/network.kotlin_module | Bin 0 -> 117 bytes .../paymentology/AttestationResponse.class | Bin 0 -> 4843 bytes .../CheckRegistrationRequests$Item.class | Bin 0 -> 3403 bytes .../CheckRegistrationRequests.class | Bin 0 -> 3152 bytes .../network/api/paymentology/KYCStatus.class | Bin 0 -> 1696 bytes .../PaymentologyApi$Companion.class | Bin 0 -> 1101 bytes .../api/paymentology/PaymentologyApi.class | Bin 0 -> 3372 bytes .../PaymentologyApiService$Companion.class | Bin 0 -> 1203 bytes ...logyApiService$checkRegistration$2$1.class | Bin 0 -> 3995 bytes ...tologyApiService$checkRegistration$2.class | Bin 0 -> 5147 bytes ...ymentologyApiService$registerKYC$2$1.class | Bin 0 -> 3937 bytes ...PaymentologyApiService$registerKYC$2.class | Bin 0 -> 4405 bytes ...ntologyApiService$registerWallet$2$1.class | Bin 0 -> 3962 bytes ...mentologyApiService$registerWallet$2.class | Bin 0 -> 4435 bytes ...vice$requestAttestationChallenge$2$1.class | Bin 0 -> 4076 bytes ...ervice$requestAttestationChallenge$2.class | Bin 0 -> 4907 bytes .../paymentology/PaymentologyApiService.class | Bin 0 -> 6073 bytes .../api/paymentology/RegisterKYCRequest.class | Bin 0 -> 4418 bytes .../paymentology/RegisterWalletRequest.class | Bin 0 -> 6179 bytes .../paymentology/RegisterWalletResponse.class | Bin 0 -> 3758 bytes .../RegistrationResponse$Item.class | Bin 0 -> 8078 bytes .../paymentology/RegistrationResponse.class | Bin 0 -> 5304 bytes .../ResponseError$DefaultImpls.class | Bin 0 -> 1010 bytes .../api/paymentology/ResponseError.class | Bin 0 -> 958 bytes .../api/paymentology/ResponsesKt.class | Bin 0 -> 1575 bytes .../CoinsResponse$Coin$Network.class | Bin 0 -> 4054 bytes .../api/tangemTech/CoinsResponse$Coin.class | Bin 0 -> 5416 bytes .../api/tangemTech/CoinsResponse.class | Bin 0 -> 4077 bytes .../CurrenciesResponse$Currency.class | Bin 0 -> 4815 bytes .../api/tangemTech/CurrenciesResponse.class | Bin 0 -> 3064 bytes .../network/api/tangemTech/GeoResponse.class | Bin 0 -> 2418 bytes .../network/api/tangemTech/HttpResponse.class | Bin 0 -> 379 bytes .../api/tangemTech/RatesResponse.class | Bin 0 -> 2863 bytes .../TangemTechApi$DefaultImpls.class | Bin 0 -> 1217 bytes .../api/tangemTech/TangemTechApi.class | Bin 0 -> 3660 bytes .../api/tangemTech/TangemTechError.class | Bin 0 -> 2765 bytes .../api/tangemTech/TangemTechResponse.class | Bin 0 -> 1011 bytes .../TangemTechService$Companion.class | Bin 0 -> 947 bytes .../TangemTechService$coins$2$1.class | Bin 0 -> 4259 bytes .../TangemTechService$coins$2.class | Bin 0 -> 4714 bytes .../TangemTechService$currencies$2$1.class | Bin 0 -> 3496 bytes .../TangemTechService$currencies$2.class | Bin 0 -> 4034 bytes .../TangemTechService$getUserTokens$2$1.class | Bin 0 -> 3695 bytes .../TangemTechService$getUserTokens$2.class | Bin 0 -> 4178 bytes .../TangemTechService$putUserTokens$2$1.class | Bin 0 -> 3838 bytes .../TangemTechService$putUserTokens$2.class | Bin 0 -> 4317 bytes .../TangemTechService$rates$2$1.class | Bin 0 -> 4501 bytes .../TangemTechService$rates$2.class | Bin 0 -> 4295 bytes .../TangemTechService$userCountry$2$1.class | Bin 0 -> 3473 bytes .../TangemTechService$userCountry$2.class | Bin 0 -> 4019 bytes .../api/tangemTech/TangemTechService.class | Bin 0 -> 9271 bytes .../api/tangemTech/TokenResponse.class | Bin 0 -> 5535 bytes .../api/tangemTech/UserTokensResponse.class | Bin 0 -> 4877 bytes .../network/common/AddHeaderInterceptor.class | Bin 0 -> 2923 bytes .../common/CacheControlHttpInterceptor.class | Bin 0 -> 1114 bytes .../common/CardPublicKeyHttpInterceptor.class | Bin 0 -> 1158 bytes .../network/common/MoshiConverter.class | Bin 0 -> 3421 bytes .../common/NetworkError$Companion.class | Bin 0 -> 814 bytes .../tangem/network/common/NetworkError.class | Bin 0 -> 2084 bytes .../network/common/NetworkModuleMessage.class | Bin 0 -> 521 bytes .../tangem/network/common/RetrofitKt.class | Bin 0 -> 4722 bytes .../kotlin/compileKotlin/build-history.bin | Bin 0 -> 31 bytes .../caches-jvm/inputs/source-to-output.tab | Bin 0 -> 4096 bytes .../inputs/source-to-output.tab.keystream | Bin 0 -> 4096 bytes .../inputs/source-to-output.tab.keystream.len | Bin 0 -> 8 bytes .../inputs/source-to-output.tab.len | Bin 0 -> 8 bytes .../inputs/source-to-output.tab.values.at | Bin 0 -> 7756 bytes .../caches-jvm/inputs/source-to-output.tab_i | Bin 0 -> 32768 bytes .../inputs/source-to-output.tab_i.len | Bin 0 -> 8 bytes .../jvm/kotlin/class-attributes.tab | Bin 0 -> 4096 bytes .../jvm/kotlin/class-attributes.tab.keystream | Bin 0 -> 4096 bytes .../kotlin/class-attributes.tab.keystream.len | Bin 0 -> 8 bytes .../jvm/kotlin/class-attributes.tab.len | Bin 0 -> 8 bytes .../jvm/kotlin/class-attributes.tab.values.at | Bin 0 -> 157 bytes .../jvm/kotlin/class-attributes.tab_i | Bin 0 -> 32768 bytes .../jvm/kotlin/class-attributes.tab_i.len | Bin 0 -> 8 bytes .../jvm/kotlin/class-fq-name-to-source.tab | Bin 0 -> 4096 bytes .../class-fq-name-to-source.tab.keystream | Bin 0 -> 4096 bytes .../class-fq-name-to-source.tab.keystream.len | Bin 0 -> 8 bytes .../kotlin/class-fq-name-to-source.tab.len | Bin 0 -> 8 bytes .../class-fq-name-to-source.tab.values.at | Bin 0 -> 3155 bytes .../jvm/kotlin/class-fq-name-to-source.tab_i | Bin 0 -> 32768 bytes .../kotlin/class-fq-name-to-source.tab_i.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/constants.tab | Bin 0 -> 4096 bytes .../jvm/kotlin/constants.tab.keystream | Bin 0 -> 4096 bytes .../jvm/kotlin/constants.tab.keystream.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/constants.tab.len | Bin 0 -> 8 bytes .../jvm/kotlin/constants.tab.values.at | Bin 0 -> 171 bytes .../caches-jvm/jvm/kotlin/constants.tab_i | Bin 0 -> 32768 bytes .../caches-jvm/jvm/kotlin/constants.tab_i.len | Bin 0 -> 8 bytes .../jvm/kotlin/inline-functions.tab | Bin 0 -> 4096 bytes .../jvm/kotlin/inline-functions.tab.keystream | Bin 0 -> 4096 bytes .../kotlin/inline-functions.tab.keystream.len | Bin 0 -> 8 bytes .../jvm/kotlin/inline-functions.tab.len | Bin 0 -> 8 bytes .../jvm/kotlin/inline-functions.tab.values.at | Bin 0 -> 274 bytes .../jvm/kotlin/inline-functions.tab_i | Bin 0 -> 32768 bytes .../jvm/kotlin/inline-functions.tab_i.len | Bin 0 -> 8 bytes .../jvm/kotlin/internal-name-to-source.tab | Bin 0 -> 4096 bytes .../internal-name-to-source.tab.keystream | Bin 0 -> 4096 bytes .../internal-name-to-source.tab.keystream.len | Bin 0 -> 8 bytes .../kotlin/internal-name-to-source.tab.len | Bin 0 -> 8 bytes .../internal-name-to-source.tab.values.at | Bin 0 -> 5410 bytes .../jvm/kotlin/internal-name-to-source.tab_i | Bin 0 -> 32768 bytes .../kotlin/internal-name-to-source.tab_i.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/package-parts.tab | Bin 0 -> 4096 bytes .../jvm/kotlin/package-parts.tab.keystream | Bin 0 -> 4096 bytes .../kotlin/package-parts.tab.keystream.len | Bin 0 -> 8 bytes .../jvm/kotlin/package-parts.tab.len | Bin 0 -> 8 bytes .../jvm/kotlin/package-parts.tab.values.at | Bin 0 -> 55 bytes .../caches-jvm/jvm/kotlin/package-parts.tab_i | Bin 0 -> 32768 bytes .../jvm/kotlin/package-parts.tab_i.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/proto.tab | Bin 0 -> 4096 bytes .../caches-jvm/jvm/kotlin/proto.tab.keystream | Bin 0 -> 4096 bytes .../jvm/kotlin/proto.tab.keystream.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/proto.tab.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/proto.tab.values.at | Bin 0 -> 24785 bytes .../caches-jvm/jvm/kotlin/proto.tab_i | Bin 0 -> 32768 bytes .../caches-jvm/jvm/kotlin/proto.tab_i.len | Bin 0 -> 8 bytes .../jvm/kotlin/source-to-classes.tab | Bin 0 -> 4096 bytes .../kotlin/source-to-classes.tab.keystream | Bin 0 -> 4096 bytes .../source-to-classes.tab.keystream.len | Bin 0 -> 8 bytes .../jvm/kotlin/source-to-classes.tab.len | Bin 0 -> 8 bytes .../kotlin/source-to-classes.tab.values.at | Bin 0 -> 3808 bytes .../jvm/kotlin/source-to-classes.tab_i | Bin 0 -> 32768 bytes .../jvm/kotlin/source-to-classes.tab_i.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/subtypes.tab | Bin 0 -> 4096 bytes .../jvm/kotlin/subtypes.tab.keystream | Bin 0 -> 4096 bytes .../jvm/kotlin/subtypes.tab.keystream.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/subtypes.tab.len | Bin 0 -> 8 bytes .../jvm/kotlin/subtypes.tab.values.at | Bin 0 -> 1039 bytes .../caches-jvm/jvm/kotlin/subtypes.tab_i | Bin 0 -> 32768 bytes .../caches-jvm/jvm/kotlin/subtypes.tab_i.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/supertypes.tab | Bin 0 -> 4096 bytes .../jvm/kotlin/supertypes.tab.keystream | Bin 0 -> 4096 bytes .../jvm/kotlin/supertypes.tab.keystream.len | Bin 0 -> 8 bytes .../caches-jvm/jvm/kotlin/supertypes.tab.len | Bin 0 -> 8 bytes .../jvm/kotlin/supertypes.tab.values.at | Bin 0 -> 955 bytes .../caches-jvm/jvm/kotlin/supertypes.tab_i | Bin 0 -> 32768 bytes .../jvm/kotlin/supertypes.tab_i.len | Bin 0 -> 8 bytes .../caches-jvm/lookups/counters.tab | 2 + .../caches-jvm/lookups/file-to-id.tab | Bin 0 -> 4096 bytes .../lookups/file-to-id.tab.keystream | Bin 0 -> 4096 bytes .../lookups/file-to-id.tab.keystream.len | Bin 0 -> 8 bytes .../caches-jvm/lookups/file-to-id.tab.len | Bin 0 -> 8 bytes .../lookups/file-to-id.tab.values.at | Bin 0 -> 115 bytes .../caches-jvm/lookups/file-to-id.tab_i | Bin 0 -> 32768 bytes .../caches-jvm/lookups/file-to-id.tab_i.len | Bin 0 -> 8 bytes .../caches-jvm/lookups/id-to-file.tab | Bin 0 -> 4096 bytes .../lookups/id-to-file.tab.keystream | Bin 0 -> 4096 bytes .../lookups/id-to-file.tab.keystream.len | Bin 0 -> 8 bytes .../caches-jvm/lookups/id-to-file.tab.len | Bin 0 -> 8 bytes .../lookups/id-to-file.tab.values.at | Bin 0 -> 999 bytes .../caches-jvm/lookups/id-to-file.tab_i | Bin 0 -> 32768 bytes .../caches-jvm/lookups/id-to-file.tab_i.len | Bin 0 -> 8 bytes .../caches-jvm/lookups/lookups.tab | Bin 0 -> 16384 bytes .../caches-jvm/lookups/lookups.tab.keystream | Bin 0 -> 12288 bytes .../lookups/lookups.tab.keystream.len | Bin 0 -> 8 bytes .../caches-jvm/lookups/lookups.tab.len | Bin 0 -> 8 bytes .../caches-jvm/lookups/lookups.tab.values.at | Bin 0 -> 7475 bytes .../caches-jvm/lookups/lookups.tab_i | Bin 0 -> 32768 bytes .../caches-jvm/lookups/lookups.tab_i.len | Bin 0 -> 8 bytes .../build/kotlin/compileKotlin/last-build.bin | Bin 0 -> 18 bytes network/build/kotlin/networkjar-classes.txt | 1 + network/build/libs/network.jar | Bin 0 -> 99352 bytes network/build/tmp/jar/MANIFEST.MF | 2 + .../com/tangem/network/common/NetworError.kt | 23 ---------- settings.gradle.kts | 1 - 236 files changed, 150 insertions(+), 155 deletions(-) rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource/api}/common/Interceptors.kt (94%) rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource/api}/common/MoshiConverter.kt (79%) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/common/NetworError.kt rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource/api}/common/Retrofit.kt (96%) rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource}/api/paymentology/PaymentologyApi.kt (95%) rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource}/api/paymentology/PaymentologyApiService.kt (91%) rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource}/api/paymentology/Requests.kt (98%) rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource}/api/paymentology/Responses.kt (98%) rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource}/api/tangemTech/Responses.kt (97%) rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource}/api/tangemTech/TangemTechApi.kt (96%) rename {network/src/main/java/com/tangem/network => core/datasource/src/main/java/com/tangem/datasource}/api/tangemTech/TangemTechService.kt (91%) delete mode 100644 network/.gitignore delete mode 100644 network/build.gradle.kts create mode 100644 network/build/.transforms/0a0a9b6fcdf800647341aabb7296a58d/results.bin create mode 100644 network/build/.transforms/10b56b612fced146421785dc9c798db5/results.bin create mode 100644 network/build/.transforms/10b56b612fced146421785dc9c798db5/transformed/jetified-network.jar create mode 100644 network/build/.transforms/129172ef9b6bb005a6da315416650247/results.bin create mode 100644 network/build/.transforms/5502ea290d619da65e391069e65e2fa9/results.bin create mode 100644 network/build/.transforms/5502ea290d619da65e391069e65e2fa9/transformed/jetified-network/classes.dex create mode 100644 network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/results.bin create mode 100644 network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/transformed/jetified-network/classes.dex create mode 100644 network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/results.bin create mode 100644 network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/transformed/jetified-network.jar create mode 100644 network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/results.bin create mode 100644 network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/transformed/jetified-network.jar create mode 100644 network/build/.transforms/882f21e6ea520a732fa9828baf6837a4/results.bin create mode 100644 network/build/.transforms/99b0c959c827d9869a7277c0ca875111/results.bin create mode 100644 network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/results.bin create mode 100644 network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/transformed/jetified-network.jar create mode 100644 network/build/.transforms/b04ba1e3b1e5614e897560d529aff7a1/results.bin create mode 100644 network/build/.transforms/b37124a83829aa553636076c9ead584a/results.bin create mode 100644 network/build/.transforms/c4301af1e3ac6220228773d71e26c811/results.bin create mode 100644 network/build/.transforms/dda44431310664cbf70b6a6d8e9af52f/results.bin create mode 100644 network/build/.transforms/e7ae8b25da52bba94522842530316ba6/results.bin create mode 100644 network/build/.transforms/f73a389fe3a1ec5bb1563a53989b4562/results.bin create mode 100644 network/build/classes/kotlin/main/META-INF/network.kotlin_module create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/AttestationResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests$Item.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/KYCStatus.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApi$Companion.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApi.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$Companion.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$requestAttestationChallenge$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$requestAttestationChallenge$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterKYCRequest.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterWalletRequest.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterWalletResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse$Item.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError$DefaultImpls.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponsesKt.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin$Network.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse$Currency.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/GeoResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/HttpResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/RatesResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi$DefaultImpls.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechError.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$Companion.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$userCountry$2$1.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$userCountry$2.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TokenResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/UserTokensResponse.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/AddHeaderInterceptor.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/CacheControlHttpInterceptor.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/CardPublicKeyHttpInterceptor.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/MoshiConverter.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/NetworkError$Companion.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/NetworkError.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/NetworkModuleMessage.class create mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/RetrofitKt.class create mode 100644 network/build/kotlin/compileKotlin/build-history.bin create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.len create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.values.at create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i create mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len create mode 100644 network/build/kotlin/compileKotlin/last-build.bin create mode 100644 network/build/kotlin/networkjar-classes.txt create mode 100644 network/build/libs/network.jar create mode 100644 network/build/tmp/jar/MANIFEST.MF delete mode 100644 network/src/main/java/com/tangem/network/common/NetworError.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e57fabb13b..1052bdb849 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -108,9 +108,9 @@ repositories { dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar")))) implementation(project(":domain")) - implementation(project(":network")) implementation(project(":common")) implementation(project(":core:ui")) + implementation(project(":core:datasource")) implementation(project(":core:utils")) implementation(project(":libs:crypto")) implementation(project(":libs:auth")) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index bfc2bf8157..a1658496e7 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit bfc2bf8157089bce6b44779bdae66df2c920de70 +Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6 diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 41b4fb59bb..d17e83bd33 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -11,9 +11,9 @@ import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.common.json.MoshiJsonConverter +import com.tangem.datasource.api.common.MoshiConverter import com.tangem.domain.DomainLayer import com.tangem.domain.common.LogConfig -import com.tangem.network.common.MoshiConverter import com.tangem.tap.common.AndroidAssetReader import com.tangem.tap.common.AssetReader import com.tangem.tap.common.analytics.Analytics @@ -156,6 +156,7 @@ class TapApplication : Application(), ImageLoaderFactory { appStateHolder.userTokensRepository = userTokensRepository } + //todo refactor: move to datasource and provide via DI private fun initMoshiConverter() { fun appAdapters(): List = listOf( BigDecimalAdapter(), diff --git a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt index 51252c819c..990b35c356 100644 --- a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt @@ -2,7 +2,7 @@ package com.tangem.tap.domain import com.tangem.common.services.Result import com.tangem.domain.common.ThrottlerWithValues -import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.store import java.math.BigDecimal diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index 0bc9a2c283..aae3cf8e9a 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -2,7 +2,7 @@ package com.tangem.tap.domain import androidx.annotation.StringRes import com.tangem.common.core.TangemError -import com.tangem.network.api.tangemTech.TangemTechError +import com.tangem.datasource.api.tangemTech.TangemTechError import com.tangem.wallet.R interface TapErrors diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt b/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt index 6e94b5a88a..0e18c4018d 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/Currency.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonClass import com.tangem.blockchain.common.Blockchain import com.tangem.domain.common.extensions.fromNetworkId -import com.tangem.network.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.CoinsResponse @JsonClass(generateAdapter = true) data class CurrencyFromJson( diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt index 7df68e80c3..fbbaad9022 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/LoadAvailableCoinsService.kt @@ -6,9 +6,9 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.services.Result import com.tangem.domain.common.extensions.getListOfCoins import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.network.api.tangemTech.CoinsResponse -import com.tangem.network.api.tangemTech.TangemTechService -import com.tangem.network.common.MoshiConverter +import com.tangem.datasource.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.TangemTechService +import com.tangem.datasource.api.common.MoshiConverter import com.tangem.tap.common.AssetReader class LoadAvailableCoinsService( diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt index 7418891662..2aa1dce1a7 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/OldUserTokensRepository.kt @@ -7,8 +7,8 @@ import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.services.Result import com.tangem.domain.common.extensions.getTokens import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.network.api.tangemTech.TangemTechService -import com.tangem.network.common.MoshiConverter +import com.tangem.datasource.api.tangemTech.TangemTechService +import com.tangem.datasource.api.common.MoshiConverter import com.tangem.tap.common.FileReader import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.tokens.models.TokenDao diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt index c0b7e18155..821a8046ac 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensNetworkService.kt @@ -2,8 +2,8 @@ package com.tangem.tap.domain.tokens import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result -import com.tangem.network.api.tangemTech.TangemTechService -import com.tangem.network.api.tangemTech.UserTokensResponse +import com.tangem.datasource.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.UserTokensResponse import com.tangem.tap.domain.NoDataError class UserTokensNetworkService(private val tangemTechService: TangemTechService) { diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 4d6646ed32..6fb7de9ad7 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -3,10 +3,10 @@ package com.tangem.tap.domain.tokens import android.content.Context import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.services.Result +import com.tangem.datasource.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.UserTokensResponse import com.tangem.domain.common.CardDTO import com.tangem.domain.common.util.userWalletId -import com.tangem.network.api.tangemTech.TangemTechService -import com.tangem.network.api.tangemTech.UserTokensResponse import com.tangem.tap.common.AndroidFileReader import com.tangem.tap.domain.NoDataError import com.tangem.tap.domain.tokens.models.BlockchainNetwork diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt index 5f36f9bd49..9e5dac0b68 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -2,9 +2,9 @@ package com.tangem.tap.domain.tokens import com.squareup.moshi.JsonAdapter import com.tangem.Log +import com.tangem.datasource.api.tangemTech.UserTokensResponse +import com.tangem.datasource.api.common.MoshiConverter import com.tangem.domain.common.CardDTO -import com.tangem.network.api.tangemTech.UserTokensResponse -import com.tangem.network.common.MoshiConverter import com.tangem.tap.common.FileReader import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.toCurrencies diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 9aed6f968c..ed727b68e6 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -10,7 +10,7 @@ import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse -import com.tangem.network.common.MoshiConverter +import com.tangem.datasource.api.common.MoshiConverter import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.common.AssetReader import com.tangem.tap.tangemSdkManager @@ -85,7 +85,7 @@ class TwinCardsManager( } private fun getAdapter(): JsonAdapter> { - return MoshiConverter.defaultMoshi().adapter( + return com.tangem.datasource.api.common.MoshiConverter.defaultMoshi().adapter( Types.newParameterizedType(List::class.java, Issuer::class.java), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt index 543640ad2f..c796b81261 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt @@ -7,7 +7,7 @@ import com.squareup.moshi.JsonClass import com.squareup.moshi.Types import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.network.common.MoshiConverter +import com.tangem.datasource.api.common.MoshiConverter import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession import com.tangem.tap.features.details.redux.walletconnect.WalletForSession import com.trustwallet.walletconnect.models.WCPeerMeta @@ -18,7 +18,7 @@ import java.nio.charset.Charset class WalletConnectRepository(val context: Application) { private val moshi = MoshiConverter.defaultMoshi() private val walletConnectAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(List::class.java, SessionDao::class.java) + Types.newParameterizedType(List::class.java, SessionDao::class.java), ) fun saveSession(session: WalletConnectSession) { @@ -76,7 +76,6 @@ class WalletConnectRepository(val context: Application) { } } - @JsonClass(generateAdapter = true) data class SessionDao( val peerId: String, @@ -91,7 +90,7 @@ data class SessionDao( remotePeerId = remotePeerId, wallet = wallet, session = session, - peerMeta = peerMeta + peerMeta = peerMeta, ) } @@ -102,7 +101,7 @@ data class SessionDao( remotePeerId = session.remotePeerId, wallet = session.wallet, session = session.session, - peerMeta = session.peerMeta + peerMeta = session.peerMeta, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt index 04933d0a46..ade3f81cde 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt @@ -12,13 +12,13 @@ import com.tangem.common.extensions.isZero import com.tangem.common.services.Result import com.tangem.domain.common.extensions.successOr import com.tangem.domain.common.util.UserWalletId -import com.tangem.network.api.paymentology.AttestationResponse -import com.tangem.network.api.paymentology.PaymentologyApiService -import com.tangem.network.api.paymentology.RegisterKYCRequest -import com.tangem.network.api.paymentology.RegisterWalletRequest -import com.tangem.network.api.paymentology.RegisterWalletResponse -import com.tangem.network.api.paymentology.RegistrationResponse -import com.tangem.network.api.paymentology.tryExtractError +import com.tangem.datasource.api.paymentology.AttestationResponse +import com.tangem.datasource.api.paymentology.PaymentologyApiService +import com.tangem.datasource.api.paymentology.RegisterKYCRequest +import com.tangem.datasource.api.paymentology.RegisterWalletRequest +import com.tangem.datasource.api.paymentology.RegisterWalletResponse +import com.tangem.datasource.api.paymentology.RegistrationResponse +import com.tangem.datasource.api.paymentology.tryExtractError import com.tangem.operations.attestation.AttestWalletKeyResponse import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.domain.getFirstToken @@ -79,7 +79,7 @@ class SaltPayActivationManager( } } - suspend fun requestAttestationChallenge(): Result { + suspend fun requestAttestationChallenge(): Result { return paymentologyService.requestAttestationChallenge(cardId, cardPublicKey) .successOr { return it } .tryExtractError() @@ -94,8 +94,8 @@ class SaltPayActivationManager( suspend fun registerWallet( attestResponse: AttestWalletKeyResponse, pinCode: String, - ): Result { - val request = RegisterWalletRequest( + ): Result { + val request = com.tangem.datasource.api.paymentology.RegisterWalletRequest( cardId = cardId, publicKey = cardPublicKey, walletPublicKey = walletPublicKey, @@ -152,12 +152,13 @@ class SaltPayActivationManager( ) } - private fun makeRegisterKYCRequest(): RegisterKYCRequest = RegisterKYCRequest( - cardId = cardId, - publicKey = cardPublicKey, - kycProvider = "UTORG", - kycRefId = kycUrlProvider.kycRefId, - ) + private fun makeRegisterKYCRequest(): com.tangem.datasource.api.paymentology.RegisterKYCRequest = + com.tangem.datasource.api.paymentology.RegisterKYCRequest( + cardId = cardId, + publicKey = cardPublicKey, + kycProvider = "UTORG", + kycRefId = kycUrlProvider.kycRefId, + ) companion object { fun stub(): SaltPayActivationManager = SaltPayActivationManager( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index 69f3e0a674..54929f3918 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -8,8 +8,8 @@ import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.domain.common.extensions.successOr import com.tangem.domain.common.extensions.withMainContext -import com.tangem.network.api.paymentology.KYCStatus -import com.tangem.network.api.paymentology.RegistrationResponse +import com.tangem.datasource.api.paymentology.KYCStatus +import com.tangem.datasource.api.paymentology.RegistrationResponse import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -295,7 +295,7 @@ suspend fun SaltPayActivationManager.update( private fun determineStep( currentStep: SaltPayActivationStep, amountToClaim: Amount?, - response: RegistrationResponse.Item, + response: com.tangem.datasource.api.paymentology.RegistrationResponse.Item, ): SaltPayActivationStep { fun getStepBaseOnAmountToClaim(amount: Amount?): SaltPayActivationStep = when (amount) { null -> SaltPayActivationStep.Success @@ -315,25 +315,25 @@ private fun determineStep( response.kycStatus != null -> { when (currentStep) { SaltPayActivationStep.KycWaiting -> when (response.kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) + com.tangem.datasource.api.paymentology.KYCStatus.NOT_STARTED, com.tangem.datasource.api.paymentology.KYCStatus.STARTED -> SaltPayActivationStep.KycIntro + com.tangem.datasource.api.paymentology.KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting + com.tangem.datasource.api.paymentology.KYCStatus.CORRECTION_REQUESTED, com.tangem.datasource.api.paymentology.KYCStatus.REJECTED -> SaltPayActivationStep.KycReject + com.tangem.datasource.api.paymentology.KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) else -> SaltPayActivationStep.KycIntro } SaltPayActivationStep.KycReject -> when (response.kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycStart - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED -> SaltPayActivationStep.KycStart - KYCStatus.REJECTED -> SaltPayActivationStep.KycStart - KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) + com.tangem.datasource.api.paymentology.KYCStatus.NOT_STARTED, com.tangem.datasource.api.paymentology.KYCStatus.STARTED -> SaltPayActivationStep.KycStart + com.tangem.datasource.api.paymentology.KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting + com.tangem.datasource.api.paymentology.KYCStatus.CORRECTION_REQUESTED -> SaltPayActivationStep.KycStart + com.tangem.datasource.api.paymentology.KYCStatus.REJECTED -> SaltPayActivationStep.KycStart + com.tangem.datasource.api.paymentology.KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) else -> SaltPayActivationStep.KycIntro } else -> when (response.kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) + com.tangem.datasource.api.paymentology.KYCStatus.NOT_STARTED, com.tangem.datasource.api.paymentology.KYCStatus.STARTED -> SaltPayActivationStep.KycIntro + com.tangem.datasource.api.paymentology.KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting + com.tangem.datasource.api.paymentology.KYCStatus.CORRECTION_REQUESTED, com.tangem.datasource.api.paymentology.KYCStatus.REJECTED -> SaltPayActivationStep.KycReject + com.tangem.datasource.api.paymentology.KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) else -> SaltPayActivationStep.KycIntro } } @@ -413,7 +413,7 @@ private suspend fun checkGasIfNeeded( } @Throws(SaltPayActivationError::class) -fun RegistrationResponse.Item.toSaltPayStep(currentStep: SaltPayActivationStep): SaltPayActivationStep { +fun com.tangem.datasource.api.paymentology.RegistrationResponse.Item.toSaltPayStep(currentStep: SaltPayActivationStep): SaltPayActivationStep { return when { passed != true -> throw SaltPayActivationError.CardNotPassed(this.error) disabledByAdmin == true -> throw SaltPayActivationError.CardDisabled(this.error) @@ -426,10 +426,10 @@ fun RegistrationResponse.Item.toSaltPayStep(currentStep: SaltPayActivationStep): kycStatus != null -> { when (kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - KYCStatus.APPROVED -> SaltPayActivationStep.Claim + com.tangem.datasource.api.paymentology.KYCStatus.NOT_STARTED, com.tangem.datasource.api.paymentology.KYCStatus.STARTED -> SaltPayActivationStep.KycIntro + com.tangem.datasource.api.paymentology.KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting + com.tangem.datasource.api.paymentology.KYCStatus.CORRECTION_REQUESTED, com.tangem.datasource.api.paymentology.KYCStatus.REJECTED -> SaltPayActivationStep.KycReject + com.tangem.datasource.api.paymentology.KYCStatus.APPROVED -> SaltPayActivationStep.Claim null -> throw UnsupportedOperationException() } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt index 6acbe4bdd2..e1317622a4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt @@ -6,7 +6,7 @@ import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.extensions.guard import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.ScanResponse -import com.tangem.network.api.paymentology.PaymentologyApiService +import com.tangem.datasource.api.paymentology.PaymentologyApiService import com.tangem.tap.common.toggleWidget.WidgetState import com.tangem.tap.domain.extensions.makeSaltPayWalletManager import com.tangem.tap.features.onboarding.products.wallet.saltPay.GnosisRegistrator diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt index 4c5dd07560..146d622d16 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/Currency.kt @@ -5,7 +5,7 @@ import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.features.addCustomToken.CustomCurrency -import com.tangem.network.api.tangemTech.TokenResponse +import com.tangem.datasource.api.tangemTech.TokenResponse import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.tokens.redux.TokenWithBlockchain diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt index d0720a000c..c60632f60e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt @@ -1,8 +1,8 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.tangem.common.services.Result -import com.tangem.network.api.tangemTech.CurrenciesResponse -import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.CurrenciesResponse +import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt index 35cc636808..9dd2b9746f 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -6,7 +6,7 @@ import com.tangem.common.extensions.calculateSha512 import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.common.services.performRequest -import com.tangem.network.common.createRetrofitInstance +import com.tangem.datasource.api.common.createRetrofitInstance import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 5e52716d98..645d5182ed 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -6,7 +6,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.services.Result import com.tangem.common.services.performRequest import com.tangem.domain.common.extensions.withIOContext -import com.tangem.network.common.createRetrofitInstance +import com.tangem.datasource.api.common.createRetrofitInstance import com.tangem.tap.common.extensions.urlEncode import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.features.wallet.models.Currency diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt index f4612643d3..d9cc63b72a 100644 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdService.kt @@ -3,7 +3,7 @@ package com.tangem.tap.network.payid import com.squareup.moshi.JsonClass import com.tangem.common.services.Result import com.tangem.common.services.performRequest -import com.tangem.network.common.createRetrofitInstance +import com.tangem.datasource.api.common.createRetrofitInstance import retrofit2.Retrofit class PayIdService { diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt index f5e7ed1f96..05d16f8724 100644 --- a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt +++ b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt @@ -2,7 +2,7 @@ package com.tangem.tap.network.payid import com.tangem.common.services.Result import com.tangem.common.services.performRequest -import com.tangem.network.common.createRetrofitInstance +import com.tangem.datasource.api.common.createRetrofitInstance /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt index 50a8859516..59c500f9a7 100644 --- a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt @@ -3,7 +3,7 @@ package com.tangem.tap.persistence import android.content.SharedPreferences import androidx.core.content.edit import com.tangem.common.json.MoshiJsonConverter -import com.tangem.network.api.tangemTech.CurrenciesResponse +import com.tangem.datasource.api.tangemTech.CurrenciesResponse import com.tangem.tap.common.entities.FiatCurrency /** diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 413f292589..4f4d41a9e4 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -4,7 +4,7 @@ import android.app.Application import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit -import com.tangem.network.common.MoshiConverter +import com.tangem.datasource.api.common.MoshiConverter import java.util.* class PreferencesStorage(applicationContext: Application) { diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index df394557fd..082885c1d7 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -42,6 +42,9 @@ dependencies { /** Project */ implementation(project(":libs:auth")) + /** Tangem libraries */ + implementation(Tangem.cardCore) + /** DI */ implementation(Library.hilt) kapt(Library.hiltKapt) diff --git a/network/src/main/java/com/tangem/network/common/Interceptors.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt similarity index 94% rename from network/src/main/java/com/tangem/network/common/Interceptors.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt index bc46e5a0a0..b8d3a7cc2a 100644 --- a/network/src/main/java/com/tangem/network/common/Interceptors.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt @@ -1,4 +1,4 @@ -package com.tangem.network.common +package com.tangem.datasource.api.common import okhttp3.Interceptor import okhttp3.Response diff --git a/network/src/main/java/com/tangem/network/common/MoshiConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt similarity index 79% rename from network/src/main/java/com/tangem/network/common/MoshiConverter.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt index 47b5cc4b64..054206db9d 100644 --- a/network/src/main/java/com/tangem/network/common/MoshiConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/MoshiConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.network.common +package com.tangem.datasource.api.common import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi @@ -9,8 +9,10 @@ import retrofit2.converter.moshi.MoshiConverterFactory /** [REDACTED_AUTHOR] */ +//todo needs to be refactored object MoshiConverter { + //todo refactor: provide via DI var INSTANCE = MoshiJsonConverter() private set @@ -23,7 +25,9 @@ object MoshiConverter { fun createFactory(moshi: Moshi = INSTANCE.moshi): Converter.Factory = MoshiConverterFactory.create(moshi) + //todo provide via DI using quealifiers fun defaultMoshi(): Moshi = INSTANCE.moshi + //todo provide via DI using quealifiers fun sdkMoshi(): Moshi = MoshiJsonConverter.INSTANCE.moshi } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/NetworError.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/NetworError.kt new file mode 100644 index 0000000000..8f4a7ae335 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/NetworError.kt @@ -0,0 +1,19 @@ +package com.tangem.datasource.api.common + +/** +[REDACTED_AUTHOR] + */ +// sealed interface NetworkModuleMessage : ModuleMessage +// +// sealed class NetworkError( +// subCode: Int, +// override val message: String, +// override val data: Any?, +// ) : NetworkModuleMessage, ModuleError() { +// override val code: Int = ModuleErrorCode.NETWORK + subCode +// +// companion object { +// // base code used for all error in the module +// // const val CODE_ANY_OTHER = 100..199, 200..299 +// } +// } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/common/Retrofit.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt similarity index 96% rename from network/src/main/java/com/tangem/network/common/Retrofit.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt index 70bb657bce..01e19dfada 100644 --- a/network/src/main/java/com/tangem/network/common/Retrofit.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/Retrofit.kt @@ -1,4 +1,4 @@ -package com.tangem.network.common +package com.tangem.datasource.api.common import okhttp3.Interceptor import okhttp3.OkHttpClient diff --git a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApi.kt similarity index 95% rename from network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApi.kt index 058cd368dd..de7c678d3b 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApi.kt @@ -1,4 +1,4 @@ -package com.tangem.network.api.paymentology +package com.tangem.datasource.api.paymentology import retrofit2.http.Body import retrofit2.http.Headers diff --git a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt similarity index 91% rename from network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt index 8cd70891c3..f46a17b36d 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/PaymentologyApiService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/PaymentologyApiService.kt @@ -1,10 +1,10 @@ -package com.tangem.network.api.paymentology +package com.tangem.datasource.api.paymentology import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.common.services.performRequest -import com.tangem.network.common.MoshiConverter -import com.tangem.network.common.createRetrofitInstance +import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.api.common.createRetrofitInstance import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/network/src/main/java/com/tangem/network/api/paymentology/Requests.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Requests.kt similarity index 98% rename from network/src/main/java/com/tangem/network/api/paymentology/Requests.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Requests.kt index 580e60bfa5..cb0206934e 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/Requests.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Requests.kt @@ -1,4 +1,4 @@ -package com.tangem.network.api.paymentology +package com.tangem.datasource.api.paymentology import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Responses.kt similarity index 98% rename from network/src/main/java/com/tangem/network/api/paymentology/Responses.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Responses.kt index b60324b42d..d62630b73e 100644 --- a/network/src/main/java/com/tangem/network/api/paymentology/Responses.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/paymentology/Responses.kt @@ -1,4 +1,4 @@ -package com.tangem.network.api.paymentology +package com.tangem.datasource.api.paymentology import com.squareup.moshi.Json import com.tangem.common.extensions.calculateHashCode diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt similarity index 97% rename from network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt index 4f88fca3b7..37c99cc0ab 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt @@ -1,4 +1,4 @@ -package com.tangem.network.api.tangemTech +package com.tangem.datasource.api.tangemTech import java.math.BigDecimal diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt similarity index 96% rename from network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index 518edd9fec..a0eafa51d8 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -1,4 +1,4 @@ -package com.tangem.network.api.tangemTech +package com.tangem.datasource.api.tangemTech import retrofit2.http.Body import retrofit2.http.GET diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt similarity index 91% rename from network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt index 6137e8dfa0..50540a226e 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechService.kt @@ -1,10 +1,10 @@ -package com.tangem.network.api.tangemTech +package com.tangem.datasource.api.tangemTech import com.tangem.common.services.Result import com.tangem.common.services.performRequest -import com.tangem.network.common.AddHeaderInterceptor -import com.tangem.network.common.CacheControlHttpInterceptor -import com.tangem.network.common.createRetrofitInstance +import com.tangem.datasource.api.common.AddHeaderInterceptor +import com.tangem.datasource.api.common.CacheControlHttpInterceptor +import com.tangem.datasource.api.common.createRetrofitInstance import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 6909db2791..e365d69c25 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -71,7 +71,7 @@ android { } dependencies { - implementation(project(":network")) + implementation(project(":core:datasource")) implementation(project(":common")) /** Tangem libraries */ diff --git a/domain/src/main/java/com/tangem/domain/DomainDialog.kt b/domain/src/main/java/com/tangem/domain/DomainDialog.kt index bc7d30a7da..22e0ae8e28 100644 --- a/domain/src/main/java/com/tangem/domain/DomainDialog.kt +++ b/domain/src/main/java/com/tangem/domain/DomainDialog.kt @@ -1,7 +1,7 @@ package com.tangem.domain import com.tangem.common.extensions.VoidCallback -import com.tangem.network.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.CoinsResponse /** [REDACTED_AUTHOR] diff --git a/domain/src/main/java/com/tangem/domain/common/extensions/TangemTechService.kt b/domain/src/main/java/com/tangem/domain/common/extensions/TangemTechService.kt index 3304349b69..925eaa5f82 100644 --- a/domain/src/main/java/com/tangem/domain/common/extensions/TangemTechService.kt +++ b/domain/src/main/java/com/tangem/domain/common/extensions/TangemTechService.kt @@ -1,8 +1,8 @@ package com.tangem.domain.common.extensions import com.tangem.common.services.Result -import com.tangem.network.api.tangemTech.CoinsResponse -import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.TangemTechService suspend fun TangemTechService.getTokens( contractAddress: String, diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt index a502ae5041..98a63b6565 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/AddCustomTokenService.kt @@ -2,8 +2,8 @@ package com.tangem.domain.features.addCustomToken import com.tangem.common.services.Result import com.tangem.domain.common.extensions.getTokens -import com.tangem.network.api.tangemTech.CoinsResponse -import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.TangemTechService /** [REDACTED_AUTHOR] diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt index 6949c1de51..3a5946f963 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenAction.kt @@ -8,7 +8,7 @@ import com.tangem.domain.common.form.Field import com.tangem.domain.common.form.FieldId import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.features.addCustomToken.CustomTokenFieldId -import com.tangem.network.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.CoinsResponse import org.rekotlin.Action /** diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index 2b56a47f90..f399fc4a34 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -56,7 +56,7 @@ import com.tangem.domain.redux.domainStore import com.tangem.domain.redux.extensions.dispatchOnMain import com.tangem.domain.redux.global.DomainGlobalAction import com.tangem.domain.redux.global.DomainGlobalState -import com.tangem.network.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.CoinsResponse import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -342,7 +342,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT private suspend fun requestInfoAboutToken( contractAddress: String, - ): List { + ): List { val tangemTechServiceManager = requireNotNull(hubState.tangemTechServiceManager) dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true)))) @@ -432,8 +432,8 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT ) private suspend fun fillTokenFields( - token: CoinsResponse.Coin, - coinNetwork: CoinsResponse.Coin.Network, + token: com.tangem.datasource.api.tangemTech.CoinsResponse.Coin, + coinNetwork: com.tangem.datasource.api.tangemTech.CoinsResponse.Coin.Network, ) { val blockchain = Blockchain.fromNetworkId(coinNetwork.networkId) ?: Blockchain.Unknown Network.setFieldValue(Field.Data(blockchain, false)) diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt index 6dde5b01c6..1dfa5ef063 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt @@ -37,7 +37,7 @@ import com.tangem.domain.features.addCustomToken.TokenDerivationPathField import com.tangem.domain.features.addCustomToken.TokenField import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.state.StringActionStateConverter -import com.tangem.network.api.tangemTech.CoinsResponse +import com.tangem.datasource.api.tangemTech.CoinsResponse import org.rekotlin.Action import org.rekotlin.StateType @@ -48,7 +48,7 @@ data class AddCustomTokenState( val form: Form = Form(listOf()), val formValidators: Map> = createFormValidators(), val formErrors: Map = emptyMap(), - val foundToken: CoinsResponse.Coin? = null, + val foundToken: com.tangem.datasource.api.tangemTech.CoinsResponse.Coin? = null, val warnings: Set = emptySet(), val screenState: ScreenState = createInitialScreenState(), val tangemTechServiceManager: AddCustomTokenService? = null, diff --git a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt index d80b0bab73..a0d0cb833b 100644 --- a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt +++ b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalHub.kt @@ -5,7 +5,7 @@ import com.tangem.common.extensions.toHexString import com.tangem.domain.redux.BaseStoreHub import com.tangem.domain.redux.DomainState import com.tangem.domain.redux.ReStoreReducer -import com.tangem.network.common.CardPublicKeyHttpInterceptor +import com.tangem.datasource.api.common.CardPublicKeyHttpInterceptor import org.rekotlin.Action /** diff --git a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt index b420816c55..a013b2128f 100644 --- a/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt +++ b/domain/src/main/java/com/tangem/domain/redux/global/DomainGlobalState.kt @@ -3,8 +3,8 @@ package com.tangem.domain.redux.global import com.tangem.domain.DomainDialog import com.tangem.domain.common.LogConfig import com.tangem.domain.common.ScanResponse -import com.tangem.network.api.paymentology.PaymentologyApiService -import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.datasource.api.paymentology.PaymentologyApiService +import com.tangem.datasource.api.tangemTech.TangemTechService /** [REDACTED_AUTHOR] @@ -19,6 +19,8 @@ data class DomainGlobalState( ) data class NetworkServices( - val tangemTechService: TangemTechService = TangemTechService(LogConfig.network.tangemTechService), - val paymentologyService: PaymentologyApiService = PaymentologyApiService(LogConfig.network.paymentologyApiService), + val tangemTechService: TangemTechService = TangemTechService( + LogConfig.network.tangemTechService), + val paymentologyService: PaymentologyApiService = PaymentologyApiService( + LogConfig.network.paymentologyApiService), ) diff --git a/network/.gitignore b/network/.gitignore deleted file mode 100644 index 42afabfd2a..0000000000 --- a/network/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/network/build.gradle.kts b/network/build.gradle.kts deleted file mode 100644 index 0c82757e05..0000000000 --- a/network/build.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -plugins { - id("java-library") - id("org.jetbrains.kotlin.jvm") -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -dependencies { - implementation(project(":common")) - - /** Tangem libraries */ - implementation(Tangem.cardCore) - - /** Other libraries */ - implementation(Library.retrofit) - implementation(Library.retrofitMoshiConverter) - implementation(Library.moshi) - implementation(Library.moshiKotlin) - implementation(Library.okHttp) - implementation(Library.okHttpLogging) - implementation(Library.timber) - implementation(Library.coroutine) -} \ No newline at end of file diff --git a/network/build/.transforms/0a0a9b6fcdf800647341aabb7296a58d/results.bin b/network/build/.transforms/0a0a9b6fcdf800647341aabb7296a58d/results.bin new file mode 100644 index 0000000000..1ed65e03c3 --- /dev/null +++ b/network/build/.transforms/0a0a9b6fcdf800647341aabb7296a58d/results.bin @@ -0,0 +1 @@ +i/ diff --git a/network/build/.transforms/10b56b612fced146421785dc9c798db5/results.bin b/network/build/.transforms/10b56b612fced146421785dc9c798db5/results.bin new file mode 100644 index 0000000000..82b3009a77 --- /dev/null +++ b/network/build/.transforms/10b56b612fced146421785dc9c798db5/results.bin @@ -0,0 +1 @@ +o/jetified-network.jar diff --git a/network/build/.transforms/10b56b612fced146421785dc9c798db5/transformed/jetified-network.jar b/network/build/.transforms/10b56b612fced146421785dc9c798db5/transformed/jetified-network.jar new file mode 100644 index 0000000000000000000000000000000000000000..af1c4b140b481f82156439504e50812fd74b6455 GIT binary patch literal 99352 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 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b zL|=Y~wkRppa?ioT>L*9w^lyZ5l z^K{mUchMNHy3aCxd4y)&EYrnavWGBere)kgg1Jr!0l0ftG{mw0JN4=m1b+ZfMk#LSf^6NEbg$w)g*U)Xi4%{}( zL@!c~RUjTVsuY+DxW(+glY^Ie^zJW5B~ zghCFSSWv7>Kdfb0-sb9;b7znMJaix2WZQQLYZBHRb-61aZM}davvxQ9|AU`Of&9dU zD^QPuMY!yd3x4i_&WdrS(I-f^;(;Hr#t?x0Km3#cgFt?Q zdtOZg@>9A=?l;9rAV2LCaCk!PnzS>Daf$UKp!F$4AYG`y4rL=v&f;Jxs>=#U<$U1* z|2lwT+ne8qM!oEGtx2vyuBBcR`}QAo;R)-uJgLBGvk06v{~bRG{T0yvPg^JPU*Cvj zO`P5A9IgI7XbMy}l#tbcVK5Uzv=WIjU%+#tRF%cM$+K<1ZAhLGVdFpxRNi*t%Z(*! zBiew@qvK`t&!4>PbG(l^7XiT1aFL=(=VD&x!uJ%;hNDYcK`4cD`m}SNqw|)oH!l3A z%lEUbEni6ba4*1~M_zm*s*glPzRN}XZLUY52aBGx89=7VRK;pwnx-Mf%7v0)HNv~ zcV*zop^+)|sK`wmfu9)ij1?0|H_M0Sg;A^sX)f);XOZM+BCdg6PYk%=W0mM&A~}_lb=3UE{l{aWlOSL znVhz4)RCh$-{?*o@BWQ7n?B00(x za-X?!ma`Kr)_tx;?vy*Zg+h)^jtD7ol^o?tLXk6+kaCx^|Ez4jyXd>$ey{($(x<$3 z<~`3d&pb2p*_mfLOSwPBlndORQA%JN8qV-@erG14V@jfRl@qR4d`jp@^^FFSkAn91 zdtfES(W`xyJgkZGMpezs)%`0H1U)LyVw-^d#CxkK!=@T^sMxw4&X{+0t6QXH$L`AJ zG8XcSew8szxj62z66)vrSwUN0UpI--JX5ju@#CmKHOaneONwR>>q*(Rn{1y2HG<|Q zjRJp2D=%MRbefbMQerP54e(k?c-N9V{IErt|LUQOr6(DV9ML~!LZdqpQEw;S(!0Pa z9eVJ3Ba1rpmdmLefshjF=j1;7kpqc0js0kK?e$^>Y}51f9SzevXmzAoydF!@jYqSd zea5kTb~%RSQm3LfvhK0CfP%GhU6IvMqFViz?7I41v!f=v$wJ@LT9seHaKV&oLz|Fa<;<=*{ zHydf;(|7&Nl41idt48{F5}hmWVgEL07-uLsX+Y6fW*~FQ+Er>l&*8>lj5G^t5WZjzL_$y*(w<5unQAzG{!~>2xOyI?En<5prln=9q%+ zXNOPh;>^}c<7b0!iAk$@sOMy!^U`}LR7#!LUBWKv^kdcX&Ktz>Gr8Q^pCq3OPgV*| ztGdY@JD%rJS7~;CvG>xR&v{}O>f_>L`AF@#XJrok}L5Mi53DmKRFQKiRu^4G*03GmA14&y=-4LgyB8 zcAB&_D(l5(O$uoxNwHb_V585uc-NCXC=bRQI5B#6p9WKsU!T0p?Y_eXG)&LrDj8JZ z4DjF?YNp_aQ>m)(z?SrhD0s2}$GmWTVkh|y;sqqH&p?j-sI*w`$G!Vm;?yA&QgV0H zY+{qWNo=F!3*IHP$%{Ik_~xswR>u^zL{$rAntHA|Gjz31cKQtB=f!aT&&NJdDPG*k zmsY2)>{xX8bU6d(Dq~6nL3_9_ooujKidQ%O;qg$4bL4wdTTzi}{Lu^!TQKnY!#mla!1NN`?AUUi` zeXiLM?$BD?nN*WmQK1<~mn~ASc(K}VkO114Pdsr3`*@o;@pS0j)pLd7^c2OnTQ6&q=zl z^P zSN4dfhf5>Cw|)El!;%~$Di^@94lv3QXym`FPImg*g$iHL_jGjugujgb;rBtcDmy#O z%x+Atqdkc)*Zo|FRltMut-B7-CEM|gx`1d^281T39~aqbfH`Oa&Ye5@u|ggc?khpTw~5u=>k#ZdIct&T+*_|Y@w|utsdR7 zqqCIKyx-^ZOq-tJ;-{Bq=&7^1;`U|t@b9Z4A$}qx7a1p1Hher@>B#rz8E&hrd5RYH z{V|UF?X}_;_d;@wi)~)bFurlw{nW*nyO{-_Ko(Q6 z!<7 zxR}a~-X!8uFK?5ec6_2>B`5C5Ik*3dI>|ZVHrm}spI?c9Fs)M3#SC}`vQD@4JPh<* za0*cnU{H-3V2HdGS$fUX?S!`Y&Z}PB`w~3nIc38PkQ3lLI;Oe%%msJP?lDZGB)+PB zl9YLsUjOoNd%g8f6Yp*#`D}996SEVR9mzlQAKbL;AdX|qFwvb$d}7;4>}nd;?`x=zH8Co z8uOx8Xvbp*ga;m}7~+fTGo zsopP~bj2qb4LK6h!%O||?b}mjUwN&Dpy{5V2mSn{3@anvIGZCf;T2?1a=J!`?cIE2 z$i43lu^!Yqb@Tj{fg&3pyx{TXp@QG z)v^Q`7SHh0mqeWKpuPkK8Q)0zoMWpDimyo?VgTN5pA>6S_>p)Ueg0s}CZ;=~xAVqd z@UGxl*T@{6;GX+h;Y38cSQ@`13>kYaZY;cL%l$sWwx^h-i$4FD z#UbcW+KqjA@b`D4dG{Ag4H#j*FY3;AGesTFo&Ym=+#)z)%;4AG4uQZdzb5VPqPX)( z50a#5%d(_gKlv!+kzPABnX2!bMXhma{j(>9(eTL zPm8fkn70fdFi2Nyi+1TN`b2D?QY9?-KC^zJO82`^^;|CPsT*JUizuof$u9d5p`WIm z-d}~b3_HJRMKm^amD)z7xfDdye->mgiAePle3Zx|q;fQd&fAoe++(kkOI=Zs|Hu1g z=O0HF&xYPCR3Pk8pCPfLXM8iGj-J0}F#j7l@ z5C0$;OD9S5O>JlzbEn0DLZYE=F)g>Y=%WAlra3&>1vAjo~rHjTq!1pfAms)9- z-WJND5sg<4ruVY$F>yHsBpor%`8LL@{NiBr&4bB^s`cLCiBC9ph`H*5Rw5-wrscIH-k*{c zJlCxDyp6j_ZhiB^X@Az(;v(~;*X1|bo>xg6mRR{A-0i0HMN-Xi_Nyy9L{4nP;T6l_ zulUu;dF`K+0{kOmPBXo~IOUZpnmr&YPUIXZ**xy)%_(AS^Fyda;?NJ-D$yt{^-J31 zsVV`NwStxI9OHPSCMa@}Qs>0N5U2EWn5tRdfF`s5jSECl@`079+ID%A_)_+Xd&Mtk zs`x}-9si-LUcXPM89rYgpC1fY4EXTVF0DqnVoJuWbMe02I9qgKrUqKvmx8q#TnMP}ts13o){T7nH8>Z23ZzV~)d?`yuCGNzTf4`;&tTG`JG zQ5|v@Y26`rN+_>b0S`u&FUG0d(W%gTt2*thRu0FYH-B=zQm)s6PKlyv^ryK3+xGnB z!V?|O=jZh^W#)>{GO+d~+xEscKon1>h?TJq2JRj8EK;Z8?rvh_vhEsYH_&)R^NnS; z`Hs*CcQ5ZSWal-}j7RAiu#4QL=Ap|mB@IDuTkg&^6(x%heIu1nomec6givI2*h*E1 zX7IUn$Hc%G7E(iBkskD7;&Af!d|z@Y#I;N+x(z1l=$GLbLCvIuN9st$vKS^xTcl*E z5wx^(wCC{o=l6f|ekxDDK$1%~H7mxt>kd_%D>r^~Mex{2rkZqho@tAY5QoQ|wBuci zG`3GgA4}gG;q`lYK2(xRh$dSrobe0$>Mr*<)kN1(eYvM2Z)}g!Yn7ehe|%N#ZUQqW*M%lGnXl%a0iMC+ z7SC7j1QB{Gv^yVn&Q|Od`Bu6(S;yEm&vYip@j_!QUoCIR0oTY(3J-@^czvQ7!b|XC zHHV?icMDqP7f$ub%#>u(qYDIYO#JXqx$CpsAZ@9b^`PjbBKuZToHx`RWWS`zU&Ts7 zlp3@wGIl7!ik5ko+fTgf z5o*V)L8XcI>{2j3=1*RDrb?81IL~;uvcwB&80&tr05 z)n!P)>J3~pdIY0v7Vuk?KG(H_7IM9g+Asv%-pmrAhWIt@!7f*qz(w*FP#8f(2$1Loue zHHU58^e@_a13rSrU;_rkjTQhrk-pjoaUlF7=qz?KpbA5G<-Mr?tR+;3@@3nyWT=DX zrrw0ggDwQ*T2H7#O13Y=0qSN8vqRY1*qgg;DzVsle6|Gm*)Zl`$A{5nB7K)}EKwU~ zZs~*oM>fIvVf3pJHWdWSc54kIdE##H0vRfSmQgW%(Fy5G%fGE2FhoE@{-A8iR@d9} z0Kg5rju@H%o=D$#0o)*`|DiOnLu6}N5NZqG@PKnuTfc%4O+xy91ig6+dqlMO}8QfUnsO46b%g9(~y}HoI1Y4B8L8$``P(y@bI|RQey;YnqlrfQt+e zYt}1o0kyG#f$?xS#%R+nw9Z)>2@Lrc0=0EB|3IE|JZJb|w`hZ_(VGeF0N!&W`VdH4J$K=mhp5n_(Y z5|?nJ|LSLJ)}{?xYy7R{`~m40N9`tiNQj50iXo?_1#YBmwEODEu=#uK<72E&)dL0^)UeRmvIANNrZM&U?PO+rMGtwSKfM@ z>I5s4Ltx4SeB*`?81mgb}8XLB-+c}_9n#P`j zzhlgyOqa|E3rNfVCp#zRy1HN|DBM&UsG{I{9`yn)3#P; zR?BLE18BT`G-YmeT{G` zZ{wuPpIa%^JH?I;v{EeS;h0WZwE~xVZ*b7QY(2Khc1C`Dcrbkm06D?h5L2+K8eD)F z-zu!_3);FtuACrou>~Ee7p$mN(Aon}q%T=BE}YGRVcl}OHP$WkbLkZ@;HiTl7PAlc zwf#5jwu=H{+w|Q~VAgkl1~Cx@oj4)vtzo|xQd>)5-c60q1RALYxCulTQnlpUh}QFqD{JF!?OJC zz}WYHZh&h^{tk>4;&MAM_U&>T-~kF;z}lPdn!}#@VPC|wfuN_s1+n>BCTs%sT|^rM zS6W;M8_o8I_ZHZ9Y-~UX_u&HB?#2ylVX#AaZ*YhX;lkMv20g5-ceL$H06U=S25^$= z?|@jgx3xpT4vx2hz0Lo3*ne5VVMjmP03H?mJK*|wy|sMUp=ma7D`J0#!#FdtU29^; zO4z^-$>4(BJpODg9`*_14bE{zTsT{p=|3Fw)=cc9WE;#Bwg1KhGXxyotzjSZ+2Fx7 z{~Hh86)~D{Yay}sdv8Doba8=hp5Jt<*!%1@c%2t;;ceP=hYi5q{;~l`yMzk> bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` literal 0 HcmV?d00001 diff --git a/network/build/.transforms/129172ef9b6bb005a6da315416650247/results.bin b/network/build/.transforms/129172ef9b6bb005a6da315416650247/results.bin new file mode 100644 index 0000000000..1ed65e03c3 --- /dev/null +++ b/network/build/.transforms/129172ef9b6bb005a6da315416650247/results.bin @@ -0,0 +1 @@ +i/ diff --git a/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/results.bin b/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/results.bin new file mode 100644 index 0000000000..6167517f86 --- /dev/null +++ b/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/results.bin @@ -0,0 +1 @@ +o/jetified-network diff --git a/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/transformed/jetified-network/classes.dex b/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/transformed/jetified-network/classes.dex new file mode 100644 index 0000000000000000000000000000000000000000..d165fde1a59d4b77589cc392189c636bbe6eec16 GIT binary patch literal 93256 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�?bHmINuxbaus@V6nVnM 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 literal 0 HcmV?d00001 diff --git a/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/results.bin b/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/results.bin new file mode 100644 index 0000000000..6167517f86 --- /dev/null +++ b/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/results.bin @@ -0,0 +1 @@ +o/jetified-network diff --git a/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/transformed/jetified-network/classes.dex b/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/transformed/jetified-network/classes.dex new file mode 100644 index 0000000000000000000000000000000000000000..d165fde1a59d4b77589cc392189c636bbe6eec16 GIT binary patch literal 93256 zcmd?Sd3;sH`Nu!!?pX+zn*bp!SqKo;gn$tCkPw!D3gLo4KnV~b3Q0%;g4!1MwbrHX z`);lFYt^>4+7@wbwc1+sYpv8;wX1DyYj>-^_cJr++;g)P{e6Fb{VuN$GxN;;%=0|6 zoH-}kn)hW^Ogl<#eBrKl|MJn6-1dQIp7Z;z)1GkXh5k0Bb1s4{gFvZy+6}Y>!vHU^cJP+K@->q zt^$vP%v+UO1a^U|z+>QVVEk=LZ3Op#=$xU zoiyaduPF821JwDTQnMaXD(7LPF8`WR-}|~!U;l;sp8d%$Dh zMes8C33wB{4SoZh?isj3XHHf>O_cc`qvU74R=G z>xW8R0UiapKY|C~8t@A+^d+Sxfm^^&!5_hxA5$l|3_J#kUsmcEunMdJji3pf1a^UT za2mJ-To3LCUk3-l%ivw`XApixsli}8m1?eUIIS{ z?}5Jp=T)V$!B9{PCV**R30MnG0=qy5_#C(ZTn4TKw}HFBz2JYq*TAFT5O@~62wnz1 z10R9V|00iI5m*9NfGuD*xC-0=?g9tFbKrL%>oxidMuVB44y*%>-~@0YXa}c*1K=`n zHMkSp1HKIY2RsBG0pA5rf@i@G!Rz2n@Gf`{{2BZk1b>1KfLu@r#)C?*04xJ*z-F)u zoCYoc*MZx>ec&75yWo4^hu|mRFnAl}zYdSV8gL4@8axDk0>VF4YA$F2mw`vYufgCq z&@r$bTnyd@qYf*z4crM{1w(&^jDf4bZ^7I*mAVkT3da3hslDK3Q1KRa1l$AO1*3mK zIp7xX3o!I;^a5N64uQXd8Sjt>+zfsMLhmw;;1qBlcn6I6CF23^1iu62zd}dBcR}pe z*bZ&UD)=?X`n^&`UHP4(5T?;3RM+xDwn39t4kq7s1Qm*C6;; z>>tPl<3SzR2F?L@fUkn@g6F|2;Qha0JN|)=fQ{e;a3a_Tz5wn7kAPReYv2v=7WftT z1Nb}mH&FjnDgv^=AdnA6fw5pRm4Nw2Dlhp1HKHN055=_g5QIGgK*eUIUpZQ0&~D3um)@ad%!v1T5vn~ zDtHt;3tj}Tf_K1s;2$6oanukn8cYTAz!I<$91l(gd%kCMJGdWw3p@>81V078 z1b+hm2BD~<27nP@ET{yF!5Yv4+QEKs7PuPR0v-py2k(Oqz&}AK=BR;SBp3sxgDOx1 z)`3RQ1WpBKflI*k;7;%W_!f8?ybOL0-Uhz~e**siD#KCPpa6^nc@FI8NkU_ICZ_Jec5CEz-6E4UXt2p$E`gV(@sz~4Y9hkk=La0oN_68I@V zRTXNc5J82wD27_u;ZU$j(M3hG6h$bqs(~O55+E1kfx%!17z*;iFi-%7gArgP7zGMJ z5hw;FU^FNNW58H24wQlMU;>y3CV|Od3MdB^U@E8t)4+6a6qo_n_NXc_3(N*{z+5m7 z%m)iVHCPC0KrL7V>cC>K1S|#1Ks{IvR)C|yG2mFR608CZU^PgBHDE0`4r~A$!6tA# zI00-1O<)Vy3buh}a3a_ac7PUeGS~@rfi}<%c7qPk3A(@@uos*H_JLEuY2bA58Sq(f z0GtWV0%wDBz`5W&a6Y&ITnH`#7lY4(OTZVvrQkAfIk*CR5nKtb0#}1;z_s8ya6PyI z+z4(0-v&Pchr!Rlo8af*EgmdDz^ z6F*BjH5u%~&$3bbFU2qXnF4OXFMJTc@JHz7;66(izwk%sQ^6yaF8(L+zkxdq$WO`< z|MSq_#GMXa#?KO8``^IN@>DGX@8K8Tia&HCvV&U(^6|f8{iXQdz@G&26aQN6Gh=-m z{wkYhy`{4?QR1(*^o^D-vLXJZ&|UvZpMRatzZw4rHvJBt{{VjJqm*-v&wmSkx6FI+ z%Xmqe2k^7x*8azQ^dI{CZ{TAHQ4Ihdz4frd0a4&!3O~4eHtiFS^4P zMEi?udZAb0chgk+{GywOi9a4Jh5j?^7k)~5p-X>VzsLqlL`@eRmo_v3`H6p<_8EV( z^-EsyucUm}@0Ka_b5SH@A7GH&5e&4Zv8L#hJzv$pW>;EC? zAGiKDe16fpLzez)=x*MB;QyYbtD95)Y@a{h=P$+ooK0VW|3&Mc>!YvqrQeAEX`5yT z{_k6VJN_rEUu45gbHGOz9zJQ)oDcnn)-QI|O)q2jtfgN{n&+)wY|am?{|4wUxM|?k zkF0+M{+F!(VbZvKm;~Kz=d;kCv1xWfcl-Vtbk{HQh06z-Biw$yLmJmFw%2X*d(hoB ze}MnTwtSfvUbcR9ODeC}VYi&PkDibJDVx3s|0~unditvMPlEox)-UsvTbInOGQVG~ z%DA&V+chSu_EV+)WvUGIeS8UGoq)KT)H3a^SM}PxTD^^% z3;o~JBkuPJ=V38k!yT-y)AQe8eBY)Vai6sAEu@$5-PV2By0765;h)T>Ln!YLgoomj zb_}EB1=^Lpsf3HwuW$=UC-Y_j?VPV&6JKEChqDrpe8ZXKCEsvMA7SYuEPaHfk7R`) z=|@`nNJ}4O>7y)tl%*Ha^J>x;T6&?S7uj|d*>)A#_#zu$tnzigis|9+=w~q__+{Ex zLO$WAxHsGIE!w?C^LMn3A5FRcA)UA%X?K%)nsgGDanN+@er#P8ly#07$5Lir8OKuU zm2n)y?EMbqi2FzFns!LIM#r0cW5|D_O@EW6OFPFfp0`^1lh%C(cdV^%tgUY>^6?W* zH}y^+ej?!ss!7e$?k4q3+=;YH+B1>(OLce?=MfTaR*%~7JGhewXJ~hmx?H=C`XlaS z@l|GtXb#y`n_F8{uZ8$~HUfF4uF zs^5}6gHyb1xS80`uM(fBuD5PTwL#BPT{=8KEmcnu&OzTL-9X9{cM$EkA9_OFsly5N zWmTxdcc~KX)~nIlovuo?8{t2X{C z*8M$hu5Di~D=BGDuDaN|^|~Fo@Jrf}OV6aWU)zYd%Hc{cxGbsu9S^G}z0K)V;{@&@CV{tSkr|JLq( z>OF_M|Bkj&sFKKtCdKEX{=F4YMCi(N(DZd4MnEH$k z4^x{}xem`)S88{$b?2+A2p6a;b-Ds|f=*X}rb)U2B+$4f-Ej3;oo=`-Z#c7|#1BVu zjcd}4u<1tF@8-)LK(xKf|EQlGeEtUK1aXC!#|;Kwqfaq zl-FX@on*r@-ctT9OYgMq9_<=FNsajCsoFLCm2)VOH%TY_6<6vXhn16Xnay8j^OxCi5xT^W z=S1&)+zIM(-OdT>3hSD2nLs*e?*z3*w|9aXq1!nD%OdTZVB0C_*4uO&wOg-tYuB_- z;-y^^)up=pi9Dqey_g7H#$Q~iM_l2{MA|8QnMi+SyeDF5gg+CpOj7SeTd$-$Mu+Rw ziPmk`?s2NahCicS(+(+L+A&FeQP)4o)<21Qq<&f3N&S;9Azmi*;Nn&dA>BP4&h%`frNPFWiZKO`*mPgOVR{8Lq<&Oa5&l>AeXRLMWp=9l<= zI&AV!wfQSq(Pl!QrWWe;{xs%|nL4~!9j)C(>P+octLL;^W7qF{b-i-NDeaz4c`^>u zkszsmIwL0aPq+0;I%&V8J3+gq9TJv)%&_zM3_G9C6Mf>%a%9GmN(0mCt)dXwoNzNmN(m$H`|ss+oqetDVdZv z$Cfw8mN&Rj*7h2VqvFh z*T`!vI}VZ4T4m(47T*tS{NHgGadIg2EK-+P*XY|KE4Si`{Sa5|hqz)t7LiBnhlFK5 zT!co8d@sU6hE6rEB0Glv7eG&^jX592X&Sob!xwE zXPr9Lx<=3HNGEzGuC%ufyCUta!y-t#>ew|(yXtJaB;8gWu2-jN_c--g?Q))LU8!Hv zN&SnhJ&}Fc6@-_lJ-Ys-)=n+6<0h`~bD26>Rg!KQC(+Zi8&V6jYxY~q*l)d{!{?|M zZTw+eR#7^Cz18>Sw%q0F6ir{Q_F321jpbN~M)EBuXDjZ}?E9u`cegr9yB%r7HfB-`XAgb>RE-mSDmKE;Z${x`aSJ9Rh_2-(8UdEcdz=fc8}&R3(sef7$?ZH zS<=$d zo!Y&D{zeE7VtfYi&{gC>elLQ@c-Z~BYBKYaXRVUPjsFVd=ZXKo#^?1CKYg8}rY$u2 zWj{QqTIAIH{zuwG{6-r;MB;&4{+-0%LHrkO{7~YYNP7H}#NTh@^SkB0i~J+jb5F>| z4__@uPh4ZvGpHztP4Q`r_UE?-2i#jW6=WyZI+Ia>vKU7ZdO2uk^o*`21Sa zzmjh8dVGnmxACKUiQl%-Q8(N8G2P;2d^c`#ROup9{nlH{>O;_ ziH#rMOa6C=|AUQ}=h*)KN&XLs|EGo9L*id1K7TPDezSe?ZvGKXj#^;j<;=Nz{o9B?(ZQ^>io{_lmZu`$Be()00{%T*moBw6vYi;~OU%Z>YbgQFw+xQw^ zyqkX~@z>h;TH^iVC;WSu_{WL=pB3DvBVJk}_DP-{zlNLK#yz>ErhRfo?$3WS zrM<-8NBr+>yzKkk_PO~-oXDNWWu^^B5%0F|Nn8GX#Jj&GHox#me!`!}h`-6^UrJcY z_s2g^{Jl0_&QP6@oJTXS*#5sm{MT*#1e;&@|FW%r#CGl%+W3jR#NR;t+d=(kQWHvJR4Jh~o=04^7nes|c2Mf^=O3JwW^gZQ z26CFw$i2hw1lo(9bmHvwrQh-^rEe^k)S7rm_t~XOoY0eTNAusMm)Z0#J<9V6;hX$q zt~(qFJ5`YocmEvm2dW|w%~PJB1PF&rSXMpR!NcKjtST(=vdYO0wI<^#UYV`yi%_at zZiHAl2X0@JrHX0?F)rNMgf@w`i2Y1dk7n1GTm@(2uTZay0OA4nhEPVPvpb$CW9lSw zq0%YgD&qzJ89ztH?-`S8w8ST-N_&V8%?c48GVzR1Xn9DOmT-1gD^-+hWTdD33?`B3 zgUhojk7q%Tm#$PAnak^uXRengkd~(!y2x=fUdYMK?)Y=~rM4g?h^)$QAt+%z-yZ)y z)iCO+wde&#Jy|RqdX>IrJBCu=++pK{R{?m`TJ$0*3!E!FdLDF1{{v0`qDLR>qvtx~ zp+858*0|g%G5wbjlzuLv&Dpr^@uvtjQvb$4NHww@Xbc5;&J(31x9wtY(X(dJv!bS4 z^vh^Tw5ia^Rms(1mCOsHLBDls(UMs~(URoqh)NEMXe!UMtX>VI_OO;=iqt$j911#B zjA;^WJsb=Ms)G0f$X`XFiU&-aL*x*-m!Hh51>6GIUG$z4!1w)t_=4~@SG89RS4F`= z$YIKUNcv<&7#=)JKQq;Dw#RGALBj#uHE9{MLIz5s`U*pL`diwPq z-K6)+f#{=O4qmromn^1@S|SRZTRj@>)U@8mQp)w~V?rgSwyzwa0`VMp5Y*$hiMAxD zvp1f}*mLtc5mt@45yo8fTl&9)Ji@ob;gC~+TNMuS{Pu7t6sSTrqCw$vaZu8yWJLBP zt}QNFm6wtcY;B%O)<%(rXb)*%-shhyXEI+N4g{PkBr)kk>3bRNFft&$@uw@`CXJDS z5}s?e7mZa#-q^UK8Kt~roN}-wGWIfm_{T!p>>rD#J-$obqFcT5y{EiHy}BtJLBp0F@x|3o+IrpI!HURIcXoP>u~jeXxa|XH;?9YW{Z;r z$B!oa7H5D;B#bXdB}$AhQza^mFIy#Qj4wka8pJ10#br)5KdE;M)~q!yJ87x+Ig^R3Xle^RGqi`DM8c&Fu(a2lM2Snwv9u>?lS|9Aw7Z=|jZ4e6 zv~SZ+mzKd9hwP5BIgyBIo{43Uc3nujWd3Qd_^uNVsVsF1rvZFD2JfUSXgE*{h*c*Y>622N2&{RLv=ujLR@a{mX=FJspWrmhj>Z>Yk0e zTc5UMs|IZLL`PPQCDC>C@vpS^arA3kG`e|r{7rn(jDtS9MfWsmf4xU>e6cgP89I(J3Rb!dsE;F!`i^@-zG$rjk>!0F|s2su!qu zO(Az&2a!%Jl=+FwHBxIMwI$;16~A$ukm!To?n|1&z-M@`3jS0q!Tz}OS!!<3{Hdsu z7Q&wb%b%rb{E@c30DqFWDgF$FKeC$dhd=M8~Rvg*kWiAAgol>~)z zZ^IENSfpANtt<)B|Ga#a2uts48w!J`_&7Qg{f?3@jiU%@I#1;ujBDqGFD3LXxsVpr z48gMItM;{vSfd%fx;C6-dOR8vgW>BC)hKN!(!32z8NJKhihks--pbu@%h#jR_$p%= zxuUz=6?`JO8}5_4VLmPuAa}DqvD^(u?m*=*bs>(vRE~rsR&^m`MrdhTrYe(#0i-EcbRvsQEKS#kQYBn;Y^|N;wTHB8dC9MpNv^+X4jYlFw8+yo4 z(I=9jVxJ5Z`edjG8JhixWoRV4DTX(N@Md*CGE@w23Xf2R8vBu<^;U*PS^jKHH3?V;LDYdc$KCSCVhDzZRHUC!`+T4!}9dBi* z(DJA$jYlFwU+E!3V?U7$jq}OS7@rJ{MTX{lVi_ugH{;;V7ydu!(1es7lKHO;85$3t(&s<=nm18P)}|hF z-Gom$*A+3>QCrP;=DLZ@b*$w2oa@Tq;dtgc+F)c%&vov~EAxx)`N?Fx7K~yEWnLW! zJy-MNeU@mlcIf1N&jQ8TwqcQ4cvPXYPwlSwqZ5yqHHT|MhxeKf8yOHwIeDwZ`SNKO4Sm;i>+X!0k{Tk`+#%wdj7Qh?Nd|kU%1sI581*t~@3B z9Y;=DMmR%RnT7&p=Q30^9+<4x)D&fZ!uKt#xLc@0+TrdcSI~~*3A=mABR|)hug~>L zkd|_Nu7_`0IWp%zmrE|J^x`Us^Pl-87gl(26+QAz_2Q;>i*wKMzT4v*Pr54hC>gKR z^5)86DeF%^-G5#u&(>1sb>;TFPR>Ls#m`wC{$K@rb2+EOADXIKlbyYu(aGMuw|##B zZ%@eHIYjI&+80JPu-$7~)6VjaXDnoO;-2M|GxpCizV2CGauqyuPxtIOUUF4MD%EIn zwwGK*8{M4Kp^3~myQ`Hwv2*!CRhBR1Y5YpjEBc^MO{1sIL(|Jio!_OGlR67D<@nDC zCB!kJ#&%pN*c<;7L? zh@0ib&FT?1+l!msBW}7EH@!#PQC{3pJ>q6~aWjbX+X+{v5B8(eGt*=ac3Ro3>VrP3 z5Blsr=+pb4AC*SGlCdt426HOTw=l zjyUnE2;sKNpL~RBVu=x-!n8wK6LY!*$zzEbSH@i)9W%9^P{@34K2h%Sz}_7NL&< zvFuzO$|m%2AU1#^J=CVZViA z>Gu0x&sGcP(&Oax%{;>q4yMQ5=fw#p)8j7i;)J8=aTj`V!rAn=o4q(V+|Rrr@2ASV z@gjVYRekUC#z)>7;A6W65E&L3jcEB5UsTJk_+naajRd>%%QWVfnanR$%rCQ4q1O|e>F`X1XUOvW=vuB#1CgJNO9Bi$=%3F)c8K>;G)!mI-PD7XR?yUgyfkA z&$ogPRP^>dU2`Q(w?6Q6Xs$b9H zA9{c0c`fwbo_Sv9qxb&2Mn*yRU^m4dr+4`UD|`X0P2Ds)J0Gi^`O>4t?NOpg<9K#x)|zW zW%MSwdLf~OJ<`)uu15b^uHuM5`Dd+z3I}Jn+2Aq7~I-pwS z?Uz#PfF%-sJGBm&FX0bT>wu*)i-+UvecYA60-;0(snl9vkx-(nHoVn9ols)jCK<+R zAcNIFMy*gX^3*E@%BjkPlDSwYnS<3U!&RUv3rf~Pp=1qV4WUA24N)T$_Oj8*(fQG1 zWAQMnhlq+LqAHflN+K^q#Rg}p*pMt08_KF8`^)s*?DY$@PqP6zH#Ru>AF-dZw&YaF ziT*hjj5Fgi^M{%FBh2JcX8ss6e+DyuCNqClzKUfJQ+xSPh0Okbn{feUCabHl8@}h- zHA|FdE%Xfv^MqThM6zbSvUVgDQ>34U*DUQBQ*81q=ow@4Eb19!^VIob>@#+or?zK| z&9m4SGgb6I@PN&;&=;dE8c*y^+iHlx;?Wyji?(zryDpqJa=9=~UQ_fXebDFkL0{Sj zeL)}eMQL<-j%eDGUVg&UpY(Euc=Yu47kKpa@<(~}^nR6i^uF3(oz~72e`@-m*Y!cK zO{0(X>P;_yf=5p;e~L#>FQ?L@n|l3gyU);A{~VIKOMzb4wcSnLnrR6>b6@5>k2c?< z-Qv-fdbIOB+5(SuwMSdz(NcT-I*)dhm#)^MUFXpjd$h|v+Cq=!-t$Qo*H~Kb^V$Gx zjI8r!z$3X+om%I)E3|4>XiHe3&1Z$Sloi?nR%nY@q1CZMt5uB!i&f+Bg{pBx4OZLe zPWqhx40fmIdaZ`Esrhki+Wa_{`EfZj%<^tC4C@i>m#pG+ncgbyNxM#alA3z1;-0dx zPf2W#Rh)ZnDwoc}y+G;K|tr!BYVX|i5>h8X|-w@?ycwi z_utGtj-Tk2a{wtE%jwypdSz$6HR|$F*0#y&f!hR(Z#-6ptpX{;P zGZb&Nr!8V@{He6no(W0mdbKxoAhq;dcWeHo>1XPEpx&dqZ8qyYJtAHl-B78shGiaI z^2pi2e_QX(Bb{GY@Avj$@W$$8>fYNkG6#^N`@Od(q4%~1e9A}fT~}OLk$xCEyxi-9 z&{F3dhVGud9gl9wzPo@gv&ifn&?n?_Yl^Q~q(Ypfmt}dG|fX1H}N#_kmIh*}}QvsRtCGFXym3=~idI-&u zv)crzUl2LszRk4a1WznDX$XEDE&08}DR)g_V54d;%HT;_p%YhSgGxA;C<&ZEt`|ju zg6%~=B3*k?%%&K`ZZCD$q@21^IzAyy$0wlU$D-rMcGL0X>Z73^-Gh1zYs+IaHGOUA z-)jw}KDlqE&$OLm#m_!V&b^OTW{)N3-p8oc>Q_B|^zW~XK7P~F#|iATe$(=omYy-qsn%rpXgYtehR4Mt5yhGM-LP$)735KhMd1 zZolr_?&%Kwu=CvKtuI-8w5Ly(cr^I-iSDxx-W$5`U1aidWKwuhz}I5Zb>mp( zouko>W2|17J@qX%J@z<|P*wbyh^X8*4(a=aAsMgYT6Z3zSp5@ST@znv?RrW+7eY_T zC(rwQd$ftj=PD#|RS!vQfZEW5nuMC{MqRUdrCO7>(%UtyMuu1SBEzm-`3imWx0M}% z-t$W*Wc&_iX0Y!-hNrI*e?a?>YEaP)hr=0gfS4Ic72QloKi63^bER^26N>A&s?{ob zX7s4&;^^Jcm@_Oop)2yPo*M^I=>UCdAvGyg1Pb^nl#GX**?dF$VumgcTC*A%aWX6CaSy>#&86Y1sZv_5~w>w}?3kpt1oL(ofb zT{)0@(nbz~o*c;hV&ov=&1kF8+6J^XsWuK?$!c*m+M8qL_;t$6qs+tE+3~9E7$c^$z%prfYbBL?wbVbW$ zL;N^xlU(|Hb}U@FoH)TC{gZpf$%?glR!`U+tGgCVR<23uh2GgVR34`uKtp<)W7ejP z|NGwf8@gYHyU!Wl^JH$VmAL|k_h_}0CW~oUB`p^`LHm zx}gWP5o%)(>L#e0dQdkq6KvE}|7vCcXYO*3LVibq!`WF*6_+EDb?e0+(Ei2iRCMRz zNS1kuzhr}owiAl!XZlMURrCx(JntgcGQQAvIiVsQs^6%hw`2Q{i#{98h!#XAM(1`# z1Dw-mhX!%LJdm2*x<%Jqy%(z=^{l?J0P+;;d29a*)L*nuo?iV)`{Zu+WSJ8#r@n`y zPeosi{%UXZ-+ES%`?x8Yo=!c(gY7rr5b+T_5xfeb5{Gpl|AfzA=rS@MJu_-UtuZ&D@pV&P~>badH`yMQ&X9aYb_M5$oS*OVSWaL!lCnKjaKN&d<<1=z9&!vogXvhy`))I*4qD{l$Lh_XLYpO-b1g-c6apt+M0M=;M26bbs%r98W(7I(@&J zGE-}loC{20hc&<{^5n*y6GUH=amumh0%8;W<0x(RkK^+mkEQNU@@~rUUfmR_-)Atg zY3S}9-pP!K*+aQ)ckS}=+8+1K&+;^Wmc!2z;O7aSh@U6Wr=k7u(@2N#vmAa7cKYHc z<=FG^-sjH~(s(h=(<4LgkN-dN_%Cghr@p<*xXaI(9zWf-yZk)izwmQv8b7DP&&}|2 z^C#lxX4)vbe)BBCZIg_@8RH*#@;McLj_i}4lyijg`6X{pDs_Jn`P`f~CaH5{LwDQ0 zz~iUec9)-Taz=WI?~A<`&qd&_y&B!NjQ9ik~{LOQXe85(4jm+PZHwR32}o7 zy+SCaLqiDtl2BZSh7$Tap&}j1=V=gkOrp2NG6t!Av9S)PQMA59ZRTC5uW_^TC3rqg zTRB;W%dZHe)-KaiW6Qgf(^bRjCe@JF?C^ z_rE)1#(be3b6M9;SM3#(oT8wdYq{%JymnpNl(w$j3O~hOG{gqc7Gn7fS``$T5!t<2avn6bC>OrjY*(eEc^$Qh7dex?4@ zyI<8#-|v3Scg(wARhGXCJ^solN;zMKziD^hAK&fXeIJnG?*KJ!K=hz+b%2V#RHpB~ z<9oa$i{9sUurJsNZ(HH*$?&!v-nPNpT`G|oeNcKIP=#5%nUfHSNaU{1-HlF1Ic-Vf zbdUGE2EeIwUh_rlTdD0fYP+4FG^r$#;&Viei$qrn zpCc-|rEHk>tx6`kTsaP;-f!wu9Yy1$=6>W=bk3DGx$9BFt~*)WrP?cMoOsZ@+vJxm zS%)=Jo0UD@YVJ?=>idzsrB?Q4Tb?XWz_CBc6T~&X2YAk@Meh<5B0?xYBaKMCIimMf_j|IE%thCXzM@Ap;rqg$uAa`Ru_S55XKHw{*9=34%&N#l>qH_PBp`g?C_2G{o^L&sSe znrC^m!Q+wKbQB(ScIVOV&$&E`rR+^ijf+LwMWbS>OluZ<(%s3#0iOhY22OpJRbgQm z+dX9ckk6@(>Iv{HB}MYyHJ6Az`yIzCYb@C#$+J`Wi5)57ODM^O2Y7dhGHXA}6XLZ_ z-|sFRfKQ*nR)5x&DgEw}-&Up1gU9zHQyZ;J&A0qIA&pkmr_Nv=JX0ki?Q7TekgKzfK(2be+suu*g_K-F$+c(lzE@*E zJUbhnodM5ifswC%-fiC6k9;**`C4H4waw#KoG}%?AA(;S{rr0CJot5t@M~a7z6Pps z1EW6|E^#LJ=d!uh_f|4-zE8r=QCmWCj`BUemy#VU2>mj!yXbfLlX>T=XkB4=gDME& zTU;1fqY}|XERm7OOl0L|&sT{7r)2zs+wIw*lzg6td@^!A`Q)v9Pc~)E-MegZ+i>Ce z@cA70d@i!NxgS2C51-G0&*vVYZ0_hsHczy&S#5dU;_+H^S!DCQ9wbtiZYn4gO{mh*`*IL{yExtsxSDe((TI&*5hW^W1 ztD_$oYPT{}V|mh<#uHiN%z-EAYppN9lOtbi(UaUum8`W+qOSC{))$UIc6zS0xM5p- zDZFXxXRSqzMpiU$++Antz17S09y&wu?zjxOH}k$mnt=c~Qun=AQP?eu)N z*L_F#(O&amxp|kk(V3d`>a*_>yZN5!HD9&P*W61!w|_tGHD8VHUt2Hzlc~WylMsHF z@XcI#CwL|r)WY8#kadfk@yMD=ej;ZHl^7)bKE(+ZOT5%AW&D)7OK|0!B&aq{XM2Ng zkLIuPlsHRNZeSg6&wUBYR>-$i%-4x_$KMg@RE-r^lIwJGiT`qHl{@-U=5Hud+GV~2 z#r-(iwwJbU?ey?M%IUvWRDk*D;S=hrXxZ^}hk?4LzvWLcO*J^?In+_n_Vg^~P@0hSk?_ z8hDL&8hACQfmiD^{wIy{yw99q4rbk-us85TR-vxAAHfeUj&nJEFgdW=4lc zCpq(?$8^Om;;vYBco35{H`evJm9#}{k=zA}{zgVc-h%r^AeI@Y2j*S6cX*f1yjdsT zidrPk8DEON9sNM=_sfm`0C)K1*8bkunP@d(xUP+@(u<>dxUzvi1gjS}9+3e4z4m6@YTBn#H&05KPQ#`OW%{c4b(gR`abA4_Cdco zjb7^AOHZeNjE?nIuYA|a(9_!=&`^9TemTubVKmT1rfBombk9yA* znm_B^=R^jwMmzHuIc&;oYXxX_a;t(^97r?p;&Hnn=|IrojSSo z-S;yM-U89KD+&*aX}fzLF7-51o?)8%SE<`J*Rejlf%W0FoXuU&+1!n4{m^STo4Z=u zI(Hr(O_{?fvzK|;_w*uq8(R9y=-&f8osee}qCo*&j^EbJpq!Mgt4p(Wbv%o>73*`W zXMJwMLfzEOLM2z<4E5$7)LXDrxAd@7?tNIQhQLkYx8F6m znL9h~cMWdgy9PJ0zINwjc?x2F=6S{+wC$HK7u2zPxyF&Nkfxq5NJ^2rhWM>%HsdHO zr!L)ZGsJ#1RNrczXVl!(&z;bly*r`qyC>#N8rtmMaqIQo!cA$q=e98!zd4OQ%hRRw z^z%HrN$=O0nH1*Nna8|mCh|2&BhOb_8BZ49WaYWQ;d>rdk8bv8cX>4W(~sQuv|YEP z^<$yeUPF&zon<{CKRH8UWhq|{3FU?dvP+A$?~PuI`8MC7l(j$|`DIKC_-lhQhlKS? z!%^!Kx1u{Yp*uIDJGW>#m%9ZbA8W}YYlFklh*K41{Sd$>w@vYL+cX+sKExL~9Eou6 zhVPS_du(BmAh~@i&tXJvS}13+u3PE4v$;3@XV#Y4jPQrFlIJe8KKamX>=8QRhh-%b z?uh?P_U5?%i<=p#iVP>$YeI_@Mz*Wy_C%~B{!^JdGYT^{5%-4pGYd1hOHuJJ=Q0kg z%YH$n{95Dx*0t~#p^N?`^*V#7!>oV4L)(lFT&&xcx|4pJ=7sy*>1omp)1~c>ACeZb zA9#Qqe_(bM|5Iq}&$WMd{8zfR_M*G3-EixAfimRYUxE6No8@x%H$+!QoF-PY=FFbBx~z_dLl(~m8u}ykhPDMu=_4L`bI0Y)K*Me|R>>*r470b3xd&_O%9(A2}hR)9m0{QlQ&?LHzKVDZUuO48QCr6NK)o|Z7KJq9Mvu5sI)St_)->jOIes! z<~U!C%QtR!&Sm7@cvURfI0f8~fR z{E^EXlpse9j!7jShYv)~hs6k!Y)4Sd=8G=513y;7g5L_?7y1rmO`ben4Ue-Qp4{O7PUO6xStJ9UyObsh<=SMr&u z3g>(pIFa#tfes9mr||Zdvu|SKgpK1jl^xIco*=mvlYR-P3!EQ1B;As~r3nvxvRoC+ zwpyL7wYn;j-6H*<*7aX>mAlZFVODT}%`iY`m=zh|jP_J-HYuGPF&DFg5t}aJ@D+Lp zkqA;RwqlmAxH+k0y0|%{8sZf<-z#ozN|Q`+b5Xh>x;U!78U&0^x@ypAG z?($L?y)X1A;{0a#Q21UfP#_B-WVpl@#nrsPKebhgtD4|H!~e4W^58c^ms@LflueoA z+>d=KAN7Zwfqgp^;(!`09}#$tzXiF(xiY-|yz^RkbqL+og6@DDCe>eQy z(0!plin@yY?VqIP$XTWN-Y7SSta%BEe(?cJHF9`oU{70=$On}op zH6J00d(7f$tmPS@^-f06S$eiJDj1j^a00>Ll9oVxWH2-<;N%4(Pla}1ucOz5cLa8i z48~>#-p4XW!qIRnFmPm`V`MlO4h;)C;Xv`?(LwcU_#8)NIG@*(Xuk7cIA_#<_$K3j zg6l_ZSNUePRr$`tsAmrIshs2S%UQl`#Zomm@XO#|!guUrC77e)!MDQ?g})ujnKgMz z;57bPAx(`1ow^I8nMVaS43{bkRhDN?RvdTql4E!p8~$VPiSU)-`$PAM-I1wg@tNdt zW(4-_+pr;U+DHV}8SQXTwxp3B1Rc7W6AV5T+5|0hP52B)`WdbY1mgA5w{TcGNuIB5 zSibzc%VfQ+Xt{CsS@(c-Wqm2}W!9CO5)xiv-J`6_V@6Hqs7tRe5DE=|z;QdDXPg==$!}(uomb!Y6HSHWr z54!WfrAWOK$h}E5>9V9=6Eg3DPt>7J=KO!MO>OcR zK1rP>Z2lL1PqF2TznoY}DgLSaRYmbjc{_l(wLr$oA)T~my7rl}#p*2bl`d%{M#d{u zr%=s!iBDQD@pZZ!(-#>}sbej%Ql~(|d^=yedDbnl?r2=8dzvm!MrJ+#I&|GaukD2{ zWv&I{y7g_uXLwjgZZm$;!c>W7FDEpKclj-TBflM%c8!jgvfXr@{2fm>-6{Aa{bz8+ z_HAIkmVXX7MYq~5&+z_4(nwkI6L*7kZ?Nu8Tq$p(EjPg51eJ83v+-wJSH@n(br}-x zj_a8=?jl?xUuQ5P7a?qz}u8HInP{Ws@Br^Gr_0PueKF z7r*%@trx1>Z=o8^6Pl}gqNx&=c8DD*rcUAY2|)TRHA|dnf#Hj*?~+r}N`7(A1=5G} zY?5DFFDUB zfr{YLL+47)khIpzNwXxYm#czYCNaE7ODKKsD`BCOsJUZO34`{!4C29=>H5@z2NEU~ z)n&pZ9Uy6M%&L#XgA7WD7Kha&FQ@HnJSO>F4i88~;sFu|HJ|&+LJRxP!qDV(YhPS4 z1$%8^L!N2ixezz^@_Uvg-KOb%bh#nND={fOAswVJ9g9$j zOVY(Z=Nvpxlq%mVm*S8vDh>T}~cPzP4V;_-q|zJmfiag`J?NYdhW@F(z7+*MA)Aa95) ztD^B3M7JfDm=VvUgSl$|!2>1T`lp9N^N%6%I_T{GS}{=l2sZcD441!1u#v>rq4PXG4@<=OikfB^ zzJTtbk<2g*$uP{(yq0PW-$&{((7d);Jzh%!;k69R0C=5~#_NGlC%?g?St@efMkgeMLnAUy8#2|+DNcJPL^D^5wlP=6YGlS{(2Al#Vthehwlk=vI2J;v zwY|XA8k3-Rno}_?<{6B>usgKw&{a~gWkFUvyJ3i%Fgk5Ek5E80jU>6vok1L0u-e}=UO4@K@Gy;_7bTiuRb{&7gBuxwYL8qiQ;up@-e|=lSS*WHmD~7+p7gU3@5h zKVwpMM*BqGjh)xc(KOv4?bA)NtP~*`1}h6RD~DTFj(|GSvT~G^Wmzd^-e-C;<5@k8 zRJJDS7Nj(EfF|n3q=-41=rYLa@<2`0jZLMD$FmT$LiNSS!2@MICKN4MGEqiNR19P9 z8uXN;9Xi8F#9}-GlRluQ-8D9x$RyTS8{>~@o)~|I&!4INL7m=+pz&vG`T$Kg_Fe43 zK$BW4Ya9_MGWPt<3{OH6t_=<)dI9x3AS(q3p|dZCT&g*L7SEh}E8 zX-53A;^Wgo6Le^RZq1=fo%lprpEkkuZlA)~s-VkE?YGR-e#=blx6IUj%S`RJ%+!9% zO#A_tog%)PWs|T6lW9~KA)cZe>n>o*p;lNSo@$tmJs50_oUEI~z~y4#2E`N5Z3}($ zJm@_ZJcHvyNNk(rOFWdsxo-2ITY1hWd9JloKGhkfORxT#R+4?CjM5#lvh1T5#uMmKrFxi2uMa(9ZLg<0(I?-+D5Xc^d|G6epxwmZW`|!Kta)ulw?e(baRGYu4OJLS_T8mxCF7GyAdsyDGpkQY>Wt(jhcL z=04YeY2{DxPiDPA#$>06S(@0*0%dDrw`DGi3qAk+jj5y`b`IB5N+0Z8)+al|b`}v= zjHI|S^NA~qAdCwJORxGh!?WqD^xBknsuoj88XR51($l_UY#13!3JOGBc238H*W6V*%ZP91XQp59AnpeGH^5 z^9C92qy~~D9z2}1Vz263Bz0S1E=h>Vv-=>1UC+Oo7B*HIT5k)E4CbAb4WR<&mBBu{ z?q5t~AvChQ^oxz_x2ofbO@^Kavf{&iIo8BQa1aV#Q}rP0#%iiQM`%fQlPsza=_ zCv{r@*%Ji2+;+{3UKHuwpKrM>yR6(qHvKdQRUvP?HxNE~G(F10v^p`aIa&n$k)o`k zR#}XVnUmZ(xBD7gZ)zKccB{~i4~t|vl~8Xp8`(~|8j>*Eg`PVxrl+`cEqy085qVG7 z65mdYI5V}26a?a#=t7=Pelzs;$r!#YJBhGuLgwREGqlv}yz%8aVg_IjIi{*%B|3tlY*0YU!a^)*nv)8DxBo~pC%~b-%vZTYFkg%pDyNW5;mXmLRdwWUGM(m=s%BPNx0_2^9tQEh5mJCs?G zp<_%pB3fuByp(!4%mHak`^()$tjMahNxEOY;&s1t1y*v?m$3c=917JFU1||K)Zft5 zAc>gvGJ%;n(Cv&BX>)EfT>YmfBU7X2a?YOGiMo5n*MA~C+bODu3U<&k^8w|GR zclgY1CzCeIiew;m*FULWUC&3X66Af%VmTVc&Cp(#+MBYZGCs z*M8doVY)7)044{3Aq#pAEFylA*V`#sHZWi|bDv3!3ep zS-T8GV@xlxqk2ddy87ZtgVq$YN*9_wvePtK@Afdpc3@=n5@lfA)r-*=y;Aq-iykES zsY^=n-&mt=OcoU}Pmp})BXO-a>3pywC3-W+v^G^TCofuOo?=3-GCJdqLu$6tYh!|X z)omYKrqhD@iEvRu9!t!267|FFR3$rY;Z%6(p}VV}c!WyyxI{>xZ7Xau2{O_XWRgH8 zu$Y%1J3YYw64>W&wuE?kfL37|A$8Rwf$T71I7^+^t-W`+boHX0^$uoHVQz^7h zdgMKTwn0%!A-mBb?;$_u7h3d~3xam}XU-|D71Z?9d=a!J-J~%)(=46F?0K@%2Wmk5 zAj73}avZW=n06-**9(O_Ef}&!?Iv17>c^NftW*N+lZ8NDdRYY|70Wn6C(uVX+9#7( zw^9pz(p01#gyAdG8O}7~a@2EEgPe zE4-zL&h;tS!1_W@;0Ec$G9%@tC(0v{yOu~L8j_x9X#F_PBo6zp)iTwG3o?tM6J*s9 zktg@wgH9giYbo-Pd`um#{>)slqfe%44i@WZ?M+istDrhU<`?u{uVZn2lCHNEI_%Mf z-IENvC)Es0cotq8sZhOnXqr`-&2HVD`UcdWfVi4ayka179Y9W{y9w<#n<(SA+ht8R z$4G{5pEqi{S)dI~XZkP>Vr9aJEDh8TMMh0^BPs@_CmYECjaNCmu(l1RN-{)I`OA^ep3P;RG8wu}2G2S2)fT)1_~9 z_x?(^Hg#-UwoPS~ zZf)DuzO%WjrL9%P3AA=~kY?ewZ5_>>ohqYr>zS(i39ZENmW*4owFv9-CqtF5E7#r2nVH1FQi+}Tw+ zRUPHe)zIA4(RN}>*Yd8?tsTuxUCm3oy4qK?ZQtI~y4_#UR8`(@Hn*H*t({#>ty`O^ zr^;kFv8|)7Y3q(7ZXY?Prss6a_S)zYyvq3C3|USrwRNdH9qQ`XU$;-QwXUP1twV)M zTefwoNa@a&T`gUVKx=c?DQz7mFM}8@Z9DPA&gL$--M(kb&X%pqoA;}j%QQ;2>|55= z%m)++b?(_IMRYbdb!^>{Y~I(U!lhk1S~_XFIQ=s7gguqhcQ) z(i2x8E4qB!)6$l`YLuaPL!F*usv2zKyK^I2x~CIi5m6LRvhC#NR$;{6rkx0ia>`Vo zY`hAVjbElFmyKV6xRx`r+naZlJDZzYyOTv8 zw@U?iX{nFha#HiwuI|*;T^)$<9F^~;-PP2!qr9eN`=aKpExVd_&QS?BW=~hk&hiy4 zos=;sjdpZX`yAoGTKR8-3YCp-tbr@b@GXNlq->e5(Y8Y9WxdgTDQ$yES@mC1+A>Yv zR5QuBugpIpeX_WANAuQ`8=AM%xsE2WH4VlLbe4)F&slMDTi4E(Rz|d=jpAFIJIfhI zJbQHFIsQR$+1&a6vgS`!>CPjo)bzBu1Ie--{ZzHJeX`n)Yubq+|9?~A;4+_hqy%Y5 zFJuwC(|e)!B@RYslg(Rql&|sByrH?Xy{)yg`BQP{-av2F_tairmt2MZzaXXQgfAx-RAoRu7G((oIV1~6u!PI-s|BslpR*mbww=P%Ls@wkyU)(Y6SFCy_ za;FI2;CiG_Ve8tDs5`#OdbX%fO7C+ArYF_qKJ)%xrNNfef659sm^P$MO7lKJS>2~& znzHW5vVV$k9NTBfsrHLuU3&!SKFyrDxVLgYML*{BQch#dkw^obTiSc(PtlBNy_VEi zM<=Ct)_pT@p7yF5qD`m}YfWy$E1dRZOPh3RFV zMj}FG8_2t@?Ek*I%^mJ~#Ln?*{E^nOR&6b7togre(!#DTHl23m>9%L$kyo--Ipb9T zPK74SpEF+n&mSMePl&jQ%9*4xCKZ|8^*l9bQqh*3ZCg*?x}&M3by;gm*F2RqsYtfm ztDANbh}%HR_Ez>C9nJGp6iVARJTdXKw06!@3GtAo>o(=C6I$^ z*>|_>)%{$_K7-Fp2&r>xM@ze`x^}=jvvwz{XT`kb4vRO8!^HjF=)w)gD z&U(Ckp30n5bn^bKi`bUyH27CGb#^vy)8$L3qit^sRqJjNZfHJn8Icr6!0fGb7v=Ck zldY;qA{b*s^Z*{V+ma)ArFBATXOFQ>$-OAa8 z&e^$t*Os=O+S{c!mb$lHZCyeS#l zlT~nTa<s73# zY3s@Da7I}lD_5S5M3EV92TTbeqX*ZktZU0WooY`j0lP?S@vg0;&Qsl3`_8yf0r zlgo}h=7fg2Rh%UyX~Te84pf>a%T}$UZKoRD=Ps|z9$PRdp<1L{#pM687KT4xe4Xw_ zpo}f?_U5H+oeX70Ehke=t#U9mN~&PLJ~_#F%`TFIrk1T*%JuG_cymhC(wr*E9$Z0U z%x(OBmF@a;<%vaArKPL)w|4E|0BmdRPDD>-FKRxqiBpwj9E)|TK}(w3yv7*~b(X2X zvK1=4Y{fF1n0LyS>H0W13M|{82FfI155PL7oZO{C`qU;{{x37fZ}XH>ubdSsu%Z?$ zQ^6HdF#oMsfxliNR;b7d2vU={0-_*?#7|;Pk-!QSmH(wtWYJ`ab?K5ZQ->+V6el5k zI-8`+($PXo#X~l^Oxg^a#Ftu^k?qkXuP;QxfvGrrMC<0QK$>;L1}LW34PN91-Q5)% zbaHxuN1AEE0kVcPX`0>^HMMT*XlvP4-o9(?w9<;Vw3qgxrHWU2|MZqt+EPnvYX86AoSCy{H@iy& z?&{2M=9_Q6`Q|(GK664#urb`SJZ~9Kj`DbPR3WkxB~1(km#Z_S({YR!!xG8R#Se~` zu8yxNM2<>}KHkEa9uCA*T_)DAo`%Dr(aE{4THPF;*w`3Uyd`mX6C9Z3xc#j{T-N<; z72=XO%*z@=Ep=fW%MsMEXN8D5&CrK;QG7EnY@_?fFCK>mD{3*Y=goAEOXuOVtcm7` zr>o11zKgz`b9Jz`4)bjHm@AzLq(Y2{Q?VGy-C`Oq=6NM0TqnjE(sOcfbR2HPDZ4vN znISzNOUN@7TOlTQD{I0LT^}o4d3I7NL|(T9L{3XY_ez}YIp3YQyw=;aqk2TbM-d~S zLR{S~MPss6I&Icl{G79qgV{Fv7&WIbr=UX2KF2aE#PqX~*r?8V)#<0B-*x9qT7{T( zE@U}76Gyoj=TcyWSa5dY)XOo?I55eq=p1El`ahFiA;zDD>IzYMM)~mq@3ZEsNIX4wRqe7_$T7QuZtB`a zK$3~fQ?O?*Q6ciXQ8~T@H;=D-=9q&WvAYMV5Z!rlbJg2azM0`n8QsFV^8}*uCux)( zLqxLZM%kSf63G-XNx}{sx3DOr@4&drRlfDIA@DUa%lPp8uzI@NplxBHmr2Y_FnUuSKrjKoF~`e(5&T9jur7J zah@X~_U)}PqI$Wf4#Du)HLs#gYJi1s2a;CN22gYy%A$-0TULV@i` zzbr-H2!?{q!3xpakY)#KQ3;m=BO=bf*b!aQ+!oyTRO@;-lOuMxkx}g$iTALK!BmzZ z@pw>Om6#n#jDL>{ttKVZ*z&ael`T!n!}X!*>h2`Fj8>UKYg+16^J>bwT8D5zb2^Ukr{lo- z>@+e8$4BZJQ#tiI1Ey`)L#8(@U5pvHh|k!(UCF_4Tgz*j z@e(<6m^`zVat@wUSBrN^Iqqn5UfguPC@}f-ow`UCe{2%2ae7E*i=XS#aM6@kne$kF zoNLX~!z;qL3Yyn#eu$%0M|Cp;?cdP2C~rx)xvCMiYVu6SI8kqC=1|Y~4@sE7;SAFk zOmEb;wF`Mo(^;Y5h@EWL?vRMg4EH@xdDp!UPkr?tEk^0q4^h(GrW}qhvTLft#xgrs)X|{2h?zaB5*fOPnLQ8GuFQDJ&Iu->y(;r_En!;1AFVpy z0rs3ZYwki^%b&ydr+u8Im0GtkFXnHnWHPSP3Y>(%d!iyWBIV8HMhsLX)&^?TjcRJ1rSk~y^ymT%W#G!@VgUYo-^?+ zT&Q@aHD`{Vo>?%WU`$c|xPr{Wf`So+V+zL=6-3lh6HVsmbD|r|Dj87#`C|$*M^DKV z4?Cvum@T1MygZ3bVz^K-WeCqwAb~wR9MhU%mZ-REoibRbpscvuF;95{^O(|ERJU}5 zK?;j$kaBI1bkomfSGHgYiTgKobxqkTYnvD2PK=rqvxAL`TKFMAQ#Rfho20THsNhx()T zyXsQHkgogLQ5X>U3K;hV<_l{+E(*-YOLHGQTo<5^x8n*xwdUi3z}FE{9l;oEvr)%$x0RB!7QK!!CRZ|mpt zB|m)RA8`G|sdJ~!yk_Cmv*s*xuCq*>F62$Zx(QdTAS#7i7Qb+~d8)|2Io`fE*(V$Y zadRZ;<=7tG?Aec)EluB`x<-_P=g)J|<)WuUxVkR52w(5$52-S+{^rd9QbM7Lb#qMXE3udBk zB)*W>jYMh{@9+p&EqYe-n$C>is)>ujVi0;kbxR%Qfop?Ji+Ry2M|TF1#}1O++>kwz zd(eD-RU`&_6K4k-aV-tcQ1}(u`??3psl_4+sB5SX z3m^NkNayh_Ki5iC=eW>Lod+8`&O~F&bxy)^^(>Q%r8;CP|tWhGM2bY!j{0z�?bHmINuxbaus@V6nVnM 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 literal 0 HcmV?d00001 diff --git a/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/results.bin b/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/results.bin new file mode 100644 index 0000000000..82b3009a77 --- /dev/null +++ b/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/results.bin @@ -0,0 +1 @@ +o/jetified-network.jar diff --git a/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/transformed/jetified-network.jar b/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/transformed/jetified-network.jar new file mode 100644 index 0000000000000000000000000000000000000000..af1c4b140b481f82156439504e50812fd74b6455 GIT binary patch literal 99352 zcma&N1yCg6k}W*IFjzBKS@xVyVMjk`O8I}Gmb?(XjH?(PGF4)F8u-FdrIhiFd1@RU3%NJ3 z{J%Ma$8-PC{dD5|x$yqknGMLv)z-n1&eGP&%G^fJ+Sb_F3S^|OuW$0Xz8bQ9qu62E zQqDV;3CLcge&{RywPSV)Z$s5QS{PN$v7qYc+ z0XaB<9O#U!3>+OZRkhSm)PV0(2W$YaIEq+Hsl+Az8Nbj*HlRoXTgnckRAWP<5Qf1B zXfFfCVtw&2{v)^A`z*iG60E#xaj?$z4d#teXY<$?nsCV9o1=MkySdsl`}sV3`{N5k z7(NEFdwW5-HMYqX zeD5$~M%`Ev*W`{=Bux+!zkC2b;kWK~8~fnd3x&i2%(Z1{_WBw(Qxk&+tp3yiapDyS z>l>Z}?nB~}$^-lNwAy=;(3!rT)!m>eU{HkTk$)~QiI zATW2Hk@>)-XaXjk9aEO9j)_}knd(dBEC~aT4Niu%eo{b2j-9X=OV}jh z+p$vq{At?eUxP)v2@4T=7)xh^tBEoNLZhIGhJ{kBMwF_&^xaJVOElWw_Voo?AWk}L z5gsKM-0Yle0RUpLXgBEVaDUDs1L$joH&(Hf+;Ykhz->SnrY3>heAT z5zoOOn=N{T6WO?pi?4U))wp>uTfMSr5x)xKX`c}QY;WW)0*b2p5IZ{Wmp2uBCk}A8->wR=$HNb=8)XdSf#Uv_SdG>JTCdP4!vKX7^gri6&h2tW zvTXHTvdy)O*j@P|2Hv=1TtZu__n{lFD|b=glNfMz3ZCgUBN$<(oowkbZg5s;nATOk z94u-AN1(4bRgl&)c7YTsjUm_ANX@TKn2$Xi^Myy}SU}&xhSPktq4<#Cf}7A_E7E)sa^GW&Pg({Qu7|37xhW*&ysgwb`8Q| zx`mWBoTjz&^n!bZ%GlQa-p{Vi5Ypp^1$IP;e1*DRSOwqgeP3U!vrIfmU1?}(RAe!I z@I^VP1}*8b4)z-+P6k<+4?108kO*~hS{_I3#qFvaUgI{=YFdK&D>)nRY>q1VGNFJ@ zG;N|?{31sRW)Uunqv?)GEB78aQq>y4FjP8xiQ$6P)GADau@RbitpO%7f74kq!weBd zGNrh5lyN5gX01!QCG*P20fDuYVwC*+QAZ^$DkP*mraUzt?84dX23*)RJ_ze(qRJh7 zHxgN(IK)3&>5+r33=C+VAw$U+ZOJ(6L!;=SSJ-4%wiufWQTvL$^6)2=h~+jJagv^l zp$2h2B(=l0DM#N2=jn#UvA2eybO{`UfjOP zDHm7*dF)}rJ;x z83nGS`JlgiaX|j^h3elH6-AJfgRP0Vla$jx3QD8;t1HSd>bqzijX5nJVpG86_#jcN z-U#w{u!xzTj3NZs;qE{R=jGpPx}J^a7gc})Ye}hevGjs#DDQ%667K>T2@7eC;t&*{ z^X-}Jmym)t6H;PKxOH-#KOAYTj>l7-u3f2~$5kKCv)dnF`d?fj4LPW@?Nl5!$H}E9 zJ1tfnVd{P-rT)loZDK8KC*LFp9Dd|{iuo-bGC(VD| zaBJ$&pJ;S;J)vve2qIJ4f7?97dx70u*~;7H6kcMWGG+a2=B@PfQG52(xDjp^7ZT)ml)+ra1%_ zU>Xig8aA{gdd(MA@30^tv&uHSZxiOqZ%;V!1$eN4!ZQ@|w(cbfJJ1I4{KE9t8!!gU z2z<>QhjYmQvlGu+w*tnf6?oU#BrY_RvPNAC& zwJO@1+8c}Vj)P_li z$t^3cXoZk`OKPl0K`_Ku$Wq8uxWtOR8E&T&slkUjy8(QIw2+S3?5Jq}9WHE#I4#*K z1fE{L@lKFM41IQB3@NvB>b5AZfxbhG5vPuGc#0Muk}#$@Ki`o~4R9JBZ;RaX%4?%( z#Op5xKG|vKcsr9M*-pUttp#w>_Yooxi3*|-!^#>e=X6V6m7p!7dcy2CX#X|e6Lky5 zgF*Il@|tX@jecOvNL-tS`ASBjq%YOM*#YeOIW4YE?-0NdL(YI$P1g~QW9)B-sU1&LR7{gmVv1Yj?(>9WW%~q(m#cdx(!DCV#zm}lM(6XBRF&*P$!YZpm zUjq30%}MN(a#AB$K;wE5mukMQ=GmWLeSEroxzJg#ndpxPp2!VpN6AjaQyu@`+r_!i z!-*3Fo$q_p(x<7jwe=xlA;GC*b=W!*p6s`c3|-;XVPAQzr=2s)f7&bi_qXDiO&z|;Bq!JVovs@9^>|DCd<$mdfdxm>wBrtQQna0%gXF8S`4t z3eL(7o`1vnBuNOeHP(T}4`z^fWportg<~$UOFZhf`R41j`U>4z->SICz*CFjmLHw3 zG=`+DJiQFs>%Hp7$0YEntSk5P{(=qil4&gPFa4&N`yk&yc6P4z(!V8*Yf6+*X}W#0 z%heb)m!2SDL8Rq(+r3`bR}()9xGHN1 zD#@{oHLeW;>Vaja`MT-p3iF-7QxvI=`qB`w?&v@dG_WtzM zv)0<7%R<8dG(leb*K7-h6!1!@me4xkhd}kedrQh`xgqtVZ@o`<&nP;=!ShIs*vbCB z+C3QL`mxv<%IqESW4k_7D&lE_NcmKUr8%&)smF8c$awwBhgb?M5v9M|(J0y9#152D zLpa~|8cffbNP&i$Xsba??%p-(@cN9-KEn^P*JUJAv!;C+tN!a6-0tD;9TAyNKjkJv z4mOLr(A8@$h$`k&!1!dJf+~%YYPJr{qPZd2pO}A{Zo1*7>-PzUqQ#{gThY?l58l-f z^6}uw58Zx01|v0frzCB}q(hKVq^0P&R)A|6gYeY*hI8pf(En?W@WFTA)-s1heKU_T z($YzIM5%c9OESc(Cb%EEZSEBwC-gPkY7+mi{vBkS$cC?Jf(7Tc`=koQNjFimS6N$K zNok}V4_T(VO<+&hi86RyT--sQ*jsNF{=YTQtgB@jA{y|gT6mwziw}D zQH=_@%1OB5J3Su}$Py3U{U9<+w-$D`AV7~D6dxD?*N~~-fHG2c%SrLYhUVs=G*U(IJ&QV+M@9RM&Gxb5bbWnpbVgV9co5#tFbS@qE*Abppz_GHPHgdLkh0Nl7LUri*fWmsZ*t@23Z}xgG}C=)BP(; zxfi*tWte#D(RYMn}EP#m=v0}%LhF(@iCJQL?3JyA#8W3*+l2bTh0^4R{ zz#y6#Mi_ALSGxA)joV4jJ7%jG2WC!x4;^ux2_7j1U$}=A#mNF?FvaYKdl*D9CnoSi zX5Tm&ZE_%7F*z6f0GLUVAM7OevPavr#wV|(wmKBLhv6tit(9LVNg~jAG<$l^0`bR2 z#$#0`nGfV*WQ4Hi*0xV;a|=BIw1x1Qc+-{~4{hrU^J}3=QW@SejZVX>Vx7!HQ_Kfd zt^F$fl5ye!Owk2uopxwbi>R!ogVnH{6cTb)ESurbP40t8vzgxMTwL70V(|l%Y}2#i zswg~YF(v(dqPBitgPAfk{UuXzCnlb&v+xUichiILXkA%&Su0w`up5m@tQIR<7 zm-+|J7}7O8WKNMm{0kz5fqtT*h!FxQGL!I>!F-(>#2ChAIGzDvXlh|9VY&X;%h&JOx1pC7#Y83Qfr_&gwE%zLo z%Ojr*$LY@N%;)FxSA;K*wc|4wX(SGU-SH9yg|B88Bt#U9oSd3X(-o{uOKhW1xrwuo z48gzECxn6VG#-o?a`{^2S#W+tOF_zRVaLMVcP_iXwK^JSom(<&u~rHg8OD zX~`H0vwD@58|v9dwO!q#C}wHCUMsF7VT;zqq{Njlop@YEU%XZ-{GeQB_Eftx1S6)V z#4>uH;p)%yC@1&f5YRTFCFFj2#SV;NekpRLg>bctFf`}*=^V(Ps^Dxw+9j4+Jy5tm zVM5}qt$UkB8H6h`2@EHk-640&!; zFj}xB8?E)iW{|8=11TATQ@fRxv7+F6v57i*Bp6843g}T6aVb_0Is4_iN1U`tqkuwd z`@{1cl&bSBeGDE0?HdlI;f0N9{!Ud)1#KIbp_Q%xcjPi1a1~~ZzM3$ACWm)SU&!_5_1$gjz;O1Ky+Gy;8-HE0d9qS?==Hp(+6CR9cdU9qeK9%5JO#$v z_5A=#o%y^tcF=r0M}D6!{9&Ai(L@Q!6fa5CMbGN+K@Qmzqw>3b0AHtuMHr{}p!nKp zt2WELB1_tz96}0(_%65g+wa0H!wA~p2Nr()o16%R5BoW-*iNDEnij<^^S6oj!aAaH zb(UTD6kFS5CI&MH`>*pvamLy4%|Z$RQ-77|GMR@xM;?A%@2Vm^^6k%(k72k8xL|l_ zLmHwo3Yw}o`Y_#M#CsodOH!cp^+;wnN2+kDQ8J03W?v8+?3YHFV>8#BprfY0q^(0m zFvJ9TlMqXbsz=I)-_Xom&HcAT*$r}lX@LCl#qv{)XZg1wE?{gd4l*zXIs6Y$P^r49 zh9U<1m?Sffml?o7gi#eyw~sOZR4 z*psU*I?cvNa~9|B zT0=0w^Up-n$yl&gmMWDKVX?aoG@d_hjI__S0J|i8u$uPay(lP_nAWfzW~M*fq5Tm3 zPoVkxFuzqREm#NVUZfWW3Ia~rrGkZNJf(>iMu`MimYSQ{r!Qf52PE1^=^y_77)JCi zEs2Et3QF|V-DlGF$Xzi!Kb*HF?p zOA3=1Wyp?GxtctwLdW~Nf;Xp4X?2bmZxo4#+AAqsomG^e z)GE#rlIXwVsr^3F?JSQ9SB|G5O-2HblfB8F!h@g(iS2%)SNc=(!YLFAs4Ri!3aq1= z+P+#l#3*ggQ=uAKvg8t!DXKEJwQWtpVr+1z%9*X;2h1k-NuqjxrRSus{xJto`%r>F zO~RVAOXAWLaAe7oICMtPftT*^+Jh8q6uy_ysR%YB19xk?D-et2Bz;_U&l5LD%u*Hk zeI38iyFJ@_M?wl;8)im=oYbaCycNoFP}v>AJ>C5%v72;ix3WwMKT4^$B4@N?Gwijj zuq8H~?E%SW?){!Ghm=(yTomIM3{KchJj}|~I;&7Bf{R#Fols%{4);=4%F#$gK5}eX zl%MQAq`hp-V|sJ@HXbDVT%fX(mwrx`U7qOJ@xo7xw``)zjMi92w*;T(p}xFTI9G5# z;GcgemCzfUQm4N-(!JiM-mr|`>{ivp*?4N#Rz(v}RhsK@8yde5N!sREk_$~Sz$$c( zbto?gc23kOthT|L3(m*AlB1HegdDZ&Msm+Z^wz!WHWgNX0AQ)hd1=E3PE(8 zFIvLqCF2C)bPCKYC=crWx?l8WDC&a+z|n0NCu?b<2=lB5h>u6Ly|AhJneV6Ji#oGr zliu}aQXkw!4@+#dfZJ@Mm~-zXzSzr{UHzdcozpb|VNL{C;?!BQ`U}`9O^l?Wt^-zU znLPb(Ay2!5dObVLG!sKmO}FxWa>i=`^rzlnb8u}O;7lZ`FTWyb-clpMJgUIV+3$hR zXT)DPDQ@8IBj%Z9deQ7)MdIdQPFUCIXU*o!ayNragZ8qDO7M4y!H>RA?K38YvW4M! zN!R}@KSo(ggh|!#l0@^m=k8&e)tm_qc>-V+&gTj12u}rh8X;W4X9#84@uVz~jqp5; zO$G*3!`x2=t=_(l<^EPP*)Ykur+2!ZvZ21cGJZ7ZVt?f%L@m7nQCX-p&D#A=GMEWE z@hJ(b#58qxZ{<~Zsh6x{=V0JYkfka$u48BYC5b@(xalC*^Kp<~c*r#nUj(`P&ErI1OO6}d#og@8v}Eq=&(_>qXH-f)a^=b;)8$F;4>Vi@0Dd@ks65^+jA^Z3bD#&8$m>-R*kCrLvhU-Bc)&<70X9 zF*LbPIm7098zbt77LP_C5D{KsY^S<5Wg%^|A~i}&;x9*Xn7Z*FULB!gC2b#lw^vmZ zPP-v100ls8kTp~-N?zb5-AGQPBsxa`Pk?s-L_k0Nzut!7`OV*)*W}x5fCM zS<8R+pcA*{u*DFEyFJ++p7&Nvg?e{bzywQM{ikPGtRZC5znIfvsu7`D3<`OsQ1f{4 z<*hSc68nmL{N(+Uiy%NNXi*4zyTVs?cCod)BTs4r+Pt{BxG*&_HC^_7dwb3Pa+N#E zunoh_T=y4Zo!Oh~g%`S=thA$tef`#s84$n#H^ksN$X0vn|CVK$u2m;NqXhV~thkS= z#df^pmpKS6WHNc1c|s^uxnzZ_DVB7K+~b866zyB%A#(S;mAe_Hitd7;*cg<_T{mJb zE@U*~!T2lJ3pZ8_kKIvTUSIrLE0)zPQU7j60a+DPclPylz;Kd#=q6Y`I-coeY8MEy zY?-K3RPJbQrjuCn!?o54{-LVXf=AN7sfvh~65`T;t{FWoGyWJb5}xt%<&wbSQCp26 ztby>6AymReAmk4ffU$ySS@ws$Rf1GmNak+b_Sv`C^QWWTSW`|PHM1IFn_yo*mjo|Ed01K1@L1G(bDJ_G(FF`K*^(SLd=E6=yYXiqn7*>Erbr!Bst0v zLqCJo2Va-l=x-1afL7i}1JH=38`76V4cwxVl5aw%OwqJlb;1U7SHNou5!VEmSR`=e z5S_V?olvAo#fN0ftZe*g3rUev-{ax5ymEV`P;5eFnNB?pzAU;*3V6}%-#-)arb|v8 zMCD{C;b)`e(<#e6T&Q#m;Q#CuN9G+j4Liw9_`2U3rLll>C#u?>JYU{Q_4k&hd-yGh zFYO2`EYtf;C5UWLI~i6_uk^qPV5Zu_-~IhX2%<_Sz=UO#nObZ|5^`=o7K=7zeW1!#q)I$wOTl~HJ|+$l$h)f*@b7Aq2wrcwqtBU5 z78q3fSww2~tzLq+%Ad@~9Y(qB`&_A)9lORx5lbjZsi?F4DZn;U{Hf^gW5SD%|H40B zBm#NtG0kCxd7t-x6_rD1>*MDwDp&tpwK{KX8ZhhE!pIbXY$mrC)xJ;Qbjc?98?3vp zD|#u}73-}o%Hu7s^Q{01{$4K~#xT{A3*#8$=q=K)P&8$9499Q9GsQ9-BOGR&dYopC zD?4Ql8~X(g-e;(PO&6aT;{OcXp#R^~g@K*<|Cn|8=WQha?`?Jl?$#h1CtE99Q}_Sj zquKKOzU6ZiDjNUG7oq>94@HovxucVVfzzkHq6l)dv$b&qkw`d!tpAa~4%Ocru?I1I z)Zz-~Ppz^+Tw!ti6~et!qv1fV6y@IDTHH_^@<0r5iJV;;or{RW_}MJ@BA_6jnUq$J z9;N(?$hg>gLm{o~9Mil61aszY@;mWcDIZhjoU5x`alKO%ytL_BFUls*wPXAARoC^^ z>(=3$@7wA0mm2hUas3wc)akW0R-*}1+J2t~#mwI>qiRz|$wfj>QT_2%k93nN=R-dhxFNYKd8+cug--amV`U&+qli!RE{ zCodiu@)IRjXDUC?7PromE zFUb+>?%f0U?J5{#3F`DUO-l_>%(Cg!!W{=SaAA&Ez<+Nz++O(0mMReC*yU?#FiD9f z{v4Qyv%!{S9wQge$v0N?ZnH|5lBLgHl)5Vt(H}N0alOwULB zY5ij{?V8hjuZSPHsXmyiCRev%l2phq)4wg~4{If5ziN)0d^>c*B7}0!6U8sIm=_pf z#;?QA`mh&hR!RJkwXy@i72+e-pNwKYI6}ocyZalJf(Bz@1`otnl8`n5-qOd+EfE&C z_&U5JdDT5WjM?ij4H%0Skfsj&kseSsBA!bFVAtx)KR<5&?pK9QVLV`LV|;CUabX=6 zVd1#?THvV?uPH8^9>9$z>xuVuV=+`r%^e@%tAm(@&G5;OB2PhMfyg`NJ(enWH-zYq zS+B@+c+w#?F7z0}xR4Yzdv2g`w*EGcu2GLAz$2pVVf8hj!F|=@XFtoq@gDeZ&xtyCI=Ce+tj&N;6*`O(KklR z>X*jQ1UUxzE#V`FG;)SfE5haUvPT#L(H*d;{yi&L(eLm4$@V**F4@crkzb{^5xeeq z@~UQ*LeAHHfHx|ni8CjcDkWxzF1l(bX81eOQ!w~+-!kZljC)w}+3{pBrU8Zch8DAH z@+GMJ8wd&r9Svl=2`ZP?@V*TjHP68AipDjxUh?{|KNGK(;WiWtTojz4`XK^ zaFgM*6%vEj8fIt4ZSDE1h`3z~(;m1AX_x0X!e_z+r{t2;5b_m4r+tPTiVgDMr^ls# z|f00#sPuRIKK+{ zClhf(bZ{y-;*CZF^6@Ty#P6p-aZMexOy+N2$;RA3-pe_aVP;5-|17vVnjYD8rT;cN zt6}UI3bV)4cZxn5cC`kmZH{~`aC8(uliwealJP?Sjcb@WBH1$g>q3=Xf4`|{2)ow4 zn0!t4PG+T}zNwBgbBAh{EOAm>#Kx*CsQd?S>Acorx;*JZfdjLid_mZD(NJ|aCZ2u> z487X`k?c3Zn(KYEEa<Qc2}wxaq++Uw#XS4s(JWHcnDg1*>x3^w#y>FzR-1uscg-}OyPXi%_7AHEyL%by zr~QAP?ac9Qnh}~!7hC+-&--^>L0k4pnY+{^;g9yhBCg_=DPSvCoTZa(**Bv+Xm+yH}uEEj#dLhl;vB1e9uGoa92!( zMy~({Xb>Vc6k3SpJ6LqeV*n0gcE}IIESufXXZ8uCJBvW#BdJgP(@P;XVap^op~d(F zu~*ONl6FI`umyWoq#cqaDv8^)WT1uHw4596V7qTib96zAlp5oD$=@B7&&QBf}p?B-udH7bY$!DU9Mk^F=0v82yCSLxIPIgB$VWrl1w;l&$nl4J7y7{nsP>PUh= z@Ac3Fq6p`y@n2RArL0w_esA2xk0W*E(jDSj2dD3XIF{A%OC5FE=m6YVMY%<&eU;S- zw%9;xN!}&(V-3bf`O$x~|H&781a}02vw?1*yKvygnD72Q{QzHy9i)M?s_~n|*ZJgK z%P8^#k!&3472mW_{n*FMKVAf?H-9g+7Od;7Abo?xZ{U9o8tS^W3_UJA>(W;*x=)Uq zo_P(8AD?+mjUz0%I}O~?@hKmNba-`5q7nu(FM1?&-mUG#^@Ju<6SEU^RNqZ#zXtY*$_xq z_yO*hoJ;CP=?xDqrcv{H>x$~rBY_5LP{M0RztoMX%&m040IXrgWD0Q|*Fw$W-LD9l zA-G>~uRSm07-2*iO!p%-h*`Ll;N{YpMTvg|(m9E>KPj!P>tyd(Y&N{%b%65;$J7AO z31zEb_iiDxI3es{B80%J>`u3PRJMY8Vi*EGo&aCrZsNiJHvH7uGlkwghIE71<%0<9 z!@fDf#3*2p+(id+KC_Nib5Y(ah%2o`;dcWc6kDSDcwt{DadHrg?9hX_D6NX+a~MPo zGWs@w(Ml-=3}z^g(#{GfQ?Rq@T2ASEDP!3|nYg3zat|IZSVA2eYV*nZP7;r z#ihm59`{G@d?x4le0|@(-2h@R+1jaM0Lft^#FP{0D)&TNa{0V`Q$Z|LN~87*Xl>O6 zXw?ydsCXDcZX+>n2?{#*2G{aVQBdiG;(@1tHl1A7fX=Sl)LwEk zS2MUQMCU)g5DAo~^1IUI#nMS*W?~p!)9~aIV=-196slOwEc*I9eceT%r2;eG_}%pz zW73*4RsEp;_@BJg8L%|lWY^iRRPMukB?xj&6=a^rAxiM&=G{7G(?P2JrS(N`r}rU_ zFPqNT(bs{feVa&EI(=_;c|6$Plr=g7m-`E;lr&)xMLKHlPBu6OwlRbf ztQD-Zh}t}!SGF$becFPWxh&l|l&)exJ?ta`7(DqqL_yx`Mb>!$E~4^Yh?+PG``X;F)C;afWE=0Ce;{Cj{h-=J|;p{`qiuuamqYR0T>nZAuRtoe#iRCQtOv!+7n=(*v z&%}jeuqT9aoYPk6Qj5%~s9g|YmMpsJ61xNS^DyA?b?hTCaP@1^tqDNQK8kS3h5Xk; z2i8`ep)if zk*FQ)wImiQP+@=J0k5Xp1xT5;eg(h<=*k9PVCsegT%qXj05R_oXQJyfX_7(3mNB-< zX0}C3XZLlb;r^Qpg%aCvt@$xu+U(9vR*%ZA6vNW22(z<)wl6(fCAN1^iN}1GZ{u1R zt?IZ3;?nZb|U(xOFbR!rBWn4`aZ!`uaxAbK3c>MDL-B;}3cX0^})Igec)g zacnR&p1f~Z1qGpDx<0Z!z`aR0kH#Qg!=;b!M0~q1({8U~zKA`<7W?-n zR3g$!crkA@SI-&=qaWWvZ1rLMVD*q4T0Nd_+0qlJb`WjZo7^@wdwIg5dr89Ldrbql zVF)CB$dq9Sq-Z2$g(0ofjD)cX>Iox~k&*?ZN$>w{1j0$``TEP}gHckS$wd5rnM{Pt zKt`7Ts`l7BgB+b4|4HqsR9#m>QN#GioV0cXOCoTUFQeGai!ig)s-;qpV#pCGRPnHv zh#rkIX1}sA^FDoAc!$a~$A6tUg+OpF@>eqDEU|rKf3bDqe4DyLCM+0>oa%Y*a(wXW zzJAaA`{x?p7yJi&yaAc1s8i%Tqx()?$Qi7Hu+M9>U@#m+*myoMMOAU)ldUG+AF zAMKCEPGPz1)HHS1%PbMgWfp@C5pA5N=|dX9Ik_r7ga|TQ2kb~JH6fi7xu}zhu$#&- zgHKzQR?qxTX%@yn$S}t&)Y+rsq0Bfmsj?dE3yY26W!T@F0hJXdfZX*ZN0+a(fJ~0v za(Z8#dT~M>E4!Y%no_-Yan)}qCeD$IgeM$9Q-FXIBgx|!Iii>x z5ZMFe6p2tI^kE|27Tm8J5iI7F$Pn_r|iG2GV)jhHlw~xEZjR}X(34u*(oYp2!LktRs&z|f7v1Bygf_L|aVMU(r1c$LzXi>n3gV%H3SZi}Ir zpEIgs8B`)g8qOO0oi4HqwJ0s&6)7lp0K$37aLo7(Plfw=!q_@FGq1O8W9ekpbu#8P z*o~b8Hy_4ol~N-JmIHH^X4~f9+^!8BM`lhcWffu72DLx^74q_Joodrb=;SURAD8<3 z8B4{&CB~NeG2SF3Ev4h~h4+OATWL!E{`G7%J>N}W88z{Uufvfq;5BFB&nWl``S|y^ zrXcVrPg429M9&MIfLHYxD)GndS9$czvWPiIw&h%sP}|hF#HB8MgmmM2dLKkYYo{{A z=}_B8n~xr}bsm5sRs5{C07r%J2}8@PyeiSOFFbirIg2)tj}T*qXFgp7f4&5%M-X9` zw_Bd@;8M3bGfrn8^H7@2xiSm79vX6(8{|gPB7B#8d4=715e1C`qKPauy?GMd!~3oQ zzU8P;S+_zvK?pV~TlbPf|Exoyd*zuCIny_lUs`e*yu&srtklERCwp4gHl18{C8(qx zbNrTy-1^G$+CcPKjM)OWF{~+M;nPC?FOO!!5{e7DA8t&yUq{0Zz%+;1W_j4&F}#gX zVi<2mg6Lc91dwh01nI5G*Jj0Jlnd?%5x@g}P=u%bm-Z$O zTCA83IhIjhJxmL~GH^w83^Y(q2&NEaOn06-q=5 z7C6eET5yVaDt|fyNRIowy|ma6fd;`1_!pPE?=}Wu417AG1OZx@UP-;y8-!@%m|oHJ zNxfEHVbw{q2KzZtgxL13h1M%WHBtF&(W@A>v2~0VMoVuVlS7cn3f3EM|7{9s6kc+c z{F62P{)DZ<|H~@#UoC`xOl}H;Obnc@oFuI6tQ`M=UNaT9#{i!zuD| z8dNmGx>y+*pNLJX%u0GOvYUoAn_QdMFRUZ}s_~W~bfrCsVIE&lf_LroUrn%c@wptO zU-NZ&LEa!{>?)WcUj<%`23nqekBn-;px{Le30s&NC~_TfHRfdCOq8o+b-4`lq7SaU zixrk^BWU(+eHviZ2NZ?}s)?E6v6Orq6p*#~ZPF6<(t2B_A~b<$kq1 zTgg?HL35Pon%LwSc?@0F8xIW8qn3S_v4jPt>pdE2e@ra|K2Amnk78s=2+oi;M|6 zhI#_!fa`9+Si>tIv70?`=i2{N^dK~WwqC4V{_+Qss5PS-Z5!imD!+Lvk96DIQIe*< zAksB#Ia3C9vPFlN=RCJP==vA+v@ zW|TQagH@n5R745G7-R;ap)^$a(IlyXPTvScH7pNHG(f63V6n?Hp$gELJ&`wQp;cA5}A~br!@uOo%AkKYwsk81aieV3?EbA9~_}pFj?&hY4>dy5?KFHOiPpFVHzkH+O5nY;om2&2e z;cBP`zP55^hM*?M1Kjj$G#>?k(Mn8vbe-^*m4sT18bIqt9q7D5DPOLQ?Dl6?fV3u& zT*Ex6fVzfOq8CyZzYI-WnOQccw?S$u4p1H574E?p|5Xxj}e!lzwlg~e)rz+$A zb(n0Zh-^MT;Fwt{^|p7`uNTZOlNOafIu{oz-%KckANMj2tyg?_CX6o8_a5X~(xq*h z7)J~q_3nSJjZ+^2n5Sw}+_{xx1Zm<8)>7#9*dOiZOAZ^WeWfoA+{O}N_b5}*c6i)6 zYL!Y7wTt85&L59VqRsj(L;d9i$VegXx@CWdLcQ!=cevGAQ|_@_3CsZlh|b0k`n@%8 z7p-4E*#XTXp@rjYetiL#NvyK)deit?UE5}&BLM+n#^$Ar8(EeJdVe2$DDWtk z)J@g6r*tuA3#YIJzpJP+Ru|z^()*^kqdne_z@A9GT`5N3u4FXdFDrw}%oZR4xeD+c z#3CSei`Zo3NlU%JzQi}yYJoV!nA*>*bi)<}yxtVNWU*>cJC!nN;KB`#89H|;3R+dI zax1Awd^G`78)>dKk<=IszO%xMk0&dKAX${E!Kuq80~Pv{lch!MS7we*RPTN#4xRi? zENRlxa^D+Qd$2Jtv!3H@l#QL^w3PjLo|G7rU2gh=(PX4I#U)a$W}wL0w3&`;afPhw zmyFk8EOx^yqpQ>mSurV-fWTC(D%po{i)5PXV=7w~cUVJ}#j363yd}|SS2UW!v3?Wq zu-GG*rh~-@yW4)*zrdt9F)hi?y~?goFw`NvPCC!*zTn!fQNLU5%VpCdsyi3OwPI+# zo@CPva?3i{z6@|#a^ObODw^@{pQT8%54TbMo+^2mC4aIC54)Ph`rWf)44Mg6ho8GD zct-#DW@Mg|_x~g99b;=@x3%Hgw!3QEwr$(CwQAe8ZQJdtZLZo_b^G?&NX~w~oG&{$ zzh*Lj&dl7`7?*~1!r^<`A_Rw3`?Ywpj*wBs2Udon3%K3xZEhg(N03#;ub(>Zf+n=( zTPWl}lHrR$mhgWR7fXv+gkN$~lfRN9a?2Fc??+@#h|2XGo=5p%WK&!D=WTgQ*KPNQ zwN7$K<%n`g9f0ZR4umZ+eahFZ*u9Z!rCYy#n!l+Guem;=%`eE9+3G*QyTspMu81yv z1`2~9R|9MRobgx|ezRhCAa;o2j5Unvyt6{5k#U@+cq=Yrh+4>?TO|3tJ@6sLi@2@Q z1SZ=MppfQhN$zZUOk-XN*1FTDf#v0b+98~7hT4(Mj$S681SAa&n5n6qIJ9Nl9k2Pi!tJ%)_hDCk@3u0fPE7lTS zoT`~juo`pHsobR zKU;qf{*EJ?IL~mB@A`p&VTcoXPJbhl_kEUTf~Ci{(E#qvY!vt7DpQ%nphRKZ2xyyy z`^^G*!B8xZ_E0hAqG#L1o(}xAN7~ol2C88;D1)7ETlWP#0D%7g703RuybCxxf3NWW zFmDK%8(3SLe3!NmGZHiW8`-R!kX0~zWj!HfgtRQnH|V zj$?a$eL!|$deGWz**T_GE~|c7v^=EZxCt!Wm!zzyInMvVogA+*Kfv~Vy))J9h%6x} ztm)E;%h@@8!*SVhzWC(WY9J04krVL>KmdvjLC4u)w3)KkF5S&+d1!hW&N5x^akkl- zp9iI*yIeEiNljn!!gr!d(E{C&7v!-ilYS(o5R zaC27BU8eRmY^a&&U~{d2w!TI_e^`R*`nJFeEJ81m$!t8(L3m?tqzot9<+*8@y&V_T z(-Sn79Cw=JMm`;hDE0h$RP}Fay{|y<|LRQM=z7iuWjy%#BX2vGsry92|IoG%q7ZQp z45Uu&$Gdu?qk{f!F92nfX%qR&8%ROD^5X`q5xg!$KAa6u^Mr^$_6)MLKiD0{ymnFz zzfn@fU5c_dP`u?4)CJ3>h?zjjg6yulwbiz(tqzj7a-}dZqtaN6xL)p(Pd4}vjelTr zwlwwgW~-&Th!L^0Z;;F^aK9IdXn75(Bn0UM1ZkMQAyGhhHo=$|uIpwME8Ry_Wo8vk zhYc5rs2nzpdP+_`F)sTbYSvM;ti!WSC};u)k3ME@nr2zu+rTjBCbQxlK2`=+mUJxK zuJjzqSojz?7&wTgVah%{Q8wA2+2EjmfY_cyumchgrKlVvbLvtf0y?!G1qadW;Y|DV zd>T(trfPpO&>PhjqtFv<7VMr&7lpm=F#-0F$9g_?$U_(T)`2WD-R@<@iOba?{jKDF zHG-$Zo&^3&s5ZoYLEkAZIplsq-<}yrdf(k2?HwDlauhmX3~OP&5Re$&HJQ1(D%)a< zG8}DI;~tPc6TqpwImDfC=QfzUtcx(?Lz>a#_!!0PlQ8P^Pi^C^ARc!&CFWw4pJtQ6 z_Uu6@q96eZtr)RD)yb9;lSj=zwv7VmwIzT-X@@I1b(?2pRFM;+^vWpBKKhx5kHOy3 z?&=AKekpxa1;zEH+oWj$26?LhyWL||V&GUvQy!SDvavp+4uJ!CDHfiN_so3?w1kyr z!5xtDTK0v`4zfoPopGN#{n$YsVUe!XTYmA0jkPZO^4~0=yb*+bv;?qP2_Up=YqzG1 zsP*rorn>uVUV5Efw}7-796RNJLCr(V2~Nw8 z{1Qc5=r0WFZ5BL?#RdB}fX%#Jca7k)=|N!< zab8kNPD4tlE0T6o$hGl3#N`DxsrF~ZAi9XV6!_93P4clyO!GU_1nc7N7B~O^)psq|UsYs(6@C4y#OvS9Tz~lv|HDA|zb;AtKj89D zXz5lvSHfOF{n`={ZJ~~mMgkv|DHv)%+^=W1Ib$bx<=<6jMWY284Jx^^Y!3@ws%h{d z5lUpWy*JN1`tUs#GQG>s-ITRX^DHrWt!K(Uc z=J$R~=LdR2LU z5Xz^vUxR@ZAS#2=lAN(3orv7HN+du(5?~bBLJupoWHU+&Ik;cbAx~QtLegerQIbK^ zE@C?sV-fuQgtqx=;-yn*bQqsjl=_^-7xnfkPtrEan!0q3fn!5Ruc0B5$n_iVs7Ln; zqghM2Q2l8;196io5&siq1D=G{c-l>9F##?&Z)Yt)WmO?zS9#D5S_7?Vbn-#V4}z%g zu5U#jU~XbAgOx%c%h+~{Rj;v!q3UaI>aOL=>}t#O1H6-t@%{XZ#-;G>%DgKfwMe2r zt%jrVR3jwDb7NpN@&=FG+z4VgqBzUqVYsE>`l0>PBREJSwkuczbWThlMbHql9=7t!%~1>?UdLd=;=FxO!! zt?3;3t}tUp&XG$yeZ%v6HDoaN+7&;!(bua;nLYid7;vnu4ZjNVd>Vr*4A2q5ctEg- zpw<2Hx)t%?sGc9+o*$8YKboH(ci?;v7Z^6iE1|Uyai;PXm#HUfaGaK;$C;b6#w9)m zwQw7Qxg|S90qiW&Mb&gQbJ!J!D^R zP=1hr(lyB4lS(Egv)*SK+=@={!zf1x;U{-tA@_cyN{-^j9iZSADS!4cF}zSmun4I= z;G*o}0{6vM3mH1lhA?ATzVIc=WNe*&hmR4^&Kuj=7o1V1PDctk75M}KHHqhPYEimT zF-wcG+j7ORvQ;aLN*_{&(3nlW+pXSWexa^S*SI6V*O~XIHykDbQz7#c4aU86BoBt_ zqRKcZB8_!bIWty!h#)vBR6%t;NSAqOA~^BI8eq;T9{WQi_8Mw%DC@4V9Uqf=gnd0` z=^taQ3mpJuQF&$Gus+y%ReS`&)-89`!?j_&Z>%(Z&Qr=M5{>oexqd|TY=3C?a;`Lv~QzCn-3enhP6TzXaQ&lpBHc;!sW#gQZtt;kge zcubGe=5>Q3i~&zbxkIOvs=`fLJ9xxDR_Lt4vrJChvSQ4=s4R{^7gP_SboPfTdJ9{*MoY7Hoz#^?xP;PZ0w8<5YuqKxYh4%{~C?2zBp7pXnTnwOGo|Jzg-t_F?gAyXX$m~x&eC$mXAVkmk-8?yrFh5}o~Z0(fXeraxM%p5&lAR>oY8}u>oomDV;Gsu z!7^j|qOjBmwUCP4>63JNq8PZ_8uWODK1Y2}4$1$Vy(gly^sPsr>^j4{@pxTETF&iP z)vlReVtR0_xwY8tPLVi5Bblf<0B^}KaFIN8%#V;l_83~lLkQXh>Zue-e6mK`gJWZH z7zum;35&7#hTJjYlJ?Xixl>3*+QQhdkA{Kx;`yUSXW{*M4`j(DyqPdmjH0h*%IUd@ zcFO5&^_wAaNyCcgw4=&^H}8I%h}z)}cS^yYX9GjZHBlsy4jj8r8p z@xQXr7jox%;R>c-^~pcqIidl(qMy9GHE)91@JCXat%P}mb$wThgI8gE662&|r^jkJ zIEH@DUMfnHYCr5!j=wy@XwE;&V9^!dGU`eksJydA@9aH2G(@i4gkpkdcU|qnqT6+- zfBr2zB-+}|4)eY8$;15jmi+%!sr@JPHmh!ZZ*?eNTQV8etodst-c7|nB#SM8t@%;% zy~VZS7Mu7ZlNC|z60QE)>#l4oy4bmI$xe0)r`dTQ1k9S~84r}*@8^0)9k|^ZWj0Xe z()*gak2jrWUb17ouGT(ppK5yn*@Kb320F!fF)Hm~Ry9gV~M z`B#}K3dCXY`5GbZs;BLSTJbO_-U_(w2>ST6oVLU2NS^_dG8;^7He9}WmzYX1d@8L~ zh+s>wGAhIvaI{=KChH^fl1ub9dTp$&Q302oAo?lfbHsx=2@iOL+Q+oDP>RMFFivZV z0r4~mn2?J3V~VT6val3In1mYb*Zip= z5HSHdA$8g$6CGT!THTYvx-`69dsi zl?LK1?|XhO$y@Fj$0~>BNjI7@t0o~vYi8V)xCOI_!(K3>#dL+k4H~h>8aZ((WSFmw zMl)*MB&a*mdk|nGZW$4cW8K3HrPKjty5f5<(-UU0D&Y!-n+2_{#~OfJrWo$q7X|f$ z=+p$E=x)O%kdL_wrQz8FTy4aqGvq>{2XqtT$!q0f^luH@ergOj4T%HCCRa`g6idkv zK9OnKhK_*1TBI-NC7Zv)sTP`^oj$e|WKI>BMf7@0=V}Y^EiaZZZY2utq-}6qCcY~s zd)6C$uZt4Q%rsWa-AIe6d@M;b6z=A{564>>M6T1s&(BIO&KBO{!*zM)N)N|tlvG-< zW(hJ+mRUHZbCe&I^_oI|uPv48iI5__(k8ef&4kGz*Gl<%>O~4+>gXv)pWc%34KXq@ zozYFWy^ElvkyaGJ+#>iJH2JKupLk_#BH-$kC?uybP_aIp(>6bSo_1VjswmW{NzW85 zcQvimpvWJ<7DblOl$qnR@pc{QcAi>2wbsDf8J#(ORsv!3lkqp69j}FYygw;{ zY2l8SZ#)NQa5ahaB@8Vt5^9<_q?AqPzc7TH;2E+wIzqadvLkxs}z zcm~veMAd+~#A8~74K4N7aml_-Ny4#GafCT&ld4Ts=)g?eNa#=80G96*eeMD7(oiGN zB);o(Y*gNdp~#c#7yNvZCq_#ImEK$DP76J6V~Dkd=fNxCAr5dE5&9)m8_Em+fd}W# zjz2calLeVZ)bF`4`1bX;u4BDM3$(fK$JXGRUz7j8u0VgqqW+~5{SQ7(4O;}mmsDF( z5DJCjk14hdH3GTTI;A>cietUSpa@WPm?qmKGqttr!WJw_ZQ0H{@F#HWsN9kf-MSOX z#|iwcnmaddVo{Rdy8Jr4?`pc6>Gn6H)|L8kzN6;{Y>%b~t<9KKb#(43YRaPZN)^*t zbjqPLd0MGq@fP#jShaavCbB;(J>jasVyujcspMGd>Fz@Q!MTYHg5XBXar~BMVHsxB zX!h55KsBkiH`%4+_-lXI(y6fTn6H5G#n>OO}+n4zo+>W~$!^D$H%k4)sT9>(a-{Nm>0t_a-m|qRBLm zpOKc4mM$9=t1UHEL@G8CDi#8&xjxy4BZ@8?G8=B|kF`!hGY}7Pfa0kPlY?eXU2fL@ zORGoGQ8lk$&ptaD(o31Gx{C_*PpX$n$kF);>>dl~Yx`EHF~YHVs4d3)eSvc<*<9Ao zC+Xk{Bev9VH5|K;*;xP3$4`cO{2>p2rcQaJA<(0>l02k3*P}CAO5gT0j1h635PC}c zi#>|~8>T`n3ehs0c8f9Xx$#J#jQ$j?+ida7$sgkrE#jKGZrYd@R7bvA#={TjgxI7P z{lzqhiHZVQ6sV2H5Rg%jAceLBM<5xKOQ9vzUIJZ%1-5MoU{KoTwHKbl`z$&V;@qtA z3ezKBlQ3F*Ua@!ev_lt)JY`{VeQUUBT3{fx1^zS7PRgvjifO8UW*DT|+R=uc_Cmo#5$GKU(HyZTg zSVqz4uRnEW?;A zFQ9g61}*1YK^?XfIn!EC@o6?a-slItm32d8IN0(Z@Pq==xcEl^-V@!Xn!3Td!gFFa z-PBG#ps|`3Mkr*GcoddEiA@JgsCQW#9DdVuV`E}s{X8W4bh$R80tjo?;Ya&iH6qlq_NlF-&J;1MPiEfbR-ROH&qz*z- zM8{+_x2diX&}9M_9fO+Y=auFa(a(#Z-Qw0Ldu@e$aeq_gS>A{MZtsgcdQ%m9L>sZF ziH?U*dTXS`?omxU73mr}<2|+^rXSS8>yA)4adXs!lws*UVsMdhHyQYQ{^C$3sY~lq z1VU?oT(w6U`)(ldvfooE0^bq(TL(28_4Itfc; zbXIJ(#O7IT%or1WFG7p(-}AG}>r1Z&4}4FgzsgFEw{?Qz7UTQ{p0e&Yyk_2Vw!Yq< zYQF&NA$yJ7NzBj^TP&?Dhl}#puG4>K7irEXlDO2CVYX<{vppqDu9w)ZX?ob&cxrSi z%~5j!j38{Qx-A*|!;zu0wKU`86WKJISJz{O}Rhqic$?>;wsUZjXwNi%TSJulA4c}U=g^{@{onfk1a&pTKwB}^Ipm_ z9cErM(!HjJXpjJ@zDDO% z_j!Jq;d>LjkAVB!OdfLud_F2&GylLOj3O(lAbQWh@VA9xI?##WfqnBw@#OYAO^6y3 zStW}}t%*`&sR$6J?64>nk0HcdN^v0jxT!U9rlC2R^A)`|)Ha}Sn=)!c9@~r&&~f9_9s9-Suq`vw7%aFY>jVs_UPWvt?AAfMy^AnhR_mE!6fZX#B$bT|2&709lYC znITYUT(y>M{!{zoaE8^j4H64Df6o8xCuuR55S!hqsS#T&()BJE^QtJgFB-b;>Ql^0xE*CzgZATsnPyuArTz zc4h(XPavc8<0m75ay2N)z2m3A2fE-5+yW4i_CRwgg-`V(W#3!*D<5A!#SH!O`v~gB zxsYS8H>j^+vz4v}Oa5Jp|TzS!Sy&x{LPf3A&rnh6)97_w6HxnsV zeQP(7;E+!Epjx2l?NMML<=Y$~T7oz9-sg5^oFs(;7;)|2vd+#~^H_R>(C;1`hwB~- zQdnJGq)MM=7xy~DO~aUea4~k|(kOme2`++_7QaT7N#RjnpGk%oS@JgO|sQ|>yo64e@4!VA;UY6_I_ZP z9vQ_W!n(fa9huENO$&mzxFmD&aR;V5kjWB)I%bGg&>kc<=tbFS82|}6g#a2ksgP?0+{?NUeD2Y zlWu4Zt?hj(Wt7SG#hDeHTXYr9)I|zqcp{dmGI&+z)Rjbp9wTaS3&ZBf#O4)-)+tWA z0O0q-<@WA*Bd!k=4KH`%)Yi;y5^qpyKk;~Hh~Qdkc?W;9{60hOx}7^_?x;J58E|yZgzTy1KOc0pX$@HL0)y=+W^lWdA=8w@~)S&tZ z-GUGI(5qiiVD+Ll4T7jWB5eBIB|lTO(YY*+1s)3@qo-nQvYRcL#LhEco(IomXvBIq z_pBENh4ilIeahvxU-p3Vy<=d$b`&0A<~0qSt7hInc6b-Bq;vNy^Fu2a%-T48LcTv% zvw53!v%Xb$tsC4j+xtyoG9ckvQSWW9T>c}MNnvl^R6Z{JTTpG@4usoa6C_Rd6k4wX z!Xlr;tTf8jnnwkfVG7gNoZK!} z4BQQ|-9Fy6J&X%7ltg=Mh94ZhQV%|YJEaJ{xxr-7UKZ~^WY8}|hxP@q_y%Gd_0fC- zLN&5l?6(KxXqqCLrK%92dC=a`4ADfXqckxZ5nAKwZeoI2pwH17(6*cDY5(Z8#-T}X z56pc2t<2*YcIn*l+XCD8-8Cusf7>Yk)?!+%H1lnQMfo&seHaaB`N>EAjJ=4K4EmrC zB2gDqRFa3dAk2|wf$6ZmwPxz3`FlChFN`eKk#KH^8Rz3WO*la*+(oWyy8X8O*7J0? z{j2plw-=}_6f-2*bGI|2(q^wy@}yN@)!9R;gQn*Ix)_*lb=x5q&ml$2Q78yk16`@O z&(47aI2p)jjKw{)6HlUD&JtE(g<=(ET%`ParmBM;K0^SDbB^PWAjsbt3o~fGeV^b3`8ir-7IJv!!EvRI_7?YJgvrR(w(NoB>LvnJ3!I#dRc7Nru7nrU>d{MFzl zc_)_f*LybB;Emis#;kXwm2BE?H)~I_UpNv@KmA!C{m7!(q?VU)20wJ&hU!PCM<*UT zVv<{4Wot9H{p?~NJMNdMy^ z^oL(rd<%=rUy5}k7!9c9L!KM8;Wr$$9zjvc0h~`2+({#`{o!Ud#l>_J6+}c5vv+%i z`WTc{Bm)Vfh2nAN)*zR4w5r7hM@FWP81PO_y+N3i)(s~{vpBX1#~Bn0JoMe6544zQ zP^N%B_J|^!p>RlX{z6amV375%hn>WxhUs);J#|m?cc*Orp<*D6^?lAbJfMdd<1r)I zoB10NTpW!Xdc6d>TmVK5_0veAd^oLRPOnAOHAJx+ANy?LV<@KVa_vT$b(3SM47wvA zv~l5)CfWnPW$GZ*>|Kz@A!LKB;u*lcnshu|84dD=fGR6D&>SMhVW_ns3~RDm4c6fO%DQYye2vj2fiA2BN_uMObT{!qdpng z)zU=estSfgIUJtL4@Fx(K``zQTn-`>T#*DEs{EU+IN{-<##lo&3>bXM`N`GqLq={U z>ht+LRSyuSPbWl(qcY8rnW+|CspM+2;bJ&Rl~Ix8QdWTtw^IN2by0)o_HK-=-Xav} z*7#YMak_TwPlht+^@J2k1pc$li-3RRhAuWRDngy+TWGif5QN+p>0yr{g!~LWy;0rg z>tUJr6phByVsch4<3A}vdK@OFOVOOomX~1?OPLp%aHbd_I9*z_$;}%sI~x{>$;LwWnZu?!8B&femH3%dtE^`m8ZnRTZm!NMXhOy6tS7x z2!#w-xjXDQcQI+kQP6 z%Vx~2kW!~vJhz{Q$bzw{h$Vs}H1HuL$MN$y34-9M+|4szM9d;%yvNFYiecOWNM~88 zMaS<41=Wxiy~QjUQW0Y_UR`eAkcc82c%5oc-QA}(uS4ljZlTkZ?Wb3QmKshHxHQPr z6nKElcOnuK`vY}5YyDBwXRx{eP=Z(&VAX~o)ot15kqj#cf>44 zl&P!@H5)lRgn$NY&JbzbYXdvCeft%bx{M715M_JBbw|*$&sGOi>juwFv zr?;Kjsd@z~W#MXV#$6@3rO1hpx}@(`7g;Ps9IQieceg=59K%vxal?Lp=BrO#(&DLG zQ&QnDqhwyw%9x_MF_yV&qH&iOT1fn|TZ2;r#=utQQzdh0&tuZO@X_+}s}dYga#KY0 z&Z|#+ThLK_Y;789y*L# ztffon{WUaP!{1EPtiV)>c=cvRA-A7R{WV{xHPTo`N{8fCq*hPPOlU~c3l04;1CZ61bJ)-pt3e;^B{Hw8q3G?C>BNc`ar z&cN}OT%T2P{t$PClwx)z6h1(`>ypG+A>VOniuaAIlAy!Wnx*N;A?^=tPtYfBPuNB5 zvq{j}G}>1zk9v5WBR?q`D$nOgddECFu>7pE>N-18;s)o#N46{KO-Cp zn3vJTs(&)x3$n;)H@s~LX&JbH4PA1a)S?cOOA0@SQjZ=-`ve2WGw81ZQLAUT$gKVF6)E}dNQyHr4RIy+#Mt?XdgpAuCMprsR8otcP z`(VE1u!16Q$e;+P3Y}K0al?57M3kCq!0c;`ISO*dC_5tO#poY3Jj%l%)3DwfD>@)T-?O7Bv{e5{MUL z8-paqG%6FL>ULo-mTrVS>KdaPgNu``&2INw+#bgMqBt_uD9tE@==jx)1I}^v@8o|L zF$rhl_oycGJ*o-)mw@&+|I^=_=8F}j+-a8ym-cQH1c6;$9XkyqPgn&eUV^?igC$eIqj1l%QttC^A0sgzN5XX&>@NvBP;JN0x=2y|>-!s< zuHl2eIvYqX=|R(R>h|1XyauzSX3L6ULi+WYbD*c~wG zr|A>7I8=g7xbsojBoYQ7g+jCX z3>eTZt$s9#h8>aVGpUtSm61#H_b80?Dr=x2YcdppC;1;MI#}8f0W;7B{@?;A&)?*T z{w|VG$NU2c;zn)IA-1+-hJ=;PIWdxwm>{mczRZN5v$8U=UEqV)+NH5HO=Wt<(q&pk zeimXw$K1wJ>&RQLHMZ()>yD_46OVk=VjOn8xDmRW_I zV;=b6haUn(xE73%nB<(I94bmif`MWCr9>&o#C8iZ2a6gto|I5*(V-ng$XvuhxMimb z@*ZU=7D=4m@f8+4E-HLNdBbsd-QgWM5(ul>yQ?}n9nPbj_pI5aH2SZ2O@{SiQy3*#s1>tbGBof5i4)G{6X$Veeot$TF}$S(lb$8 z=Ba>~JO3rrQ@3q?r9Kt0R>d<*4;l@>tJUC6HYw7;{rGM)Y>sta$wdY0RN7E)z-qpX zWl9)$ZY*z;W?H?@jCIasddW~NAe0{$O}uAVNrP7r zK#?p=0m=c&1a*XxghFjTMmUJN!=zn<|4N{Nj>nPTZ5GNpC3%K3dh*2JOY#EhsS`^4 zwe-78d{Z~v;PF9Nsn|gxdEP=r5#{%V8>_D;W3BN%X1TkkqVbc72J`^kHXSU;hbJYXHMhx`32`zN%saC z-=N*vB~9nKBR4_`wcAc%w_CUxP49{g2NkWFbQ%wxGuLzqcAK%rEqCsAsFX*F6mtv+ z3mKirqFDgp>}YAHKv@XJbDgoz(V0})On7o#Ke(h1 z(9Ryi)(RoA_O3q&TR@~B-2u}4p#c;hD229Y2s8IXS{=Y5@5txCl9KnQ^ezg|G95T8 z$KAfFrRcog-no+a%n+7`D$V>Z_QdeyOi!eD=Gk7y`RO5F-137u{z3}gwNUGWFn(i7 z&PAd5sUtocwa)25O0ifw>X|sE@C&R1!NxoGeB!UmRjEx6e+$5?5ztvsax#!KRDA{t zeubZ24FGH)>WUz3k5H3z^nbLQ`oTFQ3J*zQcj0c`7@`?%j1f^c3t0>LhxH*lVn`JN zxv82;-!{T9%dp(_XLD4;hAaH>^{oRz-NTw~$^e@H9swMGmIJf{FaaI`B>_=`xIdu% z=R>e06QVTxt$b&GE8nXBjCD zNs6HmL40ra;|r$^FF&vA#;2G2+`nRya61sv7_zGOE?h*788jcLqdM~sS>?YUma3Y3 zMjaU|*M~~R`KP6&+}4?zcg@P<(cWJbDvn*N+PzqmV9@Qb@A{snWR0|lU3q7(E4ztLN0-N zjRlF*-{eAn9-pFV&n7!eZyQ*7Tgm^5lJLv`{Rp%4|5F+WQJ|W~YVe(cq`T2Mj!~wy zd+x#A!reT}mhfPr$h@h(+DZ@69E#Jar20Sk|NOKC$p0(wz-eh_%NB zTBWO_J3~`|)ZQ#q888=4G~A#@xRh4?V~Fu~BM8A_j#{QV211(K`y1peY%cDo!5Q?d z5GAg$XdNv~Sy55H_aE~2Lk@_ntF2$0&PFQ8ezN1QxU5ODk)qm}^PXww{gA%=yV-rI zhuFG`G}H}=G;8LRA3*UYOc5T8x=mVL#;xp^Xgk)e&yB0myf`kKQ7z;j9BORol+GKj z-rZ@eiXM~m$J7$da@?0`2bGE3OU+vt%t4dTC3_a*uy+;s!qRy(L?8XP))-V!pqH>lTB(s z-}-mx=cvgZpDsT_D#-Mepg2M*Xhv)Batz>gK6esy90AOV5dsm=DE>7mjg+Y~)s%hm zU@J^I6=4ry9%hG$h&S2%9;0DH0}&&c9eYibGs=R1*gxfO%8S7btpT!XLQbU#$3g;P z22zBmj$i;3bBa{PjFuYETe!rYJxNM(`}Ri0hlm&J3Zz8gs$Rm}X#5%GUg#G$`yC=9+q3IP5ev@3)sE&ErL?x!?UMx>KAUvG6YgSJi}Zo;@aPVJ0cx z_CMVUakuBRn$wzcsF9>I^@Ih}k>wA;<*k_~GwSHm_Icd{a-EzBv*RKyO%BHN(zixN z3%bml@nZi`mJk=q^Y5_znZ`Kf+fcpzd`mHx`FDVX!v?1XcJlUF^fUuBG?^#K^{$3M zOq%3QKK!)V21r8%=)LiVp>UVt0Z2yXOiBgg1$^HU7CRS2Zkv#0BykBR(`d_W7U8@- zFh%{b{QEG6QvXZkh0!pu`Cud^p*SwFkU}2HB)>mr(4g@n-)!v!5Ls{$~BuR8gN^zYEJ|wXKP>e%T zUG$Jrv^d-Wxkt;W^|E4fQ%mG$QikQNL2q|k;J71cp>3o$m98+K52c4Xa`2pSKwWMW zHSPKxAkp{?xHR0KoTOV#wba!T+IvH>-cCA+Mo+N%zPK&LV(9=9!8> z2gL~sE6c0#>mVUkwKa^@Tp@^JNSGR$7R*<7F1Gx6`!m_nV%uxedU@5-eA$B9)LiA<^$}&P^U3$9`|Yaxdi#y^{jJyU1^Xx7=CM6_DT-pokC}+z zBx=i*??7I(Y;WnQ^K{=<>$>sv=863&F8tk;^LgX6lLze4Nl5)V++jiG|n2 z)dy4tv9ip->pEhobp^P$bum@4C@T_)oXcD+?GK%|rUUNK69^{BZcQ{v!}c^v^0Ta@ ziHeBLnSLT@(D*u)i|trD+lhTsMfm(%zX|7wYTO?q|s814SC5@ zgt4S>+)e{&^8@*+sj6fbd0{9d>0(i7uBHL#odGv_$wJ1}(F8$tWB};A!~HBQ7z3(J zO!Y)TV1ef_`E$D+g652E$`CYQt!zQKV6AjPY5A6X*>S97)bg$@Xv{T!4n=3F8>_wum^dqxkkx0|T3Krn%PtvaASI%*Y)p)X zPq{SdfxU54Vi1+UFN2&{z;ZfDMo^bpos$;mw+q2oomD$@&&vV8PW!ks(o&RiVJQ4w zB|Z`VM&u{Zay{)*PE)Wd(PM=-rIU{~UiRz(rFNAOmZN2=Bj49;$DTkF3O=Hg zcXyVs6?M1mtFqdywuYFSXB-yXe03BP%h2yr$W363tv?cZW9jO2dc@;phpHmOl6=F3 zfTiq9Gxtb)1{gmpm370_ms~`odS!WBK+Qf9@>Xn=s!JrETgw_r4XK{c0C(~`m;g7L zt`kCybBK}sLWQK>X2sqffR7e*OwI8Re8=)_hq{*JA~WcT%z>H#)%G$nx1HHppH~6k za8OgvLxzf&9hkg0)D{kg|KwH9p*jcS(}A7Npl%z-iO&wRAy*Sj;N3^v$=TjV$D()Y zSlH4d?|IN(SUMJOA?mFPElMq#DfUWuB!Wp}EmCM{465>aY#c*{Hu&GLlEQu+v}8Vu zvW^}Wvi6LRux&h}VfSv0T-Q0vhPWZgh1_>-(5JJf8Z=G6P*2-AbPa=2)0#Wl4tgt2 zo3sWQ4e>2Zg}SkLV6`)|y&uLimZSPGlV|FHPa0j=K53Yo;A(q+I zLD|BELc*v9xNxc~wR0EHJtOn4_|ImYE4H}f&zOPp+#5`+HV)Jdp zl;_QSEGIoKH(<%7naof|V7gHQjpzP?t;8!}5hl8zae!Uk{mH_qF9W9@3EVOW7+GCk>`%T$WPgB%8<%&V-w0~PeLcFgq zgOqSv<_3A{zStUc<-XV(l;fVHCwPr-PKE9(WrQPeA;tq2FCaF4Ng&K1T@zD)c|6t) zy&97`UOdTs?8f{hGANDHC$JnJ#x(GY9ZfrRx<0PMB?ff9(23exH`_?y{uBo~S?^f= zr~7-(vue(7Dj#>19jnLDfI#=$q3$lsiRf6DLo@s}aRRZ3W~GAdXP>5SJJ6xhzzt_J zp%IV;jknXKeEd*cl;KKLmYT|wu)+f2=7N)sh!{>vNmVLKO|`kkn27nMh>R5#=U)m6 zZp{TLE~v2VlxO9tO+Gpzfia^yTGi&qoDu95i3cHsS7;`jldtgm+1GvN?dW=Z32&RO;^Wm=8KB-_I-@196^A3W7r|2I}U|5OUsf`fT$9@fFT+ zoecQAzGF(fgIjzEV?~K;Z5;#8Gm4b zPmV>qcmSG_2GIAQv3Uliv(x((@UuGeh;OSx7vb3V z9d$(GW$wUTAr_YAhsN-@OjXq`k)-d|Z=x>N7l6okt;seU=F*LLI&ALd(prXF?2t72 zVLL3td6Zt8FbgH`L4;13Td$B+qlg_gVN0FXLEK4qCrrKy*>Np6iqir7$J>?XL|d*< zd?lcFW2h?_-;91OOv_a}^|3}(8MpxdZ-?~+?ZQFLmsMxlR6DwL^q8$VwMKR97Yajj#|Z4WGMb%W`EwHK+L6zNaGN+z2P&e{C*Af*P_+r0A?e=n;I&{w*Ws!Q_+h%lJ)iqNqgMqJB zVSQamOjn#}fz2@n{PA9W1j~_=Z0?`bISUJvWua0c0>6t` z`JlB*22@H5~jjc=$H zEEje4WPJ&w_Jx4E8SAZfve;_XREPr`Y5^J_7yU!p^B1hp8Nj(z~L8h%BoT+hBp zHW(=qIDe~@uB6q^2jm=?T5kU>2^!dV43;yXF#;CL$bAjVMsU9h6Eo9oLE{ zAnZ&?-=meaYP37_!)tl#9{2DxQ{_Dok40I|N%`o0-haMVF|#?=apmS=X5-*uqx#ic z1vAC$yla7mF9FLo&1e}HBLk;^!~?LxjlT4O#B6zH5Mz4ujj|Z;PX}evspWGK=N=6! z_qw4}F>qE9X`|N1+m3#>K45C1n9TSPHd+Y6cL($#n5ilq^xd+ch(Z*A#1-Q1{}hyN z1jXHI!RR@wZ1|==+w(JBmT-Zhz|1GD%d5VltTR1X9S7SdU7eFLiV6|*t}@R?gB)T` zgIHDwT!E2_q>*jy_yt_&DVDfeaSb&;I+IqEfwWK-@OsS-Y(;zG8+D6(V1=(1rajea5?1Q!V?rikWB^?p1 zyvj@LykIf*pXe@<&@SE{Jt`=;h=6FrMh1PWXa@aoE69c<%1-Z-Jbd~Gdaaa2awRSG zjW7X5LgiC9fO?G~&*Ol<^6d*6CHqs8c?J9=ph!8zj;%st_))$h{Y9;vQ0b}YZ3KQPC6 zeVR5&h;-4O&&E@|{{2v9Y~&2Q0!b7ZTYSR#aJTa9{4@W?<8){gF#%e!aR{ znY4M2xMxtvv0$pV@c{J#MyIj^w=5J2!t~%(WYCbE%Bw3OG!t9~TRW)S>i!dzzi4=u7CGEwZ3bASqOtl2FW3m? z$sQD(BPqi_nhsmE9U6p*4ntKRSoRuoxhTz~~Jy^_@uB z-j~egcxS~Yg^#;mV54j$5Zr7cb@md`Kt6tHZL#xh9a5C)p{h<-${eve~=D|6}8OVnNGi?d+jvRkXHe#T`MBbRaLpNlPG#&@~fd~36s>+V&lok z7vZQ|>T@>12!{>M#@PLQ(rjyrZX)LHUhELVrK|_lKjtsEeq$7l~Z)6U$ThV8oebGaPLT z8~hE=qlF**1{t^v4r>qQeTF&DzohYq^GSb)MWf8r@_iq2L(L*;RLr4js#zV`+X;3p zVoj`!smnHyzg}GI@ca`blWp@5Nv*xwhFMBkVXXZF&Q)SciBtT8eO6=Z*8t_EIvh_wg8(oy=u(uN3ErlJ_~z^#T@U)uXX5& z#oNrt^`hKwrs?ahxK464qlu~wtlr;Kc8xki)A+jW{hD<60J+@#`v`?ppGucZI5o3a zai4F%wz(loxEarY*9RX@FT|*RcpWL<@M4o8N()KfNYW={<8xzP>8_`nbsyfGH(ae= zp~o*rq<|*}2UI}L_CQQ1<4^q=R!Q!kYQi(Y6nF*Ap>K$od@gc*G-q}1D~jI1FdH%LawvjJ)Oloy>MH6Yb?N*Z%?V*v$KL`F zZY@=8^N2jhv+rj7(DAn0r6=6NiyN!M*%(Yolg%b~j{v*U0e9cv^3%iAD;ZMv#~eUO z)^%6JROyB+y7;<);tmxZon&QyOOPpy&c%q7*;#IXsq!;n4N{H^0=IX{OEsI1jYGk# ztEGVFn88zW^&Gj|;ejLn*f#4)x8w38=y2*6hct)xUd{8P_@xv+zy02tU{qJgLz95n z8}n6+bs@8i;zw4+E9Ns_lwHEx71Gt(U8%GoY0y=%J>pgYb!Sd5;`JnNYWMHw9bP#Y zV8S@==PrK$ND3GO?3+fe{5B=X6|~In9J6)vuAdCo%6{a2JRr0nb})3X?c_|9*Q|cO zx~D$>W5++0gad2<=$(54y>sRNs|n!$r3?S-3j067acy;NQM8Z0g5z3LO6LM}nreacO=mD2D1&)iGs!Ye@u?U3%q}0CEqZei&a!NlbK4nbO8pv z3HdNDpoO8{_}l)13|j-+SvC{A0~T;t}fsK3Y_vDO%NpU>t!3GMue2_-ZS zlj+VKNpuQ+iV8%60CzZ?&xcP&+n+9BuB(8V@l6_&M^`=6=d&ND>j?6mLp`GbYW8RE z;Byh#=o07Pu2@k}oJ2q0L0&B?e@YlIItR`KnibvV3O0~xz9084ha3r)ic| zfr!urSu^mBCVYKHv(TG%(-TYS*&}6nR3~q8$+F1{nON`?T$#SP_=6o)fzVVzZLyaC zRAp4wxsjV4+_JR!{p;uH>sUc+aq!~4vq` z2ehP^UTC`da|E#0tbG*<{D$1w?6 zK=IJc%X*df(D*X_qD5uTHQnCn%4V;o^JnjD%qp&|k~t5^TTFMk)!ULA`$VNeZh3)& z1t58-UH|2mQ@Odv{pFXJ_cvH+1S!mr2nO^y@@~o#jQaO!5lDUWSc)Yr zX7MO2ykF%Apd5IjV?d)65w!M3a`eXYy=kk!@h+mw3+ME%AV$-IyFi1UU= z=)TR^!YkINEI2v8nb()bAEYS#^JT;w-ISTuc}MKiNaX0C=LzTN`5Z4;11BgX?CITP z@6RgWysrq4IXAE@mou6_hhQa4VdVVn(e@WZpvGz3l4^9H9I`GqsS_5}5`hmbB8fz( zbn1-Lb=&ZidXrNU(WLZZjU~SJuITO<3E;I&jaJr_RyG#5vRxe;=|}pjJJgkAD|Dk`KQ+&ew({uF5Y8BAFxe8-l7!$yg}7;PJhbz5tpamj7RU@ zca!KT!uHUV!xl#=cEyGEb7ogLFN-0Ni&UplNh9X4A~HIYgkL+QvfpeU?O_>IJ+FD6 z|I;m(moxHa1+HFF0>w}E|MJxMS7OFV+WDU-CzUBXRB>Pk4A5@I+>_eRl?NY`I_Rp+ z;WrE7x>3w#*~FFCt2WP8GGb;=u}Qdi&K0XI*YPB9T*dc@p;b7#JG`Z$+Ac{S3WcIj zHhVdq@nqcj)ZxK8_3`J9@k>tr!1jy7u@!-f=&_ivK&0x_fzQd~A3Z7(d`JhksSd_? zUp#V>)(QYq@EgL`fvuq8Wyb{mP6Ee(y;w!ZfVh@}J|hh}Z7?BCmD|b(Q@I`=j))PK zxGl+i^}VQ(4y0c-D_PX!#bSl!e8wlLLBU)ZCp7!q^cst$S)6I=IrF4~59!@4)uLFOpGzGTGg?wCV&QVz5@xtH zwm7ZM16-BSfEQl`wGI>ACM$Yk+`KH!gte4`PKm=zkB4`+{YliwO3RxXUUCZ@gw!$> zYo=G&>WbQpNGEpej)>K1?9)fp8qCdsZV!`Q8NpG42)L?p*D5Vp^mW$Y5Q5naw8%;6 z5Nvl`!r3;=Ayf(77~<$qHR2sqo(=b9%f-tN3+LeaD#f{KsEOv5!@_!CkzohKI2<xQ!fgih+D_+=6g=xw+T?2Whe~7e zKSufA=@U3^maxn=Pi@yo`QchI8K)Mq%_O$MnvXT)n%SScXxq1#8w*29$VqFtp*VwZ zEMcmak9u&_XDh`=696kwEyZHF+B4CRqnOgOSQJ-?B=jP+6sVHfu!?ABeVuYlh_IG; z7~}$BsKe8qacTkyHZ$!Cw3*(KtYu;sh%yTPT?VJnlQm1TK_f%%KSJ-nGQcz6RVeCf z{_tOMaeNEi`J#@+0fEkcz{_eA}n%>UHyp|M5is^Uv7Um(0N{ z>}>h8w3aFyS}h7&i^nrFTfST95RsRy37j?;LFuFQ?pR-KkuJRveEnO+=0JzQS%T^>Zuz>5l0Y+fz<5j7QF%sohCr#3zCw?2MC6tks! zJw4Tz9sFGRV$y@0mu;#{IVeRP$?9_$T}+C=eQt2sKFg#E+huYQBlp!vw;CM*kd$os-*a4J@igzt zXU?eU$3&=i(E4a0vYZRQUpUiQ25P#709>((XGo*ZC+HL>WNH!$^*ALv5(_tP$sW`s z<0Iu&`bVQ$l;fr#%TNVWEsEQ#O7=urGutF5yCeoACVr)E6J&L#5kWX`1e!arNj%&s zK@Clh)aD^&_c=tvE2pJDma8ci+Zte<(!>+*kbt|ZHmYT1DwmN-f>B?{1;_T2S3COW zb@rz)O*9lMtrxpD(sMmAKr%zP?d zmYt~ZSFNt5T8kdlJ(j1-uQ@L^rpi!Mkfl274Kxw~yJXflsSO>L*c?IVc36MYxk+pE z9vwT1>AD3^kv)WtY9B4{QG{9E?dCyczvh%^ABFD8vn0qWcPqnT{(fAz{#^*CX1Ri- zVj)Dgr+VrhFVP&<+U3ov(D_ZZ^PB96blQq+FtGdWQW=p(?}VjBA>a^BU3>JF@DVf0 zO6kektr6!%Ku40aPdASu0Wdc+YF|1V`(00-@y>A!vbLw|+l6YO3woD`xv5UcPPtiq zdhPhMGQi-RxRrajm~lHoXCehF-Tg_876aGr*0obe)xNIpeK>Bh8EdsPhLHAkOy^O3 zcq;*a_fE`)lh%fA@WH{cbMnADK&m^b?={P$qfZvgm-nps+nHMnW)|KnLE~fK>qF|i zjf-ZZs+GLFS)a5W?qJFneZia?mwf%DKh-h64txhUJzHE@jx8TUKSQ*IVo)3et3VOB z1Q9&RA_7CGCT zih={+D7nTNO$}k34U?@+TnI*;FKdB%bX;g?a6<;U$ndD38#x;aN3JB>2!$nEWu!x< zQH86~bICFjFM9;HDSIYFdg>;zi1Z$}x;g_Pf?ZSD~W}4E4cv0_$mck$&+`FpH>b zOsbu(!yxHj!3Kd{{NRO02idHiVrtW;?m9#X^N1GBqSNxf*gX;REY2AcE!K4LA|Ka9 zof-z*FabaEl@WdWu43V~c~_Xf$D^2xFySx>b5HIy?>76#2AWS#dWAC$X+=BW@`3EB zd4dlD?snV)IbU@4wuQ~BhLD|Gf(JcfQ0~P);SOR%GeG>g$nHZjVXHqJbFhCP%xF;` zZ)d>>h0*w9{SZ>E&2OCz-=WkJLi|PJze)oh#j7|C?flJOgC0Bq@==V<71wtw>zG4s z@bDA*56vsm*VXl*Zm(;vEBjr)3PgKP{@W2R_nj-moz_ufY|_APpG?%H&`(}UWcRHN z%1obpqA&?k1j|Cf+Z#bohm6Iq^Aa&scLZUdf1X*yu8}ocGuS9(=Zec1P>`z$F{`5# z0Tk3HDW&NY)c6T4q12v3vc~W!HxOpuEz?IDYzzsHtWaMrLod|=-LxRd zXPXhtltBW;E!Fjxw@qylBtWViHK_ZcF@TT0(~C)3I1?L_K~1lPYp-_z_}yg7 zFP^>8fR~#)xiTS)npWc9!r^58<~Z-2oLfa`O3Rcu@4kM@Xj2?7@VnP9PH~m zm_CYzk3b05`I>Mmqlz|eah7gmt&~D!736YiSI81+#+rNINhEzQiQOH&8D zI2{_6>bOhTzSXhF$wBl9{mgO+v10JTbOCe4a`3gFAVYPvxlox(>1}=X$e;z-re=Y8 z6~BuBZC74xC4f~#TX}%Rw~~W<5rO;Wd&2w7Onx^q6TxuNNcoAh)|$&(hN<7{Zf{qd zpO0=|u6hW;>wuvY?~TbZe>3UNwmk-G&zbq~7cHUHPUCNX*a1dEF?soE376rP!-2I_ zQ3sM~oP1jgK6ACG^5R5+7TDUZ-eV2yeGXaicB`{<$!aj^mansoF4AL3f9+Ka-S@7y z^Z)A5253^P1n=%!V$#gd1tdKzWS!5hnM~S;JLX1rHy9^SfqTL~Mqe-`nDo@L*VqJ= z$V%0i{0Z1Fe)TgdY30=@EO*?-`y5QZN7t=)*TK!vjt`#t=9k?JEVVK88@h}Z1DgPu z2mlIJM_}#y2({m~*e~9?&#yb-%C6?bBoLf+(Vv$-wNGt7H$s?->=KFN0`q>~rFr-@ zX+;=u`7av=PSYUCqDea?Tk0fRgXFF3U_}#GFGkN{f8?K#zkov^`U}D#ZVL_E5c_>+ znOlZuZrI+TLf9!3)F>lja_CY-;mWixBA!4$fx(lE^#MA{in#@G;UHDT9lLa6QjxH{ zNtLqk)vMHgonqi7-zSr9)&EX^fEo$qc61v0jPWm3QDC%-OQw25CFO$WcMnmeba%2? z`Jm6gglcWc&A&Xx;JkAlws-&}U!1CKSv*|+N`GVyuf&_4I3*X(%5MTP_A0X07gS-v z4GjW4f$5Jl8jqD2pxXLf@8#ql=?@v#do5dTPUducY=+A^O_|M3zLcUSHfsVH~!sDkSz56a0z9nCS&nQ@j0C$wAKvuvl_g7ow+dm z+7u`v{j-BDF=o0#9O2Bs-)HRlZ!M0UcSW6EGTNbcdc3eXWEtr8WZvm4dm@b|hdAml z)nd$1n7A8lZ03iP+0ENnJ#_UC3_fB_HvMcqNbdLK6iSM1u+iG37W7O>?OvSDZMC`& zb#FNQC<7sRu)MzJ?0wIpXKK;x;?dPObsA0n=!&e)W2e_y(Q+x4`mtH~PVrWw(UXp% zSLZFZFA?~v=vu#;AVOw@3y$^8AUtPt2A)%vuZcP{GjWwNY?J$_d$7OzOv5h_J<gDRKHO8T^956&*_~Nk1P*MDNw6ZvzOZZ79=S$rIc0wbg4to`NTuxpM#W>4y&+j z^v+o5MZHn8V{UUjH`%VDI$~(rS^m4&inwGxg5Gm-e65Y}-mr@9aPStrLqn5?LyUSw z$xyB>DNUS0I#DAQ+iT!nMh<@CgnLEfTsLc4w}WWKmhT=mmcu~F;T0*2kva~ z#C6BN=3)*r5BO`o>>|zrUsBh`^4BJ&2?57~B1LRf^WW0gboLXptj7Ad7_s<=;L$^mN^&?|mixp#2$O(-s@#LpxTkfk=x>vf9B zDf=-+egH0(?$7{jMhn|qypYcbj7b{<+hV>q?&yXP zI~x8CPEKZlxu#zTcZ4(@*VdhDNWKYH$W1Aq5tw3JU%GbQsM(x`sL3qOuvZg{+2gxl z@z61-wOoT284c4Gi(313wNRh*%eM=ta1j6=xDg6$rPs0*Ij?_p{ znd{$6t*YxP%<*a2PRthI@eh1dIWzko1S9j&_VZx&eO2x4MD49h^9P#TlZ(Tj4Ovue z_R21^*Pfp-gnta0+wM7e4GdNo?LG7ALH7^d1mPPAXc^lPHE|mBmpvm5_{KG$cy*FA zu`hH4se4%*0lZCX#uv-S)kf(uR;AM9kZ!wR+p&!9G$BY;uzyUsVF?UO(R#A)L>YLa z{Mar{@buUFQ8Ew*ua7}g#0-H{_4#R{jpAv*#1euSiRce6g~vFAll*&afX8c@ zDy*tKF(iRY9;H<&sa)mY#t%`FT)4c2hWHl9-LaDzI9b`?iQWrjR-qxZE&kp7y2Q6J z$LjR?GNu0sv5mrS(vr#HkhK|D4_yHwU*io<4?qlh{=0w59~u zU{k`;XN9j0>cL7>bbquz!$X)Gz^f4a@b*Bbg zZY{8_)LM!yTCxufGYu?cYAjA7kIc0i=QG8^;V`)q4oq=Yy9%qnVZ)q~l!_+yY*9Zm z;gCS(#=+?7A!D=1srH0WgeM-&Sw=ZQG!c;oV-VY!LRsZE!OgsHnq&@Q=Pgbu9F#_- zOXG<61dWe!h1>}@2M`<79Pb~U#zjbl>3-XNlp>==-vPgnO&VpB+HOjcUn^!W)55kB zY4F0ZWX&jQ;2|g2DALdOJwbsUu3Et*K^avwU;0pa%H{O8y$=hQKpy>`NnBUCLhTm9 zG=5&s{n6y6JB}tL&12*zL^6}Ay%6j_HQT^E3=ZF%8|BpiQ&tF{q|9o2C6ChHCR5JI ze^)!RLoNW^KUPB2F?FxKi#=ly!Y==XfURy%?@z2#!iGd*nQ$XW%}TXL!VqS~t54HO zwTI11+s~L7&N$+ZkP`GPx;kL#1PJKiKKJJE}VejVxokR)}>vNXgBXeNRnZDPO_wM1vP6g96~Y$l$he z>F%3&mcFFXE4A{;WfGHVq6ij1f_6~U%*Mr8!HW_e9Uc0k( zi0fq{)j-V=wq-eCkVY!#NK&uY!81l@A(<3em>mq9EYYhMCB;5-*Vd`OnufPrrPW%2 zKcQx6zZKw`J%e#uE;Or+5GTAH|c)O)axv*lKb&03l z1zPx+Pb6+yE4E?#8>nVN)Q3)w@eWb9^wbmjV{BF(0SqH7fn4KUqFs6OQql^udsC6d zI}8_RX&Sp_PdRC|U`>ul{{s@L#`5d4vv@ zx{IQIlAf{>cKQ=WU=-RRrx)y!UyR?YP-yOGHCK+Otxpl#q8g40vnv~!ct}zP) z*H7??A9U61`%uP{Z6@kH+9|kK+u2e~H`8|@bl^5?G@v$RUxxdgVjD)Irjo_yw0c?^ zI%Rl?Xut5OBFsOQ395%9!+Q!UJs}9k*caecw1P>~^r4iM^-UO6Po*5C$7kGFM=?Zztw7c<6fsZtetm%vmd;-FW{q$Jd&i*%HIUk#%yOWp3m!2 zHCvxX=ql@lq*@wIuT>81|%qj-;(NL_siiBos{`HU?B9PUTp{F})5cY&@bj~z2y z{m@Qe9IGClJ>B($nRrRZGt;8T8kcrWgMRACKIna*zhFXp#g1B(L>IN1aZfA4(MM@sj{~=|Nlc&vSKVFOx*4DzCx|(i<4%4s4&eI<@iKIcoThWF;z_ zrLuHnH;7wr4i!gxRC`J}q6LOa*2U!?e1E^H>>u4v#Qq;u+4#>C1Kge`Pf)ng3YA4E zzFm*0%tnJ!{L!cadfTACdzOgMl0Tv#VC1W-=piT{g$>sxb`d&d^1@`%%r*~i_AuGR zLSKo?y5uK5q#^G>*WE_DuT@uNt~P@O{GL!+GP;+pT}W=Cz&gY1HX!z#r@UV|CLMIP zpAkaFZ*-%t9QyHg8(6cQ#KQKlqs_l%@3o9OWHSf@5lInvDO|RWa88kq>$?*6GWN@O zZg_ogLu^B@cw3+U(MAItI=%oAN&omF0Y3jX9dUox#;KSZTQGwDN5z66V_r+b*Dqi0 zfYvAXfAtC~hQLlzz+W%+@AFI6KLMDV7)?b)x6Ki~;u`ep=1xnN7jMw0s4J?<`EJO3OWT*TyTb%HMU(DusNc*Q9n zvOWRJsd|eg3twIktem_t8b1r^lRMdaO_2;Qb>LO!9|#Nj5}cxSvB`T6yB_$tmOd-k zBvb7u7F`;{`0JEQM$7nwr*V_p#Ycrx!I7wLo{J{sTynB(kspT&qO25PVXAIQSt_H% z*MzdFyA;T&WMzU6M&}=vex;89P+c<+Ekl~M83o6?)|8uI6gD*hWJA=uR|MvKJRZAe zkjP7jwzRNq0O}!+X1j15ZV6?M+QapU=IJE$P#CK7`u8aWeCQHD#&yRchQ~HM^$_*e z0M~E(KjP#NY`5q4f-N(7vlm6U`gMf|e9GOO|DXx0x zb4|BPcy8oC6od6kEYW99P0!P# zF_igPu81qI21E;w%+<*{SfCf1&p09=OSF%;C+dxvylGs|V;}-y}M5`%vhZ@=(FR}e~PR~V6g?$`68_Qxm zp=XfZPo*MKpaNs;PsC`7^lVByQ8hI0a;~J+z){@7pcb1{_r6F06bxgyccheylxk#L z`6{L8Yhlwx!fGGC^LXUm;oR7$zH`xtOcJnEqb|O&-gGQJB01kZ<|^p$87Dy_AWBNv z5F*kXM~_<>l<^t45X|##G$%bH=Xd7VxST*vM`i~bpO@g%@_wi z$}7@C5(B=o_~1NVMxhQ0X|sGXg_bT7)|MLWebKYb9uqYAJ!~^0T@vo#1}eNLoa~&8 zv4!~7%S5-Soq(*=!9P{MUCIc0LQ=UpS@$Zb-K0FNcwkR0;W#RgSzE9Mz{g=Sr%&)w z^mL4i*wo7X*;C6&`4otgXX6oEw`V6^mBz9z1j7yVwo?@q2Bp|xN@`Mc+J5?Zh-#U_ ztZnsgMAe;s8QyV3)BF|vXakhDl#c|#rdZa4d9D=)FCK4HgxWM8gwulJsR)e~%Zh%Q zmRUVztOmO%_-20XJWq9C4KK^QMmB7>hf?4=A~ zJPP6Yp_pJK(43`4JiqO#x}2{zBXY+->{`M>V0c-lwH}fu7?;9_-5vAEvsQn3rjZg3 zp;Z~1CqBH0hHKZxb}K&-*`ku7X~DP)B-qd8YV^6#-}|0 z^&*%WKZA5GhfVECGxLVGbbiJQma^gWm0VlC|Eas|dSc>(!Q0H&;baL6ukd6-dReH} zHwe<25yVBT$-vpA!_qR!5UBI0=WFv|#W{s_vD@_Ka@NWGd>u2tC;olX<6z127?rR5 zxZE%H1Yq^6ZDuYKy*8G!)MT}j7OC95Is*ib zyffHrKY9#r!p*YktV^AW%{&^U?xjFQqUney>t)OX13nWd{Vr|!%phsaj|~noWioS6 zNCRt73ArVqJ|m}{EMQ#;q!2a+^Y*U+_hjO>1)SLYIGq6nKcLou)t9`2eSKWT#(vX0 zBVu5&W@s=(6Xo;`zt_KEg3s@6E_Wx?j|l9}UxE75g(u|{uMCMJ z0Jhnt5CI0^(o-43mByF5_ERC8*KvlI_qMxMQ@s?~sQI#?m(aCeNn4?l;I;3{NsK?)@Q(2FH9kc;+9|02Uc4xH0Wt1 zu=W6SkX;9I-Qg| zob;-nwRjh|D!m-Rw{%*ky@Ytn7|8FNO5j#s; zr+*DsK!&BI?cZ+CgxVT#p9Of@O48B`|0F|F$EEc*k`xRKleGzuWRkR6@o(9VU%e!c zXb1+k6n(4=JsEJYlgrSXi_m^5%HnVEu{U2$^XHq8Ipb!``jE|P+FH@YAXX7!M)X;J zXE^eHy7M`D+tdH?0sC{#z1yB!pTSf{R9{INTbF%{9=8J9RMtsjY5vX8tgZ6ITkgQx zG^^s>ALb`>pn9SUxpJXT8Yiaud^`5fC&8EM*5jtxFJus(fyLH`WPJxFw&D-%mKHCw z&q7?BHfjU&;c1zJ6F2i9eURdXHY9GT-yfz{d4?$I&0ic%jj8MN(<6d~wOPZfQ)CC` zEq<6a*-Wa14>&-?sKw3lkYf0zp&}x~|GYb^Miz z@VrEo)nWTOZkyk{F8(s~fz$r>NE4w<&(875Gg3v{xJ3hwieiyMuKous+*s|B#HfPm zNc_&M-=ApU^N@#dwtH@nv;gPdp_p;K{1RzBl(p5dm!ptgrWu!Wa`#lUJCL~TmFOr& z*qt=@;EY3PK#H9AN~2Xc)N80}%9<0UW-=)fiWNsPOpV2u)l~avJlErfQzArZ0#wgW z@ZVq_CYVG5w6s_`1L38tSQZ+sqHqT-8+UJwh!q_8_A#iHik@x{rSP4Fzv z^Dp+b`RK6a$lV7-)uF@`us4NjEkwp*<0XykswLGp)ygLwK8F^hXLh^g3eVJ5?RhP! zMTK_RF90gr7-bRxK-}XOvRnFK;}P7cMGxI$G+8O`ECTY8sl}EfvrEl#enu4^q2)~9 zM=?K!y zMG~MCYEus3zdkjaNp>>%*VszUy+TQiE>8)kT8QY-4~DSt_3IR(WtwTn6R{oPQ$*}PwR`bcoZ&)472qFyF` z!*L$-Ared+C;MYnUW|b=aIL>unsDn5g`lE$t>PLXucueLH;adxZc?QsLTv;!4NikW zBXw$k46_~&yUHOj@z+?@j;*yU!jtnW_sVcB@MkfDV^Et@?8Tm;*YaL%7>um*kxWJ< zn&lCKGD0n}w+uN#v_ntUCg4!(3n&mLmB{dl-e)i0t9Fu0y6(D&M%g`oEsbn8GvY&* zf9&7kv*CP0kV@^0lI4w={L(oQ`-hMn>>9tV96X{r4M6e3A?c;sygENaOLF4$3}>k{ zsl;_Pqjmie;M3 zj^@t-w&?^itf`AGdvOE|CFDsJvwk&}#{;}7-bkc4Oiy7a36@iOT*WYtf6 zEDUA@WCkPefXGp>-$ozy&y%3TG0dKNQiurfL*BZxq?t{|&(!Z72C`~Qieol``>QkG ze!>TXt0|s!hEFc4CYGws zH`U}jaa|QwXw7u@;fU69hm&9pg*s)I2Prq|H7p@F(Q+&9O@MP&cCLP}_Yp~}q{S0R zy{2^uLWM;2U63`>2cVomi!&WiRM&7gJb;~2Mr@WiYX!xxIiHFKkbhcp{s5bL$O`B( z9aH{e^mdd3pZ3$s+wVj1aQnC82|!W9S@>ri<#%4q3ADFuyZIqK*S6OBNln-8`N?t?m!mp;6SIA=yadkHuLSrdGEo z&SN#yLyW;Ji<#bU;kl)@#C*@V5}hFdKLU<0W7`oSbGlhc@$mAmm}xMSxg?$2!{v~& zO7kH@37(5Y3PU9Tj4!V105Kw>PgKCZt+aIMz(ewzy}a?)Gxrp4bV?ecmwo(DO&>y| zMbGQ6K38)adp&M9eEtIUi9ZMhD4&ThHgiE=1~RVAZi?tSGY8b|LloLm26Vk2D6oc> zt{*ahh-*jhfP%Qa47@5l=4gJ{t6v{aTqe;@ct_tR?IRu9$C)RYdmWPZK!(VMXz^+s zWgK+&shldc{hKR)B=7k|+r9rsuIZ;Rlq&$ZoxXwe<%{J1PpRf#_jh|2XH_RtM-@A3 zQ(NFY{-1M&`q|$F#DLWXV){H`>Rc4ESq>i?*xTvFTc|3qS&R&{ewdbx&89V5*FJs_ zDwoG&{{p0fG6?%IyB-yWCb z1wh@uZZQ?Aurl(5GmqWX^<<3|Hxki;(=U%YxX4W=4bwG;r;nn7FQxrzO=87tF{FG9 z?!oe%qDzgg#{Pdud&loezjj-0YIYjgRvmtO1Hds3-Z%lKE-rk5if3LVG@q^5^ zhj|yfEI&1f4&lfAhTqoPsFXt~8FP6W_=X+`qY1W1OIQrkbf%^OPOQgVjLjyfHvVB# zInVb@X<>5MiDQC-KjR%rD#n>zaYJ9t^3<+aZTLeijKZ)}{A=BeCzvM?tiSP$pPKmU zqc|((*-kamm{va_4U6Pp_2ECi?Pzi_>cy~`oVU|gW&M*Lq{j<<$|aQ1Aa@ z3(;v|3HMqC<$iS@1qB5Rs8*r1l0}fDf@i|01i7@`r+>;(1nRTG+#YccO&;4OL{FEpk2* z;>SqK49FMX`UbykR9ArRAp+uA%u0 zEN|S?eJ@vblG)7Yfl9gf`BuajDvW=AXGH0PRfidxdDQCZo=Lf}G`TWFKv6f94M+-2 zv6&F4x`{0)G2cEJ{)qEeB}1sNz$JEuqMWh}54(tCa_J($nr!-fl`C>AZGQ$M6;O&+ zStM4K>8W>@ecB>ceMNgSpoG0Yp0U0-GPewYex){0EJ?BLMs-K!LBiOaVwMQxSG+01 zq0lJB#VbSI7XL}_stOK8LNI{C$>7X_#ZGtpX9p_wM)X>Nk!c|mN(CDOOc4;Q&MKtH zLL?inX%oJbP8_~OqEwC=YZ#|t6u#8F#1WiU2rNw|E8Iv!`Ln@E{CA`Q?t7R4w)$<6 z6#{g&3LJVV;OKQxQMj)~Zd+4T9IHyT<@QrCT$8GpzdLw7xnSIaUMfsd^)$lJVn=a2 zG&C)ug58QT>amVhDOVXoEJ4`5i3vcNZ|f80eC_5M(H@X;XpvvCjBSIb>mzE^o#`^oagvacyuUtG`5|YRoyLoTAZ%ibjDeif0FTe z(=?y;YtLA>JZ~;OPLolvgmhjO`So}{*sncB(7eI?te*OU@nR9_5dHX;cxPflyZp)B zR(pb1u{eR9t;Z}uKjcnUiJ*u)POz+r*`67H&cW(8Hz)xQD}AJ!|UV3XNEJj4NtaHRt#9v8n7c5*;$ep z9CY+|9*c?)D|WFI=)cDuAZ`qa`vKXWMz!5`t+decTop$J%;v-lwuo>pl3LEz%-PS{E6VILR~0y*+06{GZwxj{23U6(?dsxOE~k9r{OjY` zBjTv-*H^)V%vWpC{|9yT|Efs~*<1YQyI5O!RScD%25S`(mRQFSK!}l%#7YL5T!dkc zP3V`BOSM015nMa~#POr`XrSRpZBj0p%KQj<$bE;(So2MI+wI+vhgl{weBrrI67@+_ zya!$VejlKo=d-^xXwh{C9eNtOSG|yU9B`Se%*A)V0+HWs9)t-`6QnYTj_St4LeKX+ zfSkhI#NMu@`XzValjx_+E?0f3B^Tz`>?XA5=<+n~RbywpV_va>!XJu0YI-w85H0Bi+C0oVOpl}ezS=hx+ZcwPNmE!JQ=O4lSJ{W(^BPuoiZC;?JapxhR~Td z7%Mfwd2_wVY+VmLrq|kg>7O%j)DMdMNFn-4Wk6D@&4#djr7Gb1)Pr8i5I7{5tF^Lo zhqCS2aORDSR&qw(+A{?3_$>fRtKciBQ^k@xt5C#e=jJ=uFwqTz%yPPrN(Na!Kfh-c zm4&GC-7X0nL8wXoqlJ{xET2$%zZxG};=ADZC2=JUTwkp*mA6-dvgz z=}GO3M%uXZGv~%H6n?pvmUh1-ueIIX4du79z2qU^53#6dIiD~ZPfHuYOThiRqL#Sl zw_~e`yA=MAI|cew(ig_Os*>*E72S94*<*!naV#z>YVNQ+BZ_^6?~4Iv|X#QwQf_zcfXD$aZ%8hHv1(kOemIo)D6 z1L^>8qBDeC{9ce!h|SkfLA&-#wr|ePh(RY&@Eknc?t?QC*jl(y+xOj1Ii6)L*WqHb zF+FP;*jP5~aZ#ADTF3?bP89|Rkq41G?;C>q(nzaZ~?H&7d@LaHdq z;YF8?ukpU?!W}#>mI=0TH@55I0g9$}Rc`PV4*iKqqIXZn_dxd^@`kGQry0f)e`B=o zowoyS4su%vA2wwfyw?ETvE4qsj|calW}34j9DIKlbuk9vrPkVuv70FB`0y$Q08u}( z%?H1WyK#605)Abf7ydUGw@w2Z_rPJqp_B{OLE{PIUyn2hn|uU=FIQOhm)`f^JktKT zjsK_K_kWDt>g!)pAKK@-=xkMhHM*a5H-WyUs7B7PzV*;A8-Cm&$6qaf9d_FYGz-{h zL(pb)?bjMg(~IO*@7nR)R+61xrXP9`&SiSCOpA885i*aJ30^4frj8o;+}t!P`{B3b zhQ`e=xel{#S8pArmd7?f-irmkV-8MX-VBUNZOo2?dz0CnEUrtra8*}gR~-cbb;dVK zXipC^Ce>-kmrNKTTqL!ZZcLO${_y`Pz@WpDh3)(K>t+}mm_o#CniAyVXV7TZ2I0M4 zhJDa`^eL_Mm!8`po`_GklzuPS*{Z7yi+sweNVAr8jPUTL-fF*&U2jnwE9$a9tu}z% zLUg?ah75cHRy>(TZJF^_`jp0_ui>t&H1QT(xg!Hdl-`H{P}fp|k*phHrKPGpBN683dw6p?vEO9;m#VN*M`IsuIoEA1s9DZ-yAV7iA8i!7+$wSu$F zo)Q`=c-P#uBp>!w+H*mpnWVEwGu+{hQ+sH~+^J;OffVnA1y)Lu;S1?YWvLOy#kvcP zM1iFnfCjHZs|-~tEpx&b|6{}|=%XDDfdwlkaA;5v;rnJNFJr)6QjmPdv!b<}Mx0%7 zqBT>dV%Bg{2gGwhuI{HC7Ye%iZHq?vcYjGJLfz?h1DnUnuW6qp50<&wDp`HWdxnvPvw=!JzvH-4f_LUlfBh6p>)$k zCvuyOiAL+x{v~481Du5w*s!3wUI0jgDS3S8JMWFdsoqT!{IK>f#P}btlv(lIfj@GhAF!^2V zw^dHDK?0JX;y0Od*c_N&JPxRgw3L=+CW^b^%87BnM@bi26WXELfLP2?ja)>l*E^6I z43SvsdDlG`p`%W`KI)@-B%)uii|h zfI46K%OO%1Um2y|2I_{Uxs|h!0f2>47M}ul)&@bGT-}H|3X`Z(VU@xUp5=s)l3e;w zH6oawbs==&C3Wr84Pq!^*iNij@7%%+Eh`n|cpsz12x(Pf3*UuTi_#bTU3l;|ipi6xLgOEH!(<=j<{aK2P$@fC89?h)b{TJ18xvsl2I7){!!V6#rI&I!_HVbqV=>yx2elt< zmu-b;$z~0zOSGI&;R@<9bKwf=Gq>UHOygtWjO#ORARK)OVW9+a%osWg@$!$lF{8G4m0AQh zbVT5HIRl|G`bZt}pNNhpxh@B#hr@}46XJ1%LL(&D$!?~Nm5E*vW_^|0aZ} z4n~Je4%^nqai1Jx){rTSQp+LJDP5Cukvu3#lHF}E9Qmdm7Z9)ZTQ~UudRJ*vT^Vp}doWR3z?pr9kofcjabr-bo2%<$b@qwL#R-Obktjgp9u#h#R00jwveCv-Om%pZ_o{# zI~b|Cz$fP{A0j98TaH?pMhr%)7z4T6#8}GLnyPlsT7CD{Z*aXGi|1Mol2z{fd;;Tp z}sX0hbPzBoe6MFt` zMe2j;KrA~?laAxW>;L-UP@F74HpMoQ=MJ8{{^oroS69a}g6eQ#o)W0r_Hz)+`nTK! zIhraFIdvH?GO?UX>qac210Q`d9}1D}+P))6oiDlVPFOrT6H0!}?!?6FQ8XBMwbs zb=KehUh;@R5(Q{ZdzrQ2^?-MOFO-EginnK)y`oLxgBH^$Jf(IFyruExrW6%fiv8r` zcbV$!5q^-z6rBm)LrSiCNn?g!iCdEg0^G00wLBwP=l(*n!HdOu*N7j(4RCJyGn&XV z8&3Hq8hvf)?`92Q%1=T~pB#|E`1haSvkXYKp7Womvk@UKzkKAc(6lLc$huaCibLd| zpIEOg2sWj?s-vfnv)})kIQH)~u>%_QRYgqp^~{UywT>7zb_M`hp5FH$8w2W8*fR*s zPCo^E<aldwyQ(xHqf6p2OaB*}ru{E+V`7evg|M<(}|J-2I z)}2uPxxp+9RqeAS1)9(TxN(-+as-KQ3AiH=G(a zKMupIzm0Dpz+zZVRMu6dTD_<(&!4x;TGdzB;AB~eR#zq;Y9|$@vjGmrxHoOnbO;`G z9*r4;_JW%76LDB`-xg1Tfb}LKn5OXP!0YX-ng~q5TZaMm!zt{fAZ0%?v$D=$ zrOm`L(s2RRajnDwiyF|@RJFw3*=lE%)KoYZ4%@o5vDOecSg?cmf^r0iBhs>3bOEOl zCO@XcG>)O0SY)Ugr@NJdTq6U@oY2sqGLHNL6w#E+9VSe|EAfhpThb-3!6nWD0}kCL)p(D zll>wRGGCVcb?Ot0yp@M0^{rD-XhyirA{##Ln2-yFltqXchtw?d``@mitLV!IV3)q( zz%~sOcR37f+23w=U0-=CHcI)=i9b0HZmGeRW<4G5mXq5Z5)LohJ|EBF?-I1G$^*QkpzkT{T~*gE%{3+J zDv$S0Ze0=CWi9^Mq*p8*(%OyA7dixD3`6&i+FJf60(i62(4+z%9Z6DrirO|uD(hjU z$H@Wnew8yzYPCTwGuUt^c(Olr8x~X!!(qR}0P}(KHkDP&X-C`IJV-Ba#)cu`APw2F zH@^Ci?4l(;X|?j%YXYe;L1c%QBkutTya_Q0tjoaU*)5I0wYZx*Kjyc+j|SEO`pKdl zNehv*u$V|s_JidXGm|OmUjBD09jOPMb4hqe*ZuS#NWG=s_;I`aK{DXChzRtg@QGx4 zD8u$3c5+Ih4f(@fB_3jTplZv5;JRRB9#7e2R_KT%gM z;--Cw9clki|0R~FWA@)a!=uI+(xn?yOpLMIGP}mqvPR@-+{7}piAdyG+)_nPm_y-( zx*cCJ^8*?Vju^{^l9te+nO~T7+WEl$?=;Z5_dosbX^>UUtpx^%MU3(dwGJ z<0ot;90+|bT=+fER5(!TTo~mC^u&v>(RuM8e1;dh^NogEydGR;#<{H{nX_Y6`$%=5 z9KAD#2fg!`l00(fCoW1s{uvsknP5>k2D~OK&@K!ekzqv{>nGT~H#=?J2VReL?j-*l zV0aAEyFmN1M&r|}-r_jet~d-c*dGeg2t>-U@K;=Vszrcj;QZxwEfKb%cX@1qWaIDuP-klQ?G}005$8+ zgX9+HQzrW2D&F4TkO4B5BLhaDf6jL*y2t1zp*nVUes^=|=n%(~4poY`tozJ!0HV@D z8B`c3GIo%H?I$9Iczjts4A&!te!4{)`;%gj1`<-W?&T8nB1+OUJgd%U=vR64FwW=x zgnEvs;CyW#nB7DS=co0CJ7x$G#ZSr-)IwF1o!&hBYo9^Pyki8CMV1A3g?p{@3nK&r zv_qt$VJIIVvxs!EoFJ^?4ac9kz~_2u6J+3EbF=-gi2n`-hXOj<1HK<5e;$*NOfSPV zjqz2t;yWTi{5#2W^AUA=Gg4`u=71~OWgjslFfek_hEeWu=fWn!QO41$J9evOD0T;P z4|50e0CQV`vxsvPxA^Gww^5o=&@Su{?T}`aIu|u=wZphW#%>7@bo!+G5Y{1=&&R(R zgn~oZ`R`!9ed8eg_KoX*w%7ZQk-M(>tgbzT_8|r-$qNR#97Se?V~v8-Pi#FT`Q5mO zv!7VM_;1mmZIapbuOGrno4f`Y3ofe#Hf=?+E6%d<4YH3dp+>k%R4yw;vX9=ItJyCM zcXxM_9mQ}^-??q&H5P`3TCZho1LA1O}24Q{2Yn9bWqy!5_C%UUYVMKVLEH{&l&oML6jil+r* zji#jqcTxsx`=Q`N*wHfKZ*vSR7%|x-_~Bg(3z>l9UNJXd%mk(tp@fIQ^ z*5)Hhl2}oKa28NxBclg~ z3Hgu?w>N0LVNzRyaxJ+{pznV;+g=bDwbUISbqnh`vuVpxDMtr_8zo~{At~5{|Azet z4J@+1G20H|uE5Az?p8ozV_lf{jN>L+oae1Uo;F9EP>qHNJXjHlDKShsCmXK?j}*?P z8F4uXkmJ78njv+)bB02R5`nNblb@6ccG(VX!G^h<8Jt%cC!KDwD9GDO6-WirT^q!+)fu~TRF)h& zY($fJ7(G*(4*lARII2b$?zkeK*HhJ{HwD3o3NB~P8YLi`M0LjI-n7`t{hc&Tv7s&# z;V0Mqo{X+Lx^i#G;fZ#qj{uNWIU}{4FQ^=z<}@Mj&6UPN#Tgo=bZ80D&7Q$jLjwtL zZ!N-}3H6_qP&c%WurlSh!v=#sN#UtDd2g7RqolGVOV`6Z*vbliub9NTR4Z~6A)6(RW$lzmkxvijrT$iS9 z#UQAi-p;G?8)|Au6eVVlB7{7v38ogW=Wlr4HjVH^Dn~6lV$txFl`85irKH}c+`=GT z`UcxYWND)XWoR`nTf`7NBTgM+!?9i?6(Q6F5HuVF-HM)IJvxpD(*XMO2^{H_DHhNG z78mgN-oMn`ez?Rg2;@bq4sz!-N!U`Me~Q)kXZ9UU7PAUfnr_^oVkQ<$qrRFx#Td-AybI~FZ3$l?7G*E;Lv)}yguF%7C zB^R`y38+J0wCzaG<=zzG*!XJRHlGW{s-!?Sf;`|R;%O>>4#BwWd!@MsdI{o=i;IV@ zhX+>{Au!F$rG;OzQg1v2m15NJ?;)LVb98tFcqcmsNG2l_jrO!HH+W|*`eXr!T!9#BW^Qe^_MX}Qy!2W53M2*m0w>*vKScdkl%Fx&+awD z?4|KCbKI4@aoR4h^-6lP_7LQ4+b@~LQRZUs`S|3`QoG{v@*_&fs#{OXr;@icGgFb7 zr7tpn_>#k1TDQICtd-WDDPw{bG^B2KXw*u465-R1J*X_hU-NLk0HYh1Hb9x;Xts{E z3u!&m2&1BwlJ+wUq3$p2N^u@}H&#=DS)jU z{IeYt`rxVMKP$xwm5#wnhq{N?JnvCaVNqotJYEZ~6HAKqfLx}=(+rTs?c}>MT+haW zG15-*;&*7t>O|-6yhotd)?o6%-#1Ar?!TAUl_WD&olzf+UEJDhFHvb;O>pn`U6HP4 ze8)RCY}16k{BuPgHeD0!$|gynqjV~Zhb$Zq&CgX0sm)RcHgU_RR(0C%c&g8UG|9f0 z^<|};ZH2~a9Zbikol?sgVM4gdhgO!0JB47DbwwKwf#TyGsvmy)P(ZJh1@d0wA1FO@ zwX@S`@}<)F8*NFCFoGW_mp5|Eq8B=2C`q5fLOeszmm>{^HuO>h;Hi1uVa(3!h+ozq z3p(WNnN~S%C;5lO)VdiEZ{DgjDuB+m@rlxIk<1h99nUcS)8AO4c|9McO61DUC|?8c8G0@vIIsy z#4+wV1ME<2?!A93h$N3~2q=!Ni-a0uz-NkzmR!J=2~Wb5U|G}gM6bkZ4Qw?oOJ$nmjr9dl{D7O z3rHQOK(%N>JFdf1?%eR$G@@iUgYCDQu)Wf=&9Ndq!K8J_IN9h->RfZsbpVUrx~pP}(H2`W?}g zJ}rJ>61PHwM^^&*Qh3U~jc(~AW%XHAN~F2{OG5T)qG?(WZkje61x za#9y`M{Ghp?gJQAsRCEcAPL`K2xW5r7Iw5Y^6R3z7k-{j#hhfp6!BQ7b&Z7jB8$-D zE$5TW;5vQ_H7~xXVu61ZIm34lW)ca)A6tFgy+neI+%GT+B2^UK-;FmQ!mhqm71kUT z)u9J7TJPq~m~R*8V<#i|!GPSDf)-Rl;v1yt7x$AG!;Nnt_1m!H3$4*(gyjTek68=+ zDeayrA3eK|d9574YnfZL69Azg*GgYx;6^4sACzcMB3?PqE9oMqXj?LE(57KCXd<`t zBezY89aKCWDjqONBJEZwEX~g$rFUws+hx*RdBq@h3V|;yS6#6mhPhsqj8SY;8QV|PM z7-MO%Tl9>)X}Ryui(Xf6%-WivTbv%!zovJ56QH(lpN??(iH51Odk?Pq1;al}53dJ$ z)IO6BuNoIUcoXll6T2Q2ZDPomW4qRykhMps zXAYqfxu8nSpK za!&59K}x<2)~5*2X^&pHy|^Nq730N`xMDSqNw&+Z>_fHlieu{#Dt{8WJmY;x7Aicn z7xavYE28&255>GPdKoC5_G!eVglKl0gKpd*( zvfXXTG)mcpkJ_~VVoA?G|BdE+%l&~B>>)p@ z+#$!1lN`>lDI)i^;Oe?*Ic3waWgT zWPFe0ZHxbAc@~-6NSnQIxUBfNHR=mO-{iAomhwGrK z7OdJ#9YWdb8>r#3oj|`wBmbgY0ng&&gj95fc8*GGI*x{VPSvHWhS@DzhM%*WHme*X+NhIM7QvJ%uOLZf+7Bv5uEJn= zlP&f_oFbJ${VUC#l;~BZmkO#e{s~(DLd#VA3H+^Kn`%}h4ymBYaR6UW=7i-4?jD3i z028K|SrMb;h_k8$EFF;(Ijvd%WOO(|u=&pp_bJPR>Uu*Nz0;?AJ&N&$Hz>o9JsKDJ z8p@uwCuf+t3Nk?$QM6TM7HCzLN6mrvEMu5FQ`3(Qk2#l^+b#?6TDs};Fy~~vW?q`j z5~*k|)?}_H6MWl=tBFZtd>E$jZB@UycF3MOW65UdaYU|0gJG%la2(LITy3>cHzjZ5 zI!n!m!X#3IQ5C)Stoy=6Szofo@3}DHuX40|?cB;<W(z} zcDUd}u2H~Taa7~-ZC(laEPSGFYdb53g)UO$`k7Ovg4$XSm9s6&gH)0Q6z{MK#YTB`SZEcjlO)} z`TVNHmggber1&JVG^K%8p%=?%j|I*BgXxJg?DAR;tbq2aogD1u4_#9F{t?0zV38L^ zPLUehX*dC<&8qB&yPONHF|_H(;`4OQalyw*yq#XNaM>!;lviJdfaJoHA5kGORqRiNA-F0#ZLS@^-HgZ z5;jC}Oa#Xs?DBYcYC^Nlo@H^T9-8d(I969{L(S?sGt(tZe&(lhrk@|3w}l^mV6FvZ z*Z5X;;du|2oC+P|8iWcebrbs)jx=c52dVIad=k+q!eTjtAQtA4**%h76;R$RHU-~S%;Pn~o!a4sGp@!a_CFR*V z%s9=Jx8aOrRqA(0n=2*crU>DW95ZlExZ^q!d4d_Bz!jLhPPSH@g{D{HEE`T5O~~f} zh_gH*aZQK%wNuXct?~rNm0hlKCBUkI)=Me*MF#lkZh$y4fyIRclr(L}5CAyUOH@i_ zO<#XpV-rgEw0H19%TNd)Bm2D;60lpn+`Rkxq!>~T_Xlc_=q)LK zg=g4$e*PT?XxthB#Jd;1n%26iT~-C{l5#e|AN#1voljg z{aj}~k7H%E0H@7CB${TY(j0AWP?Jg zOF}Rj;p%l~=ZHHcn}nJM>sOZA`k2-EwIB02ygiuu5(1(?0HW_1cR$ioCEo*HX!Sl!m z#=6YyHojIuB)9 z=IZRoCGOph9FL3#X{np|%ZgxN7u$kgW~hgOF0+224c?C zH0!_8(I=hMb(M7!>1$kqyloCh$LMP#@TuR%M2=X8St7dYq2Vf*q2pwuN%<(qMkGgU zD9GBmLWL7*&+do@BBw*`9lcEg&u@+}zL!aVQW;<^^S@TDkkP2pM%Nh}dbI9HmW0_(b6hwh7 zl1DD$EbYsw8I7Hrn-^r6mB4LoTg;IvSp`nfu^k!*|7%UoEI8S4_l2wFls0Xr?qE4VY#6=upTIv zb$rOvrIh`>;XNS<48A%jy>NR^G0`9Gn4Zc|i1wIB_*H{?iq*4@v$~{o?KnYmliVJJz9HYYAWY z61eUWus!<6yN%RK48dSjYnPadZuAqUtTLQG0|SN6imTJS%r-yuyfAKN^wx4=H;@H& z&AL))tEt(^y;RJv%6_2YG)hjJWwkPWiqTDkUM7KOoIFINA zBuoUKST_ifV+G~UD38GwI#RrY9y#k^#1Nt;v5W?zWLkkRB=TF@ND3&?F(f6A+VtGhd(Aqi7MwvIQO`Mr2O{m* zWt{R8qTJN|0lT5WPxdff*;gZgwT)7;wOQoB;wf{@3u{T2-^)f=jiU1DZ0V*ogI6|(}6 z9ib%U{an;VB`F(@lx9&}obZu%6_t77AnG+$IU#YNcsS6bj7N3;&@#x!-HrVO2b&E@_|}xrv?7KB12&x0Ri+;pG;Ir$ z8z(5Lb!v*$+iS{kA#L7B-Icv7?(3KRwncF#p?%IZdYvyYu(@^ZZidoZE<1L7{bXQz zF}c?H*gCb=%HsR>K34s0a)%};!4jQRoav^65Nl17xl&Y;$}+K(R{gNhT6NsMyr7g< z%{fytU0c&B7RU9>xq{)_;MK%Y-tkcyQE8D$Ta>B2wiYEC>4bfY31wS?!#OdBsd%EW zt=h5P5<0b0RW5g$#ot*8(G#4%16s4J6831hjMME^j27l{p93E=;DrSe+zCl4FpMwF|fMMao4~5rPrF`O0(R&%j=u^-$2=7K%rh%_bB=< zq4s5eT7@)3PLY1Ms28n)o9r+hl4HtPRh~9rCB_Xvc*O!`O6DD|hppskg(UfHn)|nR z`iLqimoZ}#N7jHh_@Ek&QGJhzrg^a`TWcthCI`jw}=vB$U)jF$I5;>Bz#S} z6P+iMkVWCa8a);%;|j+*OgYyJ-ZWjFVoc;l=wZgG)@P;*%KEVUX$0%2?e3@$O}{iPde2a1s!(Wos2!@>9_1Y<_q<4Kw#tr%?#zrra}`q5K1RGk z9md(JDjKd#lyQ`3Wfr_#NiAI-|Ke_{RcqNsvQ}!K6Q~4Z!+vW#ZZ>B2_~`Nc`MB|c zCpBLNHn}jj8xco@@)3+suSJk&?|_S;F#H^zy&UPfN07t-pGiSEPwxl~tn6fm34J6E zNmx-kx*2E7Do}~OE9e6eIPk46m2Xe~n~D@kSefH$l%ftmlupe!Wm9|q#Ix+rFh(s) zNG{?=cVXnsC&M$|H$_^$LjP}KM;g|Wgh4+uKbPrhwE1=X)3?ChVXK6r z2nI=KHbRwa?$tF$!1gmNHn7y3Ly;K51|9KsN!yRXGG%y!wzS7E>Y!{r{Ftw@aD=&p z6y%NA4S}Yb+M;;~jPiyOagS7>fNr~AZ=2)?mt%l7$8O~9|Rd-jBE#=Yb+IuN!(*;*#&jark^O_8WV*` zO(;+fD-5;FM8jM6#iFyLj8eaOPzIKV8%fpHp<|Iz&)E`*Ka8@7Q|RP1cAMlEEMv>U z*`T_&kv3HW)oBa6kEK@g(39Z&wIy|HZRE0J6aGP=TIDMy)14!On2W4Ry+lyx5!55( z&g~@vH^i$!xk;~nM!z`;I@P-`xs&i|cfd6y@Q>?NzMlB&Xs%4kUa1rs4y_x#c}Uh9JkqNna6#!zIYH zkm%K?w~p7mLRgHhv+cKkP7zim{g^W4Y3#WOcF$Z)$0wEq&@?tZVJkU&Qq#XhSoz32 zdIc*?g-xG!U6X9+cm#|)pYDC{xKAJE_LA{NdGjU0?w*J;IC;}3YE=_f3nn$37b}TW zL}6+|TEs+7#0+GOY=)YmAkMcq%e_{%1d+17i<>5%gvLTKfQt(w%za1lnge~{KDhXy z0Qn%5INDSPOdNHMrd1<4bzRWClyh?irMaMwDPu$lb=w?T!}db`^d7f+L@xCV6$W4TwaS5v$E8-1XJqnWZfo3U*HyCC#LA}m` zg=vEQLH}-@A3(-zu^ScH{_hWl7&YYKy01}U_y1#*n3?57E z6UqQmqX@oSvRSuTkKP-n%YQ!KzqqHv>rTN@`x_{|W+&$Cn!-N^Ei_P_`f5$gvl>#y zWyV@<$YbTt0slMcohFvv z6WoG9?&SF_kN++}yLOFwCCHAuDLJJ?!G8p=Ad-tcG_MLXu9)H!;2Y-n)dew{4Cn*I zkG+&-)eI-5jL=K>9cFcR-~K|#Q`~y0hl9-1%;Bs5@w2p^R5=?`Lu7!k?Y8z;!q7g2r~L}$JKJ$fYk%EceU)w9r1L+4?K)0#qGPl5Nu=ZPQ-)4n z1Cmu;TDumU@qH|C47sW!Rz8SKezrSU>0gT2nE703`f=;s{rsJ~Wd{85dQYnX?3Od+ zdk73GQgXu)6kP-hri!GST25zPmTal{v($)(AG_~}%cAKS)Otf$6)I1NPWsbczI=MGH}5MSJY%XO;H6=q2g3DRcu3txSVJ)-lzRjlA*1T{5vMpzoTE`cOJ=;&Y)qLm0Agb4yv#wPe0UPeqcjQx}V zB9EyUHddgQpR8BBqL3GDjeNSCnOM<~lFT?AdawgZcGDKhkpb=ilS?)pivlL>?)UL> zdG_`1@>SNBgMM#imaW3F6PBRb<6*?u?(VB9sGBq0#5jjNk@pAFgPxT3l+(4fKBV?y zI`5~!V<$10C2XauQQj1nsCKok{wUcVy}z!A2u$=(jO#5^h&>ZiBKj9G3xgVla)c-8`l7CBoICUt+kD zIU}UAbIE7o?C2)a1-k>|=ZHu8JSrDXD}KoRrnt8C7C*O9WHG_c7DJ)=<~A-3OAZc0 zIVzz`2&ibN67hGk6UHco`NM+mkk{#rHL>eqLh-mo@Zw<`dd&r(MuJ$qZgu0RMF z(@)0?lq<4F?$8d9z7WRBV~g8aUTlDc4#d;;?wOH}ij*VG^brlAxjUN28;w^+~(>)_Q)UU3Q~QsmE0Y zwVAM{-Br@ttfsld#c?xMqHo^aTzC)Gwc93mf@XT?5RDTH{!zW9+R@YNE;KNGci95%do-fuq5u-DCS}&x(45#qiU9Te0BBCgA$t++W5*hwl4d1T4ds1MI(t ziz)q+!T7JZSf=V1F7}UbBrB1GIEV}T>({XU>%HzcxDXWzLJMRDy%88kqbcF) z;O76bb2Tr}gXMkKbM5ydQ*ou11-{AdgX#k=YPU6=th)%HZR&W*_jtzj=hj312_>)B(8eLV4u ze=#uwAG|< zUY&!w&7iH9-9vLKbvDchHG@(Ms|}h=QR8;u(ji?P42l`H@TRB37)KWm7(oI;7sTGc z5}0s= zkwkAegmGQbu|~hrw~ui{p`XNEhud&QZ#7}O3p*b++caeC8$|dH43MrJHd?7V5 z=`WJ_bq#@?1j3X8k#0FUAK=h-=HW14+dYN@$Tli!AkKsg(0b4~1bIqWdRtP>$afoF zS_;%O=1le!+GVftGImrD7yBIz!FozWQ1sk1-_bpt+~(b~MMmY$6@?l^b`Nio-=8^a zQ%>0toDfbDZgoSXTddHZtO_jhx%7+$AYeiu;K1G0v|3X#_t-dy3wPdmFV*g14%s|+ zQJ6Pq*h)?Y8RrxJFVfyAI?``%+wG*|R64fPv2EM7ZQI?k?R1ikZQHhO+fFCh^wEVYPaV{0ji=`CnseUh8~D!K`q<7_U>Z0Uv3}66M2St9ej;s)*y86`f%s`9 zw8gd_{8EFcNv1Es;)P&K9kAw z61$7(?0-SuKNQLg&YYZKmER~KPCRJpXibFG64A>zSju&;wW~9p-%!uq#4I4Ez z&>sEIsaFtH{Zi0dY=+yWZ~%r2pSs_EHcqy+10_&3~1QxJ>+n4 z^aH3lNr|7-&Y!8RLOm##V}pdgk+Ld-vR+W|$E*TdsGJSXj2~blaYM&g5}(nRdOQ;( zdUXDC&wFRa&euX7-k7a zO$?+gO%4j!n}bO<+Hj&9M{O-z6r@F_8!?)r^H`Hn6+b&q&zRQx+dA>#PZGbIniIbx zr}t`|bY=hfSvalg2{{@G#wm3Med#`aot%P>sQ}0+ex>8gbKV-;m*+_kO6~VDF3oDt z$~qOY`-J*Er$KBRZOPFQDF5y1D0a#KqE2`h*qM3VgOnP;F;17$ClC_=4oUNkcm2KO znxi{@<6)B>6Gf zK6-=<7XpeL{!uBBUF=hNhrNDq#1g0y0$QGy5)ig9r3Kra)Tte8_UzE__(ArPFJD{> zX3o#W>sG6-o`8tVPI^CEyRCXmreRNWA;mG(oLbW0OgCrdYja^uK2bg|3PB*qj4IXR zbhO>HS0ZimC1x7axL_jbkZm?RtQshg4lg*0F9zuXK_GW}Ewe0Kt{K7Hsjuvfb&#)&8RPyF(KEu zegt;)J8+x1VH5OoxKcI&@Y#-*w^0BWTWI5v*)W?{l(!k z1`r+-%}La%teb1pY50x+x*FVBaWr~Q;$~yhN8XHB4;4rzGv+)6>s)Z>XE{Pb3e{2}_b!jrmi1$)>|FHn+RvUfZY|dBQ(}F!(#A<_zg{ z{Gk_AIt-=*NQ*oT7AO(E5M;C{VU7}k0|p5M_{N`>5bO2$6R3<|Z(FE^Fv5#)Pw__y zf+Mi?Gw-*$3SiNNA@FVJ@*^v%3yjom)}IIEDZPs+pmj1_cSsD59=((WoWux5=Q+6! zVpT~xepPdHeoMsVe|82twDZXrhT{ssQJPKzV4c}sf0d>kYMf~PYOb`B`o5Pr(7)8p$aU@T6sXJt#>g8>kK|Wr5efbd(Mc1+XOvCES z6%2Sy`;|X0)v9!BM%ig5A!S_dG>dL7Oiq-3eb-#Pt0RrO?6@Tamh$2yIlydM4AZ#d zjth_dd*Pl@#>*_h1iO=h{yw*($Y)_`6e6lBcWsba=>2GQvBoI1kGtx^pTGDt+5G@H z1eM~XWg9V#O}nGGjdLV_)D^ZP&_9MAKbvHW_ywJGOEipXmgv33%Z)$DlUQZe^{Vuq z%pzQFRaqho$%cfIO74n+r_F6jGS9G-32`x>*P=eCSep6%{;fJcs7ka!HF=V^ZAs8O zvdi6Dl1q>qlPY{4+cN`)JhTi~6Z_`E3{~bV!K=SG)yMTW;V$pGkAQdYQYRhS9xnvP z0sosZNiGGkH=YDRq~+-xejjcI2^{eO9<*w>s^^UT@r6C$Xq=RVy+yrFyUuXG!e)-4 zL<9W^OFUpNM&&KT0u#1jG~IyCNvH*J!CzN*#wOv)&%PM3>_Z*@3s3F-Y0!j+4;8&q zN-0a5{&=eQ2^_uZH<18_Rp+#HGQyr176)kGB#O4Rb(6{XV#!22`q|kN*9da9cBWsU zyrN!*h)8MsLe3_&@H209kd22RBgTv}gG}Z_sGz?^{r10hGQ7s_V-iKBTu?NGw2a^$Lz7c26R7RKYh&Wdl!V=d|>rc zTpuX_SValbJ$V(LxR;Mx(s~5!?g2_U%kJSYBP4TwJo)^bzo+>GsyzZVPnHjVdjuq& zjPR+e&<4ubQHfone+mjKQM{DdW!rlul*o5KPMXE9-~!%!;W$miE4Z=KJad_ZSL|M) zYuWI2oQawOoB(wJOUQPw-gJmI?vTh_?nO0dC!`Gse?lQZbP2q_b^Kz$-b3GW`cV@6{lC<(`k@xJ%46PW#cy=FOy*I$5ji ztVrpfSxU`Y^~t>eZq;kTnrHGD?B*7j9eq^%s@>v%`~W!|XZNDrOasUvoO~P{x9KbW z?jC4JGTi-+swLbEw^)bklmFNw6h;Yll>lX3aLE5{gzUdU-u`{!b^J3|VnI?fke&)N zjSezT(1_3;jB$AxVF`q*5yxJepuQ1U-mu(FWRY^N6UT0&^z_~JHEu-9G(E-m_OD!t z3F#CJ)z+;vZ@bP`}zF6SBzTl1Ixss4 z(V0kZc&ydJV%n@-i%@y`GTmV$e>K$$YCC7dYSE-Xc;er<)By)W%6aZU>og*|o%Rm!?x8G+vH*8Hf5S(Y`IE60`k zW3DN>^V-Byc2!(cxCl(yukIJDcNiD`@EjRB8!opXco=2N{Fh^kNKtx!21ExEK>>F^ z!rC0JvZQP!PDcPtSf%^TWoc>&A3{NxE45Pb2$_PeJ_aq@3pen9mRONUmMt^PlT>Re zqtc9&BE`e%jaKL+7T0?j>G}L~=`kb-12qm)_h~f3fX#(nD1`c5i-2D( z4w<)-0Y~oOajThtl@{r?M!+pL?nn(AN42l+cjgrgm(=_ZqX5I%xHMGDo*Dr=f8;jG z?(r+T{kPuX3GzdGP5CD1#lk_hqY9TyLe;tanku_c`A*pj5wd(gZ%8}gYaw&5&Yr9g z`rF-iVX(950cJ8ILM`CFE9kyyR5MOgICmk=4iOY+u8rW?dd4iF6j9(;@D`Q4_o&X0 zxq%L6vqP!7%qP!i!Z`dS+oMoT+lpw-tFVd|Wh|*j&W?Zs-fO!5?9>O3?=ph2%_x3= z9>nABNQn~XiGTC*q$4T`Z^8)^hmlwixT+9rU%l)=f+xx5ZmYVFZDgK2oE$_221)Q* za3jPZkUWj>SaToG68`2DbZ(Xl8*{K|x6$@^^blA4zO*{uJl*=b@ZR0v`cT}obkus< zEX$%W=R(4C$}mg(MP+G)^KTEtQvPHmu1cFk5m{Mr$7N+_nVt_00sA{%Mhxc6P}nyZ z&lB?j+8#8J(}W~f7Rg=tyyk%Z4LHaLJzNE@6hplEba@q{sSgZK+5GQb)pS4ZGUH}t zRx+QftU^{7x5R{6hz{sbiuqnsMhlG+?=xDA>;q`+=oV}(=eNfOgG)F}erZZRuGTyC z&_A3V%wkQjI;KI5`UDMfvy+TUI{~EYz>Y3coUY|UU0jpFRh=20|!|r zy9DWgpG`kc+QXHzXMV>RnthL%PQmjE^t&$+6EE~S5X1QBWqJ6SPd~8hQ7PV30A_a zlZBx*Z;_B0Xx4>g|Jl4k0F0CR3kXyX2-BYItP5S#f1Nt^Qjby? zMwM;33H8MO#Jur|t7{PSAqe z>oZrB3>X|W!lvYnyw%52rPX2>=P?pgVZ3&JTa{#Rr{2Dtu7qGsZA!L0c$~ZnlTb&l z6P1vE(sups40Ntj^kDg=8{w>?m0z|>q_co_QralKl!fNu5Od|TS^$TBjmrl|<=Zyy zd664x{fztl7^KSrEVKMx=2xgtfrIN!Jmi*LhPjs)q=VDKbeC)yd-njH!^xqMcC_Bg z09EeDHF^9N$e)L{Gq?;L)EP$yy7P>|AWlnzqGQV0Zvw$t!PhGT`e;${1T|qVBM>1 zrK>(C9HAsU2QrwtI)c~tkxR&;u$2+G)jXxQ-DDd=&#r8Hum-hXozS<&(H_tyyt{$p z8;HLJvG)uOOmP5-2HHJGXC@qC^0s`kwnJO{r`S=VsfR40C88;Skqr*7^o|(^sfX-C z4p~OQefCdUmv`TWaE7Kn;r@NIE0_a+5Dtuf%>zci{&)Ed|JsfHGy3%(y6%788*Qj- zIiZN4e0Vyo7;A__`Ge6>*Tzneo+HUCAos+AX+TRD3!wz0j98nw#jm8fn6hr;UOlx{ z@oF3d$eCmEm&_*8slLGeVMsn$s_R3FCxLx7e3{-T@@fUvwk#FNxlQ2z`1}L=)9WUw zyRTJ8+ABRNWuTsGTSdFl#VGz6Q0oq2TOPy)@z0H6XW*+P>$f;EPY_NVVy%_@LzYK*=0a_nH46GV z-1LbfEeh79kz1q)(Mn-81mLU-1kB+y%{mk7rOeHWPWQd}+-D*+6}da#3VO0KT?*VP zf~n?39nddTnVJ%QHd~;;(QYbWwu%vC4bQ{pk!vEvWJ9-sVWEbBhI|}EL)8!BmH-dR z$d`&Mj3rIv2vp(Yx;^_etB*|V$H866RFA?g7lc4dJTdLmCll|63W^Sx<@P`ssO;A$ zNMD_?#y^4-rQ|RR5EvUmIfxihs@pZND zBA2Gt3C9RA((unIu5)Jdw><~Hdl@XK?^3Jp-H6YmH(QeH4U_sPWDE`865Iv#-x#9` zDeltKOs+@c>RPE)FgOY@W~7-8S88ZUR~;2x2Tb?NFNW3CNT8huCNXpgRfDGePf&YM znw2Gt<;1R0vJ@=lZu)Lk$8}^-E~tijQw<;%a3y|%h9E!aRQ(ztJ)^)B$15)@dOhVF zX@a%mJTS5fgrt*eMhynL>;P$I5htGqcc+qTO&5p@P|i-{TBNxn0~F3DkE^>n+FOT8 z7`?MhiYoNpRaEZ?&qVN8@-#6ipGNd{XPjmsF1wqI)aK(+(iYFtC@+*#(wj_B<@n)G zVM*?C5hfZDZMHjCa@0E0BCTRs>Tx#F)T`mVUj&HF5BcvARpQn29uzFdLrNR;yiOH9RT2xP6ZM z@SiB-jBgl=rj5ZEH8#t3woDQ@KX_zu(CdvfD;@(gw^Ng&?>gukwPORP`7PnwMss{O zv)9C>EX5;VrS{kIDv^xyGTlUb%)|~3B zbrFlPN)b059^g@Gj z@G(rmy~k@%nsPuG`olUPpAcST&&{sBxaQ!uIfRaLkJ>3^+%ann0M*?nLqZ^ClAhnN z9Dd|R6Rbh5_>LkSO6-eG`xJuB7Owy1?QyMXbi8@{Iibgp-J_jNwJHl3i@}=5+b!S;kE9x?__Hn4_){4Pr5&@be+arG86;Z zQe{SqY!gRVj|QLhuXWj_Z^|xRW6xaMyCCiSTy(N|gaIjx?i>MX*Om}fJK7LcdvLU_ zH>8`nlIV7A?nnP|u4|7zQ6>x=b&CH#QqKQFD)G-iqPM@q)gPCvHU#-3%EEXs0z@`* zvo;{VX%1#WgkQT0MQSK*=hj@;{!&VSU2ANGf8c&>!+&GE#Ymfp=6hx1h~C<;o)e?& z8D&rFc-p^Q0h*UCTef(9{CNQC?PPbk{5~=gmVElM5h1WtF9-o}VC7HF9N*i_0#$(ic z9wzJkBD2UWRGR`ut$HV5Pgxc(isX=2-_z1!`k5YL9Y+y?7c0oQn>CDx(8@6nYda}x zxW~|9ha*@PFKtKe!BZprQuGfNWnbm&U$sRsT|3};#>Z$6_cbTrX?HE8(>Hdt2qMDU z+W}07A_4qDRTjT|e|HO>39)N87+CpCyjEIFElBFht&#X?eIhTvX0BSIGQN+VK>XOe zSJVNWvK@J0cO~e=#liy5Eg`xvGX+a8GYU5%Ly~kz;V)%T;`;4i-DkuwZ`8yu%t1l_ zEWV4joaJaDn@(~9b1pt(+7l0>LZC55=O;QxJEN?g3M!^mM%7l~DUn^b7=|A^H_(E3 zg3T0ccm0)ts=AFbYf|KQRXHak60CNrcPf^^kw}(Y<+wuTL85c8#;I{Kr&`^-29wl! zzQIuD9C|+A1s17Xs=->cU83BY`Alu3h1BfGcX&l7?*fU6#?xs`R@go!r>v}!Q1n0K zv!GtbGM~%O%lpEn&exKzj9w1*&ieL0Ps}%n8yuXs)d3gBD8)s3vxaz#4$e7Rs5nL| zn7t_)(ta5z=C?pI^JyxNajNP}5@?KFV?ue~(X}^VY8M=B`+IShvKtCr$?hhe5WJ@q zB38qjs>??Xbsl>k4&TpCv9U?n*Fx0&XPiDdakoW>yS>|u+44>p6f`xyo%8WfJ#nzCr43K{6rPQfN5K4dGqj&--SBpfs+h0=|QQ#w+& zr6*HlRJ7K8RM@Vx#2JhMfT7r)&|Olt<+q1*WsYBAQC14=lD6Y5@!udyN?way87&n@ z&b!#weqEMXcobkxlX*bL9~yCkmDjz-8=zuJ3vtAORyBM)RT?;zw!K49^C|PyKDZjs zX-*gll30Z=^`@hU-XwQ7j+?vgo|}IX*!^^QiQ0qNT#0Tn=y&)cS9&$lqnV@SV01xE zuEIDE`zxupZ-Xkt%3s)E@P?C?Y6}hAwc@$!uyW>>#3c`kg}E4He_DiO{^o#1Nx~Yb z9y55MwtXH&OE=uYt09T3roH}$mQ%jQ*=(z@=2&I?(b2F z37rD2DH|Ija9L}=oV@3szgnXU!Gl?I5@~L>#GX^(q`R2Dqdu|cH%SFsm99An!kvrO z9%k4)_n^*;i;mMoV+37qOd8}yqTS6om$y6Ww~P0;k1vzVF||0mKJ*?GblhBs^s(H= z!wqsaxDPfKtl42^k-fMbWoOpm#1PgJ9pbXG)RZeZJ2?(3$G!>XrIdd07_dB_7O#SW z0o&jw%F*oAHys*U@Z@d3D1L!1K(rKPzmja)5>;y5V@;oX7`oiO`R$+nmT1&k{*%XD z5S6H)gP9yuB5fYj0{?Mt-Yk)&|M|CB%qMDpOrY{*y94~I_^VmQGa-Gz-2p{)eackJ zqnG;)uP3ciQJ7p^bA-|ud#3K-it|3J?0Vl=3RkCC`Cj3yb(aTz+Smm8rQtoB3+*+> z5UKEoFk|>lBgnUIbZx7`PZ#zel-E!X7^4goZLedC@D521T9T+8qK+5EOpeKljILee zgBc+$1Y+p0L1lR%1NpWqp)&S$2lSNV^9jk*c#}-YQ`fzQFM<-32AZkWBQWM#P^-m3 zc;);Fbb`lD%%$U`vlqB-*N2StTB)4@X;IZ-FxH?*bs*NDPFDI`#o;VJZD0Wo6b{MN zcZsOyCkTxU8=a9s-v&J(7e&VhVNAa>jODN*-5JWK>Or>U)JzFhA8E4x=wtAQuR(cL z3Bm61hT3vuCFdE4=RI~Htv=}3U?9dESN-(4G<@2L4u|7z zi2Lp6NPhsK*S#2KSqQ2OcS%ZRQ&#CI&;??*)#VL-jo}24%VUO)nr>r+ZeYfrEk_Rj z%EB*$c@Z?>)Iw306~1pxBl9g#wdAoI!{28H*+5!j=j^dFG7>VR$0xU*aS9`& zLw7GvB4nB*U};}VB&bk^=SL&%mK(223!Var@H4HpRJ~# zM_@2iOIOYZ*;1WMa+=LWg9vR?ZK?q=(oB{%a!6TQ{ImWxAm+mW7O#>C@Vm<2;4~&N zC^|=GX%^R#bx~fk(k-EJJUtf|In6vjE>w$1X%W9$QpYN7!7%P9elw&i&9zRvQ<0Um zV`f2=q_)6OWTZ^9NEG8V(AWcY9@vH>)ko&qL!Rrz9AjDpmFw*4288MyU+=1WvKyB*#YtQ&?= zxW=7%2HhcH+=Nu3un3pdyIWTEXv)GSg~^?x)RCRTX2sC50gsFSD2VCbVj*+_qSFu% zo&LA(?|&4${vDYb)Xvn9#ZW#ckf8d%_)+FIvMeeSaHG|$Y9WZ=v0!y0bt{&-YCxzj zpAT-x$iL-YE%JVb`yP{>QtXx-)o1vghkS_e*j!mBUyBoGU+j#Jn;c%gZ!E31yzE`+ z`GQ{~b($S3PvoDI4^qpGLb##HSP7^qrZ=&SE^v4-i;*@b%_!s5poFZ&CHT?@|Ja2s z%(DtEPft#BWe!-o^(LjT3htv3nmG_&m31)UtRx0@!A!aUyI>MnmBpCgwD7Kr6LdCT zE+%$8mqr3D3*F5?x^?FWh$^5kbt-OTSP|2h;Z`OsZO6W^%bv-qIkh)$eD9Uqm5U2* zulx0}dnYLx=VBNaQgqmII*lhZri-&360`G?nYk^7DWs8NYA(yxw=t-5oQ|G1(Mk%m zFqn4W&dR6krC*@j8JW$o1MLgQ?rOYZPn)1^OU5KGz1fUrvUT4t07buts|g+n)j@!z zil~SzHX7^lSM>9TYskVZg8mJ`J?JxqHo?=62w*pWN7$^dfVe^Rn@})?=m3z!+^j$t zC5%a)B(*?HG_{uG9kjkqs1eB%?QDj?h?73~5==}dKQD?&`&@?4XyyYuhI&*gY#(n* z9`$as}?436muZaf_a!EGSN%|%d)Y%!9-`~(4#1-eXemQvvJ5X#2<;R zLc=4AODQ>b23G~p=bU)%gDZK~%2?o}c=;^*_g)x^En~)%_yJ%qO!UDpRW!a0z@={e z1Q!nz4?7C(VM{uar1BB|;o&I`8w<9XIn>n|4A=`p`&Tc_4D9HU8SBX)aeldHmnz3oCw z8O=_^-%8AVWBEZ|wvVC1wbb<0*fm^>sW1s=r)hS*5C_X<{l(;zT$dJhU?0g4q&;{H zAjT$Z)H-Q~$|Hc;`({_hozy@yNl+GE8G_5SuCEXq-%iLqAR&WiJG@yJe4TqZ9^^!s zXk@O{-@cnrfui;mqyG%>5qdD_ZIwB?F);ne!#7y0Sc*mluCui7qr8qOA~iI=Ip8YN z79pv7MX=A{$y;&^N&Z4HCCLx-;AO2VZ*hFUZ6ebOpr|p+TZ^77Z zU{gX_-G~x(O4doCNSZQe4`_}+ zcU6{7{GrUS5F|iy@UDkTmYC!07_PNB85hS)Na`^@Y~CoU#e)scF!1B;L}frRJ_9S7 z#=0lfz`jgP&01?&cf+o@)goae5#EWEg~Kl}tx=gb8+uqMYx;(p-!qovbiVjc@6pFP zCtRRluAj5o>9>s}PX67OM0VnJBHz%ZxZ|nN)@UTRBmH_0aFv;4)Ok@YHF zoZsu*f7VeiYj^0QL-&bKHT(@|Nq!QiAjy-^KHy}L71U;f%!k5ClY!pyj%sIN!#h~g zzcNk!IffDDZfj;dvj8yj^|y$z^9gXPFk4#8*Qnf==diT~e-zt+o8j$_3Xj1<)l z9s(~VGKz6^2Zrr-54T~{%eLQO4fqDN-poNe^E)(WD*`pspjzlkF%PS>KmtDQQ`P>Wb2U>zEfgDBM0j|0?K}XSwzgzl({FM=uUDlWgLug)Zr!2 zMrH@@5l=ZFb8M@^H!I%OtA`Zr?)B&q=LlMpudd&Iv8xhjjY)${UHgVT+7p~@uo(nr zV1Rrc#qFiDtg41!6XXf?S3OMDlRj16gcZH!@Fb+T%~1Kcfhv;e;=E)pjtla$6&n#! z*eIva?V_FeM`5MDY+|Y_2oVG?6H=yj0C9Bca$La#}C7qKsLzwk;S>-%)qV zGs{Khr4N2f17;BZ&3LznGqK%RuUjRazGFWo?@XaqSGKM`L7voG%J_0$&t9v_^tbVr z?otv~@@-bJP(WA)5wcL$r1fjp=!A%^ngsptjI8l;Di@L2Fq#SU#lOZ^-?Od!+qMb& zt>HfJJ;S|>Mew0C(>jh1bcW5htaHQn{bjgdyHE5Px`X9POo2*Ngtv@%Q!zQFj2|S$ z0{7pKLm<5{bMqYCioj|hbG~>YnqI?f08T#paUg!o?@}(Et{Wc5#E+Lw2B`*%vuOjo zSn_O>TYgCa$i&tX9Ljwn2xlsaP2F=;GxnluOW9#`^sNAItH2im*YJc#`5rxV+C0_@ z!_N#A%d!rRIGt9aSywVP1!+xJ()sW0WFi)sT;NhO#pHkU(rFN^q*qdbXCe>p(Q%q` z?h$Vq9#m5{NRO zbwe2fIszlk_8~b!9Ws!hQ}m%)PD=vjoyEv=xbf^IlUo?<#wpWv>)87~WaIL=xdZAz zq_=VUR@8?1enTmVE2!A6Db!MFEaa*L&z7nDS+2B=qrIIZX~*prX|LP3{2*-eNi~dh zE$N1|jN%coWz4|7&|J*Sb;BI-Z4aRWh&8p;R^H|29+9WGzebOi$^2vj&CWeT9Eyxo zN124d4?}rQx40P=uVWM=!cJETuO~R~IgegG6mCV`z;mMc$!rXvH*aYS*(YCg3U{@B zXAqs9Yv}+wY=1x^eYL82yAdcoP}nwV(a|V%Z>eyF#`^kI88QN_>4Y%2p)%d|lhiHp zf*UQ+)|2n`x8FgZOfZilYl@tylErDsl)<;%@8!=mkud@8xP7}HG!SwwM4{aAyGjNa z-kt|(!6zm2B3j0Vr^v{~h>74cLgQTN*~Qm9W%HYlb~caTjjUg@4CLIc?qZLCR_iym z6!5=UtpRh*S`1dKBu({FYV^dP4_pU*X7}d><0`bbGx>>KIqK=hEglRr*vWI@!Mn^@ zOS+{<4ctY_I=bXSH{*+x;I=JXA?Om7synp`OV&}1@oP5en(wlg{)QB&vQx88L;ke} z2*Q8!(dE?t<)cdyCL~Y0M}crOoltx!CsoFC4%#$V3Y|mg)eVn|Q4M8<3+&td1o3&8 z%?>1qmYHYvB*;aJ9KhEnIK=%C*n^Ml;9?%l?19m1MYJq@k=KJY?~6l#$&$+hr7us6 zl!?aJ70~2hx5KdORPz?=YpzYIGx58bPkK-B55pw#k8K80BQ1BfL!U1vX8GLec%OKF zH(vhuT~A;q2RUUi*B^F7Q}RU~UpDy=ZwS4dgGu!R9)qzo=?El^(nZsQs|B(ncT=qb zvMav}VO)md6tYO3>0t3nnq&|d%Fm-fBNuAtsx;I80n0K0VfldK@b|1b$o(qgJ0`Ld zY?OMI3a@D?TVIx=>9nAopC>3yj`kxIzG?df`Zss~MW&`84Gp39Yh-f4JJt zeylilVbtl?zpwJ}9r(|$Knuh(&;r5vUujMM-|6%J*s%P|2f<1hOX97hSvG(Kby0|_ z1^Zq-t?KXB_>)|uB4q*g=bDPk?3)yKlIfn9Q;^M25~Ir}_wV{qzE;PB_rSn1U89cKlK}2KmJS4Y-m7!HpOwKvzTu>YPclees8Ujm%i@8kfF! z0i^M#o^?svo*XRzlVhrsv!q9VxR1up*s+AG(517_Yj_0p{-41-Zsm zqqq)Ag6~Iw5<{|U*2*zV#=&WQFDl26{8Kp0%g(^D^?3Ng{lsf`Kd%gDNsI5o_z0x< zh%aBhYJGo4FI+c~pqyU#v^v_6*FI6)4R5@om*o@T(pV`&jK{V}GP67=*05bDikYv> z3^_`o#x|faswfF8ye}@XmmCSM%PHCtL#Z%eEGhA74Ks@oOxVyY7aNJ!t1CTs%NAl? zDIGYezG*Kd@$A*19wW{2K~9gfkjb|pO^eGQ)=0ROHk08`(~(n;GM_2p6IHBb%tc%9 zx(MwcAw3@j%zXK|Ga4tKmmOnZsCJR1lP7`lizS8TG6|RHp(d^NAdMuhj#YYzSWtKC zJ4Mtf%w2-V9MasuY`py;u?5MBbpSdrd}Gun`@C$R)E5{2C+mdgy85r(Ge0dNGmTzM zI>)7?-v!U*I<5eRu|%^CkbQ3*M|QCQs&ix-62AC)t?W#Ht!6R_anB^xBvq^tD1$gx zwqxxam9hNYm>f9UJQw;qDo$VEP6_ab=(tae*iG4bCX9B(Jl+@wtv_tnNHdQbYTn=W z9?Zab#8(p=;)lSH9Aq`3J{cthh%5NbvfsGYv=%tG3heL)8&s6LWdc}>xPEQ6altd0 zJR0Mgy9<1!qFt?~-7SAd70#DUy*9KyE8lR&(Ua|l`upInKWL@W?0l&!y=O`6PbZk5 zR>X0^)GLy|5j9OnI^^i9tD}16r_pL>onYZ*G7cb6FgR6aDAwH5wqA!{On zIohkOfF_oI6kHBcb_LLDto>JN#l{BMDe_kIXMo=OwA?~XOwC_zqB#YaH>iq5q{~{RC;G5$y#=!XJgL@17b9QPA%oB z)+|bsWIE4!imkPGt|g@nWqi7~d7$qJNei_URz(7-lazLiMP$zBR`qs3%Fghl^z(G@ zG-LT`DNnCG)hiCnl6x7Vicm)#VC$1kjPEQ+CkXw9^4_WjU4@FV+ul{p6QUZ@kJTN) zI8<0E3KKdT71x*fd;d1-GC)ml9WA&qbOg0Bxn-o9Ob`-{g_Z!bH!9H(s?>8u{m>SL zAtQ7#*ud@rL8yHOm=oEe=|r$*KJ;*@;Mi(sS{cYf&zJx>sA8Bt2POm4!_KDz{UQAHXh@ z^OPHhj(NY65qiSPH@PR6*Dh2>XH)qS^7Gyx6>5`k%#<>Ha=G*nK^n8XV|W6*k6QYs zjl@=gSw_sQ5fejG0Hwh zB8&1m%RV|GoqJuW#eruBbwL-PreZ$u%-UokLVU2%?=TTVtO)1~Jz>wvDch_7*x=t8 z_%mi2Ix0@*6q+g-F=CO%1^xJOZf~8xGlI>cm7b$1BC#gN_H(zqh1!aQv9LgpxNqV( z0{!V#;&SJQ{jC!Zw(F;5F?Lx(GvRQn1h@fg5bwIFxr|BeTwa=RyyGX+9|-yox7BOb zO}K3n9E7(kdiU@EA_i7d0qO!}uN+-+Zaoo>MTCAxyl!UGKwFRCNx1hWWlXsoYD;Kj znFw=!XlD&58RnK!m@uS5v#jw`4Lz%;s;UgKQ+DJHmD0B___N3)2IEd~ zrhrl7y4sR(N)8$*PcrsaA4z`S%j4bOQx{;v+h&d&e8IsQ8j=2Nixi zXZXwtkp3^=a!evYF`l8lFoA-*G-99WCwcy&O!x`R{-mMq(EYD?xjc*OpBR~ZoQMz5 z4LhSat=I8TO!1GiLpXokiDCYZKlj7W+~u9IbU5^J&-AI2n`T3N!<_XoV;ZE)to!`g zed*<2C+G0NT47-p?tud z=nZ1$a4?||XrR{>A}FhOk6emcCN3Ag5fQZ%2~}{BLswiY%(Dz;`2rMzhxQeMdgGna zdVnoQUGMYzx@O1`S^E>z)fE`k_oH4)Eqybqb7%dw%m8z8X36S1V%X&|T5I~nrsyEw z+B-zFCL@TlRDs^|R4n50dSnkh&aCU{U(nH2Jhq>x4Z7=nVwJiw&#Nc!d)TKY@!u53 z@oLStlQ=w4c2#iF^3w8hp(74cMf@JqgZAX1%`CpjP*4|T7s&t2^7k`@aWj}Tg+)8( zaHmP6PNXSUAAbK2X`)MjB(5Xy608RZ@PhxBqXyXEODt?+YT#n+EMa4B?ezCCGoUmk ziz0x+!%}r=VVbeSF#P;eQ~_j0HIMd7R45kLH+>vphCz`TCBVSFvRiAJU()B!D!#pe z;k>XL?H|O?UUxHoc|zjy^>wGy#}cnI-q*J&IlV9I{UX5{bmm;?WrgNklE)PUmK@xP zZIvcF6=O5JDL!dgB9;u z$K(A1=zRq~TTZBC1{lJc%*ld?E_>YTkHX7@5P;yKhd|zCyr=i&&3Mm^&5P8DJkwb; z$)QG=`VRD= zoW@Hq$Qg{=N-#F@kUqL5k7NB%rXxD>5@)8S-`+m_r`vm2iW~}7e_6a@st@gRLIBDa z2{|YGsMt1Jd0(Do|wfW zu2on*;RdGY_3kzB?RTvP#@l@ZsLNEwty8F#rSqiFs7zOgla1yua}RLSNi46X*Y^)f zs1~GIv?f%dVle1$qLK3HC0XI~jMEEEG}y(4*2v>&3}QLCh1mr!@JMjP@JXTVvhkas zuJpe=LE-25lLcC26nUX7#OD6$D>f|uWeA226*8hnII%62uan`-uphsh7%|7bw|h?a z@9?BLMLYxzgr_`c+NglboImS(#HFG+o%46pBTiETSzLG>j$Ub zm=A$RlI|vE=+D#r)6Drmej2yuc?C;X9Vp3)foL#M9ZyXLFqIB#YA3cB zjA*ekXV5lTsN5J3tNl{`K zHKbn$e_%xHMh1W2>^T%Pk#)SizW*YFqRbiBSclEJ#gT=PL?*!riwrg9qTh7cKcTs` z42~>MhBKQoIdPSsYMB?uZ29+*tD&Kzp(kaM#OAQ9SrsW$-iU&uXnnBJ*U;Rx7}|ae zED5^GICMpj``tkCMs}fJuzDpdpn;Cf3%blvNzgvhstxfYn2!tL-w(DWGH&zmizr^Q zIG%{!c{30#Hg<TX zT8VN>Gnv=|Ki*+fsTk4ecZWQ*joE8ke$6sxr8H0mE=$*2DzC#W>4Mpm%?Fnon@vP04>ndER{jA{!dW?aiyZ(Py*q=9@V^0+ zgUmwC>twN(C|Z{8xx96A<^Dr3jf_%PZtF!66~D)#dU692=A5zvabLr6o!jH8$>q=y z!AsaCpv|3eUV0W<8&$Vev_n7Q$~)D>T-Kyk={pdhMn8<)oK71sRjPjD)?nh$ME?t*Pzr;90QKnd-o%Un1gKRgWT6BM{sJcr1h{$+ zDO8js$tmu%g@(q}Q$4K*uW#;)UDKWyjr6arq0N`iOx5rJw9VvaGsUVb)qB6 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b 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 bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` literal 0 HcmV?d00001 diff --git a/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/results.bin b/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/results.bin new file mode 100644 index 0000000000..82b3009a77 --- /dev/null +++ b/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/results.bin @@ -0,0 +1 @@ +o/jetified-network.jar diff --git a/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/transformed/jetified-network.jar b/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/transformed/jetified-network.jar new file mode 100644 index 0000000000000000000000000000000000000000..af1c4b140b481f82156439504e50812fd74b6455 GIT binary patch literal 99352 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 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b 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 bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` literal 0 HcmV?d00001 diff --git a/network/build/.transforms/882f21e6ea520a732fa9828baf6837a4/results.bin b/network/build/.transforms/882f21e6ea520a732fa9828baf6837a4/results.bin new file mode 100644 index 0000000000..1ed65e03c3 --- /dev/null +++ b/network/build/.transforms/882f21e6ea520a732fa9828baf6837a4/results.bin @@ -0,0 +1 @@ +i/ diff --git a/network/build/.transforms/99b0c959c827d9869a7277c0ca875111/results.bin b/network/build/.transforms/99b0c959c827d9869a7277c0ca875111/results.bin new file mode 100644 index 0000000000..e69de29bb2 diff --git a/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/results.bin b/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/results.bin new file mode 100644 index 0000000000..82b3009a77 --- /dev/null +++ b/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/results.bin @@ -0,0 +1 @@ +o/jetified-network.jar diff --git a/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/transformed/jetified-network.jar b/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/transformed/jetified-network.jar new file mode 100644 index 0000000000000000000000000000000000000000..af1c4b140b481f82156439504e50812fd74b6455 GIT binary patch literal 99352 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 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b 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 bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` literal 0 HcmV?d00001 diff --git a/network/build/.transforms/b04ba1e3b1e5614e897560d529aff7a1/results.bin b/network/build/.transforms/b04ba1e3b1e5614e897560d529aff7a1/results.bin new file mode 100644 index 0000000000..e69de29bb2 diff --git a/network/build/.transforms/b37124a83829aa553636076c9ead584a/results.bin b/network/build/.transforms/b37124a83829aa553636076c9ead584a/results.bin new file mode 100644 index 0000000000..1ed65e03c3 --- /dev/null +++ b/network/build/.transforms/b37124a83829aa553636076c9ead584a/results.bin @@ -0,0 +1 @@ +i/ diff --git a/network/build/.transforms/c4301af1e3ac6220228773d71e26c811/results.bin b/network/build/.transforms/c4301af1e3ac6220228773d71e26c811/results.bin new file mode 100644 index 0000000000..1ed65e03c3 --- /dev/null +++ b/network/build/.transforms/c4301af1e3ac6220228773d71e26c811/results.bin @@ -0,0 +1 @@ +i/ diff --git a/network/build/.transforms/dda44431310664cbf70b6a6d8e9af52f/results.bin b/network/build/.transforms/dda44431310664cbf70b6a6d8e9af52f/results.bin new file mode 100644 index 0000000000..1ed65e03c3 --- /dev/null +++ b/network/build/.transforms/dda44431310664cbf70b6a6d8e9af52f/results.bin @@ -0,0 +1 @@ +i/ diff --git a/network/build/.transforms/e7ae8b25da52bba94522842530316ba6/results.bin b/network/build/.transforms/e7ae8b25da52bba94522842530316ba6/results.bin new file mode 100644 index 0000000000..e69de29bb2 diff --git a/network/build/.transforms/f73a389fe3a1ec5bb1563a53989b4562/results.bin b/network/build/.transforms/f73a389fe3a1ec5bb1563a53989b4562/results.bin new file mode 100644 index 0000000000..e69de29bb2 diff --git a/network/build/classes/kotlin/main/META-INF/network.kotlin_module b/network/build/classes/kotlin/main/META-INF/network.kotlin_module new file mode 100644 index 0000000000000000000000000000000000000000..f8f85d02efee0ee47ab1d5218f5dd57d13cc490e GIT binary patch literal 117 zcmZQzU|?ooU|%ypsH! v{Pap8?x57-g8aPV)MD=vE_E(REP8-Kx%qiQTtTTNMfqu&CEg`U3|b5TgOwqZ literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/AttestationResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/AttestationResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..793362e93dd59265ca9785bfc8ede3d13da2be1d GIT binary patch literal 4843 zcmbtX`%@d&75=Us?6OE^5e5S$HnE8fl0i0cnq;|~JQrOa&H){JF68#i?ORWrX9*K+B2PTR=phHYld<&F4|ZR?h;*=f_5 z)~%drSh@rupss4`T0BGC@u|gCJ!MPq3!K`E$fBGJ`MjBz5EQ68lQz=!Ie}QuLSlZr zBznfqr;X*oGD_00^<_Oj*gGrGJ#OZgIbWOG5lZ8x1Tg>Q#0{))f z*$^Zs3Ib>ls86kEnT$?;3-}fiA%wB79zHY*NLC@0(k)9s%!kl~2-BMdf;!tO5Gk|7 zvn_-cwDPe7B-uGOY%Y=H=CY$jqeEy%2cJDC5U^L$r04nZy@aer7Ra&CvThHTdJHmYy;xE&v9L znuaqPN3gj_-p%+n3Lrmb%nrUa!MtpTW8&6YK^M;m*lPb!%l}=d_`p9rCuU}x5 z0>^7;tYt$8ahz1pk5i#esZ{#QOQpX?l4N|UDpp>^T|_>oP$hn*SWE>worw?S^V$YE z{{n%LieVwqOF%r1GYSTAHiV~fL?QgV7{XC>^K2-DXK;*X!vs^)UeT*y>Lo{@u@np^ z84@m0fLAnY#mzZEB}pPkX>T%wBrdbcae<~1U&1sqx<+xEB>eWwyXUSbn8Ih7V~jI9 zBXFb)(lpJQMqIyX>y%b%AMqh$<1%%Nmjv2VS|(Msz&=5xu5lYU`lL=`+6*45 zw{vBFePY9`=x*I3u0P3aE<;I830&&gQ^8kA8LUyVA!yJQEa7FUc(RK+Gxco3d}%RI zz96KRc1~0JQf6*LAhq{~yC=JA)BqK|1epktU*5Sbw>{h(@SPNiSVUHR9f3A+(jTc)10BATL88<-6p3cCGppZuzs{#B zV-4zl_w4A$xBX-FY73ut<_&7A%j2w6^?=LAD~DGdJEOMIX;Q1mzM{$jndXjChw7U)NuwX%5!1~@4a6%`3_1dG zgBqG{EqZo+=T4xs6;}66w-sIWwiF6i6R|a_>hyls#!9A$+Pw2o-C<4yHK%9yRECwE zP;gQuulW*|in~_-8eJL(rwfLi&gw~HJ#D4w^*2;{5mL)@@43eDw4qNHvWt5DD!&l< zRcF#oG^^#)y!MFQ)v~T=c`d8cJEcNsc*fRJYZF?|V^e0#LO!KmOtav2PsZ$H*5DL1 zkpTHpN8bp)KxzIP1}=^DG)DOK>5TNed~A=NkyW%tT2q@PsqYilCXGQFJEHdyipK6i zjdtF{{%GGlv_;zk_t5!g+V$bLe1>!a=gE#C(wrbmhS7`>da9ns?`Y?=D+y2G5Uu#U zYc0>%MxR4tE4j)#_=!uheoyljc95Onr19u}n{XIi#KwlkiAFg^F!~p$f1-(N6Gs7L z59<%P2R!}dF%tY7qTOmi`|nl>O4JZUmp-~{SkYE(#c&PgN-O9ARzpx{wV;vzDd;$Q zTtT9Ng2{os6^(s>VBl_a3nyZ8|Ac%^e26+)Y-8YE1pRlJxbNBnoSut(`aYij$3yt8 zaV%prpW)fYvOlX=Qj7{JYCg~kFg;iX^YnXOI)9Sjsi?my~ zfQyu0#*)N1aWO;>O)u&HW8kgIIRRkA{&@k~oF@cKKHLW1mgOEBonDT-%Xf|55+Otu77nHKd7*cSp( z$LuXwuGw6aba{jt8p0%-UPfLe-b}V;wMSm z!B1(5pC$1Q-d%vO@N@iP0bLdjS@@-eu!Ub)XmY+S&bQt9$`%e=eC()&ZVS&?IA)>4 h!qXOxSXi-;wy3@V{{>XsKFt6C literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests$Item.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests$Item.class new file mode 100644 index 0000000000000000000000000000000000000000..2f0d60cc5b715b7384c76359185e99f83243c3eb GIT binary patch literal 3403 zcmb_eOLG)e6#j0{WTt1*Njd`wpP|b!OV6lbPlO$ifnJiX;_{Uec}h}mfdu%rhT*Fc#&WCS8iH!t4?)&(OGfBD6k{f_ZFRxTTU2- zT3&OiQ63qE+zHonqn8zql;`hXxDo}fw=%n(GqI#FG%p&3 zAGhqlX*I2eAFjIAh0ynA6|$b)a1=6gl`{&5=KWyBT63c1z;?aRvOUj_V(mifq90vs z)$4qctJ*=WQq$3`uy5Uu>aJ(4-Dp^@7de4v*R6^t+JtU3%%ewPpenYe?=RZ{1@ht+ z0}=~&G{8U~%p!WxuTW^Vmg{cyb?2szT~xa~v1FhN14U#ps8HJO00Z6FTY!ck1G^Gx zgl0#pZm4i3_RRl_6+N=_NTI0A@8qmA&PR5u9?kh4Goe+D{9wTj)}3HhA_pA@#J*gS z1{|dMD^4^QdtYI9d1AY@3I?xS-gJ^ek)dz;9`o*K=iwM#Q1zQP71ka}_ld{jC_`k9 z*AkNzelI@`*zI1}&Z%^;(o}maCK;H(qy*&sAQ@k zWnNAy>73B7Tr*??Q_E4vYvj(2S^8$LDpwkE1^+C`V6-LW*|NV4Uo}MsZ z;jmEbEBdM(uFm;2=?dkE$^(5Uu`iF)242OfBIdxZkqexV8LXgqImb3;*I8NvomOnR z{F=h}fAttP7<;+yu%vYMsss?oRNcn%x1bE;-H*|jok@@&oho29(qM0U-NY+ltGH{@$h7SI{%WPR$& zX@*pd<@eX!TL+8UUahE^cW)K>r%q5aS$m#qapJzxH+w_>(O_~7hr=-%SysR$lG7VnnIi{Pv!`Z!R zMxFoTOM&Pk*Cee-_a|^)s>S|9Jca61dN`}+%siQ!nxstK%-{PiR}xd%T=MDQQ#kp* zrePj7JC)RmLigr~e0rV9HnQ0ABDdjGyc=%lGHFiTA69&lByiun>p2%&jb$fzQxZ!O z!mrx(B|C8CoH7rlSW5{~dqzC~Ilfik&ewXbM0Rz3!EUD5M#Wu@p6qG;l4+0AoTqJmt6-xtC;=ZQ&U_b5bCKk9fo3OUb>Fsgm(CO4Fs? zTqo~f&*aok7@nN|38O#ovxb!zE(-%aFga-l(2rpj(JJ`|5(tc8A5R2JRS`-Q(BP8W zt5GulV}Tx`!1Ee^J;D=B`Z}QpJ-EB9?nf?eJr*h(f?s^BZc z*NDF-!906n#N59sx^3X>hbKmEw_P>JYdVwf9A1kR+LHn+I=jMeVr`SVxc&I#)VJu$ z-k$m$+SlmH+@87%Z9ybV^Y=3@#!QjI^XTR|E5bC0AG(pv>wjZ+9$H_VGa{Y1?LZx8 zEnTJ_QgC_6`wJwb)CRbUDo03*-wKmT<Vh9bMHPqm7 zZ;-meZ*N}1TUa9I+t-n);H?VYso*NEk;L^1-o<;@p+dZm53XZZh literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests.class new file mode 100644 index 0000000000000000000000000000000000000000..2a895f7c7b982c0ecf2a5bae9234e882eb73897c GIT binary patch literal 3152 zcmb_e-E$LF6#v~#nrt>pvrYMEOHq_c(+1dpC_?#=7Le4mK#P@96>pO@-FCAZcQ;jD z9G>_Sbo5bY93Obl87+*=I2GP}^zU&T&)wao+c2mzVUpaBbI;fB+;h&&AAkS)8-SCj zGMuYA4c+H<-Dv2x;eY11YdUY5dXwL67`E?NPW`SvwPIA)=8U@O`7ZZO$DT7jZ5f{L zrNJ1qRld%3i*)GMmRF6cpGJZqb=I^^|2#u~p%O5(eACh^q+()hfnl+edbZM)CnIIw zXiS_BbRViXZe3qB{AHJ$wx@I3cKpC^PoH)C*_LHZFvxBMLPmz+r8UR5Oj}=FZ|J7& z8?MbQy=?m~DKM)9G|RB3D&i1nxXxYPFo=<1G{O=K!9<8Tpv(&;b@EKbF7zr;kz+^{ z#J&f83X;fE5IcjUB7@x-NZ7-W^jA!e;X-#IBFx#k;cwj?L%uN9X_evDGsG8kGYksd zaMQ6Vt&S6>s?)s7aHBI1CdRr(`BkUR7lfc&&%}MF6uQ!b461-XQZqi`Ez4*4uFxgw z&WkL^5-_aFt^=#$0A3RAk5KXWPMFpVySEZ1p6m?!w!1>eIoUF;n&GBVP%tJ^{?K#V zN@1%s&&eoKE8)$iVb>Ur#tUt$tYScS7@AOV6vu=uy+g7ckLS1;NFEoGCm4ncQ&E+~ z@I_bx#*>2a4HbtmDpsc`I4j&+nR04IQ08S7r*TG z7d0-vY#E|?ao4ykRQ+f7Y3o+;HZF?pGs)27_$#7w2D zT4qQpkzB7V#Rce)P4{aG9cE9+nmUvmU?q0->6eM=jJAuYy|iYu&e2*E>$!b#c`_)x z8%oM4E&b$&6cvC}qx>Sgm%6L{@adzDDxN@wbo7ifOm_dGra^CYcI(MFwnnWgGjG;y z?zdb@@BMQv+cz6V*O_9DL?VKvN7PxDQeMB7T z)YX1rcPQO8@AK-~3~xqaYT338cgo_PXHZ=!^G?gH8dpqVXE0K^@QiafPJJvHWLsW5 zw6t5KorEj6O>>HFMW$J%nR+%7SfX8p&Lx_)VqSR!ZTunn$BVyU?@vUN@Bxjz#F>IZ zpDc1Ten_;vp$zt6fOaGu*rR_yw3d*@M>MA?=>vm(7{pLyPA3}W2YSYT#@-(TZec_C z2GTEdt06mmyEt|&gI6J36jlzC4RJkv!^tnchAbjb882?&wb0HTI!b_LiJRIk@d}wU zG>gy-KgQVNLyYH-Y@j40H}JYPwt+V{@Yc4QVd#bgqFwT2Oc^LFC)syy@Y+ Fe*kYSAW#4R literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/KYCStatus.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/KYCStatus.class new file mode 100644 index 0000000000000000000000000000000000000000..affddaa7477e44e0d409111c10c33df36a743cf6 GIT binary patch literal 1696 zcmb7E-BTM?6#w1L$1dA#o2H0>wXGT?Sqcf1R@zV`O#^O83C)H$ojfp0vf@Ir%Valb z-|)ph#3vs$ow1!dIvF1OM>(E*H#%|ZOOm-C=iJ}evj)_pLEp4iR3r<+xUxff?lBnbq#uWN;;&X^d@CPSp$L{gTO$8&_Dvbxck` z!Uqi38zb&k4v+0-keBfxL#)~MTncA0op~RjVFt4*rty)CNd{R&Qu&6V_&>SMj2+v( zQ7y%`<@@;&ABH6yb&K~+Uc*h?=B50EycZskXQ_XZd}+5*J1}d7YOVA{!yGi8fX@l> zFE1VPG8FUI1*2x{ZXaw_st1L=y=sMWtzjVq+#x`*QmvMXHKVe7P%S;DG6=0pAt1}3 zR7+3kots(?nJdH$RdMEO$h=3)^=4Z%s)9L{T$)gjR_Ei-Hn&NWq1D+$m^)e|QbNt{#*aZ>ulNf`$c zv%K|5G6sncKJ68vN{G;(^yv#XUPJwrt`hcWk5in$7kEzl?GS=XToL_bT*ejgF5@Z^ zBvB=<1O*egh9tdk)l&iisC`WRh8qKX{0kQ*%uU=HV$vkV?Wjpfe}hj4kT`}3t6KPi z&&U>cpPFGPwMPDMsXK}IACPwRNFVd29_>Rn^;jQ^rXKGjXXv2Kk1o21~#9cvbD1x~E2Ai)i z#S_UyB4;U;z#66O0cPJ{$3bWNC9;+&_-4^*wSG}NRZAn!R zgzbiwa9vLcMbty9=BZlOamXuC|C1(tI!O}am>30z$)GPboP)Lu(y13CpRd$b*OqGK zr%7aBL~iF8<~Eh`!~51>Q)~$(i=HS3MdACQ@2O%m038&Icdj z+~g}6Q_JyE?9E{J30ZWb-^<1H5i>sc7^il4bAd&562nVixcd)FO5g^==;4hpq)*&W zv8@=UR=TdTJF?<#+o4@||s@D+Cx(Mt@9~zSt z*Ofup5n(9lw2W1+8#LrII|`T_)>%JQ1@pA67|oKK*=Tu$a?!470CR-nB+ro4$cwI* zv|FTEBxyz%ncIi{nE=OadZXV!9(U+X{&v9VLCO@y$ifeTi^Jg97o|EJ`HHWS^ww tavRVpn5y7n1($G{Ag)w!71uVvLR`o62J{d{hS+|Ay%JE7%cTL^pwX(34V>!i|zIvpyCDejx9xY@%SmHmp!38XWLY>`i@er{#5XS@FcCKx5}-G zV7THhoZ>RQQFF!o1b1D_Gy)d%UFnlO+KI9He0_re`VHZ8#816V^H%e4z#@8xz1uKiZ zT{Wdi_l2dyI|LoLb+c%5Rq?3Ok2N%39HV!w{o5+{cv&dnwSi?iT^+C1lXD8>E}6G& z;pHqYrI3_U)}4xHh$p5cqO`Hm6CCL=KMl}EU8G)=+6&b7_Ru|xdcC+$Jq2@FwUKhFZ|Wr$LW&4Sf-#E$BB+ zgTB-?bQ)tV=*y=;k9P-6oW6;-h@2czG(xTai~Wr2zu?`Uf=&dFs7=tPs}zC^&Eqlk zHQWnkQKa$rXl9d}h~zPq$NPDFfDcK;bRIXL2jQB*%}rdCn3cFC(JOIV;*P{e5>bh? c#K#hI5@XWWxhpX$k&>8~$Vv=JWF!{;1wd%=`~Uy| literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$Companion.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$Companion.class new file mode 100644 index 0000000000000000000000000000000000000000..cdd37ff9637e5d47a985c32ff752f58fee0aae92 GIT binary patch literal 1203 zcmb_bTTc@~6#izm^s=BWph&?xs4XfB=!20{%7dAf5vSdqZFZ+s-gxqd z_~fHLk!WHX9{o|qGo=cNzIb^#bG~!V_wAW8XTN^?^aa3O%rdOi!@x5_@5;c_(!36% zrYBm;Yl*!;Y7_e5?w-^UZWFuN*ftz;d^WH zrD*%6L|->iyKcg0Lqts(Ef%XTxSiwZbp1H_BIsoqX8%l$VfY7OLmIIojG#;MpySY< zFl&<5fPpnB{zh3eN8Zjn!@chQh|9;h%X8g*m#4`g(A`fTzIDdC`wM)IJxHbb;kzWy z@rTKrGr{K)hwqaEmcPKP>e9cxS*pLe44I!h$&mm3+Rio&&A$}xNQP`hX}Q@BYBG8% zYCc6qDq&stRS_wR`@zX4ZEaK_%etdtMe)_+rC=B;Yb~RaFXC9zM!Q>KJF3fdW##gH zX7v{y=IQ+scBpX3qaTCjaD$L%r%dO-%mLCL$>AuFwP|1pMY2Oj1<1f9G>k!t@E-XN ziP^f$XJnr_2N?N4;H1Fv34u&sV1{a}{3#&EsFP9hZ6~+s^k34o1?2{>t zvlyeCgPT<2_DES=LmsyXb6h7OV433QsCIlCP8s<_ctw5l zNBHP+`BYu@qFu|feDv@5>T>y=B&1x166=duE9dNUW}kgKdw=`<@%OL40XUDZ1ttwA zr}?^_l{w9p{)*!+X?ot&^7?8{+P-5s*;Q?F(_%1hrlq@V8ZvGyNMmVAW=+p`b>DRC z_}O@W7((Fj=#t}GrmY!{>lA#`mY%k_oYPF(m#(c_TDsunrJWhobMqN}V1rw++$z|H zlI3Yv%T#}ICtMu*lg*P^zzgp2QCeaF6zs=W-$cX#QdT(eC`SK z{u>9vs1@iapV-BTFzN;BlcsI@mju4;s>G@tI+`EKxp!J?~(FTODUtnL{Erng+a#b>SzqTlv z(W1n*3dFlodlUuziDDm~i=Yne0=3FBfvKu&2=;52$O$yiM?;#+(iLcovlR1|R3$7B z+sjCS{ieO_EXgt;2%PHLS&-d3w*>kEWN*QkVL4yOj!9q7=)TU97PkZr2fNm{?`49Ul}L+h?9N!P0-jEEIc@Fyu(q)&1#F%pLcbWc&FR8 z;}-$V)5Z&yrO#WEW}kc-o8v1^a&_;-D}t>{xu)T1sS0`B4D2hh z&Gbxqa%6mXWLTiRD;8N!C?;LBJI!(Lwa7E?y*tD^8(&~0QEoJX6vhO?L?V@d0&PK%(elK!QmYT+ZGyhxN*X9|rE723cb3_f z*a%GTB_x?(uh~OU<;63@=K_0^Lh^Ng91G(EHptX?dU|ktXoRg`BPvC&qqvEW8t@_XD30QoI!_!9<@=*}7qev>uX0sXT-JN zzhOm&G6c28q;5O*YR)NmEaZz-K|x?t1je7C!k_ZY4r7@ueaOi$YuiR07Sj#gHC4ly zRs&md+RWOzUqs#gtUqIEOwX6UHIMQ=K3>SpOSimpbgIDDbWW!1Wz#b$VDL#RVxQXn zby9croa9S>3tKd0+tMAfbkCD)p3Nh+VL2XalvlIhWCS8wXW$Dtn};2~5(C&v>Sua~J2& z`42Pu(M_TiEJF%Y?j-4sa=HBxLbJ+TWoyp^yhC&Ea=#QbrGh+_ptVBCxO)L+)p zkm#Wc_mSN$zo8<(f$~+L>hJ{*wv`Wp39-Vpn%6kia%Ge~t-+Vv3E>33;yg#{IJ2|E z<3?k literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2.class new file mode 100644 index 0000000000000000000000000000000000000000..881cbd34239363eea2a1fba29ffccb02676f8421 GIT binary patch literal 5147 zcmcgvYf~H78Geos2+P98B(aTiBefc^z$|vtIyEFN2pr4Cf+BEAY3eT0K`g9xmE9GF zUfo;TruX|jy{Fd?Zl^Z1%(P5D^h1A3|3POmeU7ve3rK+sosMSr9PN3}dwJf=dC$Mz z`0L*QJcB<7q_cKG^K>gO3z{XpJGQf|=|w{;>T3mQdA4ci*R;vNpW&jBkNPVB~hveY7TLS<5e@orzzlOf4Z&JN5rEioW zk$6^ja;cm^$6g#`IEvU2gwQ4sjTcMvrjflN*91ayBQYFDyIMOzXLyT-8y^rDIt=bH zJcNf8$43O>J*jF=Gze&FA44lnMbV7Y0%4_sz~mtmsEd-ok+>tX(pZs>z|lA}r)Wx* zp8{R`sVLB9SS$9j^s`;y>0sRWNpda#y#Gm{zYgym1!Pptm-08Ir{{D}XPSuxfzJBw z?_c(~ZZ@&tJ{5_@s%Q} zggh&7%v*HqI}_Gb-7rgz6nLbbXBncGF&M?WF(fb&D8LO9IJx^knUXJ;r0coylqU-j zu;TRu`1plfu6mMas6%69&6C59qpvAN-@}mg>}zthQW%L%&)i551ixU$FoI+h!yroC zK|}oYK!o~VD%p26iYs_Q;JJWGZ{4&AQcR|rlTDLHN7hYkK zI}?e=t*MQZZZL+MNJlY_34w?F4cn^~6W$xebxg5x9K?i!r$GEd1k(bq^&Fxg4IXH3 z!-zS+L8dC`tte*j;(n(t&6(Y%X;70Rm}9mEfb_$&)@P(E?bQrDSTIFEp zN~IR+BzrYTY94U4;I5$r`CE(SIyuHJ zXa~?Sd;)im;N$pY3{Rp^?XxjgRA26MW;XPF|GgH#QV;JVJ` z=0M4AxVQe<7r~E+&z@_rz_p-%{94F+**>{m$6qnLhM%y(Pue-=W5>A7%6v7Z+s8S}|OM42HLxQ#Ka6 zS0{BxFGyb7caX(WmL;8}sk^RZGktu_%9^&z%O^u*(dKn3nz2hxR$euff}K9oTYChu z`wa00!@`R&BZgfk(B7`@UFDH?tn$d45m%e}utagk&p3MtP314A6213vEYWu#9f|Yz zaWc`|8D2-{A4wwcbH1Ockl_p-oq_83K|pgo1h@dTCm1?M3|aY7|2 zSCq(qlao?nbp!9X98UZhmwv~8fGoe2JhdOV-MfM3HgRQ^B**x4Y+!61*KTg&`fT3@ zCfAWkZ(>F*pFA(t@xCp*Ke2@mB*OP`X#?}Ko#EGUv@?89@#(#{iQH_*!a9tO?zbCq3v>sX;l_efK5 zg94Fsz8E#TQx>CU3i6N-xmOdcWGTUIAzzECc$Ow#{0NyUw(W`Dz6bc|7D9i*ryk%l zyT!KG#J1zJc!m792cHXw{cufe)!wpLr9&Bl_F-1U&vUM}3SY-JQurpmmBP329d35< UU3_m2VHe+b@dFn>bn(i60qiIaH2?qr literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2$1.class new file mode 100644 index 0000000000000000000000000000000000000000..89ffce8aa65a55dd460a7723345013a834c3d248 GIT binary patch literal 3937 zcmd5;TXz#x6#h*}$xN7;6za9= zn}5K6;BxtdF1aXcY4y<`;;(SI{7yOzy$m$si&-n@>~m(HeLH)9`~30u&%XdTgD(Xx z8BR&_b-O4_nl1fXj=P}gWm7BbOC@Rhj^z}Wv@7ctgJm->-9^)oNmmw4&zJ7y>7nGQ zWM33Q;J4g@<6EY!8IJ2zeAAYmHosWXOq-0hZfW_7SC)1mrM}Y!=mT|~U2~W>GbwLYr03Tn{g_A& zbYBzr^Z#gBop&4_uFW~h`&EuM`*Wt3JSmW=E1co72jbX?cnc!fC9s28dh6c0C6Er0y#Yjqbr?o@ig z=Xa}tfOnRJ$v?v+=z%ElZ>&OKdKCiG0R*NAf$8!()Jum8-yAXCTh4Kd+Pw59+0oRjl%~@rq^XvzDa6$DYLI_==NU-CKc(IOZVn3rNG{>TV-m#jQ$v2Qb9jg4@QRvIEr($LpKcRc}cp*?vpGFc>f8g zx}T;-SnV0S8p8kx;cIyR7%+|Eb%E1Q;dK-jSZC|{s(@9Wt08a_Qbg-}=Rjyq3}a*` zhA=8{^igljvTYW1E6+rhBexA%RuB-yMFGuVOHKGrqSIxPlw@aOR^D)2ndn@t{m#TK z)1OP!;PXw{gG*_Z99rL3F?L`X`sM`uI*jl zT3H)nBQUj{kW7lbWE(}b_sdnbe)b*(*ex+um+C>Q5fV7Fou;sxs@A}hov5>Mu4T@a z$5Cj0dZe_da9gOMSq&gWEhwUa=Wm;eViS)P`j}KJ%gwQsUNL?pI-U7pu=$7}Lwax3(PL*5j4ZtaO8&Lz5N0j7u_WFPff70fUcv z4;$0wuUB+eFG;@2H?YOCwk_QuOZPmcLc<`Ox9rHSy1aAm?pTW8`e)ygSu%7p9jt3qoY64ei$0t0~ z%(;v6r~F5m`RFFG8!SNzQ|@Hwj&ga=Lqw*Pxysg_`*@4y-r@dg(3A@D)PlALDnKF# z(AAnMC4G}BmS>=$JR6)3U)KF4_kP0*7gi za;PuR(vs?-3-?gmEWf2DzlHKupqlU*4|W6x!GyTQwVIbXHgaVIo;Kif?nH1DUvQow z^&GSFh{uh@44*!D9xu%B%P}=1)$pRn+&S){6LZvK&Y&TS`79Q)uu#gvhLc4ZH+k5D M3y&f__#P_%039iQ^Z)<= literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2.class new file mode 100644 index 0000000000000000000000000000000000000000..267240bc80522079713765bead57e98dc0a09270 GIT binary patch literal 4405 zcmd5B1}kF1rgYm9k(4AlEEIp@j^trVc7}BY;E~kZoAbjx?4&D z1CwFj-uVYS%)?Zm$Wo+g#>FGQgP%cBd{=5Ivem{;g$L~FdvAB2KFfE{`OekPzy0_> z0MFoSfpa;hp!NL4!_u=QuPE)+DWkBmY7Ez;mB?Dj z&iSTed-`OsIyDl!zuc&9tfo1ebBeOAqQT&pW7BfUP})XLPBjF`Wx=s^uj;?2&r7dl z`6GReGP{B>A4Gf6uq^5G(;}Uf!_V`7YQrcSx)Tq$+~9fnPDy(HUU(1pUJ&@@ujtwKOxSgo!{OSV z>fBuwX1l*`dZ|Hyk$r&~u6a0yLx>%Oh7N%Pv{Qju;LN^shHOv_hY?r$jtHcBGPM%h zr9K!)3}a^`6+OkM;@ zFalBFm8-JoK$S%Yg47xyiw+bUh;txZ`F6axXH$-w*EgiU;udW(f1ZjbzdFhYM&d)9`WM(Gb$}VQ-CT7Mb z#s#{1LO_n8A19;e!+<~={gM{J$iy&+A(elp1P%x0t30cWJR{KQue;8rS$onjt&%GR z?ho5jrscCZ9mU^phWXgQ;rCRzsi6p-Qwe`CieZfG#kiCv(biabmlBHLMFQR6;m$5I zQ*En@zG-<1gR{h-DBU&3Ed;D8u+US>*KqD1+M9Trt00DPOhhq;NrA`iw5SaGSKhF) z%qlr?IVX#%8Y4I-py$}zlD?DdcG(#VvOBpV>84C}SIe+FdCBzGlXV~t;N>0LmsQD? z4_IX?iVUU&BFu5sXay3@P71{DxK{0+5xhcn&AE~eR-oA_un#br6Tk6Tz|vR?$-3Ge z)C2VN#;-=wB>uf*YVQ}>rP(N=tOVv+TiLKm45qybq`<}6hTp^ChR!r0BXIjq(7e4_ z?o8-vIq$FQ)jCwjsVT(!7_wMUkS()U1Y4+pjNlD+$IMK2VRU9}f^A_N$}zl!w-4e? z7%@DAht>DwWUV-)XtJv1k5E>kB&#!F3DJaah|>a(?`b3gGlo~Oq2y;qTIk_rQ>}Q|&{i2%_tw?U=+@P!c1){X@vW;59!Rt%v_Wl9q_x48 zb3c7aV$xO_+MyA{cI+z!r{s~(PBeD+@V#Yy+|%|7Jl8;ocS+e1{F5%bcYa(5So=Fs z){^aR&a2HO6vGGj7tcat&MGT5G3AgIFBq<=I`4uSc&5pkdE4+S?W==Cn%kT1m4RoN8QZHH$fV^C{o8bME2%8LtRy6}?;>Q4B_9 zDyYdIsIJ@4>JInc#05Hgjqj__B`bk<)uHRc#fk}_3konT0Bo^tRM}BRJG8)qnrRYD zVEQbTDm>%qzWy6{eGA(6Sh<1Koo3^8vvEkQQ6IlSzQOEkb+fftD`u4smGI-E07a}2c&pYtxk)i7r;i&W;Q`3sJ%cpp~- zI9l)}pJ;dtUvW0LdW0$csK*D%Wu8OvIG$MMn-gkCso_bF3HOwTZWL(8UPem>P6ovc f-hrEe2S0-n%KX^FC0t%ctA{Hd-u3XFhqd1Ug(etS literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2$1.class new file mode 100644 index 0000000000000000000000000000000000000000..c33561c86a8e3a682dcee405f0136fa81ad4f69e GIT binary patch literal 3962 zcmd5;TUQ%Z6#h=Qq=d9wq_sEOXk&puGo`IoORRFWvD_rkil}umoQ8qPOgb|O#Vh*g zPw)r0Ts~Eoy=d1`^wHnqPjI>X4hboQAd$Y9wQ_E=&%T|#zrFwX`{!Q(T*8+E-?5y`sy4M{GmtY=(k+;VjJh&odcJgT>Xs$_=!Ixs z5JKR$v1P}%Oj|P?*U9^)Ej?|ikkw3^w6<<(sl1ny_QIH+O)u#Eo79TrcHTC8)3H5m zw6y9=Y<}OuZOF07Hm@tv#JRCrOqkxL>$LQ8lp_1D_Kz*;1zoe~0c|q9Bn_Wu0=@sm zgdpk#PL*!#Vnz^+0*wjNHvOvtUw74{balV?*L1hi;5&Kg`DJJC;>`Z;8v=j+PnE0j zrK>xwO0e>wmAQ@nqUlA?3nXgtYjyVhVKgJ$ga8f-9H6ibcMCktTZPerR>iVSAljAO zBM)dx7zgl72o2~Es8?1AOg@E8)m$df#A8F61?dVjN11v#=05XKAhMT+0tZdI;4I4$ z00^Az+L?acJC6k7Rd{d1lwq09XU3(kFX+C`1Qxdij#YoF__ELMRvjUbuP#jf=_x@A zM3#S}2#WC{D8@^m7$+#kb6apQUY&Urr}6d195wXY1fg>5|4^2)_&dww!MsB5MQ&S@o!z052 z9bHvC9Y!zCh0uezKm+xX7Cn*-<2?FQBwi3`DTyb9<}F_dxg>DNUv!<7NqbZ`t-LD* z4p)0v2I$MU9KuU@Mc_&;s9oL1L0q98x?xDqi_$!nq-ajSdn(-O-cP15?Gtz{gnkgz zH!Hv{*c!we0vGpTbr6H}@b+RW;M8XAKFh$)91LR^Bh47XsKDtbH8aW5pV6%p!p zHDpdqO-e;|rUYzb zAtW&_5M+j_$P;MasiZ*b6V;04Jczf6@rEm@puk{P6(HH#PV{&0MIbP<7n4McrDYF3 z<-TT?EdfiB0`H~}b4-<_qK5=7?Inp~Dh(z?FcWry#8xY7ETxsfQ*N57NDU)}8KwU` z>q1GoGA)SrSrL;HshNR^p%IpVP1QE+4&xR+Y{Cc7!#IJH>OS!|kn0QM9n7gMalCEw z$`*VNWD5$wg&G>w00PyrTGa9WU4nXGp^x#jzSbC8Uke3}23ptGjt9<*YrlWRJPa%l z%9<0p?bxeXC+{(RYb*b|xJ2OkJ~Df^MmmT*EBKJJz=&-hbC^gsbk|f>Vnz)t!YMOj z>;4AR9%Swr%j0^k^sQx#9rr{&o0e|r=4LO5@3tMM231T z78anjRdr7rGg}^>Ht^4MAms{IYI-}6x5F2`7r07vjF zcO=D8G87vt#WJw5I)>w9waRq>A<{77xK|>+rAVdD%p+*yu^(~qJ8t+JEKVDoj&hyo zd4yN_{m>)4&Z#}Nj)7SHeO&wrZ`NV1_Zx(I?(>kCWBuWmNEsIjzD?~9lOQJ55 ziDJ_d>v@3p))Dvt=?7S-l+sd`(gL|f%DZJL+YI8Sb$}3XlIkdf`btuoVm&nB0Wy{R zo67u~$X_|C0iW?=d+8z=5C!hl{5i*Z?u?S9by(v`0H^T<=XtK4V|1SKco3Oq`-A84 x!aTp6QA1P>FM5ofvmQFJNIB*_>XKMWVmS#5*(7W@>QauEJ=}pymL5D0{y%%_jadKy literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2.class new file mode 100644 index 0000000000000000000000000000000000000000..5caed368c7d7bbe69c20245e1104fbae55a483b8 GIT binary patch literal 4435 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 literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService.class new file mode 100644 index 0000000000000000000000000000000000000000..a1a8bc67d1569bfb2f7d28c8e675f112daf27f46 GIT binary patch literal 6073 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 Ns0Q{Gi8m#@{{nsdL8Sly literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse$Item.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse$Item.class new file mode 100644 index 0000000000000000000000000000000000000000..313a0e1bc052081f24ab05220304a55c3f54bfa1 GIT binary patch literal 8078 zcmds6|8pC~6@Pn@rL%O7&$10s5{Q);Y|D1!#MGn;2FFeya-0wn2!uexSvqXRvShSp zgDHj3FKJ6#px;uU^cyW-+NLdSu|vyDCk>hD48Qar=}-NU>6CtU_oO?^N)*~*rmcsZ z-uv9!x4WPBcHi#t!9V}_CnCCq{=jH7TbfTfR&mCjPZe$F@lts%Wi8}W3)b?yU35x? z(#&$|s6CVCPT6wurQ%VWFO-Vh?#?*&e3%#`^R%^Or3&zox_kPxopr(#U=+G7U(7qV zGx}TalxJ|vDd&qbdwj@vsZ_A7;wJ^I@34Yg%ft_!m^kKG&LVFxJlAL6aYpee;g~u4vF3+v$f}J}xy?n~b&F71ZwojGHGpWjg2KB2~1B{vxjUzHp2JKL2icwUcZo~}Qsm8SnF$ca6p);`f zh%t! zqBEQ4jPAIm4zIZWkrmA|wlg6UgHcCspO0@hF3Lo4grrzwQVEH&*(+n=+RGpytKojU zx;zB?SP;MtR%l0bT(!DFA_@>hSB)zS??nF}Z{;J|;BTM1xLk6v3<2&JtYR6$A@)RU zmplCm;_bZ|_A9se6{LV?HsV)q^((vm3c?-rIvrZ0VGO}+Xl-@XY{81M?> z|5Fe$zL~oF`hOmmhv;Dumuajxr=<8}biGHQg2or~1ysi{*%5kF6bM5cPkL3w8r5l5 zZ1oEZb`kY-u+Fkrzi+u7_ZW1V=7iG%Hl~3tRaoE=)pd=d>V&T_H0fwTIC@MvQiTVO zunP;na=(MRh&^whYi+>+{5YmG&{ZuqK$fxdDa76QfPEI5gv7+&2mN!ER%-hL@(i}K zfv%cT0sL9;JJ2P|1hg+O+R{65byB`0=5p4c9PJa*SKwgQ;6;PymS`6$+gvPAV(2Mk6gyx8!w*O zr46z1OV6^Ht}|2HqzwgPx*6OQ+{lKSAcb5hg12Fxto!6*(DQlphBjUT{5NYk||}E zOewo$O4%h-$}X9ZwF;dW5R;S*OtzbY7*x|P-P|Du%MQGME&!raK!+eR$U7D3=h)HC zO1RNFf;?&86x7?y7;txlKD+~Xo0=z39f=0xPV+h#yF>CBUk~H<%J4k%jWBPoq=^T| zr5Be7pO;P2i%W`UzFB&4Iq~2v<_#E&o5?;eH_8dQx$MKYnz2i7g>cZ)a(G~gh21+W zqutlCt<)>pVfrObP@;Tb^Q_!<`Hng4G@Fku7M=XOohdHmc^am0&~P%Yuh2*`-n>HT zWIVD$qse%5g~omhZyJf`2(_S}E{cG;R@zP-)J<{RG`CU@o_)AQ_Ji|5+~HC*3Tc9d zDFgf8q3x4uRJ51Ip<#S(M1&;bph--yFUrZJxU9<9O^UlI<|i=0Z%9gl)v%NVm%5Y$ zgUwPByhWrW*oxBcA)ktZCVbu@ol7gZxRe_4-uR3R4{haKOP{VI;c zJlg24x{i8k9d$Q!bfB)IWUZr{8#>C=bu?J(sJEe`$-0h)Y8@pTIyzW4YQwdT1{yk= zs_ST^*3piJjt(IvQ%|=Fmgbu`@2(LGg1+iRByyGmoR zHrwcEbvpjWa#>uhv04hp>Nb{3p>gAT>o%6@-?;I8bsNi+Z`}BJ-NrJ_8#lhcF55D- z8#jKSZey9wjT@h+rm#O$GKCvAez5MGW!g4we6sGaWvVuAj7vVMmq_bhp=OGDoKW@n zJ-WvNVvPpK%C{`&DhRW{9LdW0-6D0uuUDBpnJ zw>ypC(=U@Qs^?Uv(d5eAhdPb0EB74kG)x^zGmMhyAT-m}2GdQ@x?F829fCIIYQyPp zr_n}dTy-R^LuKTu>2xzxt)h;mBT$1Cbu1lC*2#fY%Bmb#!N^-EYDA+g_}hR#ObcHn z+`W7izv8s?(tGsii3>C{)gQ~R(!7`>eqtphcdt@8<|w&G$V*C23i$~o4+{B7B@YSt zDJ2gJ`DrDO2>FbX(?b5dl1GL7MJ10xHv3lT%lG%M(pSAiR!5VU>Ksj&_-LYn#XfN7 z^P!r-3%6}Hx6VTD{tJZzZ&E~vtMuX{6}-p8F){KJ4Kj z;)QCzBOVS?JUs2;An4mw`%w=ES)Ttf4+n9=&v`^sBN6u3sN*C2Um`6`8V-K!BOG^? ztTA@vzeAPP^*W4Kr@zy8=(~s_(T8q~8J(l=;T>{-Vd!#$x}5BPgPMZp`~OVZE7TM? z-+z&`Ln7D%`1dY-UxFga(7Szl5A+_No`jzC>4VS*efkjeA)h`Beb}duKp*kxY3S+K z#fZ@PD3np(%Q5I<6+P%hsDoO<`ah@x5o%R`Mr&)86Qax2ZTm94NMxTc@&2@U4X99ujyU|`cL!1tC+Ry0#r+YX($mt}fqnwU$x|h>^oQ`w4pVI@JPH=jN)5Dz3 Pae9^0b8-MVPQUmUfWw3! literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..8348bb03fe80dbec07a8fd63c62ad0f4b3f9efb9 GIT binary patch literal 5304 zcmbtX>vI#=760AUYb`H$EnDCxnBWv!Hj!;e0+?5DOf1JH#HLORZJI^i*o&o=vv)-T zZBzQ9kMxy3XvuV@Go9(<10ULHF-$YlQu3iQ{hK;X&)Ho`Ygr*Ge9+!=&%NjUxJUo~ z@8A9o;4nTEIFc_Fv#w#Un#HVby6=>n^{laBWjBm#Mbmamh0^M^?1H&!N!Kx4t7I>j za-(ERGXWu>Uooy4*#ePgU%7n6%)1Fh1jd`=>GPR(oRX73Odx*TvMu+dz|o;Ok8jzv z3fVc5JbBSac!CyP$Ff%^LztZHnyaQWIlLrry`J{?TyxrNw{S4$n#IYJ|A+AXb0udr zd&P7wJBDS;tYO zUmFl;@vcpkR!o78(A7#aJNN=h$Nhl^nZPpCV00I5*cQ2rrs%85E`| zstMOA=iQPsZ#e5gHVy<7`BI@k)j{PWvr{$rWvZ89c0h5%y~c@7z8V_d?&=YYCXvBG zj)tENk}$QdHAxQ*(`)7}@cSP3m<#6qfgu1W!|a>Ea3ddsa! zNGYL;#=7Zony1V$Rw?!;n=8~%+ZYU-pDS(@s0NM+yfoBgW`wv*1|ARFq2VZwCozc= zQX@lfzmoKSIro>60C!?aff4u<=F z(08||dLUv_!xCO&DX-U2R;~RM_GQH*+z$=MoaQ^9`OH>{G+f0y{LH>fFJ#GG<0m@& zLIxKFUaia2Cofci_R1SJZ@R{c;TqHtl;YJ0je~+easGgeUnlVpLgS{WDO*AH%t zC6zA4`#rdkRP=!w*qZb(JN-pXDr$-@FiwrC4{xen4{l5-W8#ngkOnPQJ^j;P@q3V5}fc}|} zZ?PhK2%98W5WJUQmEnb56>hITycH!5D!Ha7s%rX1(=`Ux(iL@Q?9*9HR+=Lru1%@arcy-lv)*+}_LlDVg3ho|b7A4E{t2oOs;s z=y6QohtvjW!14ZU$Y<7RHfmY4R&B#AJ2WixE|hK8Dw;X_swFL&m|v*P+N6wM_HA>P zZJsL^FPqK0!YH_uuur#oP|v`smTQ`?V{%j5J-@f1z_pHQ7C_zFItHBM(qT2nkpdn=8~gsYsj zdN>-1>u@^lX*@&(KcyQtiC>J?fsxdX`{>Br!>-K8U36#q?xH_)@Gkacx}$gT^q=XD zf}hd1ha|_*O|pBaIrX9weYEQ*+1>a#ogeo3;d3}ZJL2r=PT4~cYpQq<>&jhYfQNRn z(6_7wzaU(i&1Ta$Oh!km8cmQ{ z>gUXqHk9A)Bi#u@&_d8h(y0##n066q=rp+zkb=ygNS{CT~HEG=qVNAy!eD{ z|Fo@_*L0h@?HM?bo;zj(^#xp%>5PN%V_LZzUdW969BsdwWrN==x~PiGnIX`ru+`X6Y77^Ny=+&ZB^ws7b?yA0ujOzfhp*!s1mc@Hd<);c z1R?32`OYOgC2>IFyAqnj_aweA(c=Moyf2*+Qk8gC;yH=uC5}k!ml&5Al{h4ESmKn# bX^AO`vl2OpSqWF-x<|M!abDtmi68tAsZ9EH literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError$DefaultImpls.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError$DefaultImpls.class new file mode 100644 index 0000000000000000000000000000000000000000..3ef964311694b4760328346b8ffc199cedcccf70 GIT binary patch literal 1010 zcmbVK-%k@k5dOBsUMWSOf{G}hptMz4iN2AT82phl1r0SAot>L+X1;y@`~|=RaE6DGNx2ugD^sqeKQuPxqOW*g9Hmlw zlbG%i-<7U!w3AP)HFou}d?yBp57U0)0x*W@o)`+AP!j&4)02@8P$V`L@#w|2bWU_7 z!?Swp%)i@gw2sray;Zuq$*|Hgw#$3ccdSs_aiO*G!Yith?-;)`ND_(~^9Rb6QD#_- zjZc*3yWuyJOc0j3$sUFm-|nPIm658+kRaA5*7m}Qt8=vbRWy)3h-lyQ;a{(rRh)c8hP z0?aeawDOj1OE30?7u3%}IxN!Su*{S)0|pjn4$ZspUS-}rhU&=;A^2;3+f%Ynup&lxyh<|iEd4|16)CUfpbjGg tMcg4vS#>zd$ip-=Y}eT8PCEOIQwZ9XCR(`~ZHd6GQ+2 literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError.class new file mode 100644 index 0000000000000000000000000000000000000000..300220064bd22526b02f7c040e867eb0ad0f8e14 GIT binary patch literal 958 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 literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin.class new file mode 100644 index 0000000000000000000000000000000000000000..5314a7565ac8c09b7fb3b9d8d4d80a0c10626595 GIT binary patch literal 5416 zcmb_g>30*?75}}_HkM^9$(A=Z*SJPrh>QyaFgP(zQUta`F(n1MWGoH#*z(BH8#&O0 zkS?T6x1%@};MLQ%~F9n;A>8tYB*Rp!e>*^X~rceb@fi zfByP60Eci#;83o(l5q@s!CcAMrt@~Ow45CGUc(O^IF?8bL@Pe8RFVXF}jidb%ol&M8^;!uZorE_;W{ zj#bD^ThbXHyeRPga}su9dOLyYpY=jIKH(a(Z@O4o$Sj%8e92%;Glp#!9mBC0*vxFv znJpIz;{uwM*U>DnXSwJUEIYHbwvw@I$1K@KA(ORLK%|wE5wr-Ta*Jke+2=iHl#CUV z2L<-my6vT>K@0~k#?Xp*6g!X*2-@V<(Z*EB>nrocf{v7c?%xWjqf;P|Ry+}OM-fDi zKyq_pVra&$CTQ3lLz@TfjUnYhpQ3Oh=U8i|fVdpPKJ=@jpXPO{&t43j*sngOX_~WW zNr5BVN&-WAy*3=ra;37jWXb7j@JuC6VEnjYL7j0{GjcmW4h^z|si@)?29g6U-Q0`2PzZZ-P;2}JmL zmN}(#LUV3TJ$TYj0n>R|pjn;sE-Hh3eqC6lke>PO3q)Bx>>sA>9IQpgY7shAJ$9%T zIm{c7E3RG_c;|T~a&Ws>>F>|Uf@couwmpccQ1<7&J1g+d^!7rrx$>WevZ+aDYqa*AFV2z1szCa20)A#auo5rlqa?NgXq)Kdr8sHmmAD zgG~E{SyN}ewDA~T$2leNb1b}(Ue!w}Z;tgg((AQTqMvtBuK^N$Ltt0>@{?s=QZiMM zQl?*{&U0km@k;@<+@Qa zUsa;EeB~;#V<(NF7+=Koe^7kvG1 zyho`R)8jjPf?Z;C*XfUMD}#2Ex`(hJyq7Qyy)Dis;;tmRpfe&pK|L04A<`R1`cHXg!c&GJ3=^YT-&qAI7d4lr98(?Zm>T367zHVZLcT*y_!((CLwfF0i;duPU9q11+c04pRJaQlv><&G^A2HR4AxuG(SZ2_SHX$8D|)ix z);?ObA<&|Mkd48eXvpjacWdUC8Zx^9-J1F3N)T(!c7xc6If2Pa;A$D%z%^oM94yYK zysavA*LdqyHK#wZnm>Yese18Mf;>H5VWumEnXVLOx>A^F_2;%*nCVJkrZ*JkD;v_d zWsx@FRTigr>rB5$r&Hmes!>%}ghM{uJCh1We7J9xg{%{4;-P^fLBFgOHdw9e`#9~n zr|O#j3W_y1x3o|hr%%6zm$<8|HT@CJUV4D(FHfQu`|ARkudcxzqyW3aTOW3 zbO)g~f|ZX{Tp;2Vs`9lJ;=jCBS_B$L2>we=HKPt{(b+~QK;+-sR;@v9{65qtPuAon1SmN6f%@W^{ zXp=}u>~rBxcfBQ{OB|CpE^%1mh{TH$;}WA1M906m1kQno|92;1;+ryhZ@~tKh zdCa3#@g-GxNb=xAQWepPRVkwnsmkA!N~L>uxetzrLe=eb&-8Tv`eS;AzyJOH4*7;)-dlI^oo;on11=3XUtMZMj--rJaBfFqW+~%goU(^Y!^< zJL@G75jeKJs#)IlQrAb0`Mp#ZoS3&FAb_2;(&{r^7%}Lkm&@k$&bAXwwiyyFi1Jw-)X3g7gGZ^}*KBfzAdf*iBj9 z9C;l&_V8jix#UqeasncwV=tcJ*?j_0Z^@AYN1rzI6eE4n_A(W0ZT$oF=mb=f@{>mB zHzX>hAuv%lx#KbjjLimaQW!G=8dVgX0^9YDRx=~j%ySflY+?0Nfw}5*{ckbvAXMLO zuXx8^X9k`c5SN##Q2|g;bauUO!T!i9<*34L^*^DRA17e_E8-# z;WQg~dDG6afB%Ea-+6|2o+Uqq_W7NLpq%qL|Bw=QL7=;zQu{b179hZ>SFpU{*LNfx%3Jz+0_9BOuntGHVQaibX zw>7+l%M{F_E&V!*+gD3gjyg+wbyPQE^9~`fiuQSy*|tlTH|cmEA8?oakh)pHTjCB| z-4R1Uf$>cRxZ|@$j!%Z|f6Df(1umoltA{2{Iw4HJ!q5lD?c`X(~bVk9e){!D|BVric?jaH(` zhNdbT%14@su2Mgu67L(JEn=e8XrnFdBdW2>H`6ix=w@W1!RQQ>-V{XdCOSg%V!ESR z69H7o!LF#P8I9Ae8wM-Wy3sV9EISj*HXAJue~$0rXi~pE{seug{;@*YqU1cu-kKf} zJS?cbc~&41zO$a_#Z<2-ffDuEjrWs0vO;}!&veQ4oV=ZJ*Bt54)8tI$xk7jTjI-!k zUa3gulQ8fxZJBgj`(i0SZx?6zDZ`~-$XdBMtLQKvPWElqTYiny7-*XDtnA8^wHj{I zGp=hF$8wgGHXT}Rrcf$o?F$au*&V8!s{@Lo)QzJQ;5Z^k@k4~@Pxw^OsL`0>_Xo** zO0-+6Mz0VWb&{p)h%|ggT+!DEbPc7N?x8h(7rWAfchHp{x`Uo{&mHvs>g)QP zzPk`3OO0enr>4+^HZ)^5@n6tdnsAX8Jd6D_G8Ld>D!{}PBZ!3P8t^5_wM0Vbf)w#z z5`8#8pctG9$0CV?I23wWt*B?SqNXhr1p(}@ z31AWf7(j@`$}%Xjf&>eM=3VXf;JOPvpBQZkl$HK2RfF<}g>V`?3*SNskt@Ytpb zBn zX=+6`uFz>>{AOrd;9a~&Yn-raH1g$mE68j}qB|0RoO6dN# rD6!WkREdKUha~zW4oD10q$LI=h9nM49Fcg}7n}FLmn2R|ND1#hFo3x; literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse$Currency.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse$Currency.class new file mode 100644 index 0000000000000000000000000000000000000000..0bd98821a2dbfee42edc6165e9a4c2c6db08386f GIT binary patch literal 4815 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 sE!iW5dO~WZ5S4Ic0HS8YQf!&#PXBN$i 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 QaoLyO@aLjG=UgoO3uR5Ho&W#< literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi$DefaultImpls.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi$DefaultImpls.class new file mode 100644 index 0000000000000000000000000000000000000000..d04556229da3730c7b12e5b64a23beace726b781 GIT binary patch literal 1217 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 literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..d1c95764c86c006b54900ccfca1b90667dfc0b13 GIT binary patch literal 1011 zcmb7DO-~y!5Ph>DyCHm(6bhlG&_hl=WCKE6Dz&PRfJgzPH0>oPv$0yUi5=}YX}LxG zn*Ih3Rm8DBs;W1D2;#tU@Ob>JH}gE6`TOtc4}e{~BkV?|7h0je%U-CZ{b`ao6n%9I zwPf@oY)x)cw<)=AG|K=)SUML&5q8~WczSj&BO4$`s6HL<$lCsWq&%V2h>h(k9Ue<7 zI>L$%1g|&DIgOMWKQ$mw?C@VIJ=L7o*(My0NAHVX*~_%Xqr$K}9_@HFy1t}>mz&A^ zL1Fg#w_i-d?GW}FPs@K?4kx#kfjIqcN7>Fm**8k_U9B7^i@pb(N_uj{kIzjuAy!Uq zIwGUmrm%8a?WZhB2T3Azr0$88{Tt}QWeqb#IF#m@75d6DX_;6)C~cPSn2@HD1-BDJ zn7>208e2l~iy0)5JWyRpSZ@xrRXzDlu{!I@&srNRta5|oySBrO>uf)5aOZDM>|1QP zz2Wcxc@)~n)iGN~v5q;+I~NOeEaF8Q#8^V9jXB0k#xi4t@rtp^SYwnK>x|coH;j${ Dj$<&F literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$Companion.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$Companion.class new file mode 100644 index 0000000000000000000000000000000000000000..cb7612c0ce4705e9cfcaf08dc45dcde985a7fb65 GIT binary patch literal 947 zcmbtSTTc@~6#izmY|Bz%xrhbtiV7yOfWF}+SYy(p)dWgoLX4SiM>_59EZLn_9(m%2 z_~ffkCO#O_M}L&@%z~H1C-voAzVCc}j#YNAF$k^X*CBS>$VlSEJ*m zMW~LTF@oIRpwS1HUdTHe%(gDJMJ9LCh+!fWVOKW9;nuz+{A|<=_y1uEMO={RW?5Z%QdZKT8%`ON!kx@CmE!n ze6FluVSAvBie*b>iWFN~Cq@`rzzpj&(}MkI^4zW&J^Kb_f&F>%V|PEH^f9MzlC?Cj zizTw<(*lgc1AIo}2|}#+9(hjQipy5aa>4v5cq*_o&S8=`2e&Bma?w>>!vbzo&T$=g x$X5xUqFR3sP7TvF%+xT8ISO&UhIw4r1Itjs!X8Q)ybOyO+zc1<;8Jcce+NVc=SKhl literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2$1.class new file mode 100644 index 0000000000000000000000000000000000000000..220689c5867017d1c2f0884b668a71c94ea7e085 GIT binary patch literal 4259 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 literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2$1.class new file mode 100644 index 0000000000000000000000000000000000000000..45476865627deab254d36fe514d0b1a6bf75c805 GIT binary patch literal 3496 zcmcImYgZdZ7=9*PQbGy=+e%w)V;c(u%9h>*#8L=0He4i7i)ia+8DL?v8+SL*dOQ8> zpYV&v4}Pj1d(obw96$P-JRYA3OSmKz$~h)yXWp52=Dp4PJ~My)^V{zLF5?@4n}(Cu zeBEA@dCivon&Yl$dch2)rlql@O_#We%u09FG-Sdkx~{YhQ+kPuiQX`Tz>Ca^<6EY! z8IJ1|ebbhnw!E6xOxu^Pty@~Q=oO?rpV9NVdA)ygwOX=Jv<=^MY)=~vsNR81{x)gD zj!m&eT`e6rpV`~);j)!e(koED>>upUEbFVfX3-^WBDX9JpK}7;uVQ={wF0Mt9WQYp zjCz6k0n;}9L4ms+`!Kg6%;g}xBnbVTcLcusUqtUKz7+=V$_qwTSy=BcnO@?8z?B09 zA&N#s8xX=_fka1okHnzHC=Q`Hf;z+mYL#w*k%MU7?TtV~!j*%~s7%Gq$|-@KDzbM})`-j%7ssTp&+ERMW zs$t>xkudo&GJ?j7JU?Rt3VQrfvG)W}&_gH)rd0j+R7l+wBpy-5aTm2^>E~SCWJ&9` z?f9jd^|bM#W$8If(#Es@waoF=O0MdxEKA~vOSz`uY3VX~+YjTMz_CT?4^K=@%uJ`p zQ}?qo*~!%SNNR-9=%^CeC_2y?K|7KHb+k)b3`07KZk$)?)+5jys3&|BEng{N7B~4z zuCq2_kLspXbfv)2Y6rrM}KZ@&kCxUA#SvIlmB{&qufB0#Fw}_m za8uy)7PO}=OD^hGmYFA04-8pQWf}%kK{IsAiu+Ey-Q}s~WqUj)7aUi{+c)a7J-%l8 zOYyR+hcLS9h_fmI7XzTV6+sGVfiR&#C5b?5wHl6XnYMB3gfT{BG+apoS$X?#y|NT` z#71CxFCha-0`eX)E?>MeytxSA3QQX!EQcwYyAdIQ{GOT)d_7k^;t5PD6L^<222oJf zg>hHlPu6K^?+Jtoy-`eKNDXy?md!2O&@+@@ zRTwE$Q?D8j7FHBd!~G9TMG*!<^S#WA^+)xQ^+%CVTPU{v=tSs(xb^3^Hx%OLy4%7aDV#;X)owFg=lpYtLdcIKIAtr>^;e@Aysg~QXT;kA}E7j4}y zf!Se}jIlDN7Xn^$hA+ACVm>F`VCLjhk?*j)OxvrbXHvk>7Qz$$cXKCoSIHa_ zrd|vj)$wEYlH-vYL=FEyG}-k8P08BsXK49}V*#ISe1nHpyv7k%;RrubqK@JiWfVz= z5J8JdC$3g9zejCp-{f<2jU|79_9G|x8{Fth``bug?|P0ae{vn~CTpKS`xV0> z%y$2PNcUsK?|S?KsoB=|pW%Z)xTglIY@1N~H{xN8pB`ZYtZ}Zjq_qzT^ifkE0jRI! zgR(M#Gl36^x{VBNc)&gN>=W!C@(VMe@ivJTu(BvjxpJMo3SsO8LbJ-$u4l;7)W@9L z2$2#`emO)jsKAIIFdvrHD9O9ju;KzW*C<4ZX-;-M#YgK1{ftka;{I+e&1Egk_-vb& znX;B0&TtSLVqLe<9OX=Jprs+%MHikTw_AQgS$+fMt02|k3vO%;HiDvDj#d99+ggt9 z1)kR6E6#*)5?`~QBXykVcf#Yu8*_X?;7y#GBj>bg;;MPeW6qrMa25vj%+H}F4VlJ5 Z8jDy;!^CnLE3mlPLmoCodT=}x{soRv*7^Vd literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2.class new file mode 100644 index 0000000000000000000000000000000000000000..dea39bd38a9cb052d0f7fa62c0a994ad8507f327 GIT binary patch literal 4034 zcmcImYgZdZ7=9)M(x!o?Xtfuu(WZq4Wa<5ey#dwO6eWbUY%b7aExXe4Y=2g`9vp2s#8hhD_V^&j`HP`xpv0MQbBhojx!v z@ZJByx-kmtc(_U726)l0O1}<&!Ss@Y0!N?71u<+vY%@aW6iD`_tN2?7Jd_y2CTxwO z9dUuMk}YuINhCMgBCt8>N<*3@=?ZK~5|;%_D!d9LHd0EU)3i&@k_>W5V1I8j3=cGK z2@JI0-H@ImGFP0xEPZ`e_jLkU%n3Z*6qtb}pGQ_3YsV=rOn&r>pzC!}NO$IA+80|HwE@s)a`gb=}9{(|e=9Jep(rd4#M!1h*QBmxiNa1;k| zgwU(2Wlxkv$%7FbQ<&Zy#c`b2sP2%O0j}1jb+ROaAv(BDv$a)rtkO=VeADuj?W|y3 z1?kQ?Zhk`EC`ylLn(VDYvvmbLxN-Gr27Cgo|s!mQ|pd4+_U?QH&xj z5Mi>bMkCPOtfWAERkcbFiQqCTo#9F9OqO`LW?q*8T?n#xR4oH{&h56T`FEq3&N7 z2o(;h!dT(*DE}(bLjosSL7~aBEtD2`aYG>y$QULurF5GY*jClQuFHh-CDluW8amVf z7SJWNXrqR=OtoUc49(K4o|WaU=*n_5v?Emg;w#I$Hg$))LxZ6~kqQmAz4rUJ=M*_j z+cKovj(t1t6g`%mz4ak$9aUz=v3ja?%500k3LLI0g6rIC>Ut5}5{Rxoqy>&t@!R<7 zU47HGf_)6{;{!fXhMievS$D=^C7ae=Q+3HnHSo!iHRo;JFSnXb7C&R@vR(+zwr2QL zA1mf_(hYWYPZTLJFVl9(^h`21zuJGHUYCE1Gk59M`A&PA(pGd8E{(x}# z-t+_XT~7TB?FVl1Z=^Dn@9*IHOy2_>eTd`NNYf?mwfh# zmf6$qsCU#GHGxWo64)6Eq*ui<_iJJ+v5BYp`tRY*6@-4myZ7+kqr~dSk)IN4)Wv7I zCbnv4S*+5bYPl4Y<7Q!Gk}8u!o|Dl82ifa(@GvFN|RT literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2$1.class new file mode 100644 index 0000000000000000000000000000000000000000..569739a3287acc2182e8c3f383933a17a1b813df GIT binary patch literal 3695 zcmcguX;&Ln7=CV8QbNimt=(*+jRgWFrK<{7S=+HJm4qsy)?v5}1CyCJGYPFL>NkIg z$K$8!>83qLIezpvc|1Nh8A2dnXg@gT+-2^4@4Maiect=mKfnDB;3B>g7&n}3(%0>b z%qDH=uQ~2&QqP&eQd%0z$#ij7O;ge>n1+mJq(A3LH|?xS+l!x%4}>8Eo=&bhzGd1; z!*QLwZ`#sJt`xFK)Aprn>sB(A_j1x+n$)w4OZwm@wIaEbw+-KPY%e(x%m#)w_ba%K zIyTwnbwxUKcCxnWTUyRaFGv1z@ao{?ieAu@7OhIoEUrky=bAv@vnU@%y}-%f#2yBO z(J0V3WZI^GRbZuiAL>?lx>DDa@0xa|?6y7JGT3uV;KBbSY+rX!WmI+e@B))sFvx|;M&}4BFS2Y#31IsDB82n@ z0Mk!^36>P9^jC4M_7g8^(s47%73nX!x=EnWZQJpSNZ=)>^OmJAT9Rs>{x34eSCpjc zsZMsH6!Xh74KJycr#JjC5(0+_c%w73GjnNedi-{3E_HK!dTe}*4(Z;-xl#1tYy`dN z7igef(xNA{D9&L(1>Jdp)<8T#DR22o$VGt`f7x}`X6y;wwDPVLIJDbcG8Ql6N(7hi zn!ttH2w3IYFy5d7x?xDqQ+^)Knem*!^7BDP&oiX~Wer9#gf}A?RPnNjpnG6h7;g)( zn(V(oQ9u|YOp9$5N?@YWmHP$K>a>Ys4CBoh#e~4Attz5fmdxl@is2~7?;0|v(l?Cj zN_pL~V!jjWa(UHR*%e!qcN|y7x=MN56zsa9GG<6WYw;YunfFw(tC?pK!9!Jb+K0_j>zh7ttz8hpw%evU>?fQV&MP3vsq?P6i1o0rdl~G}QByd2RPNj#Z zN5>hMn;t1z9K~&X(u9wpM{yh{)OjLiC^rzr`94%tLvz<#1QS6beK7}bk|g{o>md!vLp`W=ZaF2bWM1aG1k$)t`5i%6TnPM3# z&W_?3S$)QN2ocgS(D<)J{78{Xo%9nVrxFiv;V1s^8z~J%PDeN&>V1OOx!>{xZ*k~I zY+yK1{}2~`#kD%j_x*rK-+e{Xd;cl4`HmZpG5rV6)!}pYEvWw+u`p&&kFf*3;93VY zuftWu*+$qjHf7Oa>KMnFz#=u9!AIT znpf&7U3(wnJ*s=3>m{ct5u_d{CCM%^?1?4KwpkP#tB^Kp>?l(MuB^A+wWzQ<;Ag`71{?;Aqae=d!7@a3QF2ol2Uct+FWq~`VRTo#?s~%(L ljE62PQ;xZSIt?otRyA12YOvvG$l+5S_Ta)JOAo$>{J#fq7AOD! literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2.class new file mode 100644 index 0000000000000000000000000000000000000000..430737ec3c7afa5e4019fd9bb64dfa7a4ebfa8ec GIT binary patch literal 4178 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 literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2.class new file mode 100644 index 0000000000000000000000000000000000000000..df5b42831bbcbf273e44c5960ee26631cff03977 GIT binary patch literal 4317 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 literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2.class new file mode 100644 index 0000000000000000000000000000000000000000..61f52d39ad78ea4a670679fd7712b37321a1fcaf GIT binary patch literal 4295 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 literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService.class new file mode 100644 index 0000000000000000000000000000000000000000..3b0642892489cddd30b86ff72de4f43cd3a218fb GIT binary patch literal 9271 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 literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TokenResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TokenResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..fa2d929554a6e7de79a96a46fd8ca483fa9d93c8 GIT binary patch literal 5535 zcmcgv>r)%o75`nK$0B62637Pp0Aq?kpvVtw$AE3Xj$=7MTA22^&h%41^iSxY(P{g;cNb{^6+4}@%&_;I zv*(;Wk9&Ui>M#HN!yf?*;U@ywe0eGB7^OLLDO)m~+vUn))>yVY(KRzapS@OIG)pt4 zy<9HYW&}b&UocjUY>`&kmuDBuyc0o#z|b?eQXi6DYyLt4;Zdt(IcEiy`zC9yuR0a0 zG&k~TNpjmA^()Mo^=f|<9LjG`?daxtXP zCq4EHH0R4q+{ioQg+j%&?HC4Jqd~^)%v&@#I{6Gc$iEp`@eloF`rq%E++@9$UNCPN zD@Er#_l#3n$vfrBlu=nUD)|XRkmK;V?V~+Wm<9=qy&z$fxC;iMR zwlH5_z9aC9|8G0`|BD?Hlaclm+?EOaz3&tI*xtNPOHv1;{2~tqK6%zLoW}*Z3t!}f zI%RiG1omzP+ldvc$a#<8auhE~tcY&{|30g5M#B{0#aLc8OWdTHI{n_hxjnZdF}#c` z((`AxSXLAJ4*@C5tJ3ls=VY*}3bPD{7#<(&+Kf^ZzshN$B8P>{=Q%KgT`ne){Q_r3 znz$oF!IuQ~_vLmD{5AP`gOfGbwFS!PATJ%9-`RmF9o&i`hapMLvla7(J%7Gj;5_?| zRJS`3EXJ^a;V6n&Vv4oO@BlIAWRuDqJ4Q{IUBK&6EQ7~JnAl1vB4NJ1qF^}bZ_!4f zSrE;rm@i13r=B%cqZn@E4SCjZ(nDouUY=9_gTzy0v>PxvWjaQ|a10`KU}?31w+k8qjw~16qiy#A-!NcbsK+#hsDf2eFIXj2 z;VMc6tSA+-()5DnXT;?e{Qw;wJf`WL>T_L}KE5wQKja2-Uy$x5%{1zP3|l< zPg*5&dSz+WtXz}VKe@@vd82sUs92Ksggsk*Rc~*bobI~n82QC1W7#u{UM;Uw^5#WL zI&SxTT;CQAjuNhe%q7gLTS8vVNPkz(4%bGm33*BL$)ZZe+nG=1>hYfDlev1lv2By_ zvZj3SV&;1%eI&_8pfRHFk^3uGjqBK8BK8Q0_F$q_E}_I8F6oD8PY@TTA5eG3{WhLMmK>w-Y9lFGk${rPI!IUlbos-x2wW(BtvJNXw}hSMzg8q;;F`G>Kdyy z?_}&2@@!pW)!Lnm-FBX!DAyxS0)Vhh#+;$utQy1hWvqOwyfGF0e_udpfD< z8ecS$=@|}^(><;)Y9iOZiQg?ve5@x^%CkzwFZKb<8FTE}fyi z-oK8yby&3?xma4*f~B&$Lf*)|g8hxL$+PB;jOWjYH2fM-H?-2|KD~3EG(#)Bw!w7Q zbQoq$&Nyof1FbRXN3g5rnVLDHmP#%*$?9bgZn`M`h5ZOb5Fl`n4tsP!h})Z41tfbI zdkb#C;a-vzzT;&pu!2>-lbierS9ylMn;!TTLcs?Ee?s8r2sJzyc#Oc5%yp3ekMI>G zl=+hH^zq%~yM25Q`5qtNOTO2~r^%;3lwY8;44KTv%U~^89gPv~A82K!&HRfsSMy<{ z)wy~XZ{n-WOwL0@La9Wm0Dh#N@8ic@e@f}scp-S}CK_^hJBPbDd>!wQ#5Zzu_05|Q zHtylQo9MBTwec+*9X7sgqsc~>jTRe88!06ju%X!)u`y)hw2fgKCv6e$l3VLzX5K+e0~4` literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/UserTokensResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/UserTokensResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..865dd59e7408b6ec53f15ea7633de89505a96b9a GIT binary patch literal 4877 zcmbVP+fy6Y8ULM?gcc#pB7?cP7-|}Wz!6}4g}9EfW6J^J8bWKkNwbIzEC`A0u1K7= zse6f?^fGMVH+7o+zTMS@OKguGwC6kD`R=!K^q>Fz zu!J1G7iPWDOV+z)Dep})g`QK&c|~W|bk;4$&v#$Qkt^o&tam@p(^dF& zcEF%QqqVW=-I0=HpazHbx}L^x0z(*)i0cUk;bn!yisdbqgU)nh6?CGm1o3Vwi}AZH zj5yOoig|{bItc|Ws&KhXTnTqQ^-Yu56zq1Z;EmO8#%nhxYBv+iWv;k+M`5GBlnm8d z0)MDWUp--|+r*?qW?(reFNN*F`h2ZxGS)JM6QNE&l9gMQ zSOUwiWSUc0gJZdzX#}kIN@kw3w!JpSWtVT!0@JZB3(a${kqT21>&VMBvcc=2=&i~X zRC_&ygfi=nN#VSwFuiLe*3*X!QF(sPTAsOVdM2*|ZDT9KhmDd?gM7dnY@PTIY}t(x zS{_sC{*$}o39VD}e^2fvG(%7L%YlTZ6KnJZr#kmWQ*b2bdPIMEPwynRHmWY3PU*U4 zXx-XqB$Xf(?T&V;(U=c6gzy34#V9k{_miR!H$DWjaF=yr%O~;mfh#c50xAI)up`dx*Gjr>+ z=4Qy2m@k%`ob{S5g1bT)i+e1?7~3k!STyjPlGhqbJtfaE;(kH6fosCY@kO==$A=jC zKjygxw?f~&lVzhK`zq}dg zVqj_`J|(U_%1_{3`Af?UvHKYDV)uts66ZSJ9*chTJH#SW?Xj2uYPLNV_kp?gn4uHW zALIDBXu4oS@8;Xrv3`Sc+>Go$KLxlnxGZ9m83) zVw7uOM~17o7EAvf-46(8zBow^C`#gy3<$z_v|=15NF`3xGWY&<7xSrom}~RiN^ABL z)W2KM>E{YMiQ%vuYAqVrE$GZXf`YW2+EHPxj9EE8M|a0Z?IWa>!7aN|%izakKZc!g zIJ+;iABX2NpR4#@tJ(K`Kj!`Y<@1RpEGfOks*-+c3;XE{XgAB1C-Dqq1})N7X_2-{ zi?mf*q)q;5`7P46qeWiX5$k8POC!$X+f*oKErjjDE|GT8rCP&|5x6&O8iD)EebdOz zK)G?Y@oHdx*f`0ire@0KGbgO&x4BBqU-$x-Za%{0)Rk?#E^4+hm%1Lnz1x^iy&1s$ z0)IDv2M8NO+qkuj?^lBq^v#7ZNad8v2vhPF3O)OrdS#+q5nYe5G}U7qStZ>2Z^R>i zMnb@Cy!%cW-(SVkgesmktdJ;ZVR15z=vPdO7Kg?bS6?x)0jw!?edpFM=|eO_XTVoE zsU;9sLc?e?lz!VEA9qe$V0cVE75NS@B?mzt06Dc5~DF7zLk# z`+n~GYxV=&50v-Os^!gSi0l7FD=lwnX{i_|RZyPE3n*ffW=i?QWrE2~BJ;J4U$Hbz zSiDH4BT6ZwMLmKele4xblA$;azv798pX1kDKPHXGax2|NB#W&q-p}F#+#!e$v-lx? zbQ{XWkMYrM^tyP-#ZO#xxVY=0$NwI2k@SI|y3k#WyEx%u!o^7!!!AZ$jJY`N;*5*4 VF3!1_bYc7SMgRMji;FIP_J7b}Q4RnA literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/common/AddHeaderInterceptor.class b/network/build/classes/kotlin/main/com/tangem/network/common/AddHeaderInterceptor.class new file mode 100644 index 0000000000000000000000000000000000000000..3cf5a9dacfcf9db59ce171e51b31e3c4d5e9255b GIT binary patch literal 2923 zcmb_eYf}?f7=F$MvdI!P21SBZQDQ|dh6JQGSgjXSB%lS0R@>4gIlz)+7qc7CUK#&I zzxGS}v7Kpc>4y&Er%r!V?Q=F40_cp+bdue3&U@bL^Sxd;v*YG6MO3Iux9T1UHq z790{dunU0*+Rzb(h9d$k-m>Ki3}xRgB@8j_hoUkhknGLAA`yve%YV~6ZSjZOu_7->Tq=UK7ay6gKo-cvPYy3_Ir9bkeVM({o|ZMXn}*<$xTN?l6TofF?1aR)ib%&5cFU6VCgsxzt|{KDxW1#`svV)@1|uQkoLQ{M z2yRtbCj^e{!o3cuj!Bk@S?_3p%gx37+CJNpicF*uOs*gT zn#HUfZYqifyv&Xvi?YP(Nmy$F5gJ-d)a(%DgzCTg+UFRlx?--b7N5ScBRpiPni@{d zSJERzv$T*mIm^?}cvBntz0-h|6}@|pZXa#XcvRJo;#x}!oZlJj8y=v_QtdFDlAf72 zJ(HHS(prF5gHW%adV#1GNPl9f)evVYfM;79rwpxA^PjB^LyIQ(>^HQ~Hi^4i8=;QQ zt&OxcB(BC|v8dLyKh_e{hFYrcp}>nDf?Blob~GC7(uVYOFdE_*zSS|)wU_t7i)Ss0 zpITr16f~44X_e`E#Wz92K9tFaM^ux5d#=E$3C~(fw&_*Ml910-Y|kpm z346_Qt%ahzvVFO*%da_kG9AcTww$h%7G(LJNgm&3&MeNEWlODV#Kdl0jZ0;R(tWd@ znJchdYhuQ%Q{lCH!%~V5RTtOgLSc zYRF@aqoN!Bn*E9p0$ArN%YBVwv_BU41F^o}ak#JlH+25ub3WmFKRJU4Q`X=`7sgZ4 zly1r(jxLUBrtFEDy)eEcO=l(f1_>_SshSg6juCFI_s5RYT>mB#0?*MmJ(y_;jJEdu ziq1?hFsjFco2vLWF)EM=#Y3C8DDXW-)a*lnEnLlny|L7~pdO$UudjrmsN) z(80h@@Ym1hHiOOMAeJd*5$F}}7dYA+9USuES!CR|8TA8vLSSyrJ2eGMMI zVP|yCPF=Zc>?(g_(*H6t`ewW9vl~W#cdC0g-`jniP(`|Xo<*%%%%~kWIj30UXQTh2 zrnV-%`FWpb(lh$9Mye+@Fg%n=8R=B2Cq0nP45zl;@;kuj`(J!hw$AsYsDB`B^o<+% zmMCQjq$KEzj}Vx^(gc<#nAjCgQJ6pxrAH7h)9XA!n~N?NtFD?n_WvOlM_rU%9CML# G;r<8dVe%RP literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/common/CacheControlHttpInterceptor.class b/network/build/classes/kotlin/main/com/tangem/network/common/CacheControlHttpInterceptor.class new file mode 100644 index 0000000000000000000000000000000000000000..6b465c1bce1d40cbd7c95e0b511da3d9588d251b GIT binary patch literal 1114 zcmaJ=TTc@~6#ixkZCwkLs#rkrE(L`xH$e*~NMdL#LV)yzr(rvmp?hg|rpoI*E z7$zElc9m}*w>?{#B7ET*v{3nxa-tN58LY^N^&PhrF{~7N5cj%SeYjKhuxOcq9(K;Kkl^?w3*BtNppaF8>Bx` zMk>-@ZH{5?>@;WbJnw#IrxwqqEuQB2v(tHAWN!v?$t)k^#l-oiB+vGpe@^zBzuXttrLH5)-qlA)q}`M%{H%5YyCx}?cf0!O%25h^3cN^37@ z(Gz;BjK}D1%h$@2RTU|6S@-=wi|#Zr2Tq-j>!(2^iN#A2cS+0X9+}RibJlnV*$zfJ zxYNPIuXyr>EF7=s9E53Co}xxEM0d%={Eoy^Op;{e0;b4=W0fS16|B*{Mw)5L`0N0Q mGM<+)Q^qW8`bDu!yyg$UA}nC>0LciG5tbqpBD{!zT!&yByaj^v*|d?3>o_hIdPamieaqo zD@S@(t>s!WBI|L-A_f#Zly;beL3!;1Vb|kGB_42BkRij=Wjfh9kmtm4wNlDW75C_7ri_Y%vZ{;xbrHqYwQC?qPso zD88_L$02&{l5FrsJI5eHr^_2#pQsAcS0ZH3U(r<3vB;Vt4&#A=A&fAXF{6viY&nfi zj2Y;_Lxv7@AVY?w;?0e|ZAhq3=Km#R=>5xYLnvP5ij$${wh}axEZX#F0|ToQekda? zM^ZJ(4Q)_suhU~g%XAxBN*g>qp4W2heV3_cv|%lmIQyb&nU1rsdS~P~&yc(2 z4c84TQ6dJ(_)Ab%FAZ`yS65V=Rlh3eq9{GF*>o!+*x?n25VPpp+$r-wMtTfRer$S5 zx}xBH$1I}P<`%U^{A7m70DE29zdt5R^9vZbME)#W3Cw+m`5)vcg}^?dIx+QFP;oFvO-! z%XDWLB16N=Dq0Xz5Jj9J>aLrdVI+UtOQslF*9 zR&^Ii`*hxR*0c@7U3GNR;+k$*wyV3QP3uM5U943qB&8f&FgVVTeTxfJw4);d2}y=_ z$H-CGIg1l~MT6ngP~L;5=9(2PZ*q4kv@htJQ^U&)j~a!hv%XXa)Tx;U&FPTHTt(-6 zdSqtG_nSYe>5fs`)T$y|n!sbWq;FEfgv^Y0r>y%YTt+9uyJg$0n3lG&Rn<(31TDRy z?Yi-Yv~ePJjZ(}`o5 zrog8#t>6=US`T-~^K@9oWrp4`xvxLjbPUcZQ_Fg#W~i9;iF=jg4nuk+_&BaH^mue? zikv8+FwTc0+2AdW9ov_e&|j4!Du zri;|y=s^;$(<>w&bbA#mP(=6sn&Eb19}ZW%sGeNDS)aV-_Y&d*cb%Fjp@Qy|4ae(w zE%;X8_JE4^;PHC3DV98 z?K=a>7Mi0Iv1CNdSFYZB`K+DpRLF0+Co0F1@t42G=v|RY6HLS%pXP>p_A>ot=zjxi zQ=vxdJ$;1aK(o3Fy?i7R54t<0!lP-ufX|hw>Gs~KS*}^7_j}9a=4!>5J^Y(M|61nk z5c5xvP^5_3FG`)GN!nV~Txnm{>B`EZwa+Brw=$xBheQ_Z4{f)?Y7Yz7{!3AW3zMb}* z??yicN&2zp*hxI0xlN)^0vK;hCh!41q_v2pK!#dq?(L8M^fTgn7|r*$_^XM+NOp7w zlfD<*Bou{2O+x^c@sv^oSeoETAxS}t@J?bOS6H0EpX>xDXO-%--$c?)@xJRBeTI&m0 zQ<_+lalg*jaaxIA_X|k(ck$J8d?Sd$Sd8uByMQXJ9+xNp3Ez`~1P4FR%*h>{lD4{n zNDjpuN;w#KNK35cunuztjAH}k6?AbZ92JfjN0p<4V}+xYL*;1axWHj?T;!PNxXF?B SBs;ya-y3a?O^#a}kNyX;NlB>y literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError$Companion.class b/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError$Companion.class new file mode 100644 index 0000000000000000000000000000000000000000..861fcc13fca6e79ec265eaa5b4d6255ba6c41fec GIT binary patch literal 814 zcmb7CO>Yx16dZfgY`TOtZJ{K576?^?L^p|$I20j404Y-1N`P|70Xc~QC%ZQCI)P(O z{18rDIU#XCRgU~9#B);MN-ytuW<0ic-v0dc{Re>i*kz~>Od30(-^w)B(tR*?921oq z9Um>>Gi!~lwoLk7Xk~N=#!woGsfZJ@#mD`T9JmkxL;j)C$~|HT_G;ZCR*;X7L&%VG z?^MRn?40$o1BS+4XKY-ebUd1-vC>XjEt2@Dd?O}_YZ;w6I~h1*4}~2|dr<2};QrFB z>foRFI$fv?`)50iVdIbCP&zRbPSBfpIt?g5=1bleFt9Q4AC*USS`8ZvwUb#f;$

    ho}O3R~3+L!i~=qJQ%cFaH|U162Sr)06MwY057WSOLyMK8_74&-y? z4V4!Y?NlneDpTZmthI5%(NB?~L80e-64bdLJ$=g#q1Z=5edX>q6uvIxn^e6FY~dEw z;&KC45dkGe=5xgCkaCYYr|!)~Z)OX__L5v0yo~c$BQ3yfLeau>4cAdYl`zK*+@bz} q_;uP_>LF-jqm9irJ>UW@aj}hUTkD5KItE<=>za)x|{E58A6|G#Mf literal 0 HcmV?d00001 diff --git a/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError.class b/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError.class new file mode 100644 index 0000000000000000000000000000000000000000..3465f18cfd11922708974c14043b05928c1bb7b4 GIT binary patch literal 2084 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 literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len new file mode 100644 index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29 GIT binary patch literal 8 McmZQz0D%Su009U9fdBvi literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab new file mode 100644 index 0000000000000000000000000000000000000000..eb5a2384d5dec9ba46a36a89fb22e1277336a203 GIT binary patch literal 4096 zcmeIu!DoVT9LMp`YSk?}?JwAQl1?4Eb{U;JblaHC#wsErB4Qa4rOaj#@h~E$h=|d2 z9XszY*bW`*{a6p3cIo>4@;tBK^ZPv>o_FVL92Rrp-n=>bTRa^|WtMBk-PwJIBe|D1 z?pbpCu)FV_|L2nL$9Q3ZS$sza=lFv&jPTJZ=5T@zzTr2H@Yp8}F@>*KM+e6^z+dd+ zsbA`02A{Ekk2u2)uCRr%L#c`9*uXYE;3wW=ARyH+j#a$E3U;xKA6P;Kr6MMR=^5a0V`kytbi4;0#?8ZSOF`rcYzIk?3wid literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.keystream new file mode 100644 index 0000000000000000000000000000000000000000..29ec4d876b5fb36c0ffdb6b7b276af1096fdc7f0 GIT binary patch literal 4096 zcmeH{O;5r=5QhCV#*3>4zoQ4NMhL+ez!;BfJ83txJ9T%8;orNoi!C3OEeB8KMl(;} z>9aH2@iAeG@zn)HK93$--s6C^h-uPCxXz4JH*vgwyi$iatau`@SJ zrzkZ%DTV6Vji~A=XeYU=UJEccbL^{^o!Ct8jnl~6T4E8Q(TN2Hq1SRVRK@WHUsxJwY4=JI#s4w@pAAyIG6vm4cs!-c* zJsaY26})DjvG4~lU&D^-!-l9MVZvshOmBaDgdO-H23ZV4(3*#ESpQyIo*&++TUqJy Wp#8N5S_7?t)k^lez literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..c580238d5e233f185fa5233d33337e20926743bf GIT binary patch literal 8 McmZQz00G7Y00620q5uE@ literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..ab20ff3fa5699928edc718640a36f7571f98b494 GIT binary patch literal 157 zcmdOA@JLNeNi9+cN=?o$N>OmjFH#6dEh^3|E=kQR@klJr@J%cTOUx-v4KB$qN=#2> SWMCi#F_NMfZZc38NFxAB2ofs* literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..7551abea53a1eec44ad6fcd091ce32cadb636302 GIT binary patch literal 32768 zcmeI%O-L0{7{>9b2t*{rB3gvhMIt5Wi=sfWD@s905iN=qCaHuFbP-8%(ctR3x`GQ` zs3>kEf`W13QralADJoE~lnApB2_liWP>+k*=qyCe;GEGg)6;hG}?2)HA#peWh3=e=Q~OM1O`&t_`EHAe&x zKmY**5I_I{1Q6IPu%=Q$=IYbUJ5=Hj=+N(!sb{Tiar90?&wk|_3vz#xFbgvv5DFaA z^DfB*srAb$K6#<8xa(+y1kDDMC>` zchvqQBtQTG1Q0*~0R#|0009ILKmdW=E^u=F@8RY3Z+E)``4K<>0apcX>3#lpzUs}! zeEll;>CW`VI_qeGD{|~BtZRzijla77kJPT_rkkd&bXR!}TxeV|)sH@A1cudSZZ2JA zrW;)lKmdXG1fJ+~%0s($F_qZ|C$(jQ|1&AbDt@%-q5u`UK4p;SdJW3lxaZ+~C#}G?-tYU0ec7sX%BJ+TEI?HK?^I zGr`wN>)&4zoQ4NMhL+ez!;BfJ83txJ9T%8;orNoi!C3OEeB8KMl(;} z>9aH2@iAeG@zn)HK93$--s6C^h-uPCxXz4JH*vgwyi$iatau`@SJ zrzkZ%DTV6Vji~A=XeYU=UJEccbL^{^o!Ct8jnl~6T4E8Q(TN2Hq1SRVRK@WHUsxJwY4=JI#s4w@pAAyIG6vm4cs!-c* zJsaY26})DjvG4~lU&D^-!-l9MVZvshOmBaDgdO-H23ZV4(3*#ESpQyIo*&++TUqJy Wp#8N5S_7?t)k^lez literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..c580238d5e233f185fa5233d33337e20926743bf GIT binary patch literal 8 McmZQz00G7Y00620q5uE@ literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..d4d34d463cce189ac8152ceca7ed21b935dbf70a GIT binary patch literal 3155 zcmeH}QA@)x6ovCs`mks78)Qy4v0F*&1bvWec4_0*+<22s_U}z6*u4pBHzxEk5Wd`V zI7yBaQ-l?AqEHjBl6&GLL-xi8v|$RqO2I8*v&B3RLy52AWt6GK!(?2%-ObcUS`1&r zYw20b8jO`6umh>7mH}*u_3s97MmpHnkxLsY_cA+L+&Xi!4rhhG3pmr{Ir7eEi~=rk zMj`UPbSV7Xq7Ob!K2V=T(Vws0k@eUZJ7E8v3As=mlJN8F6{stzu+6~$9Xvht+Z-Yl zi^OGa)U#OjlhfkRewv~C=r%_QXu)mo=vJKmfX_vGq9w5f)@0?(24sycHpu2jymnqD f7Txrw9b2t*{rB3gvhMIt5Wi=sfWD@s905iN=qCaHuFbP-8%(ctR3x`GQ` zs3>kEf`W13QralADJoE~lnApB2_liWP>+k*=qyCe;GEGg)6;hG}?2)HA#peWh3=e=Q~OM1O`&t_`EHAe&x zKmY**5I_I{1Q6IPu%=Q$=IYbUJ5=Hj=+N(!sb{Tiar90?&wk|_3vz#xFbgvv5DFaA z^DfB*srAb$K6#<8xa(+y1kDDMC>` zchvqQBtQTG1Q0*~0R#|0009ILKmdW=E^u=F@8RY3Z+E)``4K<>0apcX>3#lpzUs}! zeEll;>CW`VI_qeGD{|~BtZRzijla77kJPT_rkkd&bXR!}TxeV|)sH@A1cudSZZ2JA zrW;)lKmdXG1fJ+~%0s($F_qZ|C$(jQ|1&Abgw{1}OppWO0ZHe)1gDeEh0LWk*9` nGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!n$PEDi;I#^= literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream new file mode 100644 index 0000000000000000000000000000000000000000..d97731b79d2bf99b3e27b08a79018314ba3250a2 GIT binary patch literal 4096 zcmeIu!3lsc6oX+mdIv{o7buTVi?o$ebbV1=LilqE$?aRcSDUG2Rco48AG@Wad{dNf h7T@=&LE|j0g&=?c0tg_000IagfB*srAb`NH1a1QS6VCtu literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..3bb2dcc6ce3baa57676d845dd21df8491d46fd34 GIT binary patch literal 8 LcmZQz00R>M06YLS literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..2a17e6e5bd9e7704741c2a3ae485eb2d2e302b87 GIT binary patch literal 8 LcmZQz0D}$y0FVHQ literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..b548581e0aee3d76a132307dbdd82c362f2e06b9 GIT binary patch literal 171 zcmW-a!3u*w3`7Sh^x#Kyf1oa2d+Vim5lTx-uc-zV)RpX3{QM3kWSG1p#G_Ja)!ajy zV6D3ab9c2k#H4eoB=)r|ZyA63Ou?kn4UCL+#211H#S4b=~` hbX=ZrW~XSlTTlau$BL2)wd+TpFgG|B=i6P literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..7546f6b6f95f2e7818d7e9362f42f4c31d364fc0 GIT binary patch literal 32768 zcmeIuu@wLi6a>NZG@?`iv6d-8{%1SbNlT9i5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly aP)K0D*R%F0v}~0C0RjXF5FkL{L*N1dUjjV< literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len new file mode 100644 index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29 GIT binary patch literal 8 McmZQz0D%Su009U9fdBvi literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab new file mode 100644 index 0000000000000000000000000000000000000000..3f3eeb4eaf0a247accf8f8d2ac608ad47ba11c33 GIT binary patch literal 4096 zcmbR3vzw0r2pB;G3gzI2PpyqWO0ZHe)1gDeEh0LWk*9` nGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!n$PEDin1c$% literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.keystream new file mode 100644 index 0000000000000000000000000000000000000000..9bc791e73fa08a438fb04841a3ef3dfbce45dc80 GIT binary patch literal 4096 zcmeIu(FuSs3_w9Q${_x^L6A^Hlaw|f-QGEbcO#sf=~5caT*B(jaf|j#x-Pef-p=Pu zf?P~38kbU8t>cx|cq{6T&P(re;|?1$>`gn$&RaE&yyoDWaT<6iCSHqjyUda5m_A(Nma=crpMj{C1A#qw{@r@afQ YB;*ulHdW!;_v*+VkFi_@% literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab_i.len new file mode 100644 index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29 GIT binary patch literal 8 McmZQz0D%Su009U9fdBvi literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab new file mode 100644 index 0000000000000000000000000000000000000000..9828685bd947ce55a65417383900248b3ae3d19e GIT binary patch literal 4096 zcmeIvyDNuL9LMpm`&~!|xfkUUi_xI0*hE&w z?stAJAMYoT(P%i|I?wAo=X{@Y>hnxhuEiTw<~_bE;cLFhusCgU8=e@JgwiB(*3@-EHbFa~i^7FOZ2-Wc#QY2Qb~x} zICfzXF5p2xdcZ*}2{kLgCft9KCa@n5F%J{M%=Vt8YV5^6%*G|$ev+=R6Enlj(y$u+ zkJ1qOa1qn-1UDa~+z9%y1~YIJx9+7)?7~OP#KK61FJ_2L^~pGs>CZZ#>X9 zDDPN|*Zxq57Z@)mIA=;rs*N8IjZaGl;rAm5CH~G)>7Lf+Svp7-G)d582BM$;ix|S; zQPf2^QGP2N)ewzN$OU2l1Z=|}1fnmeB7*Sp2r6Pbz^Q-8X1z|T0#Tn_TV9J@(`p6ehmxVU@8+_;gys^emEEbmv~F_L=Vy|QLo+L%`})`CGM1)_876i zGO$lwWKCFZOo!*{m5X(2AXq7<3A&lS>Ue>Rq}5SF{j9^j+89%e7eQa^%p#ueH=mZf zra)RymrpGXA{bZJ-a+J^y;(hszuWKGHVCfkjkq5?p=NF$;$bc&@|ZQCuF7kNVG9JL zdbR4RycYU1iUS<{s!&eF5}UX?e!Gqpx z)<-%+IpF4o8Dpk}e6b^VwK<{Yf=`4gIFS?^sfF5gXIZ1&A9wTC=H)Crb2DqctIVv}`jRuRidat2gyoj$p?>u6 Kdx$_g0)GJjmRleI literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..99fb428ef82a10e4c03121703cb059904928b991 GIT binary patch literal 8 McmZQz00G|j009vI{Qv*} literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..8f5a4cfb5f78c2d5abb8fb11b497d18752c86db8 GIT binary patch literal 8 McmZQz00E{1003+NV*mgE literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..e4085d22a405f9171eeb3072becc3c2951f9c823 GIT binary patch literal 5410 zcmeI0!A`7Mg9vL=R>yBVD08b#|%Y?cG8m985Hn5)N~0()^wI z_Mc||kzx#b0Td}HkQUKBQj`KY<2+bD)~-x+J=g0EWFAQ`;YBb$8Z literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..9a163d18960e6aa9a298264ea050f9f6f9e78df4 GIT binary patch literal 32768 zcmeI4ZAcVR7=<$fmjj#{?!t6ve5)lq==9X-t}t-o=IVrCP{z z7kb}-!Y8x##|y1zowfw3A(t{x^2uA(8Vpz;wPx<$PukvaT%GBjI;{Q|0wh2JdJ#B?^(^uB`$oO=_10}hgCo7} zUwZd3T7MAx<=uml(?<7YKQRe(z*a*2>r-O7lgCvv&Z~Xb_Ls`zm`+K61W14cNPq-L zfCNZ@1V}(^0(Enrvr_G&V!QO-T?cag_7Btjd%iQU7H}!+q4y3sf&|nFjN>@YFFQ1) zUdaj)AOX<{B;$N8+r8!g^>TrDK5WVVBD!z-BLNa10TLhqGZL7Zh~Iit{z}gt)b^f0D~5D_{=na3@*bU( zBP^JY!gF1bLppcVYDI?qQF z2MLe>2`o2(KFmkkYqkpeJBxz=6z6olP~+5q9PT7LuUl6GjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk0O=tB0OryToB#j- literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream new file mode 100644 index 0000000000000000000000000000000000000000..2ee70c30c4c22b9def7c510db8d4e3ee269a4301 GIT binary patch literal 4096 zcmeIuu?>JQ3`9|eq4yx^*dRhCiom`ov4pZc48Q;scjf6znsF7mfC_n)UP=Epypa2CZKmiI+fC3cwlfVO+K_LGC literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..1cf91b4131aa307448ca194b1a30bce7f17f541b GIT binary patch literal 8 LcmZQz0E18f09^o9 literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..01bdaa1da7d937c7e7d98e54ba912f88ab95c7f2 GIT binary patch literal 8 LcmZQz0D}nt0GI%g literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..6fcb00a84b3a1bca9ed654273f68daba3ae64ad7 GIT binary patch literal 55 zcmdOA@JLNeNi9+cN=?o$N>OmjFH#6dEh^3|E=kQR@klJr@J%cTOUx-v4KB$qN=#2> IWMBj$02BWb2mk;8 literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..cb322b350b06a636c9e1d1c7d2ec89c1570d5819 GIT binary patch literal 32768 zcmeIuu@QhE5JkZo%_zxQvvP?k u^0EX75FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjYm75D&FSp-D@ literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i.len new file mode 100644 index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29 GIT binary patch literal 8 McmZQz0D%Su009U9fdBvi literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab new file mode 100644 index 0000000000000000000000000000000000000000..6bd2e27ec4d7ecfc427aae4b83155c6016dabab7 GIT binary patch literal 4096 zcmeIv%PWLY7{~F)b;?4slK)_=T*98RRI{*^DSr$-Gr)u zS!M&8W$!xl%UBGiWO@}2WtV)EyUT_Dp=f;h1bhNM0iS?Rz$f4n@Cozb5))%cT>)<)dO%XJieTJhe1DYzhwq>C)gt91*W;Z%l1K=NKv@GFnjJ xtMa0G2XV91UT>@PKT>@PKT>@PKTR)#n6Dt4! literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..7c994b224517870b747bc16e99683b65d2ad5e02 GIT binary patch literal 8 McmZQz00H)Q009I5_5c6? literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..8e9520632399f01da95f660f3d1ea6f36fd2d158 GIT binary patch literal 8 McmZQz00G7w006=Ox&QzG literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..82e7cd7f2d98b47863b5176283a62dd961dee97c GIT binary patch literal 24785 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 literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..7aee661b0145d7e8d6b1e40b8103ea10c6d3de8b GIT binary patch literal 32768 zcmeI(O-NK>7{&2h5k&-L1e%Z<2#)1q2%!(cLJ@OUgFy?!Y#}Wt!%|lg21Z4Ki9=9K zLMUM%lo3TREg}(O5|SdYqD?_TErb$Ubb>Z68|R((!~bkV;Xda)zYFika3`vY)>jAkSP2q1s}0tk2}kWt^ZmeE?zYFn?{ zr_UK)l$|NIUW5Y65*X0c2eR#-{&&crF4$4N>}+#b5qXtpiWT@mP0k%j(=DOdW?1A#&a#B|OjMiyg* z$RYy)1Q0*~0aF4MdOzEJ>mARhn*wGo0tg_000IagfB*srAbN0uuP5U7qhNstIT%uU0v!bMspN#R{)VQx*XP5O7%F mxX#su+Bn}^&+#5@mf!2esgA??nX2D2lj9gJLjZw12>b!;7FAgVO{LlaYfZ zd3}K*G*OTpCLtb@uEuFg%6mVf*drIVc#Cn~$L-_HnQ6e9ahO42`tnkw7hPIF3upl? Sparym7SIA(KnrMrN(HXA-c$bo literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..49c7f00ac3220be67a6c33cc304cc29c6d6f66e0 GIT binary patch literal 8 McmZQz00HI&006E4qyPW_ literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..a5413564614472ae2e55bb44e79510947726def0 GIT binary patch literal 8 LcmZQz0D~I<0N?=G literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..d75d4e5bb3eca1b5faa7d619fe56ac30f9877948 GIT binary patch literal 3808 zcmcIn&5qMB5Y7P!A)$SSJ&X_+HY6;t{ECuYXsNoY+VXorCY^1gCU&r$MU}Yl61)UY z!Hp~8d601P(`M6p>!#&WsnLAjH=Y^KjBTrlWQcsfhd$%O{TGbycae)gNTjk!#JEG^ zHzZ83FBvBz{AS(pS!7E>M;O@@cZVN(=pA!_a zA*K3F9rVT|3{m9@+z-zjg4@|47dXuwu(jWN;J(@w5je+NPpm-MRX`O^_-bIjqtbeqFU3Ga?Z)D)5AJ z#-DDCfMYx(Nhn)U9Eu||_@ZOsc95aMqRd= z(Cm1&iDaQ8+Om?$zt^k-4MR402I!CNrj&8Pak$=mkj|_bvHHl zMp!@}ulH;j1gH6AT@UUN)$KR&FyS1jA1pw+S}ccQ2?SpDX{xKm68a^w4IF$ekPp}d z7jZYpQwk_VbZwyI(qLW;X&b22-&e~-a!#Q(9D^(`B$fSzE>cndv!`9MR-Pc=Y#yzz zm4ypA2K-rlgKJO%S}ZuVl+^}>b(M20IE^}IPE>(;ETNK5!S%Lg0Owg94u5{7z|~FP z%hC;H0&r@bV}x>X+rT}kbB+o}O;6%vdrvAH;MA&WPqSkESUHkZ#3DxR#&DQDq*_$% zvOdP@wRCqyG;?#0sHcR-R0XndHpNP)RlCp}#fRPGQyBPdoK|3!7II|W)T?)x7zaxI z9Jz9~g8Y8jG4rX^N#9`|6hgfSZ|H?`^R&a>HZ#+{`VKACtCk*~>eDRzJyelA3*^!w F@edYNvdaJf literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..f2ceb1bfe5f9c6f21d41c00e761ab43fdca6dcf1 GIT binary patch literal 32768 zcmeIup=yFr9LC`fEQ7J&HEh8)gXY1|W-u6r&E1DEkfCKTmW-~6$uLmc;)-Bd8VuGJ zb_omqcL6p*&)IXz7d|-W{XNBLQ7Rk)1hN#EM%7EFy3R6A6afMR2oNAZfB*pk1PBly zKwysouW{E&sa@XVm98Q{fB*pk*$Lc5QLXZEm0g%10t5&UAV7cs0RjXF5FkK+009C7 zb_&en&uTXM`#1y$5FkK+009C72oNAZfB*pk1PBlyK!5-N0tXP-#G8ZW*~I~#_9OuU z1PBlyK!8930^`{DdHLQ-V5*4(2oNAZfB*pk1PBlyK!5-N0t5&UNLpYL>u)y0UeXc9 z6Cgl<009C72oN}Ofl=(czx}Eo`AqW&{0o6)RDSmxzq!S}MbN|b(|K-PdI;nw@EB3u N=buH6ZJN>(*a9B78^izr literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len new file mode 100644 index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29 GIT binary patch literal 8 McmZQz0D%Su009U9fdBvi literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab new file mode 100644 index 0000000000000000000000000000000000000000..db036e4ef9e3c1054513a84eacc1f2f4e827897d GIT binary patch literal 4096 zcmbR3vzw0r2sl9m32x4n_4oh(X+WAANG}4?xBvYAe-TJ80n$%_bTN?T0b1e&q^*IpB#;IQkAl$<7!85Z k5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC70URL!0Fl2bmH+?% literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream new file mode 100644 index 0000000000000000000000000000000000000000..a6e8d241a6307a41e251a74138a9df0dcaa82aca GIT binary patch literal 4096 zcmeIuy$ZrW3O2sJJ@hmir*d|M;CV zwjhQIv_L2?_B9w+fis;&5^lKY;EBO81&q9BfBn)i4M{O8Asy!p$E6cCn))Zp1h+ho z(d6jwB(^b literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..8d483a8627f5fa128d8b36b8e902dc3208456d2e GIT binary patch literal 8 McmZQz00G7X003$LVgLXD literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..eb0b8a069dd4fe98c3148ae79618cb3e6ef0b6db GIT binary patch literal 8 LcmZQz0D}_%0MG!* literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..ac14c83bb7bfeb15a86fda4b7c91adb4fed95e2a GIT binary patch literal 1039 zcmbW0K~BRk5JkP>99_W*XF~xYSX2>ER0UOO0kJgBD2?iPqVYs1cj8uD1d}u>MWh(E z);#||Uwc1) literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..b8cf2164ea9cf5c7bb28cc6e83bfc6970525f9bd GIT binary patch literal 32768 zcmeIup>D!Z6b9fXOweR%8Z%3H1bknG%pu6^GKB-NC1eQ+WCl=36WGPZzlY+ZbBkM; z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7csfn)`KqSJHoyOA9!b6C`W}>qwAp@A5*56I|mK z$9TauHu&i6<=)35&Txf0Ofbh7TUE2`(|v>)E^&%`>|u@_jB951hkJr2oMVdoPNM=U YpaLqO0xF;aDxd-?paLqO0)JQF4Hh&`8~^|S literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream new file mode 100644 index 0000000000000000000000000000000000000000..b2079c9286248bc7d034f500c9e98cd5b1ce4cf1 GIT binary patch literal 4096 zcmeH^O-sZu5QhCV-i#N&5IrnKTv=A6u87B&4&AV6rc9W788s|vlE z6~!EM0rcP*$Ux0V=hN{_n>A$Unb6Id>k)Nvh@#@Dd3V+3E_y+@@#@lFY literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..9030374dca137536d6078ed07bb999a59028e3a0 GIT binary patch literal 8 McmZQz00HJD006Q8r~m)} literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..09407efabbe5467ec2b0cdc5cce0a25e55419604 GIT binary patch literal 8 McmZQz00Bk;000^Q5&!@I literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..3c0a2eb080ff9e1edfd524e371c0e9962372c4cb GIT binary patch literal 955 zcmeH`y>7xl5QMKM9Xx=G2)af2;Q=T>rZ7Tar9b&rwq@V#X>TpW+jFr&N+cpAQlv?B z)i;{knVG8;wLwnySP+|JO`PnILmdR2q&ifY_8+?K@F2vx#8>rFkOk4ML<z1wS|)%;#8Ckc#-L*M)k&59IyF zKiPi_ZGXeRKFLIlsi|U+Z?JKKT%eC#y&ZoH*ph>#F+=Zv1I*O)OJ6R^mQ2?oWc`z2 W2yrv{%_d%e?J%Kl!1Ep5epP3Wykpn^ literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..8b8b1ed85aa7c99909f1f3b1f2a5e24308c52652 GIT binary patch literal 32768 zcmeI(F(?Ib90%}|MKZ~(7?ep^l@giWB8v=U%JcHZN99#qtX>y}KJJ|f5FkK+009C72oU%zP>Q9d!sz1XCszJK z;5nKeFXiX|qPLy|2oNAZfB=F26gY}uy|d+k|D0)71_c_DWP9O0pTXoJ6Zo~jR=n9& zoIdz<11}OFK!5;&Zv>j+`)u#={Nj>&5$T z9}WTpQYui4eeV?J`ck@m+a^GO009C72oNAZfB*pk1PEkS;3Sf_bmvYp3m`ZF0t5&U zs6gN@YBrlkW-2IELVy4P0tD(_;3*Q071!G8KB#~M2oNApRe`hU*3;iPQPrlJ5y-ee yIr2a3Ty|ufL3{!P2oNAZpauemF>`A3C|85gMiL-EfB*pk1PBlyK!8As1>OKy-7fP0 literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len new file mode 100644 index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29 GIT binary patch literal 8 McmZQz0D%Su009U9fdBvi literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab b/network/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab new file mode 100644 index 0000000000..920202f822 --- /dev/null +++ b/network/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab @@ -0,0 +1,2 @@ +11 +0 \ No newline at end of file diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab new file mode 100644 index 0000000000000000000000000000000000000000..94e8c84a412e3bd484e6cc0f11cafca0e0fca317 GIT binary patch literal 4096 zcmbR3vzw0r2)IE63!{qVN=YZx%0~MSA z(tCmQAs{UXG-MBuP6N{0fb>ovy#YuIFflN!0@B$)dI?AzNY4Y(oXiXiGk|m$ke&pj s@!2;jHyQ$?Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0wXO10Dj#wu>b%7 literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream new file mode 100644 index 0000000000000000000000000000000000000000..13bcc6123c91d07994ff4d2eaeeaeb2839c71a54 GIT binary patch literal 4096 zcmeIx!EOQ}5P;!6jOWfX80%pht%lur@X`=6Rcc{y7}xmrvYKemU3wdCB;+Ic2ey;7 zS-sC^-RE0lCJ<51#0SXiAXp#a+x8Y51rQr|7#;^~@{sH$U=;eH-38XaHq+!^4`Tm` zZjpQMu-wqry20S_gjsSiyLx6$6f+K}8G+uzcj)$D@JVj!klk&pgMJ^{wl@2m*7A>Z zd3}K*G*OTpCLtb@uEuFg%6mVf*drIVc#Cn~$L-_HnQ6e9ahO42`tnkw7hPIF3upl? Sparym7SIA(KnrMrN(HXA-c$bo literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..49c7f00ac3220be67a6c33cc304cc29c6d6f66e0 GIT binary patch literal 8 McmZQz00HI&006E4qyPW_ literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..a5413564614472ae2e55bb44e79510947726def0 GIT binary patch literal 8 LcmZQz0D~I<0N?=G literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..afce831e8f06b6776dd87e9f59f94f1de8d53662 GIT binary patch literal 115 zcmW;6OA3H631vnF5QOd5aR<5XDU_=}gME gdP>Jg&q~+Ji$x!J$ARmuPHja1lai zZD_5|B?$TpN1ks!-+SKk6t$vKczYLki;bhJljYtQTS$Na0RjX{5V(xY!T02*glLfj z2oNBUP+%CH+r6K4!s;p$AV7cs0RjXF5FkK+009EY1+Jt2^XJ!Z@-x&YK!5-N0t5&U zAV7cs0RjXFq!oCN=C|vIVcG)}CqRGz0RjXF5FkK+009C72oNB!g}{CMoweqFTSWL; zg1}>B9@Re4SHY4Ur)rQs9cX9WBG4Z~?Gr$y9@Ce^Ovkcy04oi4~&#zel oZ}1Frc!95vng2%*fg%GkAOkWW12P~3G9UvoAOkWW12PZ{ES$eCVgLXD literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream new file mode 100644 index 0000000000000000000000000000000000000000..b73411eda599178cd04ec5f0e2c9f90e9d194ae5 GIT binary patch literal 4096 xcmeIu2@wDw3;>|%{AT~a_V6GA(Rz-#_Nf~fFkrxd0RsjM7%*VKfB^%44m^IW05|{u literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..296694d3c0b2935793fbfb8b84f4496d1f079fc4 GIT binary patch literal 8 LcmZQz00V9S022TV literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..a5413564614472ae2e55bb44e79510947726def0 GIT binary patch literal 8 LcmZQz0D~I<0N?=G literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..52384eb654aa1c4eac73383308d3092ffe64a521 GIT binary patch literal 999 zcmcK3K~BRk5Cu>lC5x`{8Bi%|D$+(yC=x8t;7$|M+GECUP;ZaZN~CKNmX@qHBmZwH zmIyoKOra*;Wsk&Ji5yIb=;9JWTY%rf@q|?*h6dln>#S6p)nZ=1KjdmA9ma3sgAA;t z1>@u=bRacZ8NoGZe=mSH(!<#%myXn&WqEVB_vY>pKLq>}ULkizW13NmYN{CuzfKrp zNb0kwr&EI`7qDk5p+>OR0BY2&R5+k7Vml35Ujpv^YkcFnakK3J54o*H@yD%G7cDuN xyNImOryloDTZ+v%ub|MHIk^rw!AUH{(>QT``AQ3B>baH3$8icH)852n{Q$$3X`%oC literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..fd274018e830e7a5904ca90fabd233646303783d GIT binary patch literal 32768 zcmeIuD-Hrt5JkbqA7_b*XnvWdsNiAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly IK;Rz)J~N~S{Qv*} literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i.len new file mode 100644 index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29 GIT binary patch literal 8 McmZQz0D%Su009U9fdBvi literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab new file mode 100644 index 0000000000000000000000000000000000000000..74829020bb4b870a3a5c93ede97e300b8014d868 GIT binary patch literal 16384 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 I@LwnJALE7zzW@LL literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream new file mode 100644 index 0000000000000000000000000000000000000000..c040fb2f1ea567af2ef43668b9c0937870c0fad5 GIT binary patch literal 12288 zcmeI&`=3vB0>|<1$)&+O$gLv9%rGuXnrK2}WASJ`kc5iJHLOBfWG6D@GL*)pi(4&G z36U_%CM>h!Vcai~E+Q<^WOTz+dtR?I+aIxy@ze8hzL#@8@Av0?&N*Kb#FF~N-T3{c zbHRc73AM#t-9LIj6d&H|1933B!=e#m$A29>y{`0rajMSdl0EV+#)+>yT(U$# zP_l8O1A;9_XO&M6Hay*Ww5YnR>IJc&`d-0% z#LLavr-(T#&Xk=Hd;IQT<_Bw=SNW;??ZKG$wtXuSN9-JYWz^nZg3A{_IVJeQ8x$qHXqX6Ghza6}JfD6|t0Mi6ZxoN~6T4wq+^{f)W%bnzky|R}A>{Kq*0Dv&a3M zX<~xJ1c?dq5gfeWP(|l?WBLh>S#ZpvM9zWednzt?zp`867Up~-x;)Y8q}W}2{Bt7t z{GsTjnP2vdUK;z>^ysBk6UGkrJrmzKH&YZ;9&$qLdVSIsK`eq0#Ef+hEn8%pZ}IcHIRrYvJ_+qW9FA zu~0BK1am_$Hw1G-unTdae&!dV%%g|D5*bH3RuJcIUOGm!ZZ_jh!SP&J(SEqcBRC$x zVMH&5haor&!C?pvLvR>^!w?*XU>6>aqNyI3=6RJurbsp&T&srT(=#MLI%z7|iknGs=I&jug5rTq(? z=8HS`j=n5ftiJi6;3N{9M1pfi)Sg7q1jC6C4o6U>U^s%|2!Wq%fPw)E1}I{|>Q@Bg5+k2ZYA$A1I+Z7SrQ~iF ztN?5JWh8rn77R!5(w#$wFZQJf;Yu4J3_)-`jkbgEK=`6KwA)Wp_rxIPO$C3v&o zJrgR_|H6F99nM&ug+h+7h1Tg7sNc{w?WN!HF-Hcf02W!RaYlG~Agj$XR^VwfSmM zIqAxA!I>*^?;N{9aOR4xn+JFLa?PU)o$4Z1BXM*a97B3bh_NXt)4SM66 zII(qQgjdQftL2sh&u2&AV&@hvcB0|r_p?Mw+l8wH7dyelPIT)w?H0k=6ulIlO(K5Y zhtYdDo7}?LBsiM{XOrM;64bP+Vug5X64WH9Nl=rZCP7Vtnj(ZX32GA5B&bPHlb|L+ zP0^OHCP7Vtngs0#Di9&1?#FP*ioBVl`v?w0uy-+{YQp*mb?Zi8Uv6O+f?Y%icTp)T~|h~VZ{ za0e^6gB8q7!ORrQOu=KjVA=>)0KxsL;7&%6ykJcbR3KO!1dD@U{s^8*1j~hBxey=h z7`OSvW9yd$+-B^Go8Kj1$ipu6dpKCw3tASmENEHKvY=%_%Yv2#Eel!}v@B>@(6XRqLCb=c1uY96 zg))xL?-lSUBzTf)^3kOJ0Z&qb2PnYzI9{#O{}8>IX%nCQW145TVA=?#jbM@p=7wNy2=yJc=v~mepm#y5Yz5xP({#%7@7UVU4l{sr3gw9lp-iaP>P@wK`DY#1f>W{5tJe* zMNo>M6hSG1QUo`XpC&ZR3AmXQ#bY-=nEv)3M+Mv;*68}ouL1Xmf(Dn@zi_J!3K|qN zC}>d7m!K~}UxK~_H3@1G)Fh}$P?Ml0K}~|11T_h264WH9Nl=rZ9YHCAQUs+4N)ePI zC`C|;pcFwVf>H#f2ucx@A}B>ril7uhDdObf`xpFS&*G&)h}Q+lZ#X=%k;w~^7bGu8 zUXZ*Xc|r1m}=0~eMJcOCHUqD;WvxMAOHOUF{JVG2uvimaDw#O-+M~{Zi>61ASnLW^8SKv7JOlZ z@C!xBg!vOita{3u>GxdA5BSY$+cR^&3^-FnV$zQDIWyu@JowQo`d=5UdVFDlNSHji zOnk9pSpg@AC=njKSXJTjF2U(JJ9F0$erny}-KYF}fPRav-5t)cim8IX2Z*+We-BXj tQfgk|mZNP1|62iKMgNKtC`zCxfuaP85-3WbD1o8`iV`SF;Qu{={{j%q*!BPb literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream.len new file mode 100644 index 0000000000000000000000000000000000000000..80a7de774ad431af673de36d70afba1377d284d2 GIT binary patch literal 8 McmZQz009+F003D4E&u=k literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.len new file mode 100644 index 0000000000000000000000000000000000000000..9e436a82c2cfcdc6e55eec6345b4adfca6e6f448 GIT binary patch literal 8 McmZQz00D&=007_sxc~qF literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.values.at new file mode 100644 index 0000000000000000000000000000000000000000..c8693c113bc21fa13fc9abe5c52483ab7f7dda94 GIT binary patch literal 7475 zcmeHM%We}f6wNzL#DWD&7Fn|6FHko~2qg9q9RU&|C<*_M?>X+=V~;(W2->t_BIVY; zZy#U3(zlb_W`EZlcORQChvVJu-Ql?V&>TDDEv=QSvAvy`n(x3zK}bvD=oa7PY$Td|IEaAGXz z!e6WF{%E^x{aLn=hv;U8WvczYQ1mJqzT9;UBCK)-p1 zwjs+GjpafuX2b}LrWxt&g=Z!8X&X81==g&1`HJfja@_u_E#w2K4I{4&#$g4!roSgv zcxRZz-cja(WAJ>m?_+1paE$W$@+^TV*A~aUMI=PFDB-(#9Y}(*8Q}~Kf4!CF zBbH~hK;IGE!ux$Z^^Da7PldVbwc<#0?93(3<{KN2eJrbLu!^;=6|v5|z`lkx@qKudpA@3m+C*-G^0+6+ zNdsK;Ov`0s?Ok*S8tZK0CqQ0Hl{zb}*Aj2U_Ht}T%r3{QRC^^Ju;u?%{QS#Wg1_#h zNxSZw5yS`2u6$kPIrwdqcPhOXciYtYd)oCpoO3~Rpk>70i1_8B{T&1{V8)xBpZ~Qe T+~Ik%3XUiG8>RmLM)}J>Yb-x0 literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i new file mode 100644 index 0000000000000000000000000000000000000000..ae1a3dd602a1ee4a12ba808089831213e6d0ab0c GIT binary patch literal 32768 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 A1^@s6 literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len new file mode 100644 index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29 GIT binary patch literal 8 McmZQz0D%Su009U9fdBvi literal 0 HcmV?d00001 diff --git a/network/build/kotlin/compileKotlin/last-build.bin b/network/build/kotlin/compileKotlin/last-build.bin new file mode 100644 index 0000000000000000000000000000000000000000..88555530dd63535c1c7eb9c0f78d3d7cba15115b GIT binary patch literal 18 YcmZ4UmVvdLhk=2y<+anAmkdAv06Dn@X#fBK literal 0 HcmV?d00001 diff --git a/network/build/kotlin/networkjar-classes.txt b/network/build/kotlin/networkjar-classes.txt new file mode 100644 index 0000000000..ef07fbb021 --- /dev/null +++ b/network/build/kotlin/networkjar-classes.txt @@ -0,0 +1 @@ +/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/AttestationResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests$Item.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/KYCStatus.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApi$Companion.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApi.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$Companion.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$requestAttestationChallenge$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$requestAttestationChallenge$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterKYCRequest.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterWalletRequest.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterWalletResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse$Item.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError$DefaultImpls.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponsesKt.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin$Network.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse$Currency.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/GeoResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/HttpResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/RatesResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi$DefaultImpls.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechError.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$Companion.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$userCountry$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$userCountry$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TokenResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/UserTokensResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/AddHeaderInterceptor.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/CacheControlHttpInterceptor.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/CardPublicKeyHttpInterceptor.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/MoshiConverter.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError$Companion.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/NetworkModuleMessage.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/RetrofitKt.class \ No newline at end of file diff --git a/network/build/libs/network.jar b/network/build/libs/network.jar new file mode 100644 index 0000000000000000000000000000000000000000..af1c4b140b481f82156439504e50812fd74b6455 GIT binary patch literal 99352 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 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b 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 bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` literal 0 HcmV?d00001 diff --git a/network/build/tmp/jar/MANIFEST.MF b/network/build/tmp/jar/MANIFEST.MF new file mode 100644 index 0000000000..58630c02ef --- /dev/null +++ b/network/build/tmp/jar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/network/src/main/java/com/tangem/network/common/NetworError.kt b/network/src/main/java/com/tangem/network/common/NetworError.kt deleted file mode 100644 index dfe852ecc9..0000000000 --- a/network/src/main/java/com/tangem/network/common/NetworError.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.network.common - -import com.tangem.common.module.ModuleError -import com.tangem.common.module.ModuleErrorCode -import com.tangem.common.module.ModuleMessage - -/** -[REDACTED_AUTHOR] - */ -sealed interface NetworkModuleMessage : ModuleMessage - -sealed class NetworkError( - subCode: Int, - override val message: String, - override val data: Any?, -) : NetworkModuleMessage, ModuleError() { - override val code: Int = ModuleErrorCode.NETWORK + subCode - - companion object { - // base code used for all error in the module - // const val CODE_ANY_OTHER = 100..199, 200..299 - } -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a0a79551bb..2ed427a000 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,5 @@ include(":app") include(":domain") -include(":network") include(":common") // region Core modules From 0bbfef1bc7f894f7b25df6471a359ec44b968308 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Nov 2022 17:07:30 +0300 Subject: [PATCH 105/176] Updated on 2026-08-14 --- .../datasource/api/tangemTech/Responses.kt | 5 +++-- .../api/tangemTech/TangemTechApi.kt | 3 ++- .../com/tangem/datasource/di/NetworkModule.kt | 19 +++++++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt index 37c99cc0ab..532a5debfb 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/Responses.kt @@ -11,7 +11,7 @@ sealed interface TangemTechResponse : HttpResponse data class CoinsResponse( val imageHost: String?, val coins: List, - val total: Int + val total: Int, ) : TangemTechResponse { data class Coin( @@ -19,13 +19,14 @@ data class CoinsResponse( val name: String, val symbol: String, val active: Boolean, - val networks: List = listOf() + val networks: List = listOf(), ) : TangemTechResponse { data class Network( val networkId: String, val contractAddress: String? = null, val decimalCount: BigDecimal? = null, + val exchangeable: Boolean? = false, ) : TangemTechResponse } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index a0eafa51d8..e518041674 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -14,11 +14,12 @@ interface TangemTechApi { @GET("coins") suspend fun coins( @Query("contractAddress") contractAddress: String? = null, + @Query("exchangeable") exchangeable: Boolean? = false, @Query("networkIds") networkIds: String? = null, @Query("active") active: Boolean? = null, @Query("searchText") searchText: String? = null, @Query("offset") offset: Int? = null, - @Query("limit") limit: Int? = null + @Query("limit") limit: Int? = null, ): CoinsResponse @GET("rates") diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index 0e88861b6d..d662a98721 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -5,6 +5,7 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.datasource.api.AuthHeaderInterceptor import com.tangem.datasource.api.oneinch.OneInchApi import com.tangem.datasource.api.referral.ReferralApi +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.di.qualifiers.OneInchEthereum import com.tangem.lib.auth.AuthProvider import dagger.Module @@ -21,6 +22,19 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) class NetworkModule { + @Provides + @Singleton + fun provideTangemTechApi(okHttpClient: OkHttpClient): TangemTechApi { + return Retrofit.Builder() + .addConverterFactory( + MoshiConverterFactory.create(), + ) + .baseUrl(DEV_TANGEM_TECH_BASE_URL) + .client(okHttpClient) + .build() + .create(TangemTechApi::class.java) + } + @Provides @Singleton @OneInchEthereum @@ -46,7 +60,7 @@ class NetworkModule { .build(), ), ) - .baseUrl(PROD_REFERRAL_BASE_URL) + .baseUrl(DEV_TANGEM_TECH_BASE_URL) .client(okHttpClient) .build() .create(ReferralApi::class.java) @@ -64,7 +78,8 @@ class NetworkModule { } private companion object { - const val PROD_REFERRAL_BASE_URL = "https://devapi.tangem-tech.com/v1/"//"https://api.tangem-tech.com/v1/" + const val PROD_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v1/" + const val DEV_TANGEM_TECH_BASE_URL = "https://devapi.tangem-tech.com/v1/" const val ONE_INCH_ETHER_BASE_URL = "" } } \ No newline at end of file From e47b11f29e77cde521035b2d7d7a30919f1cb4a5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 29 Nov 2022 22:29:19 +0300 Subject: [PATCH 106/176] Updated on 2026-08-14 --- .../datasource/api/oneinch/OneInchApi.kt | 48 +++++++++---------- .../tangem/feature/swap/SwapRepositoryImpl.kt | 45 +++++++++++++++++ .../swap/converters/QuotesConverter.kt | 16 +++++++ .../swap/converters/TokensConverter.kt | 32 +++++++++++++ .../feature/swap/domain/SwapInteractor.kt | 4 ++ .../feature/swap/domain/SwapRepository.kt | 17 +++++++ .../feature/swap/domain/models/Currency.kt | 28 +++++++++++ .../feature/swap/domain/models/QuoteModel.kt | 7 +++ 8 files changed, 173 insertions(+), 24 deletions(-) create mode 100644 features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt create mode 100644 features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt create mode 100644 features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt index 5ce908280b..774d001e04 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/OneInchApi.kt @@ -42,7 +42,7 @@ interface OneInchApi { @GET("approve/transaction") suspend fun approveTransaction( @Query("tokenAddress") tokenAddress: String, - @Query("amount") amount: String?, + @Query("amount") amount: String? = null, ): ApproveCalldataResponse /** @@ -118,14 +118,14 @@ interface OneInchApi { @Query("fromTokenAddress") fromTokenAddress: String, @Query("toTokenAddress") toTokenAddress: String, @Query("amount") amount: String, - @Query("protocols") protocols: String?, - @Query("fee") fee: String?, - @Query("gasLimit") gasLimit: String?, - @Query("connectorTokens") connectorTokens: String?, - @Query("complexityLevel") complexityLevel: String?, - @Query("mainRouteParts") mainRouteParts: String?, - @Query("parts") parts: String?, - @Query("gasPrice") gasPrice: String?, + @Query("protocols") protocols: String? = null, + @Query("fee") fee: String? = null, + @Query("gasLimit") gasLimit: String? = null, + @Query("connectorTokens") connectorTokens: String? = null, + @Query("complexityLevel") complexityLevel: String? = null, + @Query("mainRouteParts") mainRouteParts: String? = null, + @Query("parts") parts: String? = null, + @Query("gasPrice") gasPrice: String? = null, ): QuoteResponse /** @@ -180,21 +180,21 @@ interface OneInchApi { @Query("amount") amount: String, @Query("fromAddress") fromAddress: String, @Query("slippage") slippage: Int, - @Query("protocols") protocols: String?, - @Query("destReceiver") destinationAddress: String?, - @Query("referrerAddress") referrerAddress: String?, - @Query("fee") fee: String?, - @Query("disableEstimate") disableEstimate: Boolean?, - @Query("permit") permit: String?, - @Query("compatibilityMode") compatibilityMode: Boolean?, - @Query("burnChi") burnChi: Boolean?, - @Query("allowPartialFill") allowPartialFill: Boolean?, - @Query("parts") parts: String?, - @Query("mainRouteParts") mainRouteParts: String?, - @Query("connectorTokens") connectorTokens: String?, - @Query("complexityLevel") complexityLevel: String?, - @Query("gasLimit") gasLimit: String?, - @Query("gasPrice") gasPrice: String?, + @Query("protocols") protocols: String? = null, + @Query("destReceiver") destinationAddress: String? = null, + @Query("referrerAddress") referrerAddress: String? = null, + @Query("fee") fee: String? = null, + @Query("disableEstimate") disableEstimate: Boolean? = null, + @Query("permit") permit: String? = null, + @Query("compatibilityMode") compatibilityMode: Boolean? = null, + @Query("burnChi") burnChi: Boolean? = null, + @Query("allowPartialFill") allowPartialFill: Boolean? = null, + @Query("parts") parts: String? = null, + @Query("mainRouteParts") mainRouteParts: String? = null, + @Query("connectorTokens") connectorTokens: String? = null, + @Query("complexityLevel") complexityLevel: String? = null, + @Query("gasLimit") gasLimit: String? = null, + @Query("gasPrice") gasPrice: String? = null, ): SwapResponse //endregion Swap } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt new file mode 100644 index 0000000000..aab22f9b8c --- /dev/null +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -0,0 +1,45 @@ +package com.tangem.feature.swap + +import com.tangem.datasource.api.oneinch.OneInchApi +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.feature.swap.converters.QuotesConverter +import com.tangem.feature.swap.converters.TokensConverter +import com.tangem.feature.swap.domain.SwapRepository +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class SwapRepositoryImpl @Inject constructor( + private val tangemTechApi: TangemTechApi, + private val oneInchApi: OneInchApi, + private val tokensConverter: TokensConverter, + private val quotesConverter: QuotesConverter, + private val coroutineDispatcher: CoroutineDispatcherProvider, +) : SwapRepository { + + override suspend fun getExchangeableTokens(networkId: String): List { + return withContext(coroutineDispatcher.io) { + tokensConverter.convertList(tangemTechApi.coins(exchangeable = true, networkIds = networkId).coins) + } + } + + override suspend fun findBestQuote(fromTokenAddress: String, toTokenAddress: String, amount: String): QuoteModel { + return withContext(coroutineDispatcher.io) { + quotesConverter.convert( + oneInchApi.quote( + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount, + ), + ) + } + } + + override suspend fun addressForTrust(): String { + return withContext(coroutineDispatcher.io) { + oneInchApi.approveSpender().address + } + } +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt new file mode 100644 index 0000000000..52723c6596 --- /dev/null +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/QuotesConverter.kt @@ -0,0 +1,16 @@ +package com.tangem.feature.swap.converters + +import com.tangem.datasource.api.oneinch.models.QuoteResponse +import com.tangem.feature.swap.domain.models.QuoteModel +import com.tangem.utils.converter.Converter + +class QuotesConverter : Converter { + + override fun convert(value: QuoteResponse): QuoteModel { + return QuoteModel( + fromTokenAmount = value.fromTokenAmount, + toTokenAmount = value.toTokenAmount, + estimatedGas = value.estimatedGas, + ) + } +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt new file mode 100644 index 0000000000..c5414d666c --- /dev/null +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/TokensConverter.kt @@ -0,0 +1,32 @@ +package com.tangem.feature.swap.converters + +import com.tangem.datasource.api.tangemTech.CoinsResponse +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.utils.converter.Converter +import javax.inject.Inject + +class TokensConverter @Inject constructor() : Converter { + + override fun convert(value: CoinsResponse.Coin): Currency { + val network = value.networks.first() + return if (network.contractAddress != null && network.decimalCount != null) { + Currency.NonNativeToken( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = network.networkId, + contractAddress = network.contractAddress!!, + decimalCount = network.decimalCount!!.intValueExact(), + logoUrl = "",//todo add logo + ) + } else { + Currency.NativeToken( + id = value.id, + name = value.name, + symbol = value.symbol, + networkId = network.networkId, + logoUrl = "", //todo add logo + ) + } + } +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt new file mode 100644 index 0000000000..eebb325e5e --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -0,0 +1,4 @@ +package com.tangem.feature.swap.domain + +interface SwapInteractor { +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt new file mode 100644 index 0000000000..5aea22d2b8 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -0,0 +1,17 @@ +package com.tangem.feature.swap.domain + +import com.tangem.feature.swap.domain.models.Currency +import com.tangem.feature.swap.domain.models.QuoteModel + +interface SwapRepository { + + suspend fun getExchangeableTokens(networkId: String): List + + suspend fun findBestQuote( + fromTokenAddress: String, + toTokenAddress: String, + amount: String, + ): QuoteModel + + suspend fun addressForTrust(): String +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt new file mode 100644 index 0000000000..05fe0cad3b --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/Currency.kt @@ -0,0 +1,28 @@ +package com.tangem.feature.swap.domain.models + +sealed class Currency( + open val id: String, + open val name: String, + open val symbol: String, + open val networkId: String, + open val logoUrl: String, +) { + + data class NativeToken( + override val id: String, + override val name: String, + override val symbol: String, + override val networkId: String, + override val logoUrl: String, + ) : Currency(id, name, symbol, networkId, logoUrl) + + data class NonNativeToken( + override val id: String, + override val name: String, + override val symbol: String, + override val networkId: String, + override val logoUrl: String, + val contractAddress: String, + val decimalCount: Int, + ) : Currency(id, name, symbol, networkId, logoUrl) +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt new file mode 100644 index 0000000000..e71378fd22 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/QuoteModel.kt @@ -0,0 +1,7 @@ +package com.tangem.feature.swap.domain.models + +data class QuoteModel( + val fromTokenAmount: String, + val toTokenAmount: String, + val estimatedGas: Int +) \ No newline at end of file From 2413e9a3b704769ca9adcdcf9e8e2571b8a362bc Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 30 Nov 2022 22:13:41 +0300 Subject: [PATCH 107/176] Updated on 2026-08-14 --- .../api/oneinch/models/TransactionDto.kt | 2 +- .../tangem/feature/swap/SwapRepositoryImpl.kt | 37 +++++++++++++++++++ .../swap/converters/ApproveConverter.kt | 17 +++++++++ .../feature/swap/converters/SwapConverter.kt | 24 ++++++++++++ .../feature/swap/domain/SwapRepository.kt | 35 ++++++++++++++++++ .../swap/domain/models/ApproveModel.kt | 14 +++++++ .../domain/models/SwapTransactionModel.kt | 29 +++++++++++++++ 7 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt create mode 100644 features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt create mode 100644 features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapTransactionModel.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TransactionDto.kt b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TransactionDto.kt index bd71ce74db..a32e827601 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TransactionDto.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/oneinch/models/TransactionDto.kt @@ -9,7 +9,7 @@ import com.squareup.moshi.Json * @property toAddress transactions will be sent to our(1inch) contract address * @property data The encoded data to call the approve method on the swapped token contract * @property value Native token value in WEI (for approve is always 0) - * @property gasPrice The encoded data to call the approve method on the swapped token contract + * @property gasPrice maximum amount of gas for a swap default: 11500000; max: 11500000 * @property gas estimated amount of the gas limit, increase this value by 25% * @constructor Create empty Transaction dto */ diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt index aab22f9b8c..2095876a1b 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt @@ -2,9 +2,12 @@ package com.tangem.feature.swap import com.tangem.datasource.api.oneinch.OneInchApi import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.feature.swap.converters.ApproveConverter import com.tangem.feature.swap.converters.QuotesConverter +import com.tangem.feature.swap.converters.SwapConverter import com.tangem.feature.swap.converters.TokensConverter import com.tangem.feature.swap.domain.SwapRepository +import com.tangem.feature.swap.domain.models.ApproveModel import com.tangem.feature.swap.domain.models.Currency import com.tangem.feature.swap.domain.models.QuoteModel import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -16,6 +19,8 @@ class SwapRepositoryImpl @Inject constructor( private val oneInchApi: OneInchApi, private val tokensConverter: TokensConverter, private val quotesConverter: QuotesConverter, + private val swapConverter: SwapConverter, + private val approveConverter: ApproveConverter, private val coroutineDispatcher: CoroutineDispatcherProvider, ) : SwapRepository { @@ -42,4 +47,36 @@ class SwapRepositoryImpl @Inject constructor( oneInchApi.approveSpender().address } } + + override suspend fun dataToApprove(tokenAddress: String, amount: String): ApproveModel { + return withContext(coroutineDispatcher.io) { + approveConverter.convert(oneInchApi.approveTransaction(tokenAddress, amount)) + } + } + + override suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): String { + return withContext(coroutineDispatcher.io) { + oneInchApi.approveAllowance(tokenAddress, walletAddress).allowance + } + } + + override suspend fun prepareSwapTransaction( + fromTokenAddress: String, + toTokenAddress: String, + amount: String, + fromAddress: String, + slippage: Int, + ) { + return withContext(coroutineDispatcher.io) { + swapConverter.convert( + oneInchApi.swap( + fromTokenAddress = fromTokenAddress, + toTokenAddress = toTokenAddress, + amount = amount, + fromAddress = fromAddress, + slippage = slippage, + ), + ) + } + } } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt new file mode 100644 index 0000000000..4d3574869d --- /dev/null +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ApproveConverter.kt @@ -0,0 +1,17 @@ +package com.tangem.feature.swap.converters + +import com.tangem.datasource.api.oneinch.models.ApproveCalldataResponse +import com.tangem.feature.swap.domain.models.ApproveModel +import com.tangem.utils.converter.Converter +import javax.inject.Inject + +class ApproveConverter @Inject constructor() : Converter { + + override fun convert(value: ApproveCalldataResponse): ApproveModel { + return ApproveModel( + data = value.data, + gasPrice = value.gasPrice, + toAddress = value.toAddress, + ) + } +} \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt new file mode 100644 index 0000000000..244ae4f2a0 --- /dev/null +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapConverter.kt @@ -0,0 +1,24 @@ +package com.tangem.feature.swap.converters + +import com.tangem.datasource.api.oneinch.models.SwapResponse +import com.tangem.feature.swap.domain.models.SwapTransactionModel +import com.tangem.utils.converter.Converter +import javax.inject.Inject + +class SwapConverter @Inject constructor() : Converter { + + override fun convert(value: SwapResponse): SwapTransactionModel { + return SwapTransactionModel( + fromTokenAddress = value.fromToken.address, + toTokenAddress = value.toToken.address, + toTokenAmount = value.fromTokenAmount, + fromTokenAmount = value.toTokenAmount, + fromWalletAddress = value.transaction.fromAddress, + toWalletAddress = value.transaction.toAddress, + data = value.transaction.data, + value = value.transaction.value, + gasPrice = value.transaction.gasPrice, + gas = value.transaction.gas, + ) + } +} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt index 5aea22d2b8..963f87bfdb 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapRepository.kt @@ -1,5 +1,6 @@ package com.tangem.feature.swap.domain +import com.tangem.feature.swap.domain.models.ApproveModel import com.tangem.feature.swap.domain.models.Currency import com.tangem.feature.swap.domain.models.QuoteModel @@ -13,5 +14,39 @@ interface SwapRepository { amount: String, ): QuoteModel + /** + * Returns address of 1inch router that must be trusted + * + * @return address + */ suspend fun addressForTrust(): String + + /** + * Generate "data" for calling contract in order to allow 1inch spend funds + * + * @param tokenAddress token you want to exchange + * @param amount number of tokens is allowed. By default infinite + */ + suspend fun dataToApprove( + tokenAddress: String, + amount: String, + ): ApproveModel + + /** + * Get the number of tokens that the 1inch router is allowed to spend + * + * @param tokenAddress Token address you want to exchange + * @param walletAddress address for which you want to check + * + * @return amount of tokens allowed to spend + */ + suspend fun checkTokensSpendAllowance(tokenAddress: String, walletAddress: String): String + + suspend fun prepareSwapTransaction( + fromTokenAddress: String, + toTokenAddress: String, + amount: String, + fromAddress: String, + slippage: Int, + ) } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt new file mode 100644 index 0000000000..2fce5634f9 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ApproveModel.kt @@ -0,0 +1,14 @@ +package com.tangem.feature.swap.domain.models + +/** + * Approve model + * + * @property data The encoded data to call the approve method on the swapped token contract + * @property gasPrice Gas price for fast transaction processing + * @property toAddress Token address that will be allowed to exchange through 1inch router + */ +data class ApproveModel( + val data: String, + val gasPrice: String, + val toAddress: String, +) \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapTransactionModel.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapTransactionModel.kt new file mode 100644 index 0000000000..f36d74eb61 --- /dev/null +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapTransactionModel.kt @@ -0,0 +1,29 @@ +package com.tangem.feature.swap.domain.models + +/** + * Swap transaction model + * + * @property fromTokenAddress token from which want to convert + * @property toTokenAddress token to want to convert + * @property toTokenAmount amount "target" token + * @property fromTokenAmount amount "initial" token + * @property fromWalletAddress wallet from address (transactions will be sent from this address) + * @property toWalletAddress transactions will be sent to our(1inch) contract address + * @property data The encoded data to call the approve method on the swapped token contract + * @property value Native token value in WEI (for approve is always 0) + * @property gasPrice maximum amount of gas for a swap default: 11500000; max: 11500000 + * @property gas estimated amount of the gas limit, increase this value by 25% + * @constructor Create empty Swap transaction model + */ +data class SwapTransactionModel( + val fromTokenAddress: String, + val toTokenAddress: String, + val toTokenAmount: String, + val fromTokenAmount: String, + val fromWalletAddress: String, + val toWalletAddress: String, + val data: String, + val value: String, + val gasPrice: String, + val gas: String, +) \ No newline at end of file From a8656385e2184e6910af5b09cfefc8754fcbbde2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 12:01:18 +0300 Subject: [PATCH 108/176] Updated on 2026-08-14 --- .../tap/features/tokens/redux/TokensMiddleware.kt | 15 ++++++++++++--- .../redux/middlewares/MultiWalletMiddleware.kt | 10 ++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 7b53918da2..eed12cb6b0 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -19,7 +19,8 @@ import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction import com.tangem.domain.redux.domainStore import com.tangem.operations.derivation.ExtendedPublicKeysMap -import com.tangem.tap.* +import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.tap.assetReader import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.ManageTokens import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -34,6 +35,12 @@ import com.tangem.tap.domain.tokens.LoadAvailableCoinsService import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletCurrenciesManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -195,10 +202,11 @@ class TokensMiddleware { } scope.launch { + val card = scanResponse.card val result = tangemSdkManager.derivePublicKeys( - cardId = scanResponse.card.cardId, + cardId = card.cardId, derivations = derivations, - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && card.isAccessCodeSet, ) when (result) { is CompletionResult.Success -> { @@ -220,6 +228,7 @@ class TokensMiddleware { onSuccess(updatedScanResponse) } + is CompletionResult.Failure -> { store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens")) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 927205c938..49b46f98f6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -6,7 +6,6 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.* import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken @@ -30,6 +29,12 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.features.wallet.redux.reducers.toWallet +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.userTokensRepository +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletCurrenciesManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -178,7 +183,8 @@ class MultiWalletMiddleware { state: WalletState?, ) = scope.launch { ScanCardProcessor.scan( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + selectedWallet.scanResponse.card.isAccessCodeSet, cardId = selectedWallet.cardId, additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, ) { scanResponse -> From 103bf16b3d8c3acd09421a65226fd88ea40d7a2f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 12:57:47 +0300 Subject: [PATCH 109/176] Updated on 2026-08-14 --- .../tap/features/wallet/ui/wallet/SingleWalletView.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 7307439224..c0265845aa 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -3,7 +3,11 @@ package com.tangem.tap.features.wallet.ui.wallet import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.beginDelayedTransition +import com.tangem.tap.common.extensions.fitChipsByGroupWidth +import com.tangem.tap.common.extensions.getQuantityString +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction @@ -36,6 +40,7 @@ class SingleWalletView : WalletView() { lCardTotalBalance.root.hide() lSingleWalletBalance.root.hide() lWalletRescanWarning.root.hide() + lWalletBackupWarning.root.hide() lCardBalance.root.show() lAddress.root.show() rowButtons.show() From b2d7534c9806dc4f6c9aec069fa28e240960a2f7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 14:31:20 +0300 Subject: [PATCH 110/176] Updated on 2026-08-14 --- .../com/tangem/tap/common/extensions/Store.kt | 2 +- .../com/tangem/tap/domain/TapWalletManager.kt | 15 ++++--- .../middlewares/MultiWalletMiddleware.kt | 7 +++- .../redux/middlewares/WalletMiddleware.kt | 31 ++++++++++---- .../wallet/redux/reducers/WalletReducer.kt | 2 +- .../redux/WalletSelectorMiddleware.kt | 41 +++++++++++++------ 6 files changed, 69 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index ed66f4c74b..69c5f1c085 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -27,7 +27,7 @@ fun Store<*>.dispatchNotification(resId: Int) { dispatchOnMain(GlobalAction.ShowNotification(resId)) } -@Suppress("unused") // receiver type +@Suppress("UnusedReceiverParameter") suspend fun Store<*>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) { store.state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh) } diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 368ed91c74..21b0ef03bf 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -1,6 +1,11 @@ package com.tangem.tap.domain -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkConfig +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.services.Result @@ -93,18 +98,16 @@ class TapWalletManager { updateConfigManager(scanResponse) withMainContext { + store.dispatch(WalletAction.UserWalletChanged(userWallet)) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) store.dispatch(WalletConnectAction.ResetState) store.dispatch(GlobalAction.SaveScanNoteResponse(scanResponse)) store.dispatch(WalletConnectAction.RestoreSessions(scanResponse)) - store.dispatch(WalletAction.UserWalletChanged(userWallet)) store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) store.dispatch(WalletAction.Warnings.CheckIfNeeded) - - if (refresh) { - loadData(userWallet, refresh = true) - } } + + loadData(userWallet, refresh) } suspend fun loadData(userWallet: UserWallet, refresh: Boolean = false) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 927205c938..b0f5307329 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -6,7 +6,6 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.* import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken @@ -30,6 +29,12 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.features.wallet.redux.reducers.toWallet +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.userTokensRepository +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletCurrenciesManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 1bb5cd9b42..ea7c79345b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -11,10 +11,18 @@ import com.tangem.common.services.Result import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.attestation.Attestation import com.tangem.operations.attestation.OnlineCardVerifier -import com.tangem.tap.* import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.copyToClipboard +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification +import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.dispatchOpenUrl +import com.tangem.tap.common.extensions.dispatchToastNotification +import com.tangem.tap.common.extensions.isGreaterThan +import com.tangem.tap.common.extensions.onCardScanned +import com.tangem.tap.common.extensions.shareText +import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -27,13 +35,24 @@ import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen -import com.tangem.tap.features.wallet.models.* +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.PendingTransactionType +import com.tangem.tap.features.wallet.models.filterByCoin +import com.tangem.tap.features.wallet.models.getPendingTransactions +import com.tangem.tap.features.wallet.models.getSendableAmounts import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletStore import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.network.NetworkStateChanged +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import com.tangem.tap.totalFiatBalanceCalculator +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.userWalletsListManagerSafe import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -273,11 +292,7 @@ class WalletMiddleware { is WalletAction.ChangeWallet -> { changeWallet() } - is WalletAction.UserWalletChanged -> { - scope.launch { - globalState.tapWalletManager.loadData(action.userWallet) - } - } + is WalletAction.UserWalletChanged -> Unit is WalletAction.WalletStoresChanged -> { scope.launch(Dispatchers.Default) { fetchTotalFiatBalance(action.walletStores, walletState) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 54833f79e2..e9658e14fa 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -25,8 +25,8 @@ import com.tangem.tap.features.wallet.models.WalletRent import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData -import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.features.wallet.ui.TokenData +import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.store import org.rekotlin.Action import timber.log.Timber diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 00540c19f2..bac210c6d4 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.walletSelector.redux +import com.tangem.common.CompletionResult import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap @@ -149,18 +150,8 @@ internal class WalletSelectorMiddleware { private fun removeWallets(walletIdsToRemove: List, state: WalletSelectorState) { scope.launch { when (walletIdsToRemove.size) { - state.wallets.size -> { // Removing all wallets - userWalletsListManager.clear() - .flatMap { walletStoresManager.clear() } - .doOnSuccess { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) - } - } - - else -> { - userWalletsListManager.delete(walletIdsToRemove.map { UserWalletId(it) }) - .flatMap { walletStoresManager.delete(walletIdsToRemove) } - } + state.wallets.size -> clearUserWallets() + else -> removeUserWallets(walletIdsToRemove, state) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) @@ -197,6 +188,31 @@ internal class WalletSelectorMiddleware { ) } + private suspend fun clearUserWallets(): CompletionResult { + return userWalletsListManager.clear() + .flatMap { walletStoresManager.clear() } + .doOnSuccess { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + + private suspend fun removeUserWallets( + walletIdsToRemove: List, + state: WalletSelectorState, + ): CompletionResult { + val prevSelectedWalletId = state.selectedWalletId + return userWalletsListManager.delete(walletIdsToRemove.map { UserWalletId(it) }) + .flatMap { walletStoresManager.delete(walletIdsToRemove) } + .doOnSuccess { + val selectedWallet = userWalletsListManager.selectedUserWalletSync ?: return@doOnSuccess + val isSelectedWalletRemoved = prevSelectedWalletId != selectedWallet.walletId.stringValue + + if (isSelectedWalletRemoved) { + store.onUserWalletSelected(selectedWallet) + } + } + } + private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( walletStores: List, ): UserWalletModel { @@ -205,6 +221,7 @@ internal class WalletSelectorMiddleware { is UserWalletModel.Type.MultiCurrency -> type.copy( tokensCount = walletStores.flatMap { it.walletsData }.size, ) + is UserWalletModel.Type.SingleCurrency -> type.copy( blockchainName = walletStores .firstOrNull() From 33147a45903615f79712685432e4d655dc85e8f2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 14:37:16 +0300 Subject: [PATCH 111/176] Updated on 2026-08-14 --- .../features/details/ui/appsettings/AppSettingsFragment.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt index bea7b87171..433db0dd49 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt @@ -14,7 +14,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store -import com.tangem.wallet.R import org.rekotlin.StoreSubscriber class AppSettingsFragment : Fragment(), StoreSubscriber { @@ -25,8 +24,8 @@ class AppSettingsFragment : Fragment(), StoreSubscriber { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) } override fun onCreateView( @@ -68,4 +67,4 @@ class AppSettingsFragment : Fragment(), StoreSubscriber { if (activity == null || view == null) return screenState.value = viewModel.updateState(state) } -} +} \ No newline at end of file From 93c4d337d220fe0f6c072406f17b6404c819d988 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 18:41:00 +0300 Subject: [PATCH 112/176] Updated on 2026-08-14 --- .../tap/domain/scanCard/ScanCardProcessor.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index eef2f2afb5..d834911426 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -124,15 +124,15 @@ object ScanCardProcessor { crossinline nextHandler: suspend (ScanResponse) -> Unit, ) { val disclaimerType = DisclaimerType.get(scanResponse) - store.dispatch(DisclaimerAction.SetDisclaimerType(disclaimerType)) + store.dispatchOnMain(DisclaimerAction.SetDisclaimerType(disclaimerType)) if (disclaimerType.isAccepted()) { nextHandler((scanResponse)) } else scope.launch(Dispatchers.Main) { delay(DELAY_SDK_DIALOG_CLOSE) - store.dispatch( + store.dispatchOnMain( DisclaimerAction.Show { - scope.launch { + scope.launch(Dispatchers.Main) { nextHandler(scanResponse) } }, @@ -155,7 +155,7 @@ object ScanCardProcessor { Analytics.addParamsInterceptor(BatchIdParamsInterceptor(scanResponse.card.batchId)) - store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) + store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) if (scanResponse.isSaltPay()) { if (scanResponse.isSaltPayVisa()) { @@ -168,9 +168,9 @@ object ScanCardProcessor { val isOnboardingCase = result.data if (isOnboardingCase) { onWalletNotCreated() - store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) - store.dispatch(OnboardingSaltPayAction.SetDependencies(manager, config)) - store.dispatch(OnboardingSaltPayAction.Update) + store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) + store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config)) + store.dispatchOnMain(OnboardingSaltPayAction.Update) navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } } else { delay(DELAY_SDK_DIALOG_CLOSE) @@ -198,13 +198,13 @@ object ScanCardProcessor { } else { if (OnboardingHelper.isOnboardingCase(scanResponse)) { onWalletNotCreated() - store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) + store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) val appScreen = OnboardingHelper.whereToNavigate(scanResponse) navigateTo(appScreen) { onProgressStateChange(it) } } else { if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { onWalletNotCreated() - store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) + store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } } else { delay(DELAY_SDK_DIALOG_CLOSE) From aa143c12fbd04f6a9fbf7caf37a3e1c8b6ceb094 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Dec 2022 10:27:45 +0300 Subject: [PATCH 113/176] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../tap/common/feedback/FeedbackData.kt | 4 +- .../details/ui/details/DetailsScreenState.kt | 2 +- .../details/ui/resetcard/ResetCardScreen.kt | 3 +- .../saltPay/ui/OnboardingSaltPayView.kt | 2 +- .../ui/components/SaveWalletScreenContent.kt | 7 +- .../WallectSelectorScreenContent.kt | 5 +- .../ui/components/WelcomeScreenContent.kt | 10 +- app/src/main/res/values-de/strings.xml | 5 - .../main/res/values-de/strings_blockchain.xml | 2 - app/src/main/res/values-fr/strings.xml | 5 - .../main/res/values-fr/strings_blockchain.xml | 1 - app/src/main/res/values-it/strings.xml | 5 - .../main/res/values-it/strings_blockchain.xml | 1 - app/src/main/res/values-ru/strings.xml | 28 +--- .../main/res/values-ru/strings_blockchain.xml | 2 - app/src/main/res/values/strings.xml | 7 - .../main/res/values/strings_blockchain.xml | 2 - .../main/res/values/strings_untranslated.xml | 19 --- core/res/.gitignore | 1 + core/res/build.gradle.kts | 21 +++ core/res/src/main/AndroidManifest.xml | 2 + .../src/main/res/values-de}/strings_final.xml | 132 +++++++++++----- .../res/values-de/strings_plurals_final.xml | 5 + .../src/main/res/values-fr}/strings_final.xml | 121 ++++++++++++--- .../res/values-fr}/strings_plurals_final.xml | 6 + .../src/main/res/values-it}/strings_final.xml | 121 ++++++++++++--- .../res/values-it}/strings_plurals_final.xml | 7 +- .../src/main/res/values-ru/strings_final.xml | 141 ++++++++++++------ .../res/values-ru/strings_plurals_final.xml | 7 + .../src/main/res/values}/strings_final.xml | 127 +++++++++++++--- .../main/res/values/strings_plurals_final.xml | 5 + core/ui/build.gradle.kts | 2 + .../ui/src/main/res/values-de/strings-app.xml | 6 + .../ui/src/main/res/values-fr/strings-app.xml | 6 + .../ui/src/main/res/values-it/strings-app.xml | 6 + .../src/main/res/values-ru/string_common.xml | 6 - .../ui/src/main/res/values-ru/strings-app.xml | 6 + core/ui/src/main/res/values/string_common.xml | 6 - core/ui/src/main/res/values/strings-app.xml | 6 + .../referral/presentation/build.gradle.kts | 1 + .../referral/ui/ParticipateBottomBlock.kt | 10 +- .../src/main/res/values-ru/string.xml | 25 ---- .../src/main/res/values/strings.xml | 25 ---- settings.gradle.kts | 1 + 45 files changed, 613 insertions(+), 300 deletions(-) create mode 100644 core/res/.gitignore create mode 100644 core/res/build.gradle.kts create mode 100644 core/res/src/main/AndroidManifest.xml rename core/{ui/src/main/res/values => res/src/main/res/values-de}/strings_final.xml (69%) rename core/{ui => res}/src/main/res/values-de/strings_plurals_final.xml (65%) rename core/{ui/src/main/res/values-it => res/src/main/res/values-fr}/strings_final.xml (62%) rename core/{ui/src/main/res/values-it => res/src/main/res/values-fr}/strings_plurals_final.xml (65%) rename core/{ui/src/main/res/values-de => res/src/main/res/values-it}/strings_final.xml (62%) rename core/{ui/src/main/res/values-fr => res/src/main/res/values-it}/strings_plurals_final.xml (65%) rename core/{ui => res}/src/main/res/values-ru/strings_final.xml (67%) rename core/{ui => res}/src/main/res/values-ru/strings_plurals_final.xml (64%) rename core/{ui/src/main/res/values-fr => res/src/main/res/values}/strings_final.xml (61%) rename core/{ui => res}/src/main/res/values/strings_plurals_final.xml (75%) create mode 100644 core/ui/src/main/res/values-de/strings-app.xml create mode 100644 core/ui/src/main/res/values-fr/strings-app.xml create mode 100644 core/ui/src/main/res/values-it/strings-app.xml delete mode 100644 core/ui/src/main/res/values-ru/string_common.xml create mode 100644 core/ui/src/main/res/values-ru/strings-app.xml delete mode 100644 core/ui/src/main/res/values/string_common.xml create mode 100644 core/ui/src/main/res/values/strings-app.xml delete mode 100644 features/referral/presentation/src/main/res/values-ru/string.xml delete mode 100644 features/referral/presentation/src/main/res/values/strings.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8dd72df3e7..2deae5eec6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -110,6 +110,7 @@ dependencies { implementation(project(":domain")) implementation(project(":network")) implementation(project(":common")) + implementation(project(":core:res")) implementation(project(":core:ui")) implementation(project(":core:utils")) implementation(project(":libs:crypto")) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt index 6578b97356..8a426cccfc 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt @@ -96,8 +96,8 @@ class FeedbackEmail : FeedbackData { } class SupportInfo : FeedbackData { - override val subjectResId: Int = R.string.details_ask_a_question - override val mainMessageResId: Int = R.string.details_ask_a_question + override val subjectResId: Int = R.string.details_chat + override val mainMessageResId: Int = R.string.details_chat override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String { return FeedbackDataBuilder(infoHolder) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index 2120746a74..68da6ac85f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -21,7 +21,7 @@ enum class SettingsElement( val titleRes: Int, ) { WalletConnect(R.drawable.ic_walletconnect, R.string.wallet_connect_title), - Chat(R.drawable.ic_chat, R.string.details_ask_a_question), + Chat(R.drawable.ic_chat, R.string.details_chat), SendFeedback(R.drawable.ic_comment, R.string.details_row_title_send_feedback), // ReferralProgram(R.drawable.ic_add_friends, R.string.details_referral_title), CardSettings(R.drawable.ic_card_settings, R.string.card_settings_title), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 6f29a508b1..85d53a13eb 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.details.ui.resetcard import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -64,7 +63,7 @@ fun ResetCardView( contentDescription = "", modifier = modifier.offset(y = (-82).dp), ) - ScreenTitle(titleRes = R.string.reset_card_to_factory_navigation_title) + ScreenTitle(titleRes = R.string.card_settings_reset_card_to_factory) } Spacer( modifier = modifier.weight(1f), diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt index dc934546aa..fcbcfb74ff 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt @@ -250,7 +250,7 @@ internal class OnboardingSaltPayView( } private fun handleClaim(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_navbar_claim) + toolbar.title = getText(R.string.onboarding_getting_started) val btnMain = actionContainer.btnContainer.findViewById(R.id.btn_main_action) val tvHeader = actionContainer.tvHeader val tvBody = actionContainer.tvBody diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt index b2eeba4b72..2451c9234a 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt @@ -83,7 +83,7 @@ internal fun SaveWalletScreenContent( SpacerH24() Text( modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.save_user_wallet_agreement_header), + text = stringResource(R.string.onboarding_navbar_save_wallet), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, @@ -100,7 +100,10 @@ internal fun SaveWalletScreenContent( PrimaryButton( modifier = Modifier.fillMaxWidth(), showProgress = showProgress, - text = stringResource(R.string.save_user_wallet_agreement_allow), + text = stringResource( + id = R.string.save_user_wallet_agreement_allow, + stringResource(id = R.string.common_biometric_authentication), + ), onClick = onSaveWalletClick, ) SpacerH16() diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 28995ad24b..106ff7f37b 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -81,7 +81,10 @@ internal fun WalletSelectorScreenContent( modifier = Modifier .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), - text = stringResource(R.string.user_wallet_list_unlock_all_face_id), + text = stringResource( + id = R.string.user_wallet_list_unlock_all, + stringResource(id = R.string.common_biometrics), + ), showProgress = state.showUnlockProgress, onClick = onUnlockClick, ) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index f9543d5c97..c0a780226b 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -58,7 +58,10 @@ internal fun WelcomeScreenContent( modifier = Modifier .padding(horizontal = TangemTheme.dimens.spacing44) .fillMaxWidth(), - text = stringResource(R.string.welcome_unlock_description), + text = stringResource( + id = R.string.welcome_unlock_description, + stringResource(id = R.string.common_biometric_authentication), + ), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, @@ -68,7 +71,10 @@ internal fun WelcomeScreenContent( modifier = Modifier .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), - text = stringResource(R.string.welcome_unlock_face_id), + text = stringResource( + id = R.string.welcome_unlock, + stringResource(id = R.string.common_biometrics), + ), showProgress = showUnlockProgress, onClick = onUnlockClick, ) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index cc06be0a27..c4e3022f03 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -2,18 +2,14 @@ Änderungen speichern Warnung - Erneut versuchen Erledigt - Abbrechen Akzeptieren Entfernen Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an - Karte scannen Absenden ein Wallet erstellen Tangem - Tokens Verifizierte Bilanz Die Bilanz wird aufgeladen… Das Konto ist nicht erstellt @@ -49,7 +45,6 @@ inkl. Gebühr %s wird gesendet %s und %s werden gesendet - Bilanz: %s Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert Details KartenID diff --git a/app/src/main/res/values-de/strings_blockchain.xml b/app/src/main/res/values-de/strings_blockchain.xml index 910ae90ce2..83e9734f88 100644 --- a/app/src/main/res/values-de/strings_blockchain.xml +++ b/app/src/main/res/values-de/strings_blockchain.xml @@ -11,12 +11,10 @@ Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden Um %s XTZ reduzieren Nein, alles senden - Ungültige Adresse Ungültiger Betrag Der Betrag geht über die Bilanz hinaus Der Gesamtbetrag geht über die Bilanz hinaus Die Gebühr geht über die Bilanz hinaus Minimum balance is %s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f631f44d40..25ebbdc84a 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -3,18 +3,14 @@ Sauvegarder les modifications Alerte - Réessayer Exécuté - Annuler J\'accepte Supprimer Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité - Scannez la carte Envoyer Créer un portefeuille Tangem - Jetons d\'authentification Solde confirmé Solde est en cours de téléchargement … Compte n\'est pas créé @@ -50,7 +46,6 @@ Inclure les commissions Sera envoyé %s Sera envoyé %s et %s - Solde : %s La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps Référénces ID de la carte diff --git a/app/src/main/res/values-fr/strings_blockchain.xml b/app/src/main/res/values-fr/strings_blockchain.xml index 60b04a9489..818ddca1ea 100644 --- a/app/src/main/res/values-fr/strings_blockchain.xml +++ b/app/src/main/res/values-fr/strings_blockchain.xml @@ -8,7 +8,6 @@ Le compte cible n\'a pas été créé. Le montant à envoyer doit être de %s + commissions ou plus Échec de réception des commissions Erreur inconnue - Adresse incorrecte Somme incorrecte Le montant dépasse le solde Le total dépasse le solde diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 613ba4dfc2..832d16e7e1 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -3,16 +3,12 @@ Mantieni le modifiche Avviso - Riprova Fatto - Annulla Rimuovere Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy - Scansiona carta Invia Crea portafoglio Tangem - Token Saldo verificato Il saldo sta per essere caricato… Conto non creato @@ -49,7 +45,6 @@ Includi commissione Sarà inviato %s Sarà inviato %s e %s - Saldo: %s La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo Requisiti ID carta diff --git a/app/src/main/res/values-it/strings_blockchain.xml b/app/src/main/res/values-it/strings_blockchain.xml index d51ca39197..e7f2fa6d3f 100644 --- a/app/src/main/res/values-it/strings_blockchain.xml +++ b/app/src/main/res/values-it/strings_blockchain.xml @@ -9,7 +9,6 @@ L\'importo supera il saldo Il conto totale non è stato creato L\'importo da inviare deve essere %s + commissione o maggiore Impossibile ottenere la commissione - Indirizzo non valido Importo non valido Il totale supera il saldo La commissione supera il saldo diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3b187cf2d3..c248c5c69d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -3,21 +3,16 @@ Tangem Сохранить изменения Предупреждение - Повторить Готово - Отмена Принять Удалить %s Вы не предоставили доступ к своей камере, измените настройки конфиденциальности - Сканировать карту Отправить Создать кошелек Tangem - Токены Подтвержденный баланс Загрузка баланса… - Сеть недоступна Аккаунт не создан Пополните счет на %s+ %s, чтобы создать аккаунт Карта пуста @@ -51,7 +46,6 @@ Включить коммисию %s будет отправлено %s и %s будет отправлено - Баланс: %s Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время Подробности ID карты @@ -117,30 +111,12 @@ Один вопрос Вам нравится приложение Tangem? Добавить токен - Адрес контракта - Обязательное поле - Пожалуйста, выберите сеть - Количество знаков после запятой должно быть корректным числом не больше %d - Адрес контракта некорректен - Путь деривации некорректен - Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить - Этот токен/сеть уже находится в вашем списке - Знаков после запятой - Сеть - Не выбрано - Например, USD Coin - Название токена - Например, USDC - Символ токена - Деривация по BIP44 - По-умолчанию Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства Проверка подлинности не удалась Эта карта может быть производственным образцом или подделкой Важная информация о безопасности \u26A0 Эта карта ранее подписывала транзакции Узнать подробнее - Я понял На этой карте доступно только %s подписей. Вы должны вывести все свои средства. Отключить Подписать @@ -162,7 +138,6 @@ Показать адрес кошелька Успешно! Ваша карта активирована и готова к использованию - Продолжить Баланс Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. @@ -171,7 +146,7 @@ Блокчейн недоступен. Попробуй позже. Хорошо, понял! Успешно! - Начнем + Подготовка Выполнить резервное копирование Оставить на потом Завершить процесс резервного копирования @@ -254,6 +229,5 @@ Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая маленькая деталь может помочь. Привет, команда поддержки, Информация ниже не является обязательной. Вы можете стереть её, если хотите. - Нет ≈ %s (вкл. комиссию: %s) diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml index ee2464671d..7378ec8c0d 100644 --- a/app/src/main/res/values-ru/strings_blockchain.xml +++ b/app/src/main/res/values-ru/strings_blockchain.xml @@ -8,7 +8,6 @@ Не удалось получить комиссию Аккаунт получателя не создан. Сумма для отправки должна быть %s + комиссия или больше Неизвестная ошибка - Неверный адрес Недопустимая сумма Сумма превышает остаток Отправляемая сумма превышает остаток @@ -17,6 +16,5 @@ Уменьшить на %s XTZ Нет, отправить все Минимальный баланс: %s - Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта. \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31d6ed2a36..ad66d064a0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,25 +4,19 @@ Save changes Warning - Retry Done - Cancel Accept Delete %s You have not given access to your camera, please adjust your privacy settings - Scan card - Send Create wallet Tangem - Tokens Verified Balance Balance is loading… - Network is unreachable Account is not created Load %s+ %s to create account @@ -61,7 +55,6 @@ Include fee %s will be sent %s and %s will be sent - Balance: %s Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while Details diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml index a286c1a5f8..a8875b9313 100644 --- a/app/src/main/res/values/strings_blockchain.xml +++ b/app/src/main/res/values/strings_blockchain.xml @@ -8,7 +8,6 @@ Failed to get fee Target account is not created. Amount to send should be %s + fee or more Unknown error - Invalid address Invalid amount Amount exceeds balance Total amount exceeds balance @@ -17,6 +16,5 @@ Reduce by %s XTZ No, send all Minimum balance is %s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. Destination account is not active. Send %s or more to activate the account. \ No newline at end of file diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 369b860e27..ba3342790f 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -43,23 +43,6 @@ //Manage tokens Add Token - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default //Details @@ -73,7 +56,6 @@ This card has signed transactions in the past Learn more This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection. - I understand There are only %s signatures available on this card. You must withdraw all of your funds. @@ -129,7 +111,6 @@ Success! Your card is activated and ready to be used - Continue Balance Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. diff --git a/core/res/.gitignore b/core/res/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/core/res/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/res/build.gradle.kts b/core/res/build.gradle.kts new file mode 100644 index 0000000000..ff743d3748 --- /dev/null +++ b/core/res/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("com.android.library") + kotlin("android") +} + +android { + defaultConfig { + compileSdk = AppConfig.compileSdkVersion + minSdk = AppConfig.minSdkVersion + targetSdk = AppConfig.targetSdkVersion + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} \ No newline at end of file diff --git a/core/res/src/main/AndroidManifest.xml b/core/res/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..1420909c0e --- /dev/null +++ b/core/res/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml similarity index 69% rename from core/ui/src/main/res/values/strings_final.xml rename to core/res/src/main/res/values-de/strings_final.xml index 450d76907c..106dd30cc9 100644 --- a/core/ui/src/main/res/values/strings_final.xml +++ b/core/res/src/main/res/values-de/strings_final.xml @@ -35,38 +35,58 @@ This feature is disabled in Demo mode Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. Available networks + %s network not found. Please, add it first and try again. + Attention Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. Tokens in Solana network are not supported by this card due to firmware limitation. Contract address copied! - If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds - Attention + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default The server is not available, please try again later Total balance The amount does not include some of your funds + Tokens Manage tokens No rate + Network is unreachable Hide token Hide %s Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. Unable to hide %s The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - %s network not found. Please, add it first and try again. - This network is not supported. Please select another network. - %s network Your wallet has not been backed up To protect your assets, we advise you to carry out this procedure - Remove token + Erneut versuchen + Chat + %s network + I understand + Yes + No Russian bank cards are not accepted at the moment Do you have a bank card of another country or a UnionPay card? - Yes - No - Chat - Tangem Bot + This network is not supported. Please select another network. Card Settings Security Mode Change access code Access code will be changed on this card only + Continue + Tangem Bot Get your card ready! Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card @@ -79,9 +99,6 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - Link More Cards - Privacy policy - Reset to factory settings This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the card @@ -90,12 +107,26 @@ Select network Scan your card To access all the networks you need to scan the card - Failed to establish WalletConnect session - Memo - Tag - Invalid Memo. It won\'t be added to the transaction. - Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Karte scannen + Save your Wallet + Note that making a transaction with your funds will still require your card + Allow to use %s + New feature + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo Attention Tap the card with the visa logo No funds for activation @@ -128,7 +159,6 @@ Tap the SaltPay card Tap the Tangem card Support - Getting started Claim %s To get started, simply claim wxDAI to your wallet Claim @@ -140,27 +170,53 @@ Do you want to exit the activation process? In this case, you will need to start from the beginning. You have used a card from another wallet. Tap the card associated with this wallet - Internal error: wallet manager not found - Welcome back! - Use biometric authentication or scan a card to access the app - Log in with biometric - Scan card - New feature - Save your Wallet - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - Allow to link wallet - Making a transaction with your funds will still require card tapping + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Bilanz: %s + Share + Copy + Erfolg + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Ungültige Adresse + This wallet has already been saved, you can add another one + Entfernen + Privacy policy Enable biometric authorization It looks like you have biometric authorization disabled, it is necessary to save wallets Enable - Cancel - Multi-currency - Single-currency - Rename Wallet - Wallet name - Unlock all with biometrics - Add new wallet + Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. %d selected - My Wallets - This wallet has already been saved, you can add another one + biometric authentication + biometrics diff --git a/core/ui/src/main/res/values-de/strings_plurals_final.xml b/core/res/src/main/res/values-de/strings_plurals_final.xml similarity index 65% rename from core/ui/src/main/res/values-de/strings_plurals_final.xml rename to core/res/src/main/res/values-de/strings_plurals_final.xml index 02204c79ec..0d9fa7c350 100644 --- a/core/ui/src/main/res/values-de/strings_plurals_final.xml +++ b/core/res/src/main/res/values-de/strings_plurals_final.xml @@ -8,4 +8,9 @@ %d token %d tokens + + + %d wallet + %d wallets + diff --git a/core/ui/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml similarity index 62% rename from core/ui/src/main/res/values-it/strings_final.xml rename to core/res/src/main/res/values-fr/strings_final.xml index ca07d2b5c7..740d0d8faf 100644 --- a/core/ui/src/main/res/values-it/strings_final.xml +++ b/core/res/src/main/res/values-fr/strings_final.xml @@ -5,7 +5,7 @@ 2 cards Shipping Free - I have a promo code... + I have a promo code… Total Other payment methods Buy now @@ -35,38 +35,58 @@ This feature is disabled in Demo mode Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. Available networks + %s network not found. Please, add it first and try again. + Attention Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. Tokens in Solana network are not supported by this card due to firmware limitation. Contract address copied! - If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds - Attention + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default The server is not available, please try again later Total balance The amount does not include some of your funds + Tokens Manage tokens No rate + Network is unreachable Hide token Hide %s Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. Unable to hide %s The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - %s network not found. Please, add it first and try again. - This network is not supported. Please select another network. - %s network Your wallet has not been backed up To protect your assets, we advise you to carry out this procedure - Remove token - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? + Réessayer + Chat + %s network + I understand Yes No - Chat - Tangem Bot + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. Card Settings Security Mode Change access code Access code will be changed on this card only + Continue + Tangem Bot Get your card ready! Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card @@ -79,9 +99,6 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - Link More Cards - Privacy policy - Reset to factory settings This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the card @@ -90,12 +107,26 @@ Select network Scan your card To access all the networks you need to scan the card - Failed to establish WalletConnect session - Memo - Tag - Invalid Memo. It won\'t be added to the transaction. - Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scannez la carte + Save your Wallet + Note that making a transaction with your funds will still require your card + Allow to use %s + New feature + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo Attention Tap the card with the visa logo No funds for activation @@ -128,7 +159,6 @@ Tap the SaltPay card Tap the Tangem card Support - Getting started Claim %s To get started, simply claim wxDAI to your wallet Claim @@ -140,4 +170,53 @@ Do you want to exit the activation process? In this case, you will need to start from the beginning. You have used a card from another wallet. Tap the card associated with this wallet - \ No newline at end of file + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Solde : %s + Share + Copy + Avec succès + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Adresse incorrecte + This wallet has already been saved, you can add another one + Supprimer + Privacy policy + Enable biometric authorization + It looks like you have biometric authorization disabled, it is necessary to save wallets + Enable + Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. + %d selected + biometric authentication + biometrics + diff --git a/core/ui/src/main/res/values-it/strings_plurals_final.xml b/core/res/src/main/res/values-fr/strings_plurals_final.xml similarity index 65% rename from core/ui/src/main/res/values-it/strings_plurals_final.xml rename to core/res/src/main/res/values-fr/strings_plurals_final.xml index e79bd3f0cd..622131bdfd 100644 --- a/core/ui/src/main/res/values-it/strings_plurals_final.xml +++ b/core/res/src/main/res/values-fr/strings_plurals_final.xml @@ -10,4 +10,10 @@ %d tokens %d tokens + + + %d wallet + %d wallets + %d wallets + diff --git a/core/ui/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml similarity index 62% rename from core/ui/src/main/res/values-de/strings_final.xml rename to core/res/src/main/res/values-it/strings_final.xml index ca07d2b5c7..b284d9eb91 100644 --- a/core/ui/src/main/res/values-de/strings_final.xml +++ b/core/res/src/main/res/values-it/strings_final.xml @@ -5,7 +5,7 @@ 2 cards Shipping Free - I have a promo code... + I have a promo code… Total Other payment methods Buy now @@ -35,38 +35,58 @@ This feature is disabled in Demo mode Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. Available networks + %s network not found. Please, add it first and try again. + Attention Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. Tokens in Solana network are not supported by this card due to firmware limitation. Contract address copied! - If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds - Attention + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default The server is not available, please try again later Total balance The amount does not include some of your funds + Tokens Manage tokens No rate + Network is unreachable Hide token Hide %s Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. Unable to hide %s The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - %s network not found. Please, add it first and try again. - This network is not supported. Please select another network. - %s network Your wallet has not been backed up To protect your assets, we advise you to carry out this procedure - Remove token - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? + Riprova + Chat + %s network + I understand Yes No - Chat - Tangem Bot + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. Card Settings Security Mode Change access code Access code will be changed on this card only + Continue + Tangem Bot Get your card ready! Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card @@ -79,9 +99,6 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - Link More Cards - Privacy policy - Reset to factory settings This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the card @@ -90,12 +107,26 @@ Select network Scan your card To access all the networks you need to scan the card - Failed to establish WalletConnect session - Memo - Tag - Invalid Memo. It won\'t be added to the transaction. - Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scansiona carta + Save your Wallet + Note that making a transaction with your funds will still require your card + Allow to use %s + New feature + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo Attention Tap the card with the visa logo No funds for activation @@ -128,7 +159,6 @@ Tap the SaltPay card Tap the Tangem card Support - Getting started Claim %s To get started, simply claim wxDAI to your wallet Claim @@ -140,4 +170,53 @@ Do you want to exit the activation process? In this case, you will need to start from the beginning. You have used a card from another wallet. Tap the card associated with this wallet - \ No newline at end of file + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Saldo: %s + Share + Copy + Con successo + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Indirizzo non valido + This wallet has already been saved, you can add another one + Rimuovere + Privacy policy + Enable biometric authorization + It looks like you have biometric authorization disabled, it is necessary to save wallets + Enable + Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. + %d selected + biometric authentication + biometrics + diff --git a/core/ui/src/main/res/values-fr/strings_plurals_final.xml b/core/res/src/main/res/values-it/strings_plurals_final.xml similarity index 65% rename from core/ui/src/main/res/values-fr/strings_plurals_final.xml rename to core/res/src/main/res/values-it/strings_plurals_final.xml index e79bd3f0cd..0d9fa7c350 100644 --- a/core/ui/src/main/res/values-fr/strings_plurals_final.xml +++ b/core/res/src/main/res/values-it/strings_plurals_final.xml @@ -1,13 +1,16 @@ %d card - %d cards %d cards %d token - %d tokens %d tokens + + + %d wallet + %d wallets + diff --git a/core/ui/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml similarity index 67% rename from core/ui/src/main/res/values-ru/strings_final.xml rename to core/res/src/main/res/values-ru/strings_final.xml index f111ad038a..a439e2b3e9 100644 --- a/core/ui/src/main/res/values-ru/strings_final.xml +++ b/core/res/src/main/res/values-ru/strings_final.xml @@ -35,52 +35,70 @@ Эта функция недоступна в демонстрационном режиме Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек. Доступные сети + Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. + Внимание Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства. Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки. Адрес контракта скопирован! - Если вы совершили ошибку с выбором сети при переводе средств с биржи, эта инструкция поможет вам восстановить средства - Внимание + Адрес контракта + Обязательное поле + Пожалуйста, выберите сеть + Количество знаков после запятой должно быть корректным числом не больше %d + Адрес контракта некорректен + Путь деривации некорректен + Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить + Этот токен/сеть уже находится в вашем списке + Знаков после запятой + Сеть + Не выбрано + Например, USD Coin + Название токена + Например, USDC + Символ токена + Деривация по BIP44 + По-умолчанию Сервер недоступен, повторите попытку позднее Баланс В сумме учтены не все монеты + Токены Управление токенами Нет цены + Сеть недоступна Скрыть токен Скрыть %s Скрыть Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. Невозможно скрыть %s Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. - Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. - Сеть не поддерживается. Пожалуйста, выберите другую сеть. - Сеть %s Бэкап кошелька не был произведен Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру - Карты банков РФ в данный момент не принимаются - У вас есть карта банка другой страны или платежной системы UnionPay? + Повторить + Чат + Сеть %s + Я понял Да Нет - Чат - Tangem Bot + Карты банков РФ в данный момент не принимаются + У вас есть карта банка другой страны или платежной системы UnionPay? + Сеть не поддерживается. Пожалуйста, выберите другую сеть. Настройки карты Тип безопасности Смена кода доступа Код доступа будет изменен только на данной карте + Продолжить + Tangem Bot Приготовьте свою карту Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. Сканировать Настройки приложения Cохранение кошелька - Если эта опция включена, кошельки всех отсканированных карт будут привязываться к приложению и при открытии приложения будет необходимо пройти биометрическую аутентификацию. Подпись транзакций все так же потребует карту. + Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. Сохранение кода доступа - Если эта опция включена, пароли от карты будут хранится на телефоне в зашифрованном виде и при работе с картой вместо пароля будет запрашиваться биометрическая аутентификация + Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены. Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. WalletConnect Подключение к Dapps - Добавить еще карты - Privacy policy - Сброс к заводским настройкам Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку Сбросить карту @@ -89,12 +107,26 @@ Выберите сеть Отсканируйте карту Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту - Memo - Tag - Недопустимый Memo. Он не будет добавлен в транзакцию. - Недопустимый Tag. Он не будет добавлен в транзакцию. - Не удалось установить сессию WalletConnect Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. + C возвращением! + Используйте %s или код доступа для входа в приложение + Войти с %s + Сканировать карту + Сохраните ваш кошелек + Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта + Использовать %s + Новый функционал + Мои кошельки + Мультивалютные + Одновалютные + Добавить новый кошелек + Переименование кошелька + Имя кошелька + Разблокировать все с %s + Недопустимый Tag. Он не будет добавлен в транзакцию. + Недопустимый Memo. Он не будет добавлен в транзакцию. + Tag + Memo Внимание Приложите карту с логотипом Visa Недостаточно средств для активации @@ -114,7 +146,7 @@ Подтверждение личности в процессе Подключите вашу карту к децентрализованной платежной системе Для начала работы с картой вам необходимо завершить процесс подтверждения личности - Пожалуйста дождитесь завершения процесса подтверждения вашей личности. Как правило, это занимает меньше часа. Вы можете закрыть приложение и вернуться позже. + Пожалуйста дождитесь завершения процесса подтверждения личности. Вы будете уведомлены через e-mail. Обычно это занимает не более часа. Вы можете закрыть приложение и вернуться позже. Код доступа Установите Код доступа для вашей SaltPay карты Чат поддержки @@ -127,7 +159,6 @@ Приложите SaltPay карту Приложите Tangem карту Чат - Начало работы Запросить %s Для начала работы просто запросите начисление wxDai на свой кошелек Запросить @@ -139,27 +170,53 @@ Вы хотите выйти из процесса активации? В этом случае вам будет необходимо начать процесс заново. Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. + Реферальная программа + Приведи друга в Tangem + Вы + Получите + на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг + Ваш друг + Получит + %s скидку + при покупке карточки на сайте tangem.com + Ваши друзья купили + Ваш персональный код + Участвовать + условия участия + Нажимая на эту кнопку вы принимаете + Вы приняли + в реферальной программе Внутренняя ошибка: не удается найти менеджер кошельков - C возвращением! - Используйте биометрическую аутентификацию или код доступа для входа в приложение - Войти с биометрией - Сканировать карту - Новый функционал - Сохраните свой кошелек - Функция сохранения кошелька позволяет вам использовать свой кошелек с биометрической авторизацией, не прикладывая карту к телефону для получения доступа. - Разрешить привязку кошелька - Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта - Включите биометрическую авторизацию - Похоже, что у вас отключена биометрическая авторизация, она необходима для сохранения кошельков - Включить - Отмена - Мультивалютные - Одновалютные - Переименование кошелька - Имя кошелька - Разблокировать все с биометрией - Добавить новый кошелек - %d выбрано - Мои кошельки + Баланс: %s + Поделиться + Копировать + Успешно + Дать разрешение + Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Количество %s + Ваш кошелек + Отправитель + Подтвердить + Обмен %s на + Обмен + Недостаточно средств + Дать разрешение + Разрешить и обменять + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. + Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. + Персональный код скопирован! + Купи Tangem Wallet со скидкой!\n%s + Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. + Неверный адрес Этот кошелек уже был сохранен, вы можете добавить другой + Удалить + Privacy policy + Включите биометрическую аутентификацию + Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков + Включить + Функция сохранения кошелька позволяет вам использовать свой кошелек с биометрической аутентификацией, не прикладывая карту к телефону для получения доступа. + %d выбрано + биометрическую аутентификацию + биометрией diff --git a/core/ui/src/main/res/values-ru/strings_plurals_final.xml b/core/res/src/main/res/values-ru/strings_plurals_final.xml similarity index 64% rename from core/ui/src/main/res/values-ru/strings_plurals_final.xml rename to core/res/src/main/res/values-ru/strings_plurals_final.xml index 35486659e4..ae295e5b79 100644 --- a/core/ui/src/main/res/values-ru/strings_plurals_final.xml +++ b/core/res/src/main/res/values-ru/strings_plurals_final.xml @@ -12,4 +12,11 @@ %d токенов %d токенов + + + %d кошелек + %d кошелька + %d кошельков + %d кошельков + diff --git a/core/ui/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values/strings_final.xml similarity index 61% rename from core/ui/src/main/res/values-fr/strings_final.xml rename to core/res/src/main/res/values/strings_final.xml index ca07d2b5c7..8c80f1aeab 100644 --- a/core/ui/src/main/res/values-fr/strings_final.xml +++ b/core/res/src/main/res/values/strings_final.xml @@ -5,7 +5,7 @@ 2 cards Shipping Free - I have a promo code... + I have a promo code… Total Other payment methods Buy now @@ -35,38 +35,58 @@ This feature is disabled in Demo mode Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. Available networks + %s network not found. Please, add it first and try again. + Attention Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. Tokens in Solana network are not supported by this card due to firmware limitation. Contract address copied! - If you chose the wrong network during your crypto transfer from the exchange, this guide will help you recover your funds - Attention + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default The server is not available, please try again later Total balance The amount does not include some of your funds + Tokens Manage tokens No rate + Network is unreachable Hide token Hide %s Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. Unable to hide %s The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - %s network not found. Please, add it first and try again. - This network is not supported. Please select another network. - %s network Your wallet has not been backed up To protect your assets, we advise you to carry out this procedure - Remove token - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? + Retry + Chat + %s network + I understand Yes No - Chat - Tangem Bot + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. Card Settings Security Mode - Change access code + Change Access Code Access code will be changed on this card only + Continue + Tangem Bot Get your card ready! Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. Scan Card @@ -79,23 +99,34 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - Link More Cards - Privacy policy - Reset to factory settings This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet - Reset the card + Reset the Card Reset to Factory Settings Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card - Failed to establish WalletConnect session - Memo - Tag - Invalid Memo. It won\'t be added to the transaction. - Invalid Tag. It won\'t be added to the transaction. Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scan card + Save your wallet + Note that making a transaction with your funds will still require your card + Allow to use %s + New feature + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo Attention Tap the card with the visa logo No funds for activation @@ -128,7 +159,6 @@ Tap the SaltPay card Tap the Tangem card Support - Getting started Claim %s To get started, simply claim wxDAI to your wallet Claim @@ -136,8 +166,57 @@ Claiming It will take a few seconds Something went wrong - Please check you email for further instructions + Please check your email for further instructions Do you want to exit the activation process? In this case, you will need to start from the beginning. You have used a card from another wallet. Tap the card associated with this wallet - \ No newline at end of file + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Balance: %s + Share + Copy + Success + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Invalid address + This wallet has already been saved, you can add another one + Delete + Privacy policy + Enable biometric authorization + It looks like you have biometric authorization disabled, it is necessary to save wallets + Enable + Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. + %d selected + biometric authentication + biometrics + diff --git a/core/ui/src/main/res/values/strings_plurals_final.xml b/core/res/src/main/res/values/strings_plurals_final.xml similarity index 75% rename from core/ui/src/main/res/values/strings_plurals_final.xml rename to core/res/src/main/res/values/strings_plurals_final.xml index 9fa94fe676..72449e9325 100644 --- a/core/ui/src/main/res/values/strings_plurals_final.xml +++ b/core/res/src/main/res/values/strings_plurals_final.xml @@ -12,4 +12,9 @@ %d tokens %d tokens + + + %d wallets + %d wallet + diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 929960a637..2ab555ec1d 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -55,4 +55,6 @@ dependencies { /** Other libraries */ implementation(Library.accompanistSystemUiController) implementation(Library.materialComponent) + + implementation(project(":core:res")) } \ No newline at end of file diff --git a/core/ui/src/main/res/values-de/strings-app.xml b/core/ui/src/main/res/values-de/strings-app.xml new file mode 100644 index 0000000000..234b37a13d --- /dev/null +++ b/core/ui/src/main/res/values-de/strings-app.xml @@ -0,0 +1,6 @@ + + + Failed to establish WalletConnect session + Link More Cards + Abbrechen + \ No newline at end of file diff --git a/core/ui/src/main/res/values-fr/strings-app.xml b/core/ui/src/main/res/values-fr/strings-app.xml new file mode 100644 index 0000000000..e148949c0d --- /dev/null +++ b/core/ui/src/main/res/values-fr/strings-app.xml @@ -0,0 +1,6 @@ + + + Failed to establish WalletConnect session + Link More Cards + Annuler + \ No newline at end of file diff --git a/core/ui/src/main/res/values-it/strings-app.xml b/core/ui/src/main/res/values-it/strings-app.xml new file mode 100644 index 0000000000..fc516e10a2 --- /dev/null +++ b/core/ui/src/main/res/values-it/strings-app.xml @@ -0,0 +1,6 @@ + + + Failed to establish WalletConnect session + Link More Cards + Annulla + \ No newline at end of file diff --git a/core/ui/src/main/res/values-ru/string_common.xml b/core/ui/src/main/res/values-ru/string_common.xml deleted file mode 100644 index 7df281b0b0..0000000000 --- a/core/ui/src/main/res/values-ru/string_common.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - Поделиться - Копировать - условия участия - \ No newline at end of file diff --git a/core/ui/src/main/res/values-ru/strings-app.xml b/core/ui/src/main/res/values-ru/strings-app.xml new file mode 100644 index 0000000000..b085fe9843 --- /dev/null +++ b/core/ui/src/main/res/values-ru/strings-app.xml @@ -0,0 +1,6 @@ + + + Не удалось установить сессию WalletConnect + Добавить еще карты + Отмена + \ No newline at end of file diff --git a/core/ui/src/main/res/values/string_common.xml b/core/ui/src/main/res/values/string_common.xml deleted file mode 100644 index b7a15f7fb0..0000000000 --- a/core/ui/src/main/res/values/string_common.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - Share - Copy - terms and conditions - \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings-app.xml b/core/ui/src/main/res/values/strings-app.xml new file mode 100644 index 0000000000..73745abf84 --- /dev/null +++ b/core/ui/src/main/res/values/strings-app.xml @@ -0,0 +1,6 @@ + + + Failed to establish WalletConnect session + Link More Cards + Cancel + \ No newline at end of file diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index 19a4cc8c13..a94eecb86b 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -48,6 +48,7 @@ android { dependencies { /** Core modules */ + implementation(project(":core:res")) implementation(project(":core:utils")) implementation(project(":core:ui")) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index a1de2d981f..0027b80634 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -13,12 +13,14 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview @@ -30,6 +32,7 @@ import com.tangem.core.ui.res.TextColorType import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.presentation.R +@OptIn(ExperimentalComposeUiApi::class) @Composable internal fun ParticipateBottomBlock( purchasedWalletCount: Int, @@ -49,9 +52,10 @@ internal fun ParticipateBottomBlock( ) { SmallInfoCard( startText = stringResource(id = R.string.referral_friends_bought_title), - endText = String.format( - stringResource(id = R.string.referral_wallets_purchased_count), - purchasedWalletCount, + endText = pluralStringResource( + id = R.plurals.referral_wallets_purchased_count, + count = purchasedWalletCount, + purchasedWalletCount ), ) PersonalCodeCard(code = code) diff --git a/features/referral/presentation/src/main/res/values-ru/string.xml b/features/referral/presentation/src/main/res/values-ru/string.xml deleted file mode 100644 index 523d489aee..0000000000 --- a/features/referral/presentation/src/main/res/values-ru/string.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - Реферальная программа - Приведи друга в Tangem - Вы - Получите - на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг - Ваш друг - Получит - %s скидку - при покупке карточки на сайте tangem.com - Нажимая на эту кнопку вы принимаете - условия участия - в реферальной программе - Участвовать - Вы приняли - Ваш персональный код - %d кошельков - Ваши друзья купили - Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. - Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. - Персональный код скопирован! - Купи Tangem Wallet со скидкой!\n%s - \ No newline at end of file diff --git a/features/referral/presentation/src/main/res/values/strings.xml b/features/referral/presentation/src/main/res/values/strings.xml deleted file mode 100644 index dfac4843e0..0000000000 --- a/features/referral/presentation/src/main/res/values/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - By tapping this button you accept - terms and conditions - of the referral program - Participate - You\'ve accepted - Your personal code - %d wallets - Your friends bought - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 76949019de..013139ff41 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,7 @@ include(":common") // region Core modules include(":core:datasource") +include(":core:res") include(":core:utils") include(":core:ui") // endregion Core modules From 6fb8e4e2ea16dccacb409a29cc1a32eb8585a722 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Dec 2022 10:28:08 +0300 Subject: [PATCH 114/176] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../tap/domain/twins/TwinCardsManager.kt | 2 +- .../repository/di/RepositoryProvider.kt | 2 +- .../DefaultWalletAmountsRepository.kt | 2 +- .../saltPay/SaltPayActivationManager.kt | 10 ++--- .../redux/OnboardingSaltPayMiddleware.kt | 38 +++++++++--------- .../datasource/api/common/Interceptors.kt | 14 +++---- .../datasource/api/common/NetworError.kt | 19 --------- .../addCustomToken/redux/AddCustomTokenHub.kt | 6 +-- .../redux/AddCustomTokenState.kt | 2 +- .../results.bin | 1 - .../results.bin | 1 - .../transformed/jetified-network.jar | Bin 99352 -> 0 bytes .../results.bin | 1 - .../results.bin | 1 - .../transformed/jetified-network/classes.dex | Bin 93256 -> 0 bytes .../results.bin | 1 - .../transformed/jetified-network/classes.dex | Bin 93256 -> 0 bytes .../results.bin | 1 - .../transformed/jetified-network.jar | Bin 99352 -> 0 bytes .../results.bin | 1 - .../transformed/jetified-network.jar | Bin 99352 -> 0 bytes .../results.bin | 1 - .../results.bin | 0 .../results.bin | 1 - .../transformed/jetified-network.jar | Bin 99352 -> 0 bytes .../results.bin | 0 .../results.bin | 1 - .../results.bin | 1 - .../results.bin | 1 - .../results.bin | 0 .../results.bin | 0 .../main/META-INF/network.kotlin_module | Bin 117 -> 0 bytes .../paymentology/AttestationResponse.class | Bin 4843 -> 0 bytes .../CheckRegistrationRequests$Item.class | Bin 3403 -> 0 bytes .../CheckRegistrationRequests.class | Bin 3152 -> 0 bytes .../network/api/paymentology/KYCStatus.class | Bin 1696 -> 0 bytes .../PaymentologyApi$Companion.class | Bin 1101 -> 0 bytes .../api/paymentology/PaymentologyApi.class | Bin 3372 -> 0 bytes .../PaymentologyApiService$Companion.class | Bin 1203 -> 0 bytes ...logyApiService$checkRegistration$2$1.class | Bin 3995 -> 0 bytes ...tologyApiService$checkRegistration$2.class | Bin 5147 -> 0 bytes ...ymentologyApiService$registerKYC$2$1.class | Bin 3937 -> 0 bytes ...PaymentologyApiService$registerKYC$2.class | Bin 4405 -> 0 bytes ...ntologyApiService$registerWallet$2$1.class | Bin 3962 -> 0 bytes ...mentologyApiService$registerWallet$2.class | Bin 4435 -> 0 bytes ...vice$requestAttestationChallenge$2$1.class | Bin 4076 -> 0 bytes ...ervice$requestAttestationChallenge$2.class | Bin 4907 -> 0 bytes .../paymentology/PaymentologyApiService.class | Bin 6073 -> 0 bytes .../api/paymentology/RegisterKYCRequest.class | Bin 4418 -> 0 bytes .../paymentology/RegisterWalletRequest.class | Bin 6179 -> 0 bytes .../paymentology/RegisterWalletResponse.class | Bin 3758 -> 0 bytes .../RegistrationResponse$Item.class | Bin 8078 -> 0 bytes .../paymentology/RegistrationResponse.class | Bin 5304 -> 0 bytes .../ResponseError$DefaultImpls.class | Bin 1010 -> 0 bytes .../api/paymentology/ResponseError.class | Bin 958 -> 0 bytes .../api/paymentology/ResponsesKt.class | Bin 1575 -> 0 bytes .../CoinsResponse$Coin$Network.class | Bin 4054 -> 0 bytes .../api/tangemTech/CoinsResponse$Coin.class | Bin 5416 -> 0 bytes .../api/tangemTech/CoinsResponse.class | Bin 4077 -> 0 bytes .../CurrenciesResponse$Currency.class | Bin 4815 -> 0 bytes .../api/tangemTech/CurrenciesResponse.class | Bin 3064 -> 0 bytes .../network/api/tangemTech/GeoResponse.class | Bin 2418 -> 0 bytes .../network/api/tangemTech/HttpResponse.class | Bin 379 -> 0 bytes .../api/tangemTech/RatesResponse.class | Bin 2863 -> 0 bytes .../TangemTechApi$DefaultImpls.class | Bin 1217 -> 0 bytes .../api/tangemTech/TangemTechApi.class | Bin 3660 -> 0 bytes .../api/tangemTech/TangemTechError.class | Bin 2765 -> 0 bytes .../api/tangemTech/TangemTechResponse.class | Bin 1011 -> 0 bytes .../TangemTechService$Companion.class | Bin 947 -> 0 bytes .../TangemTechService$coins$2$1.class | Bin 4259 -> 0 bytes .../TangemTechService$coins$2.class | Bin 4714 -> 0 bytes .../TangemTechService$currencies$2$1.class | Bin 3496 -> 0 bytes .../TangemTechService$currencies$2.class | Bin 4034 -> 0 bytes .../TangemTechService$getUserTokens$2$1.class | Bin 3695 -> 0 bytes .../TangemTechService$getUserTokens$2.class | Bin 4178 -> 0 bytes .../TangemTechService$putUserTokens$2$1.class | Bin 3838 -> 0 bytes .../TangemTechService$putUserTokens$2.class | Bin 4317 -> 0 bytes .../TangemTechService$rates$2$1.class | Bin 4501 -> 0 bytes .../TangemTechService$rates$2.class | Bin 4295 -> 0 bytes .../TangemTechService$userCountry$2$1.class | Bin 3473 -> 0 bytes .../TangemTechService$userCountry$2.class | Bin 4019 -> 0 bytes .../api/tangemTech/TangemTechService.class | Bin 9271 -> 0 bytes .../api/tangemTech/TokenResponse.class | Bin 5535 -> 0 bytes .../api/tangemTech/UserTokensResponse.class | Bin 4877 -> 0 bytes .../network/common/AddHeaderInterceptor.class | Bin 2923 -> 0 bytes .../common/CacheControlHttpInterceptor.class | Bin 1114 -> 0 bytes .../common/CardPublicKeyHttpInterceptor.class | Bin 1158 -> 0 bytes .../network/common/MoshiConverter.class | Bin 3421 -> 0 bytes .../common/NetworkError$Companion.class | Bin 814 -> 0 bytes .../tangem/network/common/NetworkError.class | Bin 2084 -> 0 bytes .../network/common/NetworkModuleMessage.class | Bin 521 -> 0 bytes .../tangem/network/common/RetrofitKt.class | Bin 4722 -> 0 bytes .../kotlin/compileKotlin/build-history.bin | Bin 31 -> 0 bytes .../caches-jvm/inputs/source-to-output.tab | Bin 4096 -> 0 bytes .../inputs/source-to-output.tab.keystream | Bin 4096 -> 0 bytes .../inputs/source-to-output.tab.keystream.len | Bin 8 -> 0 bytes .../inputs/source-to-output.tab.len | Bin 8 -> 0 bytes .../inputs/source-to-output.tab.values.at | Bin 7756 -> 0 bytes .../caches-jvm/inputs/source-to-output.tab_i | Bin 32768 -> 0 bytes .../inputs/source-to-output.tab_i.len | Bin 8 -> 0 bytes .../jvm/kotlin/class-attributes.tab | Bin 4096 -> 0 bytes .../jvm/kotlin/class-attributes.tab.keystream | Bin 4096 -> 0 bytes .../kotlin/class-attributes.tab.keystream.len | Bin 8 -> 0 bytes .../jvm/kotlin/class-attributes.tab.len | Bin 8 -> 0 bytes .../jvm/kotlin/class-attributes.tab.values.at | Bin 157 -> 0 bytes .../jvm/kotlin/class-attributes.tab_i | Bin 32768 -> 0 bytes .../jvm/kotlin/class-attributes.tab_i.len | Bin 8 -> 0 bytes .../jvm/kotlin/class-fq-name-to-source.tab | Bin 4096 -> 0 bytes .../class-fq-name-to-source.tab.keystream | Bin 4096 -> 0 bytes .../class-fq-name-to-source.tab.keystream.len | Bin 8 -> 0 bytes .../kotlin/class-fq-name-to-source.tab.len | Bin 8 -> 0 bytes .../class-fq-name-to-source.tab.values.at | Bin 3155 -> 0 bytes .../jvm/kotlin/class-fq-name-to-source.tab_i | Bin 32768 -> 0 bytes .../kotlin/class-fq-name-to-source.tab_i.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/constants.tab | Bin 4096 -> 0 bytes .../jvm/kotlin/constants.tab.keystream | Bin 4096 -> 0 bytes .../jvm/kotlin/constants.tab.keystream.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/constants.tab.len | Bin 8 -> 0 bytes .../jvm/kotlin/constants.tab.values.at | Bin 171 -> 0 bytes .../caches-jvm/jvm/kotlin/constants.tab_i | Bin 32768 -> 0 bytes .../caches-jvm/jvm/kotlin/constants.tab_i.len | Bin 8 -> 0 bytes .../jvm/kotlin/inline-functions.tab | Bin 4096 -> 0 bytes .../jvm/kotlin/inline-functions.tab.keystream | Bin 4096 -> 0 bytes .../kotlin/inline-functions.tab.keystream.len | Bin 8 -> 0 bytes .../jvm/kotlin/inline-functions.tab.len | Bin 8 -> 0 bytes .../jvm/kotlin/inline-functions.tab.values.at | Bin 274 -> 0 bytes .../jvm/kotlin/inline-functions.tab_i | Bin 32768 -> 0 bytes .../jvm/kotlin/inline-functions.tab_i.len | Bin 8 -> 0 bytes .../jvm/kotlin/internal-name-to-source.tab | Bin 4096 -> 0 bytes .../internal-name-to-source.tab.keystream | Bin 4096 -> 0 bytes .../internal-name-to-source.tab.keystream.len | Bin 8 -> 0 bytes .../kotlin/internal-name-to-source.tab.len | Bin 8 -> 0 bytes .../internal-name-to-source.tab.values.at | Bin 5410 -> 0 bytes .../jvm/kotlin/internal-name-to-source.tab_i | Bin 32768 -> 0 bytes .../kotlin/internal-name-to-source.tab_i.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/package-parts.tab | Bin 4096 -> 0 bytes .../jvm/kotlin/package-parts.tab.keystream | Bin 4096 -> 0 bytes .../kotlin/package-parts.tab.keystream.len | Bin 8 -> 0 bytes .../jvm/kotlin/package-parts.tab.len | Bin 8 -> 0 bytes .../jvm/kotlin/package-parts.tab.values.at | Bin 55 -> 0 bytes .../caches-jvm/jvm/kotlin/package-parts.tab_i | Bin 32768 -> 0 bytes .../jvm/kotlin/package-parts.tab_i.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/proto.tab | Bin 4096 -> 0 bytes .../caches-jvm/jvm/kotlin/proto.tab.keystream | Bin 4096 -> 0 bytes .../jvm/kotlin/proto.tab.keystream.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/proto.tab.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/proto.tab.values.at | Bin 24785 -> 0 bytes .../caches-jvm/jvm/kotlin/proto.tab_i | Bin 32768 -> 0 bytes .../caches-jvm/jvm/kotlin/proto.tab_i.len | Bin 8 -> 0 bytes .../jvm/kotlin/source-to-classes.tab | Bin 4096 -> 0 bytes .../kotlin/source-to-classes.tab.keystream | Bin 4096 -> 0 bytes .../source-to-classes.tab.keystream.len | Bin 8 -> 0 bytes .../jvm/kotlin/source-to-classes.tab.len | Bin 8 -> 0 bytes .../kotlin/source-to-classes.tab.values.at | Bin 3808 -> 0 bytes .../jvm/kotlin/source-to-classes.tab_i | Bin 32768 -> 0 bytes .../jvm/kotlin/source-to-classes.tab_i.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/subtypes.tab | Bin 4096 -> 0 bytes .../jvm/kotlin/subtypes.tab.keystream | Bin 4096 -> 0 bytes .../jvm/kotlin/subtypes.tab.keystream.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/subtypes.tab.len | Bin 8 -> 0 bytes .../jvm/kotlin/subtypes.tab.values.at | Bin 1039 -> 0 bytes .../caches-jvm/jvm/kotlin/subtypes.tab_i | Bin 32768 -> 0 bytes .../caches-jvm/jvm/kotlin/subtypes.tab_i.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/supertypes.tab | Bin 4096 -> 0 bytes .../jvm/kotlin/supertypes.tab.keystream | Bin 4096 -> 0 bytes .../jvm/kotlin/supertypes.tab.keystream.len | Bin 8 -> 0 bytes .../caches-jvm/jvm/kotlin/supertypes.tab.len | Bin 8 -> 0 bytes .../jvm/kotlin/supertypes.tab.values.at | Bin 955 -> 0 bytes .../caches-jvm/jvm/kotlin/supertypes.tab_i | Bin 32768 -> 0 bytes .../jvm/kotlin/supertypes.tab_i.len | Bin 8 -> 0 bytes .../caches-jvm/lookups/counters.tab | 2 - .../caches-jvm/lookups/file-to-id.tab | Bin 4096 -> 0 bytes .../lookups/file-to-id.tab.keystream | Bin 4096 -> 0 bytes .../lookups/file-to-id.tab.keystream.len | Bin 8 -> 0 bytes .../caches-jvm/lookups/file-to-id.tab.len | Bin 8 -> 0 bytes .../lookups/file-to-id.tab.values.at | Bin 115 -> 0 bytes .../caches-jvm/lookups/file-to-id.tab_i | Bin 32768 -> 0 bytes .../caches-jvm/lookups/file-to-id.tab_i.len | Bin 8 -> 0 bytes .../caches-jvm/lookups/id-to-file.tab | Bin 4096 -> 0 bytes .../lookups/id-to-file.tab.keystream | Bin 4096 -> 0 bytes .../lookups/id-to-file.tab.keystream.len | Bin 8 -> 0 bytes .../caches-jvm/lookups/id-to-file.tab.len | Bin 8 -> 0 bytes .../lookups/id-to-file.tab.values.at | Bin 999 -> 0 bytes .../caches-jvm/lookups/id-to-file.tab_i | Bin 32768 -> 0 bytes .../caches-jvm/lookups/id-to-file.tab_i.len | Bin 8 -> 0 bytes .../caches-jvm/lookups/lookups.tab | Bin 16384 -> 0 bytes .../caches-jvm/lookups/lookups.tab.keystream | Bin 12288 -> 0 bytes .../lookups/lookups.tab.keystream.len | Bin 8 -> 0 bytes .../caches-jvm/lookups/lookups.tab.len | Bin 8 -> 0 bytes .../caches-jvm/lookups/lookups.tab.values.at | Bin 7475 -> 0 bytes .../caches-jvm/lookups/lookups.tab_i | Bin 32768 -> 0 bytes .../caches-jvm/lookups/lookups.tab_i.len | Bin 8 -> 0 bytes .../build/kotlin/compileKotlin/last-build.bin | Bin 18 -> 0 bytes network/build/kotlin/networkjar-classes.txt | 1 - network/build/libs/network.jar | Bin 99352 -> 0 bytes network/build/tmp/jar/MANIFEST.MF | 2 - 197 files changed, 39 insertions(+), 75 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/common/NetworError.kt delete mode 100644 network/build/.transforms/0a0a9b6fcdf800647341aabb7296a58d/results.bin delete mode 100644 network/build/.transforms/10b56b612fced146421785dc9c798db5/results.bin delete mode 100644 network/build/.transforms/10b56b612fced146421785dc9c798db5/transformed/jetified-network.jar delete mode 100644 network/build/.transforms/129172ef9b6bb005a6da315416650247/results.bin delete mode 100644 network/build/.transforms/5502ea290d619da65e391069e65e2fa9/results.bin delete mode 100644 network/build/.transforms/5502ea290d619da65e391069e65e2fa9/transformed/jetified-network/classes.dex delete mode 100644 network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/results.bin delete mode 100644 network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/transformed/jetified-network/classes.dex delete mode 100644 network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/results.bin delete mode 100644 network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/transformed/jetified-network.jar delete mode 100644 network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/results.bin delete mode 100644 network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/transformed/jetified-network.jar delete mode 100644 network/build/.transforms/882f21e6ea520a732fa9828baf6837a4/results.bin delete mode 100644 network/build/.transforms/99b0c959c827d9869a7277c0ca875111/results.bin delete mode 100644 network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/results.bin delete mode 100644 network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/transformed/jetified-network.jar delete mode 100644 network/build/.transforms/b04ba1e3b1e5614e897560d529aff7a1/results.bin delete mode 100644 network/build/.transforms/b37124a83829aa553636076c9ead584a/results.bin delete mode 100644 network/build/.transforms/c4301af1e3ac6220228773d71e26c811/results.bin delete mode 100644 network/build/.transforms/dda44431310664cbf70b6a6d8e9af52f/results.bin delete mode 100644 network/build/.transforms/e7ae8b25da52bba94522842530316ba6/results.bin delete mode 100644 network/build/.transforms/f73a389fe3a1ec5bb1563a53989b4562/results.bin delete mode 100644 network/build/classes/kotlin/main/META-INF/network.kotlin_module delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/AttestationResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests$Item.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/KYCStatus.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApi$Companion.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApi.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$Companion.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$requestAttestationChallenge$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$requestAttestationChallenge$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterKYCRequest.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterWalletRequest.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterWalletResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse$Item.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError$DefaultImpls.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponsesKt.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin$Network.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse$Currency.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/GeoResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/HttpResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/RatesResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi$DefaultImpls.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechError.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$Companion.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$userCountry$2$1.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$userCountry$2.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TokenResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/UserTokensResponse.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/AddHeaderInterceptor.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/CacheControlHttpInterceptor.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/CardPublicKeyHttpInterceptor.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/MoshiConverter.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/NetworkError$Companion.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/NetworkError.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/NetworkModuleMessage.class delete mode 100644 network/build/classes/kotlin/main/com/tangem/network/common/RetrofitKt.class delete mode 100644 network/build/kotlin/compileKotlin/build-history.bin delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.len delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.values.at delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i delete mode 100644 network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len delete mode 100644 network/build/kotlin/compileKotlin/last-build.bin delete mode 100644 network/build/kotlin/networkjar-classes.txt delete mode 100644 network/build/libs/network.jar delete mode 100644 network/build/tmp/jar/MANIFEST.MF diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a1658496e7..897f0f7c40 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6 +Subproject commit 897f0f7c402fd53c5ee1fa4ab601ecd4e43ff389 diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index ed727b68e6..7917ddaa07 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -85,7 +85,7 @@ class TwinCardsManager( } private fun getAdapter(): JsonAdapter> { - return com.tangem.datasource.api.common.MoshiConverter.defaultMoshi().adapter( + return MoshiConverter.defaultMoshi().adapter( Types.newParameterizedType(List::class.java, Issuer::class.java), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt index 315606c659..7fc7ba38e7 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/di/RepositoryProvider.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.walletStores.repository.di import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.network.api.tangemTech.TangemTechService +import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index df28ac9d4c..fd8a037dba 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -14,9 +14,9 @@ import com.tangem.common.flatMap import com.tangem.common.flatMapOnFailure import com.tangem.common.map import com.tangem.common.services.Result +import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId -import com.tangem.network.api.tangemTech.TangemTechService import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.replaceByOrAdd import com.tangem.tap.domain.model.UserWallet diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt index ade3f81cde..187425c2af 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt @@ -79,7 +79,7 @@ class SaltPayActivationManager( } } - suspend fun requestAttestationChallenge(): Result { + suspend fun requestAttestationChallenge(): Result { return paymentologyService.requestAttestationChallenge(cardId, cardPublicKey) .successOr { return it } .tryExtractError() @@ -94,8 +94,8 @@ class SaltPayActivationManager( suspend fun registerWallet( attestResponse: AttestWalletKeyResponse, pinCode: String, - ): Result { - val request = com.tangem.datasource.api.paymentology.RegisterWalletRequest( + ): Result { + val request = RegisterWalletRequest( cardId = cardId, publicKey = cardPublicKey, walletPublicKey = walletPublicKey, @@ -152,8 +152,8 @@ class SaltPayActivationManager( ) } - private fun makeRegisterKYCRequest(): com.tangem.datasource.api.paymentology.RegisterKYCRequest = - com.tangem.datasource.api.paymentology.RegisterKYCRequest( + private fun makeRegisterKYCRequest(): RegisterKYCRequest = + RegisterKYCRequest( cardId = cardId, publicKey = cardPublicKey, kycProvider = "UTORG", diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index 54929f3918..4d6c4a3c00 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -295,7 +295,7 @@ suspend fun SaltPayActivationManager.update( private fun determineStep( currentStep: SaltPayActivationStep, amountToClaim: Amount?, - response: com.tangem.datasource.api.paymentology.RegistrationResponse.Item, + response: RegistrationResponse.Item, ): SaltPayActivationStep { fun getStepBaseOnAmountToClaim(amount: Amount?): SaltPayActivationStep = when (amount) { null -> SaltPayActivationStep.Success @@ -315,25 +315,25 @@ private fun determineStep( response.kycStatus != null -> { when (currentStep) { SaltPayActivationStep.KycWaiting -> when (response.kycStatus) { - com.tangem.datasource.api.paymentology.KYCStatus.NOT_STARTED, com.tangem.datasource.api.paymentology.KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - com.tangem.datasource.api.paymentology.KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - com.tangem.datasource.api.paymentology.KYCStatus.CORRECTION_REQUESTED, com.tangem.datasource.api.paymentology.KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - com.tangem.datasource.api.paymentology.KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) + KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro + KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting + KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject + KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) else -> SaltPayActivationStep.KycIntro } SaltPayActivationStep.KycReject -> when (response.kycStatus) { - com.tangem.datasource.api.paymentology.KYCStatus.NOT_STARTED, com.tangem.datasource.api.paymentology.KYCStatus.STARTED -> SaltPayActivationStep.KycStart - com.tangem.datasource.api.paymentology.KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - com.tangem.datasource.api.paymentology.KYCStatus.CORRECTION_REQUESTED -> SaltPayActivationStep.KycStart - com.tangem.datasource.api.paymentology.KYCStatus.REJECTED -> SaltPayActivationStep.KycStart - com.tangem.datasource.api.paymentology.KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) + KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycStart + KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting + KYCStatus.CORRECTION_REQUESTED -> SaltPayActivationStep.KycStart + KYCStatus.REJECTED -> SaltPayActivationStep.KycStart + KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) else -> SaltPayActivationStep.KycIntro } else -> when (response.kycStatus) { - com.tangem.datasource.api.paymentology.KYCStatus.NOT_STARTED, com.tangem.datasource.api.paymentology.KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - com.tangem.datasource.api.paymentology.KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - com.tangem.datasource.api.paymentology.KYCStatus.CORRECTION_REQUESTED, com.tangem.datasource.api.paymentology.KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - com.tangem.datasource.api.paymentology.KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) + KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro + KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting + KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject + KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) else -> SaltPayActivationStep.KycIntro } } @@ -413,7 +413,7 @@ private suspend fun checkGasIfNeeded( } @Throws(SaltPayActivationError::class) -fun com.tangem.datasource.api.paymentology.RegistrationResponse.Item.toSaltPayStep(currentStep: SaltPayActivationStep): SaltPayActivationStep { +fun RegistrationResponse.Item.toSaltPayStep(currentStep: SaltPayActivationStep): SaltPayActivationStep { return when { passed != true -> throw SaltPayActivationError.CardNotPassed(this.error) disabledByAdmin == true -> throw SaltPayActivationError.CardDisabled(this.error) @@ -426,10 +426,10 @@ fun com.tangem.datasource.api.paymentology.RegistrationResponse.Item.toSaltPaySt kycStatus != null -> { when (kycStatus) { - com.tangem.datasource.api.paymentology.KYCStatus.NOT_STARTED, com.tangem.datasource.api.paymentology.KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - com.tangem.datasource.api.paymentology.KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - com.tangem.datasource.api.paymentology.KYCStatus.CORRECTION_REQUESTED, com.tangem.datasource.api.paymentology.KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - com.tangem.datasource.api.paymentology.KYCStatus.APPROVED -> SaltPayActivationStep.Claim + KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro + KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting + KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject + KYCStatus.APPROVED -> SaltPayActivationStep.Claim null -> throw UnsupportedOperationException() } } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt index b8d3a7cc2a..17b348b924 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/Interceptors.kt @@ -7,7 +7,7 @@ import okhttp3.Response [REDACTED_AUTHOR] */ open class AddHeaderInterceptor( - private val headers: Map + private val headers: Map, ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { @@ -21,10 +21,10 @@ open class AddHeaderInterceptor( } } -class CacheControlHttpInterceptor(maxAgeSeconds: Int) : AddHeaderInterceptor(mapOf( - "Cache-Control" to "max-age=$maxAgeSeconds", -)) +class CacheControlHttpInterceptor(maxAgeSeconds: Int) : AddHeaderInterceptor( + mapOf("Cache-Control" to "max-age=$maxAgeSeconds"), +) -class CardPublicKeyHttpInterceptor(cardPublicKeyHex: String) : AddHeaderInterceptor(mapOf( - "card_public_key" to cardPublicKeyHex, -)) \ No newline at end of file +class CardPublicKeyHttpInterceptor(cardPublicKeyHex: String) : AddHeaderInterceptor( + mapOf("card_public_key" to cardPublicKeyHex), +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/NetworError.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/NetworError.kt deleted file mode 100644 index 8f4a7ae335..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/NetworError.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.datasource.api.common - -/** -[REDACTED_AUTHOR] - */ -// sealed interface NetworkModuleMessage : ModuleMessage -// -// sealed class NetworkError( -// subCode: Int, -// override val message: String, -// override val data: Any?, -// ) : NetworkModuleMessage, ModuleError() { -// override val code: Int = ModuleErrorCode.NETWORK + subCode -// -// companion object { -// // base code used for all error in the module -// // const val CODE_ANY_OTHER = 100..199, 200..299 -// } -// } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index f399fc4a34..3a48aa34a7 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -342,7 +342,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT private suspend fun requestInfoAboutToken( contractAddress: String, - ): List { + ): List { val tangemTechServiceManager = requireNotNull(hubState.tangemTechServiceManager) dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true)))) @@ -432,8 +432,8 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT ) private suspend fun fillTokenFields( - token: com.tangem.datasource.api.tangemTech.CoinsResponse.Coin, - coinNetwork: com.tangem.datasource.api.tangemTech.CoinsResponse.Coin.Network, + token: CoinsResponse.Coin, + coinNetwork: CoinsResponse.Coin.Network, ) { val blockchain = Blockchain.fromNetworkId(coinNetwork.networkId) ?: Blockchain.Unknown Network.setFieldValue(Field.Data(blockchain, false)) diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt index 1dfa5ef063..978232de57 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt @@ -48,7 +48,7 @@ data class AddCustomTokenState( val form: Form = Form(listOf()), val formValidators: Map> = createFormValidators(), val formErrors: Map = emptyMap(), - val foundToken: com.tangem.datasource.api.tangemTech.CoinsResponse.Coin? = null, + val foundToken: CoinsResponse.Coin? = null, val warnings: Set = emptySet(), val screenState: ScreenState = createInitialScreenState(), val tangemTechServiceManager: AddCustomTokenService? = null, diff --git a/network/build/.transforms/0a0a9b6fcdf800647341aabb7296a58d/results.bin b/network/build/.transforms/0a0a9b6fcdf800647341aabb7296a58d/results.bin deleted file mode 100644 index 1ed65e03c3..0000000000 --- a/network/build/.transforms/0a0a9b6fcdf800647341aabb7296a58d/results.bin +++ /dev/null @@ -1 +0,0 @@ -i/ diff --git a/network/build/.transforms/10b56b612fced146421785dc9c798db5/results.bin b/network/build/.transforms/10b56b612fced146421785dc9c798db5/results.bin deleted file mode 100644 index 82b3009a77..0000000000 --- a/network/build/.transforms/10b56b612fced146421785dc9c798db5/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/jetified-network.jar diff --git a/network/build/.transforms/10b56b612fced146421785dc9c798db5/transformed/jetified-network.jar b/network/build/.transforms/10b56b612fced146421785dc9c798db5/transformed/jetified-network.jar deleted file mode 100644 index af1c4b140b481f82156439504e50812fd74b6455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99352 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 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b 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 bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` diff --git a/network/build/.transforms/129172ef9b6bb005a6da315416650247/results.bin b/network/build/.transforms/129172ef9b6bb005a6da315416650247/results.bin deleted file mode 100644 index 1ed65e03c3..0000000000 --- a/network/build/.transforms/129172ef9b6bb005a6da315416650247/results.bin +++ /dev/null @@ -1 +0,0 @@ -i/ diff --git a/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/results.bin b/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/results.bin deleted file mode 100644 index 6167517f86..0000000000 --- a/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/jetified-network diff --git a/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/transformed/jetified-network/classes.dex b/network/build/.transforms/5502ea290d619da65e391069e65e2fa9/transformed/jetified-network/classes.dex deleted file mode 100644 index d165fde1a59d4b77589cc392189c636bbe6eec16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93256 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�?bHmINuxbaus@V6nVnM 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 diff --git a/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/results.bin b/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/results.bin deleted file mode 100644 index 6167517f86..0000000000 --- a/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/jetified-network diff --git a/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/transformed/jetified-network/classes.dex b/network/build/.transforms/616278befc6f71b7ce9b02b5deafc7e4/transformed/jetified-network/classes.dex deleted file mode 100644 index d165fde1a59d4b77589cc392189c636bbe6eec16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93256 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�?bHmINuxbaus@V6nVnM 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 diff --git a/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/results.bin b/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/results.bin deleted file mode 100644 index 82b3009a77..0000000000 --- a/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/jetified-network.jar diff --git a/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/transformed/jetified-network.jar b/network/build/.transforms/631b83fd2688ae40c00f4c5be3549138/transformed/jetified-network.jar deleted file mode 100644 index af1c4b140b481f82156439504e50812fd74b6455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99352 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 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b 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 bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` diff --git a/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/results.bin b/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/results.bin deleted file mode 100644 index 82b3009a77..0000000000 --- a/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/jetified-network.jar diff --git a/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/transformed/jetified-network.jar b/network/build/.transforms/6a76cf0cfeadcf7e00cd3bdb30b05405/transformed/jetified-network.jar deleted file mode 100644 index af1c4b140b481f82156439504e50812fd74b6455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99352 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 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b 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 bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` diff --git a/network/build/.transforms/882f21e6ea520a732fa9828baf6837a4/results.bin b/network/build/.transforms/882f21e6ea520a732fa9828baf6837a4/results.bin deleted file mode 100644 index 1ed65e03c3..0000000000 --- a/network/build/.transforms/882f21e6ea520a732fa9828baf6837a4/results.bin +++ /dev/null @@ -1 +0,0 @@ -i/ diff --git a/network/build/.transforms/99b0c959c827d9869a7277c0ca875111/results.bin b/network/build/.transforms/99b0c959c827d9869a7277c0ca875111/results.bin deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/results.bin b/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/results.bin deleted file mode 100644 index 82b3009a77..0000000000 --- a/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/jetified-network.jar diff --git a/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/transformed/jetified-network.jar b/network/build/.transforms/9e88dd083bb632e3aaf29dc1b80c8bc4/transformed/jetified-network.jar deleted file mode 100644 index af1c4b140b481f82156439504e50812fd74b6455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99352 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 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b 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 bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` diff --git a/network/build/.transforms/b04ba1e3b1e5614e897560d529aff7a1/results.bin b/network/build/.transforms/b04ba1e3b1e5614e897560d529aff7a1/results.bin deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/network/build/.transforms/b37124a83829aa553636076c9ead584a/results.bin b/network/build/.transforms/b37124a83829aa553636076c9ead584a/results.bin deleted file mode 100644 index 1ed65e03c3..0000000000 --- a/network/build/.transforms/b37124a83829aa553636076c9ead584a/results.bin +++ /dev/null @@ -1 +0,0 @@ -i/ diff --git a/network/build/.transforms/c4301af1e3ac6220228773d71e26c811/results.bin b/network/build/.transforms/c4301af1e3ac6220228773d71e26c811/results.bin deleted file mode 100644 index 1ed65e03c3..0000000000 --- a/network/build/.transforms/c4301af1e3ac6220228773d71e26c811/results.bin +++ /dev/null @@ -1 +0,0 @@ -i/ diff --git a/network/build/.transforms/dda44431310664cbf70b6a6d8e9af52f/results.bin b/network/build/.transforms/dda44431310664cbf70b6a6d8e9af52f/results.bin deleted file mode 100644 index 1ed65e03c3..0000000000 --- a/network/build/.transforms/dda44431310664cbf70b6a6d8e9af52f/results.bin +++ /dev/null @@ -1 +0,0 @@ -i/ diff --git a/network/build/.transforms/e7ae8b25da52bba94522842530316ba6/results.bin b/network/build/.transforms/e7ae8b25da52bba94522842530316ba6/results.bin deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/network/build/.transforms/f73a389fe3a1ec5bb1563a53989b4562/results.bin b/network/build/.transforms/f73a389fe3a1ec5bb1563a53989b4562/results.bin deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/network/build/classes/kotlin/main/META-INF/network.kotlin_module b/network/build/classes/kotlin/main/META-INF/network.kotlin_module deleted file mode 100644 index f8f85d02efee0ee47ab1d5218f5dd57d13cc490e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117 zcmZQzU|?ooU|%ypsH! v{Pap8?x57-g8aPV)MD=vE_E(REP8-Kx%qiQTtTTNMfqu&CEg`U3|b5TgOwqZ diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/AttestationResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/AttestationResponse.class deleted file mode 100644 index 793362e93dd59265ca9785bfc8ede3d13da2be1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4843 zcmbtX`%@d&75=Us?6OE^5e5S$HnE8fl0i0cnq;|~JQrOa&H){JF68#i?ORWrX9*K+B2PTR=phHYld<&F4|ZR?h;*=f_5 z)~%drSh@rupss4`T0BGC@u|gCJ!MPq3!K`E$fBGJ`MjBz5EQ68lQz=!Ie}QuLSlZr zBznfqr;X*oGD_00^<_Oj*gGrGJ#OZgIbWOG5lZ8x1Tg>Q#0{))f z*$^Zs3Ib>ls86kEnT$?;3-}fiA%wB79zHY*NLC@0(k)9s%!kl~2-BMdf;!tO5Gk|7 zvn_-cwDPe7B-uGOY%Y=H=CY$jqeEy%2cJDC5U^L$r04nZy@aer7Ra&CvThHTdJHmYy;xE&v9L znuaqPN3gj_-p%+n3Lrmb%nrUa!MtpTW8&6YK^M;m*lPb!%l}=d_`p9rCuU}x5 z0>^7;tYt$8ahz1pk5i#esZ{#QOQpX?l4N|UDpp>^T|_>oP$hn*SWE>worw?S^V$YE z{{n%LieVwqOF%r1GYSTAHiV~fL?QgV7{XC>^K2-DXK;*X!vs^)UeT*y>Lo{@u@np^ z84@m0fLAnY#mzZEB}pPkX>T%wBrdbcae<~1U&1sqx<+xEB>eWwyXUSbn8Ih7V~jI9 zBXFb)(lpJQMqIyX>y%b%AMqh$<1%%Nmjv2VS|(Msz&=5xu5lYU`lL=`+6*45 zw{vBFePY9`=x*I3u0P3aE<;I830&&gQ^8kA8LUyVA!yJQEa7FUc(RK+Gxco3d}%RI zz96KRc1~0JQf6*LAhq{~yC=JA)BqK|1epktU*5Sbw>{h(@SPNiSVUHR9f3A+(jTc)10BATL88<-6p3cCGppZuzs{#B zV-4zl_w4A$xBX-FY73ut<_&7A%j2w6^?=LAD~DGdJEOMIX;Q1mzM{$jndXjChw7U)NuwX%5!1~@4a6%`3_1dG zgBqG{EqZo+=T4xs6;}66w-sIWwiF6i6R|a_>hyls#!9A$+Pw2o-C<4yHK%9yRECwE zP;gQuulW*|in~_-8eJL(rwfLi&gw~HJ#D4w^*2;{5mL)@@43eDw4qNHvWt5DD!&l< zRcF#oG^^#)y!MFQ)v~T=c`d8cJEcNsc*fRJYZF?|V^e0#LO!KmOtav2PsZ$H*5DL1 zkpTHpN8bp)KxzIP1}=^DG)DOK>5TNed~A=NkyW%tT2q@PsqYilCXGQFJEHdyipK6i zjdtF{{%GGlv_;zk_t5!g+V$bLe1>!a=gE#C(wrbmhS7`>da9ns?`Y?=D+y2G5Uu#U zYc0>%MxR4tE4j)#_=!uheoyljc95Onr19u}n{XIi#KwlkiAFg^F!~p$f1-(N6Gs7L z59<%P2R!}dF%tY7qTOmi`|nl>O4JZUmp-~{SkYE(#c&PgN-O9ARzpx{wV;vzDd;$Q zTtT9Ng2{os6^(s>VBl_a3nyZ8|Ac%^e26+)Y-8YE1pRlJxbNBnoSut(`aYij$3yt8 zaV%prpW)fYvOlX=Qj7{JYCg~kFg;iX^YnXOI)9Sjsi?my~ zfQyu0#*)N1aWO;>O)u&HW8kgIIRRkA{&@k~oF@cKKHLW1mgOEBonDT-%Xf|55+Otu77nHKd7*cSp( z$LuXwuGw6aba{jt8p0%-UPfLe-b}V;wMSm z!B1(5pC$1Q-d%vO@N@iP0bLdjS@@-eu!Ub)XmY+S&bQt9$`%e=eC()&ZVS&?IA)>4 h!qXOxSXi-;wy3@V{{>XsKFt6C diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests$Item.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests$Item.class deleted file mode 100644 index 2f0d60cc5b715b7384c76359185e99f83243c3eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3403 zcmb_eOLG)e6#j0{WTt1*Njd`wpP|b!OV6lbPlO$ifnJiX;_{Uec}h}mfdu%rhT*Fc#&WCS8iH!t4?)&(OGfBD6k{f_ZFRxTTU2- zT3&OiQ63qE+zHonqn8zql;`hXxDo}fw=%n(GqI#FG%p&3 zAGhqlX*I2eAFjIAh0ynA6|$b)a1=6gl`{&5=KWyBT63c1z;?aRvOUj_V(mifq90vs z)$4qctJ*=WQq$3`uy5Uu>aJ(4-Dp^@7de4v*R6^t+JtU3%%ewPpenYe?=RZ{1@ht+ z0}=~&G{8U~%p!WxuTW^Vmg{cyb?2szT~xa~v1FhN14U#ps8HJO00Z6FTY!ck1G^Gx zgl0#pZm4i3_RRl_6+N=_NTI0A@8qmA&PR5u9?kh4Goe+D{9wTj)}3HhA_pA@#J*gS z1{|dMD^4^QdtYI9d1AY@3I?xS-gJ^ek)dz;9`o*K=iwM#Q1zQP71ka}_ld{jC_`k9 z*AkNzelI@`*zI1}&Z%^;(o}maCK;H(qy*&sAQ@k zWnNAy>73B7Tr*??Q_E4vYvj(2S^8$LDpwkE1^+C`V6-LW*|NV4Uo}MsZ z;jmEbEBdM(uFm;2=?dkE$^(5Uu`iF)242OfBIdxZkqexV8LXgqImb3;*I8NvomOnR z{F=h}fAttP7<;+yu%vYMsss?oRNcn%x1bE;-H*|jok@@&oho29(qM0U-NY+ltGH{@$h7SI{%WPR$& zX@*pd<@eX!TL+8UUahE^cW)K>r%q5aS$m#qapJzxH+w_>(O_~7hr=-%SysR$lG7VnnIi{Pv!`Z!R zMxFoTOM&Pk*Cee-_a|^)s>S|9Jca61dN`}+%siQ!nxstK%-{PiR}xd%T=MDQQ#kp* zrePj7JC)RmLigr~e0rV9HnQ0ABDdjGyc=%lGHFiTA69&lByiun>p2%&jb$fzQxZ!O z!mrx(B|C8CoH7rlSW5{~dqzC~Ilfik&ewXbM0Rz3!EUD5M#Wu@p6qG;l4+0AoTqJmt6-xtC;=ZQ&U_b5bCKk9fo3OUb>Fsgm(CO4Fs? zTqo~f&*aok7@nN|38O#ovxb!zE(-%aFga-l(2rpj(JJ`|5(tc8A5R2JRS`-Q(BP8W zt5GulV}Tx`!1Ee^J;D=B`Z}QpJ-EB9?nf?eJr*h(f?s^BZc z*NDF-!906n#N59sx^3X>hbKmEw_P>JYdVwf9A1kR+LHn+I=jMeVr`SVxc&I#)VJu$ z-k$m$+SlmH+@87%Z9ybV^Y=3@#!QjI^XTR|E5bC0AG(pv>wjZ+9$H_VGa{Y1?LZx8 zEnTJ_QgC_6`wJwb)CRbUDo03*-wKmT<Vh9bMHPqm7 zZ;-meZ*N}1TUa9I+t-n);H?VYso*NEk;L^1-o<;@p+dZm53XZZh diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests.class deleted file mode 100644 index 2a895f7c7b982c0ecf2a5bae9234e882eb73897c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3152 zcmb_e-E$LF6#v~#nrt>pvrYMEOHq_c(+1dpC_?#=7Le4mK#P@96>pO@-FCAZcQ;jD z9G>_Sbo5bY93Obl87+*=I2GP}^zU&T&)wao+c2mzVUpaBbI;fB+;h&&AAkS)8-SCj zGMuYA4c+H<-Dv2x;eY11YdUY5dXwL67`E?NPW`SvwPIA)=8U@O`7ZZO$DT7jZ5f{L zrNJ1qRld%3i*)GMmRF6cpGJZqb=I^^|2#u~p%O5(eACh^q+()hfnl+edbZM)CnIIw zXiS_BbRViXZe3qB{AHJ$wx@I3cKpC^PoH)C*_LHZFvxBMLPmz+r8UR5Oj}=FZ|J7& z8?MbQy=?m~DKM)9G|RB3D&i1nxXxYPFo=<1G{O=K!9<8Tpv(&;b@EKbF7zr;kz+^{ z#J&f83X;fE5IcjUB7@x-NZ7-W^jA!e;X-#IBFx#k;cwj?L%uN9X_evDGsG8kGYksd zaMQ6Vt&S6>s?)s7aHBI1CdRr(`BkUR7lfc&&%}MF6uQ!b461-XQZqi`Ez4*4uFxgw z&WkL^5-_aFt^=#$0A3RAk5KXWPMFpVySEZ1p6m?!w!1>eIoUF;n&GBVP%tJ^{?K#V zN@1%s&&eoKE8)$iVb>Ur#tUt$tYScS7@AOV6vu=uy+g7ckLS1;NFEoGCm4ncQ&E+~ z@I_bx#*>2a4HbtmDpsc`I4j&+nR04IQ08S7r*TG z7d0-vY#E|?ao4ykRQ+f7Y3o+;HZF?pGs)27_$#7w2D zT4qQpkzB7V#Rce)P4{aG9cE9+nmUvmU?q0->6eM=jJAuYy|iYu&e2*E>$!b#c`_)x z8%oM4E&b$&6cvC}qx>Sgm%6L{@adzDDxN@wbo7ifOm_dGra^CYcI(MFwnnWgGjG;y z?zdb@@BMQv+cz6V*O_9DL?VKvN7PxDQeMB7T z)YX1rcPQO8@AK-~3~xqaYT338cgo_PXHZ=!^G?gH8dpqVXE0K^@QiafPJJvHWLsW5 zw6t5KorEj6O>>HFMW$J%nR+%7SfX8p&Lx_)VqSR!ZTunn$BVyU?@vUN@Bxjz#F>IZ zpDc1Ten_;vp$zt6fOaGu*rR_yw3d*@M>MA?=>vm(7{pLyPA3}W2YSYT#@-(TZec_C z2GTEdt06mmyEt|&gI6J36jlzC4RJkv!^tnchAbjb882?&wb0HTI!b_LiJRIk@d}wU zG>gy-KgQVNLyYH-Y@j40H}JYPwt+V{@Yc4QVd#bgqFwT2Oc^LFC)syy@Y+ Fe*kYSAW#4R diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/KYCStatus.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/KYCStatus.class deleted file mode 100644 index affddaa7477e44e0d409111c10c33df36a743cf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1696 zcmb7E-BTM?6#w1L$1dA#o2H0>wXGT?Sqcf1R@zV`O#^O83C)H$ojfp0vf@Ir%Valb z-|)ph#3vs$ow1!dIvF1OM>(E*H#%|ZOOm-C=iJ}evj)_pLEp4iR3r<+xUxff?lBnbq#uWN;;&X^d@CPSp$L{gTO$8&_Dvbxck` z!Uqi38zb&k4v+0-keBfxL#)~MTncA0op~RjVFt4*rty)CNd{R&Qu&6V_&>SMj2+v( zQ7y%`<@@;&ABH6yb&K~+Uc*h?=B50EycZskXQ_XZd}+5*J1}d7YOVA{!yGi8fX@l> zFE1VPG8FUI1*2x{ZXaw_st1L=y=sMWtzjVq+#x`*QmvMXHKVe7P%S;DG6=0pAt1}3 zR7+3kots(?nJdH$RdMEO$h=3)^=4Z%s)9L{T$)gjR_Ei-Hn&NWq1D+$m^)e|QbNt{#*aZ>ulNf`$c zv%K|5G6sncKJ68vN{G;(^yv#XUPJwrt`hcWk5in$7kEzl?GS=XToL_bT*ejgF5@Z^ zBvB=<1O*egh9tdk)l&iisC`WRh8qKX{0kQ*%uU=HV$vkV?Wjpfe}hj4kT`}3t6KPi z&&U>cpPFGPwMPDMsXK}IACPwRNFVd29_>Rn^;jQ^rXKGjXXv2Kk1o21~#9cvbD1x~E2Ai)i z#S_UyB4;U;z#66O0cPJ{$3bWNC9;+&_-4^*wSG}NRZAn!R zgzbiwa9vLcMbty9=BZlOamXuC|C1(tI!O}am>30z$)GPboP)Lu(y13CpRd$b*OqGK zr%7aBL~iF8<~Eh`!~51>Q)~$(i=HS3MdACQ@2O%m038&Icdj z+~g}6Q_JyE?9E{J30ZWb-^<1H5i>sc7^il4bAd&562nVixcd)FO5g^==;4hpq)*&W zv8@=UR=TdTJF?<#+o4@||s@D+Cx(Mt@9~zSt z*Ofup5n(9lw2W1+8#LrII|`T_)>%JQ1@pA67|oKK*=Tu$a?!470CR-nB+ro4$cwI* zv|FTEBxyz%ncIi{nE=OadZXV!9(U+X{&v9VLCO@y$ifeTi^Jg97o|EJ`HHWS^ww tavRVpn5y7n1($G{Ag)w!71uVvLR`o62J{d{hS+|Ay%JE7%cTL^pwX(34V>!i|zIvpyCDejx9xY@%SmHmp!38XWLY>`i@er{#5XS@FcCKx5}-G zV7THhoZ>RQQFF!o1b1D_Gy)d%UFnlO+KI9He0_re`VHZ8#816V^H%e4z#@8xz1uKiZ zT{Wdi_l2dyI|LoLb+c%5Rq?3Ok2N%39HV!w{o5+{cv&dnwSi?iT^+C1lXD8>E}6G& z;pHqYrI3_U)}4xHh$p5cqO`Hm6CCL=KMl}EU8G)=+6&b7_Ru|xdcC+$Jq2@FwUKhFZ|Wr$LW&4Sf-#E$BB+ zgTB-?bQ)tV=*y=;k9P-6oW6;-h@2czG(xTai~Wr2zu?`Uf=&dFs7=tPs}zC^&Eqlk zHQWnkQKa$rXl9d}h~zPq$NPDFfDcK;bRIXL2jQB*%}rdCn3cFC(JOIV;*P{e5>bh? c#K#hI5@XWWxhpX$k&>8~$Vv=JWF!{;1wd%=`~Uy| diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$Companion.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$Companion.class deleted file mode 100644 index cdd37ff9637e5d47a985c32ff752f58fee0aae92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1203 zcmb_bTTc@~6#izm^s=BWph&?xs4XfB=!20{%7dAf5vSdqZFZ+s-gxqd z_~fHLk!WHX9{o|qGo=cNzIb^#bG~!V_wAW8XTN^?^aa3O%rdOi!@x5_@5;c_(!36% zrYBm;Yl*!;Y7_e5?w-^UZWFuN*ftz;d^WH zrD*%6L|->iyKcg0Lqts(Ef%XTxSiwZbp1H_BIsoqX8%l$VfY7OLmIIojG#;MpySY< zFl&<5fPpnB{zh3eN8Zjn!@chQh|9;h%X8g*m#4`g(A`fTzIDdC`wM)IJxHbb;kzWy z@rTKrGr{K)hwqaEmcPKP>e9cxS*pLe44I!h$&mm3+Rio&&A$}xNQP`hX}Q@BYBG8% zYCc6qDq&stRS_wR`@zX4ZEaK_%etdtMe)_+rC=B;Yb~RaFXC9zM!Q>KJF3fdW##gH zX7v{y=IQ+scBpX3qaTCjaD$L%r%dO-%mLCL$>AuFwP|1pMY2Oj1<1f9G>k!t@E-XN ziP^f$XJnr_2N?N4;H1Fv34u&sV1{a}{3#&EsFP9hZ6~+s^k34o1?2{>t zvlyeCgPT<2_DES=LmsyXb6h7OV433QsCIlCP8s<_ctw5l zNBHP+`BYu@qFu|feDv@5>T>y=B&1x166=duE9dNUW}kgKdw=`<@%OL40XUDZ1ttwA zr}?^_l{w9p{)*!+X?ot&^7?8{+P-5s*;Q?F(_%1hrlq@V8ZvGyNMmVAW=+p`b>DRC z_}O@W7((Fj=#t}GrmY!{>lA#`mY%k_oYPF(m#(c_TDsunrJWhobMqN}V1rw++$z|H zlI3Yv%T#}ICtMu*lg*P^zzgp2QCeaF6zs=W-$cX#QdT(eC`SK z{u>9vs1@iapV-BTFzN;BlcsI@mju4;s>G@tI+`EKxp!J?~(FTODUtnL{Erng+a#b>SzqTlv z(W1n*3dFlodlUuziDDm~i=Yne0=3FBfvKu&2=;52$O$yiM?;#+(iLcovlR1|R3$7B z+sjCS{ieO_EXgt;2%PHLS&-d3w*>kEWN*QkVL4yOj!9q7=)TU97PkZr2fNm{?`49Ul}L+h?9N!P0-jEEIc@Fyu(q)&1#F%pLcbWc&FR8 z;}-$V)5Z&yrO#WEW}kc-o8v1^a&_;-D}t>{xu)T1sS0`B4D2hh z&Gbxqa%6mXWLTiRD;8N!C?;LBJI!(Lwa7E?y*tD^8(&~0QEoJX6vhO?L?V@d0&PK%(elK!QmYT+ZGyhxN*X9|rE723cb3_f z*a%GTB_x?(uh~OU<;63@=K_0^Lh^Ng91G(EHptX?dU|ktXoRg`BPvC&qqvEW8t@_XD30QoI!_!9<@=*}7qev>uX0sXT-JN zzhOm&G6c28q;5O*YR)NmEaZz-K|x?t1je7C!k_ZY4r7@ueaOi$YuiR07Sj#gHC4ly zRs&md+RWOzUqs#gtUqIEOwX6UHIMQ=K3>SpOSimpbgIDDbWW!1Wz#b$VDL#RVxQXn zby9croa9S>3tKd0+tMAfbkCD)p3Nh+VL2XalvlIhWCS8wXW$Dtn};2~5(C&v>Sua~J2& z`42Pu(M_TiEJF%Y?j-4sa=HBxLbJ+TWoyp^yhC&Ea=#QbrGh+_ptVBCxO)L+)p zkm#Wc_mSN$zo8<(f$~+L>hJ{*wv`Wp39-Vpn%6kia%Ge~t-+Vv3E>33;yg#{IJ2|E z<3?k diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2.class deleted file mode 100644 index 881cbd34239363eea2a1fba29ffccb02676f8421..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5147 zcmcgvYf~H78Geos2+P98B(aTiBefc^z$|vtIyEFN2pr4Cf+BEAY3eT0K`g9xmE9GF zUfo;TruX|jy{Fd?Zl^Z1%(P5D^h1A3|3POmeU7ve3rK+sosMSr9PN3}dwJf=dC$Mz z`0L*QJcB<7q_cKG^K>gO3z{XpJGQf|=|w{;>T3mQdA4ci*R;vNpW&jBkNPVB~hveY7TLS<5e@orzzlOf4Z&JN5rEioW zk$6^ja;cm^$6g#`IEvU2gwQ4sjTcMvrjflN*91ayBQYFDyIMOzXLyT-8y^rDIt=bH zJcNf8$43O>J*jF=Gze&FA44lnMbV7Y0%4_sz~mtmsEd-ok+>tX(pZs>z|lA}r)Wx* zp8{R`sVLB9SS$9j^s`;y>0sRWNpda#y#Gm{zYgym1!Pptm-08Ir{{D}XPSuxfzJBw z?_c(~ZZ@&tJ{5_@s%Q} zggh&7%v*HqI}_Gb-7rgz6nLbbXBncGF&M?WF(fb&D8LO9IJx^knUXJ;r0coylqU-j zu;TRu`1plfu6mMas6%69&6C59qpvAN-@}mg>}zthQW%L%&)i551ixU$FoI+h!yroC zK|}oYK!o~VD%p26iYs_Q;JJWGZ{4&AQcR|rlTDLHN7hYkK zI}?e=t*MQZZZL+MNJlY_34w?F4cn^~6W$xebxg5x9K?i!r$GEd1k(bq^&Fxg4IXH3 z!-zS+L8dC`tte*j;(n(t&6(Y%X;70Rm}9mEfb_$&)@P(E?bQrDSTIFEp zN~IR+BzrYTY94U4;I5$r`CE(SIyuHJ zXa~?Sd;)im;N$pY3{Rp^?XxjgRA26MW;XPF|GgH#QV;JVJ` z=0M4AxVQe<7r~E+&z@_rz_p-%{94F+**>{m$6qnLhM%y(Pue-=W5>A7%6v7Z+s8S}|OM42HLxQ#Ka6 zS0{BxFGyb7caX(WmL;8}sk^RZGktu_%9^&z%O^u*(dKn3nz2hxR$euff}K9oTYChu z`wa00!@`R&BZgfk(B7`@UFDH?tn$d45m%e}utagk&p3MtP314A6213vEYWu#9f|Yz zaWc`|8D2-{A4wwcbH1Ockl_p-oq_83K|pgo1h@dTCm1?M3|aY7|2 zSCq(qlao?nbp!9X98UZhmwv~8fGoe2JhdOV-MfM3HgRQ^B**x4Y+!61*KTg&`fT3@ zCfAWkZ(>F*pFA(t@xCp*Ke2@mB*OP`X#?}Ko#EGUv@?89@#(#{iQH_*!a9tO?zbCq3v>sX;l_efK5 zg94Fsz8E#TQx>CU3i6N-xmOdcWGTUIAzzECc$Ow#{0NyUw(W`Dz6bc|7D9i*ryk%l zyT!KG#J1zJc!m792cHXw{cufe)!wpLr9&Bl_F-1U&vUM}3SY-JQurpmmBP329d35< UU3_m2VHe+b@dFn>bn(i60qiIaH2?qr diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2$1.class deleted file mode 100644 index 89ffce8aa65a55dd460a7723345013a834c3d248..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3937 zcmd5;TXz#x6#h*}$xN7;6za9= zn}5K6;BxtdF1aXcY4y<`;;(SI{7yOzy$m$si&-n@>~m(HeLH)9`~30u&%XdTgD(Xx z8BR&_b-O4_nl1fXj=P}gWm7BbOC@Rhj^z}Wv@7ctgJm->-9^)oNmmw4&zJ7y>7nGQ zWM33Q;J4g@<6EY!8IJ2zeAAYmHosWXOq-0hZfW_7SC)1mrM}Y!=mT|~U2~W>GbwLYr03Tn{g_A& zbYBzr^Z#gBop&4_uFW~h`&EuM`*Wt3JSmW=E1co72jbX?cnc!fC9s28dh6c0C6Er0y#Yjqbr?o@ig z=Xa}tfOnRJ$v?v+=z%ElZ>&OKdKCiG0R*NAf$8!()Jum8-yAXCTh4Kd+Pw59+0oRjl%~@rq^XvzDa6$DYLI_==NU-CKc(IOZVn3rNG{>TV-m#jQ$v2Qb9jg4@QRvIEr($LpKcRc}cp*?vpGFc>f8g zx}T;-SnV0S8p8kx;cIyR7%+|Eb%E1Q;dK-jSZC|{s(@9Wt08a_Qbg-}=Rjyq3}a*` zhA=8{^igljvTYW1E6+rhBexA%RuB-yMFGuVOHKGrqSIxPlw@aOR^D)2ndn@t{m#TK z)1OP!;PXw{gG*_Z99rL3F?L`X`sM`uI*jl zT3H)nBQUj{kW7lbWE(}b_sdnbe)b*(*ex+um+C>Q5fV7Fou;sxs@A}hov5>Mu4T@a z$5Cj0dZe_da9gOMSq&gWEhwUa=Wm;eViS)P`j}KJ%gwQsUNL?pI-U7pu=$7}Lwax3(PL*5j4ZtaO8&Lz5N0j7u_WFPff70fUcv z4;$0wuUB+eFG;@2H?YOCwk_QuOZPmcLc<`Ox9rHSy1aAm?pTW8`e)ygSu%7p9jt3qoY64ei$0t0~ z%(;v6r~F5m`RFFG8!SNzQ|@Hwj&ga=Lqw*Pxysg_`*@4y-r@dg(3A@D)PlALDnKF# z(AAnMC4G}BmS>=$JR6)3U)KF4_kP0*7gi za;PuR(vs?-3-?gmEWf2DzlHKupqlU*4|W6x!GyTQwVIbXHgaVIo;Kif?nH1DUvQow z^&GSFh{uh@44*!D9xu%B%P}=1)$pRn+&S){6LZvK&Y&TS`79Q)uu#gvhLc4ZH+k5D M3y&f__#P_%039iQ^Z)<= diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2.class deleted file mode 100644 index 267240bc80522079713765bead57e98dc0a09270..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4405 zcmd5B1}kF1rgYm9k(4AlEEIp@j^trVc7}BY;E~kZoAbjx?4&D z1CwFj-uVYS%)?Zm$Wo+g#>FGQgP%cBd{=5Ivem{;g$L~FdvAB2KFfE{`OekPzy0_> z0MFoSfpa;hp!NL4!_u=QuPE)+DWkBmY7Ez;mB?Dj z&iSTed-`OsIyDl!zuc&9tfo1ebBeOAqQT&pW7BfUP})XLPBjF`Wx=s^uj;?2&r7dl z`6GReGP{B>A4Gf6uq^5G(;}Uf!_V`7YQrcSx)Tq$+~9fnPDy(HUU(1pUJ&@@ujtwKOxSgo!{OSV z>fBuwX1l*`dZ|Hyk$r&~u6a0yLx>%Oh7N%Pv{Qju;LN^shHOv_hY?r$jtHcBGPM%h zr9K!)3}a^`6+OkM;@ zFalBFm8-JoK$S%Yg47xyiw+bUh;txZ`F6axXH$-w*EgiU;udW(f1ZjbzdFhYM&d)9`WM(Gb$}VQ-CT7Mb z#s#{1LO_n8A19;e!+<~={gM{J$iy&+A(elp1P%x0t30cWJR{KQue;8rS$onjt&%GR z?ho5jrscCZ9mU^phWXgQ;rCRzsi6p-Qwe`CieZfG#kiCv(biabmlBHLMFQR6;m$5I zQ*En@zG-<1gR{h-DBU&3Ed;D8u+US>*KqD1+M9Trt00DPOhhq;NrA`iw5SaGSKhF) z%qlr?IVX#%8Y4I-py$}zlD?DdcG(#VvOBpV>84C}SIe+FdCBzGlXV~t;N>0LmsQD? z4_IX?iVUU&BFu5sXay3@P71{DxK{0+5xhcn&AE~eR-oA_un#br6Tk6Tz|vR?$-3Ge z)C2VN#;-=wB>uf*YVQ}>rP(N=tOVv+TiLKm45qybq`<}6hTp^ChR!r0BXIjq(7e4_ z?o8-vIq$FQ)jCwjsVT(!7_wMUkS()U1Y4+pjNlD+$IMK2VRU9}f^A_N$}zl!w-4e? z7%@DAht>DwWUV-)XtJv1k5E>kB&#!F3DJaah|>a(?`b3gGlo~Oq2y;qTIk_rQ>}Q|&{i2%_tw?U=+@P!c1){X@vW;59!Rt%v_Wl9q_x48 zb3c7aV$xO_+MyA{cI+z!r{s~(PBeD+@V#Yy+|%|7Jl8;ocS+e1{F5%bcYa(5So=Fs z){^aR&a2HO6vGGj7tcat&MGT5G3AgIFBq<=I`4uSc&5pkdE4+S?W==Cn%kT1m4RoN8QZHH$fV^C{o8bME2%8LtRy6}?;>Q4B_9 zDyYdIsIJ@4>JInc#05Hgjqj__B`bk<)uHRc#fk}_3konT0Bo^tRM}BRJG8)qnrRYD zVEQbTDm>%qzWy6{eGA(6Sh<1Koo3^8vvEkQQ6IlSzQOEkb+fftD`u4smGI-E07a}2c&pYtxk)i7r;i&W;Q`3sJ%cpp~- zI9l)}pJ;dtUvW0LdW0$csK*D%Wu8OvIG$MMn-gkCso_bF3HOwTZWL(8UPem>P6ovc f-hrEe2S0-n%KX^FC0t%ctA{Hd-u3XFhqd1Ug(etS diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2$1.class deleted file mode 100644 index c33561c86a8e3a682dcee405f0136fa81ad4f69e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3962 zcmd5;TUQ%Z6#h=Qq=d9wq_sEOXk&puGo`IoORRFWvD_rkil}umoQ8qPOgb|O#Vh*g zPw)r0Ts~Eoy=d1`^wHnqPjI>X4hboQAd$Y9wQ_E=&%T|#zrFwX`{!Q(T*8+E-?5y`sy4M{GmtY=(k+;VjJh&odcJgT>Xs$_=!Ixs z5JKR$v1P}%Oj|P?*U9^)Ej?|ikkw3^w6<<(sl1ny_QIH+O)u#Eo79TrcHTC8)3H5m zw6y9=Y<}OuZOF07Hm@tv#JRCrOqkxL>$LQ8lp_1D_Kz*;1zoe~0c|q9Bn_Wu0=@sm zgdpk#PL*!#Vnz^+0*wjNHvOvtUw74{balV?*L1hi;5&Kg`DJJC;>`Z;8v=j+PnE0j zrK>xwO0e>wmAQ@nqUlA?3nXgtYjyVhVKgJ$ga8f-9H6ibcMCktTZPerR>iVSAljAO zBM)dx7zgl72o2~Es8?1AOg@E8)m$df#A8F61?dVjN11v#=05XKAhMT+0tZdI;4I4$ z00^Az+L?acJC6k7Rd{d1lwq09XU3(kFX+C`1Qxdij#YoF__ELMRvjUbuP#jf=_x@A zM3#S}2#WC{D8@^m7$+#kb6apQUY&Urr}6d195wXY1fg>5|4^2)_&dww!MsB5MQ&S@o!z052 z9bHvC9Y!zCh0uezKm+xX7Cn*-<2?FQBwi3`DTyb9<}F_dxg>DNUv!<7NqbZ`t-LD* z4p)0v2I$MU9KuU@Mc_&;s9oL1L0q98x?xDqi_$!nq-ajSdn(-O-cP15?Gtz{gnkgz zH!Hv{*c!we0vGpTbr6H}@b+RW;M8XAKFh$)91LR^Bh47XsKDtbH8aW5pV6%p!p zHDpdqO-e;|rUYzb zAtW&_5M+j_$P;MasiZ*b6V;04Jczf6@rEm@puk{P6(HH#PV{&0MIbP<7n4McrDYF3 z<-TT?EdfiB0`H~}b4-<_qK5=7?Inp~Dh(z?FcWry#8xY7ETxsfQ*N57NDU)}8KwU` z>q1GoGA)SrSrL;HshNR^p%IpVP1QE+4&xR+Y{Cc7!#IJH>OS!|kn0QM9n7gMalCEw z$`*VNWD5$wg&G>w00PyrTGa9WU4nXGp^x#jzSbC8Uke3}23ptGjt9<*YrlWRJPa%l z%9<0p?bxeXC+{(RYb*b|xJ2OkJ~Df^MmmT*EBKJJz=&-hbC^gsbk|f>Vnz)t!YMOj z>;4AR9%Swr%j0^k^sQx#9rr{&o0e|r=4LO5@3tMM231T z78anjRdr7rGg}^>Ht^4MAms{IYI-}6x5F2`7r07vjF zcO=D8G87vt#WJw5I)>w9waRq>A<{77xK|>+rAVdD%p+*yu^(~qJ8t+JEKVDoj&hyo zd4yN_{m>)4&Z#}Nj)7SHeO&wrZ`NV1_Zx(I?(>kCWBuWmNEsIjzD?~9lOQJ55 ziDJ_d>v@3p))Dvt=?7S-l+sd`(gL|f%DZJL+YI8Sb$}3XlIkdf`btuoVm&nB0Wy{R zo67u~$X_|C0iW?=d+8z=5C!hl{5i*Z?u?S9by(v`0H^T<=XtK4V|1SKco3Oq`-A84 x!aTp6QA1P>FM5ofvmQFJNIB*_>XKMWVmS#5*(7W@>QauEJ=}pymL5D0{y%%_jadKy diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2.class deleted file mode 100644 index 5caed368c7d7bbe69c20245e1104fbae55a483b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4435 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 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService.class deleted file mode 100644 index a1a8bc67d1569bfb2f7d28c8e675f112daf27f46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6073 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 Ns0Q{Gi8m#@{{nsdL8Sly diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse$Item.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse$Item.class deleted file mode 100644 index 313a0e1bc052081f24ab05220304a55c3f54bfa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8078 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 Pae9^0b8-MVPQUmUfWw3! diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse.class deleted file mode 100644 index 8348bb03fe80dbec07a8fd63c62ad0f4b3f9efb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5304 zcmbtX>vI#=760AUYb`H$EnDCxnBWv!Hj!;e0+?5DOf1JH#HLORZJI^i*o&o=vv)-T zZBzQ9kMxy3XvuV@Go9(<10ULHF-$YlQu3iQ{hK;X&)Ho`Ygr*Ge9+!=&%NjUxJUo~ z@8A9o;4nTEIFc_Fv#w#Un#HVby6=>n^{laBWjBm#Mbmamh0^M^?1H&!N!Kx4t7I>j za-(ERGXWu>Uooy4*#ePgU%7n6%)1Fh1jd`=>GPR(oRX73Odx*TvMu+dz|o;Ok8jzv z3fVc5JbBSac!CyP$Ff%^LztZHnyaQWIlLrry`J{?TyxrNw{S4$n#IYJ|A+AXb0udr zd&P7wJBDS;tYO zUmFl;@vcpkR!o78(A7#aJNN=h$Nhl^nZPpCV00I5*cQ2rrs%85E`| zstMOA=iQPsZ#e5gHVy<7`BI@k)j{PWvr{$rWvZ89c0h5%y~c@7z8V_d?&=YYCXvBG zj)tENk}$QdHAxQ*(`)7}@cSP3m<#6qfgu1W!|a>Ea3ddsa! zNGYL;#=7Zony1V$Rw?!;n=8~%+ZYU-pDS(@s0NM+yfoBgW`wv*1|ARFq2VZwCozc= zQX@lfzmoKSIro>60C!?aff4u<=F z(08||dLUv_!xCO&DX-U2R;~RM_GQH*+z$=MoaQ^9`OH>{G+f0y{LH>fFJ#GG<0m@& zLIxKFUaia2Cofci_R1SJZ@R{c;TqHtl;YJ0je~+easGgeUnlVpLgS{WDO*AH%t zC6zA4`#rdkRP=!w*qZb(JN-pXDr$-@FiwrC4{xen4{l5-W8#ngkOnPQJ^j;P@q3V5}fc}|} zZ?PhK2%98W5WJUQmEnb56>hITycH!5D!Ha7s%rX1(=`Ux(iL@Q?9*9HR+=Lru1%@arcy-lv)*+}_LlDVg3ho|b7A4E{t2oOs;s z=y6QohtvjW!14ZU$Y<7RHfmY4R&B#AJ2WixE|hK8Dw;X_swFL&m|v*P+N6wM_HA>P zZJsL^FPqK0!YH_uuur#oP|v`smTQ`?V{%j5J-@f1z_pHQ7C_zFItHBM(qT2nkpdn=8~gsYsj zdN>-1>u@^lX*@&(KcyQtiC>J?fsxdX`{>Br!>-K8U36#q?xH_)@Gkacx}$gT^q=XD zf}hd1ha|_*O|pBaIrX9weYEQ*+1>a#ogeo3;d3}ZJL2r=PT4~cYpQq<>&jhYfQNRn z(6_7wzaU(i&1Ta$Oh!km8cmQ{ z>gUXqHk9A)Bi#u@&_d8h(y0##n066q=rp+zkb=ygNS{CT~HEG=qVNAy!eD{ z|Fo@_*L0h@?HM?bo;zj(^#xp%>5PN%V_LZzUdW969BsdwWrN==x~PiGnIX`ru+`X6Y77^Ny=+&ZB^ws7b?yA0ujOzfhp*!s1mc@Hd<);c z1R?32`OYOgC2>IFyAqnj_aweA(c=Moyf2*+Qk8gC;yH=uC5}k!ml&5Al{h4ESmKn# bX^AO`vl2OpSqWF-x<|M!abDtmi68tAsZ9EH diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError$DefaultImpls.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError$DefaultImpls.class deleted file mode 100644 index 3ef964311694b4760328346b8ffc199cedcccf70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1010 zcmbVK-%k@k5dOBsUMWSOf{G}hptMz4iN2AT82phl1r0SAot>L+X1;y@`~|=RaE6DGNx2ugD^sqeKQuPxqOW*g9Hmlw zlbG%i-<7U!w3AP)HFou}d?yBp57U0)0x*W@o)`+AP!j&4)02@8P$V`L@#w|2bWU_7 z!?Swp%)i@gw2sray;Zuq$*|Hgw#$3ccdSs_aiO*G!Yith?-;)`ND_(~^9Rb6QD#_- zjZc*3yWuyJOc0j3$sUFm-|nPIm658+kRaA5*7m}Qt8=vbRWy)3h-lyQ;a{(rRh)c8hP z0?aeawDOj1OE30?7u3%}IxN!Su*{S)0|pjn4$ZspUS-}rhU&=;A^2;3+f%Ynup&lxyh<|iEd4|16)CUfpbjGg tMcg4vS#>zd$ip-=Y}eT8PCEOIQwZ9XCR(`~ZHd6GQ+2 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError.class b/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError.class deleted file mode 100644 index 300220064bd22526b02f7c040e867eb0ad0f8e14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 958 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 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin.class deleted file mode 100644 index 5314a7565ac8c09b7fb3b9d8d4d80a0c10626595..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5416 zcmb_g>30*?75}}_HkM^9$(A=Z*SJPrh>QyaFgP(zQUta`F(n1MWGoH#*z(BH8#&O0 zkS?T6x1%@};MLQ%~F9n;A>8tYB*Rp!e>*^X~rceb@fi zfByP60Eci#;83o(l5q@s!CcAMrt@~Ow45CGUc(O^IF?8bL@Pe8RFVXF}jidb%ol&M8^;!uZorE_;W{ zj#bD^ThbXHyeRPga}su9dOLyYpY=jIKH(a(Z@O4o$Sj%8e92%;Glp#!9mBC0*vxFv znJpIz;{uwM*U>DnXSwJUEIYHbwvw@I$1K@KA(ORLK%|wE5wr-Ta*Jke+2=iHl#CUV z2L<-my6vT>K@0~k#?Xp*6g!X*2-@V<(Z*EB>nrocf{v7c?%xWjqf;P|Ry+}OM-fDi zKyq_pVra&$CTQ3lLz@TfjUnYhpQ3Oh=U8i|fVdpPKJ=@jpXPO{&t43j*sngOX_~WW zNr5BVN&-WAy*3=ra;37jWXb7j@JuC6VEnjYL7j0{GjcmW4h^z|si@)?29g6U-Q0`2PzZZ-P;2}JmL zmN}(#LUV3TJ$TYj0n>R|pjn;sE-Hh3eqC6lke>PO3q)Bx>>sA>9IQpgY7shAJ$9%T zIm{c7E3RG_c;|T~a&Ws>>F>|Uf@couwmpccQ1<7&J1g+d^!7rrx$>WevZ+aDYqa*AFV2z1szCa20)A#auo5rlqa?NgXq)Kdr8sHmmAD zgG~E{SyN}ewDA~T$2leNb1b}(Ue!w}Z;tgg((AQTqMvtBuK^N$Ltt0>@{?s=QZiMM zQl?*{&U0km@k;@<+@Qa zUsa;EeB~;#V<(NF7+=Koe^7kvG1 zyho`R)8jjPf?Z;C*XfUMD}#2Ex`(hJyq7Qyy)Dis;;tmRpfe&pK|L04A<`R1`cHXg!c&GJ3=^YT-&qAI7d4lr98(?Zm>T367zHVZLcT*y_!((CLwfF0i;duPU9q11+c04pRJaQlv><&G^A2HR4AxuG(SZ2_SHX$8D|)ix z);?ObA<&|Mkd48eXvpjacWdUC8Zx^9-J1F3N)T(!c7xc6If2Pa;A$D%z%^oM94yYK zysavA*LdqyHK#wZnm>Yese18Mf;>H5VWumEnXVLOx>A^F_2;%*nCVJkrZ*JkD;v_d zWsx@FRTigr>rB5$r&Hmes!>%}ghM{uJCh1We7J9xg{%{4;-P^fLBFgOHdw9e`#9~n zr|O#j3W_y1x3o|hr%%6zm$<8|HT@CJUV4D(FHfQu`|ARkudcxzqyW3aTOW3 zbO)g~f|ZX{Tp;2Vs`9lJ;=jCBS_B$L2>we=HKPt{(b+~QK;+-sR;@v9{65qtPuAon1SmN6f%@W^{ zXp=}u>~rBxcfBQ{OB|CpE^%1mh{TH$;}WA1M906m1kQno|92;1;+ryhZ@~tKh zdCa3#@g-GxNb=xAQWepPRVkwnsmkA!N~L>uxetzrLe=eb&-8Tv`eS;AzyJOH4*7;)-dlI^oo;on11=3XUtMZMj--rJaBfFqW+~%goU(^Y!^< zJL@G75jeKJs#)IlQrAb0`Mp#ZoS3&FAb_2;(&{r^7%}Lkm&@k$&bAXwwiyyFi1Jw-)X3g7gGZ^}*KBfzAdf*iBj9 z9C;l&_V8jix#UqeasncwV=tcJ*?j_0Z^@AYN1rzI6eE4n_A(W0ZT$oF=mb=f@{>mB zHzX>hAuv%lx#KbjjLimaQW!G=8dVgX0^9YDRx=~j%ySflY+?0Nfw}5*{ckbvAXMLO zuXx8^X9k`c5SN##Q2|g;bauUO!T!i9<*34L^*^DRA17e_E8-# z;WQg~dDG6afB%Ea-+6|2o+Uqq_W7NLpq%qL|Bw=QL7=;zQu{b179hZ>SFpU{*LNfx%3Jz+0_9BOuntGHVQaibX zw>7+l%M{F_E&V!*+gD3gjyg+wbyPQE^9~`fiuQSy*|tlTH|cmEA8?oakh)pHTjCB| z-4R1Uf$>cRxZ|@$j!%Z|f6Df(1umoltA{2{Iw4HJ!q5lD?c`X(~bVk9e){!D|BVric?jaH(` zhNdbT%14@su2Mgu67L(JEn=e8XrnFdBdW2>H`6ix=w@W1!RQQ>-V{XdCOSg%V!ESR z69H7o!LF#P8I9Ae8wM-Wy3sV9EISj*HXAJue~$0rXi~pE{seug{;@*YqU1cu-kKf} zJS?cbc~&41zO$a_#Z<2-ffDuEjrWs0vO;}!&veQ4oV=ZJ*Bt54)8tI$xk7jTjI-!k zUa3gulQ8fxZJBgj`(i0SZx?6zDZ`~-$XdBMtLQKvPWElqTYiny7-*XDtnA8^wHj{I zGp=hF$8wgGHXT}Rrcf$o?F$au*&V8!s{@Lo)QzJQ;5Z^k@k4~@Pxw^OsL`0>_Xo** zO0-+6Mz0VWb&{p)h%|ggT+!DEbPc7N?x8h(7rWAfchHp{x`Uo{&mHvs>g)QP zzPk`3OO0enr>4+^HZ)^5@n6tdnsAX8Jd6D_G8Ld>D!{}PBZ!3P8t^5_wM0Vbf)w#z z5`8#8pctG9$0CV?I23wWt*B?SqNXhr1p(}@ z31AWf7(j@`$}%Xjf&>eM=3VXf;JOPvpBQZkl$HK2RfF<}g>V`?3*SNskt@Ytpb zBn zX=+6`uFz>>{AOrd;9a~&Yn-raH1g$mE68j}qB|0RoO6dN# rD6!WkREdKUha~zW4oD10q$LI=h9nM49Fcg}7n}FLmn2R|ND1#hFo3x; diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse$Currency.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse$Currency.class deleted file mode 100644 index 0bd98821a2dbfee42edc6165e9a4c2c6db08386f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4815 zcmcIn-BTOa7608u(k@~#i;OVBj!gm>eSifR90lxrnA){$ASouqj-57(w1EY&BDyOa z_amig<8-EPeaKAv*oRIZ+?i%3*qLUg<2rAB=|9wIa_-%gv_XpH8INJO=XZ|o{oQlU zz31ripa1{!UjSzCdxi^z@=n$@oegUz>saoca%DSf?%KX-)hcXe7pj$t~L$uH{Dgi7>q4*&&-yHBm3t1mQ`?7bTG`cCtj7?ialz?D#8qr^R{EVuQHUAiw)}Q zZpC&s=AWM>wZ<^KSgvejw=8$PV%iSRnvPR;P1mNNv&&_7xmqgCGbnaZLzLm}=8i=Z8ODOKd1-4ATWU?m5%fgS z4TB+6C>JdaF+vXUXy{{5E2e8*TwT!6Pv$BmPQw5p_wFuD7-Z;33KA73q6pyyhFEh3 zbwqKh6AFfO#60wpjy?~K=;-&*X&nO|O6nN&P+DxYX>*3x7TX{A6K)cvHN0WD`65GK zGS%!OMM@tpPy#%(ObK9!l5NFHfM2xnJ=pulTqV<7UEm5AnO+F)c^Eo{~OFlsD7fk9I z-PbXX^CC5GFlcVsJ4%LQhr7#iRYxQDs1pe~tHQnPYRmZh=th}>lbDM7e8;wT>eZ_c{*t2Lm)^c@c-KwmL z3K2KETrf*(X2lk|ZyY(yRexc8kF@)`YZkVb%w2z(o_8FpvQRQPx2SZZ*UQyP!MbdV zeG)$9T9b2_qD~p2{6y$CCf*E$e=X{VWGBg(cs(@P=pE5yqZdV!joubbHhN_=+30+r zkth4K<9|u2B$qO=uCLHL9y?0oL@Z8YA{09&L^zfZA`&}JBK;62)0qRjm>xgCaC+hZ zqv=HG0AuMy_yDPNB65Jt7euJwH#ELP8;#*KGBi#iiCLsDhcsR%%VmsXkv{u`=3Vie zAj<@4E#c8xKE15|(AbMJ$ohH6QkLlI zBAosbFaCpo=f5BV`7V6ay$QaL!DH*C*jf<{A0|q+C7NnUGgArRmwQh9|0K&jS1QDr=^LGRjd~E1VeL^+qWRvAtD%qo z1Wj~+#kd;w@$gbyRee0V99IpElunv7HWwnz@U_%jn6x2Z%gja6_0S*Sq8Iw_Xcr9; zy{oIcyPI~WC#2tg_DG6ObK`4Vy7>^7V^(w(Q|yf#34*sK6^89us)g!zqE+ zJe(n6km2S7+-juW>tZ)rbTKAgsD4g3`6GJnHO~k0{Bh&dCSmfye7~lU6AgvDXehL6 zLm@wea-ZnD)9)v0je=iCK?Z%zcZQ(|i$2?@nO~9=t^Q6r^H+pJ_cQ;7@@IrQ?q?oB zSrT52)8ALvl1AYf>B9kil=RVnK1TXjKu?jL3g{WqGc`RVoK+yss|T8@{SSRAlpgUT z8;pp6e5>EzJJ?2vVi7k`6<;JD5a__CG)75N@Jsv(3EU%H!N>TFIF9G;Zj)^oe@r@)?7jEgd(QdJx!*bW_rHGm z6~I}T3};QJru&Ay%xk*M{ZActO*hu9NVLGs6@9Yqy4*G`?#*#;-LXBM24m1xjSWMu zlB#}lX_cFP8VQEfxMf@ZWrp5-IXF=Ft*TzOJb!HD4#PsLbiCXSfT0M}gR#qj*<)qL zUDj8*zvLR0?dgVXJHFvt1XiDQ{MmZ7I>w-wjYwo<7>=wte$}$|)s32N**7~)hu-{za=2~Rqmf;#TP6ru=qd;QukA(pTl6hfdnXswoLbn1HS%ySjtUJ)7 zAc-7hur)&}GT4`ag#8Rjf5q|`&b5^v3bt#R`^}qU$mK^`-7(C+Al&?ia3WV{9~hD^%=g*w-;XP9hG9F z3d^kB#Hl!pBLeIYgX}vYLmBq%P$14*hJ(%O4o^#3|uMZ_#XI;2+Qhnx}>48HU09WJLK`Xi*r! z!L#Dv>ne_8Sj^5-URDfmWzwnepjb;PF5sdlR;s5I|DL6x-j*hJ)~h<#hbV)ddLJrhvTn^{*#L(FFMF@yM;Y_FARpBa?~YfxNlSp z-=G+z+D3vN5GFn;@c{!{Bl%~RSQTkjq0;|j>){DS>XQ^n+j^+bPtuNtYdJ;pPDPT% zo*Zs18%NbcKuct$F+i>zm&H>YaSfSgO_q++s|*#0&pX&uv9O>WAjoNxNyzXr86HCgyVoKg4!G-`#M@)X+W6ZDQ2e#U_xNhaZa`U<@ihVTK+ z4}@oN5Ph^Ft-ue9{G{Ynwh`G@!vm4+5c#29 z$a^r@FnFxhAPWHgA0yP_DNQ2y^MpR zErVAeAPO(X$%nX>p26hjUqcodD32C4@oMPjK5ZqyI_WeuSvo6pF48FSHTVo8cb{N1 zcVZJop}C3Iw2@7m+r%49m|@g|5zP2yGtr$JIKn#!5Nu-nG1-r2O^)=bz;HRjai2!f zu7*bo-=QPD@_$W|ic$d@`h(89cCA^0v8Y^`CEu!w!A`&H(N|-L;ZIo$>nG$Aka}mtL sE!iW5dO~WZ5S4Ic0HS8YQf!&#PXBN$i z#f$j~snSOuEFXAKOCe>dEF^FKCd;zV%&;sMMIPq#IlbNA>Hbc4*Z%qUZ@&W=#m5ZS z3syyQbaRbYG?P1DShWpJuNuB;krztZLvGD;yK0#>kAN|#>-wgymB~kYw7SjSZlw3P9T^*bzkOu0n|yHhk~L zdQGozGGs_L$Ka(mep*yfv?HdV4Jtz*m0pS>f`oz~E-)lcIz*x1Vhbc(qClOJVKZp? zvl(lmo-&NAac7E>WavnxPu{_x5F))3H=SXZkLnYKspf@?*DS}&3>f4(NZsSb~lYd^<%T!B8I_H2x>h6L6>KtkteYMP~uw=c) z?+d6i>%V~=#a-MJ_5U_Q$Z|@ecbeWG`CYG>Rz`!<0 z|D_>LL$oVW1^>LaHK<5klKbs#DU#Z*NOG8DtKuGMaivUZN#1`dckOMBNJDJ0EfH3w z9%(4B|6N#8gEK8^M4YByJ`K!VP-WVap)5p$ZBg(u!{{vr(Zq`kYgKZ z+iA~eAM-$-vH5rdUZxOwf^tX=@Ba{{VIURhC2#$Y`aL-fi5Vgy;*Vi!wSzHf-`Jp5 z>z%Kgj#1$`bJMU5dbx?iIY{m96;3Q~n0&5YS>?4w@w(zwtb$%%(rbn|`^tgiSd9^Q z1hj3z(F+^1dewJR7OZ-$z#kX_xZB5AIuQ+qsTG5iOPIcd7=0w?#bc9fAt{rjkwR7r zbjp*INUGUH>vP02yXeSde?{j{q?52nYdZquJ48ssh>`q&beB9IbfKG0oa6!5fF+c@*`XLJ|E3z|BS+ z9@lOJJ9ulG@S9nZeSd~AjQeqXN>a3cGL!utq2PA*4@lo46xh!0L7Ei-U8C-+r!Kp&@sPEzF3EBmB#X|;bT7O7NovuWMyE(N&y1Bf+N-JZYCsVAs>VuGK~Mn*lyd*oR8^PR^n!4{ zE~7}aMI@Tc)@A*%y)JvAOP_CnsNVDl-QoW>Wpx${H=YQMKMSstdO~zpEEkEsGMOfv zjh5D%Tt66RCYippw(!auHXN`zwyMQB1df042q&ET{QWkcV2oOfFh)Jb4kD)5jnTl~ R804^z1HN}?I4_OTveg4@pf6wzUdX@iw_O+a|kpcT@Gn z;fX)N8F}=<@qq`O(Za}#Q{l}&#lOLEJa>1SZqiW-Gr1poa?bDk&bjBF`{zHu{SM$1 zID=8K8-~ZNWzjG!;eBa4s|H^)L(#mb)Qnl~33pbwYqsTz6c~fP!q>S`CoALD(u$~f zDa05~y%=iVcO+4_@e&Nl3#Mgyml(3SvJc(#%(^kn*NS5c4Cgw87XlsM{ha5R)^af- zxn?()>Y{kbH$76eon>Q1cuNj9E!W_dWqaH+DH3DG_GX&(dXXXF$Ou(*GaOpAy}D@` zE9(uzv^?Qhyl#{%>BTiGZVG!C`YSb2Sq;r^bB8wsDKd`%=tC=pt$?}Wy+;(IXp}H(QU&hdz8|(1FaJEyn z%M>$c#D(fYSl&q@Sh3f>W|-_auj1G%Fs^lg?2^&TyOP;gHOlZ|hO`V|q$)n=&AP|% zAotS5J5pPU6lQQ!ULhH24C8gl@F*RmX9sn}(6@DH(Tl*aZ~JZrnv+elUKLIX;~Mg^ z#`@Z-Bj*?YMHR>C9`dy{VO1H9M@xCDa3is_As5pyP>|qM18tVAkpF{|Z~2zAJjF1S zyBbzzB%qMIuXsi(o=qc%F}Zu2vQgu1?W$cB{zWRKQN#s#kuFjw;)^D)({<^OhO$NI zF2l(GwFc@7Ty_z1!5wj3LcQ>Qx3JTg#8r8}uTg8Uy_)O|*+}w2MTXPu)w0VHrz>0z zyVSJsc$Isccq)zc81-}}r=*;~z*b5Al_|dpw5!tb{Iz-igr*EA{@A>)Df$SF9vVqa zQEBMinlGlwUsVz->az#xz~=o4rNAclXS2F8*prQCl|tN~6NT8b?~{Zu)vtGT{&VyB zgV@xtt_>;$0_anfoyMfXruORHq>`Rc^*w{}0aoa_`TX11RF|G1sZM)F`(E0|<$m_y zP86b`66L|Tn$p#0KPIy>;t7T`yST$UpC!sDB`_7fh}0Rj-YfUSDs_ggIdj?KUelq< zJ22a{JhLH6*1GAMR5e$&9~tV2L3w7%rX^;YjV0mC%gT}!VOMy4fjg%B4uvC~Sgq%! z4Rp_($1AJ!K89x6oZWOP;)W@K_lFn@JJ{ePHIX>Qm86lCFAIHtMAf0-ChpKGKF+q*d;u9~S!Q z!Y3q4QEmE;dT{_lp&Nr_lyxM=f5E^{wAsc+h@B1;LhOL~!8Yc-IMf0>(h10Xg1Z5s zhD(W(!Jj2|GN4Ql{8K1*@{e&u@(bXCcr&edGi~v{(Z((syWWKnyh$L^%Te+Xr?qz| z{>^t#Wd_RQ`3;N)eme67<4i3qT9s>TR+vs0T)9q4!Ib1 QaoLyO@aLjG=UgoO3uR5Ho&W#< diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi$DefaultImpls.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi$DefaultImpls.class deleted file mode 100644 index d04556229da3730c7b12e5b64a23beace726b781..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1217 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 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechResponse.class deleted file mode 100644 index d1c95764c86c006b54900ccfca1b90667dfc0b13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1011 zcmb7DO-~y!5Ph>DyCHm(6bhlG&_hl=WCKE6Dz&PRfJgzPH0>oPv$0yUi5=}YX}LxG zn*Ih3Rm8DBs;W1D2;#tU@Ob>JH}gE6`TOtc4}e{~BkV?|7h0je%U-CZ{b`ao6n%9I zwPf@oY)x)cw<)=AG|K=)SUML&5q8~WczSj&BO4$`s6HL<$lCsWq&%V2h>h(k9Ue<7 zI>L$%1g|&DIgOMWKQ$mw?C@VIJ=L7o*(My0NAHVX*~_%Xqr$K}9_@HFy1t}>mz&A^ zL1Fg#w_i-d?GW}FPs@K?4kx#kfjIqcN7>Fm**8k_U9B7^i@pb(N_uj{kIzjuAy!Uq zIwGUmrm%8a?WZhB2T3Azr0$88{Tt}QWeqb#IF#m@75d6DX_;6)C~cPSn2@HD1-BDJ zn7>208e2l~iy0)5JWyRpSZ@xrRXzDlu{!I@&srNRta5|oySBrO>uf)5aOZDM>|1QP zz2Wcxc@)~n)iGN~v5q;+I~NOeEaF8Q#8^V9jXB0k#xi4t@rtp^SYwnK>x|coH;j${ Dj$<&F diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$Companion.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$Companion.class deleted file mode 100644 index cb7612c0ce4705e9cfcaf08dc45dcde985a7fb65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 947 zcmbtSTTc@~6#izmY|Bz%xrhbtiV7yOfWF}+SYy(p)dWgoLX4SiM>_59EZLn_9(m%2 z_~ffkCO#O_M}L&@%z~H1C-voAzVCc}j#YNAF$k^X*CBS>$VlSEJ*m zMW~LTF@oIRpwS1HUdTHe%(gDJMJ9LCh+!fWVOKW9;nuz+{A|<=_y1uEMO={RW?5Z%QdZKT8%`ON!kx@CmE!n ze6FluVSAvBie*b>iWFN~Cq@`rzzpj&(}MkI^4zW&J^Kb_f&F>%V|PEH^f9MzlC?Cj zizTw<(*lgc1AIo}2|}#+9(hjQipy5aa>4v5cq*_o&S8=`2e&Bma?w>>!vbzo&T$=g x$X5xUqFR3sP7TvF%+xT8ISO&UhIw4r1Itjs!X8Q)ybOyO+zc1<;8Jcce+NVc=SKhl diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2$1.class deleted file mode 100644 index 220689c5867017d1c2f0884b668a71c94ea7e085..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4259 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 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2$1.class deleted file mode 100644 index 45476865627deab254d36fe514d0b1a6bf75c805..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3496 zcmcImYgZdZ7=9*PQbGy=+e%w)V;c(u%9h>*#8L=0He4i7i)ia+8DL?v8+SL*dOQ8> zpYV&v4}Pj1d(obw96$P-JRYA3OSmKz$~h)yXWp52=Dp4PJ~My)^V{zLF5?@4n}(Cu zeBEA@dCivon&Yl$dch2)rlql@O_#We%u09FG-Sdkx~{YhQ+kPuiQX`Tz>Ca^<6EY! z8IJ1|ebbhnw!E6xOxu^Pty@~Q=oO?rpV9NVdA)ygwOX=Jv<=^MY)=~vsNR81{x)gD zj!m&eT`e6rpV`~);j)!e(koED>>upUEbFVfX3-^WBDX9JpK}7;uVQ={wF0Mt9WQYp zjCz6k0n;}9L4ms+`!Kg6%;g}xBnbVTcLcusUqtUKz7+=V$_qwTSy=BcnO@?8z?B09 zA&N#s8xX=_fka1okHnzHC=Q`Hf;z+mYL#w*k%MU7?TtV~!j*%~s7%Gq$|-@KDzbM})`-j%7ssTp&+ERMW zs$t>xkudo&GJ?j7JU?Rt3VQrfvG)W}&_gH)rd0j+R7l+wBpy-5aTm2^>E~SCWJ&9` z?f9jd^|bM#W$8If(#Es@waoF=O0MdxEKA~vOSz`uY3VX~+YjTMz_CT?4^K=@%uJ`p zQ}?qo*~!%SNNR-9=%^CeC_2y?K|7KHb+k)b3`07KZk$)?)+5jys3&|BEng{N7B~4z zuCq2_kLspXbfv)2Y6rrM}KZ@&kCxUA#SvIlmB{&qufB0#Fw}_m za8uy)7PO}=OD^hGmYFA04-8pQWf}%kK{IsAiu+Ey-Q}s~WqUj)7aUi{+c)a7J-%l8 zOYyR+hcLS9h_fmI7XzTV6+sGVfiR&#C5b?5wHl6XnYMB3gfT{BG+apoS$X?#y|NT` z#71CxFCha-0`eX)E?>MeytxSA3QQX!EQcwYyAdIQ{GOT)d_7k^;t5PD6L^<222oJf zg>hHlPu6K^?+Jtoy-`eKNDXy?md!2O&@+@@ zRTwE$Q?D8j7FHBd!~G9TMG*!<^S#WA^+)xQ^+%CVTPU{v=tSs(xb^3^Hx%OLy4%7aDV#;X)owFg=lpYtLdcIKIAtr>^;e@Aysg~QXT;kA}E7j4}y zf!Se}jIlDN7Xn^$hA+ACVm>F`VCLjhk?*j)OxvrbXHvk>7Qz$$cXKCoSIHa_ zrd|vj)$wEYlH-vYL=FEyG}-k8P08BsXK49}V*#ISe1nHpyv7k%;RrubqK@JiWfVz= z5J8JdC$3g9zejCp-{f<2jU|79_9G|x8{Fth``bug?|P0ae{vn~CTpKS`xV0> z%y$2PNcUsK?|S?KsoB=|pW%Z)xTglIY@1N~H{xN8pB`ZYtZ}Zjq_qzT^ifkE0jRI! zgR(M#Gl36^x{VBNc)&gN>=W!C@(VMe@ivJTu(BvjxpJMo3SsO8LbJ-$u4l;7)W@9L z2$2#`emO)jsKAIIFdvrHD9O9ju;KzW*C<4ZX-;-M#YgK1{ftka;{I+e&1Egk_-vb& znX;B0&TtSLVqLe<9OX=Jprs+%MHikTw_AQgS$+fMt02|k3vO%;HiDvDj#d99+ggt9 z1)kR6E6#*)5?`~QBXykVcf#Yu8*_X?;7y#GBj>bg;;MPeW6qrMa25vj%+H}F4VlJ5 Z8jDy;!^CnLE3mlPLmoCodT=}x{soRv*7^Vd diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2.class deleted file mode 100644 index dea39bd38a9cb052d0f7fa62c0a994ad8507f327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4034 zcmcImYgZdZ7=9)M(x!o?Xtfuu(WZq4Wa<5ey#dwO6eWbUY%b7aExXe4Y=2g`9vp2s#8hhD_V^&j`HP`xpv0MQbBhojx!v z@ZJByx-kmtc(_U726)l0O1}<&!Ss@Y0!N?71u<+vY%@aW6iD`_tN2?7Jd_y2CTxwO z9dUuMk}YuINhCMgBCt8>N<*3@=?ZK~5|;%_D!d9LHd0EU)3i&@k_>W5V1I8j3=cGK z2@JI0-H@ImGFP0xEPZ`e_jLkU%n3Z*6qtb}pGQ_3YsV=rOn&r>pzC!}NO$IA+80|HwE@s)a`gb=}9{(|e=9Jep(rd4#M!1h*QBmxiNa1;k| zgwU(2Wlxkv$%7FbQ<&Zy#c`b2sP2%O0j}1jb+ROaAv(BDv$a)rtkO=VeADuj?W|y3 z1?kQ?Zhk`EC`ylLn(VDYvvmbLxN-Gr27Cgo|s!mQ|pd4+_U?QH&xj z5Mi>bMkCPOtfWAERkcbFiQqCTo#9F9OqO`LW?q*8T?n#xR4oH{&h56T`FEq3&N7 z2o(;h!dT(*DE}(bLjosSL7~aBEtD2`aYG>y$QULurF5GY*jClQuFHh-CDluW8amVf z7SJWNXrqR=OtoUc49(K4o|WaU=*n_5v?Emg;w#I$Hg$))LxZ6~kqQmAz4rUJ=M*_j z+cKovj(t1t6g`%mz4ak$9aUz=v3ja?%500k3LLI0g6rIC>Ut5}5{Rxoqy>&t@!R<7 zU47HGf_)6{;{!fXhMievS$D=^C7ae=Q+3HnHSo!iHRo;JFSnXb7C&R@vR(+zwr2QL zA1mf_(hYWYPZTLJFVl9(^h`21zuJGHUYCE1Gk59M`A&PA(pGd8E{(x}# z-t+_XT~7TB?FVl1Z=^Dn@9*IHOy2_>eTd`NNYf?mwfh# zmf6$qsCU#GHGxWo64)6Eq*ui<_iJJ+v5BYp`tRY*6@-4myZ7+kqr~dSk)IN4)Wv7I zCbnv4S*+5bYPl4Y<7Q!Gk}8u!o|Dl82ifa(@GvFN|RT diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2$1.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2$1.class deleted file mode 100644 index 569739a3287acc2182e8c3f383933a17a1b813df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3695 zcmcguX;&Ln7=CV8QbNimt=(*+jRgWFrK<{7S=+HJm4qsy)?v5}1CyCJGYPFL>NkIg z$K$8!>83qLIezpvc|1Nh8A2dnXg@gT+-2^4@4Maiect=mKfnDB;3B>g7&n}3(%0>b z%qDH=uQ~2&QqP&eQd%0z$#ij7O;ge>n1+mJq(A3LH|?xS+l!x%4}>8Eo=&bhzGd1; z!*QLwZ`#sJt`xFK)Aprn>sB(A_j1x+n$)w4OZwm@wIaEbw+-KPY%e(x%m#)w_ba%K zIyTwnbwxUKcCxnWTUyRaFGv1z@ao{?ieAu@7OhIoEUrky=bAv@vnU@%y}-%f#2yBO z(J0V3WZI^GRbZuiAL>?lx>DDa@0xa|?6y7JGT3uV;KBbSY+rX!WmI+e@B))sFvx|;M&}4BFS2Y#31IsDB82n@ z0Mk!^36>P9^jC4M_7g8^(s47%73nX!x=EnWZQJpSNZ=)>^OmJAT9Rs>{x34eSCpjc zsZMsH6!Xh74KJycr#JjC5(0+_c%w73GjnNedi-{3E_HK!dTe}*4(Z;-xl#1tYy`dN z7igef(xNA{D9&L(1>Jdp)<8T#DR22o$VGt`f7x}`X6y;wwDPVLIJDbcG8Ql6N(7hi zn!ttH2w3IYFy5d7x?xDqQ+^)Knem*!^7BDP&oiX~Wer9#gf}A?RPnNjpnG6h7;g)( zn(V(oQ9u|YOp9$5N?@YWmHP$K>a>Ys4CBoh#e~4Attz5fmdxl@is2~7?;0|v(l?Cj zN_pL~V!jjWa(UHR*%e!qcN|y7x=MN56zsa9GG<6WYw;YunfFw(tC?pK!9!Jb+K0_j>zh7ttz8hpw%evU>?fQV&MP3vsq?P6i1o0rdl~G}QByd2RPNj#Z zN5>hMn;t1z9K~&X(u9wpM{yh{)OjLiC^rzr`94%tLvz<#1QS6beK7}bk|g{o>md!vLp`W=ZaF2bWM1aG1k$)t`5i%6TnPM3# z&W_?3S$)QN2ocgS(D<)J{78{Xo%9nVrxFiv;V1s^8z~J%PDeN&>V1OOx!>{xZ*k~I zY+yK1{}2~`#kD%j_x*rK-+e{Xd;cl4`HmZpG5rV6)!}pYEvWw+u`p&&kFf*3;93VY zuftWu*+$qjHf7Oa>KMnFz#=u9!AIT znpf&7U3(wnJ*s=3>m{ct5u_d{CCM%^?1?4KwpkP#tB^Kp>?l(MuB^A+wWzQ<;Ag`71{?;Aqae=d!7@a3QF2ol2Uct+FWq~`VRTo#?s~%(L ljE62PQ;xZSIt?otRyA12YOvvG$l+5S_Ta)JOAo$>{J#fq7AOD! diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2.class deleted file mode 100644 index 430737ec3c7afa5e4019fd9bb64dfa7a4ebfa8ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4178 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 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2.class deleted file mode 100644 index df5b42831bbcbf273e44c5960ee26631cff03977..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4317 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 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2.class deleted file mode 100644 index 61f52d39ad78ea4a670679fd7712b37321a1fcaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4295 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 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService.class deleted file mode 100644 index 3b0642892489cddd30b86ff72de4f43cd3a218fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9271 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 diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TokenResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TokenResponse.class deleted file mode 100644 index fa2d929554a6e7de79a96a46fd8ca483fa9d93c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5535 zcmcgv>r)%o75`nK$0B62637Pp0Aq?kpvVtw$AE3Xj$=7MTA22^&h%41^iSxY(P{g;cNb{^6+4}@%&_;I zv*(;Wk9&Ui>M#HN!yf?*;U@ywe0eGB7^OLLDO)m~+vUn))>yVY(KRzapS@OIG)pt4 zy<9HYW&}b&UocjUY>`&kmuDBuyc0o#z|b?eQXi6DYyLt4;Zdt(IcEiy`zC9yuR0a0 zG&k~TNpjmA^()Mo^=f|<9LjG`?daxtXP zCq4EHH0R4q+{ioQg+j%&?HC4Jqd~^)%v&@#I{6Gc$iEp`@eloF`rq%E++@9$UNCPN zD@Er#_l#3n$vfrBlu=nUD)|XRkmK;V?V~+Wm<9=qy&z$fxC;iMR zwlH5_z9aC9|8G0`|BD?Hlaclm+?EOaz3&tI*xtNPOHv1;{2~tqK6%zLoW}*Z3t!}f zI%RiG1omzP+ldvc$a#<8auhE~tcY&{|30g5M#B{0#aLc8OWdTHI{n_hxjnZdF}#c` z((`AxSXLAJ4*@C5tJ3ls=VY*}3bPD{7#<(&+Kf^ZzshN$B8P>{=Q%KgT`ne){Q_r3 znz$oF!IuQ~_vLmD{5AP`gOfGbwFS!PATJ%9-`RmF9o&i`hapMLvla7(J%7Gj;5_?| zRJS`3EXJ^a;V6n&Vv4oO@BlIAWRuDqJ4Q{IUBK&6EQ7~JnAl1vB4NJ1qF^}bZ_!4f zSrE;rm@i13r=B%cqZn@E4SCjZ(nDouUY=9_gTzy0v>PxvWjaQ|a10`KU}?31w+k8qjw~16qiy#A-!NcbsK+#hsDf2eFIXj2 z;VMc6tSA+-()5DnXT;?e{Qw;wJf`WL>T_L}KE5wQKja2-Uy$x5%{1zP3|l< zPg*5&dSz+WtXz}VKe@@vd82sUs92Ksggsk*Rc~*bobI~n82QC1W7#u{UM;Uw^5#WL zI&SxTT;CQAjuNhe%q7gLTS8vVNPkz(4%bGm33*BL$)ZZe+nG=1>hYfDlev1lv2By_ zvZj3SV&;1%eI&_8pfRHFk^3uGjqBK8BK8Q0_F$q_E}_I8F6oD8PY@TTA5eG3{WhLMmK>w-Y9lFGk${rPI!IUlbos-x2wW(BtvJNXw}hSMzg8q;;F`G>Kdyy z?_}&2@@!pW)!Lnm-FBX!DAyxS0)Vhh#+;$utQy1hWvqOwyfGF0e_udpfD< z8ecS$=@|}^(><;)Y9iOZiQg?ve5@x^%CkzwFZKb<8FTE}fyi z-oK8yby&3?xma4*f~B&$Lf*)|g8hxL$+PB;jOWjYH2fM-H?-2|KD~3EG(#)Bw!w7Q zbQoq$&Nyof1FbRXN3g5rnVLDHmP#%*$?9bgZn`M`h5ZOb5Fl`n4tsP!h})Z41tfbI zdkb#C;a-vzzT;&pu!2>-lbierS9ylMn;!TTLcs?Ee?s8r2sJzyc#Oc5%yp3ekMI>G zl=+hH^zq%~yM25Q`5qtNOTO2~r^%;3lwY8;44KTv%U~^89gPv~A82K!&HRfsSMy<{ z)wy~XZ{n-WOwL0@La9Wm0Dh#N@8ic@e@f}scp-S}CK_^hJBPbDd>!wQ#5Zzu_05|Q zHtylQo9MBTwec+*9X7sgqsc~>jTRe88!06ju%X!)u`y)hw2fgKCv6e$l3VLzX5K+e0~4` diff --git a/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/UserTokensResponse.class b/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/UserTokensResponse.class deleted file mode 100644 index 865dd59e7408b6ec53f15ea7633de89505a96b9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4877 zcmbVP+fy6Y8ULM?gcc#pB7?cP7-|}Wz!6}4g}9EfW6J^J8bWKkNwbIzEC`A0u1K7= zse6f?^fGMVH+7o+zTMS@OKguGwC6kD`R=!K^q>Fz zu!J1G7iPWDOV+z)Dep})g`QK&c|~W|bk;4$&v#$Qkt^o&tam@p(^dF& zcEF%QqqVW=-I0=HpazHbx}L^x0z(*)i0cUk;bn!yisdbqgU)nh6?CGm1o3Vwi}AZH zj5yOoig|{bItc|Ws&KhXTnTqQ^-Yu56zq1Z;EmO8#%nhxYBv+iWv;k+M`5GBlnm8d z0)MDWUp--|+r*?qW?(reFNN*F`h2ZxGS)JM6QNE&l9gMQ zSOUwiWSUc0gJZdzX#}kIN@kw3w!JpSWtVT!0@JZB3(a${kqT21>&VMBvcc=2=&i~X zRC_&ygfi=nN#VSwFuiLe*3*X!QF(sPTAsOVdM2*|ZDT9KhmDd?gM7dnY@PTIY}t(x zS{_sC{*$}o39VD}e^2fvG(%7L%YlTZ6KnJZr#kmWQ*b2bdPIMEPwynRHmWY3PU*U4 zXx-XqB$Xf(?T&V;(U=c6gzy34#V9k{_miR!H$DWjaF=yr%O~;mfh#c50xAI)up`dx*Gjr>+ z=4Qy2m@k%`ob{S5g1bT)i+e1?7~3k!STyjPlGhqbJtfaE;(kH6fosCY@kO==$A=jC zKjygxw?f~&lVzhK`zq}dg zVqj_`J|(U_%1_{3`Af?UvHKYDV)uts66ZSJ9*chTJH#SW?Xj2uYPLNV_kp?gn4uHW zALIDBXu4oS@8;Xrv3`Sc+>Go$KLxlnxGZ9m83) zVw7uOM~17o7EAvf-46(8zBow^C`#gy3<$z_v|=15NF`3xGWY&<7xSrom}~RiN^ABL z)W2KM>E{YMiQ%vuYAqVrE$GZXf`YW2+EHPxj9EE8M|a0Z?IWa>!7aN|%izakKZc!g zIJ+;iABX2NpR4#@tJ(K`Kj!`Y<@1RpEGfOks*-+c3;XE{XgAB1C-Dqq1})N7X_2-{ zi?mf*q)q;5`7P46qeWiX5$k8POC!$X+f*oKErjjDE|GT8rCP&|5x6&O8iD)EebdOz zK)G?Y@oHdx*f`0ire@0KGbgO&x4BBqU-$x-Za%{0)Rk?#E^4+hm%1Lnz1x^iy&1s$ z0)IDv2M8NO+qkuj?^lBq^v#7ZNad8v2vhPF3O)OrdS#+q5nYe5G}U7qStZ>2Z^R>i zMnb@Cy!%cW-(SVkgesmktdJ;ZVR15z=vPdO7Kg?bS6?x)0jw!?edpFM=|eO_XTVoE zsU;9sLc?e?lz!VEA9qe$V0cVE75NS@B?mzt06Dc5~DF7zLk# z`+n~GYxV=&50v-Os^!gSi0l7FD=lwnX{i_|RZyPE3n*ffW=i?QWrE2~BJ;J4U$Hbz zSiDH4BT6ZwMLmKele4xblA$;azv798pX1kDKPHXGax2|NB#W&q-p}F#+#!e$v-lx? zbQ{XWkMYrM^tyP-#ZO#xxVY=0$NwI2k@SI|y3k#WyEx%u!o^7!!!AZ$jJY`N;*5*4 VF3!1_bYc7SMgRMji;FIP_J7b}Q4RnA diff --git a/network/build/classes/kotlin/main/com/tangem/network/common/AddHeaderInterceptor.class b/network/build/classes/kotlin/main/com/tangem/network/common/AddHeaderInterceptor.class deleted file mode 100644 index 3cf5a9dacfcf9db59ce171e51b31e3c4d5e9255b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2923 zcmb_eYf}?f7=F$MvdI!P21SBZQDQ|dh6JQGSgjXSB%lS0R@>4gIlz)+7qc7CUK#&I zzxGS}v7Kpc>4y&Er%r!V?Q=F40_cp+bdue3&U@bL^Sxd;v*YG6MO3Iux9T1UHq z790{dunU0*+Rzb(h9d$k-m>Ki3}xRgB@8j_hoUkhknGLAA`yve%YV~6ZSjZOu_7->Tq=UK7ay6gKo-cvPYy3_Ir9bkeVM({o|ZMXn}*<$xTN?l6TofF?1aR)ib%&5cFU6VCgsxzt|{KDxW1#`svV)@1|uQkoLQ{M z2yRtbCj^e{!o3cuj!Bk@S?_3p%gx37+CJNpicF*uOs*gT zn#HUfZYqifyv&Xvi?YP(Nmy$F5gJ-d)a(%DgzCTg+UFRlx?--b7N5ScBRpiPni@{d zSJERzv$T*mIm^?}cvBntz0-h|6}@|pZXa#XcvRJo;#x}!oZlJj8y=v_QtdFDlAf72 zJ(HHS(prF5gHW%adV#1GNPl9f)evVYfM;79rwpxA^PjB^LyIQ(>^HQ~Hi^4i8=;QQ zt&OxcB(BC|v8dLyKh_e{hFYrcp}>nDf?Blob~GC7(uVYOFdE_*zSS|)wU_t7i)Ss0 zpITr16f~44X_e`E#Wz92K9tFaM^ux5d#=E$3C~(fw&_*Ml910-Y|kpm z346_Qt%ahzvVFO*%da_kG9AcTww$h%7G(LJNgm&3&MeNEWlODV#Kdl0jZ0;R(tWd@ znJchdYhuQ%Q{lCH!%~V5RTtOgLSc zYRF@aqoN!Bn*E9p0$ArN%YBVwv_BU41F^o}ak#JlH+25ub3WmFKRJU4Q`X=`7sgZ4 zly1r(jxLUBrtFEDy)eEcO=l(f1_>_SshSg6juCFI_s5RYT>mB#0?*MmJ(y_;jJEdu ziq1?hFsjFco2vLWF)EM=#Y3C8DDXW-)a*lnEnLlny|L7~pdO$UudjrmsN) z(80h@@Ym1hHiOOMAeJd*5$F}}7dYA+9USuES!CR|8TA8vLSSyrJ2eGMMI zVP|yCPF=Zc>?(g_(*H6t`ewW9vl~W#cdC0g-`jniP(`|Xo<*%%%%~kWIj30UXQTh2 zrnV-%`FWpb(lh$9Mye+@Fg%n=8R=B2Cq0nP45zl;@;kuj`(J!hw$AsYsDB`B^o<+% zmMCQjq$KEzj}Vx^(gc<#nAjCgQJ6pxrAH7h)9XA!n~N?NtFD?n_WvOlM_rU%9CML# G;r<8dVe%RP diff --git a/network/build/classes/kotlin/main/com/tangem/network/common/CacheControlHttpInterceptor.class b/network/build/classes/kotlin/main/com/tangem/network/common/CacheControlHttpInterceptor.class deleted file mode 100644 index 6b465c1bce1d40cbd7c95e0b511da3d9588d251b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1114 zcmaJ=TTc@~6#ixkZCwkLs#rkrE(L`xH$e*~NMdL#LV)yzr(rvmp?hg|rpoI*E z7$zElc9m}*w>?{#B7ET*v{3nxa-tN58LY^N^&PhrF{~7N5cj%SeYjKhuxOcq9(K;Kkl^?w3*BtNppaF8>Bx` zMk>-@ZH{5?>@;WbJnw#IrxwqqEuQB2v(tHAWN!v?$t)k^#l-oiB+vGpe@^zBzuXttrLH5)-qlA)q}`M%{H%5YyCx}?cf0!O%25h^3cN^37@ z(Gz;BjK}D1%h$@2RTU|6S@-=wi|#Zr2Tq-j>!(2^iN#A2cS+0X9+}RibJlnV*$zfJ zxYNPIuXyr>EF7=s9E53Co}xxEM0d%={Eoy^Op;{e0;b4=W0fS16|B*{Mw)5L`0N0Q mGM<+)Q^qW8`bDu!yyg$UA}nC>0LciG5tbqpBD{!zT!&yByaj^v*|d?3>o_hIdPamieaqo zD@S@(t>s!WBI|L-A_f#Zly;beL3!;1Vb|kGB_42BkRij=Wjfh9kmtm4wNlDW75C_7ri_Y%vZ{;xbrHqYwQC?qPso zD88_L$02&{l5FrsJI5eHr^_2#pQsAcS0ZH3U(r<3vB;Vt4&#A=A&fAXF{6viY&nfi zj2Y;_Lxv7@AVY?w;?0e|ZAhq3=Km#R=>5xYLnvP5ij$${wh}axEZX#F0|ToQekda? zM^ZJ(4Q)_suhU~g%XAxBN*g>qp4W2heV3_cv|%lmIQyb&nU1rsdS~P~&yc(2 z4c84TQ6dJ(_)Ab%FAZ`yS65V=Rlh3eq9{GF*>o!+*x?n25VPpp+$r-wMtTfRer$S5 zx}xBH$1I}P<`%U^{A7m70DE29zdt5R^9vZbME)#W3Cw+m`5)vcg}^?dIx+QFP;oFvO-! z%XDWLB16N=Dq0Xz5Jj9J>aLrdVI+UtOQslF*9 zR&^Ii`*hxR*0c@7U3GNR;+k$*wyV3QP3uM5U943qB&8f&FgVVTeTxfJw4);d2}y=_ z$H-CGIg1l~MT6ngP~L;5=9(2PZ*q4kv@htJQ^U&)j~a!hv%XXa)Tx;U&FPTHTt(-6 zdSqtG_nSYe>5fs`)T$y|n!sbWq;FEfgv^Y0r>y%YTt+9uyJg$0n3lG&Rn<(31TDRy z?Yi-Yv~ePJjZ(}`o5 zrog8#t>6=US`T-~^K@9oWrp4`xvxLjbPUcZQ_Fg#W~i9;iF=jg4nuk+_&BaH^mue? zikv8+FwTc0+2AdW9ov_e&|j4!Du zri;|y=s^;$(<>w&bbA#mP(=6sn&Eb19}ZW%sGeNDS)aV-_Y&d*cb%Fjp@Qy|4ae(w zE%;X8_JE4^;PHC3DV98 z?K=a>7Mi0Iv1CNdSFYZB`K+DpRLF0+Co0F1@t42G=v|RY6HLS%pXP>p_A>ot=zjxi zQ=vxdJ$;1aK(o3Fy?i7R54t<0!lP-ufX|hw>Gs~KS*}^7_j}9a=4!>5J^Y(M|61nk z5c5xvP^5_3FG`)GN!nV~Txnm{>B`EZwa+Brw=$xBheQ_Z4{f)?Y7Yz7{!3AW3zMb}* z??yicN&2zp*hxI0xlN)^0vK;hCh!41q_v2pK!#dq?(L8M^fTgn7|r*$_^XM+NOp7w zlfD<*Bou{2O+x^c@sv^oSeoETAxS}t@J?bOS6H0EpX>xDXO-%--$c?)@xJRBeTI&m0 zQ<_+lalg*jaaxIA_X|k(ck$J8d?Sd$Sd8uByMQXJ9+xNp3Ez`~1P4FR%*h>{lD4{n zNDjpuN;w#KNK35cunuztjAH}k6?AbZ92JfjN0p<4V}+xYL*;1axWHj?T;!PNxXF?B SBs;ya-y3a?O^#a}kNyX;NlB>y diff --git a/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError$Companion.class b/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError$Companion.class deleted file mode 100644 index 861fcc13fca6e79ec265eaa5b4d6255ba6c41fec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 814 zcmb7CO>Yx16dZfgY`TOtZJ{K576?^?L^p|$I20j404Y-1N`P|70Xc~QC%ZQCI)P(O z{18rDIU#XCRgU~9#B);MN-ytuW<0ic-v0dc{Re>i*kz~>Od30(-^w)B(tR*?921oq z9Um>>Gi!~lwoLk7Xk~N=#!woGsfZJ@#mD`T9JmkxL;j)C$~|HT_G;ZCR*;X7L&%VG z?^MRn?40$o1BS+4XKY-ebUd1-vC>XjEt2@Dd?O}_YZ;w6I~h1*4}~2|dr<2};QrFB z>foRFI$fv?`)50iVdIbCP&zRbPSBfpIt?g5=1bleFt9Q4AC*USS`8ZvwUb#f;$

    ho}O3R~3+L!i~=qJQ%cFaH|U162Sr)06MwY057WSOLyMK8_74&-y? z4V4!Y?NlneDpTZmthI5%(NB?~L80e-64bdLJ$=g#q1Z=5edX>q6uvIxn^e6FY~dEw z;&KC45dkGe=5xgCkaCYYr|!)~Z)OX__L5v0yo~c$BQ3yfLeau>4cAdYl`zK*+@bz} q_;uP_>LF-jqm9irJ>UW@aj}hUTkD5KItE<=>za)x|{E58A6|G#Mf diff --git a/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError.class b/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError.class deleted file mode 100644 index 3465f18cfd11922708974c14043b05928c1bb7b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2084 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 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len deleted file mode 100644 index 131e265740f37d77b7c4a3676d2a7704ca3e4a29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz0D%Su009U9fdBvi diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab deleted file mode 100644 index eb5a2384d5dec9ba46a36a89fb22e1277336a203..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIu!DoVT9LMp`YSk?}?JwAQl1?4Eb{U;JblaHC#wsErB4Qa4rOaj#@h~E$h=|d2 z9XszY*bW`*{a6p3cIo>4@;tBK^ZPv>o_FVL92Rrp-n=>bTRa^|WtMBk-PwJIBe|D1 z?pbpCu)FV_|L2nL$9Q3ZS$sza=lFv&jPTJZ=5T@zzTr2H@Yp8}F@>*KM+e6^z+dd+ zsbA`02A{Ekk2u2)uCRr%L#c`9*uXYE;3wW=ARyH+j#a$E3U;xKA6P;Kr6MMR=^5a0V`kytbi4;0#?8ZSOF`rcYzIk?3wid diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.keystream deleted file mode 100644 index 29ec4d876b5fb36c0ffdb6b7b276af1096fdc7f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeH{O;5r=5QhCV#*3>4zoQ4NMhL+ez!;BfJ83txJ9T%8;orNoi!C3OEeB8KMl(;} z>9aH2@iAeG@zn)HK93$--s6C^h-uPCxXz4JH*vgwyi$iatau`@SJ zrzkZ%DTV6Vji~A=XeYU=UJEccbL^{^o!Ct8jnl~6T4E8Q(TN2Hq1SRVRK@WHUsxJwY4=JI#s4w@pAAyIG6vm4cs!-c* zJsaY26})DjvG4~lU&D^-!-l9MVZvshOmBaDgdO-H23ZV4(3*#ESpQyIo*&++TUqJy Wp#8N5S_7?t)k^lez diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.len deleted file mode 100644 index c580238d5e233f185fa5233d33337e20926743bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00G7Y00620q5uE@ diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab.values.at deleted file mode 100644 index ab20ff3fa5699928edc718640a36f7571f98b494..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmdOA@JLNeNi9+cN=?o$N>OmjFH#6dEh^3|E=kQR@klJr@J%cTOUx-v4KB$qN=#2> SWMCi#F_NMfZZc38NFxAB2ofs* diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-attributes.tab_i deleted file mode 100644 index 7551abea53a1eec44ad6fcd091ce32cadb636302..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI%O-L0{7{>9b2t*{rB3gvhMIt5Wi=sfWD@s905iN=qCaHuFbP-8%(ctR3x`GQ` zs3>kEf`W13QralADJoE~lnApB2_liWP>+k*=qyCe;GEGg)6;hG}?2)HA#peWh3=e=Q~OM1O`&t_`EHAe&x zKmY**5I_I{1Q6IPu%=Q$=IYbUJ5=Hj=+N(!sb{Tiar90?&wk|_3vz#xFbgvv5DFaA z^DfB*srAb$K6#<8xa(+y1kDDMC>` zchvqQBtQTG1Q0*~0R#|0009ILKmdW=E^u=F@8RY3Z+E)``4K<>0apcX>3#lpzUs}! zeEll;>CW`VI_qeGD{|~BtZRzijla77kJPT_rkkd&bXR!}TxeV|)sH@A1cudSZZ2JA zrW;)lKmdXG1fJ+~%0s($F_qZ|C$(jQ|1&AbDt@%-q5u`UK4p;SdJW3lxaZ+~C#}G?-tYU0ec7sX%BJ+TEI?HK?^I zGr`wN>)&4zoQ4NMhL+ez!;BfJ83txJ9T%8;orNoi!C3OEeB8KMl(;} z>9aH2@iAeG@zn)HK93$--s6C^h-uPCxXz4JH*vgwyi$iatau`@SJ zrzkZ%DTV6Vji~A=XeYU=UJEccbL^{^o!Ct8jnl~6T4E8Q(TN2Hq1SRVRK@WHUsxJwY4=JI#s4w@pAAyIG6vm4cs!-c* zJsaY26})DjvG4~lU&D^-!-l9MVZvshOmBaDgdO-H23ZV4(3*#ESpQyIo*&++TUqJy Wp#8N5S_7?t)k^lez diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len deleted file mode 100644 index c580238d5e233f185fa5233d33337e20926743bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00G7Y00620q5uE@ diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at deleted file mode 100644 index d4d34d463cce189ac8152ceca7ed21b935dbf70a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3155 zcmeH}QA@)x6ovCs`mks78)Qy4v0F*&1bvWec4_0*+<22s_U}z6*u4pBHzxEk5Wd`V zI7yBaQ-l?AqEHjBl6&GLL-xi8v|$RqO2I8*v&B3RLy52AWt6GK!(?2%-ObcUS`1&r zYw20b8jO`6umh>7mH}*u_3s97MmpHnkxLsY_cA+L+&Xi!4rhhG3pmr{Ir7eEi~=rk zMj`UPbSV7Xq7Ob!K2V=T(Vws0k@eUZJ7E8v3As=mlJN8F6{stzu+6~$9Xvht+Z-Yl zi^OGa)U#OjlhfkRewv~C=r%_QXu)mo=vJKmfX_vGq9w5f)@0?(24sycHpu2jymnqD f7Txrw9b2t*{rB3gvhMIt5Wi=sfWD@s905iN=qCaHuFbP-8%(ctR3x`GQ` zs3>kEf`W13QralADJoE~lnApB2_liWP>+k*=qyCe;GEGg)6;hG}?2)HA#peWh3=e=Q~OM1O`&t_`EHAe&x zKmY**5I_I{1Q6IPu%=Q$=IYbUJ5=Hj=+N(!sb{Tiar90?&wk|_3vz#xFbgvv5DFaA z^DfB*srAb$K6#<8xa(+y1kDDMC>` zchvqQBtQTG1Q0*~0R#|0009ILKmdW=E^u=F@8RY3Z+E)``4K<>0apcX>3#lpzUs}! zeEll;>CW`VI_qeGD{|~BtZRzijla77kJPT_rkkd&bXR!}TxeV|)sH@A1cudSZZ2JA zrW;)lKmdXG1fJ+~%0s($F_qZ|C$(jQ|1&Abgw{1}OppWO0ZHe)1gDeEh0LWk*9` nGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!n$PEDi;I#^= diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream deleted file mode 100644 index d97731b79d2bf99b3e27b08a79018314ba3250a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIu!3lsc6oX+mdIv{o7buTVi?o$ebbV1=LilqE$?aRcSDUG2Rco48AG@Wad{dNf h7T@=&LE|j0g&=?c0tg_000IagfB*srAb`NH1a1QS6VCtu diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len deleted file mode 100644 index 3bb2dcc6ce3baa57676d845dd21df8491d46fd34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmZQz00R>M06YLS diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.len deleted file mode 100644 index 2a17e6e5bd9e7704741c2a3ae485eb2d2e302b87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmZQz0D}$y0FVHQ diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at deleted file mode 100644 index b548581e0aee3d76a132307dbdd82c362f2e06b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 171 zcmW-a!3u*w3`7Sh^x#Kyf1oa2d+Vim5lTx-uc-zV)RpX3{QM3kWSG1p#G_Ja)!ajy zV6D3ab9c2k#H4eoB=)r|ZyA63Ou?kn4UCL+#211H#S4b=~` hbX=ZrW~XSlTTlau$BL2)wd+TpFgG|B=i6P diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i deleted file mode 100644 index 7546f6b6f95f2e7818d7e9362f42f4c31d364fc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuu@wLi6a>NZG@?`iv6d-8{%1SbNlT9i5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly aP)K0D*R%F0v}~0C0RjXF5FkL{L*N1dUjjV< diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len deleted file mode 100644 index 131e265740f37d77b7c4a3676d2a7704ca3e4a29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz0D%Su009U9fdBvi diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab deleted file mode 100644 index 3f3eeb4eaf0a247accf8f8d2ac608ad47ba11c33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmbR3vzw0r2pB;G3gzI2PpyqWO0ZHe)1gDeEh0LWk*9` nGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!n$PEDin1c$% diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab.keystream deleted file mode 100644 index 9bc791e73fa08a438fb04841a3ef3dfbce45dc80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIu(FuSs3_w9Q${_x^L6A^Hlaw|f-QGEbcO#sf=~5caT*B(jaf|j#x-Pef-p=Pu zf?P~38kbU8t>cx|cq{6T&P(re;|?1$>`gn$&RaE&yyoDWaT<6iCSHqjyUda5m_A(Nma=crpMj{C1A#qw{@r@afQ YB;*ulHdW!;_v*+VkFi_@% diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/inline-functions.tab_i.len deleted file mode 100644 index 131e265740f37d77b7c4a3676d2a7704ca3e4a29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz0D%Su009U9fdBvi diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab deleted file mode 100644 index 9828685bd947ce55a65417383900248b3ae3d19e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIvyDNuL9LMpm`&~!|xfkUUi_xI0*hE&w z?stAJAMYoT(P%i|I?wAo=X{@Y>hnxhuEiTw<~_bE;cLFhusCgU8=e@JgwiB(*3@-EHbFa~i^7FOZ2-Wc#QY2Qb~x} zICfzXF5p2xdcZ*}2{kLgCft9KCa@n5F%J{M%=Vt8YV5^6%*G|$ev+=R6Enlj(y$u+ zkJ1qOa1qn-1UDa~+z9%y1~YIJx9+7)?7~OP#KK61FJ_2L^~pGs>CZZ#>X9 zDDPN|*Zxq57Z@)mIA=;rs*N8IjZaGl;rAm5CH~G)>7Lf+Svp7-G)d582BM$;ix|S; zQPf2^QGP2N)ewzN$OU2l1Z=|}1fnmeB7*Sp2r6Pbz^Q-8X1z|T0#Tn_TV9J@(`p6ehmxVU@8+_;gys^emEEbmv~F_L=Vy|QLo+L%`})`CGM1)_876i zGO$lwWKCFZOo!*{m5X(2AXq7<3A&lS>Ue>Rq}5SF{j9^j+89%e7eQa^%p#ueH=mZf zra)RymrpGXA{bZJ-a+J^y;(hszuWKGHVCfkjkq5?p=NF$;$bc&@|ZQCuF7kNVG9JL zdbR4RycYU1iUS<{s!&eF5}UX?e!Gqpx z)<-%+IpF4o8Dpk}e6b^VwK<{Yf=`4gIFS?^sfF5gXIZ1&A9wTC=H)Crb2DqctIVv}`jRuRidat2gyoj$p?>u6 Kdx$_g0)GJjmRleI diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len deleted file mode 100644 index 99fb428ef82a10e4c03121703cb059904928b991..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00G|j009vI{Qv*} diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len deleted file mode 100644 index 8f5a4cfb5f78c2d5abb8fb11b497d18752c86db8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00E{1003+NV*mgE diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at deleted file mode 100644 index e4085d22a405f9171eeb3072becc3c2951f9c823..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5410 zcmeI0!A`7Mg9vL=R>yBVD08b#|%Y?cG8m985Hn5)N~0()^wI z_Mc||kzx#b0Td}HkQUKBQj`KY<2+bD)~-x+J=g0EWFAQ`;YBb$8Z diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i deleted file mode 100644 index 9a163d18960e6aa9a298264ea050f9f6f9e78df4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI4ZAcVR7=<$fmjj#{?!t6ve5)lq==9X-t}t-o=IVrCP{z z7kb}-!Y8x##|y1zowfw3A(t{x^2uA(8Vpz;wPx<$PukvaT%GBjI;{Q|0wh2JdJ#B?^(^uB`$oO=_10}hgCo7} zUwZd3T7MAx<=uml(?<7YKQRe(z*a*2>r-O7lgCvv&Z~Xb_Ls`zm`+K61W14cNPq-L zfCNZ@1V}(^0(Enrvr_G&V!QO-T?cag_7Btjd%iQU7H}!+q4y3sf&|nFjN>@YFFQ1) zUdaj)AOX<{B;$N8+r8!g^>TrDK5WVVBD!z-BLNa10TLhqGZL7Zh~Iit{z}gt)b^f0D~5D_{=na3@*bU( zBP^JY!gF1bLppcVYDI?qQF z2MLe>2`o2(KFmkkYqkpeJBxz=6z6olP~+5q9PT7LuUl6GjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk0O=tB0OryToB#j- diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream deleted file mode 100644 index 2ee70c30c4c22b9def7c510db8d4e3ee269a4301..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIuu?>JQ3`9|eq4yx^*dRhCiom`ov4pZc48Q;scjf6znsF7mfC_n)UP=Epypa2CZKmiI+fC3cwlfVO+K_LGC diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.keystream.len deleted file mode 100644 index 1cf91b4131aa307448ca194b1a30bce7f17f541b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmZQz0E18f09^o9 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.len deleted file mode 100644 index 01bdaa1da7d937c7e7d98e54ba912f88ab95c7f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmZQz0D}nt0GI%g diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab.values.at deleted file mode 100644 index 6fcb00a84b3a1bca9ed654273f68daba3ae64ad7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55 zcmdOA@JLNeNi9+cN=?o$N>OmjFH#6dEh^3|E=kQR@klJr@J%cTOUx-v4KB$qN=#2> IWMBj$02BWb2mk;8 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i deleted file mode 100644 index cb322b350b06a636c9e1d1c7d2ec89c1570d5819..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuu@QhE5JkZo%_zxQvvP?k u^0EX75FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjYm75D&FSp-D@ diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/package-parts.tab_i.len deleted file mode 100644 index 131e265740f37d77b7c4a3676d2a7704ca3e4a29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz0D%Su009U9fdBvi diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab deleted file mode 100644 index 6bd2e27ec4d7ecfc427aae4b83155c6016dabab7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIv%PWLY7{~F)b;?4slK)_=T*98RRI{*^DSr$-Gr)u zS!M&8W$!xl%UBGiWO@}2WtV)EyUT_Dp=f;h1bhNM0iS?Rz$f4n@Cozb5))%cT>)<)dO%XJieTJhe1DYzhwq>C)gt91*W;Z%l1K=NKv@GFnjJ xtMa0G2XV91UT>@PKT>@PKT>@PKTR)#n6Dt4! diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len deleted file mode 100644 index 7c994b224517870b747bc16e99683b65d2ad5e02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00H)Q009I5_5c6? diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.len deleted file mode 100644 index 8e9520632399f01da95f660f3d1ea6f36fd2d158..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00G7w006=Ox&QzG diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at deleted file mode 100644 index 82e7cd7f2d98b47863b5176283a62dd961dee97c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24785 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 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i deleted file mode 100644 index 7aee661b0145d7e8d6b1e40b8103ea10c6d3de8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI(O-NK>7{&2h5k&-L1e%Z<2#)1q2%!(cLJ@OUgFy?!Y#}Wt!%|lg21Z4Ki9=9K zLMUM%lo3TREg}(O5|SdYqD?_TErb$Ubb>Z68|R((!~bkV;Xda)zYFika3`vY)>jAkSP2q1s}0tk2}kWt^ZmeE?zYFn?{ zr_UK)l$|NIUW5Y65*X0c2eR#-{&&crF4$4N>}+#b5qXtpiWT@mP0k%j(=DOdW?1A#&a#B|OjMiyg* z$RYy)1Q0*~0aF4MdOzEJ>mARhn*wGo0tg_000IagfB*srAbN0uuP5U7qhNstIT%uU0v!bMspN#R{)VQx*XP5O7%F mxX#su+Bn}^&+#5@mf!2esgA??nX2D2lj9gJLjZw12>b!;7FAgVO{LlaYfZ zd3}K*G*OTpCLtb@uEuFg%6mVf*drIVc#Cn~$L-_HnQ6e9ahO42`tnkw7hPIF3upl? Sparym7SIA(KnrMrN(HXA-c$bo diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len deleted file mode 100644 index 49c7f00ac3220be67a6c33cc304cc29c6d6f66e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00HI&006E4qyPW_ diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len deleted file mode 100644 index a5413564614472ae2e55bb44e79510947726def0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmZQz0D~I<0N?=G diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at deleted file mode 100644 index d75d4e5bb3eca1b5faa7d619fe56ac30f9877948..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3808 zcmcIn&5qMB5Y7P!A)$SSJ&X_+HY6;t{ECuYXsNoY+VXorCY^1gCU&r$MU}Yl61)UY z!Hp~8d601P(`M6p>!#&WsnLAjH=Y^KjBTrlWQcsfhd$%O{TGbycae)gNTjk!#JEG^ zHzZ83FBvBz{AS(pS!7E>M;O@@cZVN(=pA!_a zA*K3F9rVT|3{m9@+z-zjg4@|47dXuwu(jWN;J(@w5je+NPpm-MRX`O^_-bIjqtbeqFU3Ga?Z)D)5AJ z#-DDCfMYx(Nhn)U9Eu||_@ZOsc95aMqRd= z(Cm1&iDaQ8+Om?$zt^k-4MR402I!CNrj&8Pak$=mkj|_bvHHl zMp!@}ulH;j1gH6AT@UUN)$KR&FyS1jA1pw+S}ccQ2?SpDX{xKm68a^w4IF$ekPp}d z7jZYpQwk_VbZwyI(qLW;X&b22-&e~-a!#Q(9D^(`B$fSzE>cndv!`9MR-Pc=Y#yzz zm4ypA2K-rlgKJO%S}ZuVl+^}>b(M20IE^}IPE>(;ETNK5!S%Lg0Owg94u5{7z|~FP z%hC;H0&r@bV}x>X+rT}kbB+o}O;6%vdrvAH;MA&WPqSkESUHkZ#3DxR#&DQDq*_$% zvOdP@wRCqyG;?#0sHcR-R0XndHpNP)RlCp}#fRPGQyBPdoK|3!7II|W)T?)x7zaxI z9Jz9~g8Y8jG4rX^N#9`|6hgfSZ|H?`^R&a>HZ#+{`VKACtCk*~>eDRzJyelA3*^!w F@edYNvdaJf diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i deleted file mode 100644 index f2ceb1bfe5f9c6f21d41c00e761ab43fdca6dcf1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIup=yFr9LC`fEQ7J&HEh8)gXY1|W-u6r&E1DEkfCKTmW-~6$uLmc;)-Bd8VuGJ zb_omqcL6p*&)IXz7d|-W{XNBLQ7Rk)1hN#EM%7EFy3R6A6afMR2oNAZfB*pk1PBly zKwysouW{E&sa@XVm98Q{fB*pk*$Lc5QLXZEm0g%10t5&UAV7cs0RjXF5FkK+009C7 zb_&en&uTXM`#1y$5FkK+009C72oNAZfB*pk1PBlyK!5-N0tXP-#G8ZW*~I~#_9OuU z1PBlyK!8930^`{DdHLQ-V5*4(2oNAZfB*pk1PBlyK!5-N0t5&UNLpYL>u)y0UeXc9 z6Cgl<009C72oN}Ofl=(czx}Eo`AqW&{0o6)RDSmxzq!S}MbN|b(|K-PdI;nw@EB3u N=buH6ZJN>(*a9B78^izr diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len deleted file mode 100644 index 131e265740f37d77b7c4a3676d2a7704ca3e4a29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz0D%Su009U9fdBvi diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab deleted file mode 100644 index db036e4ef9e3c1054513a84eacc1f2f4e827897d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmbR3vzw0r2sl9m32x4n_4oh(X+WAANG}4?xBvYAe-TJ80n$%_bTN?T0b1e&q^*IpB#;IQkAl$<7!85Z k5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC70URL!0Fl2bmH+?% diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream deleted file mode 100644 index a6e8d241a6307a41e251a74138a9df0dcaa82aca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIuy$ZrW3O2sJJ@hmir*d|M;CV zwjhQIv_L2?_B9w+fis;&5^lKY;EBO81&q9BfBn)i4M{O8Asy!p$E6cCn))Zp1h+ho z(d6jwB(^b diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len deleted file mode 100644 index 8d483a8627f5fa128d8b36b8e902dc3208456d2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00G7X003$LVgLXD diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len deleted file mode 100644 index eb0b8a069dd4fe98c3148ae79618cb3e6ef0b6db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmZQz0D}_%0MG!* diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at deleted file mode 100644 index ac14c83bb7bfeb15a86fda4b7c91adb4fed95e2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1039 zcmbW0K~BRk5JkP>99_W*XF~xYSX2>ER0UOO0kJgBD2?iPqVYs1cj8uD1d}u>MWh(E z);#||Uwc1) diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i deleted file mode 100644 index b8cf2164ea9cf5c7bb28cc6e83bfc6970525f9bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIup>D!Z6b9fXOweR%8Z%3H1bknG%pu6^GKB-NC1eQ+WCl=36WGPZzlY+ZbBkM; z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7csfn)`KqSJHoyOA9!b6C`W}>qwAp@A5*56I|mK z$9TauHu&i6<=)35&Txf0Ofbh7TUE2`(|v>)E^&%`>|u@_jB951hkJr2oMVdoPNM=U YpaLqO0xF;aDxd-?paLqO0)JQF4Hh&`8~^|S diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream deleted file mode 100644 index b2079c9286248bc7d034f500c9e98cd5b1ce4cf1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeH^O-sZu5QhCV-i#N&5IrnKTv=A6u87B&4&AV6rc9W788s|vlE z6~!EM0rcP*$Ux0V=hN{_n>A$Unb6Id>k)Nvh@#@Dd3V+3E_y+@@#@lFY diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len deleted file mode 100644 index 9030374dca137536d6078ed07bb999a59028e3a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00HJD006Q8r~m)} diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len deleted file mode 100644 index 09407efabbe5467ec2b0cdc5cce0a25e55419604..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00Bk;000^Q5&!@I diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at deleted file mode 100644 index 3c0a2eb080ff9e1edfd524e371c0e9962372c4cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 955 zcmeH`y>7xl5QMKM9Xx=G2)af2;Q=T>rZ7Tar9b&rwq@V#X>TpW+jFr&N+cpAQlv?B z)i;{knVG8;wLwnySP+|JO`PnILmdR2q&ifY_8+?K@F2vx#8>rFkOk4ML<z1wS|)%;#8Ckc#-L*M)k&59IyF zKiPi_ZGXeRKFLIlsi|U+Z?JKKT%eC#y&ZoH*ph>#F+=Zv1I*O)OJ6R^mQ2?oWc`z2 W2yrv{%_d%e?J%Kl!1Ep5epP3Wykpn^ diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i deleted file mode 100644 index 8b8b1ed85aa7c99909f1f3b1f2a5e24308c52652..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI(F(?Ib90%}|MKZ~(7?ep^l@giWB8v=U%JcHZN99#qtX>y}KJJ|f5FkK+009C72oU%zP>Q9d!sz1XCszJK z;5nKeFXiX|qPLy|2oNAZfB=F26gY}uy|d+k|D0)71_c_DWP9O0pTXoJ6Zo~jR=n9& zoIdz<11}OFK!5;&Zv>j+`)u#={Nj>&5$T z9}WTpQYui4eeV?J`ck@m+a^GO009C72oNAZfB*pk1PEkS;3Sf_bmvYp3m`ZF0t5&U zs6gN@YBrlkW-2IELVy4P0tD(_;3*Q071!G8KB#~M2oNApRe`hU*3;iPQPrlJ5y-ee yIr2a3Ty|ufL3{!P2oNAZpauemF>`A3C|85gMiL-EfB*pk1PBlyK!8As1>OKy-7fP0 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len deleted file mode 100644 index 131e265740f37d77b7c4a3676d2a7704ca3e4a29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz0D%Su009U9fdBvi diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab b/network/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab deleted file mode 100644 index 920202f822..0000000000 --- a/network/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab +++ /dev/null @@ -1,2 +0,0 @@ -11 -0 \ No newline at end of file diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab deleted file mode 100644 index 94e8c84a412e3bd484e6cc0f11cafca0e0fca317..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmbR3vzw0r2)IE63!{qVN=YZx%0~MSA z(tCmQAs{UXG-MBuP6N{0fb>ovy#YuIFflN!0@B$)dI?AzNY4Y(oXiXiGk|m$ke&pj s@!2;jHyQ$?Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0wXO10Dj#wu>b%7 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream deleted file mode 100644 index 13bcc6123c91d07994ff4d2eaeeaeb2839c71a54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIx!EOQ}5P;!6jOWfX80%pht%lur@X`=6Rcc{y7}xmrvYKemU3wdCB;+Ic2ey;7 zS-sC^-RE0lCJ<51#0SXiAXp#a+x8Y51rQr|7#;^~@{sH$U=;eH-38XaHq+!^4`Tm` zZjpQMu-wqry20S_gjsSiyLx6$6f+K}8G+uzcj)$D@JVj!klk&pgMJ^{wl@2m*7A>Z zd3}K*G*OTpCLtb@uEuFg%6mVf*drIVc#Cn~$L-_HnQ6e9ahO42`tnkw7hPIF3upl? Sparym7SIA(KnrMrN(HXA-c$bo diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len deleted file mode 100644 index 49c7f00ac3220be67a6c33cc304cc29c6d6f66e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00HI&006E4qyPW_ diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len deleted file mode 100644 index a5413564614472ae2e55bb44e79510947726def0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmZQz0D~I<0N?=G diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.values.at deleted file mode 100644 index afce831e8f06b6776dd87e9f59f94f1de8d53662..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115 zcmW;6OA3H631vnF5QOd5aR<5XDU_=}gME gdP>Jg&q~+Ji$x!J$ARmuPHja1lai zZD_5|B?$TpN1ks!-+SKk6t$vKczYLki;bhJljYtQTS$Na0RjX{5V(xY!T02*glLfj z2oNBUP+%CH+r6K4!s;p$AV7cs0RjXF5FkK+009EY1+Jt2^XJ!Z@-x&YK!5-N0t5&U zAV7cs0RjXFq!oCN=C|vIVcG)}CqRGz0RjXF5FkK+009C72oNB!g}{CMoweqFTSWL; zg1}>B9@Re4SHY4Ur)rQs9cX9WBG4Z~?Gr$y9@Ce^Ovkcy04oi4~&#zel oZ}1Frc!95vng2%*fg%GkAOkWW12P~3G9UvoAOkWW12PZ{ES$eCVgLXD diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream deleted file mode 100644 index b73411eda599178cd04ec5f0e2c9f90e9d194ae5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 xcmeIu2@wDw3;>|%{AT~a_V6GA(Rz-#_Nf~fFkrxd0RsjM7%*VKfB^%44m^IW05|{u diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len deleted file mode 100644 index 296694d3c0b2935793fbfb8b84f4496d1f079fc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmZQz00V9S022TV diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len deleted file mode 100644 index a5413564614472ae2e55bb44e79510947726def0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 LcmZQz0D~I<0N?=G diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.values.at deleted file mode 100644 index 52384eb654aa1c4eac73383308d3092ffe64a521..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 999 zcmcK3K~BRk5Cu>lC5x`{8Bi%|D$+(yC=x8t;7$|M+GECUP;ZaZN~CKNmX@qHBmZwH zmIyoKOra*;Wsk&Ji5yIb=;9JWTY%rf@q|?*h6dln>#S6p)nZ=1KjdmA9ma3sgAA;t z1>@u=bRacZ8NoGZe=mSH(!<#%myXn&WqEVB_vY>pKLq>}ULkizW13NmYN{CuzfKrp zNb0kwr&EI`7qDk5p+>OR0BY2&R5+k7Vml35Ujpv^YkcFnakK3J54o*H@yD%G7cDuN xyNImOryloDTZ+v%ub|MHIk^rw!AUH{(>QT``AQ3B>baH3$8icH)852n{Q$$3X`%oC diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i deleted file mode 100644 index fd274018e830e7a5904ca90fabd233646303783d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuD-Hrt5JkbqA7_b*XnvWdsNiAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly IK;Rz)J~N~S{Qv*} diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i.len deleted file mode 100644 index 131e265740f37d77b7c4a3676d2a7704ca3e4a29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz0D%Su009U9fdBvi diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab deleted file mode 100644 index 74829020bb4b870a3a5c93ede97e300b8014d868..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 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 I@LwnJALE7zzW@LL diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream deleted file mode 100644 index c040fb2f1ea567af2ef43668b9c0937870c0fad5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&`=3vB0>|<1$)&+O$gLv9%rGuXnrK2}WASJ`kc5iJHLOBfWG6D@GL*)pi(4&G z36U_%CM>h!Vcai~E+Q<^WOTz+dtR?I+aIxy@ze8hzL#@8@Av0?&N*Kb#FF~N-T3{c zbHRc73AM#t-9LIj6d&H|1933B!=e#m$A29>y{`0rajMSdl0EV+#)+>yT(U$# zP_l8O1A;9_XO&M6Hay*Ww5YnR>IJc&`d-0% z#LLavr-(T#&Xk=Hd;IQT<_Bw=SNW;??ZKG$wtXuSN9-JYWz^nZg3A{_IVJeQ8x$qHXqX6Ghza6}JfD6|t0Mi6ZxoN~6T4wq+^{f)W%bnzky|R}A>{Kq*0Dv&a3M zX<~xJ1c?dq5gfeWP(|l?WBLh>S#ZpvM9zWednzt?zp`867Up~-x;)Y8q}W}2{Bt7t z{GsTjnP2vdUK;z>^ysBk6UGkrJrmzKH&YZ;9&$qLdVSIsK`eq0#Ef+hEn8%pZ}IcHIRrYvJ_+qW9FA zu~0BK1am_$Hw1G-unTdae&!dV%%g|D5*bH3RuJcIUOGm!ZZ_jh!SP&J(SEqcBRC$x zVMH&5haor&!C?pvLvR>^!w?*XU>6>aqNyI3=6RJurbsp&T&srT(=#MLI%z7|iknGs=I&jug5rTq(? z=8HS`j=n5ftiJi6;3N{9M1pfi)Sg7q1jC6C4o6U>U^s%|2!Wq%fPw)E1}I{|>Q@Bg5+k2ZYA$A1I+Z7SrQ~iF ztN?5JWh8rn77R!5(w#$wFZQJf;Yu4J3_)-`jkbgEK=`6KwA)Wp_rxIPO$C3v&o zJrgR_|H6F99nM&ug+h+7h1Tg7sNc{w?WN!HF-Hcf02W!RaYlG~Agj$XR^VwfSmM zIqAxA!I>*^?;N{9aOR4xn+JFLa?PU)o$4Z1BXM*a97B3bh_NXt)4SM66 zII(qQgjdQftL2sh&u2&AV&@hvcB0|r_p?Mw+l8wH7dyelPIT)w?H0k=6ulIlO(K5Y zhtYdDo7}?LBsiM{XOrM;64bP+Vug5X64WH9Nl=rZCP7Vtnj(ZX32GA5B&bPHlb|L+ zP0^OHCP7Vtngs0#Di9&1?#FP*ioBVl`v?w0uy-+{YQp*mb?Zi8Uv6O+f?Y%icTp)T~|h~VZ{ za0e^6gB8q7!ORrQOu=KjVA=>)0KxsL;7&%6ykJcbR3KO!1dD@U{s^8*1j~hBxey=h z7`OSvW9yd$+-B^Go8Kj1$ipu6dpKCw3tASmENEHKvY=%_%Yv2#Eel!}v@B>@(6XRqLCb=c1uY96 zg))xL?-lSUBzTf)^3kOJ0Z&qb2PnYzI9{#O{}8>IX%nCQW145TVA=?#jbM@p=7wNy2=yJc=v~mepm#y5Yz5xP({#%7@7UVU4l{sr3gw9lp-iaP>P@wK`DY#1f>W{5tJe* zMNo>M6hSG1QUo`XpC&ZR3AmXQ#bY-=nEv)3M+Mv;*68}ouL1Xmf(Dn@zi_J!3K|qN zC}>d7m!K~}UxK~_H3@1G)Fh}$P?Ml0K}~|11T_h264WH9Nl=rZ9YHCAQUs+4N)ePI zC`C|;pcFwVf>H#f2ucx@A}B>ril7uhDdObf`xpFS&*G&)h}Q+lZ#X=%k;w~^7bGu8 zUXZ*Xc|r1m}=0~eMJcOCHUqD;WvxMAOHOUF{JVG2uvimaDw#O-+M~{Zi>61ASnLW^8SKv7JOlZ z@C!xBg!vOita{3u>GxdA5BSY$+cR^&3^-FnV$zQDIWyu@JowQo`d=5UdVFDlNSHji zOnk9pSpg@AC=njKSXJTjF2U(JJ9F0$erny}-KYF}fPRav-5t)cim8IX2Z*+We-BXj tQfgk|mZNP1|62iKMgNKtC`zCxfuaP85-3WbD1o8`iV`SF;Qu{={{j%q*!BPb diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream.len deleted file mode 100644 index 80a7de774ad431af673de36d70afba1377d284d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz009+F003D4E&u=k diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.len deleted file mode 100644 index 9e436a82c2cfcdc6e55eec6345b4adfca6e6f448..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz00D&=007_sxc~qF diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.values.at b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.values.at deleted file mode 100644 index c8693c113bc21fa13fc9abe5c52483ab7f7dda94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7475 zcmeHM%We}f6wNzL#DWD&7Fn|6FHko~2qg9q9RU&|C<*_M?>X+=V~;(W2->t_BIVY; zZy#U3(zlb_W`EZlcORQChvVJu-Ql?V&>TDDEv=QSvAvy`n(x3zK}bvD=oa7PY$Td|IEaAGXz z!e6WF{%E^x{aLn=hv;U8WvczYQ1mJqzT9;UBCK)-p1 zwjs+GjpafuX2b}LrWxt&g=Z!8X&X81==g&1`HJfja@_u_E#w2K4I{4&#$g4!roSgv zcxRZz-cja(WAJ>m?_+1paE$W$@+^TV*A~aUMI=PFDB-(#9Y}(*8Q}~Kf4!CF zBbH~hK;IGE!ux$Z^^Da7PldVbwc<#0?93(3<{KN2eJrbLu!^;=6|v5|z`lkx@qKudpA@3m+C*-G^0+6+ zNdsK;Ov`0s?Ok*S8tZK0CqQ0Hl{zb}*Aj2U_Ht}T%r3{QRC^^Ju;u?%{QS#Wg1_#h zNxSZw5yS`2u6$kPIrwdqcPhOXciYtYd)oCpoO3~Rpk>70i1_8B{T&1{V8)xBpZ~Qe T+~Ik%3XUiG8>RmLM)}J>Yb-x0 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i deleted file mode 100644 index ae1a3dd602a1ee4a12ba808089831213e6d0ab0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmdU2cYKmp_kZfdjT=W5H?9PuNvIQdtP3~JP$zEOXf>kM7_A!AT5(h{YQ&Akf!0B^ zDz1tfM-?}YDAuUd`TOQ$4}NmVQ%JP$`~L199=_+Cd!KvHy5}KD%YBt4GndA&3pp-s z*Tm>FME}TAVczoBiC^<635#&trhT0f`Q-nrd_iuGYp}lMJ%9W2WaGFJ_NM36`8n>e zG~R1JS8o;Fbg=Yr2H+cn|es#pLNT- z6-v$hglxzP-GSAzPn^I$Npo?W(3JS6sWkQzvaA$4OInoUp5Ll{nD`#}@z@q)a|0rx z_?cg7DDm9Qhp$z1VSbZRStO(vC>3Cx9*;J^CVdCuOB8l#H{tYBJ>sP0gCZUL%~RuC zv-?g?X7x;&IPRVO)pFu%;iq!;Wu07EI4<9Si{**WQX%5}aEHf;;=qOo`MEUg+#x<~ z3G8~kZCpb>`M<^2f5mYd_w_*(ejIAV`N$P7&lA?eZbRx6izQ4{0z1WSA2ml?jN`g} z$Tr1=x{_&BY+I6Nl-l4=QHOg?{mqx-d9Siux}|}iDkM+u!>6xDoNjs2O07YL9qrL? z8K0gV=d5(6{un;_zseR9CG8P`xTq5?-I}(P zpDRUN&YR-?jb9_R#EgAZP5AMl4E8$9y`P_2%8%rzE0Z=QgtHn>)cKjGo4paEzGUDzsGFDay}GD}-qz5IH~5}Ud^0?gC*J;?us^CsJ$u=xh0uk4_g}ubt{yjBEytlZ z{^pTfjyjwiv~dsN^inCFKOwpC4>B>wjToX{livCM^es@xUg*ku(u=`|xPz@`$#GA| zuRS)L`01qr#QC!J@;2$6_u2Z`w?civNb3j@7d3*jmk^`AFEYR0L41~a;MabJ9}rck zhX(Q1N)(Lf|GXhZ-BG<2p^BwF#E7p&)28|4MFjHZ@qp96n^4Ca?D%Gb8Xe!8?IDh| zSK;T%u+FVT6C3kuq%D!+xjnbOsLx96u-B{#bs+t`;YLSV@=>28pH)!r3SB+rW!q41 z*2RUtAch)#QFjcEBFsnasDCqS&1tQIXDh6B_U2%R#pP~3C4C(_uJ32{^AIL#Lf-ya zdhHP%;>X}9-Q9-!zG(^JZ3*l@?!>*qq!%carsPx27T)-ZKwkfGcm9uR)YHXh;}+05 z7VKw9!cUC|6V>AUy$}EJkT6TNSifAwsOJ{c!_ociO`X*Q+IV>oV|9c+>eN?|Y2Db%gN$ z?m63Dlb#0q*fAq|IpK^_D>9*pvV6yk&Ntv(KRGHi$>@A`oN+A7AAmyc8{wil%p~=3a{er)7_AVJUH}<)csKt>OtIYalrkF&w7Zn$M54u_I;u6p$nn6XAWchs9iTGbKaobo4^6p&Cr4{(}0_AIvr&Tr<*}>{- zaPL(KvJ6T|v`jtJtywvX<%pZ|S+|cQz8(I!XVX7df)+kLvb7xVv(qE@^dq?(k-vII zRU2WV3V3!=j!6{^uwdrOmn$hzS9D_@=Z(NRxn7s{sRuNVrAG8wH*P+8;6$E;6|G@W zAU{?uxaqC44%jbONJpVYjDFOaiPw%o=sl3T-z#zN371~1%0B0Fu@m(u`@zP8GQ1AX zIeP8f7=QD{$b;F6?bH0}WmK+*o%^;Pv?|c~F^C6kt9{2wUxaw*ec+DwULk&o3N8EQAnxG~=Q9J5d-P7%drv&Wu z(*1SB=c6Lr`yFlwqxs}~d>QWRciZot;FA;IiStF7H`FG~OHmKAJG)qU<)10g!ebAw zh^~F+GT!M1d>Z#`gVD?Qmp@ogmn-%>I?I@q<0giz4x+~?QD^mQYpquyZv>U}Dcj1> zkfDAZI@9}kH6#y zLDfq=WVp{~h_h8s&HaRQxX%{tJde8Xs>) z+|*ezd_n~3_nTQo#;13kK=o5jnloSI2`46usu=2^c^<^a-l}6a8{mijS&f+muye_k zt-SssJzgqCU3{O(+ef)^o^xlPUtskesHYW99C|?f2;^sueA#Xi4wPEqr#CIj#`)9N z;ym-lG|OGAQ6B77hEbS;O=&mfE4=a&Z%g?;N z!Y4Q3IsMz@^44`ey+HXw1^<0Bch4!7|D~}Mbvkrgk015O+v_L3 zPH8_s4%EFW^1NjTvQ!2A@w-AU5?=-X-pw<(wjB1}SZ4@t9FRUAwIU89>K@*W}3>V_D{;IfUd~!a%8+9?Zy+_F>*C9Tp3qljTa_pn+jL>st zod0G;>02-V?+;ScrRM4dvxpyozTirX*Db{6%vk4DvFioR=oe~q{eFa3z0Ft2Fqc; zUWGWUntE6fAydQVb+W)?msT$Ed7lwsM&96oGBxT$;Uy<0q@IoM(vTTX*M$GZ@>y&^ zzgGOf-fvibda()TXk4?QnVqM=`I{LB7+F4H9p+U!w0Q|(K59XoDL=FvvghMaqi$W( z?5s(cs2k^A)FKvENqY!Oaa{h^XAp&-H%!ROx2x+-N;&XItNBKHSCb=yldqQQVTFHt z^lpEJoo_+j<+=P#X%2C6c4Yh3nrs}`X0K#9>B;fEMd5RCPQo(8!x(4odJ61o=9uKo z#P?Fj<0P^jD%h=+EsXabQo_#dSM~6TM;+E}QLXqB?|OfK(4sChe(h~PSTQdaAik@f z`>PAD!hczyg}t}ky+0xb5x3<`$Nm}JjurJVyj!&QewLtL8__8Ko)dMYnxlSsf9pE& z-j%oe=!yRH0+nkJch^lZZCQN@?vH}v!D5!rha<3mQQvQSlH7**mYvcTgg=+YAWv(Z zEZdUSks@OY_j{@$%tr-?Q>8W689q77*CD^Gh1<_WNsXM|}nQA=&*Ke-Ym=Mcw#j^6oxv#A&etTfBC6VxKPt#&=WTUSBj=`>8JB zx!(=?p}7uurwDz~K!kax^#dxQ1O9fSDfe0&``q>Yn`j5Z)1;(jl9ht)G8o(r}d|D5sR#AR_+daN;!EL&f#04>CM-gHPAR*ktq5^&Z=7)&@%g9(9b&c9 zeZBKpJu%{iE0oEbZ>&7MScrJi6>JQvrXB|5uRhQ4B;x83=N%Ig%6c*E^z!!!GYIoh z9nRmO)5?>4a({di&KvWpd3k?&tXzXQFMB~5OME3f`g-9?Z~PKJP#S|co?5zCJ<_)p z#P`~s(8q*DcwUmH-gYzkfk&&~$cfKKm57JLrw4!Kll$WZDlb!_TcsP*XoN5&cYuzbQIc+47ffVYl#^$-UM2x4)*$15;jqZK6Pbe z1s^}iaX&TWc2-R9>LT=owxJbwq<5ZLjC^i$h}R!{>$zbm;nziYkgu@NGXc`tj_7Z~kJ}Gp6u`iEUUui&cpCe)Ep{_(g?$%-pX_5b5ioC%N3f zC*MgPDAgh#I?emZ$BstS>F}w!PSH9N;Df{Wjv&lZIqaF+S>O)KCoD$3&OTN@fv`X7 zg1u%o?l>d@b;EGm>chX(s?9VAJ29INr%TJPsRabiAKV^2?7m#6~y zqjr|bqk^8GaM$(3Cu)SBZiTh>=7Ajhd3$NiG<{Z%8~**QS~k?p_ag_YsEQZek@b6NQO^v;h#e>!5ts7C3XNBU~mb7&D`XTlNi)4O4sn}msK zasF=lJ+N-Zh6MS$Npn2q_6TUG5eLT;Q~ugAu%XP2?;(dpnfz(%(AV}Jex#EPC&?AP zZ7S(IV277Q~zlD-RhlXP#1ns9ol9r<^>`0V3+^Q5rEi6ZT!d~$z$ zCH$Aa+Vqo3_`$yM#!}LkpfAY&Jp3mwMxUF#OD2H}cA8vzQ)S}wQ9bh{^j5oW0i`PXJg5CADR?Sz*9aB4eC;xrjiiCM1`<_$I`>q4#YvWKXVQy zK2Zf$2yL{pH(@98=4sApV+s4CMy&hw+<#X-QBYw@h zHo*IV4Hf$BC9|qjwjdwM?6SbCABPqA$EK^?gjs4szqzaBb#I^H#634?T*t*q)c<-j z-o>XBo|IFr z*UPA$5jVxMqpj;^bpC&8o)C4i*S1_q?DIs((>~p{!TTSF1D~=rk(gr z2+R^Yxe8$s{Hm%`*QdUbJW%S!ePXM!pbqJqkbeXBM9dbTZ`m<08CmepLy5lV@%WY4 z?LQA2@^AKH_jW!l2l~+ME%l#$9y{#h(k3U!QO7mM!;t-H4<+hDrCUYzrThiq)4B_$ zq^$)Tqu*MZ?b|V|+z5Z#&qwl}ht&@h8*uNPZg9jEsQ#DMbKqP9JFmXYx2_)dp($v7 zTRyoorGJ}q(0dQ_>HXV$HT-&N+2qlm`y35yl4R`oeZ{Qnw7oWI)Qz-~Sc0lwEN7FV!4VHftBxn(lEpT6NlK89SCZ*gPgqWg;P zQKMcYH-FVC&{Y)Z>sp-S|NTY@)-5%(2Jd=y_;2Zx%bWZ?Pl9+U`^QgHq_|HEITxJs zr!PXEeSX<2@AqT=^m*m5Pvp<$f306+pKHN650x9~Q}49sQ{t+})F%B1c)Hf^ceCAy z``0x}`t)~d+zf@UHJL&B7Ss)qwv&;tKPthxbE0baeQz*f9})ZKz)Be#DSx4vv-T7pO{3dVn_V;?MceHgZcKvZU5hoY_xKqf1 z{-l3=mIDs-d%fT3o|2rUYS_8Z&0+Uh{(lM^QCC_NDBgs9UW}OI=2s0@vwRWk^g1+a zOO{Vq0sGWw(rXZ5EBfz2*?Je%BF@J*=~s{VI@HY^LvEk5;LYMilkM$nkY{OGLPc_x zs&PMVtue+YpIAK>3*onE#w*iUz7BaL$uiQ1&+2_?tU#m;=>MUw&$@`0G8KB1~wr2^Rd?4Y_sXubn^ z+TMOGny^1=!25x2K&NJWdN#zR%kegtPwqk=(fak06E5`I(cPM~;?pC(6mimWjq(Ly zqH@&j5rV7>2!A%kev;>gBANmlLNWe3TOGc8ADFhUmKk<1+v5&&zy7-*Oj{F%lCih8NMGL(uO@8erfPKCzI=mzC{ZSA6R^Y?+ zfa8>JJqH9%Plqr*Iq^M+%Qk0D%f;Al=?@is>Ou_s>Ykb0hV(N^Wr#oh&K!Q@){c0o zI_Kd!CG4>6s;dXz^Q_2|mJ5CzOL7PH8D6=yk6%d6(v)u;hu_`V!G(MFu5H*WR_;LE zE$}?oK9)~dj(7`NI3ikv{@~{W${^l+AbmUfrjn=b*C%YjdGpu5dQXG*x|LkbdBkU_1@@0isuXZu zu=820Lms@F_%PI+QipbbkdoYtN>nS-(M5-TF@N`;`xBp~V)*0mB-3s{G+5H+S6{j9a1jwV%{nMY3o)-D>Am|3K{Ya0GD&f~kU0-VWVH zQXQ1wJ?hpk4-#mdK&cURV@>kl6QnP|J)3-2b(63K=f330VIa&>JI*(3S7d$SJ4@mB zaXlOT#+41{e9}VkEzR?xuwjP_a~`h?7xs}vE!kJ;R}7aNdQdkPD@{v!SV zFI0l~DxGiIll1Oag#P8mj=WdWJJ0eJes6wDt%G`W2D_6d0DYOEN53)jWq0qr7K6G~ z;`cwJY4sS?%~R(}wi4pr{wp}-DDjC(5GO-lj7C%kHl*;oD6WdPp6C%*yTaAZY#9#BDPOc|8OHJsv z62l8DBfc8?c}hR1MEE~V6^MtOJN9I_5AwHP3F`NRTlVAr^a7Q85Et3Do$~g5Hq@Ek zy=ILFbUx{ckynFdN)=(2DiIeEEjG1a`GnobgB8cRZ6!?9j5<&?u}XKsEHz`@VyElw zWch^UxVKI7uD@|0enaQfc~1QFQYGy2aA~9GsvP+5qn5r?lbtWdJ+)ud=@HA1K|FT0 zt(!o6H}0o?NpU{)%Y}S;vU*$u>G4q$^zu$0mB1(0;=DyBUmMLQcf+2`ww~touN(6( z-njOb=4+9k*&`Q(5$2;7=pB#GHGogf$B#fgc~(68ERq}1ht}%85!EB@;e?&Di~s0q zK;4SZxvpW_TKKLN@1KQC*&_Mm#P^_XeTbUp?b~JW%=x`9LP$Rq&E z=8-_>`O`BX9)+P}e9r5@J*=5m2VHjB!wtI)znH&E+ETphV!y$smRU(I!aX&w`qKS` zS!%%zFtgp|7IN6@sXe?3E4N`M&Yq3D^+Sj{sEYpe2CMg_u@U}_U3f6V_dD%J0ek*2 z`FNrM5pueqxSI|3I3E{^DETtO2)hOUc{+)2H3!ZS6!o|{hdLd6usUzvu=C|&ystT( zyNNGDoj!Q`7;m3JdMtIqfBUO9I?wXIH1@!*H@midN9#(E&*2T{74o3&t+{h5Opfz= zG}q3M{NJJ;)F;90c00wWL(U8K>GYfk#DOsDn^3|;J@`8cUiKMoJ#G~wLYx`ZXTH+Ge+QP~W7?^e`F z4w@~h*N-B(8Mxf)U_|f7VTIm1>(ds51EmV&U5?{@3JMXwq8N1zcAgvcB&WIdZ!BMh zdR?r20iU|h>ao~}%wBl%@dq{heS1Ue&a9lU4Srm|rpP_Q{-^?R6|%6@BzpG8~ z{F?g$$~nOW5`-L zt0h4lThja7EtXH%gZ!BM?&(#+EEOUya-5&oP!7M#>bCRNVJr6MndB+M>Jye=Klf&i z+)tRLIz0E6zcviFLNKV6vZx7`G~F4{*Nk|V_7mGkUyA3(?kM`0uo35JGSfQS4Lf@7 z7P~-vJ}N?eXj%EIH+*v98}a;dQ|9y5C(`4i9@LSfpMR^vCujK)ILGyeZ||{uJ}gIk z-8>YV#uVBNf@r3zk1o}O})`v^^{;xOP6pzuP*)$1(M>TRvm)|Wf5QoI-VGBgklc)Q*3IhatrrH=b9cjq-a04- z{$cvzzCz6VIdSe*nkPnl<*E0!2I0@868LHE*|V=ccOAa@4#a(%H@^R03N`M7*oM`| z^37xUGT3pkv8m9G^EWRT`vWVFfW2#FI{z2(W8l|jeRO9DOK?9WhQ!XdqYhLp^_sU1 zkUmQzP(RO3je5xP-QeGRq_`G?c%Ii`Sp68p;q5}D??sfr@0%RUcG-sWZ*G4Y-u&kg zf%|^J*u9hfS<6>yM_z6^Kf>DMnox_pK$~cZDy@ks|LJeRn@Zjkv9o zlm-8$JzS{MXHLtWr7iX43SduDbR&eE6vHB#%M-ovxn7e;14t0;C4?g+C?5H4RWbU^NByJ8kH4RlpPeONs*Ax7XIT z_U7y7Zo`iJo*r6djlXr2s3Xqg33gU50{vWF>52tf*iYKJQ+1MC(f1teGVCDXf1Ac2 zE;?#n%weCSN1c9`qriHW?+@Ehk4h#sn@f5ro!+`tmA@lQ)QtCr1>^FMBAi|-LLW5w zfd;!zJ@m+zj!!nsO3lqUib0;|i(Pb^T|t2T4|!QCgyp9f%V6iK@|FuFxX=GA^JWk` z--9}|t=PU$8Q%Z)6z{*1mHWdE%p27&xdNY_74@jp-QZ;g)NjqSOAYw+0_D5$y!G3w z)ggT;*4?>%e1F0o)Zr+}7&5$p*V%jSSkq6T!t@OqVtL1>bv)!!Yy_ZRD zMO;YNM0@RM#C{jFF}!xeQU~U2{m}+HhkY|<2I>2w9@O_6%R4L~JssBXp1Il+!Xm`y zmD3A`6aHVJcJ!eg`h`sq;vR{Uu0r+tGQ)(tPHy-N|E3-iJg?Nu67j@!!7d|To_s_& zqtpz4SzZ7w4g<9&27i( z@nH?>{j{#X_ZP!%bDc{qe0s!B`O7owiVm}+=oe~P3M3OhP^vGEzjyE6)*H8h&Qpo; zcdl8r1NihMs5i~N-RG_Iyn1;4(%~;_^U3-6YOJ3}p+}Yc-yBYy>qN1XYM%D^KP~My A1^@s6 diff --git a/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len b/network/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len deleted file mode 100644 index 131e265740f37d77b7c4a3676d2a7704ca3e4a29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8 McmZQz0D%Su009U9fdBvi diff --git a/network/build/kotlin/compileKotlin/last-build.bin b/network/build/kotlin/compileKotlin/last-build.bin deleted file mode 100644 index 88555530dd63535c1c7eb9c0f78d3d7cba15115b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18 YcmZ4UmVvdLhk=2y<+anAmkdAv06Dn@X#fBK diff --git a/network/build/kotlin/networkjar-classes.txt b/network/build/kotlin/networkjar-classes.txt deleted file mode 100644 index ef07fbb021..0000000000 --- a/network/build/kotlin/networkjar-classes.txt +++ /dev/null @@ -1 +0,0 @@ -/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/AttestationResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests$Item.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/CheckRegistrationRequests.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/KYCStatus.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApi$Companion.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApi.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$Companion.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$checkRegistration$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerKYC$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$registerWallet$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$requestAttestationChallenge$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService$requestAttestationChallenge$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/PaymentologyApiService.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterKYCRequest.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterWalletRequest.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegisterWalletResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse$Item.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/RegistrationResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError$DefaultImpls.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponseError.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/paymentology/ResponsesKt.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin$Network.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse$Coin.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CoinsResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse$Currency.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/CurrenciesResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/GeoResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/HttpResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/RatesResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi$DefaultImpls.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechApi.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechError.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$Companion.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$coins$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$currencies$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$getUserTokens$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$putUserTokens$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$rates$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$userCountry$2$1.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService$userCountry$2.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TangemTechService.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/TokenResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/api/tangemTech/UserTokensResponse.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/AddHeaderInterceptor.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/CacheControlHttpInterceptor.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/CardPublicKeyHttpInterceptor.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/MoshiConverter.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError$Companion.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/NetworkError.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/NetworkModuleMessage.class:/Users/vladislav/Documents/projects/tangem/tangem-app-android/network/build/classes/kotlin/main/com/tangem/network/common/RetrofitKt.class \ No newline at end of file diff --git a/network/build/libs/network.jar b/network/build/libs/network.jar deleted file mode 100644 index af1c4b140b481f82156439504e50812fd74b6455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99352 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 CI@U4PorC(;mEat>tw(~G^(?D}#-Fs%b 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 bLm;%yh`{_5b#z?`?-~;xo{u@0@8kU+({#u` diff --git a/network/build/tmp/jar/MANIFEST.MF b/network/build/tmp/jar/MANIFEST.MF deleted file mode 100644 index 58630c02ef..0000000000 --- a/network/build/tmp/jar/MANIFEST.MF +++ /dev/null @@ -1,2 +0,0 @@ -Manifest-Version: 1.0 - From b470ba91923ec1ef6355a65deca12dd1d2e99d11 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Dec 2022 11:15:34 +0300 Subject: [PATCH 115/176] Updated on 2026-08-14 --- .../com/tangem/tap/common/DialogManager.kt | 2 +- .../warningMessage/WarningMessagesManager.kt | 10 +- .../ui/dialogs/AddMoreBackupCardsDialog.kt | 2 +- .../ui/dialogs/BackupInProgressDialog.kt | 2 +- .../ui/dialogs/UnfinishedBackupFoundDialog.kt | 2 +- .../main/res/layout/dialog_wallet_trade.xml | 2 +- app/src/main/res/values-de/strings.xml | 89 ------- app/src/main/res/values-fr/strings.xml | 83 ------- app/src/main/res/values-it/strings.xml | 88 ------- .../main/res/values-ru/strings_blockchain.xml | 2 + app/src/main/res/values/strings.xml | 103 -------- .../main/res/values/strings_blockchain.xml | 2 + .../main/res/values/strings_untranslated.xml | 215 ---------------- .../src/main/res/values-de/strings-app.xml | 233 ++++++++++++++++++ .../main/res/values-de/strings-blockchain.xml | 4 + .../src/main/res/values-de/strings_final.xml | 5 + .../src/main/res/values-fr/strings-app.xml | 233 ++++++++++++++++++ .../main/res/values-fr/strings-blockchain.xml | 4 + .../src/main/res/values-fr/strings_final.xml | 5 + .../src/main/res/values-it/strings-app.xml | 233 ++++++++++++++++++ .../main/res/values-it/strings-blockchain.xml | 4 + .../src/main/res/values-it/strings_final.xml | 5 + .../src/main/res/values-ru/strings-app.xml | 146 +++++------ .../main/res/values-ru/strings-blockchain.xml | 4 + .../src/main/res/values-ru/strings_final.xml | 5 + core/res/src/main/res/values/strings-app.xml | 233 ++++++++++++++++++ .../main/res/values/strings-blockchain.xml | 4 + .../res/src/main/res/values/strings_final.xml | 5 + .../ui/src/main/res/values-de/strings-app.xml | 6 - .../ui/src/main/res/values-fr/strings-app.xml | 6 - .../ui/src/main/res/values-it/strings-app.xml | 6 - .../ui/src/main/res/values-ru/strings-app.xml | 6 - core/ui/src/main/res/values-ru/strings.xml | 4 - core/ui/src/main/res/values/strings-app.xml | 6 - core/ui/src/main/res/values/strings.xml | 4 - 35 files changed, 1064 insertions(+), 699 deletions(-) delete mode 100644 app/src/main/res/values-de/strings.xml delete mode 100644 app/src/main/res/values-fr/strings.xml delete mode 100644 app/src/main/res/values-it/strings.xml delete mode 100644 app/src/main/res/values/strings.xml delete mode 100644 app/src/main/res/values/strings_untranslated.xml create mode 100644 core/res/src/main/res/values-de/strings-app.xml create mode 100644 core/res/src/main/res/values-de/strings-blockchain.xml create mode 100644 core/res/src/main/res/values-fr/strings-app.xml create mode 100644 core/res/src/main/res/values-fr/strings-blockchain.xml create mode 100644 core/res/src/main/res/values-it/strings-app.xml create mode 100644 core/res/src/main/res/values-it/strings-blockchain.xml rename app/src/main/res/values-ru/strings.xml => core/res/src/main/res/values-ru/strings-app.xml (76%) create mode 100644 core/res/src/main/res/values-ru/strings-blockchain.xml create mode 100644 core/res/src/main/res/values/strings-app.xml create mode 100644 core/res/src/main/res/values/strings-blockchain.xml delete mode 100644 core/ui/src/main/res/values-de/strings-app.xml delete mode 100644 core/ui/src/main/res/values-fr/strings-app.xml delete mode 100644 core/ui/src/main/res/values-it/strings-app.xml delete mode 100644 core/ui/src/main/res/values-ru/strings-app.xml delete mode 100644 core/ui/src/main/res/values-ru/strings.xml delete mode 100644 core/ui/src/main/res/values/strings-app.xml delete mode 100644 core/ui/src/main/res/values/strings.xml diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index d1bb43ad95..f25d1ddf4c 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -77,7 +77,7 @@ class DialogManager : StoreSubscriber { is WalletConnectDialog.UnsupportedCard -> SimpleAlertDialog.create( titleRes = R.string.wallet_connect_title, - messageRes = R.string.wallet_connect_scanner_error_no_ethereum_wallet, + messageRes = R.string.wallet_connect_scanner_error_not_valid_card, context = context, ) is WalletConnectDialog.AddNetwork -> diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt index 683302ba73..f75a4922ce 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt @@ -77,7 +77,7 @@ class WarningMessagesManager { priority = WarningMessage.Priority.Critical, listOf(WarningMessage.Location.MainScreen), null, - R.string.alert_title, + R.string.common_warning, R.string.alert_developer_card, WarningMessage.Origin.Local, ) @@ -89,7 +89,7 @@ class WarningMessagesManager { priority = WarningMessage.Priority.Info, listOf(WarningMessage.Location.MainScreen), null, - R.string.alert_title, + R.string.common_warning, R.string.alert_card_signed_transactions, WarningMessage.Origin.Local, ) @@ -142,7 +142,7 @@ class WarningMessagesManager { priority = WarningMessage.Priority.Critical, listOf(WarningMessage.Location.MainScreen), blockchains = null, - titleResId = R.string.alert_title, + titleResId = R.string.common_warning, messageResId = R.string.warning_low_signatures_format, origin = WarningMessage.Origin.Local, messageFormatArg = remainingSignatures.toString(), @@ -155,7 +155,7 @@ class WarningMessagesManager { priority = WarningMessage.Priority.Critical, listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen), null, - R.string.alert_title, + R.string.common_warning, R.string.warning_testnet_card_message, WarningMessage.Origin.Local, ) @@ -167,7 +167,7 @@ class WarningMessagesManager { priority = WarningMessage.Priority.Critical, listOf(WarningMessage.Location.MainScreen), null, - R.string.alert_title, + R.string.common_warning, R.string.alert_demo_message, WarningMessage.Origin.Local, ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt index 92d0c3e0c1..d98e9907b9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt @@ -11,7 +11,7 @@ class AddMoreBackupCardsDialog { companion object { fun create(context: Context): AlertDialog { return AlertDialog.Builder(context).apply { - setTitle(R.string.alert_title) + setTitle(R.string.common_warning) setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added) setPositiveButton(R.string.common_continue) { _, _ -> store.dispatch(BackupAction.ShowAccessCodeInfoScreen) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt index 9b619eef7d..e4fa7525b5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt @@ -10,7 +10,7 @@ class BackupInProgressDialog { companion object { fun create(context: Context): AlertDialog { return AlertDialog.Builder(context).apply { - setTitle(R.string.alert_title) + setTitle(R.string.common_warning) setMessage(R.string.onboarding_backup_exit_warning) setPositiveButton(R.string.warning_button_ok) { _, _ -> store.dispatch(GlobalAction.HideDialog) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt index 697dc71f67..d13afaa088 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/UnfinishedBackupFoundDialog.kt @@ -12,7 +12,7 @@ class UnfinishedBackupFoundDialog { companion object { fun create(context: Context): AlertDialog { return AlertDialog.Builder(context).apply { - setTitle(R.string.alert_title) + setTitle(R.string.common_warning) setMessage(R.string.welcome_interrupted_backup_alert_message) setPositiveButton(R.string.welcome_interrupted_backup_alert_resume) { _, _ -> store.dispatch(GlobalAction.HideDialog) diff --git a/app/src/main/res/layout/dialog_wallet_trade.xml b/app/src/main/res/layout/dialog_wallet_trade.xml index 062d0286d1..cb4e47fecd 100644 --- a/app/src/main/res/layout/dialog_wallet_trade.xml +++ b/app/src/main/res/layout/dialog_wallet_trade.xml @@ -10,7 +10,7 @@ android:layout_width="match_parent" android:layout_height="56dp" android:padding="16dp" - android:text="@string/wallet_dialog_choose_trade_action" + android:text="@string/wallet_choose_trade_action" android:textColor="@color/darkGray2" android:textSize="14sp" /> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml deleted file mode 100644 index c4e3022f03..0000000000 --- a/app/src/main/res/values-de/strings.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - Änderungen speichern - Warnung - Erledigt - Akzeptieren - Entfernen - - Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an - Absenden - ein Wallet erstellen - Tangem - Verifizierte Bilanz - Die Bilanz wird aufgeladen… - Das Konto ist nicht erstellt - Laden Sie %s+ %s auf um ein Konto zu erstellen - Leere Karte - Erstellen Sie ein Wallet um die Tangem-Karte verwenden zu können - Diese Karte wird nicht unterstützt - Ihre Tangem-Karte wurde für die Arbeit mit einer anderen App ausgestellt. Bitte sehen Sie sich den Namen und die Anleitung auf Ihrer Karte an und installieren Sie eine korrekte App - Die Transaktion läuft… - Senden - an %s - Erhalten - von %s - Adresse erkunden - PayString erstellen - PayString erstellen - PayString Name - $payid.tangem.com - PayString sind Ihre einzigartigen Identifikationsdaten wie Telefonnummer, E-Mail-Adresse oder ABN. - Fehlerantwort beim Erstellen der PayString - Diese PayString existiert bereits. Versuchen Sie mit einer anderen erneut. - Adresse oder PayString - Adresse - Absenden - Netzgebühr - Betrag - Gebühr - Gesamt - Höchstbetrag - Niedrig - Normal - Priorität - inkl. Gebühr - %s wird gesendet - %s und %s werden gesendet - Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert - Details - KartenID - Emittent - Signiert - %s Hasch - App Währung - Langes Tippen - Dieser Mechanismus schützt vor Annäherungsangriffen auf eine Karte. Es wird eine Verzögerung zwischen dem Empfang und der Ausführung eines Befehls erzwungen. Nach der ersten signierten Transaktion wird dieses Telefon mit der Karte verknüpft und die Transaktionen werden sofort signiert - Passcode - Bevor Sie einen Befehl ausführen, der eine Änderung des Kartenstatus zur Folge hat, müssen Sie den Passcode eingeben. - Zugangscode - Sie müssen den richtigen Zugangscode eingeben, bevor Sie die Karte scannen. - Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben. - Warnung - - Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet - Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel. - Tippen um zu signieren - Tippen Sie um den Zugangscode zu ändern - Tippen Sie um den Passcode zu ändern - - - Nutzungsbedingungen - - Keine Internetverbindung - PayString wird von der Blockchain nicht unterstützt - PayString ist nicht registriert - PayString-Anfrage ist fehlgeschlagen - - Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein - - Die Adresse wurde erfolgreich kopiert - - - Zuerst geben Sie die gewünschte PayString ein - - Legen Sie die Karte zum Scannen an - Legen Sie die Karte an - - ≈ %s (inkl. Gebühr: %s) - diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml deleted file mode 100644 index 25ebbdc84a..0000000000 --- a/app/src/main/res/values-fr/strings.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - Sauvegarder les modifications - Alerte - Exécuté - J\'accepte - Supprimer - - Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité - Envoyer - Créer un portefeuille - Tangem - Solde confirmé - Solde est en cours de téléchargement … - Compte n\'est pas créé - Pour créer un compte, téléchargez %s+ %s - Carte vide - Créez un portefeuille pour commencer à utiliser votre carte Tangem - Cette carte n\'est pas prise en charge - Votre carte Tangem a été créée pour fonctionner avec une autre application. Regardez le nom et les instructions sur votre carte et installez l\'application pertinente - Transaction en cours… - Envoi - jusqu\'à %s - Réception - de %s - Explorer l\'adresse - Créer PayString - Créer PayString - Dénomination PayString - $payid.tangem.com - Votre PayString est une information qui vous est propre, comme un numéro de téléphone, une adresse du courrier électronique ou un ABN. - Mauvaise réponse lors de la création de PayString - Ce PayString existe déjà. Essayez une autre variante. - Adresse ou PayString - Adresse - Envoyer - Commissions du réseau - Somme - Commissions - Total - Somme maximale - Bas - Normal - Priorité - Inclure les commissions - Sera envoyé %s - Sera envoyé %s et %s - La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps - Référénces - ID de la carte - Emetteur - Signé - %s hashes - Monnaie de l\'application - Tenez la carte fermement - Ce mécanisme protège contre les attaques sans contact sur la carte. Il y a un délai entre la réception et l\'exécution de la commande. Après la première transaction signée, ce téléphone sera associé à la carte et les transactions seront signées immédiatement. - Mot de passe - Avant d\'exécuter une commande qui modifie l\'état de la carte, vous devrez entrer un mot de passe. - Code d\'accès - Vous devrez entrer le mot de passe correct avant de scanner la carte - Cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable. - Attention - Cette carte n\'est pas conçue pour fonctionner avec Tangem - La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement. - Touchez pour signer - Touchez, pour modifier le code d\'accès - Touchez, pour modifier le mot de passe - - Conditions d\'utilisation - - Pas de connexion internet - PayString non pris en charge par la blockchain - PayString non enregistré - La demande de PayString a échoué - L\'adresse est la même que celle de votre portefeuille - L\'adresse a été copiée avec succès - Saisissez d\'abord le PayString requis - Posez pour scanner - Posez la carte - - ≈ %s (incl. les commissions : %s) - diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml deleted file mode 100644 index 832d16e7e1..0000000000 --- a/app/src/main/res/values-it/strings.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - Mantieni le modifiche - Avviso - Fatto - Rimuovere - Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy - Invia - Crea portafoglio - Tangem - Saldo verificato - Il saldo sta per essere caricato… - Conto non creato - Scarica %s+ %s per creare un account - Carta vuota - Crea un portafoglio per iniziare ad utilizzare la tua carta Tangem - Questa carta non è supportata - La tua carta Tangem è stata creata per funzionare con un\'altra applicazione. Leggere il nome e le istruzioni sulla carta e installare l\'applicazione corretta - Transazione in corso… - Invia - su %s - Ricevi - da %s - Cerca indirizzo - Crea PayString - Crea PayString - Nome PayString - $payid.tangem.com - Il tuo PayString – informazioni per te uniche, come il tuo numero di telefono, indirizzo email o ABN. - Errore di risposta durante la creazione del PayString - Questo PayString esiste già. Scegline un altro. - Indirizzo o PayString - Indirizzo - Invia - Costi della rete - Importo - Commissione - Totale - Importo totale - - Insufficiente - Normale - Prioritario - Includi commissione - Sarà inviato %s - Sarà inviato %s e %s - La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo - Requisiti - ID carta - Emittente - Firmato - Hash %s - Valuta dell\'applicazione - Mantenimento della carta - Questo meccanismo protegge dagli avvicinamenti senza contatto sulla carta. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla carta e le transazioni verranno firmate immediatamente. - Password - Dovrai inserire una password prima di eseguire qualsiasi comando che modifichi lo stato della carta. - Codice di accesso - Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto - Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile. - Attenzione - Questa carta non è progettata per funzionare con Tangem - La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento. - Avvicina per firmare - Avvicina per modificare il codice di accesso - Avvicina per modificare la password - - Accetta - Termini del servizio - Nessuna connessione a Internet - - PayString non supportato dalla blockchain - PayString non registrato - Richiesta PayString fallita - - L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio - - L\'indirizzo è stato copiato con successo - - - Inserire prima il PayString desiderato - - Avvicina per scansionare - Avvicina la carta - - ≈ %s (inc. commissione: %s) - diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml index 7378ec8c0d..68343c5dab 100644 --- a/app/src/main/res/values-ru/strings_blockchain.xml +++ b/app/src/main/res/values-ru/strings_blockchain.xml @@ -17,4 +17,6 @@ Нет, отправить все Минимальный баланс: %s Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта. + По умолчанию + Устаревший \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index ad66d064a0..0000000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - Tangem - - Save changes - Warning - Done - Accept - Delete - %s - - You have not given access to your camera, please adjust your privacy settings - - Send - Create wallet - Tangem - - Verified Balance - Balance is loading… - - Account is not created - Load %s+ %s to create account - Empty card - Create wallet to start using Tangem card - This card is not supported - Your Tangem card was made to work with a different application. Please see the name and instructions on your card, and install the correct app - - Transaction is in progress… - to %s - from %s - Receiving - Sending - - Explore address - Create PayString - - Create PayString - PayString name - $payid.tangem.com - Your PayString is information unique to you, like your phone number, email or ABN. - Error response while creating PayString - This PayString already exists. Try a different one. - - Address or PayString - Address - Send - Network fee - Amount - Fee - Total - Maximum amount - Low - Normal - Priority - Include fee - %s will be sent - %s and %s will be sent - Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while - - Details - Card ID - Issuer - Signed - %s hashes - App Currency - Long Tap - This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. - Passcode - Before execution any command entailing a change of the card state you will have to enter the passcode. - Access code - You will have to submit the correct access code before scanning the card - - This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about. - Warning - This card is not designed to work with this app - The card you scanned is a development card. Don\'t accept it as a payment. - - Tap to sign - To create wallet, connect your phone and the card exactly as it shown above - To change the access code, connect your phone and the card exactly as it shown above - To change the passcode, connect your phone and the card exactly as it shown above - - - Terms of Service - - No internet connection - - PayString unsupported by blockchain - PayString not registered - PayString request failed - - Address is the same as wallet address - - Address was copied to clipboard - - - Enter desired PayString first - - Tap to scan - Tap the card - - ≈ %s (inc. fee: %s) - diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml index a8875b9313..40c996a5a3 100644 --- a/app/src/main/res/values/strings_blockchain.xml +++ b/app/src/main/res/values/strings_blockchain.xml @@ -17,4 +17,6 @@ No, send all Minimum balance is %s Destination account is not active. Send %s or more to activate the account. + Default + Legacy \ No newline at end of file diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml deleted file mode 100644 index ba3342790f..0000000000 --- a/app/src/main/res/values/strings_untranslated.xml +++ /dev/null @@ -1,215 +0,0 @@ - - Scan card failed. Try again - - Buy - Sell - Exchange - Default - Legacy - - Create twin wallet - Generate wallet keys on both cards to start using your Twins - - One wallet. Two cards. - This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. - - Card terms of use - - Tangem Twin - This action is irreversible. You will not have access to the old wallet. - Scan the #%s twin card - Preparing card - Creating wallet - Scan the card #%s - You\'ve scanned same card. To create twin wallet you need to scan card with number %d - - //Common - Start - Add - Search - - Send Feedback - Can\'t send a transaction - Reason: %s - Are you having difficulty scanning your card? - I\'m okay - Request support - Send feedback - Really cool! - Can be better - One question - How do you like Tangem? - - //Manage tokens - - Add Token - - //Details - - Please wait for %s transaction to complete to be able to send funds - - Authenticity check failed - This card might be a production sample or counterfeit - - - Important security information \u26A0 - This card has signed transactions in the past - Learn more - This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection. - - There are only %s signatures available on this card. You must withdraw all of your funds. - - //Wallet Connect - Wallet Connect Sessions - Ooops. No Sessions. - No opened WalletConnect sessions - Open session - - Request to create transaction for %s\n%s\n\nAmount: %s\nFee: %s\nTotal: %s\nBalance: %s - - Can\'t send transaction. Not enough funds. - - Request to start a session for\n%s\n\nNETWORK: %s\n\nURL: %s - - Requesting to sign a message.\n\n%s - - - Disconnect - Sign - Sign and send - Reject - - Clipboard contain WalletConnect code. Use copied value or scan QR-code - Paste from clipboard - Scan new code - This card can\'t be used to establish WalletConnect session - The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters. - Failed to establish WalletConnect session: timeout error. Please, try again later. - - This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes. - - - Do you want to buy or sell crypto? - - Oops, the current version of the application is not ready to work with this card, please check for updates. - - - - Activating card - How it works? - Receive crypto - - Create a wallet - Let\'s generate all the keys on your card and create a secure wallet - Create wallet - - Top up your wallet - To get started, simply top up the wallet with any amount - To get started, simply top up the wallet with more than %s %s - Buy crypto - Show the wallet\'s address - - Success! - Your card is activated and ready to be used - Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. - - If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. - The twinning process is partly complete. You can\'t exit it now. - - - Blockchain is unreachable. Try later - - Ok, Got it! - - Success! - Getting started - - Backup now - Skip for later - Finalize the backup process - Add backup card - Scan the card #%d - Scan primary card - - No backup cards - Prepare your card - One backup card added - Two backup cards added - Backup card #%d - - To start the backup process add up to two backup cards. - Prepare the primary card - You can add one more card or finalize the backup process - Max number of cards added. Finalize the backup process. - - You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it in backup, otherwise you can buy it in shop. Do you like to continue backup process? - - Create Access Code - Re-enter your Access Code - Protect - Personalize - Restore - You have to setup a single access code to protect all your wallets - You can set an individual access code on each card later. - The access code can be restored with a linked card, don\'t keep all cards at one place. - Choose any word, phrase, or number you want as your access code. - Access code must be at least 4 characters long - Entered access code didn\'t match initial access code - - Create - Submit - Creating a backup - The backup process is partly complete. You can\'t exit it now. - - Backup wallet - Identical cards - Access code - Access code restore - - You can backup your keys up to two other blank Tangem Wallet cards. - All the backup cards can be used as full-functional with the identical keys. - You will be able to set an access code to protect your wallets. - Access code can be restored with one of backup cards. - - Your wallet card is configured and ready for use. - - You have an interrupted backup. Do you want to resume? - Yes, resume - Discard - This is an irreversible action - If you will discard the backup now, then you will have to reset the cards to factory settings to start over again - Resume backup - Discard - Primary card - Scan the primary card - Continue to my wallet - Sending %s - Prepare the backup card with number %s - Prepare the primary card with number %s - - Dapp %s, requesting to\nsign BNB transaction.\n\n%s - Transaction details:\n -From: %s\n -To: %s\n -Amount: %s - Trade order for %s\n -Price: %s\n -Amount to receive: %s\n -Amount to pay: %s - - Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. - - My suggestions - Can\'t scan a card - Can\'t send a transaction - Tangem feedback - Feedback - Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? - Please tell us more about your issue. Every small detail can help. - Hi support team, - Following information is optional. You can erase it if you don\'t want to share it. - - diff --git a/core/res/src/main/res/values-de/strings-app.xml b/core/res/src/main/res/values-de/strings-app.xml new file mode 100644 index 0000000000..9b09fb053e --- /dev/null +++ b/core/res/src/main/res/values-de/strings-app.xml @@ -0,0 +1,233 @@ + + + Tangem + Änderungen speichern + Warnung + Erledigt + Abbrechen + Akzeptieren + Sie haben keinen Zugang zur Kamera erteilt, bitte passen Sie Ihre Datenschutzeinstellungen an + Absenden + ein Wallet erstellen + Tangem + Verifizierte Bilanz + Die Bilanz wird aufgeladen… + Das Konto ist nicht erstellt + Leere Karte + Erstellen Sie ein Wallet um die Tangem-Karte verwenden zu können + Diese Karte wird nicht unterstützt + Ihre Tangem-Karte wurde für die Arbeit mit einer anderen App ausgestellt. Bitte sehen Sie sich den Namen und die Anleitung auf Ihrer Karte an und installieren Sie eine korrekte App + Die Transaktion läuft… + Erhalten + von %s + Senden + an %s + Adresse erkunden + PayString erstellen + PayString erstellen + PayString Name + $payid.tangem.com + PayString sind Ihre einzigartigen Identifikationsdaten wie Telefonnummer, E-Mail-Adresse oder ABN. + Fehlerantwort beim Erstellen der PayString + Diese PayString existiert bereits. Versuchen Sie mit einer anderen erneut. + Adresse oder PayString + Adresse + Absenden + Netzgebühr + Betrag + Gebühr + Gesamt + Höchstbetrag + Niedrig + Normal + Priorität + inkl. Gebühr + %s wird gesendet + %s und %s werden gesendet + Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert + Details + KartenID + Emittent + Signiert + %s Hasch + App Währung + Langes Tippen + Dieser Mechanismus schützt vor Annäherungsangriffen auf eine Karte. Es wird eine Verzögerung zwischen dem Empfang und der Ausführung eines Befehls erzwungen. Nach der ersten signierten Transaktion wird dieses Telefon mit der Karte verknüpft und die Transaktionen werden sofort signiert + Passcode + Bevor Sie einen Befehl ausführen, der eine Änderung des Kartenstatus zur Folge hat, müssen Sie den Passcode eingeben. + Zugangscode + Sie müssen den richtigen Zugangscode eingeben, bevor Sie die Karte scannen. + Diese Karte wurde früher bereits aufgeladen und Transaktionen wurden damit signiert. Ziehen Sie eine sofortige Auszahlung aller Beträge in Betracht, wenn Sie diese Karte von einer nicht vertrauenswürdigen Quelle erhalten haben. + Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet + Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel. + Tippen um zu signieren + To create wallet, connect your phone and the card exactly as it shown above + Tippen Sie um den Zugangscode zu ändern + Tippen Sie um den Passcode zu ändern + Nutzungsbedingungen + Keine Internetverbindung + PayString wird von der Blockchain nicht unterstützt + PayString ist nicht registriert + PayString-Anfrage ist fehlgeschlagen + Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein + Die Adresse wurde erfolgreich kopiert + Zuerst geben Sie die gewünschte PayString ein + Legen Sie die Karte zum Scannen an + Legen Sie die Karte an + Scan card failed. Try again + Buy + Sell + Exchange + Create twin wallet + Generate wallet keys on both cards to start using your Twins + One wallet. Two cards. + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. + Card terms of use + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Scan the #%s twin card + Preparing card + Creating wallet + Scan the card #%s + You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d + Start + Add + Search + Send Feedback + Can\'t send a transaction + Reason: %s + Are you having difficulty scanning your card? + I\'m okay + Request support + Send feedback + Really cool! + Can be better + One question + How do you like Tangem? + Add custom token + Please wait for %s transaction to complete to be able to send funds + Authenticity check failed + This card might be a production sample or counterfeit + Important security information \u26A0 + This card has signed transactions in the past + Learn more + There are only %s signatures available on this card. You must withdraw all of your funds. + Disconnect + Sign + Sign and send + Reject + This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes. + Do you want to buy or sell crypto? + Oops, the current version of the application is not ready to work with this card, please check for updates. + Activating card + How does it work? + Receive crypto + Create a wallet + Let\'s generate all the keys on your card and create a secure wallet + Create wallet + Top up your wallet + To get started, simply top up the wallet with any amount + To get started, simply top up the wallet with more than %s %s + Buy crypto + Show the wallet\'s address + Success! + Your card is activated and ready to be used + Balance + Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. + The twinning process is partly complete. You can\'t exit it now. + OK, ich hab\'s! + Getting started + Backup now + Skip for later + Finalize the backup process + Add a backup card + Scan the card #%d + Scan primary card + No backup cards + Prepare your card + One backup card added + Two backup cards added + Backup card #%d + To start the backup process add up to two backup cards. + Prepare the primary card + You can add one more card or finalize the backup process + Max number of cards added. Finalize the backup process. + You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? + Create Access Code + Re-enter your Access Code + Protect + Personalize + Restore + You have to set up a single access code to protect all your wallets. + You can set an individual access code on each card later. + The access code can be restored with a linked card, don\'t keep all cards in one place. + Choose any word, phrase, or number you want as your access code. + Access code must be at least 4 characters long + Entered access code didn\'t match the initial access code + Create + Submit + Creating a backup + The backup process is partly complete. You can\'t exit it now. + Backup wallet + Identical cards + Access code + Access code restore + You can backup your keys up to two other blank Tangem Wallet cards. + All the backup cards can be used as full-functional with the identical keys. + You will be able to set an access code to protect your wallets. + Access code can be restored with one of backup cards. + Your wallet card is configured and ready for use. + Link More Cards + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + This is an irreversible action + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + Discard + Scan the primary card + Continue to my wallet + Sending %s + Prepare the backup card with number %s + Prepare the primary card with number %s + Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. + This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection. + Request to create transaction for %s\n%s\n\nAmount: %s\nFee: %s\nTotal: %s\nBalance: %s + Request to start a session for\n%s\n\nNETWORK: %s\n\nURL: %s + WalletConnect Sessions + Ooops. No Sessions. + No opened WalletConnect sessions + Open session + Can\'t send transaction. Not enough funds. + Requesting to sign a message.\n\n%s + Clipboard contain WalletConnect code. Use copied value or scan QR-code + Paste from clipboard + Scan new code + This card can\'t be used to establish WalletConnect session + The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters. + Failed to establish WalletConnect session: timeout error. Please, try again later. + Dapp %s, requesting to\nsign BNB transaction.\n\n%s + Transaction details:\nFrom: %s\nTo: %s\nAmount: %s + Trade order for %s\nPrice: %s\nAmount to receive: %s\nAmount to pay: %s + My suggestions + Can\'t scan a card + Can\'t send a transaction + Tangem feedback + Feedback + Tell us what functions you are missing, and we will try to help you. + Please tell us what card do you have? + Please tell us more about your issue. Every small detail can help. + Hi support team, + The following information is optional. You can erase it if you don\'t want to share it. + ≈ %s (inkl. Gebühr: %s) + Primary Card + Blockchain is unreachable. Try later + Fehler + OK + Failed to establish WalletConnect session. Please, try again later. + + + %s + \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings-blockchain.xml b/core/res/src/main/res/values-de/strings-blockchain.xml new file mode 100644 index 0000000000..6a190d1cbe --- /dev/null +++ b/core/res/src/main/res/values-de/strings-blockchain.xml @@ -0,0 +1,4 @@ + + + Laden Sie %s+ %s auf um ein Konto zu erstellen + \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml index 106dd30cc9..5e5e0bc90b 100644 --- a/core/res/src/main/res/values-de/strings_final.xml +++ b/core/res/src/main/res/values-de/strings_final.xml @@ -113,6 +113,11 @@ Log in with %s Karte scannen Save your Wallet + Would you like to use %s? + Access the app + Log into the app and check your balance without scanning the card + Access code + %s will be requested instead of the access code for interactions with your wallet Note that making a transaction with your funds will still require your card Allow to use %s New feature diff --git a/core/res/src/main/res/values-fr/strings-app.xml b/core/res/src/main/res/values-fr/strings-app.xml new file mode 100644 index 0000000000..8d7d037a69 --- /dev/null +++ b/core/res/src/main/res/values-fr/strings-app.xml @@ -0,0 +1,233 @@ + + + Tangem + Sauvegarder les modifications + Alerte + Exécuté + Annuler + J\'accepte + Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité + Envoyer + Créer un portefeuille + Tangem + Solde confirmé + Solde est en cours de téléchargement … + Compte n\'est pas créé + Carte vide + Créez un portefeuille pour commencer à utiliser votre carte Tangem + Cette carte n\'est pas prise en charge + Votre carte Tangem a été créée pour fonctionner avec une autre application. Regardez le nom et les instructions sur votre carte et installez l\'application pertinente + Transaction en cours… + Réception + de %s + Envoi + jusqu\'à %s + Explorer l\'adresse + Créer PayString + Créer PayString + Dénomination PayString + $payid.tangem.com + Votre PayString est une information qui vous est propre, comme un numéro de téléphone, une adresse du courrier électronique ou un ABN. + Mauvaise réponse lors de la création de PayString + Ce PayString existe déjà. Essayez une autre variante. + Adresse ou PayString + Adresse + Envoyer + Commissions du réseau + Somme + Commissions + Total + Somme maximale + Bas + Normal + Priorité + Inclure les commissions + Sera envoyé %s + Sera envoyé %s et %s + La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps + Référénces + ID de la carte + Emetteur + Signé + %s hashes + Monnaie de l\'application + Tenez la carte fermement + Ce mécanisme protège contre les attaques sans contact sur la carte. Il y a un délai entre la réception et l\'exécution de la commande. Après la première transaction signée, ce téléphone sera associé à la carte et les transactions seront signées immédiatement. + Mot de passe + Avant d\'exécuter une commande qui modifie l\'état de la carte, vous devrez entrer un mot de passe. + Code d\'accès + Vous devrez entrer le mot de passe correct avant de scanner la carte + Cette carte a déjà été rechargée et a signé des transactions avant. Envisagez la possibilité de retirer tous les fonds immédiatement si vous avez reçu cette carte d\'une source non fiable. + Cette carte n\'est pas conçue pour fonctionner avec Tangem + La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement. + Touchez pour signer + To create wallet, connect your phone and the card exactly as it shown above + Touchez, pour modifier le code d\'accès + Touchez, pour modifier le mot de passe + Conditions d\'utilisation + Pas de connexion internet + PayString non pris en charge par la blockchain + PayString non enregistré + La demande de PayString a échoué + L\'adresse est la même que celle de votre portefeuille + L\'adresse a été copiée avec succès + Saisissez d\'abord le PayString requis + Posez pour scanner + Posez la carte + Scan card failed. Try again + Buy + Sell + Exchange + Create twin wallet + Generate wallet keys on both cards to start using your Twins + One wallet. Two cards. + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. + Card terms of use + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Scan the #%s twin card + Preparing card + Creating wallet + Scan the card #%s + You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d + Start + Add + Search + Send Feedback + Can\'t send a transaction + Reason: %s + Are you having difficulty scanning your card? + I\'m okay + Request support + Send feedback + Really cool! + Can be better + One question + How do you like Tangem? + Add custom token + Please wait for %s transaction to complete to be able to send funds + Authenticity check failed + This card might be a production sample or counterfeit + Important security information \u26A0 + This card has signed transactions in the past + Learn more + There are only %s signatures available on this card. You must withdraw all of your funds. + Disconnect + Sign + Sign and send + Reject + This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes. + Do you want to buy or sell crypto? + Oops, the current version of the application is not ready to work with this card, please check for updates. + Activating card + How does it work? + Receive crypto + Create a wallet + Let\'s generate all the keys on your card and create a secure wallet + Create wallet + Top up your wallet + To get started, simply top up the wallet with any amount + To get started, simply top up the wallet with more than %s %s + Buy crypto + Show the wallet\'s address + Success! + Your card is activated and ready to be used + Balance + Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. + The twinning process is partly complete. You can\'t exit it now. + Ok, je l\'ai! + Getting started + Backup now + Skip for later + Finalize the backup process + Add a backup card + Scan the card #%d + Scan primary card + No backup cards + Prepare your card + One backup card added + Two backup cards added + Backup card #%d + To start the backup process add up to two backup cards. + Prepare the primary card + You can add one more card or finalize the backup process + Max number of cards added. Finalize the backup process. + You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? + Create Access Code + Re-enter your Access Code + Protect + Personalize + Restore + You have to set up a single access code to protect all your wallets. + You can set an individual access code on each card later. + The access code can be restored with a linked card, don\'t keep all cards in one place. + Choose any word, phrase, or number you want as your access code. + Access code must be at least 4 characters long + Entered access code didn\'t match the initial access code + Create + Submit + Creating a backup + The backup process is partly complete. You can\'t exit it now. + Backup wallet + Identical cards + Access code + Access code restore + You can backup your keys up to two other blank Tangem Wallet cards. + All the backup cards can be used as full-functional with the identical keys. + You will be able to set an access code to protect your wallets. + Access code can be restored with one of backup cards. + Your wallet card is configured and ready for use. + Link More Cards + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + This is an irreversible action + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + Discard + Scan the primary card + Continue to my wallet + Sending %s + Prepare the backup card with number %s + Prepare the primary card with number %s + Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. + This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection. + Request to create transaction for %s\n%s\n\nAmount: %s\nFee: %s\nTotal: %s\nBalance: %s + Request to start a session for\n%s\n\nNETWORK: %s\n\nURL: %s + WalletConnect Sessions + Ooops. No Sessions. + No opened WalletConnect sessions + Open session + Can\'t send transaction. Not enough funds. + Requesting to sign a message.\n\n%s + Clipboard contain WalletConnect code. Use copied value or scan QR-code + Paste from clipboard + Scan new code + This card can\'t be used to establish WalletConnect session + The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters. + Failed to establish WalletConnect session: timeout error. Please, try again later. + Dapp %s, requesting to\nsign BNB transaction.\n\n%s + Transaction details:\nFrom: %s\nTo: %s\nAmount: %s + Trade order for %s\nPrice: %s\nAmount to receive: %s\nAmount to pay: %s + My suggestions + Can\'t scan a card + Can\'t send a transaction + Tangem feedback + Feedback + Tell us what functions you are missing, and we will try to help you. + Please tell us what card do you have? + Please tell us more about your issue. Every small detail can help. + Hi support team, + The following information is optional. You can erase it if you don\'t want to share it. + ≈ %s (incl. les commissions : %s) + Primary Card + Blockchain is unreachable. Try later + Erreur + OK + Failed to establish WalletConnect session. Please, try again later. + + + %s + \ No newline at end of file diff --git a/core/res/src/main/res/values-fr/strings-blockchain.xml b/core/res/src/main/res/values-fr/strings-blockchain.xml new file mode 100644 index 0000000000..813aba22b5 --- /dev/null +++ b/core/res/src/main/res/values-fr/strings-blockchain.xml @@ -0,0 +1,4 @@ + + + Pour créer un compte, téléchargez %s+ %s + \ No newline at end of file diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml index 740d0d8faf..fdba526339 100644 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ b/core/res/src/main/res/values-fr/strings_final.xml @@ -113,6 +113,11 @@ Log in with %s Scannez la carte Save your Wallet + Would you like to use %s? + Access the app + Log into the app and check your balance without scanning the card + Access code + %s will be requested instead of the access code for interactions with your wallet Note that making a transaction with your funds will still require your card Allow to use %s New feature diff --git a/core/res/src/main/res/values-it/strings-app.xml b/core/res/src/main/res/values-it/strings-app.xml new file mode 100644 index 0000000000..c5e3134366 --- /dev/null +++ b/core/res/src/main/res/values-it/strings-app.xml @@ -0,0 +1,233 @@ + + + Tangem + Mantieni le modifiche + Avviso + Fatto + Annulla + Accetta + Non hai fornito l\'accesso alla tua videocamera, modifica le tue impostazioni sulla privacy + Invia + Crea portafoglio + Tangem + Saldo verificato + Il saldo sta per essere caricato… + Conto non creato + Carta vuota + Crea un portafoglio per iniziare ad utilizzare la tua carta Tangem + Questa carta non è supportata + La tua carta Tangem è stata creata per funzionare con un\'altra applicazione. Leggere il nome e le istruzioni sulla carta e installare l\'applicazione corretta + Transazione in corso… + Riceviu + da %s + Inviau + su %s + Cerca indirizzo + Crea PayString + Crea PayString + Nome PayString + $payid.tangem.com + Il tuo PayString – informazioni per te uniche, come il tuo numero di telefono, indirizzo email o ABN. + Errore di risposta durante la creazione del PayString + Questo PayString esiste già. Scegline un altro. + Indirizzo o PayString + Indirizzo + Invia + Costi della rete + Importo + Commissione + Totale + Importo totale + Insufficiente + Normale + Prioritario + Includi commissione + Sarà inviato %s + Sarà inviato %s e %s + La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo + Requisiti + ID carta + Emittente + Firmato + Hash %s + Valuta dell\'applicazione + Mantenimento della carta + Questo meccanismo protegge dagli avvicinamenti senza contatto sulla carta. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla carta e le transazioni verranno firmate immediatamente. + Password + Dovrai inserire una password prima di eseguire qualsiasi comando che modifichi lo stato della carta. + Codice di accesso + Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto + Questa carta è già stata ricaricata e ha firmato transazioni in passato. Valuta la possibilità di prelevare immediatamente tutti i fondi se hai ricevuto questa carta da una fonte inaffidabile. + Questa carta non è progettata per funzionare con Tangem + La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento. + Avvicina per firmare + To create wallet, connect your phone and the card exactly as it shown above + Avvicina per modificare il codice di accesso + Avvicina per modificare la password + Termini del servizio + Nessuna connessione a Internet + PayString non supportato dalla blockchain + PayString non registrato + Richiesta PayString fallita + L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio + L\'indirizzo è stato copiato con successo + Inserire prima il PayString desiderato + Avvicina per scansionare + Avvicina la carta + Scan card failed. Try again + Buy + Sell + Exchange + Create twin wallet + Generate wallet keys on both cards to start using your Twins + One wallet. Two cards. + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. + Card terms of use + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Scan the #%s twin card + Preparing card + Creating wallet + Scan the card #%s + You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d + Start + Add + Search + Send Feedback + Can\'t send a transaction + Reason: %s + Are you having difficulty scanning your card? + I\'m okay + Request support + Send feedback + Really cool! + Can be better + One question + How do you like Tangem? + Add custom token + Please wait for %s transaction to complete to be able to send funds + Authenticity check failed + This card might be a production sample or counterfeit + Important security information \u26A0 + This card has signed transactions in the past + Learn more + There are only %s signatures available on this card. You must withdraw all of your funds. + Disconnect + Sign + Sign and send + Reject + This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes. + Do you want to buy or sell crypto? + Oops, the current version of the application is not ready to work with this card, please check for updates. + Activating card + How does it work? + Receive crypto + Create a wallet + Let\'s generate all the keys on your card and create a secure wallet + Create wallet + Top up your wallet + To get started, simply top up the wallet with any amount + To get started, simply top up the wallet with more than %s %s + Buy crypto + Show the wallet\'s address + Success! + Your card is activated and ready to be used + Balance + Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. + The twinning process is partly complete. You can\'t exit it now. + Ok, ho capito! + Getting started + Backup now + Skip for later + Finalize the backup process + Add a backup card + Scan the card #%d + Scan primary card + No backup cards + Prepare your card + One backup card added + Two backup cards added + Backup card #%d + To start the backup process add up to two backup cards. + Prepare the primary card + You can add one more card or finalize the backup process + Max number of cards added. Finalize the backup process. + You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? + Create Access Code + Re-enter your Access Code + Protect + Personalize + Restore + You have to set up a single access code to protect all your wallets. + You can set an individual access code on each card later. + The access code can be restored with a linked card, don\'t keep all cards in one place. + Choose any word, phrase, or number you want as your access code. + Access code must be at least 4 characters long + Entered access code didn\'t match the initial access code + Create + Submit + Creating a backup + The backup process is partly complete. You can\'t exit it now. + Backup wallet + Identical cards + Access code + Access code restore + You can backup your keys up to two other blank Tangem Wallet cards. + All the backup cards can be used as full-functional with the identical keys. + You will be able to set an access code to protect your wallets. + Access code can be restored with one of backup cards. + Your wallet card is configured and ready for use. + Link More Cards + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + This is an irreversible action + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + Discard + Scan the primary card + Continue to my wallet + Sending %s + Prepare the backup card with number %s + Prepare the primary card with number %s + Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. + This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection. + Request to create transaction for %s\n%s\n\nAmount: %s\nFee: %s\nTotal: %s\nBalance: %s + Request to start a session for\n%s\n\nNETWORK: %s\n\nURL: %s + WalletConnect Sessions + Ooops. No Sessions. + No opened WalletConnect sessions + Open session + Can\'t send transaction. Not enough funds. + Requesting to sign a message.\n\n%s + Clipboard contain WalletConnect code. Use copied value or scan QR-code + Paste from clipboard + Scan new code + This card can\'t be used to establish WalletConnect session + The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters. + Failed to establish WalletConnect session: timeout error. Please, try again later. + Dapp %s, requesting to\nsign BNB transaction.\n\n%s + Transaction details:\nFrom: %s\nTo: %s\nAmount: %s + Trade order for %s\nPrice: %s\nAmount to receive: %s\nAmount to pay: %s + My suggestions + Can\'t scan a card + Can\'t send a transaction + Tangem feedback + Feedback + Tell us what functions you are missing, and we will try to help you. + Please tell us what card do you have? + Please tell us more about your issue. Every small detail can help. + Hi support team, + The following information is optional. You can erase it if you don\'t want to share it. + ≈ %s (inc. commissione: %s) + Primary Card + Blockchain is unreachable. Try later + Errore + OK + Failed to establish WalletConnect session. Please, try again later. + + + %s + \ No newline at end of file diff --git a/core/res/src/main/res/values-it/strings-blockchain.xml b/core/res/src/main/res/values-it/strings-blockchain.xml new file mode 100644 index 0000000000..5ec38a340a --- /dev/null +++ b/core/res/src/main/res/values-it/strings-blockchain.xml @@ -0,0 +1,4 @@ + + + Scarica %s+ %s per creare un account + \ No newline at end of file diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml index b284d9eb91..b812e23de8 100644 --- a/core/res/src/main/res/values-it/strings_final.xml +++ b/core/res/src/main/res/values-it/strings_final.xml @@ -113,6 +113,11 @@ Log in with %s Scansiona carta Save your Wallet + Would you like to use %s? + Access the app + Log into the app and check your balance without scanning the card + Access code + %s will be requested instead of the access code for interactions with your wallet Note that making a transaction with your funds will still require your card Allow to use %s New feature diff --git a/app/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings-app.xml similarity index 76% rename from app/src/main/res/values-ru/strings.xml rename to core/res/src/main/res/values-ru/strings-app.xml index c248c5c69d..34b9be72db 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings-app.xml @@ -1,29 +1,27 @@ - + Tangem Сохранить изменения Предупреждение Готово + Отмена Принять - Удалить - %s - Вы не предоставили доступ к своей камере, измените настройки конфиденциальности + Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. Отправить Создать кошелек Tangem Подтвержденный баланс - Загрузка баланса… + Баланс загружается… Аккаунт не создан - Пополните счет на %s+ %s, чтобы создать аккаунт Карта пуста Создайте кошелек, чтобы начать пользоваться картой Tangem Эта карта не поддерживается Ваша карта Tangem предназначена для работы с другим приложением. Пожалуйста, ознакомьтесь с названием и инструкциями на вашей карте и установите правильное приложение. - Транзакция в процессе… - до %s - от %s + Транзакция подтверждается… Получение + от %s Отправка + к %s Проверить адрес Создать PayString Создать PayString @@ -35,34 +33,33 @@ Адрес или PayString Адрес Отправить - Комиссия сети + Сетевая комиссия Сумма Комиссия Всего Максимальная сумма - Низкий - Нормальный - Приоритетный - Включить коммисию + Низкая + Нормальная + Приоритетная + Включая комиссию %s будет отправлено %s и %s будет отправлено Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время Подробности - ID карты + Номер карты Эмитент Подписано %s хэшей Валюта приложения - Длительное удержание + Задержка сканирования Этот механизм защищает карту от бесконтактных атак. Между сканированием карты и выполнением команды будет добавлена задержка. Пароль - Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль + Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. Код доступа - Перед сканированием карты вам нужно будет ввести правильный код доступа + Перед сканированием карты вам нужно будет ввести правильный код доступа. Эта карта ранее пополнялась и подписывала транзакции. Выведите средства как можно быстрее, если вы получили эту карту из ненадежного источника. Если это ваша карта, то не о чем беспокоиться. - Предупреждение Эта карта не предназначена для работы с этим приложением - Карта, которую Вы отсканировали, является картой для разработчиков. Не принимайте ее в качестве оплаты. + Карта, которую вы отсканировали, является картой разработчика. Не принимайте её в качестве оплаты. Нажмите, чтобы подписать Чтобы создать кошелек, соедините телефон и карту в точности, как показано выше. Чтобы изменить код доступа, соедините телефон и карту в точности, как показано выше. @@ -73,7 +70,6 @@ PayString не зарегистрирован Не удалось выполнить запрос PayString Адрес совпадает с адресом кошелька - Адрес скопирован в буфер обмена Сначала введите желаемую PayString Приложите, чтобы отсканировать @@ -82,15 +78,13 @@ Купить Продать Обменять - По умолчанию - Устаревший Создать кошелек Сгенерируйте ключи кошелька на обеих картах, чтобы начать использовать свои Twin карты Один кошелек. Две карты. Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька. Условия использования карты Tangem Twin - Это действие необратимо. У Вас не будет доступа к старому кошельку. + Это действие необратимо. У вас не будет доступа к старому кошельку. Отсканируйте twin-карту #%s Подготовка карты Создание кошелька @@ -102,28 +96,28 @@ Отправить отзыв Не могу отправить транзакцию Причина: %s - У Вас возникли трудности со сканированием вашей карты? + У вас возникли трудности со сканированием карты? Отмена Обратиться в поддержку Отправить отзыв Очень круто! - Может быть лучше + Можно лучше Один вопрос - Вам нравится приложение Tangem? + Как вам Tangem? Добавить токен Пожалуйста, дождитесь завершения транзакции %s, чтобы иметь возможность отправить средства Проверка подлинности не удалась Эта карта может быть производственным образцом или подделкой Важная информация о безопасности \u26A0 - Эта карта ранее подписывала транзакции - Узнать подробнее - На этой карте доступно только %s подписей. Вы должны вывести все свои средства. + Эта карта подписывала транзакции в прошлом + Узнать больше + На этой карте осталось только %s подписей. Вы должны вывести все свои средства. Отключить Подписать Подписать и отправить Отклонить - Это тестовая карта. Не принимайте ее в качестве оплаты. Она должна использоваться только в целях тестирования и разработки. - Вы хотите купить или продать криптовалюту? + Это тестовая карта. Не принимайте её в качестве оплаты. Эта карта должна использоваться только в целях тестирования и разработки. + Вы хотите купить или продать криптовалюту? К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений Активация карты Как это работает? @@ -133,7 +127,7 @@ Создать кошелек Пополните свой кошелек Пополните кошелек на любую сумму, чтобы начать пользоваться картой - Чтобы начать, просто пополните кошелек более чем на %s %s + Пополните кошелек более чем на %s %s, чтобы начать пользоваться картой Купить криптовалюту Показать адрес кошелька Успешно! @@ -141,15 +135,13 @@ Баланс Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. - Если процесс создания кошелька каким-либо образом прервется, вам придется начать все сначала. + Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала. Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. - Блокчейн недоступен. Попробуй позже. - Хорошо, понял! - Успешно! + Понятно! Подготовка - Выполнить резервное копирование - Оставить на потом - Завершить процесс резервного копирования + Создать резервную копию + Пропустить + Завершите процесс резервного копирования Добавить резервную карту Сканировать карту #%d Сканировать основную карту @@ -161,73 +153,81 @@ Чтобы начать процесс резервного копирования, добавьте одну или две резервные карты. Подготовьте основную карту Вы можете добавить еще одну карту или завершить процесс резервного копирования - Добавлено максимальное количество карт + Добавлено максимальное количество карт. Завершите процесс резервного копирования. Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? - Создать код доступа - Введите код доступа повторно - Защитить - Персонализировать - Восстановить - Вам необходимо установить единый код доступа для защиты всех ваших кошельков - Позже Вы сможете установить индивидуальный код доступа для каждой карты - Код доступа можно восстановить с помощью привязанной карты, не храните все карты в одном месте - Выберите любое слово, фразу или номер в качестве кода доступа - Код доступа должен состоять не менее чем из 4 символов - Введенный код доступа не соответствует исходному коду доступа + Создайте код доступа + Повторно введите код доступа + Защита + Персонализация + Восстановление + Вам необходимо установить единый код доступа для защиты всех ваших карт. + Позже вы сможете установить индивидуальный код доступа для каждой карты. + Код доступа можно восстановить с помощью привязанной карты. Не храните все карты в одном месте. + Выберите любое слово, фразу или число в качестве кода доступа. + Код доступа должен состоять не менее чем из 4 символов. + Введенные коды доступа не совпадают Создать - Подтвердить - Создание резервной копии - Процесс резервного копирования частично завершен. Вы не можете выйти из него сейчас. - Резервный кошелек + Отправить + Резервная копия + Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. + Резервная копия карты Идентичные карты Код доступа Восстановление кода доступа - Вы можете сделать до двух резервных копий карты Tangem Wallet - Все резервные карты могут использоваться как полнофункциональные с одинаковыми ключами - Вы сможете установить код доступа для защиты своих кошельков - Код доступа можно восстановить с помощью одной из резервных карт + Вы можете сделать резервную копию своих ключей на одной или двух других пустых картах Wallet. + Все резервные карты являются полнофункциональными и содержат одинаковые ключи. + Вы сможете установить код доступа для защиты своих кошельков. + Код доступа можно восстановить с помощью одной из резервных карт. Ваша карта настроена и готова к использованию. - У Вас есть прерванное резервное копирование. Хотите возобновить? + Добавить еще карты + Вы не закончили резервное копирование. Хотите продолжить? Да, возобновить Отказаться Это необратимое действие - Если сейчас отменить резервное копирование, то придется вернуть карты до заводских настроек, чтобы начать заново + Если сейчас отказаться, то придётся сбрасывать карты до заводских настроек, чтобы начать заново Возобновить резервное копирование Отказаться - Основная карта Сканировать основную карту Перейти к моему кошельку Отправка %s Подготовьте резервную карту с номером %s Подготовьте основную карту с номером %s - Сеть Solana взымает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы использовать его бесплатно. - Эта карта не является векселем на предъявителя. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец скрывает автономную подпись, что является проблемой безопасности.\nНе принимайте эту карту в качестве физического платежа от кого-то, кому Вы не доверяете.\nВо всех остальных отношениях - это совершенно безопасно.\nTangem — единственный аппаратный кошелек, предлагающий защиту от подсчета подписей. + Сеть Solana взимает арендную плату в размере %s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %s, чтобы не платить арендную плату. + Эта карта не является платежным средством. В настоящее время мы не можем сопоставить количество подписей на карте с информацией в блокчейне. Это нормально, но в редких случаях может означать, что предыдущий владелец удерживает подписанную транзакцию от публикации, что является поводом для беспокойства.\nНе принимайте эту карту в качестве физического платежа от кого-то, кому вы не доверяете.\nВо всех остальных отношениях эта карта совершенно безопасна.\nTangem — единственный аппаратный кошелек, предлагающий защиту методом подсчета подписей. Запрос на создание транзакции для %s\n%s\n\nСумма: %s\nКомиссия: %s\nВсего: %s\nБаланс: %s Запрос на открытие сессии для\n%s\n\nСЕТЬ: %s\n\nURL: %s Сессии WalletConnect Упс. Нет сессий. Нет открытых сессий WalletConnect Открытая сессия - Не удается отправить транзакцию. Недостаточно средств. + Невозможно отправить транзакцию. Недостаточно средств. Запрос на подпись сообщения.\n\n%s Буфер обмена содержит код WalletConnect. Использовать скопированное значение или отсканировать QR-код Вставить из буфера обмена Сканировать новый код - Эту карту нельзя использовать для установки сеанса WalletConnect. + Эту карту нельзя использовать с WalletConnect. Операция не может быть завершена. \n\nВы уже установили сеанс WalletConnect с этими параметрами. - Не удалось установить сеанс WalletConnect: ошибка времени выполнения. Пожалуйста, повторите попытку позже. + Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже. Dapp %s, запрос на\nподпись транзакции с BNB.\n\n%s - Сведения о транзакции:\nОт: %s\nКому: %s\nСумма: %s + Детали транзакции:\nОт: %s\nК: %s\nСумма: %s Торговый ордер на %s\nЦена: %s\nСумма к получению: %s\nСумма к оплате: %s Мои предложения Не могу отсканировать карту Не могу отправить транзакцию Обращение в поддержку Tangem Обращение в поддержку - Расскажите, каких функций Вам не хватает, и мы постараемся Вам помочь - Скажите, пожалуйста, какая у Вас карта? - Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая маленькая деталь может помочь. + Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. + Скажите, пожалуйста, какая у вас карта? + Пожалуйста, расскажите нам больше о вашей проблеме. Каждая маленькая деталь может помочь. Привет, команда поддержки, Информация ниже не является обязательной. Вы можете стереть её, если хотите. ≈ %s (вкл. комиссию: %s) - + Основная карта + Блокчейн недоступен. Попробуй позже. + Ошибка + Ок + Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. + + + %s + \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings-blockchain.xml b/core/res/src/main/res/values-ru/strings-blockchain.xml new file mode 100644 index 0000000000..d253004626 --- /dev/null +++ b/core/res/src/main/res/values-ru/strings-blockchain.xml @@ -0,0 +1,4 @@ + + + Пополните счет на %s+ %s, чтобы создать аккаунт + \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml index a439e2b3e9..d8a604f287 100644 --- a/core/res/src/main/res/values-ru/strings_final.xml +++ b/core/res/src/main/res/values-ru/strings_final.xml @@ -113,6 +113,11 @@ Войти с %s Сканировать карту Сохраните ваш кошелек + Вы хотите использовать %s? + Доступ в приложение + Войдите в приложение и следите за своим балансом без сканирования карты + Код доступа + Для операций с вашим кошельком будет запрашиваться %s вместо кода доступа карты Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта Использовать %s Новый функционал diff --git a/core/res/src/main/res/values/strings-app.xml b/core/res/src/main/res/values/strings-app.xml new file mode 100644 index 0000000000..4d8976d15d --- /dev/null +++ b/core/res/src/main/res/values/strings-app.xml @@ -0,0 +1,233 @@ + + + Tangem + Save changes + Warning + Done + Cancel + Accept + You have not given access to your camera, please adjust your privacy settings + Send + Create wallet + Tangem + Verified Balance + Balance is loading… + Account is not created + Empty card + Create wallet to start using Tangem card + This card is not supported + Your Tangem card was made to work with a different application. Please see the name and instructions on your card, and install the correct app + Transaction is in progress… + Receiving + from %s + Sending + to %s + Explore address + Create PayString + Create PayString + PayString name + $payid.tangem.com + Your PayString is information unique to you, like your phone number, email or ABN. + Error response while creating PayString + This PayString already exists. Try a different one. + Address or PayString + Address + Send + Network fee + Amount + Fee + Total + Maximum amount + Low + Normal + Priority + Include fee + %s will be sent + %s and %s will be sent + Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while + Details + Card ID + Issuer + Signed + %s hashes + App Currency + Long Tap + This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. + Passcode + Before executing any command entailing a change of the card state you will have to enter the passcode. + Access code + You will have to submit the correct access code before scanning the card + This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about. + This card is not designed to work with this app + The card you scanned is a development card. Don\'t accept it as a payment. + Tap to sign + To create wallet, connect your phone and the card exactly as it shown above + To change the access code, connect your phone and the card exactly as it shown above + To change the passcode, connect your phone and the card exactly as it shown above + Terms of Service + No internet connection + PayString unsupported by blockchain + PayString not registered + PayString request failed + Address is the same as wallet address + Address was copied to clipboard + Enter desired PayString first + Tap to scan + Tap the card + Scan card failed. Try again + Buy + Sell + Exchange + Create twin wallet + Generate wallet keys on both cards to start using your Twins + One wallet. Two cards. + This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet. + Card terms of use + Tangem Twin + This action is irreversible. You will not have access to the old wallet. + Scan the #%s twin card + Preparing card + Creating wallet + Scan the card #%s + You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d + Start + Add + Search + Send Feedback + Can\'t send a transaction + Reason: %s + Are you having difficulty scanning your card? + I\'m okay + Request support + Send feedback + Really cool! + Can be better + One question + How do you like Tangem? + Add custom token + Please wait for %s transaction to complete to be able to send funds + Authenticity check failed + This card might be a production sample or counterfeit + Important security information \u26A0 + This card has signed transactions in the past + Learn more + There are only %s signatures available on this card. You must withdraw all of your funds. + Disconnect + Sign + Sign and send + Reject + This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes. + Do you want to buy or sell crypto? + Oops, the current version of the application is not ready to work with this card, please check for updates. + Activating card + How does it work? + Receive crypto + Create a wallet + Let\'s generate all the keys on your card and create a secure wallet + Create wallet + Top up your wallet + To get started, simply top up the wallet with any amount + To get started, simply top up the wallet with more than %s %s + Buy crypto + Show the wallet\'s address + Success! + Your card is activated and ready to be used + Balance + Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. + The twinning process is partly complete. You can\'t exit it now. + Ok, Got it! + Getting started + Backup now + Skip for later + Finalize the backup process + Add a backup card + Scan the card #%d + Scan primary card + No backup cards + Prepare your card + One backup card added + Two backup cards added + Backup card #%d + To start the backup process add up to two backup cards. + Prepare the primary card + You can add one more card or finalize the backup process + Max number of cards added. Finalize the backup process. + You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? + Create Access Code + Re-enter your Access Code + Protect + Personalize + Restore + You have to set up a single access code to protect all your wallets. + You can set an individual access code on each card later. + The access code can be restored with a linked card, don\'t keep all cards in one place. + Choose any word, phrase, or number you want as your access code. + Access code must be at least 4 characters long + Entered access code didn\'t match the initial access code + Create + Submit + Creating a backup + The backup process is partly complete. You can\'t exit it now. + Backup wallet + Identical cards + Access code + Access code restore + You can backup your keys up to two other blank Tangem Wallet cards. + All the backup cards can be used as full-functional with the identical keys. + You will be able to set an access code to protect your wallets. + Access code can be restored with one of backup cards. + Your wallet card is configured and ready for use. + Link More Cards + You have an interrupted backup. Do you want to resume? + Yes, resume + Discard + This is an irreversible action + If you will discard the backup now, then you will have to reset the cards to factory settings to start over again + Resume backup + Discard + Scan the primary card + Continue to my wallet + Sending %s + Prepare the backup card with number %s + Prepare the primary card with number %s + Solana network charges a rent of %s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. + This card is not a bearer note. We can\'t currently match the signature count on the card with the information on the blockchain. This is normal but in rare cases can mean a previous holder is holding back an offline signature, which is a security concern.\nDo not accept this card as physical payment from someone you don\'t trust.\nIt\'s perfectly safe in all other respects.\nTangem is the only hardware wallet to offer signature count protection. + Request to create transaction for %s\n%s\n\nAmount: %s\nFee: %s\nTotal: %s\nBalance: %s + Request to start a session for\n%s\n\nNETWORK: %s\n\nURL: %s + WalletConnect Sessions + Ooops. No Sessions. + No opened WalletConnect sessions + Open session + Can\'t send transaction. Not enough funds. + Requesting to sign a message.\n\n%s + Clipboard contain WalletConnect code. Use copied value or scan QR-code + Paste from clipboard + Scan new code + This card can\'t be used to establish WalletConnect session + The operation couldn\'t be completed. \n\nYou have already established a WalletConnect session with this parameters. + Failed to establish WalletConnect session: timeout error. Please, try again later. + Dapp %s, requesting to\nsign BNB transaction.\n\n%s + Transaction details:\nFrom: %s\nTo: %s\nAmount: %s + Trade order for %s\nPrice: %s\nAmount to receive: %s\nAmount to pay: %s + My suggestions + Can\'t scan a card + Can\'t send a transaction + Tangem feedback + Feedback + Tell us what functions you are missing, and we will try to help you. + Please tell us what card do you have? + Please tell us more about your issue. Every small detail can help. + Hi support team, + The following information is optional. You can erase it if you don\'t want to share it. + ≈ %s (inc. fee: %s) + Primary Card + Blockchain is unreachable. Try later + Error + OK + Failed to establish WalletConnect session. Please, try again later. + + + %s + \ No newline at end of file diff --git a/core/res/src/main/res/values/strings-blockchain.xml b/core/res/src/main/res/values/strings-blockchain.xml new file mode 100644 index 0000000000..97b6423f40 --- /dev/null +++ b/core/res/src/main/res/values/strings-blockchain.xml @@ -0,0 +1,4 @@ + + + Load %s+ %s to create account + \ No newline at end of file diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml index 8c80f1aeab..c7b9866169 100644 --- a/core/res/src/main/res/values/strings_final.xml +++ b/core/res/src/main/res/values/strings_final.xml @@ -113,6 +113,11 @@ Log in with %s Scan card Save your wallet + Would you like to use %s? + Access the app + Log into the app and check your balance without scanning the card + Access code + %s will be requested instead of the access code for interactions with your wallet Note that making a transaction with your funds will still require your card Allow to use %s New feature diff --git a/core/ui/src/main/res/values-de/strings-app.xml b/core/ui/src/main/res/values-de/strings-app.xml deleted file mode 100644 index 234b37a13d..0000000000 --- a/core/ui/src/main/res/values-de/strings-app.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - Failed to establish WalletConnect session - Link More Cards - Abbrechen - \ No newline at end of file diff --git a/core/ui/src/main/res/values-fr/strings-app.xml b/core/ui/src/main/res/values-fr/strings-app.xml deleted file mode 100644 index e148949c0d..0000000000 --- a/core/ui/src/main/res/values-fr/strings-app.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - Failed to establish WalletConnect session - Link More Cards - Annuler - \ No newline at end of file diff --git a/core/ui/src/main/res/values-it/strings-app.xml b/core/ui/src/main/res/values-it/strings-app.xml deleted file mode 100644 index fc516e10a2..0000000000 --- a/core/ui/src/main/res/values-it/strings-app.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - Failed to establish WalletConnect session - Link More Cards - Annulla - \ No newline at end of file diff --git a/core/ui/src/main/res/values-ru/strings-app.xml b/core/ui/src/main/res/values-ru/strings-app.xml deleted file mode 100644 index b085fe9843..0000000000 --- a/core/ui/src/main/res/values-ru/strings-app.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - Не удалось установить сессию WalletConnect - Добавить еще карты - Отмена - \ No newline at end of file diff --git a/core/ui/src/main/res/values-ru/strings.xml b/core/ui/src/main/res/values-ru/strings.xml deleted file mode 100644 index 61522a01ed..0000000000 --- a/core/ui/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - Хорошо, понял! - \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings-app.xml b/core/ui/src/main/res/values/strings-app.xml deleted file mode 100644 index 73745abf84..0000000000 --- a/core/ui/src/main/res/values/strings-app.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - Failed to establish WalletConnect session - Link More Cards - Cancel - \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml deleted file mode 100644 index 077070e47c..0000000000 --- a/core/ui/src/main/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - Ok, Got it! - \ No newline at end of file From 68e3da1f4cdcde4cf0e3d5fcf65adc9481ac8f4f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 20:29:09 +0300 Subject: [PATCH 116/176] Updated on 2026-08-14 --- .../details/ui/details/DetailsScreenState.kt | 2 +- .../ui/SaveWalletBottomSheetFragment.kt | 10 +- .../ui/components/SaveWalletScreenContent.kt | 211 +++++++++++++----- .../com/tangem/core/ui/components/Spacers.kt | 11 + .../com/tangem/core/ui/res/TangemDimens.kt | 1 + .../res/drawable/ic_face_recognition_24.xml | 12 + .../ui/src/main/res/drawable/ic_lock_24.xml | 0 7 files changed, 190 insertions(+), 57 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_face_recognition_24.xml rename app/src/main/res/drawable/ic_lock.xml => core/ui/src/main/res/drawable/ic_lock_24.xml (100%) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index 68da6ac85f..e55525ae1d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -30,7 +30,7 @@ enum class SettingsElement( LinkMoreCards(R.drawable.ic_more_cards, R.string.details_row_title_create_backup), TermsOfService(R.drawable.ic_text, R.string.disclaimer_title), // General Terms of Service of the App TermsOfUse(R.drawable.ic_text, R.string.details_row_title_card_tou), // Terms of Use for S2C cards only - PrivacyPolicy(R.drawable.ic_lock, R.string.details_row_privacy_policy); + PrivacyPolicy(R.drawable.ic_lock_24, R.string.details_row_privacy_policy); } @Immutable diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt index f3d0d97747..6050723fbd 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -6,7 +6,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels @@ -18,7 +24,7 @@ import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() { - override val expandedHeightFraction: Float = .95f + override val expandedHeightFraction: Float = .98f private val viewModel by viewModels() diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt index 2451c9234a..558eeff759 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt @@ -1,27 +1,34 @@ package com.tangem.tap.features.saveWallet.ui.components import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.capitalize +import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerH24 +import com.tangem.core.ui.components.SpacerH32 +import com.tangem.core.ui.components.SpacerH4 +import com.tangem.core.ui.components.SpacerHHalf +import com.tangem.core.ui.components.SpacerW24 +import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R @@ -34,79 +41,141 @@ internal fun SaveWalletScreenContent( onCloseClick: () -> Unit, ) { Column( - modifier = modifier - .padding(horizontal = TangemTheme.dimens.spacing16), + modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally, ) { - Hand() - IconButton( + Header( + modifier = Modifier.fillMaxWidth(), + onCloseClick = onCloseClick, + ) + SpacerHHalf() + Title( modifier = Modifier - .align(Alignment.End) - .padding(all = TangemTheme.dimens.spacing8) - .size(TangemTheme.dimens.size32), - onClick = onCloseClick, + .widthIn(max = TangemTheme.dimens.size200), + ) + SpacerH32() + Description( + modifier = Modifier + .padding( + start = TangemTheme.dimens.spacing34, + end = TangemTheme.dimens.spacing56, + ) + .fillMaxWidth(), + ) + SpacerHHalf() + Footer( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + showProgress = showProgress, + onSaveWalletClick = onSaveWalletClick, + ) + SpacerH16() + } +} + +@Composable +private fun Header( + modifier: Modifier = Modifier, + onCloseClick: () -> Unit, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + Hand() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_close), - tint = TangemTheme.colors.icon.secondary, - contentDescription = stringResource(id = R.string.common_cancel), - ) + IconButton( + modifier = Modifier.size(TangemTheme.dimens.size32), + onClick = onCloseClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_close), + tint = TangemTheme.colors.icon.secondary, + contentDescription = stringResource(id = R.string.common_cancel), + ) + } + SpacerW8() } - Spacer(modifier = Modifier.weight(.7f)) + } +} + +@Composable +private fun Title( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing32), + ) { Icon( modifier = Modifier.size(TangemTheme.dimens.size56), painter = painterResource(id = R.drawable.ic_fingerprint_24), - tint = TangemTheme.colors.icon.informative, + tint = TangemTheme.colors.icon.primary1, contentDescription = null, ) - SpacerH24() - Box( - modifier = Modifier - .background( - color = TangemTheme.colors.text.accent.copy(alpha = .12f), - shape = TangemTheme.shapes.roundedCornersMedium, - ), - ) { - Text( - modifier = Modifier - .padding( - vertical = TangemTheme.dimens.spacing4, - horizontal = TangemTheme.dimens.spacing12, - ), - text = stringResource(R.string.save_user_wallet_agreement_new_feature), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.accent, - textAlign = TextAlign.Center, - ) - } - SpacerH24() Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.onboarding_navbar_save_wallet), + text = stringResource( + id = R.string.save_user_wallet_agreement_header, + stringResource(id = R.string.common_biometrics).capitalize(Locale.current), + ), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, ) - SpacerH12() - Text( - modifier = Modifier.fillMaxWidth(fraction = .87f), - text = stringResource(R.string.save_user_wallet_agreement_description), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.secondary, - textAlign = TextAlign.Center, + } +} + +@Composable +private fun Description( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), + horizontalAlignment = Alignment.Start, + ) { + DescriptionItem( + iconPainter = painterResource(id = R.drawable.ic_face_recognition_24), + title = stringResource(id = R.string.save_user_wallet_agreement_access_title), + description = stringResource(id = R.string.save_user_wallet_agreement_access_description), ) - Spacer(modifier = Modifier.weight(1f)) + DescriptionItem( + iconPainter = painterResource(id = R.drawable.ic_lock_24), + title = stringResource(id = R.string.save_user_wallet_agreement_code_title), + description = stringResource( + id = R.string.save_user_wallet_agreement_code_description, + stringResource(id = R.string.common_biometrics).capitalize(Locale.current), + ), + ) + } +} + +@Composable +private fun Footer( + modifier: Modifier = Modifier, + showProgress: Boolean, + onSaveWalletClick: () -> Unit, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + ) { PrimaryButton( modifier = Modifier.fillMaxWidth(), showProgress = showProgress, text = stringResource( id = R.string.save_user_wallet_agreement_allow, - stringResource(id = R.string.common_biometric_authentication), + stringResource(id = R.string.common_biometrics).capitalize(Locale.current), ), onClick = onSaveWalletClick, ) - SpacerH16() Text( modifier = Modifier.fillMaxWidth(fraction = .7f), text = stringResource(R.string.save_user_wallet_agreement_notice), @@ -114,7 +183,41 @@ internal fun SaveWalletScreenContent( color = TangemTheme.colors.text.tertiary, textAlign = TextAlign.Center, ) - SpacerH16() + } +} + +@Composable +private fun DescriptionItem( + modifier: Modifier = Modifier, + iconPainter: Painter, + title: String, + description: String, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.Top, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = iconPainter, + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + SpacerW24() + Column { + Text( + text = title, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + SpacerH4() + Text( + text = description, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + } } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt index 7b99b3f27e..fcc5dc2d24 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Spacers.kt @@ -61,6 +61,11 @@ fun SpacerH24(modifier: Modifier = Modifier) { SpacerH(24.dp, modifier) } +@Composable +fun SpacerH28(modifier: Modifier = Modifier) { + SpacerH(28.dp, modifier) +} + @Composable fun SpacerH32(modifier: Modifier = Modifier) { SpacerH(32.dp, modifier) @@ -71,6 +76,12 @@ fun SpacerH32(modifier: Modifier = Modifier) { fun ColumnScope.SpacerHMax(modifier: Modifier = Modifier) { Spacer(modifier = modifier.weight(1f)) } + +// TODO: Refactor with context receivers +@Composable +fun ColumnScope.SpacerHHalf(modifier: Modifier = Modifier) { + Spacer(modifier = modifier.weight(.5f)) +} // endregion Horizontal // region Vertical diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 5b111b5e96..9c88cb58fa 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -60,6 +60,7 @@ data class TangemDimens internal constructor( val spacing16: Dp = 16.dp, val spacing24: Dp = 24.dp, val spacing32: Dp = 32.dp, + val spacing34: Dp = 34.dp, val spacing44: Dp = 44.dp, val spacing50: Dp = 50.dp, val spacing54: Dp = 54.dp, diff --git a/core/ui/src/main/res/drawable/ic_face_recognition_24.xml b/core/ui/src/main/res/drawable/ic_face_recognition_24.xml new file mode 100644 index 0000000000..a0b2816482 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_face_recognition_24.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/core/ui/src/main/res/drawable/ic_lock_24.xml similarity index 100% rename from app/src/main/res/drawable/ic_lock.xml rename to core/ui/src/main/res/drawable/ic_lock_24.xml From e7fc11392a485a1d45cd2e123dbdbd5c08b1615b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Dec 2022 12:56:01 +0300 Subject: [PATCH 117/176] Updated on 2026-08-14 --- .../ui/components/WalletItem.kt | 77 +++++++++++++------ 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index c95d59deec..ada0168d08 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -7,8 +7,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -85,14 +84,15 @@ private fun WalletCardImage( .height(TangemTheme.dimens.size32) .clip(TangemTheme.shapes.roundedCornersSmall2) - val checkedColorFilter by rememberUpdatedState( - newValue = if (isChecked) { + val tintColor = TangemTheme.colors.icon.accent.copy(alpha = .6f) + val checkedColorFilter = remember(isChecked) { + if (isChecked) { ColorFilter.tint( - color = TangemTheme.colors.icon.accent.copy(alpha = .6f), + color = tintColor, blendMode = BlendMode.SrcOver, ) - } else null, - ) + } else null + } SubcomposeAsyncImage( modifier = cardImageModifier, @@ -106,22 +106,15 @@ private fun WalletCardImage( contentDescription = null, ) - if (isSelected) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .size(TangemTheme.dimens.size18) - .background( - color = Color.White, - shape = CircleShape, - ), - contentAlignment = Alignment.Center, - ) { - Icon( - modifier = Modifier.padding(all = (0.5).dp), - painter = painterResource(id = R.drawable.ic_check_circle_18), - tint = TangemTheme.colors.icon.accent, - contentDescription = null, + when { + isChecked -> { + CheckedWalletMark( + modifier = Modifier.matchParentSize(), + ) + } + isSelected -> { + SelectedWalletBadge( + modifier = Modifier.align(Alignment.TopEnd), ) } } @@ -308,4 +301,42 @@ private fun CardImageShimmer( .background(TangemTheme.colors.button.secondary), ) } +} + +@Composable +private fun SelectedWalletBadge( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .size(TangemTheme.dimens.size18) + .background( + color = Color.White, + shape = CircleShape, + ), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.padding(all = (0.5).dp), + painter = painterResource(id = R.drawable.ic_check_circle_18), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + } +} + +@Composable +private fun CheckedWalletMark( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier, + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + tint = TangemTheme.colors.icon.primary2, + contentDescription = null, + ) + } } \ No newline at end of file From 126900b6ffc4b4f52e0b813e0a08cc51149b18b9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Dec 2022 13:30:44 +0300 Subject: [PATCH 118/176] Updated on 2026-08-14 --- .../WallectSelectorScreenContent.kt | 251 +++++++++++------- .../com/tangem/core/ui/res/TangemDimens.kt | 1 + 2 files changed, 158 insertions(+), 94 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 106ff7f37b..5e972b5dfc 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -1,11 +1,23 @@ package com.tangem.tap.features.walletSelector.ui.components -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState @@ -15,7 +27,11 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButtonIconRight +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.SpacerH24 +import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.cardsettings.resolveReference @@ -35,31 +51,16 @@ internal fun WalletSelectorScreenContent( onEditSelectedWalletClick: () -> Unit, onDeleteSelectedWalletsClick: () -> Unit, ) { - val editingWalletsSize by rememberUpdatedState(newValue = state.editingWalletsIds.size) - Column(modifier = modifier) { - Hand() - SpacerH12() - if (editingWalletsSize > 0) { - EditWalletsBar( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - editingWalletsSize = editingWalletsSize, - onClearSelectedClick = onClearSelectedClick, - onEditSelectedWalletClick = onEditSelectedWalletClick, - onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, - ) - } else { - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.user_wallet_list_title), - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - ) - } - SpacerH12() + Header( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + editingWalletsIds = state.editingWalletsIds, + onClearSelectedClick = onClearSelectedClick, + onEditSelectedWalletClick = onEditSelectedWalletClick, + onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, + ) Column( modifier = Modifier .verticalScroll( @@ -76,28 +77,15 @@ internal fun WalletSelectorScreenContent( onWalletLongClick = onWalletLongClick, ) SpacerH24() - if (state.isLocked) { - PrimaryButton( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - text = stringResource( - id = R.string.user_wallet_list_unlock_all, - stringResource(id = R.string.common_biometrics), - ), - showProgress = state.showUnlockProgress, - onClick = onUnlockClick, - ) - SpacerH12() - } - SecondaryButtonIconRight( + Footer( modifier = Modifier .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), - text = stringResource(R.string.user_wallet_list_add_button), - showProgress = state.showAddCardProgress, - icon = painterResource(id = R.drawable.ic_tangem), - onClick = onAddCardClick, + isLocked = state.isLocked, + showUnlockProgress = state.showUnlockProgress, + showAddCardProgress = state.showAddCardProgress, + onUnlockClick = onUnlockClick, + onAddCardClick = onAddCardClick, ) SpacerH16() } @@ -105,62 +93,42 @@ internal fun WalletSelectorScreenContent( } @Composable -private fun EditWalletsBar( +private fun Header( modifier: Modifier = Modifier, - editingWalletsSize: Int, + editingWalletsIds: List, onClearSelectedClick: () -> Unit, onEditSelectedWalletClick: () -> Unit, onDeleteSelectedWalletsClick: () -> Unit, ) { - val showEditAction = remember(editingWalletsSize) { editingWalletsSize in 1 until 2 } - Row( - modifier = modifier - .padding(vertical = TangemTheme.dimens.spacing8) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - IconButton( - modifier = Modifier.size(TangemTheme.dimens.size32), - onClick = onClearSelectedClick, + val editingWalletsSize by rememberUpdatedState(newValue = editingWalletsIds.size) + val hasEditingWallets by remember { + derivedStateOf { editingWalletsSize > 0 } + } + + Column(modifier = modifier) { + Hand() + Box( + modifier = Modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size44), + contentAlignment = Alignment.Center, ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_close), - tint = TangemTheme.colors.icon.primary1, - contentDescription = "Unselect wallets", - ) - } - SpacerW16() - Text( - text = stringResource(id = R.string.user_wallet_list_editing_count, editingWalletsSize), - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, - ) - SpacerWMax() - if (showEditAction) { - IconButton( - modifier = Modifier.size(TangemTheme.dimens.size32), - onClick = onEditSelectedWalletClick, - ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_pencil_outline_24), - tint = TangemTheme.colors.icon.primary1, - contentDescription = "Edit wallet name", + if (hasEditingWallets) { + EditWalletsBar( + modifier = Modifier.fillMaxWidth(), + editingWalletsSize = editingWalletsSize, + onClearSelectedClick = onClearSelectedClick, + onEditSelectedWalletClick = onEditSelectedWalletClick, + onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, + ) + } else { + Text( + text = stringResource(R.string.user_wallet_list_title), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, ) } - SpacerW8() - } - IconButton( - modifier = Modifier.size(TangemTheme.dimens.size32), - onClick = onDeleteSelectedWalletsClick, - ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_trash), - tint = TangemTheme.colors.icon.warning, - contentDescription = "Delete selected wallets", - ) } } } @@ -208,6 +176,101 @@ private fun WalletsList( } } +@Composable +private fun Footer( + modifier: Modifier = Modifier, + isLocked: Boolean, + showUnlockProgress: Boolean, + showAddCardProgress: Boolean, + onUnlockClick: () -> Unit, + onAddCardClick: () -> Unit, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + if (isLocked) { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource( + id = R.string.user_wallet_list_unlock_all, + stringResource(id = R.string.common_biometrics), + ), + showProgress = showUnlockProgress, + onClick = onUnlockClick, + ) + } + SecondaryButtonIconRight( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.user_wallet_list_add_button), + showProgress = showAddCardProgress, + icon = painterResource(id = R.drawable.ic_tangem), + onClick = onAddCardClick, + ) + } +} + +@Composable +private fun EditWalletsBar( + modifier: Modifier = Modifier, + editingWalletsSize: Int, + onClearSelectedClick: () -> Unit, + onEditSelectedWalletClick: () -> Unit, + onDeleteSelectedWalletsClick: () -> Unit, +) { + val showEditAction by remember(editingWalletsSize) { + derivedStateOf { editingWalletsSize in 1 until 2 } + } + + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + IconButton( + modifier = Modifier.size(TangemTheme.dimens.size32), + onClick = onClearSelectedClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_close), + tint = TangemTheme.colors.icon.primary1, + contentDescription = "Unselect wallets", + ) + } + Text( + text = stringResource(id = R.string.user_wallet_list_editing_count, editingWalletsSize), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + SpacerWMax() + if (showEditAction) { + IconButton( + modifier = Modifier.size(TangemTheme.dimens.size32), + onClick = onEditSelectedWalletClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_pencil_outline_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = "Change wallet name", + ) + } + } + IconButton( + modifier = Modifier.size(TangemTheme.dimens.size32), + onClick = onDeleteSelectedWalletsClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_trash), + tint = TangemTheme.colors.icon.warning, + contentDescription = "Delete selected wallets", + ) + } + } +} + // region Preview @Composable private fun WalletSelectorScreenContentSample( diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 5b111b5e96..6fdc0363f4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -40,6 +40,7 @@ data class TangemDimens internal constructor( val size32: Dp = 32.dp, val size40: Dp = 40.dp, val size42: Dp = 42.dp, + val size44: Dp = 44.dp, val size48: Dp = 48.dp, val size56: Dp = 56.dp, val size62: Dp = 62.dp, From c813b59dfa573bb7ec1d5dd1a01e38066ef09a87 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Dec 2022 15:08:58 +0300 Subject: [PATCH 119/176] Updated on 2026-08-14 --- .../main/res/values-de/strings_blockchain.xml | 20 ----------------- .../main/res/values-fr/strings_blockchain.xml | 21 ------------------ .../main/res/values-it/strings_blockchain.xml | 21 ------------------ .../main/res/values-ru/strings_blockchain.xml | 22 ------------------- .../main/res/values/strings_blockchain.xml | 22 ------------------- .../main/res/values-de/strings-blockchain.xml | 19 ++++++++++++++++ .../main/res/values-fr/strings-blockchain.xml | 19 ++++++++++++++++ .../main/res/values-it/strings-blockchain.xml | 19 ++++++++++++++++ .../main/res/values-ru/strings-blockchain.xml | 19 ++++++++++++++++ .../main/res/values/strings-blockchain.xml | 19 ++++++++++++++++ 10 files changed, 95 insertions(+), 106 deletions(-) delete mode 100644 app/src/main/res/values-de/strings_blockchain.xml delete mode 100644 app/src/main/res/values-fr/strings_blockchain.xml delete mode 100644 app/src/main/res/values-it/strings_blockchain.xml delete mode 100644 app/src/main/res/values-ru/strings_blockchain.xml delete mode 100644 app/src/main/res/values/strings_blockchain.xml diff --git a/app/src/main/res/values-de/strings_blockchain.xml b/app/src/main/res/values-de/strings_blockchain.xml deleted file mode 100644 index 83e9734f88..0000000000 --- a/app/src/main/res/values-de/strings_blockchain.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - Nicht genug Geld - Interner Fehler der Blockchain - Falsche Gebühr - Minimaler Betrag ist %s - Restbestand zu klein - Das Zielkonto ist nicht erstellt. Der abzusendende Betrag soll %s + Gebühr oder mehr sein - Erhalt der Gebühr fehlgeschlagen - Unbekannter Fehler - Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden - Um %s XTZ reduzieren - Nein, alles senden - Ungültiger Betrag - Der Betrag geht über die Bilanz hinaus - Der Gesamtbetrag geht über die Bilanz hinaus - Die Gebühr geht über die Bilanz hinaus - Minimum balance is %s - Destination account is not active. Send %s or more to activate the account. - \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_blockchain.xml b/app/src/main/res/values-fr/strings_blockchain.xml deleted file mode 100644 index 818ddca1ea..0000000000 --- a/app/src/main/res/values-fr/strings_blockchain.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - Solde insuffisant - Erreur interne de la blockchain - Commission non valide - Le montant minimal est de %s - Le reste est trop petit - Le compte cible n\'a pas été créé. Le montant à envoyer doit être de %s + commissions ou plus - Échec de réception des commissions - Erreur inconnue - Somme incorrecte - Le montant dépasse le solde - Le total dépasse le solde - Les commissions dépassent le solde - Pour ne pas payer une commission élevée la prochaine fois que vous rechargez votre portefeuille, veuillez réduire le montant de %s XTZ - Réduire de %s XTZ - Non, envoyer toute la somme - Minimum balance is %s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Destination account is not active. Send %s or more to activate the account. - \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_blockchain.xml b/app/src/main/res/values-it/strings_blockchain.xml deleted file mode 100644 index e7f2fa6d3f..0000000000 --- a/app/src/main/res/values-it/strings_blockchain.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - Saldo insufficiente - Errore interno della blockchain - Commissione non valida - L\'importo minimo è di %s - L\'importo residuo è molto basso - Errore sconosciuto - L\'importo supera il saldo - Il conto totale non è stato creato L\'importo da inviare deve essere %s + commissione o maggiore - Impossibile ottenere la commissione - Importo non valido - Il totale supera il saldo - La commissione supera il saldo - Per evitare di pagare una commissione maggiore la prossima volta che ricarichi il tuo portafoglio, riduci l\'importo di %s XTZ - Riduci di %s XTZ - No, invia l\'intero importo - Minimum balance is %s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Destination account is not active. Send %s or more to activate the account. - \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings_blockchain.xml b/app/src/main/res/values-ru/strings_blockchain.xml deleted file mode 100644 index 68343c5dab..0000000000 --- a/app/src/main/res/values-ru/strings_blockchain.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - Недостаточный баланс - Внутренняя ошибка блокчейна - Неверная комиссия - Минимальная сумма: %s - Сдача слишком мала - Не удалось получить комиссию - Аккаунт получателя не создан. Сумма для отправки должна быть %s + комиссия или больше - Неизвестная ошибка - Недопустимая сумма - Сумма превышает остаток - Отправляемая сумма превышает остаток - Комиссия превышает остаток - Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ - Уменьшить на %s XTZ - Нет, отправить все - Минимальный баланс: %s - Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта. - По умолчанию - Устаревший - \ No newline at end of file diff --git a/app/src/main/res/values/strings_blockchain.xml b/app/src/main/res/values/strings_blockchain.xml deleted file mode 100644 index 40c996a5a3..0000000000 --- a/app/src/main/res/values/strings_blockchain.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - Insufficient balance - Blockchain internal error - Invalid Fee - Minimum amount is %s - Change is too small - Failed to get fee - Target account is not created. Amount to send should be %s + fee or more - Unknown error - Invalid amount - Amount exceeds balance - Total amount exceeds balance - Fee exceeds balance - To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ - Reduce by %s XTZ - No, send all - Minimum balance is %s - Destination account is not active. Send %s or more to activate the account. - Default - Legacy - \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings-blockchain.xml b/core/res/src/main/res/values-de/strings-blockchain.xml index 6a190d1cbe..076b179fd2 100644 --- a/core/res/src/main/res/values-de/strings-blockchain.xml +++ b/core/res/src/main/res/values-de/strings-blockchain.xml @@ -1,4 +1,23 @@ + Interner Fehler der Blockchain + Minimaler Betrag ist %s + Restbestand zu klein + Erhalt der Gebühr fehlgeschlagen + Nicht genug Geld + Falsche Gebühr + Das Zielkonto ist nicht erstellt. Der abzusendende Betrag soll %s + Gebühr oder mehr sein + Unbekannter Fehler + Minimum balance is %s + Ungültiger Betrag + Der Betrag geht über die Bilanz hinaus + Der Gesamtbetrag geht über die Bilanz hinaus + Die Gebühr geht über die Bilanz hinaus + Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden + Um %s XTZ reduzieren + Nein, alles senden + Destination account is not active. Send %s or more to activate the account. Laden Sie %s+ %s auf um ein Konto zu erstellen + Default + Legacy \ No newline at end of file diff --git a/core/res/src/main/res/values-fr/strings-blockchain.xml b/core/res/src/main/res/values-fr/strings-blockchain.xml index 813aba22b5..4c4fa1b8ff 100644 --- a/core/res/src/main/res/values-fr/strings-blockchain.xml +++ b/core/res/src/main/res/values-fr/strings-blockchain.xml @@ -1,4 +1,23 @@ + Erreur interne de la blockchain + Le montant minimal est de %s + Le reste est trop petit + Échec de réception des commissions + Solde insuffisant + Commission non valid + Le compte cible n\'a pas été créé. Le montant à envoyer doit être de %s + commissions ou plus + Erreur inconnue + Minimum balance is %s + Somme incorrecte + Le montant dépasse le solde + Le total dépasse le solde + Les commissions dépassent le solde + Pour ne pas payer une commission élevée la prochaine fois que vous rechargez votre portefeuille, veuillez réduire le montant de %s XTZ + Réduire de %s XTZ + Non, envoyer toute la somme + Destination account is not active. Send %s or more to activate the account. Pour créer un compte, téléchargez %s+ %s + Default + Legacy \ No newline at end of file diff --git a/core/res/src/main/res/values-it/strings-blockchain.xml b/core/res/src/main/res/values-it/strings-blockchain.xml index 5ec38a340a..2b0cbcb901 100644 --- a/core/res/src/main/res/values-it/strings-blockchain.xml +++ b/core/res/src/main/res/values-it/strings-blockchain.xml @@ -1,4 +1,23 @@ + Errore interno della blockchain + L\'importo minimo è di %s + L\'importo residuo è molto basso + Impossibile ottenere la commissione + Saldo insufficiente + Commissione non valida + Il conto totale non è stato creato L\'importo da inviare deve essere %s + commissione o maggiore + Errore sconosciuto + Minimum balance is %s + Importo non valido + L\'importo supera il saldo + Il totale supera il saldo + La commissione supera il saldo + Per evitare di pagare una commissione maggiore la prossima volta che ricarichi il tuo portafoglio, riduci l\'importo di %s XTZ + Riduci di %s XTZ + No, invia l\'intero importo + Destination account is not active. Send %s or more to activate the account. Scarica %s+ %s per creare un account + Default + Legacy \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings-blockchain.xml b/core/res/src/main/res/values-ru/strings-blockchain.xml index d253004626..31f48e58f7 100644 --- a/core/res/src/main/res/values-ru/strings-blockchain.xml +++ b/core/res/src/main/res/values-ru/strings-blockchain.xml @@ -1,4 +1,23 @@ + Внутренняя ошибка блокчейна + Минимальная сумма: %s + Сдача слишком мала + Не удалось получить комиссию + Недостаточный баланс + Неверная комиссия + Аккаунт получателя не создан. Сумма для отправки должна быть %s + комиссия или больше + Неизвестная ошибка + Минимальный баланс: %s + Недопустимая сумма + Сумма превышает остаток + Отправляемая сумма превышает остаток + Комиссия превышает остаток + Чтобы не платить повышенную комиссию при следующем пополнении кошелька, уменьшите сумму на %s XTZ + Уменьшить на %s XTZ + Нет, отправить все + Аккаунт получателя не активирован. Отправьте %s или более для активации аккаунта. Пополните счет на %s+ %s, чтобы создать аккаунт + По умолчанию + Устаревший \ No newline at end of file diff --git a/core/res/src/main/res/values/strings-blockchain.xml b/core/res/src/main/res/values/strings-blockchain.xml index 97b6423f40..50a4278791 100644 --- a/core/res/src/main/res/values/strings-blockchain.xml +++ b/core/res/src/main/res/values/strings-blockchain.xml @@ -1,4 +1,23 @@ + Blockchain internal error + Minimum amount is %s + Change is too small + Failed to get fee + Insufficient balance + Invalid Fee + Target account is not created. Amount to send should be %s + fee or more + Unknown error + Minimum balance is %s + Invalid amount + Amount exceeds balance + Total amount exceeds balance + Fee exceeds balance + To avoid paying an increased commission the next time you top up your wallet, reduce the amount by %s XTZ + Reduce by %s XTZ + No, send all + Destination account is not active. Send %s or more to activate the account. Load %s+ %s to create account + Default + Legacy \ No newline at end of file From f3e5e0cafc35bfafe46891b79958ede5e16b852d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Dec 2022 15:28:30 +0300 Subject: [PATCH 120/176] Updated on 2026-08-14 --- app/build.gradle.kts | 2 +- core/res/build.gradle.kts | 14 ++++++++++++++ libs/auth/build.gradle.kts | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2deae5eec6..63fbb50de7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,7 +16,7 @@ android { targetSdk = AppConfig.targetSdkVersion versionCode = if (project.hasProperty("versionCode")) { - project.property("versionCode") as Int + (project.property("versionCode") as String).toInt() } else { AppConfig.versionCode } diff --git a/core/res/build.gradle.kts b/core/res/build.gradle.kts index ff743d3748..77151f2a46 100644 --- a/core/res/build.gradle.kts +++ b/core/res/build.gradle.kts @@ -18,4 +18,18 @@ android { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } } \ No newline at end of file diff --git a/libs/auth/build.gradle.kts b/libs/auth/build.gradle.kts index a1ea108aae..b8dd6f840d 100644 --- a/libs/auth/build.gradle.kts +++ b/libs/auth/build.gradle.kts @@ -20,4 +20,18 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 isCoreLibraryDesugaringEnabled = false } + + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } } \ No newline at end of file From f312de53c6802ddca8fd0a0534880d1f3eb1f1bf Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Dec 2022 18:56:07 +0300 Subject: [PATCH 121/176] Updated on 2026-08-14 --- .../tap/domain/scanCard/ScanCardProcessor.kt | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index d834911426..d1935892e4 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -55,29 +55,38 @@ object ScanCardProcessor { ) = withMainContext { onProgressStateChange(true) onScanStateChange(true) - tangemSdkManager.scanProduct( + + tangemSdkManager.changeDisplayedCardIdNumbersCount(null) + + val result = tangemSdkManager.scanProduct( userTokensRepository = userTokensRepository, cardId = cardId, additionalBlockchainsToDerive = additionalBlockchainsToDerive, useBiometricsForAccessCode = useBiometricsForAccessCode, ) + + store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + + result .doOnFailure { error -> onProgressStateChange(false) onScanStateChange(false) onFailure(error) } .doOnSuccess { scanResponse -> + tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) + onScanStateChange(false) checkForUnfinishedBackupForSaltPay( backupService = backupService, scanResponse = scanResponse, onProgressStateChange = { onProgressStateChange(it) }, - nextHandler = { + nextHandler = { scanResponse1 -> showDisclaimerIfNeed( - scanResponse = scanResponse, - nextHandler = { + scanResponse = scanResponse1, + nextHandler = { scanResponse2 -> onScanSuccess( - scanResponse = scanResponse, + scanResponse = scanResponse2, onProgressStateChange = onProgressStateChange, onSuccess = onSuccess, onWalletNotCreated = onWalletNotCreated, @@ -87,7 +96,6 @@ object ScanCardProcessor { ) }, ) - } } From 27b0d0f46195e5520633b99b078a5f709887f167 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 10 Dec 2022 16:42:01 +0300 Subject: [PATCH 122/176] Updated on 2026-08-14 --- .../TotalFiatBalanceCalculator.kt | 22 ++++++++++++- .../DefaultTotalFiatBalanceCalculator.kt | 16 +++++++-- .../redux/middlewares/WalletMiddleware.kt | 33 ++++++++++--------- .../wallet/ui/wallet/MultiWalletView.kt | 32 ++++++++++-------- .../redux/WalletSelectorMiddleware.kt | 4 ++- .../res/layout/layout_card_total_balance.xml | 31 ++++++++++------- 6 files changed, 92 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/TotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/TotalFiatBalanceCalculator.kt index d9b586d3e6..9790310eb0 100644 --- a/app/src/main/java/com/tangem/tap/domain/totalBalance/TotalFiatBalanceCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/TotalFiatBalanceCalculator.kt @@ -5,10 +5,30 @@ import com.tangem.tap.domain.model.WalletStoreModel import java.math.BigDecimal interface TotalFiatBalanceCalculator { + + /** + * Calculate total fiat balance for list of [WalletStoreModel] + * @param prevAmount Previous amount, used in [TotalFiatBalance.Refreshing] + * @param walletStores List of [WalletStoreModel] to calculate fiat amount + * @param initial Initial [TotalFiatBalance] state, used when list of [WalletStoreModel] is empty + * @return [TotalFiatBalance] with state found with the [WalletStoreModel] list + * */ suspend fun calculate( - prevAmount: BigDecimal, + prevAmount: BigDecimal?, walletStores: List, + initial: TotalFiatBalance, ): TotalFiatBalance + /** + * Same as [TotalFiatBalanceCalculator.calculate] but returns null if list of [WalletStoreModel] is empty + * @param prevAmount Previous amount, used in [TotalFiatBalance.Refreshing] + * @param walletStores List of [WalletStoreModel] to calculate fiat amount + * @return [TotalFiatBalance] with state found with the [WalletStoreModel] list + * */ + suspend fun calculateOrNull( + prevAmount: BigDecimal?, + walletStores: List, + ): TotalFiatBalance? + companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt index be318667dd..512e986cbf 100644 --- a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt @@ -11,11 +11,19 @@ import java.math.BigDecimal internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { override suspend fun calculate( - prevAmount: BigDecimal, + prevAmount: BigDecimal?, walletStores: List, + initial: TotalFiatBalance, ): TotalFiatBalance { + return calculateOrNull(prevAmount, walletStores) ?: initial + } + + override suspend fun calculateOrNull( + prevAmount: BigDecimal?, + walletStores: List, + ): TotalFiatBalance? { return if (walletStores.isEmpty()) { - TotalFiatBalance.Loading + null } else { withContext(Dispatchers.Default) { val walletsData = walletStores @@ -25,7 +33,9 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { when (walletsData.findStatus()) { TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading - TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing(prevAmount) + TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing( + amount = prevAmount ?: BigDecimal.ZERO, + ) TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount()) TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(calculateAmount()) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index ea7c79345b..c0daa2d56d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -294,28 +294,29 @@ class WalletMiddleware { } is WalletAction.UserWalletChanged -> Unit is WalletAction.WalletStoresChanged -> { - scope.launch(Dispatchers.Default) { - fetchTotalFiatBalance(action.walletStores, walletState) - findMissedDerivations(action.walletStores) - tryToShowAppRatingWarning(action.walletStores) - } + fetchTotalFiatBalance(action.walletStores, walletState) + findMissedDerivations(action.walletStores) + tryToShowAppRatingWarning(action.walletStores) } is WalletAction.TotalFiatBalanceChanged -> Unit } } private fun fetchTotalFiatBalance(walletStores: List, state: WalletState) { - scope.launch { - val totalFiatBalance = totalFiatBalanceCalculator.calculate( - prevAmount = state.totalBalance?.fiatAmount ?: BigDecimal.ZERO, + scope.launch(Dispatchers.Default) { + val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull( + prevAmount = state.totalBalance?.fiatAmount, walletStores = walletStores, ) - store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance)) + + if (totalFiatBalance != null) { + store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance)) + } } } private fun findMissedDerivations(wallStores: List) { - scope.launch { + scope.launch(Dispatchers.Default) { val missedDerivations = wallStores .filter { store -> store.walletsData.any { it.status is WalletDataModel.MissedDerivation } @@ -327,11 +328,13 @@ class WalletMiddleware { } private fun tryToShowAppRatingWarning(walletStores: List) { - warningsMiddleware.tryToShowAppRatingWarning( - hasNonZeroWallets = walletStores - .flatMap { it.walletsData } - .any { it.status.amount.isGreaterThan(BigDecimal.ZERO) }, - ) + scope.launch(Dispatchers.Default) { + warningsMiddleware.tryToShowAppRatingWarning( + hasNonZeroWallets = walletStores + .flatMap { it.walletsData } + .any { it.status.amount.isGreaterThan(BigDecimal.ZERO) }, + ) + } } private fun showSaveWalletIfNeeded() { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 6703abd9a9..64f83bc76e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -9,7 +9,11 @@ import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Portfolio -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.animateVisibility +import com.tangem.tap.common.extensions.formatAmountAsSpannedString +import com.tangem.tap.common.extensions.getQuantityString +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.CurrenciesRepository @@ -43,6 +47,7 @@ class MultiWalletView : WalletView() { lAddress.root.hide() rowButtons.hide() lSingleWalletBalance.root.hide() + lCardTotalBalance.root.show() rvMultiwallet.show() btnAddToken.show() setupWalletCardNumber(binding) @@ -134,21 +139,20 @@ class MultiWalletView : WalletView() { totalBalance: TotalBalance?, progressState: ProgressState, ) = with(binding.lCardTotalBalance) { - root.isVisible = totalBalance != null - if (totalBalance != null) { - // Skip changes when on refreshing state - if (totalBalance.state == ProgressState.Refreshing || - progressState == ProgressState.Refreshing - ) return@with + if (totalBalance == null) { + veilBalance.animateVisibility(show = true) + root.isVisible = progressState == ProgressState.Loading + } else { + root.isVisible = true - if (totalBalance.state == ProgressState.Loading) { - veilBalance.veil() - } else { - veilBalance.unVeil() + // Skip changes when on refreshing state + if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) { + return@with } - tvProcessing.animateVisibility( - show = totalBalance.state == ProgressState.Error, - ) + + veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading) + tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading) + tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error) tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString( currencySymbol = totalBalance.fiatCurrency.symbol, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index bac210c6d4..4a53049625 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder @@ -26,6 +27,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber +import java.math.BigDecimal internal class WalletSelectorMiddleware { val middleware: Middleware = { _, appStateProvider -> @@ -221,7 +223,6 @@ internal class WalletSelectorMiddleware { is UserWalletModel.Type.MultiCurrency -> type.copy( tokensCount = walletStores.flatMap { it.walletsData }.size, ) - is UserWalletModel.Type.SingleCurrency -> type.copy( blockchainName = walletStores .firstOrNull() @@ -233,6 +234,7 @@ internal class WalletSelectorMiddleware { fiatBalance = totalFiatBalanceCalculator.calculate( prevAmount = fiatBalance.amount, walletStores = walletStores, + initial = TotalFiatBalance.Loaded(BigDecimal.ZERO), ), ) } diff --git a/app/src/main/res/layout/layout_card_total_balance.xml b/app/src/main/res/layout/layout_card_total_balance.xml index c8dc2dd7bd..3f47e6b612 100644 --- a/app/src/main/res/layout/layout_card_total_balance.xml +++ b/app/src/main/res/layout/layout_card_total_balance.xml @@ -27,20 +27,13 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + app:layout_constraintTop_toBottomOf="@id/tv_title"> - + + + Date: Sun, 11 Dec 2022 09:43:03 +0300 Subject: [PATCH 123/176] Updated on 2026-08-14 --- .../com/tangem/tap/common/extensions/Navigation.kt | 6 +++--- .../tap/common/redux/navigation/NavigationAction.kt | 5 ++++- .../common/redux/navigation/NavigationMiddleware.kt | 4 ++-- .../com/tangem/tap/features/home/HomeFragment.kt | 5 +++++ .../saveWallet/redux/SaveWalletMiddleware.kt | 2 +- .../redux/WalletSelectorMiddleware.kt | 4 ++-- .../tap/features/welcome/ui/WelcomeFragment.kt | 13 ++++++++++++- 7 files changed, 29 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 3abbbac6d1..711e7ed172 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -54,12 +54,12 @@ fun FragmentActivity.openFragment( if (screen.isDialogFragment) { (fragment as DialogFragment).show(transaction, screen.name) if (addToBackstack) { - transaction.addToBackStack(null) + transaction.addToBackStack(screen.name) } } else { transaction.replace(R.id.fragment_container, fragment, screen.name) - if (addToBackstack && (screen != AppScreen.Home && screen != AppScreen.Welcome)) { - transaction.addToBackStack(null) + if (addToBackstack) { + transaction.addToBackStack(screen.name) } transaction.commitAllowingStateLoss() } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt index df26b85edc..3e4f3a9702 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationAction.kt @@ -12,7 +12,10 @@ sealed class NavigationAction : Action { val addToBackstack: Boolean = true, ) : NavigationAction() - data class PopBackTo(val screen: AppScreen? = null) : NavigationAction() + data class PopBackTo( + val screen: AppScreen? = null, + val inclusive: Boolean = false, + ) : NavigationAction() data class OpenUrl(val url: String) : NavigationAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index da9430b1e1..90d6b880da 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -32,13 +32,13 @@ val navigationMiddleware: Middleware = { _, state -> AppScreen.Home, AppScreen.Welcome, -> { - navState?.activity?.get()?.popBackTo(screen = null, inclusive = true) + navState?.activity?.get()?.popBackTo(screen, action.inclusive) if (navState?.backStack?.contains(screen) != true) { store.dispatchOnMain(NavigationAction.NavigateTo(screen)) } } else -> { - navState?.activity?.get()?.popBackTo(screen = action.screen) + navState?.activity?.get()?.popBackTo(screen, action.inclusive) } } } diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index 88a3e28b3c..450d428367 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf @@ -42,6 +43,10 @@ class HomeFragment : Fragment(), StoreSubscriber { return ComposeView(inflater.context).apply { setContent { + BackHandler { + requireActivity().finish() + } + AppCompatTheme { ScreenContent() } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 286b00e96d..b8338279a4 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -82,7 +82,7 @@ internal class SaveWalletMiddleware { userWalletsListManager.selectedUserWalletSync?.walletId == userWallet.walletId if (isSavedWalletSelected) { - store.dispatchOnMain(NavigationAction.PopBackTo()) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) } else { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index bac210c6d4..81ba0b4cf1 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -127,7 +127,7 @@ internal class WalletSelectorMiddleware { store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) if (isSavedWalletSelected) { - store.dispatchOnMain(NavigationAction.PopBackTo()) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.onUserWalletSelected(selectedWallet) } } @@ -141,7 +141,7 @@ internal class WalletSelectorMiddleware { store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } .doOnSuccess { selectedWallet -> - store.dispatchOnMain(NavigationAction.PopBackTo()) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.onUserWalletSelected(selectedWallet) } } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt index 8ccaffcc44..9ff1ad727f 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt @@ -1,12 +1,19 @@ package com.tangem.tap.features.welcome.ui +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.colorResource @@ -39,6 +46,10 @@ internal class WelcomeFragment : ComposeFragment() { setSystemBarsColor(color = backgroundColor) } + BackHandler { + requireActivity().finish() + } + Box( modifier = modifier .systemBarsPadding(), From 0d236333516c7cf6da3103a9c2abbdfe8d987854 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 11 Dec 2022 09:44:49 +0300 Subject: [PATCH 124/176] Updated on 2026-08-14 --- .../tap/features/details/redux/DetailsMiddleware.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 11cd4bb5f4..d991fe21ba 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -121,12 +121,15 @@ class DetailsMiddleware { .flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) } .doOnSuccess { Analytics.send(Settings.CardSettings.FactoryResetFinished()) - val screen = if (userWalletsListManager.hasSavedUserWallets) { - AppScreen.Welcome + + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(selectedUserWallet) } else { - AppScreen.Home + userWalletsListManager.lock() + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } - store.dispatchOnMain(NavigationAction.PopBackTo(screen)) } .doOnFailure { error -> (error as? TangemSdkError)?.let { sdkError -> From 4a14fb92db8b29eedb362eb69f53bef4eb6c7350 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 11 Dec 2022 11:04:10 +0300 Subject: [PATCH 125/176] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TapWalletManager.kt | 2 +- .../tap/features/wallet/redux/WalletAction.kt | 8 +++-- .../tap/features/wallet/redux/WalletState.kt | 7 ++++- .../wallet/redux/reducers/WalletReducer.kt | 13 ++++++++- .../wallet/ui/wallet/MultiWalletView.kt | 29 ++++++++++--------- .../wallet/ui/wallet/SingleWalletView.kt | 1 + 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 21b0ef03bf..7eeb396463 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -147,7 +147,7 @@ class TapWalletManager { updateConfigManager(data) withMainContext { - store.dispatch(WalletAction.ResetState(data.card.cardId)) + store.dispatch(WalletAction.ResetState(data.card)) store.dispatch(WalletConnectAction.ResetState) store.dispatch(GlobalAction.SaveScanNoteResponse(data)) store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 1505f4bd2a..be432defdc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -1,7 +1,11 @@ package com.tangem.tap.features.wallet.redux import android.content.Context -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType import com.tangem.domain.common.CardDTO import com.tangem.tap.common.entities.FiatCurrency @@ -20,7 +24,7 @@ import java.math.BigDecimal sealed class WalletAction : Action { - data class ResetState(val newCardId: String) : WalletAction() + data class ResetState(val newCard: CardDTO) : WalletAction() data class SetIfTestnetCard(val isTestnet: Boolean) : WalletAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index cffbcc23bf..b8413753cb 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -14,7 +14,11 @@ import com.tangem.tap.common.toggleWidget.WidgetState import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState -import com.tangem.tap.features.wallet.models.* +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.PendingTransaction +import com.tangem.tap.features.wallet.models.TotalBalance +import com.tangem.tap.features.wallet.models.WalletRent +import com.tangem.tap.features.wallet.models.WalletWarning import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount import com.tangem.tap.features.wallet.redux.reducers.findProgressState import com.tangem.tap.features.wallet.ui.BalanceStatus @@ -43,6 +47,7 @@ data class WalletState( val showBackupWarning: Boolean = false, val missingDerivations: List = emptyList(), val loadingUserTokens: Boolean = false, + val walletCardsCount: Int? = null, ) : StateType { // if you do not delegate - the application crashes on startup, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index e9658e14fa..61ea00554a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -53,7 +53,12 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS is WalletAction.Warnings -> newState = handleCheckSignedHashesActions(action, newState) is WalletAction.MultiWallet -> newState = multiWalletReducer.reduce(action, newState) - is WalletAction.ResetState -> newState = WalletState(cardId = action.newCardId) + is WalletAction.ResetState -> { + newState = WalletState( + cardId = action.newCard.cardId, + walletCardsCount = action.newCard.findCardsCount(), + ) + } is WalletAction.SetIfTestnetCard -> newState = newState.copy(isTestnet = action.isTestnet) is WalletAction.EmptyWallet -> { newState = newState.copy( @@ -333,6 +338,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS showBackupWarning = card.isMultiwalletAllowed && card.settings.isBackupAllowed && card.backupStatus == CardDTO.BackupStatus.NoBackup, + walletCardsCount = card.findCardsCount(), ) } is WalletAction.WalletStoresChanged -> { @@ -366,6 +372,11 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS return newState } +private fun CardDTO.findCardsCount(): Int? { + return (this.backupStatus as? CardDTO.BackupStatus.Active)?.cardCount?.inc() + ?.takeIf { this.isMultiwalletAllowed } +} + @JvmName("walletStoreModelToReduxModel") private fun List.mapToReduxModel( isMultiWalletAllowed: Boolean, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 6703abd9a9..8104a68d9e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -3,13 +3,16 @@ package com.tangem.tap.features.wallet.ui.wallet import android.widget.Button import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager -import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Portfolio -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.animateVisibility +import com.tangem.tap.common.extensions.formatAmountAsSpannedString +import com.tangem.tap.common.extensions.getQuantityString +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.CurrenciesRepository @@ -45,19 +48,8 @@ class MultiWalletView : WalletView() { lSingleWalletBalance.root.hide() rvMultiwallet.show() btnAddToken.show() - setupWalletCardNumber(binding) } - private fun setupWalletCardNumber(binding: FragmentWalletBinding) = with(binding) { - val card = store.state.globalState.scanResponse?.card - if (card?.backupStatus is CardDTO.BackupStatus.Active) { - val cardCount = (card.backupStatus as CardDTO.BackupStatus.Active).cardCount + 1 - tvTwinCardNumber.show() - tvTwinCardNumber.text = tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, cardCount) - } else { - tvTwinCardNumber.hide() - } - } override fun onViewCreated() { setupWalletsRecyclerView() @@ -79,6 +71,7 @@ class MultiWalletView : WalletView() { handleTotalBalance(binding, state.totalBalance, state.state) handleBackupWarning(binding, state.showBackupWarning) handleRescanWarning(binding, state.missingDerivations.isNotEmpty()) + setupWalletCardNumber(binding, state.walletCardsCount) walletsAdapter.submitList(state.walletsData) binding.pbLoadingUserTokens.show(state.loadingUserTokens) @@ -107,6 +100,16 @@ class MultiWalletView : WalletView() { handleErrorStates(state = state, binding = binding, fragment = fragment) } + private fun setupWalletCardNumber(binding: FragmentWalletBinding, walletCardsCount: Int?) = with(binding) { + if (walletCardsCount != null) { + tvTwinCardNumber.show() + tvTwinCardNumber.text = + tvTwinCardNumber.getQuantityString(R.plurals.card_label_card_count, walletCardsCount) + } else { + tvTwinCardNumber.hide() + } + } + private fun handleBackupWarning( binding: FragmentWalletBinding, showBackupWarning: Boolean, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index c0265845aa..abca20330d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -33,6 +33,7 @@ class SingleWalletView : WalletView() { } private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) { + tvTwinCardNumber.hide() rvMultiwallet.hide() btnAddToken.hide() rvPendingTransaction.hide() From fed010a0577369712dd0b486ca2d2b4b11f5fb2c Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 10 Dec 2022 13:07:10 +0300 Subject: [PATCH 126/176] Updated on 2026-08-14 --- .../saveWallet/redux/SaveWalletMiddleware.kt | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index b8338279a4..64101d2fb5 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -59,6 +59,16 @@ internal class SaveWalletMiddleware { } } + /** + + * or from [SaveWalletState.backupInfo] if provided from + * [com.tangem.tap.features.onboarding.OnboardingHelper.trySaveWalletAndNavigateToWalletScreen] + * + * If saved user's wallet was selected then pop back to [AppScreen.Wallet] + * or navigate to [AppScreen.WalletSelector] otherwise + * + * TODO: Update that logic after onboarding and backup features refactoring + * */ private fun saveWallet(state: SaveWalletState) { val scanResponse = state.backupInfo?.scanResponse ?: store.state.globalState.scanResponse @@ -69,6 +79,8 @@ internal class SaveWalletMiddleware { .backupCardsIds(state.backupInfo?.backupCardsIds) .build() + val isFirstSavedWallet = !userWalletsListManager.hasSavedUserWallets + saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) .flatMap { userWalletsListManager.save(userWallet) } .doOnFailure { error -> @@ -76,19 +88,22 @@ internal class SaveWalletMiddleware { } .doOnSuccess { preferencesStorage.shouldSaveUserWallets = true - preferencesStorage.shouldSaveAccessCodes = true + + // Enable saving access codes only if this is the first time user save the wallet + preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet || + preferencesStorage.shouldSaveAccessCodes val isSavedWalletSelected = userWalletsListManager.selectedUserWalletSync?.walletId == userWallet.walletId + store.dispatchOnMain(SaveWalletAction.Save.Success) + if (isSavedWalletSelected) { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) } else { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) } - - store.dispatchOnMain(SaveWalletAction.Save.Success) - store.onUserWalletSelected(userWallet) } } } From af5d54c2bb52ce3eb42ee8e964d19ce41eb9b9b3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 9 Dec 2022 18:44:32 +0300 Subject: [PATCH 127/176] Updated on 2026-08-14 --- .../com/tangem/tap/LockUserWalletsTimer.kt | 6 ++++- .../com/tangem/tap/domain/TangemSdkManager.kt | 7 ------ .../tap/domain/scanCard/ScanCardProcessor.kt | 2 -- .../details/redux/DetailsMiddleware.kt | 24 ++++++++++--------- .../tap/features/home/redux/HomeMiddleware.kt | 5 +++- .../features/tokens/redux/TokensMiddleware.kt | 2 -- .../middlewares/MultiWalletMiddleware.kt | 3 --- .../redux/WalletSelectorMiddleware.kt | 12 +++++++++- .../welcome/redux/WelcomeMiddleware.kt | 10 ++++++++ 9 files changed, 43 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index c4b3d48e73..0bfb76a8ac 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -6,7 +6,11 @@ import androidx.lifecycle.lifecycleScope import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import timber.log.Timber import kotlin.time.Duration diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 4d58f2467d..a1b2dc8e1c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -61,10 +61,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co cardId: String? = null, additionalBlockchainsToDerive: Collection? = null, messageRes: Int? = null, - useBiometricsForAccessCode: Boolean = false, ): CompletionResult { - setAccessCodeRequestPolicy(useBiometricsForAccessCode) - val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) return runTaskAsyncReturnOnMain( runnable = ScanProductTask( @@ -109,9 +106,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun derivePublicKeys( cardId: String, derivations: Map>, - useBiometricsForAccessCode: Boolean = false, ): CompletionResult { - setAccessCodeRequestPolicy(useBiometricsForAccessCode) return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId) } @@ -192,9 +187,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun scanCard( cardId: String? = null, - useBiometricsForAccessCode: Boolean = false, ): CompletionResult { - setAccessCodeRequestPolicy(useBiometricsForAccessCode) return runTaskAsyncReturnOnMain( runnable = ScanTask(), cardId = cardId, diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index d1935892e4..6ab45894b9 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -44,7 +44,6 @@ import kotlinx.coroutines.launch // TODO: Create repository for that object ScanCardProcessor { suspend fun scan( - useBiometricsForAccessCode: Boolean = false, additionalBlockchainsToDerive: Collection? = null, cardId: String? = null, onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {}, @@ -62,7 +61,6 @@ object ScanCardProcessor { userTokensRepository = userTokensRepository, cardId = cardId, additionalBlockchainsToDerive = additionalBlockchainsToDerive, - useBiometricsForAccessCode = useBiometricsForAccessCode, ) store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index d991fe21ba..0a0820e863 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -77,11 +77,7 @@ class DetailsMiddleware { } DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanCard( - cardId = state.scanResponse?.card?.cardId, - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - state.scanResponse?.card?.isAccessCodeSet == true, - ) + tangemSdkManager.scanCard(cardId = state.scanResponse?.card?.cardId) .doOnSuccess { card -> val currentCardId = store.state.globalState.scanResponse?.card ?.userWalletId @@ -220,7 +216,7 @@ class DetailsMiddleware { private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch { if (state.saveWallets == enable) return@launch if (enable) { - saveCurrentWallet() + saveCurrentWallet(state) } else { deleteSavedWallets() if (state.saveAccessCodes) { @@ -233,16 +229,16 @@ class DetailsMiddleware { if (state.saveAccessCodes == enable) return@launch if (enable) { if (!state.saveWallets) { - saveCurrentWallet() + saveCurrentWallet(state) } - saveAccessCodes() + saveAccessCodes(state) } else { deleteSavedAccessCodes() } } - private suspend fun saveCurrentWallet() { - val scanResponse = store.state.detailsState.scanResponse ?: return + private suspend fun saveCurrentWallet(state: DetailsState) { + val scanResponse = state.scanResponse ?: return val userWallet = UserWalletBuilder(scanResponse).build() userWalletsListManager.save(userWallet) @@ -277,8 +273,11 @@ class DetailsMiddleware { } } - private fun saveAccessCodes() { + private fun saveAccessCodes(state: DetailsState) { preferencesStorage.shouldSaveAccessCodes = true + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = state.scanResponse?.card?.isAccessCodeSet == true, + ) store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveAccessCode, @@ -291,6 +290,9 @@ class DetailsMiddleware { tangemSdkManager.clearSavedUserCodes() .doOnSuccess { preferencesStorage.shouldSaveAccessCodes = false + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = false, + ) store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveAccessCode, diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index c33b6d9a9b..8f4282fed9 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -22,6 +22,7 @@ import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -71,8 +72,10 @@ private fun handleHomeAction(action: Action) { private fun readCard() = scope.launch { delay(timeMillis = 200) - ScanCardProcessor.scan( + tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + ) + ScanCardProcessor.scan( onProgressStateChange = { showProgress -> if (showProgress) { changeButtonState(ButtonState.PROGRESS) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index eed12cb6b0..5182944017 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -35,7 +35,6 @@ import com.tangem.tap.domain.tokens.LoadAvailableCoinsService import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -206,7 +205,6 @@ class TokensMiddleware { val result = tangemSdkManager.derivePublicKeys( cardId = card.cardId, derivations = derivations, - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && card.isAccessCodeSet, ) when (result) { is CompletionResult.Success -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 49b46f98f6..55ea7ddbfc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -29,7 +29,6 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.features.wallet.redux.reducers.toWallet -import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.userTokensRepository @@ -183,8 +182,6 @@ class MultiWalletMiddleware { state: WalletState?, ) = scope.launch { ScanCardProcessor.scan( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - selectedWallet.scanResponse.card.isAccessCodeSet, cardId = selectedWallet.cardId, additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, ) { scanResponse -> diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 2b6e9f77e9..3d14fe926b 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -20,6 +20,7 @@ import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import com.tangem.tap.totalFiatBalanceCalculator import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager @@ -129,6 +130,7 @@ internal class WalletSelectorMiddleware { store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) if (isSavedWalletSelected) { + updateAccessCodeRequestPolicy(selectedWallet) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.onUserWalletSelected(selectedWallet) } @@ -143,6 +145,7 @@ internal class WalletSelectorMiddleware { store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } .doOnSuccess { selectedWallet -> + updateAccessCodeRequestPolicy(selectedWallet) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.onUserWalletSelected(selectedWallet) } @@ -176,7 +179,6 @@ internal class WalletSelectorMiddleware { crossinline onCardScanned: suspend (ScanResponse) -> Unit, ) { ScanCardProcessor.scan( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, onSuccess = { onCardScanned(it) }, @@ -210,11 +212,19 @@ internal class WalletSelectorMiddleware { val isSelectedWalletRemoved = prevSelectedWalletId != selectedWallet.walletId.stringValue if (isSelectedWalletRemoved) { + updateAccessCodeRequestPolicy(selectedWallet) store.onUserWalletSelected(selectedWallet) } } } + private fun updateAccessCodeRequestPolicy(userWallet: UserWallet) { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + userWallet.scanResponse.card.isAccessCodeSet, + ) + } + private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( walletStores: List, ): UserWalletModel { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 73556a2d6f..9e18faf7c0 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -10,8 +10,10 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -48,6 +50,11 @@ internal class WelcomeMiddleware { } .doOnSuccess { selectedUserWallet -> if (selectedUserWallet != null) { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + selectedUserWallet.scanResponse.card.isAccessCodeSet, + ) + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) store.onUserWalletSelected(selectedUserWallet) @@ -65,6 +72,9 @@ internal class WelcomeMiddleware { store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error)) } .doOnSuccess { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = false, + ) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) store.onUserWalletSelected(userWallet) From 51ed623916ad3b452d93149122403952012de46d Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 11 Dec 2022 10:26:15 +0300 Subject: [PATCH 128/176] Updated on 2026-08-14 --- .../userWalletList/UserWalletsListManager.kt | 10 ++- .../BiometricUserWalletsListManager.kt | 66 ++++++++----------- .../DummyUserWalletsListManager.kt | 6 +- .../details/redux/DetailsMiddleware.kt | 2 +- .../features/onboarding/OnboardingHelper.kt | 6 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 4 +- .../features/tokens/redux/TokensMiddleware.kt | 2 +- .../middlewares/MultiWalletMiddleware.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 2 +- .../redux/WalletSelectorMiddleware.kt | 2 +- .../tap/persistence/PreferencesStorage.kt | 2 +- 11 files changed, 48 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index 3dc337ae61..62ea1a37a4 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -19,8 +19,14 @@ interface UserWalletsListManager { suspend fun selectWallet(walletId: UserWalletId): CompletionResult - suspend fun save(userWallet: UserWallet): CompletionResult - suspend fun update(userWallet: UserWallet): CompletionResult + /** + * Save user's wallet + * @param userWallet User's wallet to save + * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an + * already saved card + * @return [CompletionResult] operation result + * */ + suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult suspend fun delete(walletIds: List): CompletionResult suspend fun clear(): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index d7ab7b396d..ad4c7597c9 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -110,12 +110,36 @@ internal class BiometricUserWalletsListManager( findSelectedWallet()!! } - override suspend fun save(userWallet: UserWallet): CompletionResult { - return saveInternal(userWallet, override = false) - } + override suspend fun save( + userWallet: UserWallet, + canOverride: Boolean, + ): CompletionResult = withUnlock { + val isWalletSaved = state.value.wallets + .filter { it.isSaved } + .flatMap(UserWallet::cardsInWallet) + .contains(userWallet.cardId) - override suspend fun update(userWallet: UserWallet): CompletionResult { - return saveInternal(userWallet, override = true) + if (isWalletSaved && !canOverride) { + CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) + } else { + keysRepository.save( + walletId = userWallet.walletId, + encryptionKey = userWallet.scanResponse.card.encryptionKey, + ) + .doOnSuccess { keys -> + state.update { prevState -> + prevState.copy( + encryptionKeys = (keys + prevState.encryptionKeys).distinctBy { it.walletId }, + ) + } + } + .flatMap { publicInformationRepository.save(userWallet) } + .flatMap { sensitiveInformationRepository.save(userWallet) } + .flatMap { loadModels() } + .doOnSuccess { + userWallet.isSaved = true + } + } } override suspend fun delete(walletIds: List): CompletionResult { @@ -162,38 +186,6 @@ internal class BiometricUserWalletsListManager( } } - private suspend fun saveInternal( - userWallet: UserWallet, - override: Boolean, - ): CompletionResult = withUnlock { - val isWalletSaved = state.value.wallets - .filter { it.isSaved } - .flatMap(UserWallet::cardsInWallet) - .contains(userWallet.cardId) - - if (isWalletSaved && !override) { - CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) - } else { - keysRepository.save( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - .doOnSuccess { keys -> - state.update { prevState -> - prevState.copy( - encryptionKeys = (keys + prevState.encryptionKeys).distinctBy { it.walletId }, - ) - } - } - .flatMap { publicInformationRepository.save(userWallet) } - .flatMap { sensitiveInformationRepository.save(userWallet) } - .flatMap { loadModels() } - .doOnSuccess { - userWallet.isSaved = true - } - } - } - private suspend inline fun withUnlock( block: () -> CompletionResult, ): CompletionResult { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt index 5135966db1..a3c3a3e929 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt @@ -40,11 +40,7 @@ class DummyUserWalletsListManager : UserWalletsListManager { } } - override suspend fun save(userWallet: UserWallet): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun update(userWallet: UserWallet): CompletionResult { + override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { return CompletionResult.Success(Unit) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 0a0820e863..297cb3d6e3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -246,7 +246,7 @@ class DetailsMiddleware { Timber.e(error, "Wallet saving failed") } .doOnSuccess { - preferencesStorage.shouldShowSaveWallet = false + preferencesStorage.shouldShowSaveUserWalletScreen = false preferencesStorage.shouldSaveUserWallets = true store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 2fe2d59e78..e14d6cff7d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -11,7 +11,6 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -56,8 +55,7 @@ class OnboardingHelper { backupCardsIds: List? = null, ) { when { - userWalletsListManager.hasSavedUserWallets -> scope.launch { - delay(timeMillis = 1_200) + preferencesStorage.shouldSaveUserWallets -> scope.launch { store.dispatchOnMain( SaveWalletAction.ProvideBackupInfo( scanResponse = scanResponse, @@ -68,7 +66,7 @@ class OnboardingHelper { store.dispatchOnMain(SaveWalletAction.Save) } tangemSdkManager.canUseBiometry && - preferencesStorage.shouldShowSaveWallet -> scope.launch { + preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { delay(timeMillis = 1_200) store.dispatchOnMain( SaveWalletAction.ProvideBackupInfo( diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 64101d2fb5..d4717299e0 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -82,7 +82,7 @@ internal class SaveWalletMiddleware { val isFirstSavedWallet = !userWalletsListManager.hasSavedUserWallets saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) - .flatMap { userWalletsListManager.save(userWallet) } + .flatMap { userWalletsListManager.save(userWallet, canOverride = true) } .doOnFailure { error -> store.dispatchOnMain(SaveWalletAction.Save.Error(error)) } @@ -109,7 +109,7 @@ internal class SaveWalletMiddleware { } private fun saveWalletWasShown() { - preferencesStorage.shouldShowSaveWallet = false + preferencesStorage.shouldShowSaveUserWalletScreen = false } private suspend fun saveAccessCodeIfNeeded( diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 5182944017..8465e9d228 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -286,7 +286,7 @@ class TokensMiddleware { ) scope.launch { - userWalletsListManager.update(updatedUserWallet) + userWalletsListManager.save(updatedUserWallet, canOverride = true) .flatMap { walletCurrenciesManager.addCurrencies( userWallet = updatedUserWallet, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 55ea7ddbfc..28f6fcc965 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -189,7 +189,7 @@ class MultiWalletMiddleware { scanResponse = scanResponse, ) - userWalletsListManager.update(userWallet) + userWalletsListManager.save(userWallet, canOverride = true) .doOnSuccess { store.state.globalState.tapWalletManager.loadData(userWallet, refresh = true) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index c0daa2d56d..83cca6c428 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -338,7 +338,7 @@ class WalletMiddleware { } private fun showSaveWalletIfNeeded() { - if (preferencesStorage.shouldShowSaveWallet + if (preferencesStorage.shouldShowSaveUserWalletScreen && tangemSdkManager.canUseBiometry && store.state.navigationState.backStack.lastOrNull() == AppScreen.Wallet ) { diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 3d14fe926b..ae053d797b 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -168,7 +168,7 @@ internal class WalletSelectorMiddleware { scope.launch { userWalletsListManager.get(walletId = UserWalletId(walletId)) .map { it.copy(name = newName) } - .flatMap { userWalletsListManager.update(it) } + .flatMap { userWalletsListManager.save(it, canOverride = true) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 4f4d41a9e4..eaf21b8d83 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -33,7 +33,7 @@ class PreferencesStorage(applicationContext: Application) { get() = preferences.getLong(CHAT_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L } set(value) = preferences.edit { putLong(CHAT_FIRST_LAUNCH_KEY, value ?: 0) } - var shouldShowSaveWallet: Boolean + var shouldShowSaveUserWalletScreen: Boolean get() = preferences.getBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, true) set(value) = preferences.edit { putBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, value) From 4710088975636d1ad36a3e5673f455b03b734ccd Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 11 Dec 2022 15:48:43 +0300 Subject: [PATCH 129/176] Updated on 2026-08-14 --- .../model/builders/WalletStoreBuilder.kt | 68 +++++++++++++------ .../DefaultWalletCurrenciesManager.kt | 4 +- .../DefaultWalletStoresManager.kt | 7 +- 3 files changed, 52 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt index 003c85904a..f41c664276 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -1,8 +1,10 @@ package com.tangem.tap.domain.model.builders import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager -import com.tangem.tap.domain.model.UserWallet +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork @@ -17,29 +19,27 @@ interface WalletStoreBuilder { fun walletManager(walletManager: WalletManager?): WalletStoreBuilder } - interface WalletMangerWalletStoreBuilder : WalletStoreBuilder { - fun blockchainNetwork(blockchainNetwork: BlockchainNetwork?): WalletStoreBuilder - } + interface WalletMangerWalletStoreBuilder : WalletStoreBuilder companion object { operator fun invoke( - userWallet: UserWallet, + userWalletId: UserWalletId, blockchainNetwork: BlockchainNetwork, ): BlockchainNetworkWalletStoreBuilder { - return BlockchainNetworkWalletStoreBuilderImpl(userWallet, blockchainNetwork) + return BlockchainNetworkWalletStoreBuilderImpl(userWalletId, blockchainNetwork) } operator fun invoke( - userWallet: UserWallet, + userWalletId: UserWalletId, walletManager: WalletManager, ): WalletMangerWalletStoreBuilder { - return WalletMangerWalletStoreBuilderImpl(userWallet, walletManager) + return WalletMangerWalletStoreBuilderImpl(userWalletId, walletManager) } } } private class BlockchainNetworkWalletStoreBuilderImpl( - private val userWallet: UserWallet, + private val userWalletId: UserWalletId, private val blockchainNetwork: BlockchainNetwork, ) : WalletStoreBuilder.BlockchainNetworkWalletStoreBuilder { private var walletManager: WalletManager? = null @@ -53,7 +53,7 @@ private class BlockchainNetworkWalletStoreBuilderImpl( val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager) return WalletStoreModel( - userWalletId = userWallet.walletId, + userWalletId = userWalletId, blockchainNetwork = blockchainNetwork, walletManager = walletManager, walletsData = (listOf(blockchainWalletData) + tokensWalletsData), @@ -63,25 +63,20 @@ private class BlockchainNetworkWalletStoreBuilderImpl( } private class WalletMangerWalletStoreBuilderImpl( - private val userWallet: UserWallet, + private val userWalletId: UserWalletId, private val walletManager: WalletManager, ) : WalletStoreBuilder.WalletMangerWalletStoreBuilder { - private var blockchainNetwork: BlockchainNetwork? = null - - override fun blockchainNetwork(blockchainNetwork: BlockchainNetwork?) = this.apply { - this.blockchainNetwork = blockchainNetwork - } override fun build(): WalletStoreModel { - val blockchainNetwork = this.blockchainNetwork ?: BlockchainNetwork.fromWalletManager(walletManager) - val blockchainWalletData = blockchainNetwork.getBlockchainWalletData(walletManager) - val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager) + val wallet = walletManager.wallet + val blockchainWalletData = wallet.blockchain.toBlockchainWalletData(walletManager) + val tokenWalletsData = wallet.getTokens().firstOrNull()?.toTokenWalletData(walletManager) return WalletStoreModel( - userWalletId = userWallet.walletId, - blockchainNetwork = blockchainNetwork, + userWalletId = userWalletId, + blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager), walletManager = walletManager, - walletsData = (listOf(blockchainWalletData) + tokensWalletsData), + walletsData = (listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData)), walletRent = null, ) } @@ -117,6 +112,35 @@ private fun BlockchainNetwork.getTokensWalletsData(walletManager: WalletManager? } } +private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): WalletDataModel { + val wallet = walletManager.wallet + return WalletDataModel( + currency = Currency.Blockchain( + blockchain = this, + derivationPath = wallet.publicKey.derivationPath?.rawPath, + ), + status = WalletDataModel.Loading, + walletAddresses = wallet.createAddressesData(), + existentialDeposit = getExistentialDeposit(walletManager), + fiatRate = null, + ) +} + +private fun Token.toTokenWalletData(walletManager: WalletManager): WalletDataModel { + val wallet = walletManager.wallet + return WalletDataModel( + currency = Currency.Token( + token = this, + blockchain = wallet.blockchain, + derivationPath = wallet.publicKey.derivationPath?.rawPath, + ), + status = WalletDataModel.Loading, + walletAddresses = wallet.createAddressesData(), + existentialDeposit = getExistentialDeposit(walletManager), + fiatRate = null, + ) +} + private fun getExistentialDeposit(walletManager: WalletManager?): BigDecimal? { return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index c664c46c0a..837ff8452c 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -187,8 +187,8 @@ internal class DefaultWalletCurrenciesManager( .flatMap { walletManager -> walletStoresRepository.storeOrUpdate( userWalletId = userWalletId, - walletStore = WalletStoreBuilder(userWallet, walletManager) - .blockchainNetwork(blockchainNetwork) + walletStore = WalletStoreBuilder(userWalletId, blockchainNetwork) + .walletManager(walletManager) .build(), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt index 52224a9f13..45a7f0e0f9 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -122,7 +122,7 @@ internal class DefaultWalletStoresManager( { walletManager -> walletStoresRepository.storeOrUpdate( userWalletId = userWalletId, - walletStore = WalletStoreBuilder(userWallet, blockchainNetwork) + walletStore = WalletStoreBuilder(userWalletId, blockchainNetwork) .walletManager(walletManager) .build(), ) @@ -158,9 +158,10 @@ internal class DefaultWalletStoresManager( refresh = refresh, ) .flatMap { walletManager -> + val userWalletId = userWallet.walletId walletStoresRepository.storeOrUpdate( - userWalletId = userWallet.walletId, - walletStore = WalletStoreBuilder(userWallet, walletManager) + userWalletId = userWalletId, + walletStore = WalletStoreBuilder(userWalletId, walletManager) .build(), ) } From 8df1379bdc4d1fa8b882acaabab5ea777071e4ba Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 15:16:01 +0300 Subject: [PATCH 130/176] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 20 ++++++--- .../java/com/tangem/tap/TapApplication.kt | 2 + .../com/tangem/tap/common/IntentHandler.kt | 18 +++----- .../features/welcome/redux/WelcomeAction.kt | 3 ++ .../welcome/redux/WelcomeMiddleware.kt | 43 +++++++++++++------ .../features/welcome/redux/WelcomeReducer.kt | 1 + .../features/welcome/redux/WelcomeState.kt | 2 + 7 files changed, 57 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index b7c9ad608a..5ef7c985a6 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -15,7 +15,6 @@ import com.tangem.operations.backup.BackupService import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tap.common.ActivityResultCallbackHolder import com.tangem.tap.common.DialogManager -import com.tangem.tap.common.IntentHandler import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.extensions.dispatchOnMain @@ -29,6 +28,7 @@ import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.features.shop.redux.ShopAction +import com.tangem.tap.features.welcome.redux.WelcomeAction import com.tangem.tap.proxy.AppStateHolder import com.tangem.wallet.R import com.tangem.wallet.databinding.ActivityMainBinding @@ -66,7 +66,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private var snackbar: Snackbar? = null private val dialogManager = DialogManager() - private val intentHandler = IntentHandler() private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind) private val onActivityResultCallbacks = mutableListOf() @@ -119,16 +118,23 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted val shopOpened = store.state.shopState.total != null if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) { - val navigateTo = if (userWalletsListManager.hasSavedUserWallets) AppScreen.Welcome else AppScreen.Home - - store.dispatchOnMain(NavigationAction.NavigateTo(navigateTo)) + if (userWalletsListManager.hasSavedUserWallets) { + store.dispatchOnMain(WelcomeAction.HandleDeepLink(intent)) + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Welcome)) + } else { + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Home)) + intentHandler.handleWalletConnectLink(intent) + } } - intentHandler.handleIntent(intent) + intentHandler.handleBackgroundScan(intent) + intentHandler.handleSellCurrencyCallback(intent) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - intentHandler.handleIntent(intent) + intentHandler.handleBackgroundScan(intent) + intentHandler.handleSellCurrencyCallback(intent) + intentHandler.handleWalletConnectLink(intent) } override fun onStart() { diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index d17e83bd33..0d483b43ee 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -16,6 +16,7 @@ import com.tangem.domain.DomainLayer import com.tangem.domain.common.LogConfig import com.tangem.tap.common.AndroidAssetReader import com.tangem.tap.common.AssetReader +import com.tangem.tap.common.IntentHandler import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsFactory import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder @@ -109,6 +110,7 @@ val walletCurrenciesManager by lazy { val totalFiatBalanceCalculator by lazy { TotalFiatBalanceCalculator.provideDefaultImplementation() } +val intentHandler by lazy { IntentHandler() } @HiltAndroidApp class TapApplication : Application(), ImageLoaderFactory { diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt index 1e41053369..cfee84fd69 100644 --- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt @@ -19,13 +19,13 @@ class IntentHandler { private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" - fun handleIntent(intent: Intent?) { - handleBackgroundScan(intent) - handleWalletConnectLink(intent) - handleSellCurrencyCallback(intent) + fun handleWalletConnectLink(intent: Intent?) { + if (intent?.scheme == WalletConnectManager.WC_SCHEME) { + store.dispatch(WalletConnectAction.HandleDeepLink(intent.data?.toString())) + } } - private fun handleBackgroundScan(intent: Intent?) { + fun handleBackgroundScan(intent: Intent?) { if (intent != null && (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action || NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action ) @@ -40,13 +40,7 @@ class IntentHandler { } } - private fun handleWalletConnectLink(intent: Intent?) { - if (intent?.scheme == WalletConnectManager.WC_SCHEME) { - store.dispatch(WalletConnectAction.HandleDeepLink(intent.data?.toString())) - } - } - - private fun handleSellCurrencyCallback(intent: Intent?) { + fun handleSellCurrencyCallback(intent: Intent?) { try { val transactionID = intent?.data?.getQueryParameter(TRANSACTION_ID_PARAM) ?: return diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt index f68e5122d7..681a3cea77 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.welcome.redux +import android.content.Intent import com.tangem.common.core.TangemError import org.rekotlin.Action @@ -14,5 +15,7 @@ internal sealed interface WelcomeAction : Action { data class Error(val error: TangemError) : WelcomeAction } + data class HandleDeepLink(val intent: Intent?) : WelcomeAction + object CloseError : WelcomeAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 9e18faf7c0..88c0038a3c 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.welcome.redux +import android.content.Intent import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.domain.common.ScanResponse @@ -10,6 +11,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.intentHandler import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store @@ -19,30 +21,37 @@ import kotlinx.coroutines.launch import org.rekotlin.Middleware internal class WelcomeMiddleware { - val middleware: Middleware = { _, _ -> + val middleware: Middleware = { _, appStateProvider -> { next -> { action -> - if (action is WelcomeAction) { - handleAction(action) + val appState = appStateProvider() + if (action is WelcomeAction && appState != null) { + handleAction(action, appState.welcomeState) } next(action) } } } - private fun handleAction(action: WelcomeAction) { + private fun handleAction(action: WelcomeAction, state: WelcomeState) { when (action) { - is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometry() - is WelcomeAction.ProceedWithCard -> proceedWithCard() - is WelcomeAction.ProceedWithBiometrics.Error -> Unit - is WelcomeAction.ProceedWithCard.Error -> Unit - is WelcomeAction.ProceedWithBiometrics.Success -> Unit - is WelcomeAction.ProceedWithCard.Success -> Unit - is WelcomeAction.CloseError -> Unit + is WelcomeAction.ProceedWithBiometrics -> { + proceedWithBiometry(state) + } + is WelcomeAction.ProceedWithCard -> { + proceedWithCard(state) + } + is WelcomeAction.ProceedWithBiometrics.Error, + is WelcomeAction.ProceedWithCard.Error, + is WelcomeAction.ProceedWithBiometrics.Success, + is WelcomeAction.ProceedWithCard.Success, + is WelcomeAction.CloseError, + is WelcomeAction.HandleDeepLink, + -> Unit } } - private fun proceedWithBiometry() { + private fun proceedWithBiometry(state: WelcomeState) { scope.launch { userWalletsListManager.unlockWithBiometry() .doOnFailure { error -> @@ -58,12 +67,14 @@ internal class WelcomeMiddleware { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) store.onUserWalletSelected(selectedUserWallet) + + handleDeepLinkIfNeeded(state.deepLinkIntent) } } } } - private fun proceedWithCard() = scope.launch { + private fun proceedWithCard(state: WelcomeState) = scope.launch { scanCardInternal { scanResponse -> val userWallet = UserWalletBuilder(scanResponse).build() @@ -78,10 +89,16 @@ internal class WelcomeMiddleware { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) store.onUserWalletSelected(userWallet) + + handleDeepLinkIfNeeded(state.deepLinkIntent) } } } + private fun handleDeepLinkIfNeeded(intent: Intent?) { + intentHandler.handleWalletConnectLink(intent) + } + private suspend inline fun scanCardInternal( crossinline onCardScanned: suspend (ScanResponse) -> Unit, ) { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt index d5f3e0276b..42a6d4d0e1 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt @@ -12,6 +12,7 @@ internal object WelcomeReducer { private fun internalReduce(action: WelcomeAction, state: WelcomeState): WelcomeState { return when (action) { + is WelcomeAction.HandleDeepLink -> state.copy(deepLinkIntent = action.intent) is WelcomeAction.ProceedWithBiometrics -> state.copy(isUnlockWithBiometricsInProgress = true) is WelcomeAction.ProceedWithCard -> state.copy(isUnlockWithCardInProgress = true) is WelcomeAction.ProceedWithBiometrics.Error -> state.copy( diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt index a4fce23f93..f40046600d 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeState.kt @@ -1,10 +1,12 @@ package com.tangem.tap.features.welcome.redux +import android.content.Intent import com.tangem.common.core.TangemError import org.rekotlin.StateType data class WelcomeState( val isUnlockWithBiometricsInProgress: Boolean = false, val isUnlockWithCardInProgress: Boolean = false, + val deepLinkIntent: Intent? = null, val error: TangemError? = null, ) : StateType \ No newline at end of file From 37f0b6529cfa0727c143145ed4c4dea93413c65a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 15:40:00 +0300 Subject: [PATCH 131/176] Updated on 2026-08-14 --- .../ui/components/SaveWalletScreenContent.kt | 17 +++-------------- .../src/main/res/values-de/strings_final.xml | 3 +++ .../src/main/res/values-fr/strings_final.xml | 3 +++ .../src/main/res/values-it/strings_final.xml | 3 +++ .../src/main/res/values-ru/strings_final.xml | 3 +++ core/res/src/main/res/values/strings_final.xml | 3 +++ 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt index 558eeff759..40b84a07d4 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt @@ -18,8 +18,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.capitalize -import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton @@ -120,10 +118,7 @@ private fun Title( contentDescription = null, ) Text( - text = stringResource( - id = R.string.save_user_wallet_agreement_header, - stringResource(id = R.string.common_biometrics).capitalize(Locale.current), - ), + text = stringResource(id = R.string.save_user_wallet_agreement_header_biometrics), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, @@ -148,10 +143,7 @@ private fun Description( DescriptionItem( iconPainter = painterResource(id = R.drawable.ic_lock_24), title = stringResource(id = R.string.save_user_wallet_agreement_code_title), - description = stringResource( - id = R.string.save_user_wallet_agreement_code_description, - stringResource(id = R.string.common_biometrics).capitalize(Locale.current), - ), + description = stringResource(id = R.string.save_user_wallet_agreement_code_description_biometrics), ) } } @@ -170,10 +162,7 @@ private fun Footer( PrimaryButton( modifier = Modifier.fillMaxWidth(), showProgress = showProgress, - text = stringResource( - id = R.string.save_user_wallet_agreement_allow, - stringResource(id = R.string.common_biometrics).capitalize(Locale.current), - ), + text = stringResource(id = R.string.save_user_wallet_agreement_allow_biometrics), onClick = onSaveWalletClick, ) Text( diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml index 5e5e0bc90b..4c4b27e82d 100644 --- a/core/res/src/main/res/values-de/strings_final.xml +++ b/core/res/src/main/res/values-de/strings_final.xml @@ -114,12 +114,15 @@ Karte scannen Save your Wallet Would you like to use %s? + Would you like to use biometrics? Access the app Log into the app and check your balance without scanning the card Access code %s will be requested instead of the access code for interactions with your wallet + Biometrics will be requested instead of the access code for interactions with your wallet Note that making a transaction with your funds will still require your card Allow to use %s + Allow to use biometrics New feature My Wallets Multi-currency diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml index fdba526339..eb4ebd8094 100644 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ b/core/res/src/main/res/values-fr/strings_final.xml @@ -114,12 +114,15 @@ Scannez la carte Save your Wallet Would you like to use %s? + Would you like to use biometrics? Access the app Log into the app and check your balance without scanning the card Access code %s will be requested instead of the access code for interactions with your wallet + Biometrics will be requested instead of the access code for interactions with your wallet Note that making a transaction with your funds will still require your card Allow to use %s + Allow to use biometrics New feature My Wallets Multi-currency diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml index b812e23de8..e7bf78ce69 100644 --- a/core/res/src/main/res/values-it/strings_final.xml +++ b/core/res/src/main/res/values-it/strings_final.xml @@ -114,12 +114,15 @@ Scansiona carta Save your Wallet Would you like to use %s? + Would you like to use biometrics? Access the app Log into the app and check your balance without scanning the card Access code %s will be requested instead of the access code for interactions with your wallet + Biometrics will be requested instead of the access code for interactions with your wallet Note that making a transaction with your funds will still require your card Allow to use %s + Allow to use biometrics New feature My Wallets Multi-currency diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml index d8a604f287..24a2b4e134 100644 --- a/core/res/src/main/res/values-ru/strings_final.xml +++ b/core/res/src/main/res/values-ru/strings_final.xml @@ -114,12 +114,15 @@ Сканировать карту Сохраните ваш кошелек Вы хотите использовать %s? + Вы хотите использовать биометрию? Доступ в приложение Войдите в приложение и следите за своим балансом без сканирования карты Код доступа Для операций с вашим кошельком будет запрашиваться %s вместо кода доступа карты + Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта Использовать %s + Использовать биометрию Новый функционал Мои кошельки Мультивалютные diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml index c7b9866169..4c90f57a48 100644 --- a/core/res/src/main/res/values/strings_final.xml +++ b/core/res/src/main/res/values/strings_final.xml @@ -114,12 +114,15 @@ Scan card Save your wallet Would you like to use %s? + Would you like to use biometrics? Access the app Log into the app and check your balance without scanning the card Access code %s will be requested instead of the access code for interactions with your wallet + Biometrics will be requested instead of the access code for interactions with your wallet Note that making a transaction with your funds will still require your card Allow to use %s + Allow to use biometrics New feature My Wallets Multi-currency From b35188e47b0efc63dec104bfb99e956b9bc2c763 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 15:43:05 +0300 Subject: [PATCH 132/176] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings_final.xml | 2 +- core/res/src/main/res/values-fr/strings_final.xml | 2 +- core/res/src/main/res/values-it/strings_final.xml | 2 +- core/res/src/main/res/values/strings_final.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml index 4c4b27e82d..8baee29b9a 100644 --- a/core/res/src/main/res/values-de/strings_final.xml +++ b/core/res/src/main/res/values-de/strings_final.xml @@ -221,7 +221,7 @@ Entfernen Privacy policy Enable biometric authorization - It looks like you have biometric authorization disabled, it is necessary to save wallets + It looks like you have biometric authentication disabled, it is necessary to save wallets Enable Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. %d selected diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml index eb4ebd8094..b4bcbfc554 100644 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ b/core/res/src/main/res/values-fr/strings_final.xml @@ -221,7 +221,7 @@ Supprimer Privacy policy Enable biometric authorization - It looks like you have biometric authorization disabled, it is necessary to save wallets + It looks like you have biometric authentication disabled, it is necessary to save wallets Enable Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. %d selected diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml index e7bf78ce69..7d710d3169 100644 --- a/core/res/src/main/res/values-it/strings_final.xml +++ b/core/res/src/main/res/values-it/strings_final.xml @@ -221,7 +221,7 @@ Rimuovere Privacy policy Enable biometric authorization - It looks like you have biometric authorization disabled, it is necessary to save wallets + It looks like you have biometric authentication disabled, it is necessary to save wallets Enable Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. %d selected diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml index 4c90f57a48..2e4774034c 100644 --- a/core/res/src/main/res/values/strings_final.xml +++ b/core/res/src/main/res/values/strings_final.xml @@ -221,7 +221,7 @@ Delete Privacy policy Enable biometric authorization - It looks like you have biometric authorization disabled, it is necessary to save wallets + It looks like you have biometric authentication disabled, it is necessary to save wallets Enable Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. %d selected From b07c9ae620bb9004dd4badf7dc84cc50f3fbf058 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Dec 2022 14:40:31 +0300 Subject: [PATCH 133/176] Updated on 2026-08-14 --- .../ui/appsettings/AppSettingsFragment.kt | 2 +- .../ui/cardsettings/CardSettingsFragment.kt | 4 +-- .../ui/cardsettings/CardSettingsScreen.kt | 21 +++++++++++---- .../ui/common/DetailsComposeElements.kt | 27 ++++++++++++------- .../details/ui/common/TangemSwitch.kt | 10 ++++--- .../details/ui/details/DetailsFragment.kt | 4 +-- .../details/ui/resetcard/ResetCardFragment.kt | 4 +-- .../ui/securitymode/SecurityModeFragment.kt | 4 +-- .../ui/walletconnect/WalletConnectFragment.kt | 4 +-- 9 files changed, 50 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt index 433db0dd49..9e311ef003 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt @@ -36,7 +36,7 @@ class AppSettingsFragment : Fragment(), StoreSubscriber { return ComposeView(requireContext()).apply { setContent { isTransitionGroup = true - AppCompatTheme { + TangemTheme { AppSettingsScreen( state = screenState.value, onBackPressed = { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt index a679020e84..a42f4a1d38 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsFragment.kt @@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState @@ -39,7 +39,7 @@ class CardSettingsFragment : Fragment(), StoreSubscriber { return ComposeView(requireContext()).apply { setContent { isTransitionGroup = true - AppCompatTheme { + TangemTheme { CardSettingsScreen( state = screenState.value, onBackPressed = { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index 2de4f118fb..31941ed1ee 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -15,6 +15,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.layout.ContentScale @@ -23,6 +25,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.details.ui.common.DetailsMainButton import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -34,14 +37,22 @@ fun CardSettingsScreen( onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { + val needReadCard = state.cardDetails == null + val backgroundColor by rememberUpdatedState( + newValue = if (needReadCard) TangemTheme.colors.background.primary + else TangemTheme.colors.background.secondary, + ) + SettingsScreensScaffold( - content = - if (state.cardDetails == null) { - { CardSettingsReadCard(state.onScanCardClick, modifier = modifier) } - } else { - { CardSettings(state = state, modifier = modifier) } + content = { + if (needReadCard) { + CardSettingsReadCard(state.onScanCardClick, modifier = modifier) + } else { + CardSettings(state = state, modifier = modifier) + } }, titleRes = R.string.card_settings_title, + backgroundColor = backgroundColor, onBackClick = onBackPressed, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index d8e6bf1b11..dd6f290ebe 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -25,18 +25,19 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.compose.TangemTypography import com.tangem.wallet.R @Composable fun SettingsScreensScaffold( + modifier: Modifier = Modifier, content: @Composable () -> Unit, background: @Composable (() -> Unit)? = null, fab: @Composable (() -> Unit)? = null, - backgroundColor: Color = colorResource(id = R.color.background_primary), + backgroundColor: Color = TangemTheme.colors.background.secondary, titleRes: Int? = null, onBackClick: () -> Unit, - modifier: Modifier = Modifier, ) { BackHandler(true, onBackClick) @@ -58,9 +59,13 @@ fun SettingsScreensScaffold( Column(modifier = modifier.fillMaxWidth()) { Text( text = stringResource(id = titleRes), - modifier = modifier.padding(start = 20.dp, end = 20.dp, bottom = 52.dp), - style = TangemTypography.headline1, - color = colorResource(id = R.color.text_primary_1), + modifier = modifier.padding( + start = TangemTheme.dimens.spacing20, + end = TangemTheme.dimens.spacing20, + bottom = TangemTheme.dimens.spacing54, + ), + style = TangemTheme.typography.h1, + color = TangemTheme.colors.text.primary1, ) content() } @@ -86,18 +91,20 @@ fun ScreenTitle( @Composable fun EmptyTopBarWithNavigation( - onBackClick: () -> Unit, - backgroundColor: Color = colorResource(id = R.color.background_primary), modifier: Modifier = Modifier, + onBackClick: () -> Unit, + backgroundColor: Color = TangemTheme.colors.background.primary, ) { TopAppBar( + modifier = modifier, title = { }, navigationIcon = { IconButton(onClick = onBackClick) { Icon( - painterResource(id = R.drawable.ic_back), "", - tint = colorResource(id = R.color.icon_primary_1), + painter = painterResource(id = R.drawable.ic_back), + contentDescription = null, + tint = TangemTheme.colors.icon.primary1, ) } }, @@ -108,10 +115,10 @@ fun EmptyTopBarWithNavigation( @Composable fun DetailsMainButton( + modifier: Modifier = Modifier, title: String, enabled: Boolean = true, onClick: (() -> Unit), - modifier: Modifier = Modifier, ) { Button( onClick = onClick, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt index bf5268bf6f..d511348af9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/TangemSwitch.kt @@ -33,10 +33,11 @@ import com.tangem.wallet.R @Composable fun TangemSwitch( modifier: Modifier = Modifier, - enabledColor: Color = colorResource(id = R.color.control_checked), - disabledColor: Color = colorResource(id = R.color.icon_informative), + checkedColor: Color = colorResource(id = R.color.control_checked), + uncheckedColor: Color = colorResource(id = R.color.icon_informative), size: Dp = 48.dp, checked: Boolean = false, + enabled: Boolean = true, onCheckedChange: (Boolean) -> Unit, ) { val transition = updateTransition(checked, label = "SwitchState") @@ -45,8 +46,8 @@ fun TangemSwitch( tween(durationMillis = 200, easing = FastOutLinearInEasing) }, label = "", - ) { enabled -> - if (enabled) enabledColor else disabledColor + ) { isChecked -> + if (isChecked) checkedColor else uncheckedColor } val interactionSource = remember { MutableInteractionSource() } @@ -55,6 +56,7 @@ fun TangemSwitch( .clickable( interactionSource = interactionSource, indication = null, + enabled = enabled, ) { onCheckedChange(!checked) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt index 2c206736e5..4cecec1a8f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsFragment.kt @@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.navigation.NavigationAction @@ -40,7 +40,7 @@ class DetailsFragment : Fragment(), StoreSubscriber { return ComposeView(requireContext()).apply { setContent { isTransitionGroup = true - AppCompatTheme { + TangemTheme { DetailsScreen( state = detailsScreenState.value, onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) }, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt index fa63874859..10270c8ecb 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt @@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store @@ -38,7 +38,7 @@ class ResetCardFragment : Fragment(), StoreSubscriber { return ComposeView(requireContext()).apply { setContent { isTransitionGroup = true - AppCompatTheme { + TangemTheme { ResetCardScreen( state = screenState.value, onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) }, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt index 96d55a808a..567c74c007 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/securitymode/SecurityModeFragment.kt @@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store @@ -38,7 +38,7 @@ class SecurityModeFragment : Fragment(), StoreSubscriber { return ComposeView(requireContext()).apply { setContent { isTransitionGroup = true - AppCompatTheme { + TangemTheme { SecurityModeScreen( state = screenState.value, onBackPressed = { store.dispatch(NavigationAction.PopBackTo()) }, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt index 8d3de292ae..ec42337992 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt @@ -9,7 +9,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState @@ -36,7 +36,7 @@ class WalletConnectFragment : Fragment(), StoreSubscriber { return ComposeView(requireContext()).apply { setContent { isTransitionGroup = true - AppCompatTheme { + TangemTheme { WalletConnectScreen( state = screenState.value, onBackPressed = { From 2c00b9b5e706ba95cd56b87bb8d90b4e357d318b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Dec 2022 20:45:53 +0300 Subject: [PATCH 134/176] Updated on 2026-08-14 --- .../tap/features/details/redux/DetailsAction.kt | 12 ++++++++---- .../saveWallet/ui/SaveWalletBottomSheetFragment.kt | 4 ++-- .../features/saveWallet/ui/SaveWalletScreenState.kt | 2 +- .../features/saveWallet/ui/SaveWalletViewModel.kt | 2 +- .../ui/components}/EnrollBiometricsDialogConent.kt | 4 ++-- .../saveWallet}/ui/models/EnrollBiometricsDialog.kt | 2 +- 6 files changed, 15 insertions(+), 11 deletions(-) rename {core/ui/src/main/java/com/tangem/core/ui/components/dialogs => app/src/main/java/com/tangem/tap/features/saveWallet/ui/components}/EnrollBiometricsDialogConent.kt (96%) rename {core/ui/src/main/java/com/tangem/core => app/src/main/java/com/tangem/tap/features/saveWallet}/ui/models/EnrollBiometricsDialog.kt (74%) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt index bf8afa7f53..d08fd627f5 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsAction.kt @@ -55,10 +55,14 @@ sealed class DetailsAction : Action { ) : AppSettings() } - object EnrollBiometrics : AppSettings() { - object Enroll : AppSettings() - object Cancel : AppSettings() - } + data class CheckBiometricsStatus( + val awaitStatusChange: Boolean, + ) : AppSettings() + + object EnrollBiometrics : AppSettings() + data class BiometricsStatusChanged( + val needEnrollBiometrics: Boolean, + ) : AppSettings() } data class ChangeAppCurrency(val fiatCurrency: FiatCurrency) : DetailsAction() diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt index 6050723fbd..f4663eda6e 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -16,12 +16,12 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels -import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent import com.tangem.core.ui.fragments.ComposeBottomSheetFragment -import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.cardsettings.resolveReference +import com.tangem.tap.features.saveWallet.ui.components.EnrollBiometricsDialogContent import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent +import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() { override val expandedHeightFraction: Float = .98f diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt index a9ca74fa12..51dbdcadc0 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt @@ -1,8 +1,8 @@ package com.tangem.tap.features.saveWallet.ui import androidx.compose.runtime.Immutable -import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.tap.features.details.ui.cardsettings.TextReference +import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog @Immutable internal data class SaveWalletScreenState( diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt index b0807fbf19..14cae87732 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt @@ -1,11 +1,11 @@ package com.tangem.tap.features.saveWallet.ui import androidx.lifecycle.ViewModel -import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.features.saveWallet.redux.SaveWalletState +import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog import com.tangem.tap.store import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/dialogs/EnrollBiometricsDialogConent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt similarity index 96% rename from core/ui/src/main/java/com/tangem/core/ui/components/dialogs/EnrollBiometricsDialogConent.kt rename to app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt index a75943cc04..d79531b7ee 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/dialogs/EnrollBiometricsDialogConent.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt @@ -1,4 +1,4 @@ -package com.tangem.core.ui.components.dialogs +package com.tangem.tap.features.saveWallet.ui.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -17,8 +17,8 @@ import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.components.TextButton -import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog @Composable fun EnrollBiometricsDialogContent( diff --git a/core/ui/src/main/java/com/tangem/core/ui/models/EnrollBiometricsDialog.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/models/EnrollBiometricsDialog.kt similarity index 74% rename from core/ui/src/main/java/com/tangem/core/ui/models/EnrollBiometricsDialog.kt rename to app/src/main/java/com/tangem/tap/features/saveWallet/ui/models/EnrollBiometricsDialog.kt index a48550d1c1..7f785a95cf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/models/EnrollBiometricsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/models/EnrollBiometricsDialog.kt @@ -1,4 +1,4 @@ -package com.tangem.core.ui.models +package com.tangem.tap.features.saveWallet.ui.models import androidx.compose.runtime.Immutable From 019e98b7104df7e089668f3cdc169c04a420b560 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Dec 2022 20:50:20 +0300 Subject: [PATCH 135/176] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Buttons.kt | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 11dae7b514..80ca296491 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -54,7 +54,7 @@ fun TextButton( onClick = onClick, enabled = enabled, showProgress = false, - colors = TangemButtonsDefaults.textButtonColors, + colors = TangemButtonsDefaults.defaultTextButtonColors, size = TangemButtonSize.Text, ) } @@ -77,7 +77,26 @@ fun TextButtonIconLeft( onClick = onClick, enabled = enabled, showProgress = false, - colors = TangemButtonsDefaults.textButtonColors, + colors = TangemButtonsDefaults.defaultTextButtonColors, + size = TangemButtonSize.Text, + ) +} + +@Composable +fun WarningTextButton( + modifier: Modifier = Modifier, + text: String, + enabled: Boolean = true, + onClick: () -> Unit, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.None, + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.warningTextButtonColors, size = TangemButtonSize.Text, ) } @@ -325,7 +344,7 @@ private fun TangemButtonSize.toShape(): Shape = when (this) { TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall } -private object TangemButtonsDefaults { +object TangemButtonsDefaults { val elevation: ButtonElevation @Composable get() = ButtonDefaults .elevation( @@ -349,17 +368,25 @@ private object TangemButtonsDefaults { disabledContentColor = TangemTheme.colors.text.disabled, ) - val textButtonColors: ButtonColors + val defaultTextButtonColors: ButtonColors @Composable get() = TangemButtonColors( backgroundColor = Color.Transparent, contentColor = TangemTheme.colors.text.secondary, disabledBackgroundColor = Color.Transparent, disabledContentColor = TangemTheme.colors.text.disabled, ) + + val warningTextButtonColors: ButtonColors + @Composable get() = TangemButtonColors( + backgroundColor = Color.Transparent, + contentColor = TangemTheme.colors.text.warning, + disabledBackgroundColor = Color.Transparent, + disabledContentColor = TangemTheme.colors.text.disabled, + ) } @Immutable -private class TangemButtonColors( +private open class TangemButtonColors( private val backgroundColor: Color, private val contentColor: Color, private val disabledBackgroundColor: Color, @@ -561,6 +588,11 @@ private fun TextButtonSample( enabled = false, onClick = { /* no-op */ }, ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + WarningTextButton( + text = "Delete", + onClick = { /* no-op */ }, + ) } } From e0ae64248dc6312526221b1fc15f414c7c6777c4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 6 Dec 2022 20:56:19 +0300 Subject: [PATCH 136/176] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TangemSdkManager.kt | 4 +- .../details/redux/DetailsMiddleware.kt | 36 ++- .../features/details/redux/DetailsReducer.kt | 12 +- .../ui/appsettings/AppSettingsFragment.kt | 13 +- .../ui/appsettings/AppSettingsScreen.kt | 246 ++++++++++-------- .../ui/appsettings/AppSettingsScreenState.kt | 9 +- .../ui/appsettings/AppSettingsViewModel.kt | 26 +- .../components/EnrollBiometricsCard.kt | 92 +++++++ .../components/SettingsAlertDialog.kt | 101 +++++++ .../saveWallet/redux/SaveWalletMiddleware.kt | 2 +- .../src/main/res/values-de/strings_final.xml | 2 + .../src/main/res/values-fr/strings_final.xml | 2 + .../src/main/res/values-it/strings_final.xml | 2 + .../src/main/res/values-ru/strings_final.xml | 2 + .../res/src/main/res/values/strings_final.xml | 2 + .../com/tangem/core/ui/res/TangemDimens.kt | 1 + .../main/res/drawable/ic_alert_circle_24.xml | 9 + 17 files changed, 417 insertions(+), 144 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/EnrollBiometricsCard.kt create mode 100644 app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt create mode 100644 core/ui/src/main/res/drawable/ic_alert_circle_24.xml diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index a1b2dc8e1c..906a0f1306 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -48,9 +48,9 @@ import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { val canUseBiometry: Boolean - get() = tangemSdk.biometricManager.canAuthenticate || canEnrollBiometrics + get() = tangemSdk.biometricManager.canAuthenticate || needEnrollBiometrics - val canEnrollBiometrics: Boolean + val needEnrollBiometrics: Boolean get() = tangemSdk.biometricManager.canEnrollBiometrics val biometricManager: BiometricManager diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 297cb3d6e3..70751b615f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -30,6 +30,7 @@ import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.rekotlin.Action @@ -194,18 +195,39 @@ class DetailsMiddleware { fun handle(state: DetailsState, action: DetailsAction.AppSettings) { when (action) { is DetailsAction.AppSettings.SwitchPrivacySetting -> { - if (tangemSdkManager.canEnrollBiometrics) { - store.dispatch(DetailsAction.AppSettings.EnrollBiometrics) - } when (action.setting) { PrivacySetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable) PrivacySetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable) } } - is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> Unit - is DetailsAction.AppSettings.EnrollBiometrics -> Unit - is DetailsAction.AppSettings.EnrollBiometrics.Enroll -> enrollBiometrics() - is DetailsAction.AppSettings.EnrollBiometrics.Cancel -> Unit + is DetailsAction.AppSettings.CheckBiometricsStatus -> { + checkBiometricsStatus(action.awaitStatusChange, state) + } + is DetailsAction.AppSettings.EnrollBiometrics -> { + enrollBiometrics() + } + is DetailsAction.AppSettings.SwitchPrivacySetting.Success, + is DetailsAction.AppSettings.BiometricsStatusChanged, + -> Unit + } + } + + /** + * @param awaitStatusChange If true then start a new coroutine and check the biometric status every 100 + * milliseconds until it changes + * */ + private fun checkBiometricsStatus(awaitStatusChange: Boolean, state: DetailsState) { + scope.launch { + if (awaitStatusChange) { + while (state.needEnrollBiometrics == tangemSdkManager.needEnrollBiometrics) { + delay(timeMillis = 100) + } + } + store.dispatchOnMain( + DetailsAction.AppSettings.BiometricsStatusChanged( + needEnrollBiometrics = tangemSdkManager.needEnrollBiometrics, + ), + ) } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 28d1b87cb8..f00f0b46b4 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -157,15 +157,17 @@ private fun handlePrivacyAction( state: DetailsState, ): DetailsState { return when (action) { - is DetailsAction.AppSettings.SwitchPrivacySetting -> state is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> when (action.setting) { PrivacySetting.SaveWallets -> state.copy(saveWallets = action.enable) PrivacySetting.SaveAccessCode -> state.copy(saveAccessCodes = action.enable) } - is DetailsAction.AppSettings.EnrollBiometrics -> state.copy(needEnrollBiometrics = true) - is DetailsAction.AppSettings.EnrollBiometrics.Enroll, - is DetailsAction.AppSettings.EnrollBiometrics.Cancel, - -> state.copy(needEnrollBiometrics = false) + is DetailsAction.AppSettings.BiometricsStatusChanged -> state.copy( + needEnrollBiometrics = action.needEnrollBiometrics, + ) + is DetailsAction.AppSettings.SwitchPrivacySetting, + is DetailsAction.AppSettings.EnrollBiometrics, + is DetailsAction.AppSettings.CheckBiometricsStatus, + -> state } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt index 9e311ef003..25a6cd0902 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsFragment.kt @@ -9,11 +9,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater -import com.google.accompanist.appcompattheme.AppCompatTheme +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.store +import com.tangem.wallet.R import org.rekotlin.StoreSubscriber class AppSettingsFragment : Fragment(), StoreSubscriber { @@ -24,8 +25,9 @@ class AppSettingsFragment : Fragment(), StoreSubscriber { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(android.R.transition.fade) - exitTransition = inflater.inflateTransition(android.R.transition.fade) + enterTransition = inflater.inflateTransition(R.transition.fade) + exitTransition = inflater.inflateTransition(R.transition.fade) + viewModel.checkBiometricsStatus() } override fun onCreateView( @@ -58,6 +60,11 @@ class AppSettingsFragment : Fragment(), StoreSubscriber { } } + override fun onResume() { + super.onResume() + viewModel.refreshBiometricsStatus() + } + override fun onStop() { super.onStop() store.unsubscribe(this) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt index afdd0de6e1..b072be7b74 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreen.kt @@ -1,24 +1,31 @@ package com.tangem.tap.features.details.ui.appsettings -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.material.AlertDialog -import androidx.compose.material.MaterialTheme +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material.Text -import androidx.compose.material.TextButton -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent -import com.tangem.core.ui.models.EnrollBiometricsDialog -import com.tangem.tap.common.compose.TangemTypography +import com.tangem.core.ui.components.SpacerH24 +import com.tangem.core.ui.components.SpacerH32 +import com.tangem.core.ui.components.SpacerH4 +import com.tangem.core.ui.components.SpacerW32 +import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.redux.PrivacySetting +import com.tangem.tap.features.details.ui.appsettings.components.EnrollBiometricsCard +import com.tangem.tap.features.details.ui.appsettings.components.SettingsAlertDialog import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.tap.features.details.ui.common.TangemSwitch import com.tangem.wallet.R @@ -51,47 +58,43 @@ private fun AppSettings( element = it, onDialogStateChange = onDialogStateChange, onSettingToggled = state.onSettingToggled, - modifier = modifier, ) } - EnrollBiometricsDialog(dialog = state.enrollBiometricsDialog) - Column( modifier = modifier .fillMaxSize(), ) { + if (state.showEnrollBiometricsCard) { + EnrollBiometricsCard( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing8) + .fillMaxWidth(), + onClick = state.onEnrollBiometrics, + ) + SpacerH24() + } + AppSettingsElement( state = state, setting = PrivacySetting.SaveWallets, onDialogStateChange = onDialogStateChange, - modifier = modifier, ) - Spacer(modifier = Modifier.size(32.dp)) + SpacerH32() AppSettingsElement( state = state, setting = PrivacySetting.SaveAccessCode, onDialogStateChange = onDialogStateChange, - modifier = modifier, ) } } -@Composable -private fun EnrollBiometricsDialog( - modifier: Modifier = Modifier, - dialog: EnrollBiometricsDialog?, -) { - if (dialog == null) return - EnrollBiometricsDialogContent(modifier, dialog) -} - @Composable private fun AppSettingsElement( + modifier: Modifier = Modifier, state: AppSettingsScreenState, setting: PrivacySetting, onDialogStateChange: (PrivacySetting?) -> Unit, - modifier: Modifier = Modifier, ) { val titleRes = when (setting) { PrivacySetting.SaveWallets -> R.string.app_settings_saved_wallet @@ -103,48 +106,63 @@ private fun AppSettingsElement( } val checked = state.settings[setting] ?: false + val titleTextColor by rememberUpdatedState( + newValue = if (state.isTogglesEnabled) { + TangemTheme.colors.text.primary1 + } else { + TangemTheme.colors.text.secondary + }, + ) + val descriptionTextColor by rememberUpdatedState( + newValue = if (state.isTogglesEnabled) { + TangemTheme.colors.text.secondary + } else { + TangemTheme.colors.text.tertiary + }, + ) + Row( modifier = modifier .fillMaxWidth() - .padding(start = 20.dp), + .padding(horizontal = TangemTheme.dimens.spacing20), verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, ) { Column( + modifier = Modifier.weight(weight = .9f), verticalArrangement = Arrangement.Center, - modifier = modifier - .weight(0.6f) - .padding(end = 4.dp), ) { Text( text = stringResource(id = titleRes), - style = TangemTypography.subtitle1, - color = colorResource(id = R.color.text_primary_1), + style = TangemTheme.typography.subtitle1, + color = titleTextColor, ) - Spacer(modifier = Modifier.size(4.dp)) + SpacerH4() Text( text = stringResource(id = subtitleRes), - style = TangemTypography.body2, - color = colorResource(id = R.color.text_secondary), + style = TangemTheme.typography.body2, + color = descriptionTextColor, ) } + SpacerW32() TangemSwitch( checked = checked, - onCheckedChange = { + enabled = state.isTogglesEnabled, + onCheckedChange = { isChecked -> onCheckedChange( element = setting, - enabled = it, + enabled = isChecked, onSettingToggled = state.onSettingToggled, onDialogStateChange = onDialogStateChange, ) }, - modifier = modifier - .padding(20.dp), ) } } private fun onCheckedChange( - element: PrivacySetting, enabled: Boolean, + element: PrivacySetting, + enabled: Boolean, onSettingToggled: (PrivacySetting, Boolean) -> Unit, onDialogStateChange: (PrivacySetting?) -> Unit, ) { @@ -152,82 +170,88 @@ private fun onCheckedChange( if (!enabled) { onDialogStateChange(element) } else { - onSettingToggled(element, enabled) + onSettingToggled(element, true) } } +// region Preview @Composable -private fun SettingsAlertDialog( - element: PrivacySetting, - onDialogStateChange: (PrivacySetting?) -> Unit, - onSettingToggled: (PrivacySetting, Boolean) -> Unit, +private fun AppSettingsScreenSample( modifier: Modifier = Modifier, ) { - val text = when (element) { - PrivacySetting.SaveWallets -> R.string.app_settings_off_saved_wallet_alert_message - PrivacySetting.SaveAccessCode -> R.string.app_settings_off_saved_access_code_alert_message + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + AppSettingsScreen( + state = AppSettingsScreenState( + settings = mapOf( + PrivacySetting.SaveWallets to true, + PrivacySetting.SaveAccessCode to false, + ), + showEnrollBiometricsCard = false, + isTogglesEnabled = true, + onSettingToggled = { _, _ -> }, + onEnrollBiometrics = {}, + ), + onBackPressed = { }, + ) } - AlertDialog( - onDismissRequest = { onDialogStateChange(null) }, - confirmButton = { - TextButton( - onClick = { - onDialogStateChange(null) - }, - ) { - Text( - text = stringResource(id = R.string.common_cancel), - color = colorResource(id = R.color.text_secondary), - fontSize = 14.sp, - fontWeight = FontWeight.Medium, - ) - } - }, - dismissButton = { - TextButton( - onClick = { - onDialogStateChange(null) - onSettingToggled(element, false) - }, - modifier = modifier.padding(bottom = 14.dp), - ) { - Text( - text = stringResource(id = R.string.common_delete), - color = colorResource(id = R.color.text_warning), - fontSize = 14.sp, - fontWeight = FontWeight.Medium, - ) - } - }, - title = { - Text( - text = stringResource(id = R.string.common_attention), - color = colorResource(id = R.color.text_primary_1), - ) - }, - text = { - Text( - text = stringResource(id = text), - color = colorResource(id = R.color.text_secondary), - ) - }, - shape = MaterialTheme.shapes.medium.copy(all = CornerSize(size = 28.dp)), - modifier = modifier.padding(vertical = 34.dp, horizontal = 24.dp), - ) } -@Preview +@Preview(showBackground = true, widthDp = 360) @Composable -fun AppSettingsScreenPreview() { - AppSettingsScreen( - state = AppSettingsScreenState( - settings = mapOf( - PrivacySetting.SaveWallets to true, - PrivacySetting.SaveAccessCode to false, +private fun AppSettingsScreenPreview_Light() { + TangemTheme { + AppSettingsScreenSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun AppSettingsScreenPreview_Dark() { + TangemTheme(isDark = true) { + AppSettingsScreenSample() + } +} + +@Composable +private fun AppSettingsScreen_EnrollBiometrics_Sample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + AppSettingsScreen( + state = AppSettingsScreenState( + settings = mapOf( + PrivacySetting.SaveWallets to true, + PrivacySetting.SaveAccessCode to false, + ), + showEnrollBiometricsCard = true, + isTogglesEnabled = false, + onSettingToggled = { _, _ -> }, + onEnrollBiometrics = {}, ), - onSettingToggled = { _, _ -> }, - enrollBiometricsDialog = null, - ), - onBackPressed = { }, - ) -} \ No newline at end of file + onBackPressed = { }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun AppSettingsScreen_EnrollBiometrics_Preview_Light() { + TangemTheme { + AppSettingsScreen_EnrollBiometrics_Sample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun AppSettingsScreen_EnrollBiometrics_Preview_Dark() { + TangemTheme(isDark = true) { + AppSettingsScreen_EnrollBiometrics_Sample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt index 1e4c1abeee..615a0a167d 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsScreenState.kt @@ -1,10 +1,11 @@ package com.tangem.tap.features.details.ui.appsettings -import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.tap.features.details.redux.PrivacySetting data class AppSettingsScreenState( - val settings: Map, - val enrollBiometricsDialog: EnrollBiometricsDialog?, - val onSettingToggled: (PrivacySetting, Boolean) -> Unit, + val settings: Map = emptyMap(), + val showEnrollBiometricsCard: Boolean = false, + val isTogglesEnabled: Boolean = true, + val onSettingToggled: (PrivacySetting, Boolean) -> Unit = { _, _ -> /* no-op */ }, + val onEnrollBiometrics: () -> Unit = { /* no-op */ }, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt index 16a6ebdbba..802435f578 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/AppSettingsViewModel.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.details.ui.appsettings -import com.tangem.core.ui.models.EnrollBiometricsDialog import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.DetailsAction @@ -20,8 +19,14 @@ class AppSettingsViewModel(private val store: Store) { PrivacySetting.SaveWallets to state.saveWallets, PrivacySetting.SaveAccessCode to state.saveAccessCodes, ), - enrollBiometricsDialog = if (state.needEnrollBiometrics) createEnrollBiometricsDialog() else null, - onSettingToggled = { privacySetting, enabled -> onSettingsToggled(privacySetting, enabled) }, + showEnrollBiometricsCard = state.needEnrollBiometrics, + isTogglesEnabled = !state.needEnrollBiometrics, + onSettingToggled = { privacySetting, enabled -> + onSettingsToggled(privacySetting, enabled) + }, + onEnrollBiometrics = { + store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics) + }, ) } @@ -29,12 +34,11 @@ class AppSettingsViewModel(private val store: Store) { store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting)) } - private fun createEnrollBiometricsDialog() = EnrollBiometricsDialog( - onCancel = { - store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics.Cancel) - }, - onEnroll = { - store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics.Enroll) - }, - ) + fun checkBiometricsStatus() { + store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(awaitStatusChange = false)) + } + + fun refreshBiometricsStatus() { + store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(awaitStatusChange = true)) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/EnrollBiometricsCard.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/EnrollBiometricsCard.kt new file mode 100644 index 0000000000..fddc2b0e7b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/EnrollBiometricsCard.kt @@ -0,0 +1,92 @@ +package com.tangem.tap.features.details.ui.appsettings.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH4 +import com.tangem.core.ui.components.SpacerW16 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.wallet.R + +@OptIn(ExperimentalMaterialApi::class) +@Composable +internal fun EnrollBiometricsCard( + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + Surface( + modifier = modifier, + color = TangemTheme.colors.background.primary, + shape = TangemTheme.shapes.roundedCornersLarge, + onClick = onClick, + ) { + Row( + modifier = Modifier.padding(all = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_alert_circle_24), + tint = TangemTheme.colors.icon.attention, + contentDescription = null, + ) + SpacerW16() + Column { + Text( + text = stringResource(id = R.string.app_settings_enable_biometrics_title), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + SpacerH4() + Text( + text = stringResource(id = R.string.app_settings_enable_biometrics_description), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + } + } + } +} + +// region Preview +@Composable +private fun EnrollBiometricsCardSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.secondary), + ) { + EnrollBiometricsCard(onClick = {}) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun EnrollBiometricsCardPreview_Light() { + TangemTheme { + EnrollBiometricsCardSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun EnrollBiometricsCardPreview_Dark() { + TangemTheme(isDark = true) { + EnrollBiometricsCardSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt new file mode 100644 index 0000000000..65514a2f86 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsAlertDialog.kt @@ -0,0 +1,101 @@ +package com.tangem.tap.features.details.ui.appsettings.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.AlertDialog +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.TextButton +import com.tangem.core.ui.components.WarningTextButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.features.details.redux.PrivacySetting +import com.tangem.wallet.R + +@Composable +internal fun SettingsAlertDialog( + element: PrivacySetting, + onDialogStateChange: (PrivacySetting?) -> Unit, + onSettingToggled: (PrivacySetting, Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + val text = when (element) { + PrivacySetting.SaveWallets -> R.string.app_settings_off_saved_wallet_alert_message + PrivacySetting.SaveAccessCode -> R.string.app_settings_off_saved_access_code_alert_message + } + + AlertDialog( + onDismissRequest = { onDialogStateChange(null) }, + confirmButton = { + TextButton( + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + text = stringResource(id = R.string.common_cancel), + onClick = { + onDialogStateChange(null) + }, + ) + }, + dismissButton = { + WarningTextButton( + text = stringResource(id = R.string.common_delete), + onClick = { + onDialogStateChange(null) + onSettingToggled(element, false) + }, + ) + }, + title = { + Text( + text = stringResource(id = R.string.common_attention), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.h2, + ) + }, + text = { + Text( + text = stringResource(id = text), + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.body2, + ) + }, + shape = TangemTheme.shapes.roundedCornersLarge, + modifier = modifier, + ) +} + +// region Preview +@Composable +private fun SettingsAlertDialogSample( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + SettingsAlertDialog( + element = PrivacySetting.SaveAccessCode, + onDialogStateChange = {}, + onSettingToggled = { _, _ -> }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun SettingsAlertDialogPreview_Light() { + TangemTheme { + SettingsAlertDialogSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun SettingsAlertDialogPreview_Dark() { + TangemTheme(isDark = true) { + SettingsAlertDialogSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index d4717299e0..bd8b4d0459 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -52,7 +52,7 @@ internal class SaveWalletMiddleware { } private fun saveWalletIfBiometricsEnrolled(state: SaveWalletState) { - if (tangemSdkManager.canEnrollBiometrics) { + if (tangemSdkManager.needEnrollBiometrics) { store.dispatchOnMain(SaveWalletAction.EnrollBiometrics) } else { saveWallet(state) diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml index 5e5e0bc90b..e3c0beeaa6 100644 --- a/core/res/src/main/res/values-de/strings_final.xml +++ b/core/res/src/main/res/values-de/strings_final.xml @@ -224,4 +224,6 @@ %d selected biometric authentication biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml index fdba526339..431975c183 100644 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ b/core/res/src/main/res/values-fr/strings_final.xml @@ -224,4 +224,6 @@ %d selected biometric authentication biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml index b812e23de8..edf533dbe1 100644 --- a/core/res/src/main/res/values-it/strings_final.xml +++ b/core/res/src/main/res/values-it/strings_final.xml @@ -224,4 +224,6 @@ %d selected biometric authentication biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml index d8a604f287..dabccca8cd 100644 --- a/core/res/src/main/res/values-ru/strings_final.xml +++ b/core/res/src/main/res/values-ru/strings_final.xml @@ -224,4 +224,6 @@ %d выбрано биометрическую аутентификацию биометрией + Включите биометрическую аутентификацию + Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml index c7b9866169..55f6540953 100644 --- a/core/res/src/main/res/values/strings_final.xml +++ b/core/res/src/main/res/values/strings_final.xml @@ -224,4 +224,6 @@ %d selected biometric authentication biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 0febb1a8af..38321bcba1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -59,6 +59,7 @@ data class TangemDimens internal constructor( val spacing12: Dp = 12.dp, val spacing14: Dp = 14.dp, val spacing16: Dp = 16.dp, + val spacing20: Dp = 20.dp, val spacing24: Dp = 24.dp, val spacing32: Dp = 32.dp, val spacing34: Dp = 34.dp, diff --git a/core/ui/src/main/res/drawable/ic_alert_circle_24.xml b/core/ui/src/main/res/drawable/ic_alert_circle_24.xml new file mode 100644 index 0000000000..278bfde255 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_alert_circle_24.xml @@ -0,0 +1,9 @@ + + + From 7d6f4142680aacb6f9094e17475c3ef765f0344f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 16:56:24 +0300 Subject: [PATCH 137/176] Updated on 2026-08-14 --- features/swap/data/build.gradle.kts | 14 ++++++++++++++ features/swap/presentation/build.gradle.kts | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/features/swap/data/build.gradle.kts b/features/swap/data/build.gradle.kts index 298948e089..0d610522bf 100644 --- a/features/swap/data/build.gradle.kts +++ b/features/swap/data/build.gradle.kts @@ -22,6 +22,20 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 isCoreLibraryDesugaringEnabled = false } + + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } } dependencies { diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index 8eac586d41..82e54a8b08 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -23,6 +23,20 @@ android { isCoreLibraryDesugaringEnabled = false } + buildTypes { + create("debug_beta") { + initWith(getByName("release")) + BuildConfigFieldFactory( + fields = listOf( + Field.Environment("release"), + Field.TestActionEnabled(true), + Field.LogEnabled(true), + ), + builder = ::buildConfigField, + ).create() + } + } + buildFeatures { compose = true } From b99a1617041aa6d8ef88c71a3b98fbecdfb548b9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 20:29:43 +0300 Subject: [PATCH 138/176] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt | 5 +++++ .../tap/features/saveWallet/redux/SaveWalletMiddleware.kt | 6 ++++++ .../walletSelector/redux/WalletSelectorMiddleware.kt | 2 +- .../tangem/tap/features/welcome/redux/WelcomeMiddleware.kt | 2 +- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index a4433cb32a..4ad10ad879 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -12,6 +12,8 @@ import com.tangem.domain.common.util.UserWalletId * @param scanResponse [ScanResponse] of primary user's wallet card. * TODO: Replace with [com.tangem.domain.common.CardDTO] * @property cardId ID of user's wallet primary card + * @property hasAccessCode Indicates if the user's wallet primary card has access code + * @property isSaved Indicates if this user wallet is saved * */ data class UserWallet( val name: String, @@ -23,5 +25,8 @@ data class UserWallet( val cardId: String get() = scanResponse.card.cardId + val hasAccessCode: Boolean + get() = scanResponse.card.isAccessCodeSet + internal var isSaved: Boolean = true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index bd8b4d0459..e0bdc6c843 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -93,6 +93,12 @@ internal class SaveWalletMiddleware { preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet || preferencesStorage.shouldSaveAccessCodes + + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + userWallet.hasAccessCode, + ) + val isSavedWalletSelected = userWalletsListManager.selectedUserWalletSync?.walletId == userWallet.walletId diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index ae053d797b..c915598593 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -221,7 +221,7 @@ internal class WalletSelectorMiddleware { private fun updateAccessCodeRequestPolicy(userWallet: UserWallet) { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.scanResponse.card.isAccessCodeSet, + userWallet.hasAccessCode, ) } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 88c0038a3c..11508acf00 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -61,7 +61,7 @@ internal class WelcomeMiddleware { if (selectedUserWallet != null) { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - selectedUserWallet.scanResponse.card.isAccessCodeSet, + selectedUserWallet.hasAccessCode, ) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) From 544a53a5c91c3b99cd82ce8021d5d679640e714b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 12 Dec 2022 21:21:04 +0300 Subject: [PATCH 139/176] Updated on 2026-08-14 --- .../java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index e518041674..edfab6f5e2 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -14,7 +14,7 @@ interface TangemTechApi { @GET("coins") suspend fun coins( @Query("contractAddress") contractAddress: String? = null, - @Query("exchangeable") exchangeable: Boolean? = false, + @Query("exchangeable") exchangeable: Boolean? = null, @Query("networkIds") networkIds: String? = null, @Query("active") active: Boolean? = null, @Query("searchText") searchText: String? = null, From a2b9f6d85cdc9339ae682850b1378296f0e6e123 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 16:27:29 +0300 Subject: [PATCH 140/176] Updated on 2026-08-14 --- .../redux/WalletSelectorMiddleware.kt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index c915598593..cb7ac66a6c 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -24,7 +24,6 @@ import com.tangem.tap.tangemSdkManager import com.tangem.tap.totalFiatBalanceCalculator import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber @@ -125,15 +124,11 @@ internal class WalletSelectorMiddleware { store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) } .doOnSuccess { - val selectedWallet = userWalletsListManager.selectedUserWallet.first() - val isSavedWalletSelected = userWallet == selectedWallet + userWalletsListManager.selectWallet(userWallet.walletId) store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - - if (isSavedWalletSelected) { - updateAccessCodeRequestPolicy(selectedWallet) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(selectedWallet) - } + updateAccessCodeRequestPolicy(userWallet) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) } } } From 46a0021f0aa30ce09e8976d53cec0dde520ef4e4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Dec 2022 19:14:19 +0300 Subject: [PATCH 141/176] Updated on 2026-08-14 --- .../tap/common/analytics/events/MainScreen.kt | 5 +++++ .../tap/common/analytics/events/MyWallets.kt | 18 +++++++++++++++ .../tap/common/analytics/events/Onboarding.kt | 5 +++++ .../tap/common/analytics/events/Settings.kt | 8 +++---- .../details/redux/DetailsMiddleware.kt | 15 +++++++++++++ .../saveWallet/redux/SaveWalletMiddleware.kt | 22 ++++++++++++++++++- .../middlewares/MultiWalletMiddleware.kt | 2 ++ .../redux/middlewares/WalletMiddleware.kt | 3 +++ .../redux/WalletSelectorMiddleware.kt | 12 ++++++++++ .../ui/WalletSelectorBottomSheetFragment.kt | 10 +++++++++ 10 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt index e19202bf4f..a42cc3568e 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt @@ -14,6 +14,11 @@ sealed class MainScreen( class CardWasScanned : MainScreen("Card Was Scanned") class ButtonMyWallets : MainScreen("Button - My Wallets") + class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen( + event = "Enable Biometric", + params = mapOf("State" to state.value), + ) + class MainCurrencyChanged(currencyType: AnalyticsParam.CurrencyType) : MainScreen( event = "Main Currency Changed", params = mapOf("Currency Type" to currencyType.value), diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt new file mode 100644 index 0000000000..0fd08ddd88 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.common.analytics.events + +sealed class MyWallets( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent("My Wallets", event, params) { + + object MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened") + object CardWasScanned : MyWallets(event = "Card Was Scanned") + object WalletUnlockTapped : MyWallets(event = "Wallet Unlock Tapped") + + object Button { + object ScanNewCard : MyWallets(event = "Button - Scan New Card") + object UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID") + object EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped") + object DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt index 5634c25afa..92f55074e9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt @@ -73,4 +73,9 @@ sealed class Onboarding( class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened") class ButtonClaim : Onboarding("Onboarding", "Button - Claim") class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully") + class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding( + category = "Onboarding / Biometric", + event = "Enable Biometric", + params = mapOf("State" to state.value), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index 38725048a7..728662bdb9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -58,16 +58,16 @@ sealed class Settings( params: Map = mapOf(), ) : Settings("Settings / App Settings", event, params) { - sealed class FaceIDSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings( - event = "Face ID Switcher Changed", + class SaveWalletSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings( + event = "Save Wallet Switcher Changed", params = mapOf("State" to state.value), ) - sealed class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings( + class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings( event = "Save Access Code Switcher Changed", params = mapOf("State" to state.value), ) - class ButtonEnableBiometricAuthentication : AppSettings("Button - Enable Biometric Authentication") + object ButtonEnableBiometricAuthentication : AppSettings("Button - Enable Biometric Authentication") } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 70751b615f..fb671a9cbc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -232,6 +232,7 @@ class DetailsMiddleware { } private fun enrollBiometrics() { + Analytics.send(Settings.AppSettings.ButtonEnableBiometricAuthentication) store.dispatchOnMain(NavigationAction.OpenBiometricsSettings) } @@ -268,14 +269,18 @@ class DetailsMiddleware { Timber.e(error, "Wallet saving failed") } .doOnSuccess { + Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On)) + preferencesStorage.shouldShowSaveUserWalletScreen = false preferencesStorage.shouldSaveUserWallets = true + store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveWallets, enable = true, ), ) + store.onUserWalletSelected(userWallet) } } @@ -284,22 +289,29 @@ class DetailsMiddleware { userWalletsListManager.clear() .flatMap { walletStoresManager.clear() } .doOnSuccess { + Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off)) + preferencesStorage.shouldSaveUserWallets = false + store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveWallets, enable = false, ), ) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } } private fun saveAccessCodes(state: DetailsState) { + Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On)) + preferencesStorage.shouldSaveAccessCodes = true tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = state.scanResponse?.card?.isAccessCodeSet == true, ) + store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveAccessCode, @@ -311,10 +323,13 @@ class DetailsMiddleware { private suspend fun deleteSavedAccessCodes() { tangemSdkManager.clearSavedUserCodes() .doOnSuccess { + Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off)) + preferencesStorage.shouldSaveAccessCodes = false tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = false, ) + store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveAccessCode, diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index e0bdc6c843..ee202bbc3a 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -4,6 +4,10 @@ import com.tangem.common.CompletionResult import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.MainScreen +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState @@ -36,9 +40,9 @@ internal class SaveWalletMiddleware { is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state) is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics() is SaveWalletAction.SaveWalletWasShown -> saveWalletWasShown() + is SaveWalletAction.Dismiss -> dismiss(state) is SaveWalletAction.Save.Success, is SaveWalletAction.ProvideBackupInfo, - is SaveWalletAction.Dismiss, is SaveWalletAction.CloseError, is SaveWalletAction.Save.Error, is SaveWalletAction.EnrollBiometrics, @@ -74,6 +78,13 @@ internal class SaveWalletMiddleware { ?: store.state.globalState.scanResponse ?: return + if (state.backupInfo != null) { + // TODO: Remove after onboarding refactoring + Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.On)) + } else { + Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On)) + } + scope.launch { val userWallet = UserWalletBuilder(scanResponse) .backupCardsIds(state.backupInfo?.backupCardsIds) @@ -114,6 +125,15 @@ internal class SaveWalletMiddleware { } } + private fun dismiss(state: SaveWalletState) { + if (state.backupInfo != null) { + // TODO: Remove after onboarding refactoring + Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.Off)) + } else { + Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.Off)) + } + } + private fun saveWalletWasShown() { preferencesStorage.shouldShowSaveUserWalletScreen = false } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 28f6fcc965..bbe6f90378 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -8,6 +8,7 @@ import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification @@ -181,6 +182,7 @@ class MultiWalletMiddleware { selectedWallet: UserWallet, state: WalletState?, ) = scope.launch { + Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( cardId = selectedWallet.cardId, additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 83cca6c428..f6e0c47011 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -12,6 +12,7 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.attestation.Attestation import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -349,9 +350,11 @@ class WalletMiddleware { private fun changeWallet() { when { userWalletsListManager.hasSavedUserWallets -> { + Analytics.send(MainScreen.ButtonMyWallets()) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) } else -> { + Analytics.send(MainScreen.ButtonScanCard()) store.dispatch(WalletAction.Scan) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index cb7ac66a6c..b2642b071c 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -7,6 +7,8 @@ import com.tangem.common.flatMap import com.tangem.common.map import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState @@ -104,6 +106,8 @@ internal class WalletSelectorMiddleware { } private fun unlockWalletsWithBiometry() { + Analytics.send(MyWallets.Button.UnlockWithBiometrics) + scope.launch { userWalletsListManager.unlockWithBiometry() .doOnFailure { error -> @@ -116,6 +120,8 @@ internal class WalletSelectorMiddleware { } private fun addWallet() = scope.launch { + Analytics.send(MyWallets.Button.ScanNewCard) + scanCardInternal { scanResponse -> val userWallet = UserWalletBuilder(scanResponse).build() @@ -124,6 +130,8 @@ internal class WalletSelectorMiddleware { store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) } .doOnSuccess { + Analytics.send(MyWallets.CardWasScanned) + userWalletsListManager.selectWallet(userWallet.walletId) store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) updateAccessCodeRequestPolicy(userWallet) @@ -148,6 +156,8 @@ internal class WalletSelectorMiddleware { } private fun removeWallets(walletIdsToRemove: List, state: WalletSelectorState) { + Analytics.send(MyWallets.Button.DeleteWalletTapped) + scope.launch { when (walletIdsToRemove.size) { state.wallets.size -> clearUserWallets() @@ -160,6 +170,8 @@ internal class WalletSelectorMiddleware { } private fun renameWallet(walletId: String, newName: String) { + Analytics.send(MyWallets.Button.EditWalletTapped) + scope.launch { userWalletsListManager.get(walletId = UserWalletId(walletId)) .map { it.copy(name = newName) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index 50ee972e7d..717f82ed16 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -1,5 +1,7 @@ package com.tangem.tap.features.walletSelector.ui +import android.app.Dialog +import android.os.Bundle import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -14,6 +16,8 @@ import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.fragment.app.viewModels import com.tangem.core.ui.fragments.ComposeBottomSheetFragment import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent @@ -22,6 +26,12 @@ import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment() { private val viewModel by viewModels() + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + Analytics.send(MyWallets.MyWalletsScreenOpened) + + return super.onCreateDialog(savedInstanceState) + } + @Composable override fun provideState(): State { return viewModel.state.collectAsState() From 9292c784cdf9e5114f7347ff5ab5fa772b4a56ea Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 19:29:23 +0300 Subject: [PATCH 142/176] Updated on 2026-08-14 --- .../walletSelector/redux/UserWalletModel.kt | 4 ++-- .../redux/WalletSelectorReducer.kt | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt index 3c61afc62c..e96c36b46d 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt @@ -11,12 +11,12 @@ data class UserWalletModel( ) { sealed interface Type { data class SingleCurrency( - val blockchainName: String? = null, + val blockchainName: String?, ) : Type data class MultiCurrency( val cardsInWallet: Int, - val tokensCount: Int = 0, + val tokensCount: Int, ) : Type } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index b9f2ca6c70..e4fb11c3b3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -66,10 +66,13 @@ internal object WalletSelectorReducer { return this.map { userWallet -> prevWallets .find { it.id == userWallet.walletId.stringValue } - ?.copy( - name = userWallet.name, - artworkUrl = userWallet.artworkUrl, - ) + ?.let { + it.copy( + name = userWallet.name, + artworkUrl = userWallet.artworkUrl, + type = userWallet.getType(prevType = it.type), + ) + } ?: with(userWallet) { UserWalletModel( id = walletId.stringValue, @@ -90,15 +93,18 @@ internal object WalletSelectorReducer { } } - private fun UserWallet.getType(): UserWalletModel.Type { + private fun UserWallet.getType(prevType: UserWalletModel.Type? = null): UserWalletModel.Type { return if (scanResponse.card.isMultiwalletAllowed) { UserWalletModel.Type.MultiCurrency( cardsInWallet = (scanResponse.card.backupStatus as? CardDTO.BackupStatus.Active) ?.cardCount?.inc() ?: 1, + tokensCount = (prevType as? UserWalletModel.Type.MultiCurrency)?.tokensCount ?: 0, ) } else { - UserWalletModel.Type.SingleCurrency() + UserWalletModel.Type.SingleCurrency( + blockchainName = (prevType as? UserWalletModel.Type.SingleCurrency)?.blockchainName, + ) } } } \ No newline at end of file From 4130c07e7001b5123ee035dac87745cc57f0652d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 22:54:02 +0300 Subject: [PATCH 143/176] Updated on 2026-08-14 --- .../features/onboarding/OnboardingHelper.kt | 19 +++++++++++++++++++ .../saveWallet/redux/SaveWalletMiddleware.kt | 12 +++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index e14d6cff7d..b1ce166222 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -1,16 +1,20 @@ package com.tangem.tap.features.onboarding +import com.tangem.common.doOnSuccess import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onCardScanned +import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -55,6 +59,18 @@ class OnboardingHelper { backupCardsIds: List? = null, ) { when { + // When should save user wallets but manager is locked, then unlock manager with card + preferencesStorage.shouldSaveUserWallets && + userWalletsListManager.isLockedSync -> scope.launch { + val userWallet = UserWalletBuilder(scanResponse).build() + + tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) + userWalletsListManager.unlockWithCard(userWallet) + .doOnSuccess { + store.onUserWalletSelected(userWallet) + } + } + // When should save user wallets, then save card without navigate to save wallet screen preferencesStorage.shouldSaveUserWallets -> scope.launch { store.dispatchOnMain( SaveWalletAction.ProvideBackupInfo( @@ -65,6 +81,8 @@ class OnboardingHelper { ) store.dispatchOnMain(SaveWalletAction.Save) } + // When should not save user wallets but device has biometry and save wallet screen has not been shown, + // then open save wallet screen tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { delay(timeMillis = 1_200) @@ -77,6 +95,7 @@ class OnboardingHelper { ) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) } + // If device has no biometry and save wallet screen has been shown, then go through old scenario else -> scope.launch { store.onCardScanned(scanResponse) } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index ee202bbc3a..aaa56a0789 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -94,6 +94,7 @@ internal class SaveWalletMiddleware { saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) .flatMap { userWalletsListManager.save(userWallet, canOverride = true) } + .flatMap { userWalletsListManager.selectWallet(userWallet.walletId) } .doOnFailure { error -> store.dispatchOnMain(SaveWalletAction.Save.Error(error)) } @@ -110,17 +111,10 @@ internal class SaveWalletMiddleware { userWallet.hasAccessCode, ) - val isSavedWalletSelected = - userWalletsListManager.selectedUserWalletSync?.walletId == userWallet.walletId - store.dispatchOnMain(SaveWalletAction.Save.Success) - if (isSavedWalletSelected) { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) - } else { - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) - } + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) } } } From c5ee1d0b1fb2e8172832d38f8c74c1361caea3b4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 22:55:16 +0300 Subject: [PATCH 144/176] Updated on 2026-08-14 --- .../implementation/BiometricUserWalletsListManager.kt | 4 ---- .../tap/features/details/redux/DetailsMiddleware.kt | 8 ++++++-- .../tap/features/welcome/redux/WelcomeMiddleware.kt | 4 +--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index ad4c7597c9..2267ab620b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -143,10 +143,6 @@ internal class BiometricUserWalletsListManager( } override suspend fun delete(walletIds: List): CompletionResult { - if (state.value.isLocked) { - return CompletionResult.Success(Unit) - } - val walletIdsToRemove = state.value.wallets .map { it.walletId } .filter { it in walletIds } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index fb671a9cbc..829103e4e7 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -121,8 +121,12 @@ class DetailsMiddleware { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(selectedUserWallet) + if (userWalletsListManager.isLockedSync) { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + } else { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(selectedUserWallet) + } } else { userWalletsListManager.lock() store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 11508acf00..fb5b8a626d 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -78,14 +78,12 @@ internal class WelcomeMiddleware { scanCardInternal { scanResponse -> val userWallet = UserWalletBuilder(scanResponse).build() + tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) userWalletsListManager.unlockWithCard(userWallet) .doOnFailure { error -> store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error)) } .doOnSuccess { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = false, - ) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) store.onUserWalletSelected(userWallet) From 05138c8897241b5abc051eb4c25613c41e818d46 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 20:33:19 +0300 Subject: [PATCH 145/176] Updated on 2026-08-14 --- .../implementation/DefaultWalletStoresManager.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt index 45a7f0e0f9..041ea91557 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -165,6 +165,14 @@ internal class DefaultWalletStoresManager( .build(), ) } + .flatMapOnFailure { error -> + when (error) { + is WalletStoresError.WalletManagerNotCreated, + is WalletStoresError.UpdateWalletManagerError, + -> CompletionResult.Success(Unit) + else -> CompletionResult.Failure(error) + } + } } internal data class State( From de2d23d22d9074928eaf9cf531b7045e812849be Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 20:34:21 +0300 Subject: [PATCH 146/176] Updated on 2026-08-14 --- .../tap/features/walletSelector/redux/UserWalletModel.kt | 2 +- .../walletSelector/redux/WalletSelectorMiddleware.kt | 8 +------- .../walletSelector/redux/WalletSelectorReducer.kt | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt index e96c36b46d..e3a5a613ef 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt @@ -11,7 +11,7 @@ data class UserWalletModel( ) { sealed interface Type { data class SingleCurrency( - val blockchainName: String?, + val blockchainName: String, ) : Type data class MultiCurrency( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index b2642b071c..6fcf6665c2 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -240,13 +240,7 @@ internal class WalletSelectorMiddleware { is UserWalletModel.Type.MultiCurrency -> type.copy( tokensCount = walletStores.flatMap { it.walletsData }.size, ) - is UserWalletModel.Type.SingleCurrency -> type.copy( - blockchainName = walletStores - .firstOrNull() - ?.blockchainNetwork - ?.blockchain - ?.fullName, - ) + is UserWalletModel.Type.SingleCurrency -> type }, fiatBalance = totalFiatBalanceCalculator.calculate( prevAmount = fiatBalance.amount, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index e4fb11c3b3..565f81b165 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -103,7 +103,7 @@ internal object WalletSelectorReducer { ) } else { UserWalletModel.Type.SingleCurrency( - blockchainName = (prevType as? UserWalletModel.Type.SingleCurrency)?.blockchainName, + blockchainName = scanResponse.getBlockchain().fullName, ) } } From bf9dbc3535758aad4cc82f060194b30895afd23b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 18:43:54 +0300 Subject: [PATCH 147/176] Updated on 2026-08-14 --- .../di/UserWalletsListManagerProvider.kt | 11 +++-- .../BiometricUserWalletsListManager.kt | 28 +++++------ .../repository/UserWalletsKeysRepository.kt | 4 +- .../BiometricUserWalletsKeysRepository.kt | 49 ++----------------- 4 files changed, 25 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt index 28475b75fa..3773da7127 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt @@ -14,10 +14,13 @@ import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricU import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository -import com.tangem.tap.domain.userWalletList.utils.json.* +import com.tangem.tap.domain.userWalletList.utils.json.ByteArrayKeyAdapter +import com.tangem.tap.domain.userWalletList.utils.json.CardBackupStatusAdapter +import com.tangem.tap.domain.userWalletList.utils.json.ExtendedPublicKeysMapAdapter +import com.tangem.tap.domain.userWalletList.utils.json.ScanResponseDerivedKeysMapAdapter +import com.tangem.tap.domain.userWalletList.utils.json.WalletDerivedKeysMapAdapter -const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage" -const val USER_WALLETS_BIOMETRIC_KEY_NAME = "user_wallets" +private const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage" fun UserWalletsListManager.Companion.provideBiometricImplementation( context: Context, @@ -43,7 +46,6 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation( ) val keysRepository = BiometricUserWalletsKeysRepository( - biometricKeyName = USER_WALLETS_BIOMETRIC_KEY_NAME, moshi = moshi, secureStorage = secureStorage, biometricManager = tangemSdkManager.biometricManager, @@ -61,7 +63,6 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation( ) return BiometricUserWalletsListManager( - tangemSdkManager = tangemSdkManager, keysRepository = keysRepository, publicInformationRepository = publicInformationRepository, sensitiveInformationRepository = sensitiveInformationRepository, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 2267ab620b..a13cd492d0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -3,7 +3,6 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* import com.tangem.domain.common.util.UserWalletId import com.tangem.domain.common.util.encryptionKey -import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.UserWalletsListManager @@ -20,7 +19,6 @@ import timber.log.Timber @OptIn(ExperimentalCoroutinesApi::class) internal class BiometricUserWalletsListManager( - private val tangemSdkManager: TangemSdkManager, private val keysRepository: UserWalletsKeysRepository, private val publicInformationRepository: UserWalletsPublicInformationRepository, private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository, @@ -83,7 +81,6 @@ internal class BiometricUserWalletsListManager( } override fun lock() { - tangemSdkManager.biometricManager.unauthenticate() state.update { prevState -> prevState.copy( encryptionKeys = emptyList(), @@ -122,14 +119,15 @@ internal class BiometricUserWalletsListManager( if (isWalletSaved && !canOverride) { CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) } else { - keysRepository.save( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - .doOnSuccess { keys -> + val newEncryptionKeys = state.value.encryptionKeys + .plus(UserWalletEncryptionKey(userWallet.walletId, userWallet.scanResponse.card.encryptionKey)) + .distinctBy { it.walletId } + + keysRepository.store(newEncryptionKeys) + .doOnSuccess { state.update { prevState -> prevState.copy( - encryptionKeys = (keys + prevState.encryptionKeys).distinctBy { it.walletId }, + encryptionKeys = newEncryptionKeys, ) } } @@ -146,21 +144,22 @@ internal class BiometricUserWalletsListManager( val walletIdsToRemove = state.value.wallets .map { it.walletId } .filter { it in walletIds } + val remainingEncryptionKeys = state.value.encryptionKeys + .filter { it.walletId !in walletIdsToRemove } changeSelectedWalletIfNeeded(walletIdsToRemove) return sensitiveInformationRepository.delete(walletIdsToRemove) .flatMap { publicInformationRepository.delete(walletIdsToRemove) } - .flatMap { keysRepository.delete(walletIdsToRemove) } - .map { keys -> + .flatMap { keysRepository.store(remainingEncryptionKeys) } + .map { state.update { prevState -> prevState.copy( - encryptionKeys = keys, + encryptionKeys = remainingEncryptionKeys, wallets = prevState.wallets.filter { it.walletId !in walletIdsToRemove }, ) } } - .flatMap { loadModels() } } override suspend fun clear(): CompletionResult { @@ -171,12 +170,11 @@ internal class BiometricUserWalletsListManager( .flatMap { keysRepository.clear() } .map { selectedUserWalletRepository.set(null) - tangemSdkManager.biometricManager.unauthenticate() state.update { State() } } } - override suspend fun get(walletId: UserWalletId): CompletionResult = withUnlock { + override suspend fun get(walletId: UserWalletId): CompletionResult { return catching { state.value.wallets.first { it.walletId == walletId } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index 9fc67b367b..974879ca31 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -1,12 +1,10 @@ package com.tangem.tap.domain.userWalletList.repository import com.tangem.common.CompletionResult -import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey internal interface UserWalletsKeysRepository { suspend fun getAll(): CompletionResult> - suspend fun save(walletId: UserWalletId, encryptionKey: ByteArray): CompletionResult> - suspend fun delete(walletIds: List): CompletionResult> + suspend fun store(encryptionKeys: List): CompletionResult suspend fun clear(): CompletionResult } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index e1a2ae7329..cd39605d96 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -6,24 +6,19 @@ import com.squareup.moshi.Types import com.tangem.common.CompletionResult import com.tangem.common.biometric.BiometricManager import com.tangem.common.biometric.BiometricStorage -import com.tangem.common.flatMap import com.tangem.common.map import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage -import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.common.extensions.replaceByOrAdd import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository internal class BiometricUserWalletsKeysRepository( - biometricKeyName: String, moshi: Moshi, secureStorage: SecureStorage, biometricManager: BiometricManager, ) : UserWalletsKeysRepository { private val biometricStorage = BiometricStorage( - biometricKeyName = biometricKeyName, biometricManager = biometricManager, secureStorage = secureStorage, ) @@ -41,50 +36,16 @@ internal class BiometricUserWalletsKeysRepository( } } - override suspend fun save( - walletId: UserWalletId, - encryptionKey: ByteArray, - ): CompletionResult> { - return getAll() - .flatMap { keys -> - if (keys.any { it.walletId == walletId }) { - return@flatMap CompletionResult.Success(Unit) - } - - val encodedKeys = keys.toMutableList() - .apply { - replaceByOrAdd(UserWalletEncryptionKey(walletId, encryptionKey)) { - it.walletId == walletId - } - } - .encode() - - biometricStorage.store( - key = StorageKey.WalletEncryptionKeys.name, - data = encodedKeys, - ) - } - .flatMap { getAll() } + override suspend fun store(encryptionKeys: List): CompletionResult { + return biometricStorage.store( + key = StorageKey.WalletEncryptionKeys.name, + data = encryptionKeys.encode(), + ) .mapFailure { error -> UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) } } - override suspend fun delete(walletIds: List): CompletionResult> { - return getAll() - .map { keys -> - val keysToRemove = keys.filter { it.walletId in walletIds }.toSet() - (keys - keysToRemove).encode() - } - .flatMap { encodedKeys -> - biometricStorage.store( - key = StorageKey.WalletEncryptionKeys.name, - data = encodedKeys, - ) - } - .flatMap { getAll() } - } - override suspend fun clear(): CompletionResult { return biometricStorage.delete(key = StorageKey.WalletEncryptionKeys.name) } From 5c8e3cc3d339c5e8b434969ef4b4620e1d4224b6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 18:44:22 +0300 Subject: [PATCH 148/176] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TangemSdkManager.kt | 21 ------------------- .../tasks/product/CreateProductWalletTask.kt | 2 +- .../domain/tasks/product/ScanProductTask.kt | 2 +- .../redux/OnboardingWalletMiddleware.kt | 1 - .../saveWallet/redux/SaveWalletMiddleware.kt | 11 ++++------ 5 files changed, 6 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 906a0f1306..51c1cea33a 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -38,7 +38,6 @@ import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tokens.UserTokensRepository -import com.tangem.tap.domain.userWalletList.di.USER_WALLETS_BIOMETRIC_KEY_NAME import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive @@ -119,16 +118,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co .map { CardDTO(it) } } - suspend fun unlockBiometricKeys(): CompletionResult { - return biometricManager.authenticate( - mode = BiometricManager.AuthenticationMode.Keys( - USER_WALLETS_BIOMETRIC_KEY_NAME, - tangemSdk.config.userCodesBiometricKeyName, - ), - ) - .map { /* no-op */ } - } - suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { return createUserCodeRepository().save( cardIds = cardsIds, @@ -137,20 +126,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co stringValue = accessCode, ), ) - .map { - biometricManager.unauthenticate( - keyName = tangemSdk.config.userCodesBiometricKeyName, - ) - } } suspend fun clearSavedUserCodes(): CompletionResult { return createUserCodeRepository().clear() - .map { - biometricManager.unauthenticate( - keyName = tangemSdk.config.userCodesBiometricKeyName, - ) - } } suspend fun setPasscode(cardId: String?): CompletionResult { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index a219814de1..98ce39259e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -59,7 +59,7 @@ class CreateProductWalletTask( private val type: ProductType, ) : CardSessionRunnable { - override val allowsAccessCodeFromRepository: Boolean = false + override val allowsRequestAccessCodeFromRepository: Boolean = false override fun run( session: CardSession, diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 537500d0b4..e20a6cc197 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -45,7 +45,7 @@ class ScanProductTask( private val additionalBlockchainsToDerive: Collection? = null, ) : CardSessionRunnable { - override val allowsAccessCodeFromRepository: Boolean + override val allowsRequestAccessCodeFromRepository: Boolean get() = !additionalBlockchainsToDerive.isNullOrEmpty() override fun run( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 761bdd1a53..37e629800a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -234,7 +234,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) when (action) { is BackupAction.StartBackup -> { - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) Analytics.send(Onboarding.Backup.Started()) backupService.discardSavedBackup() val primaryCard = scanResponse?.primaryCard diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index aaa56a0789..d11d5139fb 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -138,13 +138,10 @@ internal class SaveWalletMiddleware { ): CompletionResult { return when { accessCode != null -> { - tangemSdkManager.unlockBiometricKeys() - .flatMap { - tangemSdkManager.saveAccessCode( - accessCode = accessCode, - cardsIds = cardsInWallet, - ) - } + tangemSdkManager.saveAccessCode( + accessCode = accessCode, + cardsIds = cardsInWallet, + ) } else -> { CompletionResult.Success(Unit) From 6945e35f164a45a3d24555ece22ca3b32ade364c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 18:44:39 +0300 Subject: [PATCH 149/176] Updated on 2026-08-14 --- buildSrc/src/main/java/Versions.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index e43bcc7e0d..e5985c63df 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -59,7 +59,8 @@ object Versions { // region Tangem const val tangemBlockchainSdk = "develop-141" - const val tangemCardSgk = "develop-171" + + const val tangemCardSgk = "develop-173" // endregion Tangem // region Testing From 0048927f61e083895dec6c55378cec6b7f10e994 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Dec 2022 14:22:24 +0300 Subject: [PATCH 150/176] Updated on 2026-08-14 --- .../wallet/ui/wallet/MultiWalletView.kt | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 2569627f80..1ceda1b7b4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -51,7 +51,6 @@ class MultiWalletView : WalletView() { btnAddToken.show() } - override fun onViewCreated() { setupWalletsRecyclerView() } @@ -69,7 +68,7 @@ class MultiWalletView : WalletView() { val fragment = fragment ?: return val binding = binding ?: return - handleTotalBalance(binding, state.totalBalance, state.state) + handleTotalBalance(binding, state.totalBalance, state.state, state.walletsData.size) handleBackupWarning(binding, state.showBackupWarning) handleRescanWarning(binding, state.missingDerivations.isNotEmpty()) setupWalletCardNumber(binding, state.walletCardsCount) @@ -137,29 +136,34 @@ class MultiWalletView : WalletView() { binding: FragmentWalletBinding, totalBalance: TotalBalance?, progressState: ProgressState, + walletsCount: Int, ) = with(binding.lCardTotalBalance) { - if (totalBalance == null) { - veilBalance.animateVisibility(show = true) - root.isVisible = progressState == ProgressState.Loading + if (walletsCount == 0) { + root.isVisible = false } else { - root.isVisible = true + if (totalBalance == null) { + veilBalance.animateVisibility(show = true) + root.isVisible = progressState == ProgressState.Loading + } else { + root.isVisible = true - // Skip changes when on refreshing state - if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) { - return@with - } + // Skip changes when on refreshing state + if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) { + return@with + } - veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading) - tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading) - tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error) + veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading) + tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading) + tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error) - tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString( - currencySymbol = totalBalance.fiatCurrency.symbol, - ) - tvCurrencyName.text = totalBalance.fiatCurrency.code + tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString( + currencySymbol = totalBalance.fiatCurrency.symbol, + ) + tvCurrencyName.text = totalBalance.fiatCurrency.code - tvCurrencyName.setOnClickListener { - store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) + tvCurrencyName.setOnClickListener { + store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) + } } } } From 22e43fe7d76e9e1c50616c6ebf0e05e3ddaaa640 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 21:41:43 +0300 Subject: [PATCH 151/176] Updated on 2026-08-14 --- .../com/tangem/tap/domain/model/UserWallet.kt | 4 ++ .../BiometricUserWalletsListManager.kt | 21 ++++++++--- .../walletSelector/redux/UserWalletModel.kt | 1 + .../redux/WalletSelectorAction.kt | 5 +++ .../redux/WalletSelectorMiddleware.kt | 37 +++++++++++++++++++ .../redux/WalletSelectorReducer.kt | 3 ++ .../tap/features/walletSelector/ui/Mapper.kt | 2 + .../ui/WalletSelectorViewModel.kt | 4 +- .../walletSelector/ui/components/MockData.kt | 2 + .../WallectSelectorScreenContent.kt | 3 -- .../ui/components/WalletItem.kt | 3 +- .../walletSelector/ui/model/UserWalletItem.kt | 6 +-- 12 files changed, 77 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 4ad10ad879..3106bd1527 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -13,6 +13,7 @@ import com.tangem.domain.common.util.UserWalletId * TODO: Replace with [com.tangem.domain.common.CardDTO] * @property cardId ID of user's wallet primary card * @property hasAccessCode Indicates if the user's wallet primary card has access code + * @property isLocked Indicates if this primary card has no currency wallets * @property isSaved Indicates if this user wallet is saved * */ data class UserWallet( @@ -28,5 +29,8 @@ data class UserWallet( val hasAccessCode: Boolean get() = scanResponse.card.isAccessCodeSet + val isLocked: Boolean + get() = scanResponse.card.wallets.isEmpty() + internal var isSaved: Boolean = true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index a13cd492d0..62f961e418 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -62,10 +62,22 @@ internal class BiometricUserWalletsListManager( override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { state.update { prevState -> - userWallet.isSaved = false + // If the previous state contains a saved user wallet with the same ID, it is also saved + userWallet.isSaved = prevState.wallets.any { + it.walletId == userWallet.walletId && it.isSaved + } + + val newEncryptionKeys = prevState.encryptionKeys + .plus(UserWalletEncryptionKey(userWallet)) + .distinctBy { it.walletId } + val newUserWallets = prevState.wallets + .plus(userWallet) + .distinctBy { it.walletId } + prevState.copy( - encryptionKeys = listOf(UserWalletEncryptionKey(userWallet)), - wallets = listOf(userWallet), + encryptionKeys = newEncryptionKeys, + wallets = newUserWallets, + selectedWalletId = userWallet.walletId, ) } @@ -73,8 +85,7 @@ internal class BiometricUserWalletsListManager( .map { state.update { prevState -> prevState.copy( - selectedWalletId = userWallet.walletId, - isLocked = prevState.wallets.size != 1, + isLocked = prevState.wallets.any { it.isLocked }, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt index e3a5a613ef..1417914782 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt @@ -8,6 +8,7 @@ data class UserWalletModel( val artworkUrl: String, val type: Type, val fiatBalance: TotalFiatBalance, + val isLocked: Boolean, ) { sealed interface Type { data class SingleCurrency( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index d18cf44dec..957661126f 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -37,6 +37,10 @@ internal sealed interface WalletSelectorAction : Action { val walletId: String, ) : WalletSelectorAction + data class UnlockWalletWithCard( + val walletId: String, + ) : WalletSelectorAction + data class RenameWallet( val walletId: String, val newName: String, @@ -56,5 +60,6 @@ internal sealed interface WalletSelectorAction : Action { ) : WalletSelectorAction data class HandleError(val error: TangemError) : WalletSelectorAction + object CloseError : WalletSelectorAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 6fcf6665c2..dd4c4ff88b 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -61,6 +61,9 @@ internal class WalletSelectorMiddleware { is WalletSelectorAction.SelectWallet -> { selectWallet(action.walletId) } + is WalletSelectorAction.UnlockWalletWithCard -> { + unlockWalletWithCard(action.walletId) + } is WalletSelectorAction.RemoveWallets -> { removeWallets(action.walletIdsToRemove, state) } @@ -155,6 +158,40 @@ internal class WalletSelectorMiddleware { } } + private fun unlockWalletWithCard(id: String) { + scope.launch { + userWalletsListManager.get(UserWalletId(id)) + .flatMap { userWallet -> + updateUserWalletWithScannedCard(userWallet) + } + .flatMap { updatedUserWallet -> + unlockUserWallet(updatedUserWallet) + } + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + } + } + } + + private suspend fun updateUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { + return tangemSdkManager.scanCard(userWallet.cardId) + .map { scannedCard -> + userWallet.copy( + scanResponse = userWallet.scanResponse.copy( + card = scannedCard, + ), + ) + } + } + + private suspend fun unlockUserWallet(userWallet: UserWallet): CompletionResult { + return userWalletsListManager.unlockWithCard(userWallet) + .doOnSuccess { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) + } + } + private fun removeWallets(walletIdsToRemove: List, state: WalletSelectorState) { Analytics.send(MyWallets.Button.DeleteWalletTapped) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index 565f81b165..959c45c8d9 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -56,6 +56,7 @@ internal object WalletSelectorReducer { ) is WalletSelectorAction.WalletStoresChanged, is WalletSelectorAction.SelectWallet, + is WalletSelectorAction.UnlockWalletWithCard, is WalletSelectorAction.RemoveWallets, is WalletSelectorAction.RenameWallet, -> state @@ -70,6 +71,7 @@ internal object WalletSelectorReducer { it.copy( name = userWallet.name, artworkUrl = userWallet.artworkUrl, + isLocked = userWallet.isLocked, type = userWallet.getType(prevType = it.type), ) } @@ -80,6 +82,7 @@ internal object WalletSelectorReducer { artworkUrl = artworkUrl, type = getType(), fiatBalance = TotalFiatBalance.Loading, + isLocked = isLocked, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt index 34ce8d3ef9..6694b8b663 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt @@ -60,6 +60,7 @@ private fun List.toUiModels( name = name, imageUrl = artworkUrl, balance = balance, + isLocked = isLocked, cardsInWallet = type.cardsInWallet, tokensCount = type.tokensCount, ) @@ -68,6 +69,7 @@ private fun List.toUiModels( name = name, imageUrl = artworkUrl, balance = balance, + isLocked = isLocked, tokenName = type.blockchainName ?: "—", ) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index ab2608b497..a7eaa3c4f4 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -37,7 +37,9 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber Unit + isLocked -> { + store.dispatch(WalletSelectorAction.UnlockWalletWithCard(walletId)) + } editingWalletsIds.isNotEmpty() && !editingWalletsIds.contains(walletId) -> { editWallet(walletId) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt index 383ed51f5d..2fab91adc1 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt @@ -14,6 +14,7 @@ internal object MockData { ), name = "Wallet", imageUrl = "https://app.tangem.com/cards/card_default.png", + isLocked = false, tokensCount = 12, cardsInWallet = 3, ) @@ -26,6 +27,7 @@ internal object MockData { ), name = "Wallet", imageUrl = "https://app.tangem.com/cards/card_default.png", + isLocked = false, tokenName = "Ethereum", ) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 5e972b5dfc..6c57a43f00 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -72,7 +72,6 @@ internal fun WalletSelectorScreenContent( singleCurrencyWallets = state.singleCurrencyWallets, selectedWalletId = state.selectedWalletId, checkedWalletIds = state.editingWalletsIds, - isLocked = state.isLocked, onWalletClick = onWalletClick, onWalletLongClick = onWalletLongClick, ) @@ -141,7 +140,6 @@ private fun WalletsList( singleCurrencyWallets: List, selectedWalletId: String?, checkedWalletIds: List, - isLocked: Boolean, onWalletClick: (walletId: String) -> Unit, onWalletLongClick: (walletId: String) -> Unit, ) { @@ -166,7 +164,6 @@ private fun WalletsList( wallet = wallet, isSelected = wallet.id == selectedWalletId, isChecked = wallet.id in checkedWalletIds, - isLocked = isLocked && wallet.id != selectedWalletId, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index ada0168d08..8ad4d7a2fc 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -39,7 +39,6 @@ internal fun WalletItem( wallet: UserWalletItem, isSelected: Boolean, isChecked: Boolean, - isLocked: Boolean, ) { Row( modifier = modifier, @@ -59,7 +58,7 @@ internal fun WalletItem( SpacerW6() TokensInfo( modifier = Modifier.weight(weight = .4f), - isLocked = isLocked, + isLocked = wallet.isLocked, balance = wallet.balance, tokensCount = (wallet as? MultiCurrencyUserWalletItem)?.tokensCount, ) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt index cadbde80db..c546cd131e 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt @@ -1,15 +1,14 @@ package com.tangem.tap.features.walletSelector.ui.model -import androidx.compose.runtime.Immutable import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R -@Immutable internal sealed interface UserWalletItem { val id: String val name: String val imageUrl: String val balance: Balance + val isLocked: Boolean val headerText: TextReference get() = when (this) { @@ -17,7 +16,6 @@ internal sealed interface UserWalletItem { is SingleCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_single_header) } - @Immutable data class Balance( val amount: String, val isLoading: Boolean, @@ -29,6 +27,7 @@ internal data class MultiCurrencyUserWalletItem( override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, + override val isLocked: Boolean, val tokensCount: Int, val cardsInWallet: Int, ) : UserWalletItem @@ -38,5 +37,6 @@ internal data class SingleCurrencyUserWalletItem( override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, + override val isLocked: Boolean, val tokenName: String, ) : UserWalletItem \ No newline at end of file From c826afb5c30bb4b185b4aef7df0d4c30f624d699 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Dec 2022 15:24:50 +0300 Subject: [PATCH 152/176] Updated on 2026-08-14 --- .../features/tokens/ui/AddTokensFragment.kt | 125 +++++++++--------- .../tokens/ui/compose/CurrenciesScreen.kt | 42 +++--- .../currencyItem/CurrencyExpandedContent.kt | 20 +-- 3 files changed, 88 insertions(+), 99 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 4ba2a2c082..ea9a272f74 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -5,12 +5,10 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf -import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import com.google.accompanist.appcompattheme.AppCompatTheme @@ -22,6 +20,8 @@ import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.BaseFragment +import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain import com.tangem.tap.features.tokens.redux.TokensAction @@ -40,8 +40,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.rekotlin.StoreSubscriber -class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), - StoreSubscriber { +class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubscriber { private val binding: FragmentAddTokensBinding by viewBinding(FragmentAddTokensBinding::bind) private var tokensState: MutableState = mutableStateOf(store.state.tokensState) @@ -51,33 +50,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), super.onCreate(savedInstanceState) setHasOptionsMenu(true) Analytics.send(ManageTokens.ScreenOpened()) - - activity?.onBackPressedDispatcher?.addCallback( - this, - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(TokensAction.ResetState) - } - }, - ) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - override fun onStart() { - super.onStart() - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.tokensState == newState.tokensState - }.select { it.tokensState } - } - } - - override fun onStop() { - super.onStop() - store.unsubscribe(this) + addBackPressHandler(this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) { @@ -96,11 +69,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), } val onLoadMore = { - store.dispatch( - TokensAction.LoadMore( - scanResponse = store.state.globalState.scanResponse, - ), - ) + store.dispatch(TokensAction.LoadMore(scanResponse = store.state.globalState.scanResponse)) } cvCurrencies.setContent { @@ -115,24 +84,23 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), } } - override fun newState(state: TokensState) { - if (activity == null || view == null) return - val toolbarTitle = - if (state.allowToAdd) R.string.main_manage_tokens else R.string.search_tokens_title - tokensState.value = state - binding.toolbar.title = getString(toolbarTitle) + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state + .skipRepeats { oldState, newState -> oldState.tokensState == newState.tokensState } + .select { it.tokensState } + } } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.menu_search -> true - R.id.menu_navigate_add_custom_token -> { - Analytics.send(ManageTokens.ButtonCustomToken()) - store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken) - true - } - else -> super.onOptionsItemSelected(item) - } + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun onDestroy() { + store.dispatch(TokensAction.ResetState) + super.onDestroy() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -156,25 +124,50 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), return super.onCreateOptionsMenu(menu, inflater) } - override fun onDestroy() { + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.menu_search -> true + R.id.menu_navigate_add_custom_token -> { + Analytics.send(ManageTokens.ButtonCustomToken()) + store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken) + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun configureTransitions() { + super.configureTransitions() + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun handleOnBackPressed() { + super.handleOnBackPressed() + store.dispatch(NavigationAction.PopBackTo()) store.dispatch(TokensAction.ResetState) - super.onDestroy() + } + + override fun newState(state: TokensState) { + if (activity == null || view == null) return + val toolbarTitle = + if (state.allowToAdd) R.string.main_manage_tokens else R.string.search_tokens_title + tokensState.value = state + binding.toolbar.title = getString(toolbarTitle) } } fun SearchView.inputtedTextAsFlow(): Flow = callbackFlow { - val watcher = - setOnQueryTextListener( - object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String?): Boolean { - return false - } + val watcher = setOnQueryTextListener( + object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean = false - override fun onQueryTextChange(newText: String?): Boolean { - trySend(newText ?: "") - return false - } - }, - ) + override fun onQueryTextChange(newText: String?): Boolean { + trySend(newText ?: "") + return false + } + }, + ) awaitClose { (watcher) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 7cf23fb44c..6d751e8956 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -83,25 +83,21 @@ fun CurrenciesScreen( Scaffold( floatingActionButton = { - if (tokensState.value.allowToAdd) SaveChangesButton(isKeyboardOpen) { - onSaveChanges(addedTokensState.value, addedBlockchainsState.value) + if (tokensState.value.allowToAdd) { + SaveChangesButton(isKeyboardOpen) { + onSaveChanges(addedTokensState.value, addedBlockchainsState.value) + } } }, floatingActionButtonPosition = FabPosition.Center, ) { - AnimatedVisibility( visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADING, enter = fadeIn(), exit = fadeOut(), ) { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxSize(), - ) { - CircularProgressIndicator( - color = Color(0xFF1ACE80), - ) + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator(color = Color(0xFF1ACE80)) } } AnimatedVisibility( @@ -148,21 +144,19 @@ private fun toggleBlockchain( currencySymbol = blockchain.currency, ), ) + } else if (isAddedOnMainScreen) { + store.dispatchDialogShow( + WalletDialog.RemoveWalletDialog( + currencyTitle = blockchain.name, + onOk = { + analyticsCurrencyTypeParam.sendOn() + addedBlockchainsState.removeAndNotify(blockchain) + }, + ), + ) } else { - if (isAddedOnMainScreen) { - store.dispatchDialogShow( - WalletDialog.RemoveWalletDialog( - currencyTitle = blockchain.name, - onOk = { - analyticsCurrencyTypeParam.sendOn() - addedBlockchainsState.removeAndNotify(blockchain) - }, - ), - ) - } else { - analyticsCurrencyTypeParam.sendOff() - addedBlockchainsState.removeAndNotify(blockchain) - } + analyticsCurrencyTypeParam.sendOff() + addedBlockchainsState.removeAndNotify(blockchain) } } else { analyticsCurrencyTypeParam.sendOn() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt index cded760ba9..c4b1b21abe 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.blockchain.common.Blockchain +import com.tangem.tap.domain.tokens.Contract import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain @@ -30,22 +31,20 @@ fun CurrencyExpandedContent( exit = fadeOut() + shrinkVertically(), ) { val blockchains = currency.contracts.map { it.blockchain } - Column( - modifier = Modifier - .fillMaxWidth(), - ) { + Column(modifier = Modifier.fillMaxWidth()) { blockchains.mapIndexed { index, blockchain -> - val contract = currency.contracts.firstOrNull { it.blockchain == blockchain } + val currencyContract = currency.contracts + .firstOrNull { it.blockchain == blockchain } ?: return@mapIndexed - val added = if (contract.address != null) { - addedTokens.map { it.token.contractAddress }.contains(contract.address) + val added = if (currencyContract.address != null) { + addedTokens.any(currencyContract::isInclude) } else { addedBlockchains.contains(blockchain) } NetworkItem( currency = currency, - contract = contract, + contract = currencyContract, blockchain = blockchain, allowToAdd = allowToAdd, added = added, @@ -57,4 +56,7 @@ fun CurrencyExpandedContent( } } } -} \ No newline at end of file +} + +private fun Contract.isInclude(addedToken: TokenWithBlockchain): Boolean = + address == addedToken.token.contractAddress && blockchain == addedToken.blockchain \ No newline at end of file From 28082f24d9d5857eb4237d96bb0a44e1eddb620e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Dec 2022 17:07:56 +0300 Subject: [PATCH 153/176] Updated on 2026-08-14 --- .../com/tangem/tap/domain/model/UserWallet.kt | 17 +++++++++++++++++ .../BiometricUserWalletsListManager.kt | 10 ++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 3106bd1527..8c3403ede9 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -1,5 +1,6 @@ package com.tangem.tap.domain.model +import com.tangem.common.extensions.toHexString import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId @@ -33,4 +34,20 @@ data class UserWallet( get() = scanResponse.card.wallets.isEmpty() internal var isSaved: Boolean = true +} + +/** + * !!! Workaround !!! + * + * Calculate same [UserWalletId] for twins instead + * */ +fun UserWallet.isTwinnedWith(other: UserWallet): Boolean { + if (!scanResponse.isTangemTwins()) return false + if (other.scanResponse.secondTwinPublicKey == scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { + return true + } + if (scanResponse.secondTwinPublicKey == other.scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { + return true + } + return false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 62f961e418..248719dd20 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -2,8 +2,8 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.model.isTwinnedWith import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey @@ -124,14 +124,16 @@ internal class BiometricUserWalletsListManager( ): CompletionResult = withUnlock { val isWalletSaved = state.value.wallets .filter { it.isSaved } - .flatMap(UserWallet::cardsInWallet) - .contains(userWallet.cardId) + .any { + // Workaround, check [UserWallet.isTwinnedWith] + it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) + } if (isWalletSaved && !canOverride) { CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) } else { val newEncryptionKeys = state.value.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet.walletId, userWallet.scanResponse.card.encryptionKey)) + .plus(UserWalletEncryptionKey(userWallet)) .distinctBy { it.walletId } keysRepository.store(newEncryptionKeys) From 5e9de0105fed4e0486f198c8edf8cd63e9b33688 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Dec 2022 18:53:56 +0300 Subject: [PATCH 154/176] Updated on 2026-08-14 --- .../tap/common/redux/navigation/NavigationMiddleware.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 90d6b880da..c8457793e5 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -32,10 +32,8 @@ val navigationMiddleware: Middleware = { _, state -> AppScreen.Home, AppScreen.Welcome, -> { - navState?.activity?.get()?.popBackTo(screen, action.inclusive) - if (navState?.backStack?.contains(screen) != true) { - store.dispatchOnMain(NavigationAction.NavigateTo(screen)) - } + navState?.activity?.get()?.popBackTo(screen, inclusive = true) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) } else -> { navState?.activity?.get()?.popBackTo(screen, action.inclusive) From 58e2c7345a8092856cb9fa7633d5c97f685ec5d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Dec 2022 18:54:59 +0300 Subject: [PATCH 155/176] Updated on 2026-08-14 --- .../com/tangem/tap/features/home/redux/HomeMiddleware.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 8f4282fed9..c879e6ca04 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -93,9 +93,14 @@ private fun readCard() = scope.launch { userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") + tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) store.onCardScanned(scanResponse) } .doOnSuccess { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + userWallet.hasAccessCode, + ) store.onUserWalletSelected(userWallet) } .doOnResult { From 564a1409c889753f4b12c83ea0793c98109d74e6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Dec 2022 19:01:36 +0300 Subject: [PATCH 156/176] Updated on 2026-08-14 --- .../java/com/tangem/tap/domain/TangemSdkManager.kt | 2 +- .../tap/domain/twins/CreateFirstTwinWalletTask.kt | 10 ++++++---- .../tap/domain/twins/CreateSecondTwinWalletTask.kt | 9 ++++++--- .../com/tangem/tap/domain/twins/TwinCardsManager.kt | 5 +++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 51c1cea33a..81ddf880c3 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -208,7 +208,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String { - return context.getString(stringResId, formatArgs) + return context.getString(stringResId, *formatArgs) } fun setAccessCodeRequestPolicy( diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index d812507524..ae9a72152a 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -4,13 +4,12 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable -import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand -class CreateFirstTwinWalletTask : CardSessionRunnable { +class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRunnable { override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, @@ -18,8 +17,11 @@ class CreateFirstTwinWalletTask : CardSessionRunnable { val card = session.environment.card val publicKey = card?.wallets?.firstOrNull()?.publicKey if (publicKey != null) { - if (TwinsHelper.getTwinCardNumber(card.cardId) == TwinCardNumber.Second) { - callback(CompletionResult.Failure(WrongTwinCard(TwinCardNumber.First))) + val requiredTwinCardNumber = TwinsHelper.getTwinCardNumber(firstCardId) + if (requiredTwinCardNumber != TwinsHelper.getTwinCardNumber(card.cardId)) { + requiredTwinCardNumber?.let { + callback(CompletionResult.Failure(WrongTwinCard(it))) + } return } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 8950bc0a02..57b66d7a62 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -7,7 +7,6 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.extensions.hexToBytes -import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask @@ -15,6 +14,7 @@ import com.tangem.operations.wallet.PurgeWalletCommand class CreateSecondTwinWalletTask( private val firstPublicKey: String, + private val firstCardId: String, private val issuerKeys: KeyPair, private val preparingMessage: Message, private val creatingWalletMessage: Message, @@ -24,8 +24,11 @@ class CreateSecondTwinWalletTask( val card = session.environment.card val publicKey = card?.wallets?.firstOrNull()?.publicKey if (publicKey != null) { - if (TwinsHelper.getTwinCardNumber(card.cardId) == TwinCardNumber.First) { - callback(CompletionResult.Failure(WrongTwinCard(TwinCardNumber.Second))) + val currentTwinCardNumber = TwinsHelper.getTwinCardNumber(card.cardId) + if (TwinsHelper.getTwinCardNumber(firstCardId) == currentTwinCardNumber) { + currentTwinCardNumber?.pairNumber()?.let { + callback(CompletionResult.Failure(WrongTwinCard(it))) + } return } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 7917ddaa07..8310047019 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -8,9 +8,9 @@ import com.tangem.common.CompletionResult import com.tangem.common.KeyPair import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString +import com.tangem.datasource.api.common.MoshiConverter import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse -import com.tangem.datasource.api.common.MoshiConverter import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.common.AssetReader import com.tangem.tap.tangemSdkManager @@ -30,7 +30,7 @@ class TwinCardsManager( suspend fun createFirstWallet(message: Message): CompletionResult { val response = tangemSdkManager.runTaskAsync( - runnable = CreateFirstTwinWalletTask(), + runnable = CreateFirstTwinWalletTask(firstCardId), cardId = firstCardId, initialMessage = message, ) @@ -48,6 +48,7 @@ class TwinCardsManager( ): CompletionResult { val task = CreateSecondTwinWalletTask( firstPublicKey = currentCardPublicKey!!, + firstCardId = firstCardId, issuerKeys = issuerKeyPair, preparingMessage = preparingMessage, creatingWalletMessage = creatingWalletMessage, From fdd2b563bda130fb1e60f1b0aeacfde1c780127d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 23 Dec 2022 20:34:08 +0400 Subject: [PATCH 157/176] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 11 + .../com/tangem/tap/LockUserWalletsTimer.kt | 26 +- .../com/tangem/tap/common/IntentHandler.kt | 44 ++- .../converters/BasicEventConverter.kt | 10 +- .../tangem/tap/common/extensions/String.kt | 7 +- .../AccessCodeRequestPolicyMiddleware.kt | 27 ++ .../com/tangem/tap/common/redux/AppState.kt | 1 + .../tap/common/redux/global/GlobalAction.kt | 2 +- .../tap/common/redux/global/GlobalReducer.kt | 2 +- .../redux/navigation/NavigationMiddleware.kt | 20 +- .../redux/navigation/NavigationReducer.kt | 2 + .../tap/common/shop/TangemShopService.kt | 4 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 28 +- .../com/tangem/tap/domain/TapWalletManager.kt | 6 +- .../tap/domain/model/TotalFiatBalance.kt | 4 - .../com/tangem/tap/domain/model/UserWallet.kt | 14 +- .../tap/domain/model/WalletDataModel.kt | 14 - .../tap/domain/model/WalletStoreModel.kt | 53 ++- .../model/builders/UserWalletBuilder.kt | 72 +++- .../model/builders/WalletStoreBuilder.kt | 13 +- .../tap/domain/scanCard/ScanCardProcessor.kt | 19 +- .../DefaultTotalFiatBalanceCalculator.kt | 13 - .../domain/twins/CreateFirstTwinWalletTask.kt | 3 + .../twins/CreateSecondTwinWalletTask.kt | 2 + .../tap/domain/twins/FinalizeTwinTask.kt | 2 + .../tap/domain/twins/TwinCardsManager.kt | 5 +- .../userWalletList/UserWalletsListManager.kt | 13 +- .../BiometricUserWalletsListManager.kt | 211 +++++------- .../DummyUserWalletsListManager.kt | 10 +- .../model/UserWalletInformation.kt | 1 + .../repository/UserWalletsKeysRepository.kt | 25 ++ .../UserWalletsPublicInformationRepository.kt | 2 + ...erWalletsSensitiveInformationRepository.kt | 3 +- .../BiometricUserWalletsKeysRepository.kt | 173 ++++++++-- ...tUserWalletsPublicInformationRepository.kt | 52 +-- ...erWalletsSensitiveInformationRepository.kt | 185 +++++++---- .../tap/domain/userWalletList/utils/Mapper.kt | 2 + .../WalletCurrenciesManager.kt | 3 +- .../DefaultWalletCurrenciesManager.kt | 212 ++++++------ .../walletStores/WalletStoresManager.kt | 6 +- .../DefaultWalletStoresManager.kt | 63 ++-- .../DummyWalletStoresManager.kt | 6 +- .../utils/CompletitionsResultOperations.kt | 27 -- .../repository/WalletAmountsRepository.kt | 28 +- .../repository/WalletManagersRepository.kt | 7 +- .../repository/WalletStoresRepository.kt | 1 + .../DefaultWalletAmountsRepository.kt | 309 +++++++++--------- .../DefaultWalletManagersRepository.kt | 53 +-- .../DefaultWalletStoresRepository.kt | 44 +-- .../utils/WalletDataOperations.kt | 3 +- .../utils/WalletStoreOperations.kt | 69 ++-- .../storage/WalletManagerStorage.kt | 6 +- .../storage/WalletStoresStorage.kt | 21 +- .../details/redux/DetailsMiddleware.kt | 40 ++- .../ui/cardsettings/CardSettingsScreen.kt | 8 +- .../details/ui/details/DetailsScreen.kt | 6 + .../disclaimer/redux/DisclaimerAction.kt | 14 +- .../disclaimer/redux/DisclaimerMiddleware.kt | 10 +- .../disclaimer/redux/DisclaimerReducer.kt | 3 +- .../disclaimer/redux/DisclaimerState.kt | 1 + .../disclaimer/ui/DisclaimerFragment.kt | 6 + .../tap/features/home/redux/HomeMiddleware.kt | 17 +- .../features/onboarding/OnboardingHelper.kt | 15 - .../twins/redux/TwinCardsMiddleware.kt | 24 +- .../products/twins/ui/TwinsCardsFragment.kt | 6 + .../saveWallet/redux/SaveWalletMiddleware.kt | 7 - .../send/redux/middlewares/SendMiddleware.kt | 35 +- .../features/tokens/redux/TokensMiddleware.kt | 4 +- .../features/tokens/ui/AddTokensFragment.kt | 3 +- .../tokens/ui/compose/CurrenciesScreen.kt | 6 + .../tap/features/wallet/redux/WalletAction.kt | 21 +- .../tap/features/wallet/redux/WalletState.kt | 106 +++--- .../wallet/redux/middlewares/Mapper.kt | 133 ++++++++ .../middlewares/MultiWalletMiddleware.kt | 2 +- .../middlewares/TradeCryptoMiddleware.kt | 6 +- .../middlewares/WalletDialogsMiddleware.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 90 ++--- .../redux/middlewares/WarningsMiddleware.kt | 6 +- .../redux/reducers/MultiWalletReducer.kt | 59 ++-- .../redux/reducers/OnWalletLoadedReducer.kt | 10 +- .../redux/reducers/TotalBalanceOperations.kt | 1 + .../wallet/redux/reducers/WalletReducer.kt | 187 +++-------- .../tap/features/wallet/ui/BalanceWidget.kt | 3 +- .../wallet/ui/WalletDetailsFragment.kt | 156 ++++++--- .../tap/features/wallet/ui/WalletFragment.kt | 13 +- .../wallet/ui/adapters/WalletAdapter.kt | 5 +- .../ui/test/TestWalletDetailsActions.kt | 2 +- .../wallet/ui/wallet/MultiWalletView.kt | 74 ++++- .../ui/wallet/SaltPaySingleWalletView.kt | 2 +- .../wallet/ui/wallet/SingleWalletView.kt | 6 +- .../features/wallet/ui/wallet/WalletView.kt | 2 + .../walletSelector/redux/UserWalletModel.kt | 3 +- .../redux/WalletSelectorAction.kt | 10 +- .../redux/WalletSelectorMiddleware.kt | 180 +++++----- .../redux/WalletSelectorReducer.kt | 18 +- .../redux/WalletSelectorState.kt | 3 +- .../tap/features/walletSelector/ui/Mapper.kt | 6 +- .../ui/WalletSelectorBottomSheetFragment.kt | 23 +- .../ui/WalletSelectorScreenState.kt | 5 +- .../ui/WalletSelectorViewModel.kt | 50 +-- .../walletSelector/ui/components/MockData.kt | 11 +- .../WallectSelectorScreenContent.kt | 165 +++++----- .../ui/components/WalletItem.kt | 15 +- .../walletSelector/ui/model/UserWalletItem.kt | 15 +- .../welcome/redux/WelcomeMiddleware.kt | 11 +- .../tap/persistence/PreferencesStorage.kt | 14 + .../tangem/tap/proxy/DerivationManagerImpl.kt | 2 +- app/src/main/res/layout/fragment_wallet.xml | 4 +- .../res/layout/fragment_wallet_details.xml | 38 +-- .../layout/item_currency_wallet_content.xml | 128 ++++---- .../res/layout/layout_card_total_balance.xml | 47 ++- buildSrc/src/main/java/Dependency.kt | 1 + buildSrc/src/main/java/Versions.kt | 3 +- .../src/main/res/values-de/strings_final.xml | 1 + .../src/main/res/values-fr/strings_final.xml | 1 + .../src/main/res/values-it/strings_final.xml | 1 + .../src/main/res/values-ru/strings_final.xml | 1 + .../res/src/main/res/values/strings_final.xml | 1 + .../fragments/ComposeBottomSheetFragment.kt | 10 +- .../tangem/core/ui/fragments/FragmentExt.kt | 14 + 121 files changed, 2218 insertions(+), 1594 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a15171a939..1876b0038c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -198,6 +198,7 @@ dependencies { implementation(Library.armadillo) implementation(Library.googlePlayServicesWallet) implementation(Library.composeShimmer) + implementation(Library.mviCoreWatcher) /** Testing libraries */ testImplementation(Test.junit) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e8a5618a92..e7d11764b4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -101,6 +101,17 @@ + + + + + + + + + + + diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 0bfb76a8ac..426f49ca25 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -16,7 +16,7 @@ import kotlin.time.Duration internal class LockUserWalletsTimer( owner: LifecycleOwner, - private val duration: Duration = with(Duration) { 5.minutes }, + private val duration: Duration = with(Duration) { 10.minutes }, ) : LifecycleOwner by owner, DefaultLifecycleObserver { @@ -25,8 +25,6 @@ internal class LockUserWalletsTimer( field?.cancel() field = value } - private var isStopped = false - private var openWelcomeScreenWhenResumed = false init { lifecycle.addObserver(this) @@ -35,22 +33,22 @@ internal class LockUserWalletsTimer( override fun onResume(owner: LifecycleOwner) { Timber.d( """ - Owner resumed - |- Was stopped: $isStopped - |- Need to open welcome screen: $openWelcomeScreenWhenResumed - """.trimIndent(), + Owner resumed + |- Was stopped: ${preferencesStorage.wasApplicationStopped} + |- Need to open welcome screen: ${preferencesStorage.shouldOpenWelcomeScreenOnResume} + """.trimIndent(), ) - isStopped = false + preferencesStorage.wasApplicationStopped = false start() - if (openWelcomeScreenWhenResumed) { + if (preferencesStorage.shouldOpenWelcomeScreenOnResume) { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) - openWelcomeScreenWhenResumed = false + preferencesStorage.shouldOpenWelcomeScreenOnResume = false } } override fun onStop(owner: LifecycleOwner) { Timber.d("Owner stopped") - isStopped = true + preferencesStorage.wasApplicationStopped = true } override fun onDestroy(owner: LifecycleOwner) { @@ -103,13 +101,13 @@ internal class LockUserWalletsTimer( Timber.d( """ Finished - |- App is stopped: $isStopped + |- App is stopped: ${preferencesStorage.wasApplicationStopped} |- Millis passed: ${currentTime - startTime} """.trimIndent(), ) userWalletsListManager.lock() - if (isStopped) { - openWelcomeScreenWhenResumed = true + if (preferencesStorage.wasApplicationStopped) { + preferencesStorage.shouldOpenWelcomeScreenOnResume = true } else { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt index cfee84fd69..4ac3547aa0 100644 --- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common import android.content.Intent import android.nfc.NfcAdapter import android.nfc.Tag +import com.tangem.tap.common.extensions.removePrefixOrNull import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.walletconnect.WalletConnectManager @@ -14,14 +15,20 @@ import timber.log.Timber class IntentHandler { - private val TRANSACTION_ID_PARAM = "transactionId" - private val CURRENCY_CODE_PARAM = "baseCurrencyCode" - private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" - private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" - fun handleWalletConnectLink(intent: Intent?) { - if (intent?.scheme == WalletConnectManager.WC_SCHEME) { - store.dispatch(WalletConnectAction.HandleDeepLink(intent.data?.toString())) + val wcUri = when (intent?.scheme) { + WalletConnectManager.WC_SCHEME -> { + intent.data?.toString() + } + TANGEM_SCHEME -> { + intent.data?.toString()?.removePrefixOrNull(TANGEM_WC_PREFIX) + } + else -> { + null + } + } + if (wcUri != null) { + store.dispatch(WalletConnectAction.HandleDeepLink(wcUri)) } } @@ -54,16 +61,25 @@ class IntentHandler { Timber.d("MoonPay Sell: $amount $currency to $destinationAddress") - store.dispatch(WalletAction.TradeCryptoAction.SendCrypto( - currencyId = currency, - amount = amount, - destinationAddress = destinationAddress, - transactionId = transactionID - )) + store.dispatch( + WalletAction.TradeCryptoAction.SendCrypto( + currencyId = currency, + amount = amount, + destinationAddress = destinationAddress, + transactionId = transactionID, + ), + ) } catch (exception: Exception) { Timber.d("Not MoonPay URL") } } - + companion object { + private const val TRANSACTION_ID_PARAM = "transactionId" + private const val CURRENCY_CODE_PARAM = "baseCurrencyCode" + private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount" + private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress" + private const val TANGEM_SCHEME = "tangem" + private const val TANGEM_WC_PREFIX = "tangem://wc?uri=" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 301ae0cb07..45d55aac2a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -25,7 +25,7 @@ class BasicSignInEventConverter( val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null return Basic.SignedIn( - state = AnalyticsParam.CardBalanceState.from(value.walletsData), + state = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), currency = cardCurrency, batch = scanResponse.card.batchId, ).apply { @@ -44,7 +44,7 @@ class BasicTopUpEventConverter( val data = BasicTopUpFilter.Data( walletId = scanResponse.card.userWalletId.stringValue, - cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsData), + cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), ) return Basic.ToppedUp(cardCurrency).apply { filterData = data } @@ -61,16 +61,16 @@ private fun AnalyticsParam.CardBalanceState.Companion.from(walletsData: List acc + i } - if (balancesCount != state.wallets.size) return false + if (balancesCount != state.walletsStores.size) return false return true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt index 7d54a7bf92..021cc3a7f2 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt @@ -62,4 +62,9 @@ fun String.toQrCode(): Bitmap { return bmp } -fun String.urlEncode(): String = Uri.encode(this) \ No newline at end of file +fun String.urlEncode(): String = Uri.encode(this) + +fun String.removePrefixOrNull(prefix: String): String? = when { + startsWith(prefix) -> substring(prefix.length) + else -> null +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt new file mode 100644 index 0000000000..e9aaabfd70 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.common.redux + +import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.preferencesStorage +import com.tangem.tap.tangemSdkManager +import org.rekotlin.Middleware + +class AccessCodeRequestPolicyMiddleware { + val middleware: Middleware = { _, _ -> + { next -> + { action -> + if (action is GlobalAction.SaveScanResponse) { + updateAccessCodeRequestPolicy(action.scanResponse) + } + next(action) + } + } + } + + private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + scanResponse.card.isAccessCodeSet, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index fad0770f65..9f7c34b90f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -93,6 +93,7 @@ data class AppState( SaveWalletMiddleware().middleware, WalletSelectorMiddleware().middleware, LockUserWalletsTimerMiddleware().middleware, + AccessCodeRequestPolicyMiddleware().middleware, ) } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index d4c5620fb3..1f51374cae 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -64,7 +64,7 @@ sealed class GlobalAction : Action { object Increment : GlobalAction() } - data class SaveScanNoteResponse(val scanResponse: ScanResponse) : GlobalAction() + data class SaveScanResponse(val scanResponse: ScanResponse) : GlobalAction() data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index f4aa80ad03..543e734f8f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -33,7 +33,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde is GlobalAction.ScanFailsCounter.Reset -> { globalState.copy(scanCardFailsCounter = 0) } - is GlobalAction.SaveScanNoteResponse -> { + is GlobalAction.SaveScanResponse -> { appStateHolder.scanResponse = action.scanResponse domainStore.dispatch(DomainGlobalAction.SaveScanNoteResponse(action.scanResponse)) globalState.copy(scanResponse = action.scanResponse) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index c8457793e5..871ef1ea5c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -28,15 +28,17 @@ val navigationMiddleware: Middleware = { _, state -> ) } is NavigationAction.PopBackTo -> { - when (val screen = action.screen) { - AppScreen.Home, - AppScreen.Welcome, - -> { - navState?.activity?.get()?.popBackTo(screen, inclusive = true) - store.dispatchOnMain(NavigationAction.NavigateTo(screen)) - } - else -> { - navState?.activity?.get()?.popBackTo(screen, action.inclusive) + if (navState?.backStack?.lastOrNull() != action.screen) { + when (val screen = action.screen) { + AppScreen.Home, + AppScreen.Welcome, + -> { + navState?.activity?.get()?.popBackTo(screen, inclusive = true) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + } + else -> { + navState?.activity?.get()?.popBackTo(screen, action.inclusive) + } } } } diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt index 7d63cb95ee..b6f7522586 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt @@ -20,6 +20,8 @@ private fun internalReduce(action: Action, state: AppState): NavigationState { navState.copy(backStack = navState.backStack + navigationAction.screen) } is NavigationAction.PopBackTo -> { + if (navState.backStack.lastOrNull() == navigationAction.screen) return navState + val screen = navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen() val index = navState.backStack.lastIndexOf(screen) + 1 state.navigationState.copy(backStack = navState.backStack.subList(0, index)) diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 271886dea1..808cb5bbbc 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -208,8 +208,8 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) { } companion object { - const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2" - const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3" + const val TANGEM_WALLET_2_CARDS_SKU = "TG115X2-S" + const val TANGEM_WALLET_3_CARDS_SKU = "TG115X3-S" val SKUS_TO_DISPLAY = listOf(TANGEM_WALLET_2_CARDS_SKU, TANGEM_WALLET_3_CARDS_SKU) } } diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 81ddf880c3..01350c6c63 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -46,6 +46,14 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { + + private val userCodeRepository by lazy { + UserCodeRepository( + biometricManager = tangemSdk.biometricManager, + secureStorage = tangemSdk.secureStorage, + ) + } + val canUseBiometry: Boolean get() = tangemSdk.biometricManager.canAuthenticate || needEnrollBiometrics @@ -119,8 +127,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { - return createUserCodeRepository().save( - cardIds = cardsIds, + return userCodeRepository.save( + cardsIds = cardsIds, userCode = UserCode( type = UserCodeType.AccessCode, stringValue = accessCode, @@ -128,8 +136,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co ) } + suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult { + return userCodeRepository.delete(cardsIds.toSet()) + } + suspend fun clearSavedUserCodes(): CompletionResult { - return createUserCodeRepository().clear() + return userCodeRepository.clear() } suspend fun setPasscode(cardId: String?): CompletionResult { @@ -166,9 +178,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co suspend fun scanCard( cardId: String? = null, + allowRequestAccessCodeFromRepository: Boolean = false, ): CompletionResult { return runTaskAsyncReturnOnMain( - runnable = ScanTask(), + runnable = ScanTask(allowRequestAccessCodeFromRepository), cardId = cardId, initialMessage = Message(context.getString(R.string.initial_message_tap_header)), ) @@ -221,13 +234,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } - private fun createUserCodeRepository() = with(tangemSdk) { - UserCodeRepository( - biometricManager = biometricManager, - secureStorage = secureStorage, - ) - } - companion object { val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 7eeb396463..ea26d15848 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -33,6 +33,7 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager import com.tangem.tap.userTokensRepository import com.tangem.tap.walletStoresManager import kotlinx.coroutines.Dispatchers @@ -94,6 +95,7 @@ class TapWalletManager { val card = scanResponse.card val attestationFailed = card.attestation.status == Attestation.Status.Failed + tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse) updateConfigManager(scanResponse) @@ -101,7 +103,7 @@ class TapWalletManager { store.dispatch(WalletAction.UserWalletChanged(userWallet)) store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse)) store.dispatch(WalletConnectAction.ResetState) - store.dispatch(GlobalAction.SaveScanNoteResponse(scanResponse)) + store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) store.dispatch(WalletConnectAction.RestoreSessions(scanResponse)) store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) store.dispatch(WalletAction.Warnings.CheckIfNeeded) @@ -149,7 +151,7 @@ class TapWalletManager { withMainContext { store.dispatch(WalletAction.ResetState(data.card)) store.dispatch(WalletConnectAction.ResetState) - store.dispatch(GlobalAction.SaveScanNoteResponse(data)) + store.dispatch(GlobalAction.SaveScanResponse(data)) store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed)) store.dispatch(WalletConnectAction.RestoreSessions(data)) diff --git a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt index ac3ac6d56a..dd9a0b27a1 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt @@ -11,10 +11,6 @@ sealed class TotalFiatBalance { object Loading : TotalFiatBalance() - data class Refreshing( - override val amount: BigDecimal, - ) : TotalFiatBalance() - data class Error( override val amount: BigDecimal, ) : TotalFiatBalance() diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 8c3403ede9..4dec0df7d1 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -6,22 +6,23 @@ import com.tangem.domain.common.util.UserWalletId /** * Represents user's wallet which stored in app persistence - * @param name User's wallet name - * @param walletId User's wallet [UserWalletId] + * @param name User wallet name + * @param walletId User wallet [UserWalletId] * @param artworkUrl User wallet card artwork URL * @param cardsInWallet List of cards IDs assigned with this user's wallet + * @param isMultiCurrency Indicates whether this user wallet can work with more than one currency * @param scanResponse [ScanResponse] of primary user's wallet card. * TODO: Replace with [com.tangem.domain.common.CardDTO] * @property cardId ID of user's wallet primary card * @property hasAccessCode Indicates if the user's wallet primary card has access code * @property isLocked Indicates if this primary card has no currency wallets - * @property isSaved Indicates if this user wallet is saved * */ data class UserWallet( val name: String, val walletId: UserWalletId, val artworkUrl: String, val cardsInWallet: Set, + val isMultiCurrency: Boolean, val scanResponse: ScanResponse, ) { val cardId: String @@ -32,17 +33,18 @@ data class UserWallet( val isLocked: Boolean get() = scanResponse.card.wallets.isEmpty() - - internal var isSaved: Boolean = true } /** * !!! Workaround !!! * * Calculate same [UserWalletId] for twins instead + * + * TODO: Remove after [REDACTED_JIRA] * */ fun UserWallet.isTwinnedWith(other: UserWallet): Boolean { - if (!scanResponse.isTangemTwins()) return false + if (!scanResponse.isTangemTwins() || !other.scanResponse.isTangemTwins()) return false + if (scanResponse.secondTwinPublicKey == null || other.scanResponse.secondTwinPublicKey == null) return false if (other.scanResponse.secondTwinPublicKey == scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { return true } diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt index 439b900c39..4462f4d8ec 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt @@ -35,12 +35,6 @@ data class WalletDataModel( open val pendingTransactions: List = emptyList() open val errorMessage: String? = null open val isErrorStatus: Boolean = false - - fun asRefreshing() = Refreshing( - amount = amount, - pendingTransactions = pendingTransactions, - errorMessage = errorMessage, - ) } object Loading : Status() @@ -74,12 +68,4 @@ data class WalletDataModel( object MissedDerivation : Status() { override val isErrorStatus: Boolean = true } - - data class Refreshing( - override val amount: BigDecimal, - override val pendingTransactions: List, - override val errorMessage: String?, - ) : Status() { - override val isErrorStatus: Boolean = errorMessage != null - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt index cba89801a3..a0511784e2 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt @@ -1,6 +1,8 @@ package com.tangem.tap.domain.model +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager +import com.tangem.common.hdWallet.DerivationPath import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel.WalletRent import com.tangem.tap.domain.tokens.models.BlockchainNetwork @@ -8,21 +10,28 @@ import java.math.BigDecimal /** * Contains info about the blockchain and its currencies - * @param userWalletId ID of the [UserWallet] which uses that store - * @param blockchainNetwork Store's [BlockchainNetwork] - * @param walletManager Store's [WalletManager], may be null if it fails to create this manager. TODO: Remove after - * WalletMiddleware refactoring + * @param userWalletId ID of the associated [UserWallet] + * @param blockchain [Blockchain] of this WalletStore + * @param derivationPath [DerivationPath] of this store, null if the card does not support the + * [HD Wallet](https://coinsutra.com/hd-wallets-deterministic-wallet/) * @param walletsData List of [WalletDataModel] which represents store's blockchain currency and tokens currencies - * @param walletRent Store's [WalletRent], null if store has no rent or currency balance is greater then + * @param walletRent [WalletRent], null if store has no rent or currency balance is greater then * [WalletRent.exemptionAmount] + * @param blockchainNetwork [BlockchainNetwork]. + * TODO: Remove after WalletMiddleware refactoring + * @param walletManager [WalletManager], may be null if it fails to create this manager. + * TODO: Remove after WalletMiddleware refactoring * */ data class WalletStoreModel( val userWalletId: UserWalletId, + val blockchain: Blockchain, + val derivationPath: DerivationPath?, + val walletsData: List, + val walletRent: WalletRent?, + @Deprecated("Don't use it, will be removed") val blockchainNetwork: BlockchainNetwork, @Deprecated("Don't use it, will be removed") val walletManager: WalletManager?, - val walletsData: List, - val walletRent: WalletRent?, ) { /** @@ -35,4 +44,34 @@ data class WalletStoreModel( val rent: BigDecimal, val exemptionAmount: BigDecimal, ) + + // TODO: Remove the generated methods after blockchainNetwork and walletManager are removed from the model + // region Generated + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is WalletStoreModel) return false + + if (userWalletId != other.userWalletId) return false + if (blockchain != other.blockchain) return false + if (derivationPath != other.derivationPath) return false + if (walletsData != other.walletsData) return false + if (walletRent != other.walletRent) return false + + return true + } + + override fun hashCode(): Int { + var result = userWalletId.hashCode() + result = 31 * result + blockchain.hashCode() + result = 31 * result + (derivationPath?.hashCode() ?: 0) + result = 31 * result + walletsData.hashCode() + result = 31 * result + (walletRent?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return "WalletStoreModel(userWalletId=$userWalletId, blockchain=$blockchain, derivationPath=$derivationPath, " + + "walletsData=$walletsData, walletRent=$walletRent)" + } + // endregion Generated } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 377b8cf8c6..6e4d834d38 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -1,15 +1,48 @@ package com.tangem.tap.domain.model.builders +import com.tangem.common.extensions.toHexString +import com.tangem.common.services.Result +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isStart2Coin +import com.tangem.domain.common.TwinCardNumber +import com.tangem.domain.common.TwinsHelper import com.tangem.domain.common.util.userWalletId -import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl +import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.operations.attestation.TangemApi import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.features.wallet.redux.Artwork class UserWalletBuilder( private val scanResponse: ScanResponse, + private val onlineCardVerifier: OnlineCardVerifier = OnlineCardVerifier(), ) { private var backupCardsIds: Set = emptySet() + private val CardDTO.isBackupNotAllowed: Boolean + get() = !this.settings.isBackupAllowed + + private val ScanResponse.userWalletName: String + get() = when (productType) { + ProductType.Note -> "Note" + ProductType.Twins -> "Twin" + ProductType.SaltPay -> "SaltPay" + ProductType.Wallet -> when { + card.isBackupNotAllowed -> "Tangem card" + card.isStart2Coin -> "Start2Coin" + else -> "Wallet" + } + } + + private val ScanResponse.isMultiCurrency: Boolean + get() = when (productType) { + ProductType.Note -> false + ProductType.Twins -> false + ProductType.SaltPay -> false + ProductType.Wallet -> !card.isStart2Coin + } + fun backupCardsIds(backupCardsIds: Set?) = this.apply { if (backupCardsIds != null) { this.backupCardsIds = backupCardsIds @@ -20,11 +53,44 @@ class UserWalletBuilder( return with(scanResponse) { UserWallet( walletId = card.userWalletId, - name = productType.name, - artworkUrl = card.getOrLoadCardArtworkUrl(), + name = userWalletName, + artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), cardsInWallet = backupCardsIds.plus(card.cardId), scanResponse = this, + isMultiCurrency = isMultiCurrency, ) } } + + private suspend fun loadArtworkUrl(cardId: String, cardPublicKey: ByteArray): String { + return when (val result = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) { + is Result.Success -> { + val artworkId = result.data.artwork?.id + if (artworkId.isNullOrEmpty()) { + getFallbackArtworkUrl(cardId) + } else { + getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) + } + } + + is Result.Failure -> getFallbackArtworkUrl(cardId) + } + } + + private fun getFallbackArtworkUrl(cardId: String): String { + return when { + cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL + cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL + else -> when (TwinsHelper.getTwinCardNumber(cardId)) { + TwinCardNumber.First -> Artwork.TWIN_CARD_1 + TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + else -> Artwork.DEFAULT_IMG_URL + } + } + } + + private fun getUrlForArtwork(cardId: String, cardPublicKeyHex: String, artworkId: String): String { + return TangemApi.Companion.BaseUrl.VERIFY.url + TangemApi.ARTWORK + + "?artworkId=${artworkId}&CID=${cardId}&publicKey=$cardPublicKeyHex" + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt index f41c664276..e76a291668 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager +import com.tangem.common.hdWallet.DerivationPath import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel @@ -54,10 +55,12 @@ private class BlockchainNetworkWalletStoreBuilderImpl( return WalletStoreModel( userWalletId = userWalletId, - blockchainNetwork = blockchainNetwork, - walletManager = walletManager, + blockchain = blockchainNetwork.blockchain, + derivationPath = blockchainNetwork.derivationPath?.let { DerivationPath(it) }, walletsData = (listOf(blockchainWalletData) + tokensWalletsData), walletRent = null, + walletManager = walletManager, + blockchainNetwork = blockchainNetwork, ) } } @@ -74,10 +77,12 @@ private class WalletMangerWalletStoreBuilderImpl( return WalletStoreModel( userWalletId = userWalletId, - blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager), - walletManager = walletManager, + blockchain = wallet.blockchain, + derivationPath = wallet.publicKey.derivationPath, walletsData = (listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData)), walletRent = null, + walletManager = walletManager, + blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 6ab45894b9..d940a338ad 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -82,6 +82,7 @@ object ScanCardProcessor { nextHandler = { scanResponse1 -> showDisclaimerIfNeed( scanResponse = scanResponse1, + onProgressStateChange = onProgressStateChange, nextHandler = { scanResponse2 -> onScanSuccess( scanResponse = scanResponse2, @@ -127,6 +128,7 @@ object ScanCardProcessor { private suspend inline fun showDisclaimerIfNeed( scanResponse: ScanResponse, + crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, crossinline nextHandler: suspend (ScanResponse) -> Unit, ) { val disclaimerType = DisclaimerType.get(scanResponse) @@ -137,11 +139,18 @@ object ScanCardProcessor { } else scope.launch(Dispatchers.Main) { delay(DELAY_SDK_DIALOG_CLOSE) store.dispatchOnMain( - DisclaimerAction.Show { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, + DisclaimerAction.Show( + onAcceptCallback = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismissCallback = { + scope.launch(Dispatchers.Main) { + onProgressStateChange(false) + } + }, + ), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt index 512e986cbf..0c0f909c80 100644 --- a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt @@ -33,9 +33,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { when (walletsData.findStatus()) { TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading - TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing( - amount = prevAmount ?: BigDecimal.ZERO, - ) TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount()) TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(calculateAmount()) } @@ -54,7 +51,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { private fun Sequence.mapToStatus(): Sequence { return this.map { walletData -> when (walletData.status) { - is WalletDataModel.Refreshing -> TotalFiatBalanceStatus.Refreshing is WalletDataModel.VerifiedOnline, is WalletDataModel.SameCurrencyTransactionInProgress, is WalletDataModel.TransactionInProgress, @@ -84,18 +80,10 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { ): TotalFiatBalanceStatus { return when (prevStatus) { TotalFiatBalanceStatus.Loading -> prevStatus - TotalFiatBalanceStatus.Refreshing -> when (newStatus) { - TotalFiatBalanceStatus.Loading -> prevStatus - TotalFiatBalanceStatus.Refreshing, - TotalFiatBalanceStatus.Error, - TotalFiatBalanceStatus.Loaded, - -> newStatus - } TotalFiatBalanceStatus.Loaded, TotalFiatBalanceStatus.Error, -> when (newStatus) { TotalFiatBalanceStatus.Loading, - TotalFiatBalanceStatus.Refreshing, TotalFiatBalanceStatus.Error, -> newStatus TotalFiatBalanceStatus.Loaded -> prevStatus @@ -105,7 +93,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { private enum class TotalFiatBalanceStatus { Loading, - Refreshing, Error, Loaded, } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index ae9a72152a..28016d704f 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -10,6 +10,9 @@ import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRunnable { + + override val allowsRequestAccessCodeFromRepository: Boolean = false + override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 57b66d7a62..628c041112 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -20,6 +20,8 @@ class CreateSecondTwinWalletTask( private val creatingWalletMessage: Message, ) : CardSessionRunnable { + override val allowsRequestAccessCodeFromRepository: Boolean = false + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val card = session.environment.card val publicKey = card?.wallets?.firstOrNull()?.publicKey diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt index 73b9e2f40a..d4e00cb3fe 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt @@ -13,6 +13,8 @@ class FinalizeTwinTask( private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair, ) : CardSessionRunnable { + override val allowsRequestAccessCodeFromRepository: Boolean = false + override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 8310047019..b910fcb589 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -20,11 +20,9 @@ class TwinCardsManager( assetReader: AssetReader, ) { private val firstCardId: String = card.cardId - private var secondCardId: String? = null private var currentCardPublicKey: String? = null - var secondCardPublicKey: String? = null - private set + private var secondCardPublicKey: String? = null private val issuerKeyPair: KeyPair = getIssuerKeys(assetReader, card.issuer.publicKey.toHexString()) @@ -57,7 +55,6 @@ class TwinCardsManager( when (response) { is CompletionResult.Success -> { secondCardPublicKey = response.data.wallet.publicKey.toHexString() - secondCardId = response.data.cardId } is CompletionResult.Failure -> {} } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index 62ea1a37a4..dfd62c05f5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -14,24 +14,23 @@ interface UserWalletsListManager { val hasSavedUserWallets: Boolean suspend fun unlockWithBiometry(): CompletionResult - suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult fun lock() - suspend fun selectWallet(walletId: UserWalletId): CompletionResult + suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** - * Save user's wallet - * @param userWallet User's wallet to save + * Save user wallet + * @param userWallet [UserWallet] to save * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an * already saved card - * @return [CompletionResult] operation result + * @return [CompletionResult] of operation * */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult - suspend fun delete(walletIds: List): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun clear(): CompletionResult - suspend fun get(walletId: UserWalletId): CompletionResult + suspend fun get(userWalletId: UserWalletId): CompletionResult companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 248719dd20..f7781ca770 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -28,21 +28,19 @@ internal class BiometricUserWalletsListManager( override val userWallets: Flow> get() = state - .mapLatest { it.wallets } + .mapLatest { it.userWallets } .distinctUntilChanged() override val selectedUserWallet: Flow get() = state .mapLatest { state -> - state.wallets.find { - it.walletId == state.selectedWalletId - } + findSelectedUserWallet(state.userWallets) } .filterNotNull() .distinctUntilChanged() override val selectedUserWalletSync: UserWallet? - get() = findSelectedWallet() + get() = findSelectedUserWallet() override val isLocked: Flow get() = state @@ -53,44 +51,13 @@ internal class BiometricUserWalletsListManager( get() = state.value.isLocked override val hasSavedUserWallets: Boolean - get() = selectedUserWalletRepository.get() != null + get() = publicInformationRepository.isNotEmpty() override suspend fun unlockWithBiometry(): CompletionResult { return unlockWithBiometryInternal() .map { selectedUserWalletSync } } - override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { - state.update { prevState -> - // If the previous state contains a saved user wallet with the same ID, it is also saved - userWallet.isSaved = prevState.wallets.any { - it.walletId == userWallet.walletId && it.isSaved - } - - val newEncryptionKeys = prevState.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } - val newUserWallets = prevState.wallets - .plus(userWallet) - .distinctBy { it.walletId } - - prevState.copy( - encryptionKeys = newEncryptionKeys, - wallets = newUserWallets, - selectedWalletId = userWallet.walletId, - ) - } - - return loadModels() - .map { - state.update { prevState -> - prevState.copy( - isLocked = prevState.wallets.any { it.isLocked }, - ) - } - } - } - override fun lock() { state.update { prevState -> prevState.copy( @@ -100,85 +67,62 @@ internal class BiometricUserWalletsListManager( } } - override suspend fun selectWallet(walletId: UserWalletId): CompletionResult = catching { - if (state.value.selectedWalletId == walletId) { - return@catching findSelectedWallet()!! + override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult = catching { + if (state.value.selectedUserWalletId == userWalletId) { + return@catching findSelectedUserWallet()!! } - if (!state.value.isLocked) { - selectedUserWalletRepository.set(walletId) + selectedUserWalletRepository.set(userWalletId) - state.update { prevState -> - prevState.copy( - selectedWalletId = walletId, - ) - } + state.update { prevState -> + prevState.copy( + selectedUserWalletId = userWalletId, + ) } - findSelectedWallet()!! + findSelectedUserWallet()!! } - override suspend fun save( - userWallet: UserWallet, - canOverride: Boolean, - ): CompletionResult = withUnlock { - val isWalletSaved = state.value.wallets - .filter { it.isSaved } - .any { - // Workaround, check [UserWallet.isTwinnedWith] - it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) - } - - if (isWalletSaved && !canOverride) { - CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) + override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { + return if (canOverride) { + saveInternal(userWallet) } else { - val newEncryptionKeys = state.value.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } + val isWalletSaved = state.value.userWallets + .any { + // Workaround, check [UserWallet.isTwinnedWith] + it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) + } - keysRepository.store(newEncryptionKeys) - .doOnSuccess { - state.update { prevState -> - prevState.copy( - encryptionKeys = newEncryptionKeys, - ) - } - } - .flatMap { publicInformationRepository.save(userWallet) } - .flatMap { sensitiveInformationRepository.save(userWallet) } - .flatMap { loadModels() } - .doOnSuccess { - userWallet.isSaved = true - } + if (isWalletSaved) { + CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) + } else { + saveInternal(userWallet) + } } } - override suspend fun delete(walletIds: List): CompletionResult { - val walletIdsToRemove = state.value.wallets - .map { it.walletId } - .filter { it in walletIds } - val remainingEncryptionKeys = state.value.encryptionKeys - .filter { it.walletId !in walletIdsToRemove } + override suspend fun delete(userWalletIds: List): CompletionResult { + if (userWalletIds.isEmpty()) { + return CompletionResult.Success(Unit) + } - changeSelectedWalletIfNeeded(walletIdsToRemove) + changeSelectedUserWalletIdIfNeeded(userWalletIds) - return sensitiveInformationRepository.delete(walletIdsToRemove) - .flatMap { publicInformationRepository.delete(walletIdsToRemove) } - .flatMap { keysRepository.store(remainingEncryptionKeys) } + return sensitiveInformationRepository.delete(userWalletIds) + .flatMap { publicInformationRepository.delete(userWalletIds) } + .flatMap { keysRepository.delete(userWalletIds) } .map { state.update { prevState -> prevState.copy( - encryptionKeys = remainingEncryptionKeys, - wallets = prevState.wallets.filter { it.walletId !in walletIdsToRemove }, + encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds }, + userWallets = prevState.userWallets.filter { it.walletId !in userWalletIds }, ) } } } override suspend fun clear(): CompletionResult { - return sensitiveInformationRepository.delete( - walletIds = state.value.wallets.map { it.walletId }, - ) + return sensitiveInformationRepository.clear() .flatMap { publicInformationRepository.clear() } .flatMap { keysRepository.clear() } .map { @@ -187,17 +131,37 @@ internal class BiometricUserWalletsListManager( } } - override suspend fun get(walletId: UserWalletId): CompletionResult { + override suspend fun get(userWalletId: UserWalletId): CompletionResult { return catching { - state.value.wallets.first { it.walletId == walletId } + state.value.userWallets.first { it.walletId == userWalletId } } } - private suspend inline fun withUnlock( - block: () -> CompletionResult, - ): CompletionResult { - return (if (state.value.isLocked) unlockWithBiometryInternal() else CompletionResult.Success(Unit)) - .flatMap { block() } + private suspend fun saveInternal(userWallet: UserWallet): CompletionResult { + val newEncryptionKeys = state.value.encryptionKeys + .plus(UserWalletEncryptionKey(userWallet)) + .distinctBy { it.walletId } + + return keysRepository.store(newEncryptionKeys) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + encryptionKeys = newEncryptionKeys, + selectedUserWalletId = userWallet.walletId, + ) + } + } + .flatMap { publicInformationRepository.save(userWallet) } + .flatMap { sensitiveInformationRepository.save(userWallet) } + .map { selectedUserWalletRepository.set(userWallet.walletId) } + .flatMap { loadModels() } + .doOnSuccess { + state.update { prevState -> + prevState.copy( + isLocked = prevState.userWallets.any { it.isLocked }, + ) + } + } } private suspend fun unlockWithBiometryInternal(): CompletionResult { @@ -223,11 +187,11 @@ internal class BiometricUserWalletsListManager( return getSavedUserWallets() .map { userWallets -> if (userWallets.isNotEmpty()) state.update { prevState -> - val wallets = (userWallets + prevState.wallets).distinctBy { it.walletId } + val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId } prevState.copy( - wallets = wallets, - selectedWalletId = findOrSetSelectedWallet(prevState.selectedWalletId, wallets), + userWallets = wallets, + selectedUserWalletId = findOrSetSelectedUserWalletId(prevState.selectedUserWalletId, wallets), ) } } @@ -247,56 +211,55 @@ internal class BiometricUserWalletsListManager( } } - private fun findOrSetSelectedWallet( + private fun findOrSetSelectedUserWalletId( prevSelectedWalletId: UserWalletId?, userWallets: List, ): UserWalletId? { - return prevSelectedWalletId - ?: (selectedUserWalletRepository.get() - ?: (userWallets.firstOrNull()?.walletId - ?.also { selectedUserWalletRepository.set(it) })) + val findUnlockedAndSet = { + userWallets.firstOrNull { !it.isLocked } + ?.walletId + ?.also { selectedUserWalletRepository.set(it) } + } + + return prevSelectedWalletId ?: (selectedUserWalletRepository.get() ?: findUnlockedAndSet()) } - private fun changeSelectedWalletIfNeeded( - walletsIdsToRemove: List, - ) { - val remainingWallets = state.value.wallets.filter { + private fun changeSelectedUserWalletIdIfNeeded(walletsIdsToRemove: List) { + val remainingWallets = state.value.userWallets.filter { it.walletId !in walletsIdsToRemove } - val selectedWallet = findSelectedWallet() + val selectedWallet = findSelectedUserWallet() when { remainingWallets.isEmpty() -> { state.update { prevState -> prevState.copy( - selectedWalletId = null, + selectedUserWalletId = null, ) } selectedUserWalletRepository.set(null) } !remainingWallets.contains(selectedWallet) -> { - val newSelectedWallet = remainingWallets.first() + val newSelectedWallet = remainingWallets.firstOrNull { !it.isLocked } state.update { prevState -> prevState.copy( - selectedWalletId = newSelectedWallet.walletId, + selectedUserWalletId = newSelectedWallet?.walletId, ) } - selectedUserWalletRepository.set(newSelectedWallet.walletId) + selectedUserWalletRepository.set(newSelectedWallet?.walletId) } } } - private fun findSelectedWallet(): UserWallet? { - return with(state.value) { - wallets.find { - it.walletId == selectedWalletId - } + private fun findSelectedUserWallet(userWallets: List = state.value.userWallets): UserWallet? { + return userWallets.find { + it.walletId == state.value.selectedUserWalletId } } private data class State( val encryptionKeys: List = emptyList(), - val wallets: List = emptyList(), - val selectedWalletId: UserWalletId? = null, + val userWallets: List = emptyList(), + val selectedUserWalletId: UserWalletId? = null, val isLocked: Boolean = true, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt index a3c3a3e929..e616c48758 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt @@ -26,15 +26,11 @@ class DummyUserWalletsListManager : UserWalletsListManager { return CompletionResult.Success(null) } - override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { - return CompletionResult.Success(Unit) - } - override fun lock() { /* no-op */ } - override suspend fun selectWallet(walletId: UserWalletId): CompletionResult { + override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult { return catching { error("Not implemented") } @@ -44,7 +40,7 @@ class DummyUserWalletsListManager : UserWalletsListManager { return CompletionResult.Success(Unit) } - override suspend fun delete(walletIds: List): CompletionResult { + override suspend fun delete(userWalletIds: List): CompletionResult { return CompletionResult.Success(Unit) } @@ -52,7 +48,7 @@ class DummyUserWalletsListManager : UserWalletsListManager { return CompletionResult.Success(Unit) } - override suspend fun get(walletId: UserWalletId): CompletionResult { + override suspend fun get(userWalletId: UserWalletId): CompletionResult { return catching { error("Not implemented") } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt index b2308343ff..d383e11e3f 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt @@ -17,4 +17,5 @@ internal data class UserWalletPublicInformation( val artworkUrl: String, val cardsInWallet: Set, val scanResponse: ScanResponse, + val isMultiCurrency: Boolean, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index 974879ca31..f10e0beeb5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -1,10 +1,35 @@ package com.tangem.tap.domain.userWalletList.repository import com.tangem.common.CompletionResult +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey internal interface UserWalletsKeysRepository { + /** + * Obtaining the encryption keys of all user wallets from the biometric vault. Biometric authentication required + * If that operation runs more than biometric cipher key expiration time then the user will not receive all + * encryption keys + * @return [CompletionResult] of operation with stored [UserWalletEncryptionKey] list + * */ suspend fun getAll(): CompletionResult> + + /** + * Store the encryption keys for user wallets. Biometric authentication not required + * @param encryptionKeys List of encryption keys for user wallets + * @return [CompletionResult] of operation + * */ suspend fun store(encryptionKeys: List): CompletionResult + + /** + * Delete encryption keys for user wallets. Biometric authentication not required + * @param userWalletsIds List of [UserWalletId] whose encryption keys will be deleted + * @return [CompletionResult] of operation + * */ + suspend fun delete(userWalletsIds: List): CompletionResult + + /** + * Clear all encryption keys for user wallets. Biometric authentication not required + * @return [CompletionResult] of operation + * */ suspend fun clear(): CompletionResult } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt index 8765a6b804..f0387d3f0e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt @@ -12,4 +12,6 @@ internal interface UserWalletsPublicInformationRepository { suspend fun delete(walletIds: List): CompletionResult suspend fun clear(): CompletionResult + + fun isNotEmpty(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt index dcc5677814..7e03055150 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -12,5 +12,6 @@ internal interface UserWalletsSensitiveInformationRepository { encryptionKeys: List, ): CompletionResult> - suspend fun delete(walletIds: List): CompletionResult + suspend fun delete(userWalletsIds: List): CompletionResult + suspend fun clear(): CompletionResult } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index cd39605d96..5e57d10474 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -6,62 +6,183 @@ import com.squareup.moshi.Types import com.tangem.common.CompletionResult import com.tangem.common.biometric.BiometricManager import com.tangem.common.biometric.BiometricStorage +import com.tangem.common.core.TangemSdkError +import com.tangem.common.doOnFailure +import com.tangem.common.fold import com.tangem.common.map import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext internal class BiometricUserWalletsKeysRepository( moshi: Moshi, - secureStorage: SecureStorage, biometricManager: BiometricManager, + private val secureStorage: SecureStorage, ) : UserWalletsKeysRepository { private val biometricStorage = BiometricStorage( biometricManager = biometricManager, secureStorage = secureStorage, ) - private val walletsKeysAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(List::class.java, UserWalletEncryptionKey::class.java), + private val encryptionKeyAdapter: JsonAdapter = moshi.adapter( + UserWalletEncryptionKey::class.java, + ) + private val userWalletsIdsListAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, UserWalletId::class.java), ) override suspend fun getAll(): CompletionResult> { - return biometricStorage.get(key = StorageKey.WalletEncryptionKeys.name) - .map { encryptionKeys -> - encryptionKeys.decodeToKeys() - } - .mapFailure { error -> - UserWalletListError.ReceiveEncryptionKeysError(error.cause ?: error) - } + return withContext(Dispatchers.IO) { + getAllInternal() + .mapFailure { error -> + UserWalletListError.ReceiveEncryptionKeysError(error.cause ?: error) + } + } } override suspend fun store(encryptionKeys: List): CompletionResult { - return biometricStorage.store( - key = StorageKey.WalletEncryptionKeys.name, - data = encryptionKeys.encode(), - ) - .mapFailure { error -> - UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) + return withContext(Dispatchers.IO) { + encryptionKeys.map { storeEncryptionKey(it) } + .fold() + .mapFailure { error -> + UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) + } + } + } + + override suspend fun delete(userWalletsIds: List): CompletionResult { + return withContext(Dispatchers.IO) { + userWalletsIds.map { userWalletId -> + deleteEncryptionKey(userWalletId) } + .fold() + .map { deleteUserWalletsIds(userWalletsIds) } + } } override suspend fun clear(): CompletionResult { - return biometricStorage.delete(key = StorageKey.WalletEncryptionKeys.name) + return withContext(Dispatchers.IO) { + getUserWalletsIds() + .map { userWalletId -> + deleteEncryptionKey(userWalletId) + } + .fold() + .map { + clearUserWalletsIds() + } + } } - private fun List.encode(): ByteArray { - return this.let(walletsKeysAdapter::toJson) - .encodeToByteArray(throwOnInvalidSequence = true) + private suspend fun getAllInternal(): CompletionResult> { + return getUserWalletsIds() + .map { userWalletId -> + // This is possible because the Card SDK cipher key has an expiration time + // If this operation runs more than that expiration time, the user will not receive all encryption keys + getEncryptionKey(userWalletId) + .doOnFailure { error -> + // If the user cancels biometric authentication, cancel the request for all keys + if (error is TangemSdkError.BiometricsAuthenticationFailed) { + return CompletionResult.Failure(error) + } + } + } + .fold(listOf()) { acc, data -> + if (data != null) acc + data else acc + } } - private fun ByteArray?.decodeToKeys(): List { - return this?.decodeToString(throwOnInvalidSequence = true) - ?.let(walletsKeysAdapter::fromJson) - .orEmpty() + private suspend fun getEncryptionKey(userWalletId: UserWalletId): CompletionResult { + return biometricStorage.get(StorageKey.WalletEncryptionKey(userWalletId).name) + .map { it.decodeToKey() } } - private enum class StorageKey { - WalletEncryptionKeys + private suspend fun storeEncryptionKey(encryptionKey: UserWalletEncryptionKey): CompletionResult { + return biometricStorage.store( + key = StorageKey.WalletEncryptionKey(encryptionKey.walletId).name, + data = encryptionKey.encode(), + ) + .map { storeUserWalletId(encryptionKey.walletId) } + } + + private suspend fun deleteEncryptionKey(userWalletId: UserWalletId): CompletionResult { + return biometricStorage.delete(StorageKey.WalletEncryptionKey(userWalletId).name) + } + + private suspend fun getUserWalletsIds(): List { + return withContext(Dispatchers.IO) { + secureStorage.get(StorageKey.UserWalletIds.name) + .decodeToUserWalletsIds() + } + } + + private suspend fun storeUserWalletId(userWalletId: UserWalletId) { + val userWalletIds = (getUserWalletsIds() + userWalletId).distinct() + + withContext(Dispatchers.IO) { + secureStorage.store(userWalletIds.encode(), StorageKey.UserWalletIds.name) + } + } + + private suspend fun deleteUserWalletsIds(userWalletsIds: List) { + val remainingIds = (getUserWalletsIds() - userWalletsIds.toSet()) + + withContext(Dispatchers.IO) { + secureStorage.store(remainingIds.encode(), StorageKey.UserWalletIds.name) + } + } + + private suspend fun clearUserWalletsIds() { + withContext(Dispatchers.IO) { + secureStorage.delete(StorageKey.UserWalletIds.name) + } + } + + private suspend fun UserWalletEncryptionKey.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(encryptionKeyAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun ByteArray?.decodeToKey(): UserWalletEncryptionKey? { + return withContext(Dispatchers.Default) { + this@decodeToKey + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(encryptionKeyAdapter::fromJson) + } + } + + private suspend fun List.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(userWalletsIdsListAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun ByteArray?.decodeToUserWalletsIds(): List { + return withContext(Dispatchers.Default) { + this@decodeToUserWalletsIds + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(userWalletsIdsListAdapter::fromJson) + .orEmpty() + } + } + + private sealed interface StorageKey { + val name: String + + class WalletEncryptionKey(userWalletId: UserWalletId) : StorageKey { + override val name: String = "user_wallet_encryption_key_${userWalletId.stringValue}" + } + + object UserWalletIds : StorageKey { + override val name: String = "user_wallets_ids_with_saved_keys" + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index 46d0fc073b..46a016f8c0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -25,19 +25,21 @@ internal class DefaultUserWalletsPublicInformationRepository( ) override suspend fun save(userWallet: UserWallet): CompletionResult { - return getAll() - .flatMap { savedInformation -> - val infoToSave = withContext(Dispatchers.Default) { - savedInformation.toMutableList() - .apply { - replaceByOrAdd(userWallet.publicInformation) { - userWallet.walletId == it.walletId + return withContext(Dispatchers.IO) { + getAll() + .flatMap { savedInformation -> + val infoToSave = withContext(Dispatchers.Default) { + savedInformation.toMutableList() + .apply { + replaceByOrAdd(userWallet.publicInformation) { + userWallet.walletId == it.walletId + } } - } - } + } - save(infoToSave) - } + save(infoToSave) + } + } } override suspend fun getAll(): CompletionResult> = catching { @@ -50,20 +52,28 @@ internal class DefaultUserWalletsPublicInformationRepository( } override suspend fun delete(walletIds: List): CompletionResult { - return getAll() - .flatMap { publicInformation -> - val infoToRemove = publicInformation - .filter { it.walletId in walletIds } - .toSet() + return withContext(Dispatchers.IO) { + getAll() + .flatMap { publicInformation -> + val infoToRemove = publicInformation + .filter { it.walletId in walletIds } + .toSet() - save( - publicInformation = publicInformation - infoToRemove, - ) - } + save( + publicInformation = publicInformation - infoToRemove, + ) + } + } } override suspend fun clear(): CompletionResult = catching { - secureStorage.delete(StorageKey.UserWalletPublicInformation.name) + withContext(Dispatchers.IO) { + secureStorage.delete(StorageKey.UserWalletPublicInformation.name) + } + } + + override fun isNotEmpty(): Boolean { + return secureStorage.get(StorageKey.UserWalletPublicInformation.name)?.isNotEmpty() == true } @JvmName("saveWithPublicInformation") diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index c338d17c67..5918312b6e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -3,12 +3,14 @@ package com.tangem.tap.domain.userWalletList.repository.implementation import android.security.keystore.KeyProperties import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi +import com.squareup.moshi.Types import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.common.util.UserWalletId import com.tangem.domain.common.util.encryptionKey +import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey @@ -28,29 +30,23 @@ internal class DefaultUserWalletsSensitiveInformationRepository( private val sensitiveInformationAdapter: JsonAdapter = moshi.adapter( UserWalletSensitiveInformation::class.java, ) + private val encryptedSensitiveInformationMapAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java), + ) + private val cipher: Cipher by lazy { Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") } override suspend fun save(userWallet: UserWallet): CompletionResult { return catching { - withContext(Dispatchers.Default) { - userWallet.sensitiveInformation - .let(sensitiveInformationAdapter::toJson) - .encodeToByteArray(throwOnInvalidSequence = true) - .encryptAndStoreIv( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - .let { encryptedInformation -> - withContext(Dispatchers.IO) { - secureStorage.store( - data = encryptedInformation, - account = StorageKey.SensitiveInformation(userWallet.walletId).name, - ) - } - } - } + val encryptedSensitiveInformation = userWallet.sensitiveInformation + .encode() + .encryptAndStoreIv(userWallet.walletId.stringValue, userWallet.scanResponse.card.encryptionKey) + + getAllEncrypted().toMutableMap() + .apply { set(userWallet.walletId.stringValue, encryptedSensitiveInformation) } + .let { saveInternal(it) } } .mapFailure { error -> UserWalletListError.SaveSensitiveInformationError(error.cause ?: error) @@ -60,77 +56,130 @@ internal class DefaultUserWalletsSensitiveInformationRepository( override suspend fun getAll( encryptionKeys: List, ): CompletionResult> { + if (encryptionKeys.isEmpty()) { + return CompletionResult.Success(emptyMap()) + } + return catching { - if (encryptionKeys.isEmpty()) { - return@catching emptyMap() - } + val encryptedSensitiveInformation = getAllEncrypted() - val keyToEncryptedInformation = withContext(Dispatchers.IO) { - encryptionKeys.associateWith { encryptionKey -> - secureStorage.get(StorageKey.SensitiveInformation(encryptionKey.walletId).name) + encryptionKeys + .associateBy { it.walletId } + .mapValues { (userWalletId, encryptionKey) -> + encryptedSensitiveInformation[userWalletId.stringValue] + ?.getIvAndDecrypt(userWalletId.stringValue, encryptionKey.encryptionKey) + .decodeToSensitiveInformation() } - } - - withContext(Dispatchers.Default) { - val keyToInformation = - mutableMapOf() - - keyToEncryptedInformation.forEach { (key, encryptedInformation) -> - val information = encryptedInformation - ?.getIvAndDecrypt( - walletId = key.walletId, - encryptionKey = key.encryptionKey, - ) - ?.decodeToString(throwOnInvalidSequence = true) - ?.let(sensitiveInformationAdapter::fromJson) - - if (information != null) { - keyToInformation[key.walletId] = information - } - } - - keyToInformation - } + .filterNotNull() } .mapFailure { error -> UserWalletListError.ReceiveSensitiveInformationError(error.cause ?: error) } } - override suspend fun delete(walletIds: List): CompletionResult = catching { - walletIds - .forEach { walletId -> - secureStorage.delete(StorageKey.SensitiveInformation(walletId).name) - } + override suspend fun delete(userWalletsIds: List): CompletionResult { + return catching { deleteInternal(userWalletsIds) } } - private fun ByteArray.encryptAndStoreIv(walletId: UserWalletId, encryptionKey: ByteArray): ByteArray { - val secretKey = SecretKeySpec(encryptionKey, algorithm) - cipher.init(Cipher.ENCRYPT_MODE, secretKey) - val encryptedData = cipher.doFinal(this) - secureStorage.store(data = cipher.iv, account = StorageKey.SensitiveInformationIv(walletId).name) - return encryptedData + override suspend fun clear(): CompletionResult { + return catching { clearInternal() } } - private fun ByteArray.getIvAndDecrypt(walletId: UserWalletId, encryptionKey: ByteArray): ByteArray? { - val iv = secureStorage.get(StorageKey.SensitiveInformationIv(walletId).name) - ?: error("IV not found") - val ivParam = IvParameterSpec(iv) - val secretKeySpec = SecretKeySpec(encryptionKey, algorithm) - return cipher - .also { it.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParam) } - .doFinal(this) + private suspend fun getAllEncrypted(): Map { + return withContext(Dispatchers.IO) { + secureStorage.get(StorageKey.UserWalletsSensitiveInformation.name) + .decodeToEncryptedSensitiveInformation() + } + } + + private suspend fun saveInternal(sensitiveInformation: Map) { + return withContext(Dispatchers.IO) { + secureStorage.store( + account = StorageKey.UserWalletsSensitiveInformation.name, + data = sensitiveInformation.encode(), + ) + } + } + + private suspend fun deleteInternal(userWalletsIds: List) { + return saveInternal( + sensitiveInformation = getAllEncrypted() - userWalletsIds.map { it.stringValue }.toSet(), + ) + } + + private suspend fun clearInternal() { + withContext(Dispatchers.IO) { + secureStorage.delete(StorageKey.UserWalletsSensitiveInformation.name) + } + } + + private suspend fun ByteArray?.decodeToEncryptedSensitiveInformation(): Map { + return withContext(Dispatchers.Default) { + this@decodeToEncryptedSensitiveInformation + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(encryptedSensitiveInformationMapAdapter::fromJson) + .orEmpty() + } + } + + private suspend fun ByteArray?.decodeToSensitiveInformation(): UserWalletSensitiveInformation? { + return withContext(Dispatchers.Default) { + this@decodeToSensitiveInformation + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(sensitiveInformationAdapter::fromJson) + } + } + + private suspend fun UserWalletSensitiveInformation.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(sensitiveInformationAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun Map.encode(): ByteArray { + return withContext(Dispatchers.Default) { + this@encode + .let(encryptedSensitiveInformationMapAdapter::toJson) + .encodeToByteArray(throwOnInvalidSequence = true) + } + } + + private suspend fun ByteArray.encryptAndStoreIv(userWalletId: String, encryptionKey: ByteArray): ByteArray { + return withContext(Dispatchers.Default) { + val secretKey = SecretKeySpec(encryptionKey, algorithm) + cipher.init(Cipher.ENCRYPT_MODE, secretKey) + val encryptedData = cipher.doFinal(this@encryptAndStoreIv) + secureStorage.store(data = cipher.iv, account = StorageKey.SensitiveInformationIv(userWalletId).name) + encryptedData + } + } + + private suspend fun ByteArray.getIvAndDecrypt( + userWalletId: String, + encryptionKey: ByteArray, + ): ByteArray? { + return withContext(Dispatchers.Default) { + val iv = secureStorage.get(StorageKey.SensitiveInformationIv(userWalletId).name) + ?: error("IV not found") + val ivParam = IvParameterSpec(iv) + val secretKeySpec = SecretKeySpec(encryptionKey, algorithm) + cipher + .also { it.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParam) } + .doFinal(this@getIvAndDecrypt) + } } private sealed interface StorageKey { val name: String - class SensitiveInformation(walletId: UserWalletId) : StorageKey { - override val name: String = "user_wallet_sensitive_information_${walletId.stringValue}" + object UserWalletsSensitiveInformation : StorageKey { + override val name: String = "user_wallets_sensitive_information" } - class SensitiveInformationIv(walletId: UserWalletId) : StorageKey { - override val name: String = "user_wallet_sensitive_information_iv_${walletId.stringValue}" + class SensitiveInformationIv(userWalletId: String) : StorageKey { + override val name: String = "user_wallet_sensitive_information_iv_${userWalletId}" } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index 810ba463f6..7d8170895c 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -14,6 +14,7 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation walletId = walletId, artworkUrl = artworkUrl, cardsInWallet = cardsInWallet, + isMultiCurrency = isMultiCurrency, scanResponse = scanResponse.copy( card = scanResponse.card.copy( wallets = emptyList(), @@ -28,6 +29,7 @@ internal fun UserWalletPublicInformation.toUserWallet(): UserWallet { artworkUrl = artworkUrl, cardsInWallet = cardsInWallet, scanResponse = scanResponse, + isMultiCurrency = isMultiCurrency, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt index f1115ee3ef..fb77a29584 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/WalletCurrenciesManager.kt @@ -2,13 +2,12 @@ package com.tangem.tap.domain.walletCurrencies import com.tangem.common.CompletionResult import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency interface WalletCurrenciesManager { suspend fun update( userWallet: UserWallet, - blockchainNetwork: BlockchainNetwork, + currency: Currency, ): CompletionResult suspend fun addCurrencies( diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index 837ff8452c..5fd2e25234 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -1,25 +1,25 @@ package com.tangem.tap.domain.walletCurrencies.implementation +import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.CompletionResult -import com.tangem.common.catching import com.tangem.common.flatMap +import com.tangem.common.fold +import com.tangem.common.map import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.builders.WalletStoreBuilder import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager -import com.tangem.tap.domain.walletStores.implementation.utils.fold import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.getTokens -import com.tangem.tap.features.wallet.models.toCurrencies +import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext internal class DefaultWalletCurrenciesManager( @@ -31,18 +31,18 @@ internal class DefaultWalletCurrenciesManager( ) : WalletCurrenciesManager { override suspend fun update( userWallet: UserWallet, - blockchainNetwork: BlockchainNetwork, - ): CompletionResult { - val walletStore = walletStoresRepository.get(userWallet.walletId).first() + currency: Currency, + ): CompletionResult = withContext(Dispatchers.Default) { + val walletStore = walletStoresRepository.getSync(userWallet.walletId) .find { - it.blockchainNetwork.blockchain == blockchainNetwork.blockchain - && it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath + it.blockchain == currency.blockchain + && it.derivationPath?.rawPath == currency.derivationPath } - return if (walletStore != null) { - walletAmountsRepository.update( - userWallet = userWallet, + if (walletStore != null) { + walletAmountsRepository.updateAmountsForWalletStore( walletStore = walletStore, + userWallet = userWallet, fiatCurrency = appCurrencyProvider(), ) } else CompletionResult.Success(Unit) @@ -52,25 +52,42 @@ internal class DefaultWalletCurrenciesManager( userWallet: UserWallet, currenciesToAdd: List, ): CompletionResult = withContext(Dispatchers.Default) { - var newBlockchainNetworks = listOf() - catching { - val card = userWallet.scanResponse.card - val savedCurrencies = withContext(Dispatchers.IO) { - userTokensRepository.getUserTokens(card) - } - newBlockchainNetworks = (savedCurrencies + currenciesToAdd) - .toBlockchainNetworks(userWallet.scanResponse.card) - val newCurrencies = newBlockchainNetworks.toCurrencies() + val card = userWallet.scanResponse.card + val updatedBlockchainNetworks = currenciesToAdd + .addMissingBlockchains(card) + .toBlockchainNetworks() + val newCurrencies = (getSavedCurrencies(userWallet.walletId) + currenciesToAdd) + .addMissingBlockchains(card) - withContext(Dispatchers.IO) { - userTokensRepository.saveUserTokens( - card = card, - tokens = newCurrencies, + updateWalletStores(userWallet, updatedBlockchainNetworks) + .map { + saveUserCurrencies(card, newCurrencies) + } + .flatMap { + walletAmountsRepository.updateAmountsForUserWallet( + userWallet = userWallet, + fiatCurrency = appCurrencyProvider(), ) } - } + } + + override suspend fun removeCurrencies( + userWallet: UserWallet, + currenciesToRemove: List, + ): CompletionResult = withContext(Dispatchers.Default) { + val card = userWallet.scanResponse.card + val remainingCurrencies = getSavedCurrencies(userWallet.walletId) + .filter { it !in currenciesToRemove } + val remainingBlockchains = remainingCurrencies + .filterIsInstance() + .map { it.blockchain } + + walletStoresRepository.deleteDifference(userWallet.walletId, remainingBlockchains) .flatMap { - newBlockchainNetworks.updateWalletStores(userWallet) + updateWalletStores(userWallet, remainingCurrencies.toBlockchainNetworks()) + } + .map { + saveUserCurrencies(card, remainingCurrencies) } } @@ -81,108 +98,69 @@ internal class DefaultWalletCurrenciesManager( return removeCurrencies(userWallet, listOf(currencyToRemove)) } - override suspend fun removeCurrencies( - userWallet: UserWallet, - currenciesToRemove: List, - ): CompletionResult = withContext(Dispatchers.Default) { - var remainingBlockchainsNetworks = emptyList() - catching { - val card = userWallet.scanResponse.card - val savedCurrencies = withContext(Dispatchers.IO) { - userTokensRepository.getUserTokens(card) - } - - val remainingCurrencies = arrayListOf() - savedCurrencies.forEach { savedCurrency -> - if (savedCurrency !in currenciesToRemove) { - remainingCurrencies.add(savedCurrency) + private suspend fun getSavedCurrencies(userWalletId: UserWalletId): List { + return withContext(Dispatchers.Default) { + walletStoresRepository.getSync(userWalletId) + .flatMap { walletStore -> + walletStore.walletsData.map { it.currency } } - } - - remainingBlockchainsNetworks = remainingCurrencies.toBlockchainNetworks(userWallet.scanResponse.card) - - withContext(Dispatchers.IO) { - userTokensRepository.saveUserTokens( - card = card, - tokens = remainingCurrencies, - ) - } } - .flatMap { - remainingBlockchainsNetworks.updateWalletStores(userWallet) - } } - private fun List.toBlockchainNetworks(card: CardDTO): List { - val blockchainNetworks = arrayListOf() - val findDerivationPath: (currency: Currency) -> String? = { currency -> - currency.derivationPath - ?: currency.blockchain.derivationPath(card.derivationStyle) - ?.rawPath + private suspend fun saveUserCurrencies(card: CardDTO, currencies: List) { + withContext(Dispatchers.IO) { + userTokensRepository.saveUserTokens( + card = card, + tokens = currencies, + ) } + } - for (currency in this.sortedByDescending { it.isBlockchain() }) { + private fun List.addMissingBlockchains(card: CardDTO): List { + val newCurrencies = arrayListOf() + + for (currency in this.sortedByDescending { it is Currency.Blockchain }) { when (currency) { is Currency.Blockchain -> { - val blockchainNetwork = BlockchainNetwork( - blockchain = currency.blockchain, - derivationPath = findDerivationPath(currency), - tokens = getTokens(currency), - ) - - blockchainNetworks.add(blockchainNetwork) + newCurrencies.add(currency.updateDerivationPath(card.derivationStyle)) } - is Currency.Token -> { - val tokenBlockchainNetworkIndex = blockchainNetworks - .indexOfFirst { - it.blockchain == currency.blockchain && - it.derivationPath == currency.derivationPath - } - if (tokenBlockchainNetworkIndex == -1) { - blockchainNetworks.add( - BlockchainNetwork( + is Currency.Token -> { + val containsTokenBlockchain = newCurrencies.any { + it.isBlockchain() && it.blockchain == currency.blockchain + } + + if (containsTokenBlockchain) { + newCurrencies.add(currency.updateDerivationPath(card.derivationStyle)) + } else { + val derivationPath = findDerivationPath(currency, card.derivationStyle) + newCurrencies.add( + Currency.Blockchain( blockchain = currency.blockchain, - derivationPath = findDerivationPath(currency), - tokens = listOf(currency.token), + derivationPath = derivationPath, ), ) - } else { - val tokenBlockchainNetwork = blockchainNetworks[tokenBlockchainNetworkIndex] - if (currency.token in tokenBlockchainNetwork.tokens) { - continue - } else { - blockchainNetworks.add( - tokenBlockchainNetworkIndex, - tokenBlockchainNetwork.copy( - tokens = tokenBlockchainNetwork.tokens + currency.token, - ), - ) - } + newCurrencies.add( + currency.copy(derivationPath = derivationPath), + ) } } } } - return blockchainNetworks + return newCurrencies } - private suspend fun List.updateWalletStores( + private suspend fun updateWalletStores( userWallet: UserWallet, + blockchainNetworks: List, ): CompletionResult { val userWalletId = userWallet.walletId - return this - .also { blockchainNetworks -> - walletStoresRepository.deleteDifference( - userWalletId = userWalletId, - currentBlockchains = blockchainNetworks.map { it.blockchain }, - ) - } + return blockchainNetworks .map { blockchainNetwork -> - walletManagersRepository.findOrMake( + walletManagersRepository.findOrMakeMultiCurrencyWalletManager( userWallet = userWallet, blockchainNetwork = blockchainNetwork, - refresh = true, ) .flatMap { walletManager -> walletStoresRepository.storeOrUpdate( @@ -194,11 +172,25 @@ internal class DefaultWalletCurrenciesManager( } } .fold() - .flatMap { - walletAmountsRepository.update( - userWallet = userWallet, - fiatCurrency = appCurrencyProvider(), - ) - } + } + + private fun Currency.updateDerivationPath(cardDerivationStyle: DerivationStyle?): Currency { + val findDerivationPath: () -> String? = { + findDerivationPath(this, cardDerivationStyle) + } + + return when (this) { + is Currency.Blockchain -> this.copy( + derivationPath = findDerivationPath(), + ) + + is Currency.Token -> this.copy( + derivationPath = findDerivationPath(), + ) + } + } + + private fun findDerivationPath(currency: Currency, cardDerivationStyle: DerivationStyle?): String? { + return currency.derivationPath ?: currency.blockchain.derivationPath(cardDerivationStyle)?.rawPath } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt index 346a6108e8..e834cf0541 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt @@ -10,7 +10,7 @@ interface WalletStoresManager { fun getAll(): Flow>> fun get(userWalletId: UserWalletId): Flow> - suspend fun delete(userWalletsIds: List): CompletionResult + suspend fun delete(userWalletsIds: List): CompletionResult suspend fun clear(): CompletionResult suspend fun fetch( @@ -23,5 +23,9 @@ interface WalletStoresManager { refresh: Boolean = false, ): CompletionResult + suspend fun updateAmounts( + userWallets: List, + ): CompletionResult + companion object } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt index 041ea91557..219d7f4af1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -2,19 +2,19 @@ package com.tangem.tap.domain.walletStores.implementation import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult +import com.tangem.common.doOnSuccess import com.tangem.common.flatMap import com.tangem.common.flatMapOnFailure +import com.tangem.common.fold import com.tangem.common.map import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.WalletStoreBuilder import com.tangem.tap.domain.tokens.UserTokensRepository import com.tangem.tap.domain.walletStores.WalletStoresError import com.tangem.tap.domain.walletStores.WalletStoresManager -import com.tangem.tap.domain.walletStores.implementation.utils.fold import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository @@ -22,6 +22,7 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.update import kotlinx.coroutines.withContext @@ -40,12 +41,12 @@ internal class DefaultWalletStoresManager( override fun get(userWalletId: UserWalletId): Flow> { return walletStoresRepository.get(userWalletId) + .distinctUntilChanged() } - override suspend fun delete(userWalletsIds: List): CompletionResult { - val walletIds = userWalletsIds.map { UserWalletId(it) } - return walletStoresRepository.delete(walletIds) - .flatMap { walletManagersRepository.delete(walletIds) } + override suspend fun delete(userWalletsIds: List): CompletionResult { + return walletStoresRepository.delete(userWalletsIds) + .flatMap { walletManagersRepository.delete(userWalletsIds) } } override suspend fun clear(): CompletionResult { @@ -55,7 +56,7 @@ internal class DefaultWalletStoresManager( override suspend fun fetch( userWallets: List, refresh: Boolean, - ): CompletionResult { + ): CompletionResult = withContext(Dispatchers.Default) { val fiatCurrency = appCurrencyProvider.invoke() val isFiatCurrencyChanged = state.value.fiatCurrency != fiatCurrency @@ -65,18 +66,18 @@ internal class DefaultWalletStoresManager( ) } - return userWallets + userWallets .mapNotNull { userWallet -> val hasNotWalletStoresForUserWallet = !walletStoresRepository.contains(userWallet.walletId) if (refresh || hasNotWalletStoresForUserWallet || isFiatCurrencyChanged) { - fetchWalletsIfNeeded(userWallet, refresh) + fetchWalletsIfNeeded(userWallet) } else null } - .fold(initial = arrayListOf()) { acc, data -> + .fold(arrayListOf()) { acc, data -> acc.apply { add(data) } } .flatMap { - walletAmountsRepository.update(it, fiatCurrency) + walletAmountsRepository.updateAmountsForUserWallets(it, fiatCurrency) } } @@ -87,22 +88,29 @@ internal class DefaultWalletStoresManager( return fetch(listOf(userWallet), refresh) } - private suspend fun fetchWalletsIfNeeded( - userWallet: UserWallet, - refresh: Boolean, - ): CompletionResult { - return if (userWallet.scanResponse.card.isMultiwalletAllowed) { - fetchMultiWallets(userWallet, refresh) + override suspend fun updateAmounts(userWallets: List): CompletionResult { + val fiatCurrency = appCurrencyProvider.invoke() + + return walletAmountsRepository.updateAmountsForUserWallets(userWallets, fiatCurrency) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + fiatCurrency = fiatCurrency, + ) + } + } + } + + private suspend fun fetchWalletsIfNeeded(userWallet: UserWallet): CompletionResult { + return if (userWallet.isMultiCurrency) { + fetchMultiWallets(userWallet) } else { - fetchSingleWallet(userWallet, refresh) + fetchSingleWallet(userWallet) } .map { userWallet } } - private suspend fun fetchMultiWallets( - userWallet: UserWallet, - refresh: Boolean, - ): CompletionResult { + private suspend fun fetchMultiWallets(userWallet: UserWallet): CompletionResult { val scanResponse = userWallet.scanResponse val userTokens = withContext(Dispatchers.IO) { userTokensRepository.getUserTokens(scanResponse.card) @@ -128,10 +136,9 @@ internal class DefaultWalletStoresManager( ) } - walletManagersRepository.findOrMake( + walletManagersRepository.findOrMakeMultiCurrencyWalletManager( userWallet = userWallet, blockchainNetwork = blockchainNetwork, - refresh = refresh, ) .flatMap { walletManager -> storeWalletStore(walletManager) @@ -149,13 +156,9 @@ internal class DefaultWalletStoresManager( } } - private suspend fun fetchSingleWallet( - userWallet: UserWallet, - refresh: Boolean, - ): CompletionResult { - return walletManagersRepository.findOrMake( + private suspend fun fetchSingleWallet(userWallet: UserWallet): CompletionResult { + return walletManagersRepository.findOrMakeSingleCurrencyWalletManager( userWallet = userWallet, - refresh = refresh, ) .flatMap { walletManager -> val userWalletId = userWallet.walletId diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt index 93c5c780c1..b1729a0b11 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt @@ -17,7 +17,7 @@ internal class DummyWalletStoresManager : WalletStoresManager { return emptyFlow() } - override suspend fun delete(userWalletsIds: List): CompletionResult { + override suspend fun delete(userWalletsIds: List): CompletionResult { return CompletionResult.Success(Unit) } @@ -32,4 +32,8 @@ internal class DummyWalletStoresManager : WalletStoresManager { override suspend fun fetch(userWallets: List, refresh: Boolean): CompletionResult { return CompletionResult.Success(Unit) } + + override suspend fun updateAmounts(userWallets: List): CompletionResult { + return CompletionResult.Success(Unit) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt deleted file mode 100644 index 3c94784ec8..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/utils/CompletitionsResultOperations.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.tap.domain.walletStores.implementation.utils - -import com.tangem.common.CompletionResult - -internal fun List>.fold(): CompletionResult { - return fold(Unit) { _, _ -> Unit } -} - -@Suppress("UNCHECKED_CAST") -internal inline fun List>.fold( - initial: R, - operation: (acc: R, data: D) -> R, -): CompletionResult { - var resultData = initial - for (result in this) { - when (result) { - is CompletionResult.Success -> { - resultData = operation(resultData, result.data) - } - is CompletionResult.Failure -> { - return result as CompletionResult.Failure - } - } - } - - return CompletionResult.Success(resultData) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt index f46a552dfe..0af5b8f85e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt @@ -6,19 +6,39 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel interface WalletAmountsRepository { - suspend fun update( + + /** + * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] + * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data + * @param userWallets list of [UserWallet] which will be used to get the list of associated [WalletStoreModel] + * @param fiatCurrency current app [FiatCurrency] + * */ + suspend fun updateAmountsForUserWallets( userWallets: List, fiatCurrency: FiatCurrency, ): CompletionResult - suspend fun update( + /** + * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] + * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data + * @param userWallet [UserWallet] which will be used to get the list of associated [WalletStoreModel] + * @param fiatCurrency current app [FiatCurrency] + * */ + suspend fun updateAmountsForUserWallet( userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult - suspend fun update( - userWallet: UserWallet, + /** + * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] + * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data + * @param walletStore [WalletStoreModel] to update + * @param userWallet [UserWallet] associated with provided [walletStore] + * @param fiatCurrency current app [FiatCurrency] + * */ + suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, + userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt index 9c8f9b1c13..0d8a2044be 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletManagersRepository.kt @@ -8,12 +8,13 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.tokens.models.BlockchainNetwork interface WalletManagersRepository { - suspend fun findOrMake( + suspend fun findOrMakeMultiCurrencyWalletManager( userWallet: UserWallet, - blockchainNetwork: BlockchainNetwork? = null, - refresh: Boolean = false, + blockchainNetwork: BlockchainNetwork, ): CompletionResult + suspend fun findOrMakeSingleCurrencyWalletManager(userWallet: UserWallet): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun delete( diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt index a5184fd40f..7b07a6eee0 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletStoresRepository.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow interface WalletStoresRepository { fun getAll(): Flow>> fun get(userWalletId: UserWalletId): Flow> + suspend fun getSync(userWalletId: UserWalletId): List suspend fun contains(userWalletId: UserWalletId): Boolean diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index fd8a037dba..640c08fe67 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -9,9 +9,10 @@ import com.tangem.blockchain.extensions.Result.Failure import com.tangem.blockchain.extensions.Result.Success import com.tangem.common.CompletionResult import com.tangem.common.catching -import com.tangem.common.doOnSuccess +import com.tangem.common.core.TangemError import com.tangem.common.flatMap import com.tangem.common.flatMapOnFailure +import com.tangem.common.fold import com.tangem.common.map import com.tangem.common.services.Result import com.tangem.datasource.api.tangemTech.TangemTechService @@ -22,9 +23,9 @@ import com.tangem.tap.common.extensions.replaceByOrAdd import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.walletStores.WalletStoresError -import com.tangem.tap.domain.walletStores.implementation.utils.fold import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore +import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStores import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithAmounts import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithError import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates @@ -40,7 +41,7 @@ import com.tangem.tap.network.NetworkConnectivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal @@ -51,30 +52,30 @@ internal class DefaultWalletAmountsRepository( private val walletStoresStorage = WalletStoresStorage private val walletManagersStorage = WalletManagerStorage - override suspend fun update( + override suspend fun updateAmountsForUserWallets( userWallets: List, fiatCurrency: FiatCurrency, ): CompletionResult { return if (userWallets.isEmpty()) CompletionResult.Success(Unit) else withContext(Dispatchers.Default) { awaitAll( - async { fetchAmounts(userWallets) }, + async { fetchAmountsForUserWallets(userWallets) }, async { fetchFiatRates(userWallets, fiatCurrency) }, ) .fold() } } - override suspend fun update( + override suspend fun updateAmountsForUserWallet( userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult { - return update(listOf(userWallet), fiatCurrency) + return updateAmountsForUserWallets(listOf(userWallet), fiatCurrency) } - override suspend fun update( - userWallet: UserWallet, + override suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, + userWallet: UserWallet, fiatCurrency: FiatCurrency, ): CompletionResult = withContext(Dispatchers.Default) { val walletId = userWallet.walletId @@ -84,33 +85,18 @@ internal class DefaultWalletAmountsRepository( async { // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] val walletManager = walletStore.walletManager - fetchAmounts(walletId, scanResponse, walletStore, walletManager) - .flatMap { fetchRentIfNeeded(walletStore, walletManager) } + fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) }, async { fetchFiatRates(listOf(userWallet), fiatCurrency) }, ) .fold() } - private suspend fun fetchAmounts( + private suspend fun fetchAmountsForUserWallets( userWallets: List, - ): CompletionResult = coroutineScope { - userWallets.map { userWallet -> - val walletId = userWallet.walletId - val scanResponse = userWallet.scanResponse - val walletStores = walletStoresStorage.getSync(walletId) - - walletStores.map { walletStore -> - async { - // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] - val walletManager = walletStore.walletManager - fetchAmounts(walletId, scanResponse, walletStore, walletManager) - .flatMap { fetchRentIfNeeded(walletStore, walletManager) } - } - } - .awaitAll() - .fold() - } + ): CompletionResult = withContext(Dispatchers.Default) { + userWallets.map { async { fetchAmountsForUserWallet(it) } } + .awaitAll() .fold() } @@ -120,7 +106,11 @@ internal class DefaultWalletAmountsRepository( ): CompletionResult { val walletsIds = userWallets.map { it.walletId } val walletStores = walletsIds - .flatMap { walletStoresStorage.getSync(it) } + .flatMap { + walletStoresStorage.getAll() + .first() + .getOrElse(it) { emptyList() } + } val currencies = walletStores .asSequence() @@ -138,21 +128,11 @@ internal class DefaultWalletAmountsRepository( return when (fiatRatesResult) { is Result.Success -> { - Timber.d( - """ - Fetched fiat rates - |- User wallets ids: $walletsIds - |- Coins ids: $coinsIds - """.trimIndent(), + updateWalletStoresWithFiatRates( + walletStores = walletStores, + fiatRates = fiatRatesResult.data.rates, ) - walletStores.forEach { walletStore -> - updateWithFiatRates( - walletStore = walletStore, - fiatRates = fiatRatesResult.data.rates, - ) - } - CompletionResult.Success(Unit) } is Result.Failure -> { @@ -175,102 +155,66 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun fetchAmounts( + private suspend fun fetchAmountsForUserWallet( + userWallet: UserWallet, + ): CompletionResult = withContext(Dispatchers.Default) { + val walletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + val walletStores = walletStoresStorage.getAll() + .first() + .getOrElse(walletId) { emptyList() } + + walletStores.map { walletStore -> + async { + // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] + val walletManager = walletStore.walletManager + fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) + } + } + .awaitAll() + .fold() + } + + private suspend fun fetchAmountsForWalletStore( walletId: UserWalletId, scanResponse: ScanResponse, walletStore: WalletStoreModel, walletManager: WalletManager?, ): CompletionResult { - val hasMissedDerivations = with(walletStore.blockchainNetwork) { - derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath) + val hasMissedDerivations = with(walletStore) { + derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath.rawPath) } - val blockchain = walletStore.blockchainNetwork.blockchain - val tokens = walletStore.blockchainNetwork.tokens.map { it.name } return when { - hasMissedDerivations -> { - Timber.e( - """ - Missed derivation - |- User wallet id: $walletId - |- Blockchain: $blockchain - """.trimIndent(), - ) - - updateWithMissedDerivation( - walletStore = walletStore, - ) - - CompletionResult.Success(Unit) - } - walletManager == null -> { - Timber.e( - """ - Wallet manager is null - |- User wallet id: $walletId - |- Blockchain: $blockchain - """.trimIndent(), - ) - - updateWithUnreachable( - walletStore = walletStore, - ) - - CompletionResult.Success(Unit) - } + hasMissedDerivations -> updateWalletStoreWithMissedDerivation(walletStore) + walletManager == null -> updateWalletStoreWithUnreachable(walletStore) else -> { withInternetConnection { walletManager.update() } .map { updateWalletManagerWithAmounts(walletId, walletManager) } - .doOnSuccess { - Timber.d( - """ - Fetched amounts - |- User wallet id: $walletId - |- Blockchain: $blockchain - |- Tokens: $tokens - """.trimIndent(), - ) - - updateWithAmounts( + .flatMap { + updateWalletStoreWithAmounts( walletStore = walletStore, - wallet = walletManager.wallet, + updatedWallet = walletManager.wallet, ) } + .flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) } .flatMapOnFailure { error -> - Timber.e( - error, - """ - Unable to fetch amounts - |- User wallet id: $walletId - |- Blockchain: $blockchain - |- Tokens: $tokens - """.trimIndent(), + updateWalletStoreWithError( + walletStore = walletStore, + wallet = walletManager.wallet, + error = error, ) - - if (error is BlockchainSdkError) { - updateWithError( - walletStore = walletStore, - wallet = walletManager.wallet, - error = error, - ) - CompletionResult.Success(Unit) - } else { - CompletionResult.Failure(error) - } } } } } - private suspend fun fetchRentIfNeeded( + private suspend fun fetchWalletStoreRentIfNeeded( walletStore: WalletStoreModel, - walletManager: WalletManager?, + walletManager: WalletManager, ): CompletionResult { val rentProvider = walletManager as? RentProvider - - if (walletManager == null || rentProvider == null) { - return CompletionResult.Success(Unit) - } + ?: return CompletionResult.Success(Unit) when (val result = rentProvider.minimalBalanceForRentExemption()) { is Success -> { @@ -288,7 +232,7 @@ internal class DefaultWalletAmountsRepository( balance < rest } - updateWithRent( + updateWalletStoreWithRent( walletStore = walletStore, rent = if (setRent) { WalletStoreModel.WalletRent( @@ -333,76 +277,123 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun updateWithError( + private suspend fun updateWalletStoreWithError( walletStore: WalletStoreModel, wallet: Wallet, - error: BlockchainSdkError, + error: TangemError, ) = withContext(Dispatchers.Default) { - walletStoresStorage.update { prevState -> - prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, - update = { - it.updateWithError( - wallet = wallet, - error = error, - ) - }, - ) + Timber.e( + error, + """ + Unable to fetch amounts + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + + if (error is BlockchainSdkError) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletStoreToUpdate = walletStore, + update = { + it.updateWithError( + wallet = wallet, + error = error, + ) + }, + ) + } + + CompletionResult.Success(Unit) + } else { + CompletionResult.Failure(error) } } - private suspend fun updateWithAmounts( + private suspend fun updateWalletStoreWithAmounts( walletStore: WalletStoreModel, - wallet: Wallet, + updatedWallet: Wallet, ) = withContext(Dispatchers.Default) { + Timber.d( + """ + Fetched amounts + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + walletStoreToUpdate = walletStore, update = { - it.updateWithAmounts(wallet = wallet) + it.updateWithAmounts(wallet = updatedWallet) }, ) } + + CompletionResult.Success(Unit) } - private suspend fun updateWithMissedDerivation( + private suspend fun updateWalletStoreWithMissedDerivation( walletStore: WalletStoreModel, ) = withContext(Dispatchers.Default) { + Timber.e( + """ + Missed derivation + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + walletStoreToUpdate = walletStore, update = { it.updateWithMissedDerivation() }, ) } + + CompletionResult.Success(Unit) } - private suspend fun updateWithUnreachable( + private suspend fun updateWalletStoreWithUnreachable( walletStore: WalletStoreModel, ) = withContext(Dispatchers.Default) { + Timber.e( + """ + Wallet manager is null + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + walletStoreToUpdate = walletStore, update = { it.updateWithUnreachable() }, ) } + + CompletionResult.Success(Unit) } - private suspend fun updateWithFiatRates( - walletStore: WalletStoreModel, + private suspend fun updateWalletStoresWithFiatRates( + walletStores: List, fiatRates: Map, ) = withContext(Dispatchers.Default) { + Timber.d( + """ + Fetched fiat rates + |- User wallets ids: ${walletStores.map { it.userWalletId }.distinct()} + """.trimIndent(), + ) + walletStoresStorage.update { prevState -> - prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, + prevState.replaceWalletStores( + walletStoresToUpdate = walletStores, update = { it.updateWithFiatRates(rates = fiatRates) }, @@ -410,18 +401,28 @@ internal class DefaultWalletAmountsRepository( } } - private suspend fun updateWithRent( + private suspend fun updateWalletStoreWithRent( walletStore: WalletStoreModel, rent: WalletStoreModel.WalletRent?, ) = withContext(Dispatchers.Default) { - walletStoresStorage.update { prevState -> - prevState.replaceWalletStore( - walletId = walletStore.userWalletId, - walletStore = walletStore, - update = { - it.updateWithRent(rent) - }, - ) + Timber.d( + """ + Fetched wallet rent + |- User wallet id: ${walletStore.userWalletId} + |- Blockchain: ${walletStore.blockchain} + |- Rent: $rent + """.trimIndent(), + ) + + if (rent != walletStore.walletRent) { + walletStoresStorage.update { prevState -> + prevState.replaceWalletStore( + walletStoreToUpdate = walletStore, + update = { + it.updateWithRent(rent) + }, + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index 600120ad42..3de7a3f6b1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -1,6 +1,10 @@ package com.tangem.tap.domain.walletStores.repository.implementation -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.DerivationParams +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.common.hdWallet.DerivationPath @@ -18,6 +22,7 @@ import com.tangem.tap.domain.walletStores.WalletStoresError import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import timber.log.Timber @@ -26,26 +31,31 @@ internal class DefaultWalletManagersRepository( ) : WalletManagersRepository { private val walletManagersStorage = WalletManagerStorage - override suspend fun findOrMake( + override suspend fun findOrMakeMultiCurrencyWalletManager( + userWallet: UserWallet, + blockchainNetwork: BlockchainNetwork, + ): CompletionResult { + return findOrMakeInternal(userWallet, blockchainNetwork) + } + + override suspend fun findOrMakeSingleCurrencyWalletManager(userWallet: UserWallet): CompletionResult { + return findOrMakeInternal(userWallet, blockchainNetwork = null) + } + + private suspend fun findOrMakeInternal( userWallet: UserWallet, blockchainNetwork: BlockchainNetwork?, - refresh: Boolean, ): CompletionResult = withContext(Dispatchers.Default) { - if (refresh) { - deleteInternal(userWallet.walletId, blockchainNetwork?.blockchain) - makeAndStore(userWallet, blockchainNetwork) - } else { - val foundWalletManager = findWalletManager( - userWalletId = userWallet.walletId, - blockchain = blockchainNetwork?.blockchain, - ) + val foundWalletManager = findWalletManager( + userWalletId = userWallet.walletId, + blockchain = blockchainNetwork?.blockchain, + ) - foundWalletManager?.updateTokens( - scanResponse = userWallet.scanResponse, - blockchainNetwork = blockchainNetwork, - ) - ?: makeAndStore(userWallet, blockchainNetwork) - } + foundWalletManager?.updateTokens( + scanResponse = userWallet.scanResponse, + blockchainNetwork = blockchainNetwork, + ) + ?: makeAndStore(userWallet, blockchainNetwork) } private suspend fun makeAndStore( @@ -150,8 +160,11 @@ internal class DefaultWalletManagersRepository( return catching { val tokens = blockchainNetwork?.tokens ?: listOfNotNull(scanResponse.getPrimaryToken()) - if (tokens.isNotEmpty()) { - walletManager.addTokens(tokens) + if (tokens != cardTokens) { + cardTokens.clear() + if (tokens.isNotEmpty()) { + walletManager.cardTokens.addAll(tokens) + } } walletManager @@ -170,7 +183,7 @@ internal class DefaultWalletManagersRepository( userWalletId: UserWalletId, blockchain: Blockchain?, ): WalletManager? { - return walletManagersStorage.getAllSync()[userWalletId]?.let { userWalletManagers -> + return walletManagersStorage.getAll().first()[userWalletId]?.let { userWalletManagers -> if (blockchain == null) userWalletManagers.firstOrNull() else userWalletManagers.find { it.wallet.blockchain == blockchain } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt index f12cd945cf..b9087070e4 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletStoresRepository.kt @@ -12,6 +12,8 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext internal class DefaultWalletStoresRepository : WalletStoresRepository { @@ -22,11 +24,15 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository { } override fun get(userWalletId: UserWalletId): Flow> { - return walletStoresStorage.get(userWalletId) + return getAll().map { it[userWalletId].orEmpty() } + } + + override suspend fun getSync(userWalletId: UserWalletId): List { + return get(userWalletId).firstOrNull() ?: emptyList() } override suspend fun contains(userWalletId: UserWalletId): Boolean { - return walletStoresStorage.getSync(userWalletId).isNotEmpty() + return getSync(userWalletId).isNotEmpty() } override suspend fun delete(userWalletsIds: List): CompletionResult = catching { @@ -39,11 +45,13 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository { userWalletId: UserWalletId, currentBlockchains: List, ): CompletionResult = catching { - walletStoresStorage.update { prevStores -> - prevStores.apply { - this[userWalletId] = this[userWalletId] - ?.filter { it.blockchainNetwork.blockchain in currentBlockchains } - .orEmpty() + if (currentBlockchains != getSync(userWalletId).map { it.blockchain }) { + walletStoresStorage.update { prevStores -> + prevStores.apply { + this[userWalletId] = this[userWalletId] + ?.filter { it.blockchain in currentBlockchains } + .orEmpty() + } } } } @@ -65,24 +73,22 @@ internal class DefaultWalletStoresRepository : WalletStoresRepository { userWalletId: UserWalletId, walletStore: WalletStoreModel, ): HashMap> = withContext(Dispatchers.Default) { - val prevStores = this@addOrUpdate - val walletStores = prevStores[userWalletId] + val currentWalletStores = this@addOrUpdate + val userWalletStores = currentWalletStores[userWalletId] - if (walletStores.isNullOrEmpty()) { - prevStores.apply { + if (userWalletStores.isNullOrEmpty()) { + currentWalletStores.apply { set(userWalletId, listOf(walletStore)) } } else { - val oldWalletStore = walletStores.find(walletStore::isSameWalletStore) - - if (oldWalletStore == null) { - prevStores.apply { - set(userWalletId, walletStores + walletStore) + val currentWalletStore = userWalletStores.find(walletStore::isSameWalletStore) + if (currentWalletStore == null) { + currentWalletStores.apply { + set(userWalletId, userWalletStores + walletStore) } } else { - prevStores.replaceWalletStore( - walletId = userWalletId, - walletStore = oldWalletStore, + currentWalletStores.replaceWalletStore( + walletStoreToUpdate = currentWalletStore, update = { it.updateWithSelf(walletStore) }, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt index 39d4adfadc..7ad0e38363 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt @@ -125,10 +125,9 @@ internal fun WalletDataModel.updateWithSelf( status = when (val newStatus = newWalletData.status) { is WalletDataModel.Loading -> when (oldStatus) { is WalletDataModel.MissedDerivation -> WalletDataModel.Loading - else -> oldStatus.asRefreshing() + else -> oldStatus } is WalletDataModel.MissedDerivation, - is WalletDataModel.Refreshing, is WalletDataModel.NoAccount, is WalletDataModel.Unreachable, is WalletDataModel.SameCurrencyTransactionInProgress, diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 8d29c62825..445ff655f2 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -4,16 +4,28 @@ import com.tangem.blockchain.common.Wallet import com.tangem.common.core.TangemError import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel +import timber.log.Timber internal inline fun HashMap>.replaceWalletStore( - walletId: UserWalletId, - walletStore: WalletStoreModel, + walletStoreToUpdate: WalletStoreModel, + update: (walletStore: WalletStoreModel) -> WalletStoreModel, +): HashMap> { + return replaceWalletStores(listOf(walletStoreToUpdate), update) +} + +internal inline fun HashMap>.replaceWalletStores( + walletStoresToUpdate: List, update: (walletStore: WalletStoreModel) -> WalletStoreModel, ): HashMap> { return this.apply { - this[walletId] = this[walletId] - ?.replaceWalletStore(walletStore, update) - .orEmpty() + val currentWalletStores = this + walletStoresToUpdate + .groupBy { it.userWalletId } + .forEach { (userWalletId, walletStoresToUpdate) -> + currentWalletStores[userWalletId] = currentWalletStores[userWalletId] + ?.replaceWalletStores(walletStoresToUpdate, update) + .orEmpty() + } } } @@ -74,20 +86,39 @@ internal fun WalletStoreModel.updateWithRent(rent: WalletStoreModel.WalletRent?) ) } -internal inline fun List.replaceWalletStore( - newWalletStore: WalletStoreModel, - update: (walletStore: WalletStoreModel) -> WalletStoreModel, -): List { - val mutableStores = ArrayList(this) - for ((index, walletStore) in this.withIndex()) { - if (walletStore.isSameWalletStore(newWalletStore)) { - mutableStores[index] = update(walletStore) - break - } - } - return mutableStores +internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean { + return this.blockchain == other.blockchain && + this.derivationPath == other.derivationPath } -internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean { - return blockchainNetwork == other.blockchainNetwork +private inline fun List.replaceWalletStores( + walletStoresToUpdate: List, + update: (walletStore: WalletStoreModel) -> WalletStoreModel, +): List { + val mutableStores = ArrayList(this) + + walletStoresToUpdate + .asSequence() + .map { it.blockchain to it.derivationPath } + .forEach { (blockchain, derivationPath) -> + val index = mutableStores.indexOfFirst { + it.blockchain == blockchain && it.derivationPath == derivationPath + } + val currentWalletStore = mutableStores[index] + val updatedWalletStore = update(currentWalletStore) + + if (currentWalletStore != updatedWalletStore) { + Timber.d( + """ + Update wallet store in storage + |- User wallet ID: ${updatedWalletStore.userWalletId} + |- Blockchain: ${updatedWalletStore.blockchain} + """.trimIndent(), + ) + + mutableStores[index] = updatedWalletStore + } + } + + return mutableStores } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt index 1dae6db10e..3646e38f6a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletManagerStorage.kt @@ -4,6 +4,8 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.domain.common.util.UserWalletId import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -16,8 +18,8 @@ internal object WalletManagerStorage { managers.tryEmit(hashMapOf()) } - suspend fun getAllSync(): Map> { - return managers.first() + fun getAll(): SharedFlow>> { + return managers.asSharedFlow() } private val mutex = Mutex() diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt index d4b6061dfc..93cdfb508f 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/storage/WalletStoresStorage.kt @@ -2,12 +2,11 @@ package com.tangem.tap.domain.walletStores.storage import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -19,20 +18,8 @@ internal object WalletStoresStorage { stores.tryEmit(hashMapOf()) } - fun getAll(): Flow>> { - return stores - } - - @OptIn(ExperimentalCoroutinesApi::class) - fun get(userWalletId: UserWalletId): Flow> { - return stores - .mapLatest { stores -> - stores[userWalletId].orEmpty() - } - } - - suspend fun getSync(userWalletId: UserWalletId): List { - return stores.first().getOrElse(userWalletId) { emptyList() } + fun getAll(): SharedFlow>> { + return stores.asSharedFlow() } private val mutex = Mutex() diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 829103e4e7..319071642c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.toHexString import com.tangem.common.flatMap import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.util.userWalletId @@ -61,7 +62,7 @@ class DetailsMiddleware { is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action) is DetailsAction.AppSettings -> managePrivacyMiddleware.handle(state, action) is DetailsAction.ShowDisclaimer -> { - val uri = store.state.detailsState.cardTermsOfUseUrl + val uri = state.cardTermsOfUseUrl if (uri != null) { store.dispatch(NavigationAction.OpenDocument(uri)) } @@ -71,19 +72,26 @@ class DetailsMiddleware { store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) } is DetailsAction.CreateBackup -> { - store.state.detailsState.scanResponse?.let { + state.scanResponse?.let { store.dispatch(GlobalAction.Onboarding.Start(it, canSkipBackup = false)) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } } DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanCard(cardId = state.scanResponse?.card?.cardId) + tangemSdkManager.scanCard(allowRequestAccessCodeFromRepository = true) .doOnSuccess { card -> - val currentCardId = store.state.globalState.scanResponse?.card - ?.userWalletId - ?.stringValue - if (card.userWalletId.stringValue == currentCardId) { + val isSameWallet = state.scanResponse?.card?.userWalletId + ?.equals(card.userWalletId) + ?: false + + // !!! Workaround !!! + // TODO: Remove after [REDACTED_JIRA] + val isTwinned = card.wallets.firstOrNull()?.publicKey?.toHexString() + ?.equals(state.scanResponse?.secondTwinPublicKey) + ?: false + + if (isSameWallet || isTwinned) { store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) } else { store.dispatchDialogShow( @@ -116,6 +124,7 @@ class DetailsMiddleware { scope.launch { tangemSdkManager.resetToFactorySettings(card.cardId) .flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) } + .flatMap { tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) } .doOnSuccess { Analytics.send(Settings.CardSettings.FactoryResetFinished()) @@ -255,10 +264,10 @@ class DetailsMiddleware { private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch { if (state.saveAccessCodes == enable) return@launch if (enable) { + saveAccessCodes(state) if (!state.saveWallets) { saveCurrentWallet(state) } - saveAccessCodes(state) } else { deleteSavedAccessCodes() } @@ -334,12 +343,15 @@ class DetailsMiddleware { useBiometricsForAccessCode = false, ) - store.dispatchOnMain( - DetailsAction.AppSettings.SwitchPrivacySetting.Success( - setting = PrivacySetting.SaveAccessCode, - enable = false, - ), - ) + tangemSdkManager.clearSavedUserCodes() + .doOnSuccess { + store.dispatchOnMain( + DetailsAction.AppSettings.SwitchPrivacySetting.Success( + setting = PrivacySetting.SaveAccessCode, + enable = false, + ), + ) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt index 31941ed1ee..5a4d1bdc53 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt @@ -15,8 +15,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.layout.ContentScale @@ -38,10 +36,6 @@ fun CardSettingsScreen( modifier: Modifier = Modifier, ) { val needReadCard = state.cardDetails == null - val backgroundColor by rememberUpdatedState( - newValue = if (needReadCard) TangemTheme.colors.background.primary - else TangemTheme.colors.background.secondary, - ) SettingsScreensScaffold( content = { @@ -52,7 +46,7 @@ fun CardSettingsScreen( } }, titleRes = R.string.card_settings_title, - backgroundColor = backgroundColor, + backgroundColor = TangemTheme.colors.background.secondary, onBackClick = onBackPressed, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt index 47946d1e73..1346fa4f24 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt @@ -25,6 +25,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.details.ui.common.ScreenTitle import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold @@ -36,6 +38,10 @@ fun DetailsScreen( onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { + SystemBarsEffect { + setSystemBarsColor(color = TangemColorPalette.Light1) + } + SettingsScreensScaffold( content = { Content(state = state, modifier = modifier) }, onBackClick = onBackPressed, diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt index ca82ff1fee..5461c66441 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerAction.kt @@ -9,9 +9,19 @@ sealed class DisclaimerAction : Action { val type: DisclaimerType, ) : DisclaimerAction() - data class Show(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction() + data class Show( + val onAcceptCallback: VoidCallback? = null, + val onDismissCallback: VoidCallback? = null, + ) : DisclaimerAction() + data class AcceptDisclaimer(val type: DisclaimerType) : DisclaimerAction() internal data class UpdateState(val type: DisclaimerType, val accepted: Boolean) : DisclaimerAction() - internal data class SetOnAcceptCallback(val onAcceptCallback: VoidCallback? = null) : DisclaimerAction() + + internal data class SetCallbacks( + val onAcceptCallback: VoidCallback? = null, + val onDismissCallback: VoidCallback? = null, + ) : DisclaimerAction() + + object OnBackPressed : DisclaimerAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt index 1b42d7b4fc..01e5de971a 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerMiddleware.kt @@ -28,7 +28,7 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { } is DisclaimerAction.Show -> { handleUpdateState = state.type.createUpdateState() - store.dispatch(DisclaimerAction.SetOnAcceptCallback(action.onAcceptCallback)) + store.dispatch(DisclaimerAction.SetCallbacks(action.onAcceptCallback, action.onDismissCallback)) store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) } is DisclaimerAction.AcceptDisclaimer -> { @@ -36,7 +36,13 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) { handleUpdateState = action.type.createUpdateState() store.dispatch(NavigationAction.PopBackTo()) state.onAcceptCallback?.invoke() - store.dispatch(DisclaimerAction.SetOnAcceptCallback(null)) + store.dispatch(DisclaimerAction.SetCallbacks(null, null)) + } + is DisclaimerAction.OnBackPressed -> { + state.onDismissCallback?.invoke() + store.dispatch(DisclaimerAction.SetCallbacks(null, null)) + store.dispatch(NavigationAction.PopBackTo()) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt index 1b282877f2..766c6a42ca 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt @@ -19,8 +19,9 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState { type = action.type, accepted = action.accepted, ) - is DisclaimerAction.SetOnAcceptCallback -> disclaimerState.copy( + is DisclaimerAction.SetCallbacks -> disclaimerState.copy( onAcceptCallback = action.onAcceptCallback, + onDismissCallback = action.onDismissCallback, ) else -> disclaimerState } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt index ee0700f5b5..d7ca859973 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerState.kt @@ -11,6 +11,7 @@ data class DisclaimerState( val accepted: Boolean = false, val type: DisclaimerType = DisclaimerType.Tangem, val onAcceptCallback: VoidCallback? = null, + val onDismissCallback: VoidCallback? = null, ) : StateType sealed class DisclaimerType( diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index 633ef10d6a..7761de53b6 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.View import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding +import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.tap.common.extensions.configureSettings import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.redux.AppState @@ -29,6 +30,7 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs override fun onStart() { super.onStart() + setStatusBarColor(R.color.backgroundLightGray) store.subscribe(subscriber = this) { state -> state .skipRepeats { oldState, newState -> oldState.disclaimerState == newState.disclaimerState } @@ -62,4 +64,8 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs webView.loadUrl(state.type.uri.toString()) } + + override fun handleOnBackPressed() { + store.dispatch(DisclaimerAction.OnBackPressed) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index c879e6ca04..0c5b990b06 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -79,13 +79,17 @@ private fun readCard() = scope.launch { onProgressStateChange = { showProgress -> if (showProgress) { changeButtonState(ButtonState.PROGRESS) - } else { - changeButtonState(ButtonState.ENABLED) } + // else { //todo hide this because + // changeButtonState(ButtonState.ENABLED) + // } }, onScanStateChange = { scanInProgress -> store.dispatch(HomeAction.ScanInProgress(scanInProgress)) }, + onFailure = { + changeButtonState(ButtonState.ENABLED) + }, onSuccess = { scanResponse -> scope.launch { if (preferencesStorage.shouldSaveUserWallets) { @@ -93,21 +97,18 @@ private fun readCard() = scope.launch { userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) store.onCardScanned(scanResponse) } .doOnSuccess { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.hasAccessCode, - ) - store.onUserWalletSelected(userWallet) + scope.launch { store.onUserWalletSelected(userWallet) } } .doOnResult { + changeButtonState(ButtonState.ENABLED) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } } else { store.onCardScanned(scanResponse) + changeButtonState(ButtonState.ENABLED) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index b1ce166222..9b5c5e9958 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -1,20 +1,16 @@ package com.tangem.tap.features.onboarding -import com.tangem.common.doOnSuccess import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onCardScanned -import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -59,17 +55,6 @@ class OnboardingHelper { backupCardsIds: List? = null, ) { when { - // When should save user wallets but manager is locked, then unlock manager with card - preferencesStorage.shouldSaveUserWallets && - userWalletsListManager.isLockedSync -> scope.launch { - val userWallet = UserWalletBuilder(scanResponse).build() - - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) - userWalletsListManager.unlockWithCard(userWallet) - .doOnSuccess { - store.onUserWalletSelected(userWallet) - } - } // When should save user wallets, then save card without navigate to save wallet screen preferencesStorage.shouldSaveUserWallets -> scope.launch { store.dispatchOnMain( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 8853e2766e..ae60d9f637 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -6,6 +6,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.common.util.userWalletId import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -31,6 +32,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -69,7 +71,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { fun updateScanResponse(response: ScanResponse) { when (twinCardsState.mode) { CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response - CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanNoteResponse(response)) + CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanResponse(response)) } } @@ -151,10 +153,15 @@ private fun handle(action: Action, dispatch: DispatchFunction) { finishCardActivation() postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) } } - + TwinCardsStep.CreateFirstWallet -> { + scope.launch { + userWalletsListManager.delete( + listOf(getScanResponse().card.userWalletId), + ) + } + } TwinCardsStep.None, TwinCardsStep.Warning, - TwinCardsStep.CreateFirstWallet, TwinCardsStep.CreateSecondWallet, TwinCardsStep.CreateThirdWallet, -> Unit @@ -294,13 +301,14 @@ private fun handle(action: Action, dispatch: DispatchFunction) { when (twinCardsState.mode) { CreateTwinWalletMode.CreateWallet -> { store.dispatchOnMain(GlobalAction.Onboarding.Stop) - OnboardingHelper.trySaveWalletAndNavigateToWalletScreen( - scanResponse = scanResponse, - backupCardsIds = listOfNotNull(twinCardsState.twinCardsManager?.secondCardPublicKey), - ) + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) } CreateTwinWalletMode.RecreateWallet -> { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + if (preferencesStorage.shouldSaveUserWallets) { + OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) + } else { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index 9511376155..36e386325e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -13,6 +13,7 @@ import coil.load import com.tangem.Message import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback +import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TwinCardNumber import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget @@ -97,6 +98,11 @@ class TwinsCardsFragment : BaseOnboardingFragment() { } } + override fun onStart() { + super.onStart() + setStatusBarColor(R.color.backgroundWhite) + } + private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) = with(containerBinding) { imvFrontCard.hide() diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index d11d5139fb..106ebfa9af 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -94,7 +94,6 @@ internal class SaveWalletMiddleware { saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) .flatMap { userWalletsListManager.save(userWallet, canOverride = true) } - .flatMap { userWalletsListManager.selectWallet(userWallet.walletId) } .doOnFailure { error -> store.dispatchOnMain(SaveWalletAction.Save.Error(error)) } @@ -105,12 +104,6 @@ internal class SaveWalletMiddleware { preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet || preferencesStorage.shouldSaveAccessCodes - - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.hasAccessCode, - ) - store.dispatchOnMain(SaveWalletAction.Save.Success) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 22d6c9138d..5bf0618fbb 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -5,18 +5,26 @@ import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.TransactionError +import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.* +import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.dispatchDialogShow +import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.safeUpdate +import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -28,13 +36,25 @@ import com.tangem.tap.domain.extensions.minimalAmount import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard -import com.tangem.tap.features.send.redux.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi +import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction +import com.tangem.tap.features.send.redux.AmountAction +import com.tangem.tap.features.send.redux.AmountActionUi import com.tangem.tap.features.send.redux.FeeAction.RequestFee +import com.tangem.tap.features.send.redux.PrepareSendScreen +import com.tangem.tap.features.send.redux.SendAction +import com.tangem.tap.features.send.redux.SendActionUi import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.send.redux.states.ExternalTransactionData import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.redux.states.TransactionExtrasState +import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdk +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -308,12 +328,15 @@ private fun updateWarnings(dispatch: (Action) -> Unit) { } private suspend fun updateWallet(walletManager: WalletManager) { - val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager) val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { + val wallet = walletManager.wallet walletCurrenciesManager.update( userWallet = selectedUserWallet, - blockchainNetwork = blockchainNetwork, + currency = Currency.Blockchain( + blockchain = wallet.blockchain, + derivationPath = wallet.publicKey.derivationPath?.rawPath, + ), ) } else { store.dispatchOnMain(WalletAction.LoadWallet(BlockchainNetwork.fromWalletManager(walletManager))) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 8465e9d228..b07a4fd3ca 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -221,7 +221,7 @@ class TokensMiddleware { val updatedScanResponse = scanResponse.copy( derivedKeys = updatedDerivedKeys, ) - store.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) + store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) delay(DELAY_SDK_DIALOG_CLOSE) onSuccess(updatedScanResponse) @@ -383,7 +383,7 @@ class TokensMiddleware { } } - val addedCurrencies = store.state.walletState.wallets.map { walletStore -> + val addedCurrencies = store.state.walletState.walletsStores.map { walletStore -> walletStore.walletsData.map { walletData -> walletData.currency } }.flatten().map { when (it) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index ea9a272f74..2fa1ab2971 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -57,7 +57,7 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc super.onViewCreated(view, savedInstanceState) (activity as? AppCompatActivity)?.setSupportActionBar(toolbar) - toolbar.setNavigationOnClickListener { activity?.onBackPressed() } + toolbar.setNavigationOnClickListener { handleOnBackPressed() } val onSaveChanges = { tokens: List, blockchains: List -> Analytics.send(ManageTokens.ButtonSaveChanges()) @@ -144,7 +144,6 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc } override fun handleOnBackPressed() { - super.handleOnBackPressed() store.dispatch(NavigationAction.PopBackTo()) store.dispatch(TokensAction.ResetState) } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 6d751e8956..149fd2b517 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.tap.common.analytics.Analytics @@ -81,6 +82,11 @@ fun CurrenciesScreen( } } + val statusBarColor = colorResource(id = R.color.backgroundLightGray) + SystemBarsEffect { + setSystemBarsColor(color = statusBarColor) + } + Scaffold( floatingActionButton = { if (tokensState.value.allowToAdd) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index be432defdc..0cc8039e96 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -12,11 +12,11 @@ import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.warningMessage.WarningMessage -import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.wallet.R import org.rekotlin.Action @@ -36,7 +36,7 @@ sealed class WalletAction : Action { data class LoadWallet( val blockchain: BlockchainNetwork? = null, - val walletManager: WalletManager? = null + val walletManager: WalletManager? = null, ) : WalletAction() { data class Success(val wallet: Wallet, val blockchain: BlockchainNetwork) : WalletAction() data class NoAccount( @@ -90,7 +90,8 @@ sealed class WalletAction : Action { val blockchain: BlockchainNetwork, ) : MultiWallet() - data class SelectWallet(val walletData: WalletData?) : MultiWallet() + data class SelectWallet(val currency: Currency?) : MultiWallet() + data class SetSingleWalletCurrency(val currency: Currency?) : MultiWallet() data class TryToRemoveWallet(val currency: Currency) : MultiWallet() data class RemoveWallet(val currency: Currency) : MultiWallet() @@ -128,7 +129,7 @@ sealed class WalletAction : Action { val wallet: Wallet? = null, val coinsList: List? = null, ) : WalletAction() { data class Success( - val fiatRates: Map + val fiatRates: Map, ) : WalletAction() object Failure : WalletAction() @@ -163,7 +164,7 @@ sealed class WalletAction : Action { object ChooseTradeActionDialog : DialogAction() data class ChooseCurrency(val amounts: List?) : DialogAction() data class RussianCardholdersWarningDialog( - val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null + val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null, ) : DialogAction() object Hide : DialogAction() @@ -181,12 +182,13 @@ sealed class WalletAction : Action { data class Buy( val checkUserLocation: Boolean = true, ) : TradeCryptoAction() + data class FinishSelling(val transactionId: String) : TradeCryptoAction() data class SendCrypto( val currencyId: String, val amount: String, val destinationAddress: String, - val transactionId: String + val transactionId: String, ) : TradeCryptoAction() } @@ -206,6 +208,9 @@ sealed class WalletAction : Action { } data class UserWalletChanged(val userWallet: UserWallet) : WalletAction() - data class WalletStoresChanged(val walletStores: List) : WalletAction() - data class TotalFiatBalanceChanged(val balance: TotalFiatBalance) : WalletAction() + data class WalletStoresChanged(val walletStores: List) : WalletAction() { + data class UpdateWalletStores(val reduxWalletStores: List) : WalletAction() + } + + data class TotalFiatBalanceChanged(val balance: TotalBalance) : WalletAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index b8413753cb..49773a9854 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -36,7 +36,7 @@ data class WalletState( val cardImage: Artwork? = null, val hashesCountVerified: Boolean? = null, val mainWarningsList: List = mutableListOf(), - val wallets: List = listOf(), + val walletsStores: List = listOf(), val isMultiwalletAllowed: Boolean = false, val cardCurrency: CryptoCurrencyName? = null, val selectedCurrency: Currency? = null, @@ -50,6 +50,12 @@ data class WalletState( val walletCardsCount: Int? = null, ) : StateType { + val walletsDataFromStores: List + get() = walletsStores.map { it.walletsData }.flatten() + + val selectedWalletData: WalletData? + get() = walletsDataFromStores.firstOrNull { it.currency == selectedCurrency } + // if you do not delegate - the application crashes on startup, // because twinCardsState has not been created yet val twinCardsState: TwinCardsState by ReadOnlyProperty { _, _ -> @@ -63,20 +69,17 @@ data class WalletState( get() = store.state.globalState.exchangeManager.featureIsSwitchedOn() val blockchains: List - get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain } + get() = walletsStores.mapNotNull { it.walletManager?.wallet?.blockchain } val currencies: List - get() = wallets.flatMap { it.walletsData }.map { it.currency } - - val walletsData: List - get() = wallets.flatMap { it.walletsData } + get() = walletsStores.flatMap { it.walletsData }.map { it.currency } val walletManagers: List - get() = wallets.mapNotNull { it.walletManager } + get() = walletsStores.mapNotNull { it.walletManager } - val primaryWallet: WalletData? = wallets.firstOrNull()?.walletsData?.firstOrNull() + val primaryWallet: WalletData? = walletsStores.firstOrNull()?.walletsData?.firstOrNull() - val primaryWalletManager: WalletManager? = if (wallets.isNotEmpty()) wallets[0].walletManager else null + val primaryWalletManager: WalletManager? = if (walletsStores.isNotEmpty()) walletsStores[0].walletManager else null val shouldShowDetails: Boolean = primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard && @@ -91,12 +94,12 @@ data class WalletState( } fun getWalletManager(blockchain: BlockchainNetwork): WalletManager? { - return wallets.find { it.blockchainNetwork == blockchain }?.walletManager + return walletsStores.find { it.blockchainNetwork == blockchain }?.walletManager } fun getWalletData(blockchain: BlockchainNetwork?): WalletData? { if (blockchain == null) return null - return walletsData.find { + return walletsDataFromStores.find { it.currency is Currency.Blockchain && it.currency.blockchain == blockchain.blockchain && it.currency.derivationPath == blockchain.derivationPath @@ -105,7 +108,7 @@ data class WalletState( fun getWalletStore(currency: Currency?): WalletStore? { if (currency == null) return null - return wallets.firstOrNull { + return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == currency.derivationPath && (it.blockchainNetwork.blockchain == currency.blockchain) } @@ -120,7 +123,7 @@ data class WalletState( fun getWalletStore(blockchainNetwork: BlockchainNetwork?): WalletStore? { if (blockchainNetwork == null) return null - return wallets.firstOrNull { + return walletsStores.firstOrNull { it.blockchainNetwork.derivationPath == blockchainNetwork.derivationPath && (it.blockchainNetwork.blockchain == blockchainNetwork.blockchain) } @@ -131,10 +134,6 @@ data class WalletState( return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency } } - fun getSelectedWalletData(): WalletData? { - return walletsData.find { it.currency == selectedCurrency } - } - private fun isPrimaryCurrency(walletData: WalletData): Boolean { return (walletData.currency is Currency.Blockchain && walletData.currency.blockchain == store.state.walletState.primaryBlockchain) @@ -142,10 +141,10 @@ data class WalletState( walletData.currency.token == store.state.walletState.primaryToken) } - fun replaceWalletInWallets(wallet: WalletStore?): List { - if (wallet == null) return wallets + fun replaceWalletStoreInWalletsStores(wallet: WalletStore?): List { + if (wallet == null) return walletsStores var changed = false - val updatedWallets = wallets.map { + val updatedWallets = walletsStores.map { if (it.blockchainNetwork == wallet.blockchainNetwork) { changed = true wallet @@ -153,7 +152,7 @@ data class WalletState( it } } - return if (changed) updatedWallets else wallets + wallet + return if (changed) updatedWallets else walletsStores + wallet } fun updateWalletData(walletData: WalletData?): WalletState { @@ -161,26 +160,23 @@ data class WalletState( return updateWalletsData(listOf(walletData)) } - fun updateWalletsData( - walletsData: List - ): WalletState { - + fun updateWalletsData(walletsData: List): WalletState { val walletStores = walletsData .map { BlockchainNetwork(it.currency.blockchain, it.currency.derivationPath, emptyList()) } .distinct().map { getWalletStore(it) }.mapNotNull { it?.updateWallets(walletsData) } - return updateWalletStores(walletStores) + return updateWalletsStores(walletStores) } fun updateWalletStore(walletStore: WalletStore?): WalletState { - return copy(wallets = replaceWalletInWallets(walletStore)) + return copy(walletsStores = replaceWalletStoreInWalletsStores(walletStore)) .updateTotalBalance() .updateProgressState() } - private fun updateWalletStores(walletStores: List): WalletState { + private fun updateWalletsStores(walletStores: List): WalletState { val walletStoresMutable = walletStores.toMutableList() - val updatedWallets = wallets.map { oldWalletStore -> + val updatedWallets = walletsStores.map { oldWalletStore -> val walletStore = walletStoresMutable.find { it.blockchainNetwork == oldWalletStore.blockchainNetwork } @@ -191,20 +187,20 @@ data class WalletState( oldWalletStore } } - return copy(wallets = updatedWallets + walletStoresMutable) + return copy(walletsStores = updatedWallets + walletStoresMutable) .updateTotalBalance() .updateProgressState() } - fun removeWallet(walletData: WalletData?): WalletState { + fun removeWalletData(walletData: WalletData?): WalletState { if (walletData == null) return this return when (val currency = walletData.currency) { is Currency.Blockchain -> { - val walletStores = wallets.filterNot { + val walletStores = walletsStores.filterNot { it.blockchainNetwork.blockchain == currency.blockchain && it.blockchainNetwork.derivationPath == currency.derivationPath } - copy(wallets = walletStores) + copy(walletsStores = walletStores) .updateTotalBalance() .updateProgressState() } @@ -223,23 +219,8 @@ data class WalletState( } } - fun replaceSomeWallets(newWallets: List): List { - val remainingWallets: MutableList = newWallets.toMutableList() - val updatedWallets = walletsData.map { wallet -> - val newWallet = newWallets - .firstOrNull { wallet.currency == it.currency } - if (newWallet == null) { - wallet - } else { - remainingWallets.remove(newWallet) - newWallet - } - } - return updatedWallets + remainingWallets - } - private fun updateTotalBalance(): WalletState { - val walletsData = this.wallets + val walletsData = this.walletsStores .flatMap(WalletStore::walletsData) return if (walletsData.isNotEmpty()) { @@ -256,7 +237,7 @@ data class WalletState( } private fun updateProgressState(): WalletState { - val walletsData = this.wallets + val walletsData = this.walletsStores .flatMap(WalletStore::walletsData) return if (walletsData.isNotEmpty()) { @@ -264,7 +245,7 @@ data class WalletState( this.copy( state = walletsData.findProgressState(), - error = this.error.takeIf { newProgressState == ProgressState.Error } + error = this.error.takeIf { newProgressState == ProgressState.Error }, ) } else this } @@ -276,6 +257,21 @@ data class WalletState( } } +fun List.replaceSomeWalletsData(newWallets: List): List { + val remainingWallets: MutableList = newWallets.toMutableList() + val updatedWallets = this.map { wallet -> + val newWallet = newWallets + .firstOrNull { wallet.currency == it.currency } + if (newWallet == null) { + wallet + } else { + remainingWallets.remove(newWallet) + newWallet + } + } + return updatedWallets + remainingWallets +} + enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error } enum class ErrorType { NoInternetConnection } @@ -287,7 +283,7 @@ sealed class WalletMainButton(enabled: Boolean) : Button(enabled) { data class WalletAddresses( val selectedAddress: AddressData, - val list: List + val list: List, ) data class AddressData( @@ -301,7 +297,7 @@ data class AddressData( data class Artwork( val artworkId: String, - val artwork: Bitmap? = null + val artwork: Bitmap? = null, ) { companion object { const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png" @@ -374,7 +370,7 @@ data class WalletData( } } - private fun assembleTokenWarnings(walletWarnings: MutableList){ + private fun assembleTokenWarnings(walletWarnings: MutableList) { if (!currency.isToken()) return val blockchainFullName = currency.blockchain.fullName @@ -391,7 +387,7 @@ data class WalletData( data class WalletStore( val walletManager: WalletManager?, val blockchainNetwork: BlockchainNetwork, - val walletsData: List + val walletsData: List, ) { fun updateWallets(walletDataList: List): WalletStore { val relevantWalletDataList = walletDataList.filter { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt new file mode 100644 index 0000000000..dbb030f67f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt @@ -0,0 +1,133 @@ +package com.tangem.tap.features.wallet.redux.middlewares + +import com.tangem.common.extensions.isZero +import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.extensions.toFiatRateString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue +import com.tangem.tap.domain.model.TotalFiatBalance +import com.tangem.tap.domain.model.WalletDataModel +import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.TotalBalance +import com.tangem.tap.features.wallet.models.WalletRent +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAddresses +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletMainButton +import com.tangem.tap.features.wallet.redux.WalletStore +import com.tangem.tap.features.wallet.ui.BalanceStatus +import com.tangem.tap.features.wallet.ui.BalanceWidgetData +import com.tangem.tap.features.wallet.ui.TokenData +import com.tangem.tap.store + +internal fun List.mapToReduxModels( + isMultiWalletAllowed: Boolean, +): List { + return this.map { walletStoreModel -> + walletStoreModel.mapToReduxModel(isMultiWalletAllowed) + } +} + +internal fun TotalFiatBalance.mapToReduxModel(): TotalBalance { + return TotalBalance( + state = when (this) { + is TotalFiatBalance.Loading -> ProgressState.Loading + is TotalFiatBalance.Error -> ProgressState.Error + is TotalFiatBalance.Loaded -> ProgressState.Done + }, + fiatAmount = amount, + fiatCurrency = store.state.globalState.appCurrency, + ) +} + +internal fun WalletStoreModel.mapToReduxModel( + isMultiWalletAllowed: Boolean, +): WalletStore { + return WalletStore( + walletManager = walletManager, + blockchainNetwork = blockchainNetwork, + walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletRent), + ) +} + +private fun List.mapToReduxModel( + isMultiWalletAllowed: Boolean, + walletRent: WalletStoreModel.WalletRent?, +): List { + return this.map { walletDataModel -> + with(walletDataModel) { + val amount = status.amount + val amountFormatted = amount.toFormattedCurrencyString( + decimals = currency.decimals, + currency = currency.currencySymbol, + ) + val appCurrency = store.state.globalState.appCurrency + val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } + val fiatAmountFormatted = fiatAmount + ?.takeIf { !status.isErrorStatus } + ?.toFormattedFiatValue(appCurrency.symbol) + val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol) + + WalletData( + currency = currency, + walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress -> + WalletAddresses( + selectedAddress = selectedAddress, + list = walletAddresses, + ) + }, + existentialDepositString = existentialDeposit?.toPlainString(), + fiatRate = fiatRate, + fiatRateString = fiatRateFormatted, + pendingTransactions = status.pendingTransactions, + mainButton = WalletMainButton.SendButton( + enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(), + ), + walletRent = walletRent?.let { + WalletRent( + minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", + rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}", + ) + }, + currencyData = BalanceWidgetData( + status = when (status) { + is WalletDataModel.Loading -> BalanceStatus.Loading + is WalletDataModel.NoAccount -> BalanceStatus.NoAccount + is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress + is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress + is WalletDataModel.Unreachable -> BalanceStatus.Unreachable + is WalletDataModel.MissedDerivation -> BalanceStatus.MissedDerivation + is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline + }, + currency = currency.currencyName, + currencySymbol = currency.currencySymbol, + blockchainAmount = status.amount, + amount = amount, + amountFormatted = amountFormatted, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmountFormatted, + token = when { + !isMultiWalletAllowed && currency is Currency.Token -> { + TokenData( + amount = amount, + amountFormatted = amountFormatted, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmountFormatted, + tokenSymbol = currency.currencySymbol, + fiatRate = fiatRate, + fiatRateString = fiatRateFormatted, + ) + } + else -> null + }, + amountToCreateAccount = (status as? WalletDataModel.NoAccount) + ?.amountToCreateAccount + ?.toString(), + errorMessage = status.errorMessage, + ), + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index bbe6f90378..9bba0011a5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -51,7 +51,7 @@ class MultiWalletMiddleware { handleAddingWalletManagers(globalState, action.walletManagers) } is WalletAction.MultiWallet.SelectWallet -> { - if (action.walletData != null) { + if (action.currency != null) { store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletDetails)) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 5d6a9fc2d7..df69426a2c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -44,7 +44,7 @@ class TradeCryptoMiddleware { return } - val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val selectedWalletData = store.state.walletState.selectedWalletData ?: return val card = store.state.globalState.scanResponse?.card ?: return val addresses = selectedWalletData.walletAddresses?.list.orEmpty() @@ -85,7 +85,7 @@ class TradeCryptoMiddleware { } private fun proceedSellAction() { - val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val selectedWalletData = store.state.walletState.selectedWalletData ?: return val appCurrency = store.state.globalState.appCurrency val addresses = selectedWalletData.walletAddresses?.list.orEmpty() @@ -107,7 +107,7 @@ class TradeCryptoMiddleware { } private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { - val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val selectedWalletData = store.state.walletState.selectedWalletData ?: return Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency))) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index f94e6def86..fb6ff1dd3f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -17,7 +17,7 @@ class WalletDialogsMiddleware { store.dispatchDialogShow(WalletDialog.SignedHashesMultiWalletDialog) } is WalletAction.DialogAction.ChooseTradeActionDialog -> { - store.state.walletState.getSelectedWalletData()?.let { + store.state.walletState.selectedWalletData?.let { Analytics.send(Token.ButtonExchange(AnalyticsParam.CurrencyType.Currency(it.currency))) } store.dispatchDialogShow(WalletDialog.ChooseTradeActionDialog) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index f6e0c47011..5dcd8b81bd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -97,11 +97,7 @@ class WalletMiddleware { when (action) { is WalletAction.TradeCryptoAction -> tradeCryptoMiddleware.handle(state, action) is WalletAction.Warnings -> warningsMiddleware.handle(action, globalState) - is WalletAction.MultiWallet -> multiWalletMiddleware.handle( - action, - walletState, - globalState - ) + is WalletAction.MultiWallet -> multiWalletMiddleware.handle(action, walletState, globalState) is WalletAction.AppCurrencyAction -> appCurrencyMiddleware.handle(action) is WalletAction.DialogAction -> walletDialogMiddleware.handle(action) is WalletAction.LoadWallet -> { @@ -129,9 +125,7 @@ class WalletMiddleware { true, ), ) - store.dispatch( - action = WalletAction.LoadWallet.Success(action.wallet, action.blockchain) - ) + store.dispatch(WalletAction.LoadWallet.Success(action.wallet, action.blockchain)) } } store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) @@ -150,7 +144,7 @@ class WalletMiddleware { action.coinsList != null -> action.coinsList else -> { if (walletState.isMultiwalletAllowed) { - walletState.walletsData.map { it.currency } + walletState.walletsDataFromStores.map { it.currency } } else { val derivationPath = walletState.primaryWallet?.currency?.derivationPath val primaryBlockchain = walletState.primaryBlockchain @@ -175,7 +169,7 @@ class WalletMiddleware { Timber.e( throwable, "Loading rates failed for [%s]", - currency.currencySymbol + currency.currencySymbol, ) } } @@ -188,14 +182,11 @@ class WalletMiddleware { } is WalletAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createWallet( - globalState.scanResponse?.card?.cardId - ) + val result = tangemSdkManager.createWallet(globalState.scanResponse?.card?.cardId) when (result) { is CompletionResult.Success -> { - val scanNoteResponse = - globalState.scanResponse?.copy(card = result.data) - scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) } + val scanResponse = globalState.scanResponse?.copy(card = result.data) + scanResponse?.let { store.onCardScanned(scanResponse) } } is CompletionResult.Failure -> {} } @@ -210,9 +201,7 @@ class WalletMiddleware { store.dispatchOnMain(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) scope.launch { - val response = OnlineCardVerifier().getCardInfo( - action.card.cardId, action.card.cardPublicKey - ) + val response = OnlineCardVerifier().getCardInfo(action.card.cardId, action.card.cardPublicKey) when (response) { is Result.Success -> { val actionList = listOf( @@ -237,11 +226,12 @@ class WalletMiddleware { refresh = action is WalletAction.LoadData.Refresh, ) } else { - val scanNoteResponse = globalState.scanResponse ?: return@launch - if (walletState.walletsData.isNotEmpty()) { - globalState.tapWalletManager.reloadData(scanNoteResponse) + val scanResponse = globalState.scanResponse ?: return@launch + + if (walletState.walletsDataFromStores.isNotEmpty()) { + globalState.tapWalletManager.reloadData(scanResponse) } else { - globalState.tapWalletManager.loadData(scanNoteResponse) + globalState.tapWalletManager.loadData(scanResponse) } } } @@ -249,8 +239,8 @@ class WalletMiddleware { is NetworkStateChanged -> { store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync - if (selectedUserWallet != null) scope.launch { - globalState.tapWalletManager.loadData(selectedUserWallet) + if (selectedUserWallet != null) { + scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet) } } else { globalState.scanResponse?.let { scanNoteResponse -> scope.launch { globalState.tapWalletManager.loadData(scanNoteResponse) } @@ -295,6 +285,7 @@ class WalletMiddleware { } is WalletAction.UserWalletChanged -> Unit is WalletAction.WalletStoresChanged -> { + updateWalletStores(action.walletStores, walletState) fetchTotalFiatBalance(action.walletStores, walletState) findMissedDerivations(action.walletStores) tryToShowAppRatingWarning(action.walletStores) @@ -303,12 +294,29 @@ class WalletMiddleware { } } + private fun updateWalletStores(wallStores: List, state: WalletState) { + scope.launch(Dispatchers.Default) { + if (!state.isMultiwalletAllowed) { + wallStores.firstOrNull()?.walletsData?.firstOrNull()?.let { + store.dispatchOnMain(WalletAction.MultiWallet.SetSingleWalletCurrency(it.currency)) + } + } + + val reduxWalletStores = wallStores.mapToReduxModels(state.isMultiwalletAllowed) + store.dispatchOnMain( + WalletAction.WalletStoresChanged.UpdateWalletStores( + reduxWalletStores = reduxWalletStores.toList(), + ), + ) + } + } + private fun fetchTotalFiatBalance(walletStores: List, state: WalletState) { scope.launch(Dispatchers.Default) { val totalFiatBalance = totalFiatBalanceCalculator.calculateOrNull( prevAmount = state.totalBalance?.fiatAmount, walletStores = walletStores, - ) + )?.mapToReduxModel() if (totalFiatBalance != null) { store.dispatchOnMain(WalletAction.TotalFiatBalanceChanged(totalFiatBalance)) @@ -361,7 +369,7 @@ class WalletMiddleware { } private fun prepareSendAction(amount: Amount?, state: WalletState?): Action { - val selectedWalletData = state?.getSelectedWalletData() + val selectedWalletData = state?.selectedWalletData val currency = selectedWalletData?.currency val walletStore = state?.getWalletStore(currency) @@ -376,36 +384,34 @@ class WalletMiddleware { if (currency != null && state.isMultiwalletAllowed) { when (currency) { is Currency.Blockchain -> { - val amountToSend = - amounts?.find { it.currencySymbol == currency.blockchain.currency } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) + val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency } + ?: return WalletAction.DialogAction.ChooseCurrency(amounts) PrepareSendScreen( coinAmount = amountToSend, coinRate = selectedWalletData.fiatRate, - walletManager = walletStore.walletManager + walletManager = walletStore.walletManager, ) } is Currency.Token -> { - val amountToSend = - amounts?.find { it.currencySymbol == currency.token.symbol } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) + val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol } + ?: return WalletAction.DialogAction.ChooseCurrency(amounts) prepareSendActionForToken( amount = amountToSend, state = state, selectedWalletData = selectedWalletData, - walletStore = walletStore + walletStore = walletStore, ) } } } else { - if (amounts?.size ?: 0 > 1) { + if ((amounts?.size ?: 0) > 1) { WalletAction.DialogAction.ChooseCurrency(amounts) } else { val amountToSend = amounts?.first() PrepareSendScreen( coinAmount = amountToSend, coinRate = selectedWalletData?.fiatRate, - walletManager = walletStore?.walletManager + walletManager = walletStore?.walletManager, ) } } @@ -416,7 +422,7 @@ class WalletMiddleware { amount: Amount, state: WalletState?, selectedWalletData: WalletData?, - walletStore: WalletStore? + walletStore: WalletStore?, ): PrepareSendScreen { val coinRate = state?.getWalletData(walletStore?.blockchainNetwork)?.fiatRate val tokenRate = if (state?.isMultiwalletAllowed == true) { @@ -431,7 +437,7 @@ class WalletMiddleware { coinRate = coinRate, walletManager = walletStore?.walletManager, tokenAmount = amount, - tokenRate = tokenRate + tokenRate = tokenRate, ) } @@ -447,7 +453,7 @@ class WalletMiddleware { val balance = walletManager.wallet.fundsAvailable(AmountType.Coin) val outgoingTxs = walletManager.wallet.getPendingTransactions( - PendingTransactionType.Outgoing + PendingTransactionType.Outgoing, ).filterByCoin() val rentExempt = result.data @@ -465,8 +471,8 @@ class WalletMiddleware { WalletAction.SetWalletRent( wallet = walletManager.wallet, minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"), - rentExempt = ("${rentExempt.stripZeroPlainString()} $currency") - ) + rentExempt = ("${rentExempt.stripZeroPlainString()} $currency"), + ), ) } else { dispatchOnMain(WalletAction.RemoveWalletRent(walletManager.wallet)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index e911c48e3c..b2ef29f6b3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -125,7 +125,8 @@ class WarningsMiddleware { if (scanResponse.isTangemTwins() || scanResponse.isDemoCard()) return null if (scanResponse.card.isMultiwalletAllowed) { - return if (scanResponse.card.hasSignedHashes()) { + val isBackupForbidden = with(scanResponse.card.settings) { !(isBackupAllowed || isHDWalletAllowed) } + return if (scanResponse.card.hasSignedHashes() && isBackupForbidden) { WarningMessagesManager.signedHashesMultiWalletWarning() } else { store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) @@ -133,8 +134,7 @@ class WarningsMiddleware { } } - val validator = store.state.walletState.walletManagers.firstOrNull() - as? SignatureCountValidator + val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator return if (validator == null) { if (scanResponse.card.hasSignedHashes()) { WarningMessagesManager.alreadySignedHashesWarning() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 3b174c915f..b1e6cebbac 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -9,9 +9,18 @@ import com.tangem.tap.common.extensions.toFiatString import com.tangem.tap.common.extensions.toFormattedCurrencyString import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.wallet.models.* -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.features.wallet.models.WalletRent +import com.tangem.tap.features.wallet.models.filterByToken +import com.tangem.tap.features.wallet.models.getPendingTransactions +import com.tangem.tap.features.wallet.models.removeUnknownTransactions +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletMainButton +import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN +import com.tangem.tap.features.wallet.redux.WalletStore import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData @@ -22,7 +31,7 @@ class MultiWalletReducer { fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { - val wallets: List = action.blockchains.mapNotNull { blockchain -> + val walletStores: List = action.blockchains.mapNotNull { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) @@ -38,13 +47,13 @@ class MultiWalletReducer { status = BalanceStatus.Loading, currency = blockchain.blockchain.fullName, currencySymbol = blockchain.blockchain.currency, - token = cardToken + token = cardToken, ), walletAddresses = createAddressList(wallet), mainButton = WalletMainButton.SendButton(false), currency = Currency.Blockchain( blockchain.blockchain, - blockchain.derivationPath + blockchain.derivationPath, ), existentialDepositString = getExistentialDeposit(walletManager), ) @@ -52,18 +61,18 @@ class MultiWalletReducer { WalletStore( walletManager = walletManager, blockchainNetwork = blockchain, - walletsData = listOf(walletData) + walletsData = listOf(walletData), ) } val selectedCurrency = if (!state.isMultiwalletAllowed) { - wallets.firstOrNull()?.walletsData?.firstOrNull()?.currency + walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency } else { - state.selectedCurrency + state.selectedWalletData?.currency } state.copy( - wallets = wallets, - selectedCurrency = selectedCurrency + walletsStores = walletStores, + selectedCurrency = selectedCurrency, ) } is WalletAction.MultiWallet.AddBlockchain -> { @@ -80,14 +89,14 @@ class MultiWalletReducer { mainButton = WalletMainButton.SendButton(false), currency = Currency.Blockchain( action.blockchain.blockchain, - action.blockchain.derivationPath + action.blockchain.derivationPath, ), existentialDepositString = getExistentialDeposit(walletManager), ) val walletStore = WalletStore( walletManager = walletManager, blockchainNetwork = action.blockchain, - walletsData = listOf(walletData) + walletsData = listOf(walletData), ) val newState = state.updateWalletStore(walletStore) @@ -125,36 +134,40 @@ class MultiWalletReducer { status = tokenBalanceStatus, amount = action.amount.value, amountFormatted = action.amount.value?.toFormattedCurrencyString( - action.amount.decimals, action.amount.currencySymbol + action.amount.decimals, action.amount.currencySymbol, ), fiatAmountFormatted = tokenWalletData.fiatRate?.let { action.amount.value?.toFiatString(it, store.state.globalState.appCurrency.symbol) } ?: UNKNOWN_AMOUNT_SIGN, - blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO + blockchainAmount = wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO, ), pendingTransactions = pendingTransactions.removeUnknownTransactions(), mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled), currency = Currency.Token( token = action.token, blockchain = action.blockchain.blockchain, - derivationPath = action.blockchain.derivationPath + derivationPath = action.blockchain.derivationPath, ), - walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)) + walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)), ) state.updateWalletData(newTokenWalletData) } is WalletAction.MultiWallet.SetIsMultiwalletAllowed -> state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed) - is WalletAction.MultiWallet.SelectWallet -> - state.copy(selectedCurrency = action.walletData?.currency) + is WalletAction.MultiWallet.SelectWallet -> { + state.copy(selectedCurrency = action.currency) + } + is WalletAction.MultiWallet.SetSingleWalletCurrency -> { + state.copy(selectedCurrency = action.currency) + } is WalletAction.MultiWallet.TryToRemoveWallet -> state is WalletAction.MultiWallet.RemoveWallet -> { - state.removeWallet(state.getWalletData(action.currency)) + state.removeWalletData(state.getWalletData(action.currency)) } is WalletAction.MultiWallet.RemoveWallets -> { var updatedState = state - action.currencies.forEach { updatedState = updatedState.removeWallet(state.getWalletData(it)) } + action.currencies.forEach { updatedState = updatedState.removeWalletData(state.getWalletData(it)) } updatedState } is WalletAction.MultiWallet.SetPrimaryBlockchain -> @@ -188,7 +201,7 @@ class MultiWalletReducer { } private fun addTokens( - tokens: List, blockchain: BlockchainNetwork, state: WalletState + tokens: List, blockchain: BlockchainNetwork, state: WalletState, ): WalletState { val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) } return state.updateWalletsData(wallets) @@ -206,10 +219,10 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat currencyData = BalanceWidgetData( status = BalanceStatus.Loading, currency = this.name, - currencySymbol = this.symbol + currencySymbol = this.symbol, ), walletAddresses = walletAddresses, mainButton = WalletMainButton.SendButton(false), - currency = currency + currency = currency, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 79e84eec36..68e6080883 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -17,6 +17,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletMainButton import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN +import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData @@ -106,18 +107,15 @@ class OnWalletLoadedReducer { mainButton = WalletMainButton.SendButton(isTokenSendButtonEnabled), ) } - val newWallets = tokens + newWalletData - val wallets = walletState.replaceSomeWallets((newWallets)) + val newWalletsData = tokens + newWalletData + val walletsData = walletState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData) - return walletState - .updateWalletsData(wallets) + return walletState.updateWalletsData(walletsData) } private fun onSingleWalletLoaded(wallet: Wallet, walletState: WalletState): WalletState { if (wallet.blockchain != walletState.primaryBlockchain) return walletState - // val ratesRespository = store.state.globalState.tapWalletManager.ratesRepository - // val tokenCurrency = val fiatCurrencyName = store.state.globalState.appCurrency.code val token = wallet.getFirstToken() val tokenData = if (token != null) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt index 79470dae62..08a7def4bd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt @@ -36,6 +36,7 @@ private fun List.mapToProgressState(): List { BalanceStatus.Unreachable, BalanceStatus.EmptyCard, BalanceStatus.UnknownBlockchain, + BalanceStatus.MissedDerivation, -> ProgressState.Error BalanceStatus.Loading, null, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 61ea00554a..254fe48e3b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -3,29 +3,38 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet -import com.tangem.common.extensions.isZero import com.tangem.common.extensions.mapNotNullValues import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.toFiatRateString +import com.tangem.tap.common.extensions.toFiatString +import com.tangem.tap.common.extensions.toFiatValue +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.getArtworkUrl import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.getFirstToken -import com.tangem.tap.domain.model.TotalFiatBalance -import com.tangem.tap.domain.model.WalletDataModel -import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.redux.AddressData +import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.tap.features.wallet.redux.ErrorType +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletAddresses +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletMainButton +import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.WalletStore +import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData -import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.store import org.rekotlin.Action @@ -63,7 +72,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS is WalletAction.EmptyWallet -> { newState = newState.copy( state = ProgressState.Done, - wallets = listOf( + walletsStores = listOf( WalletStore( walletManager = null, blockchainNetwork = BlockchainNetwork( @@ -85,7 +94,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS is WalletAction.LoadData.Failure -> { when (action.error) { is TapError.NoInternetConnection -> { - val wallets = newState.wallets + val wallets = newState.walletsStores .map { store -> store.copy( walletsData = store.walletsData.map { @@ -95,20 +104,19 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ), ) }, - - ) + ) } newState = newState.copy( state = ProgressState.Error, error = ErrorType.NoInternetConnection, - wallets = wallets, + walletsStores = wallets, ) } is TapError.UnknownBlockchain -> { newState = newState.copy( state = ProgressState.Done, - wallets = listOf( + walletsStores = listOf( WalletStore( walletManager = null, blockchainNetwork = BlockchainNetwork( @@ -153,7 +161,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS BalanceStatus.Loading } if (action.blockchain == null) { - val wallets = newState.wallets.map { walletStore -> + val wallets = newState.walletsStores.map { walletStore -> walletStore.copy( walletsData = walletStore.walletsData.map { walletData -> walletData.copy( @@ -170,7 +178,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS } newState = newState.copy( state = ProgressState.Loading, - wallets = wallets, + walletsStores = wallets, ) } else { val walletManager = newState.getWalletManager(action.blockchain) ?: return newState @@ -178,7 +186,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS walletManager.cardTokens.map { Currency.fromBlockchainNetwork(action.blockchain, it) } - val newWallets = newState.walletsData.filter { currencies.contains(it.currency) } + val newWalletsData = newState.walletsDataFromStores.filter { currencies.contains(it.currency) } .map { wallet -> wallet.copy( currencyData = wallet.currencyData.copy( @@ -189,8 +197,8 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS mainButton = WalletMainButton.SendButton(false), ) } - val wallets = newState.replaceSomeWallets(newWallets) - val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(wallets) + val walletsData = newState.walletsDataFromStores.replaceSomeWalletsData(newWalletsData) + val walletStore = newState.getWalletStore(action.blockchain)?.updateWallets(walletsData) newState = newState.updateWalletStore(walletStore) } } @@ -260,7 +268,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS newState = newState.updateWalletsData(updatedWallets) val progressState = - if (newState.walletsData.any { it.currencyData.status == BalanceStatus.Loading }) { + if (newState.walletsDataFromStores.any { it.currencyData.status == BalanceStatus.Loading }) { ProgressState.Loading } else { ProgressState.Done @@ -292,15 +300,13 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS } is WalletAction.TradeCryptoAction -> return newState is WalletAction.ChangeSelectedAddress -> { - val selectedWalletData = newState.getWalletData(newState.selectedCurrency) - - val walletAddresses = - newState.getWalletData(selectedWalletData?.currency)?.walletAddresses - ?: return newState + val walletAddresses = newState.getWalletData(newState.selectedCurrency)?.walletAddresses + ?: return newState val address = walletAddresses.list.firstOrNull { it.type == action.type } ?: return newState + newState = newState.updateWalletData( - selectedWalletData?.copy( + newState.selectedWalletData?.copy( walletAddresses = WalletAddresses( selectedAddress = address, list = walletAddresses.list, @@ -328,37 +334,41 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS val card = scanResponse.card newState = WalletState( cardId = card.cardId, - isMultiwalletAllowed = card.isMultiwalletAllowed, + isMultiwalletAllowed = isMultiCurrency, cardImage = Artwork( artworkId = artworkUrl, ), isTestnet = card.isTestCard, state = ProgressState.Loading, - wallets = newState.wallets, - showBackupWarning = card.isMultiwalletAllowed && + showBackupWarning = isMultiCurrency && card.settings.isBackupAllowed && card.backupStatus == CardDTO.BackupStatus.NoBackup, walletCardsCount = card.findCardsCount(), + totalBalance = if (isMultiCurrency) { + TotalBalance(ProgressState.Loading, BigDecimal.ZERO, store.state.globalState.appCurrency) + } else { + null + }, ) } - is WalletAction.WalletStoresChanged -> { + is WalletAction.WalletStoresChanged.UpdateWalletStores -> { newState = newState.copy( - wallets = action.walletStores.mapToReduxModel(newState.isMultiwalletAllowed), + walletsStores = action.reduxWalletStores, ) } is WalletAction.TotalFiatBalanceChanged -> { newState = newState.copy( - totalBalance = action.balance.mapToReduxModel(), + totalBalance = action.balance, ) } is WalletAction.LoadData.Success -> { val selectedCurrency = if (!newState.isMultiwalletAllowed) { - newState.wallets.firstOrNull() + newState.walletsStores.firstOrNull() ?.walletsData ?.firstOrNull() ?.currency } else { - newState.selectedCurrency + newState.selectedWalletData?.currency } newState = newState.copy( @@ -377,117 +387,6 @@ private fun CardDTO.findCardsCount(): Int? { ?.takeIf { this.isMultiwalletAllowed } } -@JvmName("walletStoreModelToReduxModel") -private fun List.mapToReduxModel( - isMultiWalletAllowed: Boolean, -): List { - return this.map { walletStoreModel -> - with(walletStoreModel) { - WalletStore( - walletManager = walletManager, - blockchainNetwork = blockchainNetwork, - walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletStoreModel.walletRent), - ) - } - } -} - -@JvmName("walletDataModelToReduxModel") -private fun List.mapToReduxModel( - isMultiWalletAllowed: Boolean, - walletRent: WalletStoreModel.WalletRent?, -): List { - return this.map { walletDataModel -> - with(walletDataModel) { - val amount = status.amount - val amountFormatted = amount.toFormattedCurrencyString( - decimals = currency.decimals, - currency = currency.currencySymbol, - ) - val appCurrency = store.state.globalState.appCurrency - val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } - val fiatAmountFormatted = fiatAmount - ?.takeIf { !status.isErrorStatus } - ?.toFormattedFiatValue(appCurrency.symbol) - val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol) - - WalletData( - currency = currency, - walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress -> - WalletAddresses( - selectedAddress = selectedAddress, - list = walletAddresses, - ) - }, - existentialDepositString = existentialDeposit?.toPlainString(), - fiatRate = fiatRate, - fiatRateString = fiatRateFormatted, - pendingTransactions = status.pendingTransactions, - mainButton = WalletMainButton.SendButton( - enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(), - ), - walletRent = walletRent?.let { - WalletRent( - minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", - rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}", - ) - }, - currencyData = BalanceWidgetData( - status = when (status) { - is WalletDataModel.Loading -> BalanceStatus.Loading - is WalletDataModel.NoAccount -> BalanceStatus.NoAccount - is WalletDataModel.Refreshing -> BalanceStatus.Refreshing - is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress - is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress - is WalletDataModel.Unreachable, - is WalletDataModel.MissedDerivation, - -> BalanceStatus.Unreachable - is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline - }, - currency = currency.currencyName, - currencySymbol = currency.currencySymbol, - blockchainAmount = status.amount, - amount = amount, - amountFormatted = amountFormatted, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - token = when { - !isMultiWalletAllowed && currency is Currency.Token -> { - TokenData( - amount = amount, - amountFormatted = amountFormatted, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - tokenSymbol = currency.currencySymbol, - fiatRate = fiatRate, - fiatRateString = fiatRateFormatted, - ) - } - else -> null - }, - amountToCreateAccount = (status as? WalletDataModel.NoAccount) - ?.amountToCreateAccount - ?.toString(), - errorMessage = status.errorMessage, - ), - ) - } - } -} - -private fun TotalFiatBalance.mapToReduxModel(): TotalBalance { - return TotalBalance( - state = when (this) { - is TotalFiatBalance.Loading -> ProgressState.Loading - is TotalFiatBalance.Refreshing -> ProgressState.Refreshing - is TotalFiatBalance.Error -> ProgressState.Error - is TotalFiatBalance.Loaded -> ProgressState.Done - }, - fiatAmount = amount, - fiatCurrency = store.state.globalState.appCurrency, - ) -} - fun createAddressList(wallet: Wallet?, walletAddresses: WalletAddresses? = null): WalletAddresses? { if (wallet == null) return null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 71bdbb7b8c..4afc4c08db 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -16,7 +16,8 @@ enum class BalanceStatus { Refreshing, NoAccount, EmptyCard, - UnknownBlockchain + UnknownBlockchain, + MissedDerivation, } data class BalanceWidgetData( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index d737ac736b..a7f230dc35 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -1,16 +1,23 @@ package com.tangem.tap.features.wallet.ui import android.os.Bundle -import android.view.* +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding +import com.badoo.mvicore.modelWatcher +import com.tangem.common.doOnResult import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tap.common.SnackbarHandler @@ -18,26 +25,38 @@ import com.tangem.tap.common.TestActions import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.DetailsScreen import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.extensions.appendIfNotNull +import com.tangem.tap.common.extensions.beginDelayedTransition +import com.tangem.tap.common.extensions.fitChipsByGroupWidth +import com.tangem.tap.common.extensions.getColor +import com.tangem.tap.common.extensions.getString +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.redux.ErrorType +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter import com.tangem.tap.features.wallet.ui.images.load import com.tangem.tap.features.wallet.ui.test.TestWallet -import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.userWalletsListManagerSafe import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentWalletDetailsBinding +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.rekotlin.StoreSubscriber class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), @@ -48,6 +67,42 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind) + private val walletDataWatcher = modelWatcher { + WalletData::pendingTransactions { + showPendingTransactionsIfPresent(it) + } + WalletData::currency { + handleCurrencyIcon(it) + } + WalletData::currencyData { + setupBalanceData(it) + } + (WalletData::currencyData or WalletData::currency) { walletData -> + setupCurrency(walletData.currencyData, walletData.currency) + setupSwipeRefresh(walletData.currencyData, walletData.currency) + } + } + + private val walletStateWatcher = modelWatcher { + (WalletState::selectedCurrency or WalletState::selectedWalletData) { state -> + val selectedWalletData = state.selectedWalletData + if (selectedWalletData != null) { + walletDataWatcher.invoke(selectedWalletData) + setupButtons(selectedWalletData, state.isExchangeServiceFeatureOn) + setupAddressCard(selectedWalletData) + handleWarnings(selectedWalletData) + } + } + (WalletState::selectedCurrency or WalletState::isExchangeServiceFeatureOn) { state -> + if (state.selectedWalletData != null) { + setupButtons(state.selectedWalletData!!, state.isExchangeServiceFeatureOn) + } + } + (WalletState::state or WalletState::error) { state -> + setupNoInternetHandling(state.state, state.error) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -79,6 +134,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), store.unsubscribe(this) } + override fun onDestroy() { + walletDataWatcher.clear() + walletStateWatcher.clear() + super.onDestroy() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) (activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar) @@ -129,50 +190,10 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun newState(state: WalletState) { if (activity == null || view == null) return - if (state.selectedCurrency == null) return - val selectedWallet = state.getSelectedWalletData() ?: return + if (state.selectedWalletData == null) return + walletStateWatcher.invoke(state) - - showPendingTransactionsIfPresent(selectedWallet.pendingTransactions) - setupCurrency(selectedWallet.currencyData, selectedWallet.currency) - setupAddressCard(selectedWallet) - setupNoInternetHandling(state) - setupBalanceData(selectedWallet.currencyData) - setupButtons(selectedWallet, state.isExchangeServiceFeatureOn) - - handleCurrencyIcon(selectedWallet) - handleWarnings(selectedWallet) updateViewMeasurements() - - binding.srlWalletDetails.setOnRefreshListener { - if (selectedWallet.currencyData.status != BalanceStatus.Loading) { - Analytics.send(Token.Refreshed()) - val blockchainNetwork = BlockchainNetwork( - blockchain = selectedWallet.currency.blockchain, - derivationPath = selectedWallet.currency.derivationPath, - tokens = emptyList(), - ) - val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync - if (selectedUserWallet != null) scope.launch { - walletCurrenciesManager.update(selectedUserWallet, blockchainNetwork) - } else { - store.dispatch( - WalletAction.LoadWallet( - blockchain = BlockchainNetwork( - selectedWallet.currency.blockchain, - selectedWallet.currency.derivationPath, - emptyList(), - ), - ), - ) - store.dispatch(WalletAction.LoadFiatRate(coinsList = listOf(selectedWallet.currency))) - } - } - } - - if (selectedWallet.currencyData.status != BalanceStatus.Loading) { - binding.srlWalletDetails.isRefreshing = false - } } private fun updateViewMeasurements() { @@ -203,6 +224,37 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } } + private fun setupSwipeRefresh(currencyData: BalanceWidgetData, currency: Currency) { + binding.srlWalletDetails.setOnRefreshListener { + if (currencyData.status != BalanceStatus.Loading && currencyData.status != BalanceStatus.Refreshing) { + Analytics.send(Token.Refreshed()) + lifecycleScope.launch(Dispatchers.Default) { + val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync + if (selectedUserWallet != null) { + walletCurrenciesManager.update(selectedUserWallet, currency) + .doOnResult { + withContext(Dispatchers.Main) { + binding.srlWalletDetails.isRefreshing = false + } + } + } else { + val blockchainNetwork = BlockchainNetwork( + blockchain = currency.blockchain, + derivationPath = currency.derivationPath, + tokens = emptyList(), + ) + + store.dispatch(WalletAction.LoadWallet(blockchainNetwork)) + store.dispatch(WalletAction.LoadFiatRate(coinsList = listOf(currency))) + } + } + } + } + + binding.srlWalletDetails.isRefreshing = currencyData.status == BalanceStatus.Loading || + currencyData.status == BalanceStatus.Refreshing + } + private fun setupButtons(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) = with(binding) { lWalletDetails.btnCopy.setOnClickListener { selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString -> @@ -231,9 +283,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), rvWarningMessages.show(warningDetails.isNotEmpty()) } - private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) { + private fun handleCurrencyIcon(currency: Currency) = with(binding.lWalletDetails.lBalance) { ivCurrency.load( - currency = wallet.currency, + currency = currency, derivationStyle = store.state.globalState .scanResponse ?.card @@ -281,9 +333,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } } - private fun setupNoInternetHandling(state: WalletState) { - if (state.state == ProgressState.Error) { - if (state.error == ErrorType.NoInternetConnection) { + private fun setupNoInternetHandling(progressState: ProgressState, errorType: ErrorType?) { + if (progressState == ProgressState.Error) { + if (errorType == ErrorType.NoInternetConnection) { binding.srlWalletDetails.isRefreshing = false (activity as? SnackbarHandler)?.showSnackbar( text = R.string.wallet_notification_no_internet, @@ -350,7 +402,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.menu_remove -> { - store.state.walletState.getSelectedWalletData()?.let { walletData -> + store.state.walletState.selectedWalletData?.let { walletData -> store.dispatch(WalletAction.MultiWallet.TryToRemoveWallet(walletData.currency)) true } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 962c7cd390..c0bde194bf 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -15,6 +15,7 @@ import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import coil.load import coil.size.Scale +import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics @@ -80,15 +81,18 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber state.select { it.walletState } } walletView.setFragment(this, binding) - viewModel.launch() } override fun onStop() { @@ -97,6 +101,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber(D BalanceStatus.Unreachable -> { root.getString(R.string.wallet_balance_blockchain_unreachable) } + BalanceStatus.MissedDerivation -> { + root.getString(R.string.wallet_balance_missing_derivation) + } else -> null } @@ -95,7 +98,7 @@ class WalletAdapter : ListAdapter(D if (wallet.walletAddresses != null) { cardWallet.setOnClickListener { Analytics.send(Portfolio.TokenTapped()) - store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet)) + store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet.currency)) } } else { cardWallet.setOnClickListener(null) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt index 4f384d33f7..857cdd2631 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/test/TestWalletDetailsActions.kt @@ -156,7 +156,7 @@ private class SolanaRentWarningActionEmitter { } private fun getBlockchainNetwork(): BlockchainNetwork { - val currency = store.state.walletState.getSelectedWalletData()!!.currency + val currency = store.state.walletState.selectedWalletData!!.currency return BlockchainNetwork(currency.blockchain, currency.derivationPath, listOf()) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 1ceda1b7b4..f7c8dc8a66 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -3,6 +3,8 @@ package com.tangem.tap.features.wallet.ui.wallet import android.widget.Button import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager +import com.badoo.mvicore.DiffStrategy +import com.badoo.mvicore.modelWatcher import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.tap.common.analytics.Analytics @@ -33,6 +35,45 @@ class MultiWalletView : WalletView() { private lateinit var walletsAdapter: WalletAdapter + private val watcher = modelWatcher { + val totalBalanceStrategy: DiffStrategy = { old, new -> + old.cardId != new.cardId || + old.totalBalance != new.totalBalance || + old.state != new.state || + old.walletsStores.size != new.walletsStores.size + } + + // !!! Workaround !!! + // Checking state properties instead of state params can reduce application performance, + // but here it is necessary because the WalletStore has an unsuitable equals method + WalletState::walletsDataFromStores { + walletsAdapter.submitList(it) + } + WalletState::loadingUserTokens { + binding?.pbLoadingUserTokens?.show(it) + } + WalletState::walletCardsCount { walletCardsCount -> + binding?.let { + setupWalletCardNumber(it, walletCardsCount) + } + } + WalletState::missingDerivations { missingDerivations -> + binding?.let { + handleRescanWarning(it, missingDerivations.isNotEmpty()) + } + } + WalletState::showBackupWarning { showBackupWarnings -> + binding?.let { + handleBackupWarning(it, showBackupWarnings) + } + } + watch({ it }, totalBalanceStrategy) { walletState -> + binding?.let { + handleTotalBalance(it, walletState.totalBalance, walletState.state, walletState.walletsDataFromStores.size) + } + } + } + override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) { setFragment(fragment, binding) onViewCreated() @@ -40,6 +81,7 @@ class MultiWalletView : WalletView() { } private fun showMultiWalletView(binding: FragmentWalletBinding) = with(binding) { + watcher.clear() tvTwinCardNumber.hide() rvPendingTransaction.hide() lCardBalance.root.hide() @@ -68,13 +110,7 @@ class MultiWalletView : WalletView() { val fragment = fragment ?: return val binding = binding ?: return - handleTotalBalance(binding, state.totalBalance, state.state, state.walletsData.size) - handleBackupWarning(binding, state.showBackupWarning) - handleRescanWarning(binding, state.missingDerivations.isNotEmpty()) - setupWalletCardNumber(binding, state.walletCardsCount) - walletsAdapter.submitList(state.walletsData) - - binding.pbLoadingUserTokens.show(state.loadingUserTokens) + watcher.invoke(state) binding.btnAddToken.setOnClickListener { val card = store.state.globalState.scanResponse!!.card @@ -91,7 +127,7 @@ class MultiWalletView : WalletView() { store.dispatch(TokensAction.AllowToAddTokens(true)) store.dispatch( TokensAction.SetAddedCurrencies( - wallets = state.walletsData, + wallets = state.walletsDataFromStores, derivationStyle = card.derivationStyle, ), ) @@ -139,21 +175,26 @@ class MultiWalletView : WalletView() { walletsCount: Int, ) = with(binding.lCardTotalBalance) { if (walletsCount == 0) { - root.isVisible = false + if (progressState != ProgressState.Loading) { + root.isVisible = false + } } else { if (totalBalance == null) { - veilBalance.animateVisibility(show = true) - root.isVisible = progressState == ProgressState.Loading + if (progressState != ProgressState.Loading) { + root.isVisible = false + } } else { root.isVisible = true - // Skip changes when on refreshing state if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) { return@with } - veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading) - tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading) + if (totalBalance.state == ProgressState.Loading) { + veilBalance.veil() + } else { + veilBalance.unVeil() + } tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error) tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString( @@ -218,6 +259,11 @@ class MultiWalletView : WalletView() { rowButtons.btnSend.text = fragment?.getText(R.string.wallet_button_create_wallet) rowButtons.onSendClick = { store.dispatch(WalletAction.CreateWallet) } } + + override fun onDestroyFragment() { + super.onDestroyFragment() + watcher.clear() + } } private val WalletDetailsButtonsRow.btnBuy: Button diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt index e765e738e8..44ff7329fc 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt @@ -52,4 +52,4 @@ class SaltPaySingleWalletView : WalletView() { ), ).setup() } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index abca20330d..e8fc90a242 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -153,14 +153,12 @@ class SingleWalletView : WalletView() { (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition() chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = - MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) + val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener - val type = - MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) + val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt index a3b09aab12..d7d7cbb405 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt @@ -17,6 +17,8 @@ abstract class WalletView { binding = null } + open fun onDestroyFragment() {} + abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) abstract fun onViewCreated() abstract fun onNewState(state: WalletState) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt index 1417914782..b381b423c3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt @@ -1,9 +1,10 @@ package com.tangem.tap.features.walletSelector.redux +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.TotalFiatBalance data class UserWalletModel( - val id: String, + val id: UserWalletId, val name: String, val artworkUrl: String, val type: Type, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index 957661126f..0985f4b68c 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -34,20 +34,16 @@ internal sealed interface WalletSelectorAction : Action { } data class SelectWallet( - val walletId: String, - ) : WalletSelectorAction - - data class UnlockWalletWithCard( - val walletId: String, + val userWalletId: UserWalletId, ) : WalletSelectorAction data class RenameWallet( - val walletId: String, + val userWalletId: UserWalletId, val newName: String, ) : WalletSelectorAction data class RemoveWallets( - val walletIdsToRemove: List, + val userWalletsIds: List, ) : WalletSelectorAction object AddWallet : WalletSelectorAction { diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index dd4c4ff88b..b6b31419de 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -19,13 +19,16 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor -import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.totalFiatBalanceCalculator import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber @@ -59,18 +62,17 @@ internal class WalletSelectorMiddleware { addWallet() } is WalletSelectorAction.SelectWallet -> { - selectWallet(action.walletId) - } - is WalletSelectorAction.UnlockWalletWithCard -> { - unlockWalletWithCard(action.walletId) + selectWallet(action.userWalletId) } is WalletSelectorAction.RemoveWallets -> { - removeWallets(action.walletIdsToRemove, state) + deleteWallets(action.userWalletsIds, state) } is WalletSelectorAction.RenameWallet -> { - renameWallet(action.walletId, action.newName) + renameWallet(action.userWalletId, action.newName) + } + is WalletSelectorAction.ChangeAppCurrency -> { + refreshUserWalletsAmounts() } - is WalletSelectorAction.ChangeAppCurrency, is WalletSelectorAction.AddWallet.Success, is WalletSelectorAction.AddWallet.Error, is WalletSelectorAction.SelectedWalletChanged, @@ -96,13 +98,17 @@ internal class WalletSelectorMiddleware { private fun updateBalances(walletStores: Map>, state: WalletSelectorState) { walletStores.forEach { (walletId, walletStores) -> - scope.launch { - val updatedWallet = state.wallets - .find { it.id == walletId.stringValue } - ?.updateWalletStoresAndCalculateFiatBalance(walletStores) + scope.launch(Dispatchers.Default) { + val foundWallet = state.wallets + .find { it.id == walletId } - if (updatedWallet != null) { - store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) + if (foundWallet != null) { + val updatedWallet = foundWallet + .updateWalletStoresAndCalculateFiatBalance(walletStores) + + if (foundWallet != updatedWallet) { + store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) + } } } } @@ -135,45 +141,38 @@ internal class WalletSelectorMiddleware { .doOnSuccess { Analytics.send(MyWallets.CardWasScanned) - userWalletsListManager.selectWallet(userWallet.walletId) store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - updateAccessCodeRequestPolicy(userWallet) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.onUserWalletSelected(userWallet) } } } - private fun selectWallet(id: String) { + private fun selectWallet(userWalletId: UserWalletId) { scope.launch { - userWalletsListManager.selectWallet(UserWalletId(id)) - .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.HandleError(error)) - } - .doOnSuccess { selectedWallet -> - updateAccessCodeRequestPolicy(selectedWallet) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(selectedWallet) - } - } - } - - private fun unlockWalletWithCard(id: String) { - scope.launch { - userWalletsListManager.get(UserWalletId(id)) + userWalletsListManager.get(userWalletId) .flatMap { userWallet -> - updateUserWalletWithScannedCard(userWallet) - } - .flatMap { updatedUserWallet -> - unlockUserWallet(updatedUserWallet) + if (userWallet.isLocked) { + unlockUserWalletWithScannedCard(userWallet) + } else { + userWalletsListManager.selectWallet(userWalletId) + } } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } + .doOnSuccess { + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync + if (selectedUserWallet != null) { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(selectedUserWallet) + } + } } } - private suspend fun updateUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { + private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { + tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) return tangemSdkManager.scanCard(userWallet.cardId) .map { scannedCard -> userWallet.copy( @@ -182,23 +181,26 @@ internal class WalletSelectorMiddleware { ), ) } - } - - private suspend fun unlockUserWallet(userWallet: UserWallet): CompletionResult { - return userWalletsListManager.unlockWithCard(userWallet) - .doOnSuccess { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) + .flatMap { updatedUserWallet -> + userWalletsListManager.save(updatedUserWallet, canOverride = true) + } + .doOnFailure { + tangemSdkManager.changeDisplayedCardIdNumbersCount( + scanResponse = userWalletsListManager.selectedUserWalletSync?.scanResponse, + ) } } - private fun removeWallets(walletIdsToRemove: List, state: WalletSelectorState) { + private fun deleteWallets(userWalletsIds: List, state: WalletSelectorState) { Analytics.send(MyWallets.Button.DeleteWalletTapped) scope.launch { - when (walletIdsToRemove.size) { + when (userWalletsIds.size) { state.wallets.size -> clearUserWallets() - else -> removeUserWallets(walletIdsToRemove, state) + else -> deleteUserWallets( + userWalletsIds = userWalletsIds, + currentSelectedWalletId = state.selectedWalletId, + ) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) @@ -206,11 +208,11 @@ internal class WalletSelectorMiddleware { } } - private fun renameWallet(walletId: String, newName: String) { + private fun renameWallet(userWalletId: UserWalletId, newName: String) { Analytics.send(MyWallets.Button.EditWalletTapped) scope.launch { - userWalletsListManager.get(walletId = UserWalletId(walletId)) + userWalletsListManager.get(userWalletId) .map { it.copy(name = newName) } .flatMap { userWalletsListManager.save(it, canOverride = true) } .doOnFailure { error -> @@ -219,6 +221,50 @@ internal class WalletSelectorMiddleware { } } + private fun refreshUserWalletsAmounts() { + scope.launch { + walletStoresManager.updateAmounts( + userWallets = userWalletsListManager.userWallets.first(), + ) + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + } + } + } + + private suspend fun clearUserWallets(): CompletionResult { + return userWalletsListManager.clear() + .flatMap { walletStoresManager.clear() } + .flatMap { tangemSdkManager.clearSavedUserCodes() } + .doOnSuccess { + // !!! Workaround !!! + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + delay(timeMillis = 280) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) + } + } + + private suspend fun deleteUserWallets( + userWalletsIds: List, + currentSelectedWalletId: UserWalletId?, + ): CompletionResult { + return userWalletsListManager.delete(userWalletsIds) + .flatMap { walletStoresManager.delete(userWalletsIds) } + .flatMap { deleteAccessCodes(userWalletsIds) } + .doOnSuccess { + val selectedWallet = userWalletsListManager.selectedUserWalletSync + + when { + selectedWallet == null -> { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + } + currentSelectedWalletId != selectedWallet.walletId -> { + store.onUserWalletSelected(selectedWallet) + } + } + } + } + private suspend inline fun scanCardInternal( crossinline onCardScanned: suspend (ScanResponse) -> Unit, ) { @@ -236,37 +282,13 @@ internal class WalletSelectorMiddleware { ) } - private suspend fun clearUserWallets(): CompletionResult { - return userWalletsListManager.clear() - .flatMap { walletStoresManager.clear() } - .doOnSuccess { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) - } - } + private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { + val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() + .asSequence() + .filter { it.walletId in userWalletsIds } + .flatMap { it.cardsInWallet } - private suspend fun removeUserWallets( - walletIdsToRemove: List, - state: WalletSelectorState, - ): CompletionResult { - val prevSelectedWalletId = state.selectedWalletId - return userWalletsListManager.delete(walletIdsToRemove.map { UserWalletId(it) }) - .flatMap { walletStoresManager.delete(walletIdsToRemove) } - .doOnSuccess { - val selectedWallet = userWalletsListManager.selectedUserWalletSync ?: return@doOnSuccess - val isSelectedWalletRemoved = prevSelectedWalletId != selectedWallet.walletId.stringValue - - if (isSelectedWalletRemoved) { - updateAccessCodeRequestPolicy(selectedWallet) - store.onUserWalletSelected(selectedWallet) - } - } - } - - private fun updateAccessCodeRequestPolicy(userWallet: UserWallet) { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.hasAccessCode, - ) + return tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) } private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index 959c45c8d9..b64fe0a3ce 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -1,9 +1,7 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.domain.common.CardDTO -import com.tangem.tap.common.extensions.replaceByOrAdd import com.tangem.tap.common.redux.AppState -import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.UserWallet import org.rekotlin.Action @@ -21,7 +19,7 @@ internal object WalletSelectorReducer { wallets = action.userWallets.updateWalletsModels(state.wallets), ) is WalletSelectorAction.SelectedWalletChanged -> state.copy( - selectedWalletId = action.selectedWallet.walletId.stringValue, + selectedWalletId = action.selectedWallet.walletId, ) is WalletSelectorAction.IsLockedChanged -> state.copy( isLocked = action.isLocked, @@ -56,7 +54,6 @@ internal object WalletSelectorReducer { ) is WalletSelectorAction.WalletStoresChanged, is WalletSelectorAction.SelectWallet, - is WalletSelectorAction.UnlockWalletWithCard, is WalletSelectorAction.RemoveWallets, is WalletSelectorAction.RenameWallet, -> state @@ -66,7 +63,7 @@ internal object WalletSelectorReducer { private fun List.updateWalletsModels(prevWallets: List): List { return this.map { userWallet -> prevWallets - .find { it.id == userWallet.walletId.stringValue } + .find { it.id == userWallet.walletId } ?.let { it.copy( name = userWallet.name, @@ -77,7 +74,7 @@ internal object WalletSelectorReducer { } ?: with(userWallet) { UserWalletModel( - id = walletId.stringValue, + id = walletId, name = name, artworkUrl = artworkUrl, type = getType(), @@ -92,12 +89,17 @@ internal object WalletSelectorReducer { userWalletModel: UserWalletModel, ): List { return ArrayList(this).apply { - replaceByOrAdd(userWalletModel) { it.id == userWalletModel.id } + val index = indexOfFirst { it.id == userWalletModel.id } + if (index == -1) { + add(userWalletModel) + } else { + this[index] = userWalletModel + } } } private fun UserWallet.getType(prevType: UserWalletModel.Type? = null): UserWalletModel.Type { - return if (scanResponse.card.isMultiwalletAllowed) { + return if (isMultiCurrency) { UserWalletModel.Type.MultiCurrency( cardsInWallet = (scanResponse.card.backupStatus as? CardDTO.BackupStatus.Active) ?.cardCount?.inc() diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt index df6ec0867d..797cc04593 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorState.kt @@ -1,12 +1,13 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.common.core.TangemError +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency import org.rekotlin.StateType data class WalletSelectorState( val wallets: List = emptyList(), - val selectedWalletId: String? = null, + val selectedWalletId: UserWalletId? = null, val isLocked: Boolean = false, val fiatCurrency: FiatCurrency = FiatCurrency.Default, val isCardSavingInProgress: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt index 6694b8b663..e5dc088a55 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt @@ -31,8 +31,8 @@ internal fun WalletSelectorScreenState.updateWithNewState( return this.copy( multiCurrencyWallets = multiCurrencyWallets, singleCurrencyWallets = singleCurrencyWallets, - selectedWalletId = newState.selectedWalletId, - editingWalletsIds = editingWalletsIds.filter { it in walletsIds }, + selectedUserWalletId = newState.selectedWalletId, + editingUserWalletsIds = editingUserWalletsIds.filter { it in walletsIds }, isLocked = newState.isLocked, showUnlockProgress = newState.isUnlockInProgress, showAddCardProgress = newState.isCardSavingInProgress, @@ -70,7 +70,7 @@ private fun List.toUiModels( imageUrl = artworkUrl, balance = balance, isLocked = isLocked, - tokenName = type.blockchainName ?: "—", + tokenName = type.blockchainName, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index 717f82ed16..4db33980de 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -7,7 +7,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -39,18 +45,12 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment = emptyList(), val singleCurrencyWallets: List = emptyList(), - val selectedWalletId: String? = null, + val selectedUserWalletId: UserWalletId? = null, val isLocked: Boolean = false, - val editingWalletsIds: List = listOf(), + val editingUserWalletsIds: List = listOf(), val renameWalletDialog: RenameWalletDialog? = null, val showAddCardProgress: Boolean = false, val showUnlockProgress: Boolean = false, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index a7eaa3c4f4..1192b37c4a 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.walletSelector.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction import com.tangem.tap.features.walletSelector.redux.WalletSelectorState @@ -35,40 +36,38 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber { - store.dispatch(WalletSelectorAction.UnlockWalletWithCard(walletId)) + editingUserWalletsIds.isNotEmpty() && isWalletLocked(userWalletId, this) -> Unit + editingUserWalletsIds.isNotEmpty() && !editingUserWalletsIds.contains(userWalletId) -> { + editWallet(userWalletId) } - editingWalletsIds.isNotEmpty() && !editingWalletsIds.contains(walletId) -> { - editWallet(walletId) + editingUserWalletsIds.isNotEmpty() && editingUserWalletsIds.contains(userWalletId) -> { + cancelWalletEditing(userWalletId) } - editingWalletsIds.isNotEmpty() && editingWalletsIds.contains(walletId) -> { - cancelWalletEditing(walletId) - } - selectedWalletId != walletId -> { - store.dispatch(WalletSelectorAction.SelectWallet(walletId)) + selectedUserWalletId != userWalletId -> { + store.dispatch(WalletSelectorAction.SelectWallet(userWalletId)) } } } - fun walletLongClicked(walletId: String) = with(state.value) { - if (!isLocked && editingWalletsIds.isEmpty()) { - editWallet(walletId) + fun walletLongClicked(userWalletId: UserWalletId) = with(state.value) { + if (!isWalletLocked(userWalletId, this) && editingUserWalletsIds.isEmpty()) { + editWallet(userWalletId) } } fun cancelWalletsEditing() { stateInternal.update { prevState -> prevState.copy( - editingWalletsIds = emptyList(), + editingUserWalletsIds = emptyList(), ) } } fun renameWallet() = with(state.value) { - if (editingWalletsIds.isNotEmpty() && renameWalletDialog == null) { - val editedWalletId = editingWalletsIds.first() + if (editingUserWalletsIds.isNotEmpty() && renameWalletDialog == null) { + val editedWalletId = editingUserWalletsIds.first() val editedWallet = (multiCurrencyWallets + singleCurrencyWallets) .find { it.id == editedWalletId } @@ -80,7 +79,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber prevState.copy( renameWalletDialog = null, - editingWalletsIds = emptyList(), + editingUserWalletsIds = emptyList(), ) } }, @@ -103,8 +102,8 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber prevState.copy( - editingWalletsIds = prevState.editingWalletsIds + walletId, + editingUserWalletsIds = prevState.editingUserWalletsIds + userWalletId, ) } } - private fun cancelWalletEditing(walletId: String) { + private fun cancelWalletEditing(userWalletId: UserWalletId) { stateInternal.update { prevState -> prevState.copy( - editingWalletsIds = prevState.editingWalletsIds - walletId, + editingUserWalletsIds = prevState.editingUserWalletsIds - userWalletId, ) } } + private fun isWalletLocked(userWalletId: UserWalletId, state: WalletSelectorScreenState): Boolean = with(state) { + multiCurrencyWallets.find { it.id == userWalletId }?.isLocked + ?: singleCurrencyWallets.find { it.id == userWalletId }?.isLocked ?: isLocked + } + private fun subscribeToStoreChanges() { store.subscribe(this) { appState -> appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt index 2fab91adc1..65349386dc 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.walletSelector.ui.components +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem @@ -7,7 +8,7 @@ import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem internal object MockData { private val multiCurrencyUserWallet = MultiCurrencyUserWalletItem( - id = "wallet_1", + id = UserWalletId("wallet_1"), balance = UserWalletItem.Balance( amount = "6781.05 $", isLoading = false, @@ -20,7 +21,7 @@ internal object MockData { ) private val singleCurrencyUserWallet = SingleCurrencyUserWalletItem( - id = "wallet_4", + id = UserWalletId("wallet_4"), balance = UserWalletItem.Balance( amount = "6781.05 $", isLoading = false, @@ -34,11 +35,11 @@ internal object MockData { val state = WalletSelectorScreenState( multiCurrencyWallets = listOf( multiCurrencyUserWallet, - multiCurrencyUserWallet.copy(id = "wallet_2"), - multiCurrencyUserWallet.copy(id = "wallet_3", tokensCount = 2, cardsInWallet = 1), + multiCurrencyUserWallet.copy(id = UserWalletId("wallet_2")), + multiCurrencyUserWallet.copy(id = UserWalletId("wallet_3"), tokensCount = 2, cardsInWallet = 1), ), singleCurrencyWallets = listOf(singleCurrencyUserWallet), - selectedWalletId = multiCurrencyUserWallet.id, + selectedUserWalletId = multiCurrencyUserWallet.id, isLocked = false, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 6c57a43f00..7897e34ae6 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -1,8 +1,8 @@ package com.tangem.tap.features.walletSelector.ui.components +import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text @@ -23,61 +23,84 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SecondaryButtonIconRight -import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme -import com.tangem.tap.features.details.ui.cardsettings.resolveReference +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.features.walletSelector.ui.WalletSelectorScreenState -import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem import com.tangem.wallet.R +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletSelectorScreenContent( - modifier: Modifier = Modifier, state: WalletSelectorScreenState, - onWalletClick: (walletId: String) -> Unit, - onWalletLongClick: (walletId: String) -> Unit, + onWalletClick: (UserWalletId) -> Unit, + onWalletLongClick: (UserWalletId) -> Unit, onUnlockClick: () -> Unit, onAddCardClick: () -> Unit, onClearSelectedClick: () -> Unit, onEditSelectedWalletClick: () -> Unit, onDeleteSelectedWalletsClick: () -> Unit, ) { - Column(modifier = modifier) { - Header( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - editingWalletsIds = state.editingWalletsIds, - onClearSelectedClick = onClearSelectedClick, - onEditSelectedWalletClick = onEditSelectedWalletClick, - onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, - ) - Column( - modifier = Modifier - .verticalScroll( - state = rememberScrollState(), - ), - ) { - WalletsList( - multiCurrencyWallets = state.multiCurrencyWallets, - singleCurrencyWallets = state.singleCurrencyWallets, - selectedWalletId = state.selectedWalletId, - checkedWalletIds = state.editingWalletsIds, + LazyColumn { + stickyHeader { + Header( + editingWalletsIds = state.editingUserWalletsIds, + onClearSelectedClick = onClearSelectedClick, + onEditSelectedWalletClick = onEditSelectedWalletClick, + onDeleteSelectedWalletsClick = onDeleteSelectedWalletsClick, + ) + } + + item { + WalletsTitle(textResId = R.string.user_wallet_list_multi_header, wallets = state.multiCurrencyWallets) + } + + itemsIndexed( + items = state.multiCurrencyWallets, + key = { _, wallet -> wallet.id.stringValue }, + ) { _, wallet -> + + WalletItem( + wallet = wallet, + isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId }, + isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds }, onWalletClick = onWalletClick, onWalletLongClick = onWalletLongClick, ) - SpacerH24() + } + + item { + WalletsTitle(textResId = R.string.user_wallet_list_single_header, wallets = state.singleCurrencyWallets) + } + + itemsIndexed( + items = state.singleCurrencyWallets, + key = { _, wallet -> wallet.id.stringValue }, + ) { _, wallet -> + WalletItem( + wallet = wallet, + isSelected = remember(state.selectedUserWalletId) { wallet.id == state.selectedUserWalletId }, + isChecked = remember(state.editingUserWalletsIds) { wallet.id in state.editingUserWalletsIds }, + onWalletClick = onWalletClick, + onWalletLongClick = onWalletLongClick, + ) + } + + item { Footer( modifier = Modifier + .padding( + top = dimensionResource(id = R.dimen.spacing24), + bottom = dimensionResource(id = R.dimen.spacing16), + ) .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), isLocked = state.isLocked, @@ -86,25 +109,28 @@ internal fun WalletSelectorScreenContent( onUnlockClick = onUnlockClick, onAddCardClick = onAddCardClick, ) - SpacerH16() } } } @Composable private fun Header( - modifier: Modifier = Modifier, - editingWalletsIds: List, + editingWalletsIds: List, onClearSelectedClick: () -> Unit, onEditSelectedWalletClick: () -> Unit, onDeleteSelectedWalletsClick: () -> Unit, ) { val editingWalletsSize by rememberUpdatedState(newValue = editingWalletsIds.size) - val hasEditingWallets by remember { - derivedStateOf { editingWalletsSize > 0 } - } + val hasEditingWallets by remember { derivedStateOf { editingWalletsSize > 0 } } - Column(modifier = modifier) { + Column( + modifier = Modifier + .fillMaxWidth() + .background( + color = TangemTheme.colors.background.plain, + shape = TangemTheme.shapes.bottomSheet, + ), + ) { Hand() Box( modifier = Modifier @@ -114,7 +140,9 @@ private fun Header( ) { if (hasEditingWallets) { EditWalletsBar( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), editingWalletsSize = editingWalletsSize, onClearSelectedClick = onClearSelectedClick, onEditSelectedWalletClick = onEditSelectedWalletClick, @@ -132,44 +160,15 @@ private fun Header( } } -@OptIn(ExperimentalFoundationApi::class) @Composable -private fun WalletsList( - modifier: Modifier = Modifier, - multiCurrencyWallets: List, - singleCurrencyWallets: List, - selectedWalletId: String?, - checkedWalletIds: List, - onWalletClick: (walletId: String) -> Unit, - onWalletLongClick: (walletId: String) -> Unit, -) { - Column(modifier = modifier) { - val walletsSection = @Composable { wallets: List -> - wallets.forEachIndexed { index, wallet -> - if (index == 0) { - Text( - modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), - text = wallet.headerText.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - ) - } - WalletItem( - modifier = Modifier - .combinedClickable( - onClick = { onWalletClick(wallet.id) }, - onLongClick = { onWalletLongClick(wallet.id) }, - ) - .padding(all = TangemTheme.dimens.spacing16), - wallet = wallet, - isSelected = wallet.id == selectedWalletId, - isChecked = wallet.id in checkedWalletIds, - ) - } - } - - walletsSection(multiCurrencyWallets) - walletsSection(singleCurrencyWallets) +private fun WalletsTitle(@StringRes textResId: Int, wallets: List<*>) { + if (wallets.isNotEmpty()) { + Text( + modifier = Modifier.padding(start = TangemTheme.dimens.spacing16), + text = stringResource(id = textResId), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) } } @@ -278,11 +277,6 @@ private fun WalletSelectorScreenContentSample( .background(color = TangemTheme.colors.background.primary), ) { WalletSelectorScreenContent( - modifier = Modifier - .background( - color = TangemTheme.colors.background.plain, - shape = TangemTheme.shapes.bottomSheet, - ), state = MockData.state.copy(isLocked = true), onWalletClick = { /* no-op */ }, onWalletLongClick = { /* no-op */ }, @@ -320,13 +314,8 @@ private fun WalletSelectorScreenContent_EditWallets_Sample( .background(TangemTheme.colors.background.primary), ) { WalletSelectorScreenContent( - modifier = Modifier - .background( - color = TangemTheme.colors.background.plain, - shape = TangemTheme.shapes.bottomSheet, - ), state = MockData.state - .copy(editingWalletsIds = listOf(MockData.state.multiCurrencyWallets[2].id)), + .copy(editingUserWalletsIds = listOf(MockData.state.multiCurrencyWallets[2].id)), onWalletClick = { /* no-op */ }, onWalletLongClick = { /* no-op */ }, onUnlockClick = { /* no-op */ }, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index 8ad4d7a2fc..3b6f021dba 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -1,7 +1,9 @@ package com.tangem.tap.features.walletSelector.ui.components +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon @@ -26,6 +28,7 @@ import com.tangem.core.ui.components.SpacerH2 import com.tangem.core.ui.components.SpacerW6 import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.res.TangemTheme +import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.compose.TangemTypography import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem @@ -33,15 +36,23 @@ import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem import com.tangem.wallet.R import com.valentinilk.shimmer.shimmer +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletItem( - modifier: Modifier = Modifier, wallet: UserWalletItem, isSelected: Boolean, isChecked: Boolean, + onWalletClick: (UserWalletId) -> Unit, + onWalletLongClick: (UserWalletId) -> Unit, ) { Row( - modifier = modifier, + modifier = Modifier + .combinedClickable( + onClick = { onWalletClick(wallet.id) }, + onLongClick = { onWalletLongClick(wallet.id) }, + ) + .height(72.dp) + .padding(all = TangemTheme.dimens.spacing16), verticalAlignment = Alignment.CenterVertically, ) { WalletCardImage( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt index c546cd131e..ddd7518ca6 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt @@ -1,21 +1,14 @@ package com.tangem.tap.features.walletSelector.ui.model -import com.tangem.tap.features.details.ui.cardsettings.TextReference -import com.tangem.wallet.R +import com.tangem.domain.common.util.UserWalletId internal sealed interface UserWalletItem { - val id: String + val id: UserWalletId val name: String val imageUrl: String val balance: Balance val isLocked: Boolean - val headerText: TextReference - get() = when (this) { - is MultiCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_multi_header) - is SingleCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_single_header) - } - data class Balance( val amount: String, val isLoading: Boolean, @@ -23,7 +16,7 @@ internal sealed interface UserWalletItem { } internal data class MultiCurrencyUserWalletItem( - override val id: String, + override val id: UserWalletId, override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, @@ -33,7 +26,7 @@ internal data class MultiCurrencyUserWalletItem( ) : UserWalletItem internal data class SingleCurrencyUserWalletItem( - override val id: String, + override val id: UserWalletId, override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index fb5b8a626d..7b0f37e36e 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -59,11 +59,6 @@ internal class WelcomeMiddleware { } .doOnSuccess { selectedUserWallet -> if (selectedUserWallet != null) { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - selectedUserWallet.hasAccessCode, - ) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) store.onUserWalletSelected(selectedUserWallet) @@ -78,8 +73,7 @@ internal class WelcomeMiddleware { scanCardInternal { scanResponse -> val userWallet = UserWalletBuilder(scanResponse).build() - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) - userWalletsListManager.unlockWithCard(userWallet) + userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error)) } @@ -100,6 +94,9 @@ internal class WelcomeMiddleware { private suspend inline fun scanCardInternal( crossinline onCardScanned: suspend (ScanResponse) -> Unit, ) { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + ) ScanCardProcessor.scan( onSuccess = { scanResponse -> scope.launch { onCardScanned(scanResponse) } diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index eaf21b8d83..d419aafffc 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -51,6 +51,18 @@ class PreferencesStorage(applicationContext: Application) { putBoolean(SAVE_ACCESS_CODES_KEY, value) } + var wasApplicationStopped: Boolean + get() = preferences.getBoolean(APPLICATION_STOPPED_KEY, false) + set(value) = preferences.edit { + putBoolean(APPLICATION_STOPPED_KEY, value) + } + + var shouldOpenWelcomeScreenOnResume: Boolean + get() = preferences.getBoolean(OPEN_WELCOME_ON_RESUME_KEY, false) + set(value) = preferences.edit { + putBoolean(OPEN_WELCOME_ON_RESUME_KEY, value) + } + fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1) fun saveTwinsOnboardingShown() { @@ -74,6 +86,8 @@ class PreferencesStorage(applicationContext: Application) { private const val SAVE_WALLET_DIALOG_SHOWN_KEY = "saveUserWalletShown" private const val SAVE_USER_WALLETS_KEY = "saveUserWallets" private const val SAVE_ACCESS_CODES_KEY = "saveAccessCodes" + private const val APPLICATION_STOPPED_KEY = "applicationStopped" + private const val OPEN_WELCOME_ON_RESUME_KEY = "openWelcomeOnResume" } } diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt index 2b4d40e0bb..7be322fc1e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt @@ -108,7 +108,7 @@ class DerivationManagerImpl( val updatedScanResponse = scanResponse.copy( derivedKeys = updatedDerivedKeys, ) - appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse)) + appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse)) delay(DELAY_SDK_DIALOG_CLOSE) onSuccess(updatedScanResponse) } diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index ab388fa0cd..b3d1b54730 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -5,7 +5,7 @@ android:id="@+id/coordinator_wallet" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/backgroundLightGray" + android:background="@color/background_secondary" android:orientation="vertical"> diff --git a/app/src/main/res/layout/fragment_wallet_details.xml b/app/src/main/res/layout/fragment_wallet_details.xml index 1467361b47..f1cdf9a73a 100644 --- a/app/src/main/res/layout/fragment_wallet_details.xml +++ b/app/src/main/res/layout/fragment_wallet_details.xml @@ -5,7 +5,7 @@ android:id="@+id/coordinator_wallet" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/backgroundLightGray" + android:background="@color/background_secondary" android:orientation="vertical"> @@ -105,27 +105,27 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/l_wallet_details" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/l_wallet_details" /> + android:id="@+id/barrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierDirection="bottom" + app:constraint_referenced_ids="l_wallet_details" /> + android:id="@+id/row_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="28dp" + android:layout_marginBottom="32dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + app:layout_constraintVertical_bias="1" + app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" + app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/layout/item_currency_wallet_content.xml b/app/src/main/res/layout/item_currency_wallet_content.xml index 18249f4c63..aa767d069c 100644 --- a/app/src/main/res/layout/item_currency_wallet_content.xml +++ b/app/src/main/res/layout/item_currency_wallet_content.xml @@ -14,77 +14,77 @@ app:layout_constraintGuide_percent="0.5" /> + android:id="@+id/tv_currency" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:ellipsize="end" + android:maxLines="1" + android:textColor="@color/text_primary_1" + android:textSize="16sp" + android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@id/guideline_horizontal" + app:layout_constraintEnd_toStartOf="@+id/tv_amount_fiat" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:lineHeight="24dp" + tools:text="Binance Smart Chain Optimal" /> + android:id="@+id/tv_amount_fiat" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textAlignment="viewEnd" + android:textColor="@color/text_primary_1" + android:textSize="16sp" + android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@id/guideline_horizontal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:lineHeight="24dp" + tools:text="1230.43 $" /> + android:id="@+id/tv_amount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textAlignment="viewEnd" + android:textColor="@color/text_tertiary" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline_horizontal" + app:lineHeight="20dp" + tools:text="2.002134 BTC" /> + android:id="@+id/tv_status" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:textColor="@color/text_tertiary" + android:textSize="12sp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline_horizontal" + app:lineHeight="20dp" + tools:text="Unreachable..." /> + android:id="@+id/tv_exchange_rate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="1" + android:textColor="@color/text_secondary" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline_horizontal" + app:lineHeight="20dp" + tools:text="46 908 $" /> diff --git a/app/src/main/res/layout/layout_card_total_balance.xml b/app/src/main/res/layout/layout_card_total_balance.xml index 3f47e6b612..ed26db5407 100644 --- a/app/src/main/res/layout/layout_card_total_balance.xml +++ b/app/src/main/res/layout/layout_card_total_balance.xml @@ -12,8 +12,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="18dp" android:layout_marginEnd="16dp" - android:layout_marginBottom="18dp" - android:animateLayoutChanges="true"> + android:layout_marginBottom="18dp"> - + app:layout_constraintTop_toBottomOf="@id/tv_title" + app:veilLayout_baseColor="@color/lightGray0" + app:veilLayout_highlightColor="@color/lightGray1" + app:veilLayout_layout="@layout/card_total_balance_shimmer" + app:veilLayout_radius="4dp" + app:veilLayout_shimmerEnable="true" + app:veilLayout_veiled="true"> - - - - + tools:text="22 325.40 $" + tools:visibility="visible" /> + diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt index c1375a7b76..54ee97cd6d 100644 --- a/buildSrc/src/main/java/Dependency.kt +++ b/buildSrc/src/main/java/Dependency.kt @@ -66,6 +66,7 @@ object Library { const val zendeskMessaging = "com.zendesk:messaging:" + Versions.zendeskMessaging const val zxingQrBarcodeScanner = "me.dm7.barcodescanner:zxing:" + Versions.zxingQrBarcodeScanner const val zxingQrCore = "com.google.zxing:core:" + Versions.zxingQrCode + const val mviCoreWatcher = "com.github.badoo.mvicore:mvicore-diff:" + Versions.mviCore } object Tangem { diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index e5985c63df..869eaf8f0b 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -55,12 +55,13 @@ object Versions { const val zendeskMessaging = "5.2.4" const val zxingQrBarcodeScanner = "1.9.8" const val zxingQrCode = "3.3.3" + const val mviCore = "1.3.1" // endregion Other libraries // region Tangem const val tangemBlockchainSdk = "develop-141" - const val tangemCardSgk = "develop-173" + const val tangemCardSgk = "develop-178" // endregion Tangem // region Testing diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml index bb7027a32f..046904ddaf 100644 --- a/core/res/src/main/res/values-de/strings_final.xml +++ b/core/res/src/main/res/values-de/strings_final.xml @@ -229,4 +229,5 @@ biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml index bacd616a66..4b3d386d9b 100644 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ b/core/res/src/main/res/values-fr/strings_final.xml @@ -229,4 +229,5 @@ biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml index 34a7c383db..53bd5f260c 100644 --- a/core/res/src/main/res/values-it/strings_final.xml +++ b/core/res/src/main/res/values-it/strings_final.xml @@ -229,4 +229,5 @@ biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml index 14e74e4d88..c56fea1dcc 100644 --- a/core/res/src/main/res/values-ru/strings_final.xml +++ b/core/res/src/main/res/values-ru/strings_final.xml @@ -229,4 +229,5 @@ биометрией Включите биометрическую аутентификацию Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App + Отсканируйте карту diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml index d41e3ca81f..673ced5d90 100644 --- a/core/res/src/main/res/values/strings_final.xml +++ b/core/res/src/main/res/values/strings_final.xml @@ -229,4 +229,5 @@ biometrics Enable biometric authentication Go to settings to enable biometric authentication in the Tangem App + Scan the card diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt index 2a7d4c0864..35dab6bca0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeBottomSheetFragment.kt @@ -18,7 +18,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.tangem.core.ui.R -import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.res.TangemTheme abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment() { @@ -52,13 +51,6 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme return ComposeView(context).apply { setContent { TangemTheme { - val backgroundColor = TangemTheme.colors.background.plain - SystemBarsEffect { - setSystemBarsColor( - color = backgroundColor, - ) - } - ScreenContent( modifier = Modifier .fillMaxWidth() @@ -66,7 +58,7 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme if (expandedHeightFraction != null) it.fillMaxHeight(expandedHeightFraction!!) else it } .background( - color = backgroundColor, + color = TangemTheme.colors.background.plain, shape = TangemTheme.shapes.bottomSheet, ), state = provideState().value, diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt new file mode 100644 index 0000000000..0de62569bb --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/FragmentExt.kt @@ -0,0 +1,14 @@ +package com.tangem.core.ui.fragments + +import android.view.WindowManager +import androidx.annotation.ColorRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment + +fun Fragment.setStatusBarColor(@ColorRes colorResId: Int) { + with(requireActivity().window) { + clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + statusBarColor = ContextCompat.getColor(requireContext(), colorResId) + } +} \ No newline at end of file From 210c83d5d5ef96e399dfd45bff9a079634665e8b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 14:11:02 +0300 Subject: [PATCH 158/176] Updated on 2026-08-14 --- .../com/tangem/tap/domain/TangemSdkManager.kt | 5 ++ .../redux/WalletSelectorMiddleware.kt | 68 +++++++++++-------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 01350c6c63..be8e4f65b2 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -234,6 +234,11 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } + fun useBiometricsForAccessCode(): Boolean { + val policy = tangemSdk.config.userCodeRequestPolicy + return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode + } + companion object { val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index b6b31419de..c7d8495f6a 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -19,6 +19,7 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -131,21 +132,45 @@ internal class WalletSelectorMiddleware { private fun addWallet() = scope.launch { Analytics.send(MyWallets.Button.ScanNewCard) - scanCardInternal { scanResponse -> - val userWallet = UserWalletBuilder(scanResponse).build() + val prevUseBiometricsForAccessCode = tangemSdkManager.useBiometricsForAccessCode() - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - } - .doOnSuccess { - Analytics.send(MyWallets.CardWasScanned) + // Update access code policy for access code saving when a card was scanned + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + ) + ScanCardProcessor.scan( + onSuccess = { scanResponse -> + saveUserWalletAndPopBackToWalletScreen(scanResponse) + .doOnFailure { error -> + // Rollback policy if card saving was failed + tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + } + }, + onFailure = { error -> + // Rollback policy if card scanning was failed + tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + }, + onWalletNotCreated = { + // No need to rollback policy, continue with the policy set before the card scan + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + }, + ) + } - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) - } - } + private suspend fun saveUserWalletAndPopBackToWalletScreen(scanResponse: ScanResponse): CompletionResult { + val userWallet = UserWalletBuilder(scanResponse).build() + + return userWalletsListManager.save(userWallet) + .doOnSuccess { + Analytics.send(MyWallets.CardWasScanned) + + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) + } } private fun selectWallet(userWalletId: UserWalletId) { @@ -265,23 +290,6 @@ internal class WalletSelectorMiddleware { } } - private suspend inline fun scanCardInternal( - crossinline onCardScanned: suspend (ScanResponse) -> Unit, - ) { - ScanCardProcessor.scan( - onSuccess = { - onCardScanned(it) - }, - onFailure = { error -> - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - }, - onWalletNotCreated = { - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo()) - }, - ) - } - private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() .asSequence() From a8776a7cb69c973c62a78d0510a2a42bb3a13b61 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 26 Dec 2022 18:11:01 +0300 Subject: [PATCH 159/176] Updated on 2026-08-14 --- .../tap/features/wallet/ui/WalletFragment.kt | 7 +++--- .../core/ui/utils/OneTouchClickListener.kt | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/OneTouchClickListener.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index c0bde194bf..8a67196de1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -16,6 +16,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import coil.load import coil.size.Scale import com.tangem.core.ui.fragments.setStatusBarColor +import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics @@ -110,9 +111,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber Unit) : View.OnClickListener { + + private var lastClickTimeMs: Long = 0L + + override fun onClick(v: View?) { + if (SystemClock.elapsedRealtime() - lastClickTimeMs > CLICK_DELAY_MS) { + lastClickTimeMs = SystemClock.elapsedRealtime() + action() + } + } + + companion object { + private const val CLICK_DELAY_MS = 500L + } +} \ No newline at end of file From 8fa38f4a7af06228b621100d42e77ccc3daeea4c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 11:30:21 +0300 Subject: [PATCH 160/176] Updated on 2026-08-14 --- .../com/tangem/tap/features/onboarding/OnboardingHelper.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 9b5c5e9958..775dca1ed3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -70,7 +70,10 @@ class OnboardingHelper { // then open save wallet screen tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { + store.onCardScanned(scanResponse) + delay(timeMillis = 1_200) + store.dispatchOnMain( SaveWalletAction.ProvideBackupInfo( scanResponse = scanResponse, From 6d6fb133cde4752508a7f8f6d0f5adcdbdb25710 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 17:59:57 +0300 Subject: [PATCH 161/176] Updated on 2026-08-14 --- .../DefaultWalletManagersRepository.kt | 6 +---- .../redux/middlewares/WalletMiddleware.kt | 2 +- .../redux/reducers/MultiWalletReducer.kt | 8 +++---- .../wallet/redux/reducers/WalletReducer.kt | 6 ++--- .../tap/features/wallet/ui/WalletViewModel.kt | 8 +++++-- .../redux/WalletSelectorMiddleware.kt | 24 ++++++++++++------- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index 3de7a3f6b1..6355f2f6d6 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -106,11 +106,7 @@ internal class DefaultWalletManagersRepository( override suspend fun delete(userWalletIds: List): CompletionResult = catching { walletManagersStorage.update { prevManagers -> - prevManagers.apply { - userWalletIds.forEach { userWalletId -> - remove(userWalletId) - } - } + prevManagers.filterKeys { it !in userWalletIds } as HashMap> } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 5dcd8b81bd..243ab5eab6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -305,7 +305,7 @@ class WalletMiddleware { val reduxWalletStores = wallStores.mapToReduxModels(state.isMultiwalletAllowed) store.dispatchOnMain( WalletAction.WalletStoresChanged.UpdateWalletStores( - reduxWalletStores = reduxWalletStores.toList(), + reduxWalletStores = reduxWalletStores, ), ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index b1e6cebbac..7cb35da559 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -31,7 +31,7 @@ class MultiWalletReducer { fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { - val walletStores: List = action.blockchains.mapNotNull { blockchain -> + val walletStores: List = action.blockchains.map { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) @@ -65,10 +65,10 @@ class MultiWalletReducer { ) } - val selectedCurrency = if (!state.isMultiwalletAllowed) { - walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency + val selectedCurrency = if (state.isMultiwalletAllowed) { + state.selectedCurrency } else { - state.selectedWalletData?.currency + walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency } state.copy( walletsStores = walletStores, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 254fe48e3b..d71909cfec 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -362,13 +362,13 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ) } is WalletAction.LoadData.Success -> { - val selectedCurrency = if (!newState.isMultiwalletAllowed) { + val selectedCurrency = if (newState.isMultiwalletAllowed) { + newState.selectedCurrency + } else { newState.walletsStores.firstOrNull() ?.walletsData ?.firstOrNull() ?.currency - } else { - newState.selectedWalletData?.currency } newState = newState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt index df0b0537ed..a9fc0ad8cf 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt @@ -8,8 +8,10 @@ import com.tangem.tap.store import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -21,8 +23,10 @@ internal class WalletViewModel : ViewModel() { private fun bootstrapSelectedWalletStoresChanges() { userWalletsListManager.selectedUserWallet - .flatMapLatest { selectedWallet -> - walletStoresManager.get(selectedWallet.walletId) + .map { it.walletId } + .distinctUntilChanged() + .flatMapLatest { selectedUserWalletId -> + walletStoresManager.get(selectedUserWalletId) } .onEach { walletStores -> store.dispatch(WalletAction.WalletStoresChanged(walletStores)) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index c7d8495f6a..cc6642743f 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -277,26 +277,32 @@ internal class WalletSelectorMiddleware { .flatMap { walletStoresManager.delete(userWalletsIds) } .flatMap { deleteAccessCodes(userWalletsIds) } .doOnSuccess { - val selectedWallet = userWalletsListManager.selectedUserWalletSync + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync when { - selectedWallet == null -> { + selectedUserWallet == null -> { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } - currentSelectedWalletId != selectedWallet.walletId -> { - store.onUserWalletSelected(selectedWallet) + + currentSelectedWalletId != selectedUserWallet.walletId -> { + store.onUserWalletSelected(selectedUserWallet) } } } } private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { - val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() - .asSequence() - .filter { it.walletId in userWalletsIds } - .flatMap { it.cardsInWallet } + val cardsIds = userWalletsListManager.userWallets.firstOrNull() + ?.asSequence() + ?.filter { it.walletId in userWalletsIds } + ?.flatMap { it.cardsInWallet } + ?.toSet() - return tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) + return if (cardsIds.isNullOrEmpty()) { + CompletionResult.Success(Unit) + } else { + tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) + } } private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( From cc57668491f01d7c28f5cd9e33844daf204b669b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 19:01:21 +0300 Subject: [PATCH 162/176] Updated on 2026-08-14 --- .../tap/features/wallet/redux/reducers/WalletReducer.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index d71909cfec..2505702c20 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -20,7 +20,6 @@ import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.features.wallet.redux.Artwork @@ -36,7 +35,6 @@ import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.proxy.AppStateHolder -import com.tangem.tap.store import org.rekotlin.Action import timber.log.Timber import java.math.BigDecimal @@ -344,8 +342,9 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS card.settings.isBackupAllowed && card.backupStatus == CardDTO.BackupStatus.NoBackup, walletCardsCount = card.findCardsCount(), + walletsStores = newState.walletsStores, totalBalance = if (isMultiCurrency) { - TotalBalance(ProgressState.Loading, BigDecimal.ZERO, store.state.globalState.appCurrency) + newState.totalBalance } else { null }, From de6b0af2281de45df3ac84370d7fdfee4f392768 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 11:10:34 +0300 Subject: [PATCH 163/176] Updated on 2026-08-14 --- .../converters/BasicEventConverter.kt | 8 ++- .../common/feedback/AdditionalFeedbackInfo.kt | 4 +- .../com/tangem/tap/domain/model/UserWallet.kt | 20 ------ .../model/builders/UserWalletBuilder.kt | 23 +++--- .../model/builders/UserWalletIdBuilder.kt | 70 +++++++++++++++++++ .../tap/domain/tokens/UserTokensRepository.kt | 16 +++-- .../BiometricUserWalletsListManager.kt | 4 +- .../model/UserWalletEncryptionKey.kt | 2 +- ...erWalletsSensitiveInformationRepository.kt | 2 +- .../utils/UserWalletEncyptionKeyCalculator.kt | 19 ++--- .../details/redux/DetailsMiddleware.kt | 32 ++++----- .../tap/features/home/redux/HomeMiddleware.kt | 2 +- .../twins/redux/TwinCardsMiddleware.kt | 4 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 2 +- .../redux/WalletSelectorMiddleware.kt | 2 + .../welcome/redux/WelcomeMiddleware.kt | 2 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 9 ++- .../tangem/domain/common/util/UserWalletId.kt | 16 +---- 18 files changed, 147 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt rename domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt => app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt (55%) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 45d55aac2a..52fa450434 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -3,11 +3,11 @@ package com.tangem.tap.common.analytics.converters import com.tangem.common.Converter import com.tangem.common.extensions.isZero import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.filters.BasicTopUpFilter import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletState @@ -29,7 +29,9 @@ class BasicSignInEventConverter( currency = cardCurrency, batch = scanResponse.card.batchId, ).apply { - filterData = scanResponse.card.userWalletId.stringValue + filterData = UserWalletIdBuilder.scanResponse(scanResponse) + .build() + ?.stringValue } } } @@ -43,7 +45,7 @@ class BasicTopUpEventConverter( val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null val data = BasicTopUpFilter.Data( - walletId = scanResponse.card.userWalletId.stringValue, + walletId = UserWalletIdBuilder.scanResponse(scanResponse).build()?.stringValue ?: "", cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), ) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 16e1bb559a..589597238f 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -10,8 +10,8 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.Address import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder class AdditionalFeedbackInfo { class EmailWalletInfo( @@ -54,7 +54,7 @@ class AdditionalFeedbackInfo { cardFirmwareVersion = data.card.firmwareVersion.stringValue cardIssuer = data.card.issuer.name signedHashesCount = formatSignedHashes(data.card.wallets) - userWalletId = data.card.userWalletId.stringValue + userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: "" } fun setWalletsInfo(walletManagers: List) { diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 4dec0df7d1..a76fbb9d1a 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -1,6 +1,5 @@ package com.tangem.tap.domain.model -import com.tangem.common.extensions.toHexString import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId @@ -33,23 +32,4 @@ data class UserWallet( val isLocked: Boolean get() = scanResponse.card.wallets.isEmpty() -} - -/** - * !!! Workaround !!! - * - * Calculate same [UserWalletId] for twins instead - * - * TODO: Remove after [REDACTED_JIRA] - * */ -fun UserWallet.isTwinnedWith(other: UserWallet): Boolean { - if (!scanResponse.isTangemTwins() || !other.scanResponse.isTangemTwins()) return false - if (scanResponse.secondTwinPublicKey == null || other.scanResponse.secondTwinPublicKey == null) return false - if (other.scanResponse.secondTwinPublicKey == scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { - return true - } - if (scanResponse.secondTwinPublicKey == other.scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { - return true - } - return false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 6e4d834d38..c993194983 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -8,7 +8,6 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper -import com.tangem.domain.common.util.userWalletId import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.attestation.TangemApi import com.tangem.tap.domain.model.UserWallet @@ -49,16 +48,20 @@ class UserWalletBuilder( } } - suspend fun build(): UserWallet { + suspend fun build(): UserWallet? { return with(scanResponse) { - UserWallet( - walletId = card.userWalletId, - name = userWalletName, - artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), - cardsInWallet = backupCardsIds.plus(card.cardId), - scanResponse = this, - isMultiCurrency = isMultiCurrency, - ) + UserWalletIdBuilder.scanResponse(scanResponse) + .build() + ?.let { + UserWallet( + walletId = it, + name = userWalletName, + artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), + cardsInWallet = backupCardsIds.plus(card.cardId), + scanResponse = this, + isMultiCurrency = isMultiCurrency, + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt new file mode 100644 index 0000000000..1fdef7c37c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt @@ -0,0 +1,70 @@ +package com.tangem.tap.domain.model.builders + +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.Secp256k1 +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.ProductType +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.common.extensions.calculateHmacSha256 +import com.tangem.domain.common.util.UserWalletId + +class UserWalletIdBuilder private constructor( + private val publicKey: ByteArray?, + private val pairTwinPublicKey: ByteArray? = null, +) { + fun build(): UserWalletId? { + val seed = if (publicKey != null) { + if (pairTwinPublicKey != null) { + Secp256k1.sum(publicKey, pairTwinPublicKey) + } else { + publicKey + } + } else null + + return seed?.let { + UserWalletId(value = calculateUserWalletId(it)) + } + } + + private fun calculateUserWalletId(seed: ByteArray?): ByteArray? { + val message = MESSAGE_FOR_WALLET_ID.toByteArray() + val keyHash = seed?.calculateSha256() + + return if (keyHash != null) { + message.calculateHmacSha256(keyHash) + } else null + } + + companion object { + private const val MESSAGE_FOR_WALLET_ID = "UserWalletID" + + @Throws(IllegalArgumentException::class) + fun card(card: CardDTO): UserWalletIdBuilder { + require(!card.isTangemTwins) { + "For twin cards use scanResponse to ID calculation" + } + + return UserWalletIdBuilder(findPublicKey(card.wallets)) + } + + fun scanResponse(scanResponse: ScanResponse): UserWalletIdBuilder { + return UserWalletIdBuilder( + publicKey = findPublicKey(scanResponse.card.wallets), + pairTwinPublicKey = when (scanResponse.productType) { + ProductType.Twins -> scanResponse.secondTwinPublicKey?.hexToBytes() + ProductType.Note, + ProductType.Wallet, + ProductType.SaltPay, + -> null + }, + ) + } + + private fun findPublicKey(wallets: List): ByteArray? { + return wallets.firstOrNull() + ?.publicKey + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index 6fb7de9ad7..b3efb6ce5b 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -6,9 +6,9 @@ import com.tangem.common.services.Result import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.datasource.api.tangemTech.UserTokensResponse import com.tangem.domain.common.CardDTO -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.AndroidFileReader import com.tangem.tap.domain.NoDataError +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.models.Currency @@ -24,7 +24,7 @@ class UserTokensRepository( private val networkService: UserTokensNetworkService, ) { suspend fun getUserTokens(card: CardDTO): List { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return emptyList() if (DemoHelper.isDemoCardId(card.cardId)) { return loadTokensOffline(card, userId).ifEmpty { loadDemoCurrencies() } } @@ -47,14 +47,14 @@ class UserTokensRepository( } suspend fun saveUserTokens(card: CardDTO, tokens: List) { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return val userTokens = tokens.toUserTokensResponse() networkService.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) } suspend fun removeUserTokens(card: CardDTO) { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return val userTokens = emptyList().toUserTokensResponse() networkService.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) @@ -70,7 +70,7 @@ class UserTokensRepository( } suspend fun loadBlockchainsToDerive(card: CardDTO): List { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return emptyList() val blockchainNetworks = loadTokensOffline(card, userId).toBlockchainNetworks() if (DemoHelper.isDemoCardId(card.cardId)) { @@ -103,6 +103,7 @@ class UserTokensRepository( coroutineScope { launch { networkService.saveUserTokens(userId = userId, tokens = userTokens) } } tokens } + else -> { val tokens = storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) tokens.distinct() @@ -114,6 +115,11 @@ class UserTokensRepository( return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card) } + private fun getUserWalletId(card: CardDTO): String? { + return UserWalletIdBuilder.card(card).build() + ?.stringValue + } + companion object { const val SORT_DEFAULT_VALUE = "manual" const val GROUP_DEFAULT_VALUE = "none" diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index f7781ca770..648323a70c 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -3,7 +3,6 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.model.isTwinnedWith import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey @@ -89,8 +88,7 @@ internal class BiometricUserWalletsListManager( } else { val isWalletSaved = state.value.userWallets .any { - // Workaround, check [UserWallet.isTwinnedWith] - it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) + it.walletId == userWallet.walletId || it.cardsInWallet.contains(userWallet.cardId) } if (isWalletSaved) { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt index cdf1edbdfb..a5c20a7790 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -2,8 +2,8 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.JsonClass import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.userWalletList.utils.encryptionKey @JsonClass(generateAdapter = true) internal data class UserWalletEncryptionKey( diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 5918312b6e..158dc0db2b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -9,13 +9,13 @@ import com.tangem.common.catching import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.sensitiveInformation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt similarity index 55% rename from domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt rename to app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt index 4dc70fd2bd..3ecfb70521 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt @@ -1,24 +1,27 @@ -package com.tangem.domain.common.util +package com.tangem.tap.domain.userWalletList.utils import com.tangem.common.extensions.calculateSha256 import com.tangem.domain.common.CardDTO import com.tangem.domain.common.extensions.calculateHmacSha256 -val CardDTO.userWalletId: UserWalletId - get() = UserWalletId(findWalletPublicKey(wallets)) +internal val CardDTO.encryptionKey: ByteArray + @Throws(IllegalArgumentException::class) + get() { + val walletPublicKey = requireNotNull(findPublicKey(wallets)) { + "Wallet public key must not be null" + } -val CardDTO.encryptionKey: ByteArray - get() = findWalletPublicKey(wallets) - ?.let { calculateEncryptionKey(it) } - ?: error("Wallet ID not found") + return calculateEncryptionKey(walletPublicKey) + } private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() val keyHash = publicKey.calculateSha256() + return message.calculateHmacSha256(keyHash) } -private fun findWalletPublicKey(wallets: List): ByteArray? { +private fun findPublicKey(wallets: List): ByteArray? { return wallets.firstOrNull() ?.publicKey } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 319071642c..2c5df4e058 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -4,10 +4,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.common.extensions.toHexString import com.tangem.common.flatMap import com.tangem.domain.common.TapWorkarounds.isTangemTwins -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings @@ -20,6 +18,7 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction @@ -27,6 +26,7 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userTokensRepository import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import com.tangem.wallet.R @@ -79,20 +79,16 @@ class DetailsMiddleware { } DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanCard(allowRequestAccessCodeFromRepository = true) - .doOnSuccess { card -> - val isSameWallet = state.scanResponse?.card?.userWalletId - ?.equals(card.userWalletId) - ?: false + tangemSdkManager.scanProduct(userTokensRepository) + .doOnSuccess { scanResponse -> + val currentUserWalletId = state.scanResponse + ?.let { UserWalletIdBuilder.scanResponse(it).build() } + val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse) + .build() + val isSameWallet = currentUserWalletId == scannedUserWalletId - // !!! Workaround !!! - // TODO: Remove after [REDACTED_JIRA] - val isTwinned = card.wallets.firstOrNull()?.publicKey?.toHexString() - ?.equals(state.scanResponse?.secondTwinPublicKey) - ?: false - - if (isSameWallet || isTwinned) { - store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) + if (isSameWallet) { + store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(scanResponse.card)) } else { store.dispatchDialogShow( AppDialog.SimpleOkDialogRes( @@ -122,8 +118,10 @@ class DetailsMiddleware { is DetailsAction.ResetToFactory.Proceed -> { val card = store.state.detailsState.cardSettingsState?.card ?: return scope.launch { + val userWalletId = UserWalletIdBuilder.card(card).build() + tangemSdkManager.resetToFactorySettings(card.cardId) - .flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) } + .flatMap { userWalletsListManager.delete(listOfNotNull(userWalletId)) } .flatMap { tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) } .doOnSuccess { Analytics.send(Settings.CardSettings.FactoryResetFinished()) @@ -275,7 +273,7 @@ class DetailsMiddleware { private suspend fun saveCurrentWallet(state: DetailsState) { val scanResponse = state.scanResponse ?: return - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return userWalletsListManager.save(userWallet) .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 0c5b990b06..fdacaa5bd1 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -93,7 +93,7 @@ private fun readCard() = scope.launch { onSuccess = { scanResponse -> scope.launch { if (preferencesStorage.shouldSaveUserWallets) { - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return@launch userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index ae60d9f637..b5bd3c7504 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -6,7 +6,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -25,6 +24,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.wallet.models.Currency @@ -156,7 +156,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { TwinCardsStep.CreateFirstWallet -> { scope.launch { userWalletsListManager.delete( - listOf(getScanResponse().card.userWalletId), + listOfNotNull(UserWalletIdBuilder.scanResponse(getScanResponse()).build()), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 106ebfa9af..60bc2e076e 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -88,7 +88,7 @@ internal class SaveWalletMiddleware { scope.launch { val userWallet = UserWalletBuilder(scanResponse) .backupCardsIds(state.backupInfo?.backupCardsIds) - .build() + .build() ?: return@launch val isFirstSavedWallet = !userWalletsListManager.hasSavedUserWallets diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index cc6642743f..c1f24294f4 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap @@ -162,6 +163,7 @@ internal class WalletSelectorMiddleware { private suspend fun saveUserWalletAndPopBackToWalletScreen(scanResponse: ScanResponse): CompletionResult { val userWallet = UserWalletBuilder(scanResponse).build() + ?: return CompletionResult.Failure(TangemSdkError.WalletIsNotCreated()) return userWalletsListManager.save(userWallet) .doOnSuccess { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 7b0f37e36e..e7cb2e01e7 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -71,7 +71,7 @@ internal class WelcomeMiddleware { private fun proceedWithCard(state: WelcomeState) = scope.launch { scanCardInternal { scanResponse -> - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return@scanCardInternal userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 8856cdc80d..e12ca9b9be 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -8,12 +8,12 @@ import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.domain.common.util.userWalletId import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.NativeToken import com.tangem.lib.crypto.models.NonNativeToken import com.tangem.tap.domain.extensions.makeWalletManagerForApp +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import org.rekotlin.Action @@ -54,7 +54,12 @@ class UserWalletManagerImpl( } override fun getWalletId(): String { - return appStateHolder.getActualCard()?.userWalletId?.stringValue ?: "" + return appStateHolder.getActualCard()?.let { + UserWalletIdBuilder.card(it) + .build() + ?.stringValue + } + ?: "" } override suspend fun isTokenAdded(currency: Currency): Boolean { diff --git a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt index 4e83c913ca..abf878da69 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt +++ b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt @@ -1,17 +1,15 @@ package com.tangem.domain.common.util -import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.domain.common.extensions.calculateHmacSha256 class UserWalletId( val stringValue: String, ) { val value = stringValue.hexToBytes() - constructor(walletPublicKey: ByteArray?) : this( - stringValue = walletPublicKey?.let { calculateUserWalletId(it).toHexString() } ?: "", + constructor(value: ByteArray?) : this( + stringValue = value?.toHexString() ?: "", ) override fun equals(other: Any?): Boolean { @@ -32,12 +30,4 @@ class UserWalletId( "UserWalletId(${take(3)}...${takeLast(3)})" } } -} - -private fun calculateUserWalletId(publicKey: ByteArray): ByteArray { - val message = MESSAGE_FOR_WALLET_ID.toByteArray() - val keyHash = publicKey.calculateSha256() - return message.calculateHmacSha256(keyHash) -} - -private const val MESSAGE_FOR_WALLET_ID = "UserWalletID" \ No newline at end of file +} \ No newline at end of file From 9ee61f80579f285d5fb683b73c21c7058d878631 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Dec 2022 11:10:51 +0300 Subject: [PATCH 164/176] Updated on 2026-08-14 --- buildSrc/src/main/java/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 869eaf8f0b..89471ec92c 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -61,7 +61,7 @@ object Versions { // region Tangem const val tangemBlockchainSdk = "develop-141" - const val tangemCardSgk = "develop-178" + const val tangemCardSgk = "develop-179" // endregion Tangem // region Testing From 6b3e84f9c34ee038f5b6e22888dc024374a1bd6a Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 17:15:32 +0300 Subject: [PATCH 165/176] Updated on 2026-08-14 --- .../tangem/tap/features/wallet/ui/WalletDetailsFragment.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index a7f230dc35..f212aac2dd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -132,12 +132,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun onStop() { super.onStop() store.unsubscribe(this) - } - - override fun onDestroy() { walletDataWatcher.clear() walletStateWatcher.clear() - super.onDestroy() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { From 0c888d210f5274f05dddd3c280173cba8faeace3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 17:00:44 +0300 Subject: [PATCH 166/176] Updated on 2026-08-14 --- .../tap/domain/model/builders/UserWalletBuilder.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 6e4d834d38..ed8eb97623 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -1,5 +1,7 @@ package com.tangem.tap.domain.model.builders +import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO @@ -40,7 +42,16 @@ class UserWalletBuilder( ProductType.Note -> false ProductType.Twins -> false ProductType.SaltPay -> false - ProductType.Wallet -> !card.isStart2Coin + ProductType.Wallet -> when { + card.isStart2Coin -> false + card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable -> true + else -> { + val cardWallets = card.wallets + require(cardWallets.isNotEmpty()) { "Card wallets must not be empty" } + + cardWallets.first().curve == EllipticCurve.Secp256k1 + } + } } fun backupCardsIds(backupCardsIds: Set?) = this.apply { From 252df193d2fd0e19f7612ded53d47e99df43be04 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 18:40:03 +0300 Subject: [PATCH 167/176] Updated on 2026-08-14 --- .../AddressInfoBottomSheetDialog.kt | 30 +++++------------ .../wallet/ui/WalletDetailsFragment.kt | 18 +++++++++-- .../wallet/ui/wallet/SingleWalletView.kt | 32 ++++++++++++++----- .../layout/dialog_onboarding_address_info.xml | 2 +- app/src/main/res/layout/layout_address.xml | 11 +++++++ .../main/res/layout/layout_wallet_details.xml | 2 +- .../src/main/res/values-de/strings-app.xml | 3 +- .../src/main/res/values-fr/strings-app.xml | 3 +- .../src/main/res/values-it/strings-app.xml | 3 +- .../src/main/res/values-ru/strings-app.xml | 3 +- core/res/src/main/res/values/strings-app.xml | 3 +- 11 files changed, 65 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt index 4635716436..b56ddaf6cd 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -11,8 +11,8 @@ import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchShare import com.tangem.tap.common.extensions.dispatchToastNotification +import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.redux.AppDialog -import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.store import com.tangem.wallet.R @@ -62,26 +62,12 @@ class AddressInfoBottomSheetDialog( Analytics.send(Token.Recieve.ButtonShareAddress()) store.dispatchShare(data.shareUrl) } - tvReceiveMessage.text = getQRReceiveMessage(tvReceiveMessage.context, stateDialog.currency) - } -} - -fun getQRReceiveMessage(context: Context, currency: Currency): String { - return when (currency) { - is Currency.Blockchain -> { - context.getString( - R.string.address_qr_code_message_format, - currency.blockchain.fullName, - currency.currencySymbol, - ) - } - is Currency.Token -> { - context.getString( - R.string.address_qr_code_message_token_format, - currency.token.name, - currency.currencySymbol, - currency.blockchain.fullName, - ) - } + val blockchain = stateDialog.currency.blockchain + tvReceiveMessage.text = tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + blockchain.fullName, + blockchain.currency, + blockchain.fullName, + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index f212aac2dd..bd1608e7de 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -36,7 +36,6 @@ import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.ErrorType @@ -324,8 +323,21 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), ) } ivQrCode.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode()) - tvReceiveMessage.text = - getQRReceiveMessage(tvReceiveMessage.context, state.currency) + + tvReceiveMessage.text = when (val currency = state.currency) { + is Currency.Blockchain -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.blockchain.fullName, + currency.currencySymbol, + currency.blockchain.fullName, + ) + is Currency.Token -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.token.name, + currency.currencySymbol, + currency.blockchain.fullName, + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index e8fc90a242..18ebc3dd4b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.fitChipsByGroupWidth import com.tangem.tap.common.extensions.getQuantityString +import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState @@ -65,7 +66,7 @@ class SingleWalletView : WalletView() { setupTwinCards(state.twinCardsState, binding) setupButtons(state.primaryWallet, binding, state.isExchangeServiceFeatureOn) - setupAddressCard(state.primaryWallet, binding) + setupAddressCard(state, binding) showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions) setupBalance(state, state.primaryWallet) } @@ -146,35 +147,50 @@ class SingleWalletView : WalletView() { } } - private fun setupAddressCard(state: WalletData, binding: FragmentWalletBinding) = with(binding.lAddress) { - if (state.walletAddresses != null && state.currency is Currency.Blockchain) { + private fun setupAddressCard(state: WalletState, binding: FragmentWalletBinding) = with(binding.lAddress) { + val primaryWallet = state.primaryWallet + if (primaryWallet?.walletAddresses != null && primaryWallet.currency is Currency.Blockchain) { binding.lAddress.root.show() - if (state.shouldShowMultipleAddress()) { + if (primaryWallet.shouldShowMultipleAddress()) { (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition() chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) + val checkedId = MultipleAddressUiHelper.typeToId(primaryWallet.walletAddresses.selectedAddress.type) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener - val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) + val type = MultipleAddressUiHelper.idToType(checkedId, primaryWallet.currency.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { chipGroupAddressType.hide() } - tvAddress.text = state.walletAddresses.selectedAddress.address + tvAddress.text = primaryWallet.walletAddresses.selectedAddress.address tvExplore.setOnClickListener { store.dispatch( WalletAction.ExploreAddress( - state.walletAddresses.selectedAddress.exploreUrl, + primaryWallet.walletAddresses.selectedAddress.exploreUrl, fragment!!.requireContext(), ), ) } + setupCardInfo(state) } else { binding.lAddress.root.hide() } } + + private fun setupCardInfo(state: WalletState) { + val textView = binding?.lAddress?.tvInfo + val blockchain = state.primaryWallet?.currency?.blockchain + if (textView != null && blockchain != null) { + textView.text = textView.getString( + id = R.string.address_qr_code_message_format, + blockchain.fullName, + blockchain.currency, + blockchain.fullName, + ) + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_onboarding_address_info.xml b/app/src/main/res/layout/dialog_onboarding_address_info.xml index ca04440067..eadb8f7f36 100644 --- a/app/src/main/res/layout/dialog_onboarding_address_info.xml +++ b/app/src/main/res/layout/dialog_onboarding_address_info.xml @@ -36,7 +36,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imv_qr_code" - tools:text="@string/address_qr_code_message_token_format" /> + tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." /> + + + tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." /> diff --git a/core/res/src/main/res/values-de/strings-app.xml b/core/res/src/main/res/values-de/strings-app.xml index 9b09fb053e..bd2e4888dc 100644 --- a/core/res/src/main/res/values-de/strings-app.xml +++ b/core/res/src/main/res/values-de/strings-app.xml @@ -133,8 +133,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. OK, ich hab\'s! diff --git a/core/res/src/main/res/values-fr/strings-app.xml b/core/res/src/main/res/values-fr/strings-app.xml index 8d7d037a69..3d926e7f7b 100644 --- a/core/res/src/main/res/values-fr/strings-app.xml +++ b/core/res/src/main/res/values-fr/strings-app.xml @@ -133,8 +133,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, je l\'ai! diff --git a/core/res/src/main/res/values-it/strings-app.xml b/core/res/src/main/res/values-it/strings-app.xml index c5e3134366..aa4147d4ba 100644 --- a/core/res/src/main/res/values-it/strings-app.xml +++ b/core/res/src/main/res/values-it/strings-app.xml @@ -133,8 +133,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, ho capito! diff --git a/core/res/src/main/res/values-ru/strings-app.xml b/core/res/src/main/res/values-ru/strings-app.xml index 34b9be72db..142d4e305f 100644 --- a/core/res/src/main/res/values-ru/strings-app.xml +++ b/core/res/src/main/res/values-ru/strings-app.xml @@ -133,8 +133,7 @@ Успешно! Ваша карта активирована и готова к использованию Баланс - Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. - Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. + Отправляйте только %s (%s) в сети %s на этот адрес. Использование другой сети может привести к утрате средств. Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала. Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. Понятно! diff --git a/core/res/src/main/res/values/strings-app.xml b/core/res/src/main/res/values/strings-app.xml index 4d8976d15d..80bfd3967b 100644 --- a/core/res/src/main/res/values/strings-app.xml +++ b/core/res/src/main/res/values/strings-app.xml @@ -133,8 +133,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, Got it! From c320f58ded1cbfc5e8ce250457f4011194233790 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 18:25:38 +0300 Subject: [PATCH 168/176] Updated on 2026-08-14 --- .../BiometricUserWalletsListManager.kt | 2 +- .../repository/UserWalletsKeysRepository.kt | 6 ++++++ .../UserWalletsPublicInformationRepository.kt | 2 -- .../BiometricUserWalletsKeysRepository.kt | 7 +++++++ .../DefaultUserWalletsPublicInformationRepository.kt | 4 ---- .../tap/features/details/redux/DetailsMiddleware.kt | 12 +++++++++--- .../tap/features/details/redux/DetailsReducer.kt | 3 +-- 7 files changed, 24 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index f7781ca770..fdd95f5f63 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -51,7 +51,7 @@ internal class BiometricUserWalletsListManager( get() = state.value.isLocked override val hasSavedUserWallets: Boolean - get() = publicInformationRepository.isNotEmpty() + get() = keysRepository.hasSavedEncryptionKeys() override suspend fun unlockWithBiometry(): CompletionResult { return unlockWithBiometryInternal() diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index f10e0beeb5..bf96081cd4 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -32,4 +32,10 @@ internal interface UserWalletsKeysRepository { * @return [CompletionResult] of operation * */ suspend fun clear(): CompletionResult + + /** + * Determine if the user has saved user wallets + * @return [Boolean] true if user has saved wallets + * */ + fun hasSavedEncryptionKeys(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt index f0387d3f0e..8765a6b804 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt @@ -12,6 +12,4 @@ internal interface UserWalletsPublicInformationRepository { suspend fun delete(walletIds: List): CompletionResult suspend fun clear(): CompletionResult - - fun isNotEmpty(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 5e57d10474..8de5a59457 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -17,6 +17,7 @@ import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext internal class BiometricUserWalletsKeysRepository( @@ -77,6 +78,12 @@ internal class BiometricUserWalletsKeysRepository( } } + override fun hasSavedEncryptionKeys(): Boolean { + return runBlocking { + getUserWalletsIds().isNotEmpty() + } + } + private suspend fun getAllInternal(): CompletionResult> { return getUserWalletsIds() .map { userWalletId -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index 46a016f8c0..7d1bd9333b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -72,10 +72,6 @@ internal class DefaultUserWalletsPublicInformationRepository( } } - override fun isNotEmpty(): Boolean { - return secureStorage.get(StorageKey.UserWalletPublicInformation.name)?.isNotEmpty() == true - } - @JvmName("saveWithPublicInformation") private suspend fun save( publicInformation: List, diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 319071642c..a170aebdfd 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -278,9 +278,6 @@ class DetailsMiddleware { val userWallet = UserWalletBuilder(scanResponse).build() userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Wallet saving failed") - } .doOnSuccess { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On)) @@ -296,6 +293,9 @@ class DetailsMiddleware { store.onUserWalletSelected(userWallet) } + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } } private suspend fun deleteSavedWallets() { @@ -315,6 +315,9 @@ class DetailsMiddleware { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } + .doOnFailure { error -> + Timber.e(error, "Unable to delete saved wallets") + } } private fun saveAccessCodes(state: DetailsState) { @@ -353,6 +356,9 @@ class DetailsMiddleware { ) } } + .doOnFailure { error -> + Timber.e(error, "Unable to delete saved access codes") + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index f00f0b46b4..11b737097a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -11,7 +11,6 @@ import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.preferencesStorage import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import org.rekotlin.Action import java.util.* @@ -57,7 +56,7 @@ private fun handlePrepareScreen( createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appCurrency = store.state.globalState.appCurrency, isBiometricsAvailable = tangemSdkManager.canUseBiometry, - saveWallets = userWalletsListManager.hasSavedUserWallets, + saveWallets = preferencesStorage.shouldSaveUserWallets, saveAccessCodes = preferencesStorage.shouldSaveAccessCodes, ) } From c48e7204eb3919864b482c993c2b30eb238f5cfd Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 29 Dec 2022 14:00:54 +0300 Subject: [PATCH 169/176] Updated on 2026-08-14 --- .../userWalletList/UserWalletsListManager.kt | 13 +++- .../BiometricUserWalletsListManager.kt | 71 ++++++++++++++----- .../DummyUserWalletsListManager.kt | 56 --------------- .../model/UserWalletEncryptionKey.kt | 7 -- .../repository/UserWalletsKeysRepository.kt | 6 +- ...erWalletsSensitiveInformationRepository.kt | 2 +- .../BiometricUserWalletsKeysRepository.kt | 5 +- ...erWalletsSensitiveInformationRepository.kt | 5 +- .../utils/UserWalletEncyptionKeyCalculator.kt | 11 +-- .../features/tokens/redux/TokensMiddleware.kt | 15 ++-- .../middlewares/MultiWalletMiddleware.kt | 19 ++--- .../redux/WalletSelectorMiddleware.kt | 4 +- .../ui/WalletSelectorViewModel.kt | 20 ++---- 13 files changed, 104 insertions(+), 130 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index dfd62c05f5..9e7619e7c5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -19,7 +19,7 @@ interface UserWalletsListManager { suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** - * Save user wallet + * Save provided user wallet and set it as selected * @param userWallet [UserWallet] to save * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an * already saved card @@ -27,6 +27,17 @@ interface UserWalletsListManager { * */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult + /** + * Same as [save] but not change selected user wallet ID + * and not terminate with [UserWalletListError.WalletAlreadySaved] if [UserWallet] already saved + * + * Can terminate with [NoSuchElementException] if unable to find [UserWallet] with provided [UserWalletId] + * @param userWalletId update [UserWallet] with that [UserWalletId] + * @param update lambda that receives stored [UserWallet] and returns updated [UserWallet] + * @return [CompletionResult] of operation with updated [UserWallet] + * */ + suspend fun update(userWalletId: UserWalletId, update: (UserWallet) -> UserWallet): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun clear(): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 4eef3db4b7..db1db2ebbd 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -10,6 +10,7 @@ import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletReposit import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.updateWith import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -84,7 +85,7 @@ internal class BiometricUserWalletsListManager( override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { return if (canOverride) { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } else { val isWalletSaved = state.value.userWallets .any { @@ -94,11 +95,25 @@ internal class BiometricUserWalletsListManager( if (isWalletSaved) { CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) } else { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } } } + override suspend fun update( + userWalletId: UserWalletId, + update: (UserWallet) -> UserWallet, + ): CompletionResult { + return get(userWalletId) + .map { storedUserWallet -> + update(storedUserWallet) + } + .flatMap { updatedUserWallet -> + saveInternal(updatedUserWallet, changeSelectedUserWallet = false) + .map { updatedUserWallet } + } + } + override suspend fun delete(userWalletIds: List): CompletionResult { if (userWalletIds.isEmpty()) { return CompletionResult.Success(Unit) @@ -111,9 +126,12 @@ internal class BiometricUserWalletsListManager( .flatMap { keysRepository.delete(userWalletIds) } .map { state.update { prevState -> + val newUserWallets = prevState.userWallets.filter { it.walletId !in userWalletIds } + prevState.copy( encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds }, - userWallets = prevState.userWallets.filter { it.walletId !in userWalletIds }, + userWallets = newUserWallets, + isLocked = newUserWallets.any { it.isLocked }, ) } } @@ -135,27 +153,23 @@ internal class BiometricUserWalletsListManager( } } - private suspend fun saveInternal(userWallet: UserWallet): CompletionResult { - val newEncryptionKeys = state.value.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } - - return keysRepository.store(newEncryptionKeys) - .doOnSuccess { - state.update { prevState -> - prevState.copy( - encryptionKeys = newEncryptionKeys, - selectedUserWalletId = userWallet.walletId, - ) - } - } + private suspend fun saveInternal( + userWallet: UserWallet, + changeSelectedUserWallet: Boolean, + ): CompletionResult { + return saveEncryptionKeyIfNotNull(userWallet) + .flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = it) } .flatMap { publicInformationRepository.save(userWallet) } - .flatMap { sensitiveInformationRepository.save(userWallet) } .map { selectedUserWalletRepository.set(userWallet.walletId) } .flatMap { loadModels() } .doOnSuccess { state.update { prevState -> prevState.copy( + selectedUserWalletId = if (changeSelectedUserWallet) { + userWallet.walletId + } else { + prevState.selectedUserWalletId + }, isLocked = prevState.userWallets.any { it.isLocked }, ) } @@ -181,6 +195,27 @@ internal class BiometricUserWalletsListManager( } } + private suspend fun saveEncryptionKeyIfNotNull(userWallet: UserWallet): CompletionResult { + val encryptionKey = userWallet.scanResponse.card.encryptionKey + ?.let { UserWalletEncryptionKey(userWallet.walletId, it) } + + return if (encryptionKey != null) { + keysRepository.save(encryptionKey) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + encryptionKeys = prevState.encryptionKeys + .plus(encryptionKey) + .distinctBy { it.walletId }, + ) + } + } + .map { encryptionKey.encryptionKey } + } else { + CompletionResult.Success(data = null) + } + } + private suspend fun loadModels(): CompletionResult { return getSavedUserWallets() .map { userWallets -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt deleted file mode 100644 index e616c48758..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.tap.domain.userWalletList.implementation - -import com.tangem.common.CompletionResult -import com.tangem.common.catching -import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.userWalletList.UserWalletsListManager -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - -class DummyUserWalletsListManager : UserWalletsListManager { - override val userWallets: Flow> - get() = flowOf(emptyList()) - override val selectedUserWallet: Flow - get() = flowOf() - override val selectedUserWalletSync: UserWallet? - get() = null - override val isLocked: Flow - get() = flowOf(true) - override val isLockedSync: Boolean - get() = true - override val hasSavedUserWallets: Boolean - get() = false - - override suspend fun unlockWithBiometry(): CompletionResult { - return CompletionResult.Success(null) - } - - override fun lock() { - /* no-op */ - } - - override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } - - override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun delete(userWalletIds: List): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun clear(): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun get(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt index a5c20a7790..df0f473bae 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -2,19 +2,12 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.JsonClass import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.userWalletList.utils.encryptionKey @JsonClass(generateAdapter = true) internal data class UserWalletEncryptionKey( val walletId: UserWalletId, val encryptionKey: ByteArray, ) { - constructor(userWallet: UserWallet) : this( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is UserWalletEncryptionKey) return false diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index bf96081cd4..99b62dc99d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -14,11 +14,11 @@ internal interface UserWalletsKeysRepository { suspend fun getAll(): CompletionResult> /** - * Store the encryption keys for user wallets. Biometric authentication not required - * @param encryptionKeys List of encryption keys for user wallets + * Save the encryption key for user wallet. Biometric authentication not required + * @param encryptionKey [UserWalletEncryptionKey] to save * @return [CompletionResult] of operation * */ - suspend fun store(encryptionKeys: List): CompletionResult + suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult /** * Delete encryption keys for user wallets. Biometric authentication not required diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt index 7e03055150..fdae3e64a0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -7,7 +7,7 @@ import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation internal interface UserWalletsSensitiveInformationRepository { - suspend fun save(userWallet: UserWallet): CompletionResult + suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult suspend fun getAll( encryptionKeys: List, ): CompletionResult> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 8de5a59457..11e558535d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -45,10 +45,9 @@ internal class BiometricUserWalletsKeysRepository( } } - override suspend fun store(encryptionKeys: List): CompletionResult { + override suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult { return withContext(Dispatchers.IO) { - encryptionKeys.map { storeEncryptionKey(it) } - .fold() + storeEncryptionKey(encryptionKey) .mapFailure { error -> UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 158dc0db2b..673ec51a10 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -38,11 +38,12 @@ internal class DefaultUserWalletsSensitiveInformationRepository( Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") } - override suspend fun save(userWallet: UserWallet): CompletionResult { + override suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult { + if (encryptionKey == null) return CompletionResult.Success(Unit) // Encryption key is null, do nothing return catching { val encryptedSensitiveInformation = userWallet.sensitiveInformation .encode() - .encryptAndStoreIv(userWallet.walletId.stringValue, userWallet.scanResponse.card.encryptionKey) + .encryptAndStoreIv(userWallet.walletId.stringValue, encryptionKey) getAllEncrypted().toMutableMap() .apply { set(userWallet.walletId.stringValue, encryptedSensitiveInformation) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt index 3ecfb70521..d97af026cc 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt @@ -4,15 +4,8 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.domain.common.CardDTO import com.tangem.domain.common.extensions.calculateHmacSha256 -internal val CardDTO.encryptionKey: ByteArray - @Throws(IllegalArgumentException::class) - get() { - val walletPublicKey = requireNotNull(findPublicKey(wallets)) { - "Wallet public key must not be null" - } - - return calculateEncryptionKey(walletPublicKey) - } +internal val CardDTO.encryptionKey: ByteArray? + get() = findPublicKey(wallets)?.let { calculateEncryptionKey(it) } private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index b07a4fd3ca..10a7d4e03a 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -281,13 +281,16 @@ class TokensMiddleware { ) { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { - val updatedUserWallet = selectedUserWallet.copy( - scanResponse = scanResponse, - ) - scope.launch { - userWalletsListManager.save(updatedUserWallet, canOverride = true) - .flatMap { + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, + ) + .flatMap { updatedUserWallet -> walletCurrenciesManager.addCurrencies( userWallet = updatedUserWallet, currenciesToAdd = currencyList, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 9bba0011a5..821f4971b4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -179,21 +179,24 @@ class MultiWalletMiddleware { } private fun scanAndUpdateCard( - selectedWallet: UserWallet, + selectedUserWallet: UserWallet, state: WalletState?, ) = scope.launch { Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( - cardId = selectedWallet.cardId, + cardId = selectedUserWallet.cardId, additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, ) { scanResponse -> - val userWallet = selectedWallet.copy( - scanResponse = scanResponse, + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, ) - - userWalletsListManager.save(userWallet, canOverride = true) - .doOnSuccess { - store.state.globalState.tapWalletManager.loadData(userWallet, refresh = true) + .doOnSuccess { updatedUserWallet -> + store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index c1f24294f4..4772b501d7 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -239,9 +239,7 @@ internal class WalletSelectorMiddleware { Analytics.send(MyWallets.Button.EditWalletTapped) scope.launch { - userWalletsListManager.get(userWalletId) - .map { it.copy(name = newName) } - .flatMap { userWalletsListManager.save(it, canOverride = true) } + userWalletsListManager.update(userWalletId) { it.copy(name = newName) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index 1192b37c4a..52a99013e3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -38,7 +38,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber Unit editingUserWalletsIds.isNotEmpty() && !editingUserWalletsIds.contains(userWalletId) -> { editWallet(userWalletId) } @@ -52,7 +51,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber - store.dispatch(WalletSelectorAction.RenameWallet(editedWalletId, newName)) + store.dispatch(WalletSelectorAction.RenameWallet(editedUserWalletId, newName)) stateInternal.update { prevState -> prevState.copy( renameWalletDialog = null, @@ -137,11 +136,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState } From 0263ee0a28a71d21f7a3c8d817434560cd15e474 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 28 Dec 2022 21:19:13 +0300 Subject: [PATCH 170/176] Updated on 2026-08-14 --- .../DefaultWalletCurrenciesManager.kt | 11 +- .../repository/WalletAmountsRepository.kt | 6 + .../DefaultWalletAmountsRepository.kt | 165 ++++++++++-------- .../utils/WalletStoreOperations.kt | 6 +- .../middlewares/MultiWalletMiddleware.kt | 4 +- 5 files changed, 117 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index 5fd2e25234..e000fe1c6d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -20,6 +20,7 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext internal class DefaultWalletCurrenciesManager( @@ -64,7 +65,15 @@ internal class DefaultWalletCurrenciesManager( saveUserCurrencies(card, newCurrencies) } .flatMap { - walletAmountsRepository.updateAmountsForUserWallet( + val updatedBlockchains = updatedBlockchainNetworks + .map { it.blockchain } + val updatedWalletStores = walletStoresRepository.get(userWallet.walletId) + .firstOrNull() + ?.filter { it.blockchain in updatedBlockchains } + ?: return@flatMap CompletionResult.Success(Unit) + + walletAmountsRepository.updateAmountsForWalletStores( + walletStores = updatedWalletStores, userWallet = userWallet, fiatCurrency = appCurrencyProvider(), ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt index 0af5b8f85e..a054ed0ff2 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt @@ -29,6 +29,12 @@ interface WalletAmountsRepository { fiatCurrency: FiatCurrency, ): CompletionResult + suspend fun updateAmountsForWalletStores( + walletStores: List, + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult + /** * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 640c08fe67..444d6fc480 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -41,7 +41,8 @@ import com.tangem.tap.network.NetworkConnectivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal @@ -60,7 +61,7 @@ internal class DefaultWalletAmountsRepository( else withContext(Dispatchers.Default) { awaitAll( async { fetchAmountsForUserWallets(userWallets) }, - async { fetchFiatRates(userWallets, fiatCurrency) }, + async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) }, ) .fold() } @@ -73,46 +74,40 @@ internal class DefaultWalletAmountsRepository( return updateAmountsForUserWallets(listOf(userWallet), fiatCurrency) } + override suspend fun updateAmountsForWalletStores( + walletStores: List, + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult { + return if (walletStores.isEmpty()) CompletionResult.Success(Unit) + else withContext(Dispatchers.Default) { + val userWalletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + + awaitAll( + async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) }, + async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) }, + ) + .fold() + } + } + override suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, userWallet: UserWallet, fiatCurrency: FiatCurrency, - ): CompletionResult = withContext(Dispatchers.Default) { - val walletId = userWallet.walletId - val scanResponse = userWallet.scanResponse - - awaitAll( - async { - // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] - val walletManager = walletStore.walletManager - fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) - }, - async { fetchFiatRates(listOf(userWallet), fiatCurrency) }, - ) - .fold() - } - - private suspend fun fetchAmountsForUserWallets( - userWallets: List, - ): CompletionResult = withContext(Dispatchers.Default) { - userWallets.map { async { fetchAmountsForUserWallet(it) } } - .awaitAll() - .fold() + ): CompletionResult { + return updateAmountsForWalletStores(listOf(walletStore), userWallet, fiatCurrency) } private suspend fun fetchFiatRates( userWallets: List, + walletStores: List?, fiatCurrency: FiatCurrency, ): CompletionResult { - val walletsIds = userWallets.map { it.walletId } - val walletStores = walletsIds - .flatMap { - walletStoresStorage.getAll() - .first() - .getOrElse(it) { emptyList() } - } + val walletStoresInternal = walletStores ?: getWalletStores(userWallets) - val currencies = walletStores + val currencies = walletStoresInternal .asSequence() .flatMap { it.walletsData } .map { it.currency } @@ -129,7 +124,7 @@ internal class DefaultWalletAmountsRepository( return when (fiatRatesResult) { is Result.Success -> { updateWalletStoresWithFiatRates( - walletStores = walletStores, + walletStores = walletStoresInternal, fiatRates = fiatRatesResult.data.rates, ) @@ -145,7 +140,6 @@ internal class DefaultWalletAmountsRepository( error, """ Unable to fetch fiat rates - |- User wallets ids: $walletsIds |- Coins ids: $coinsIds """.trimIndent(), ) @@ -155,20 +149,34 @@ internal class DefaultWalletAmountsRepository( } } + private suspend fun fetchAmountsForUserWallets( + userWallets: List, + ): CompletionResult = withContext(Dispatchers.Default) { + userWallets.map { async { fetchAmountsForUserWallet(it) } } + .awaitAll() + .fold() + } + private suspend fun fetchAmountsForUserWallet( userWallet: UserWallet, ): CompletionResult = withContext(Dispatchers.Default) { - val walletId = userWallet.walletId + val userWalletId = userWallet.walletId val scanResponse = userWallet.scanResponse - val walletStores = walletStoresStorage.getAll() - .first() - .getOrElse(walletId) { emptyList() } + val walletStores = getWalletStores(listOf(userWallet)) + fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) + } + + private suspend fun fetchAmountForWalletStores( + userWalletId: UserWalletId, + scanResponse: ScanResponse, + walletStores: List, + ): CompletionResult = coroutineScope { walletStores.map { walletStore -> async { // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] val walletManager = walletStore.walletManager - fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) + fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager) } } .awaitAll() @@ -176,7 +184,7 @@ internal class DefaultWalletAmountsRepository( } private suspend fun fetchAmountsForWalletStore( - walletId: UserWalletId, + userWalletId: UserWalletId, scanResponse: ScanResponse, walletStore: WalletStoreModel, walletManager: WalletManager?, @@ -186,11 +194,15 @@ internal class DefaultWalletAmountsRepository( } return when { - hasMissedDerivations -> updateWalletStoreWithMissedDerivation(walletStore) - walletManager == null -> updateWalletStoreWithUnreachable(walletStore) + hasMissedDerivations -> { + updateWalletStoreWithMissedDerivation(walletStore) + } + walletManager == null -> { + updateWalletStoreWithUnreachable(walletStore) + } else -> { withInternetConnection { walletManager.update() } - .map { updateWalletManagerWithAmounts(walletId, walletManager) } + .map { updateWalletManagerWithAmounts(userWalletId, walletManager) } .flatMap { updateWalletStoreWithAmounts( walletStore = walletStore, @@ -248,35 +260,6 @@ internal class DefaultWalletAmountsRepository( return CompletionResult.Success(Unit) } - private suspend inline fun withInternetConnection(crossinline block: suspend () -> Unit): CompletionResult { - return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - val error = WalletStoresError.NoInternetConnection - Timber.e(error) - CompletionResult.Failure(error) - } else withContext(Dispatchers.IO) { - catching { block() } - } - } - - private suspend fun updateWalletManagerWithAmounts( - walletId: UserWalletId, - walletManager: WalletManager, - ) = withContext(Dispatchers.Default) { - walletManagersStorage.update { prevManagers -> - val newManagersForUserWallet = prevManagers[walletId].orEmpty() - .toMutableList() - .apply { - replaceByOrAdd(walletManager) { - it.wallet.blockchain == it.wallet.blockchain - } - } - - prevManagers.apply { - set(walletId, newManagersForUserWallet) - } - } - } - private suspend fun updateWalletStoreWithError( walletStore: WalletStoreModel, wallet: Wallet, @@ -425,4 +408,44 @@ internal class DefaultWalletAmountsRepository( } } } + + private suspend inline fun withInternetConnection(crossinline block: suspend () -> Unit): CompletionResult { + return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + val error = WalletStoresError.NoInternetConnection + Timber.e(error) + CompletionResult.Failure(error) + } else withContext(Dispatchers.IO) { + catching { block() } + } + } + + private suspend fun updateWalletManagerWithAmounts( + userWalletId: UserWalletId, + walletManager: WalletManager, + ) = withContext(Dispatchers.Default) { + walletManagersStorage.update { prevManagers -> + val newManagersForUserWallet = prevManagers[userWalletId].orEmpty() + .toMutableList() + .apply { + replaceByOrAdd(walletManager) { + it.wallet.blockchain == it.wallet.blockchain + } + } + + prevManagers.apply { + set(userWalletId, newManagersForUserWallet) + } + } + } + + private suspend fun getWalletStores(userWallets: List): List { + return userWallets + .map { it.walletId } + .flatMap { userWalletId -> + walletStoresStorage.getAll() + .firstOrNull() + ?.get(userWalletId) + .orEmpty() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 445ff655f2..2aa7ffcf2d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -62,9 +62,11 @@ internal fun WalletStoreModel.updateWithSelf( ): WalletStoreModel { val oldStore = this return oldStore.copy( - walletManager = newWalletStore.walletManager, - walletRent = newWalletStore.walletRent, + derivationPath = newWalletStore.derivationPath, walletsData = oldStore.walletsData.updateWithSelf(newWalletStore.walletsData), + walletRent = newWalletStore.walletRent, + blockchainNetwork = newWalletStore.blockchainNetwork, + walletManager = newWalletStore.walletManager, ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 821f4971b4..ba82d17ccd 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalState @@ -181,7 +182,7 @@ class MultiWalletMiddleware { private fun scanAndUpdateCard( selectedUserWallet: UserWallet, state: WalletState?, - ) = scope.launch { + ) = scope.launch(Dispatchers.Default) { Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( cardId = selectedUserWallet.cardId, @@ -196,6 +197,7 @@ class MultiWalletMiddleware { }, ) .doOnSuccess { updatedUserWallet -> + store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } From 0155e3c7420238e4cedd3e440bcd2751a8193fe0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 14:33:08 +0400 Subject: [PATCH 171/176] Updated on 2026-08-14 --- .../com/tangem/tap/features/tokens/redux/TokensMiddleware.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 10a7d4e03a..af6ab1607f 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -149,7 +149,7 @@ class TokensMiddleware { && blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() ) { store.dispatchDebugErrorNotification("Nothing to save") - store.dispatch(NavigationAction.PopBackTo()) + store.dispatchOnMain(NavigationAction.PopBackTo()) return@launch } From 1f6d1fd2bd70b417ccf5947b078479565842a1b0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Dec 2022 12:28:17 +0300 Subject: [PATCH 172/176] Updated on 2026-08-14 --- .../wallet/ui/dialogs/ScanFailsDialog.kt | 2 +- .../{strings-app.xml => strings.xml} | 229 ++++++++++++++++- .../src/main/res/values-de/strings_final.xml | 233 ------------------ ..._plurals_final.xml => strings_plurals.xml} | 0 .../{strings-app.xml => strings.xml} | 229 ++++++++++++++++- .../src/main/res/values-fr/strings_final.xml | 233 ------------------ ..._plurals_final.xml => strings_plurals.xml} | 0 .../{strings-app.xml => strings.xml} | 229 ++++++++++++++++- .../src/main/res/values-it/strings_final.xml | 233 ------------------ .../{strings-app.xml => strings.xml} | 227 ++++++++++++++++- .../src/main/res/values-ru/strings_final.xml | 233 ------------------ .../values/{strings-app.xml => strings.xml} | 231 ++++++++++++++++- .../res/src/main/res/values/strings_final.xml | 233 ------------------ ..._plurals_final.xml => strings_plurals.xml} | 0 14 files changed, 1131 insertions(+), 1181 deletions(-) rename core/res/src/main/res/values-de/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values-de/strings_final.xml rename core/res/src/main/res/values-de/{strings_plurals_final.xml => strings_plurals.xml} (100%) rename core/res/src/main/res/values-fr/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values-fr/strings_final.xml rename core/res/src/main/res/values-fr/{strings_plurals_final.xml => strings_plurals.xml} (100%) rename core/res/src/main/res/values-it/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values-it/strings_final.xml rename core/res/src/main/res/values-ru/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values-ru/strings_final.xml rename core/res/src/main/res/values/{strings-app.xml => strings.xml} (51%) delete mode 100644 core/res/src/main/res/values/strings_final.xml rename core/res/src/main/res/values/{strings_plurals_final.xml => strings_plurals.xml} (100%) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt index d3bc64fa17..cef955c28e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt @@ -24,7 +24,7 @@ class ScanFailsDialog { Analytics.send(IntroductionProcess.ButtonRequestSupport()) store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) } - setNeutralButton(R.string.alert_troubleshooting_scan_card_ok) { _, _ -> } + setNeutralButton(R.string.common_cancel) { _, _ -> } setOnDismissListener { store.dispatchDialogHide() } }.create() } diff --git a/core/res/src/main/res/values-de/strings-app.xml b/core/res/src/main/res/values-de/strings.xml similarity index 51% rename from core/res/src/main/res/values-de/strings-app.xml rename to core/res/src/main/res/values-de/strings.xml index bd2e4888dc..9f2035db71 100644 --- a/core/res/src/main/res/values-de/strings-app.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Erneut versuchen + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Karte scannen + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Bilanz: %s + Share + Copy + Erfolg + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Ungültige Adresse + This wallet has already been saved, you can add another one + Entfernen + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Änderungen speichern Warnung @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -216,7 +433,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -226,6 +443,12 @@ Fehler OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values-de/strings_final.xml b/core/res/src/main/res/values-de/strings_final.xml deleted file mode 100644 index 046904ddaf..0000000000 --- a/core/res/src/main/res/values-de/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Erneut versuchen - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Karte scannen - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Bilanz: %s - Share - Copy - Erfolg - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Ungültige Adresse - This wallet has already been saved, you can add another one - Entfernen - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values-de/strings_plurals_final.xml b/core/res/src/main/res/values-de/strings_plurals.xml similarity index 100% rename from core/res/src/main/res/values-de/strings_plurals_final.xml rename to core/res/src/main/res/values-de/strings_plurals.xml diff --git a/core/res/src/main/res/values-fr/strings-app.xml b/core/res/src/main/res/values-fr/strings.xml similarity index 51% rename from core/res/src/main/res/values-fr/strings-app.xml rename to core/res/src/main/res/values-fr/strings.xml index 3d926e7f7b..cf498616e2 100644 --- a/core/res/src/main/res/values-fr/strings-app.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Réessayer + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scannez la carte + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Solde : %s + Share + Copy + Avec succès + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Adresse incorrecte + This wallet has already been saved, you can add another one + Supprimer + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Sauvegarder les modifications Alerte @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -216,7 +433,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -226,6 +443,12 @@ Erreur OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values-fr/strings_final.xml b/core/res/src/main/res/values-fr/strings_final.xml deleted file mode 100644 index 4b3d386d9b..0000000000 --- a/core/res/src/main/res/values-fr/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Réessayer - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scannez la carte - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Solde : %s - Share - Copy - Avec succès - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Adresse incorrecte - This wallet has already been saved, you can add another one - Supprimer - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values-fr/strings_plurals_final.xml b/core/res/src/main/res/values-fr/strings_plurals.xml similarity index 100% rename from core/res/src/main/res/values-fr/strings_plurals_final.xml rename to core/res/src/main/res/values-fr/strings_plurals.xml diff --git a/core/res/src/main/res/values-it/strings-app.xml b/core/res/src/main/res/values-it/strings.xml similarity index 51% rename from core/res/src/main/res/values-it/strings-app.xml rename to core/res/src/main/res/values-it/strings.xml index aa4147d4ba..af7ce360a9 100644 --- a/core/res/src/main/res/values-it/strings-app.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Riprova + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scansiona carta + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Saldo: %s + Share + Copy + Con successo + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Indirizzo non valido + This wallet has already been saved, you can add another one + Rimuovere + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Mantieni le modifiche Avviso @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -216,7 +433,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -226,6 +443,12 @@ Errore OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values-it/strings_final.xml b/core/res/src/main/res/values-it/strings_final.xml deleted file mode 100644 index 53bd5f260c..0000000000 --- a/core/res/src/main/res/values-it/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Riprova - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change access code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scansiona carta - Save your Wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check you email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Saldo: %s - Share - Copy - Con successo - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Indirizzo non valido - This wallet has already been saved, you can add another one - Rimuovere - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values-ru/strings-app.xml b/core/res/src/main/res/values-ru/strings.xml similarity index 51% rename from core/res/src/main/res/values-ru/strings-app.xml rename to core/res/src/main/res/values-ru/strings.xml index 142d4e305f..a715487104 100644 --- a/core/res/src/main/res/values-ru/strings-app.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 карты + 2 карты + Доставка + Бесплатно + У меня есть промо-код… + Итого + Другие способы оплаты + Купить сейчас + Встречайте\nTangem + Покупайте + Храните + Отправляйте + Расплачивайтесь + Обменивайте + Вкладывайте + Занимайте + Революционный аппаратный кошелек + Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. + Все ключи в безопасности + До + трех карт + с одним кошельком + Тысячи криптовалют + Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте + Поддержка DeFi + Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах + Кошелек для каждого + Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. + Купить + Поиск токенов + Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие. + Эта функция недоступна в демонстрационном режиме + Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек. + Доступные сети + Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. + Внимание + Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства. + Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки. + Адрес контракта скопирован! + Адрес контракта + Обязательное поле + Пожалуйста, выберите сеть + Количество знаков после запятой должно быть корректным числом не больше %d + Адрес контракта некорректен + Путь деривации некорректен + Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить + Этот токен/сеть уже находится в вашем списке + Знаков после запятой + Сеть + Не выбрано + Например, USD Coin + Название токена + Например, USDC + Символ токена + Деривация по BIP44 + По-умолчанию + Сервер недоступен, повторите попытку позднее + Баланс + В сумме учтены не все монеты + Токены + Управление токенами + Нет цены + Сеть недоступна + Скрыть токен + Скрыть %s + Скрыть + Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. + Невозможно скрыть %s + Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. + Бэкап кошелька не был произведен + Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру + Повторить + Чат + Сеть %s + Я понял + Да + Нет + Карты банков РФ в данный момент не принимаются + У вас есть карта банка другой страны или платежной системы UnionPay? + Сеть не поддерживается. Пожалуйста, выберите другую сеть. + Настройки карты + Тип безопасности + Смена кода доступа + Код доступа будет изменен только на данной карте + Продолжить + Tangem Bot + Приготовьте свою карту + Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. + Сканировать + Настройки приложения + Cохранение кошелька + Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. + Сохранение кода доступа + Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. + При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены. + Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. + WalletConnect + Подключение к Dapps + Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа + Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку + Сбросить карту + Сброс к заводским настройкам + Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. + Выберите сеть + Отсканируйте карту + Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту + Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. + C возвращением! + Используйте %s или код доступа для входа в приложение + Войти с %s + Сканировать карту + Доступ в приложение + Войдите в приложение и следите за своим балансом без сканирования карты + Код доступа + Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта + Мои кошельки + Мультивалютные + Одновалютные + Добавить новый кошелек + Переименование кошелька + Имя кошелька + Разблокировать все с %s + Недопустимый Tag. Он не будет добавлен в транзакцию. + Недопустимый Memo. Он не будет добавлен в транзакцию. + Tag + Memo + Внимание + Приложите карту с логотипом Visa + Недостаточно средств для активации + Пожалуйста обратитесь в службу поддержки + Ввод одинаковых цифр является не безопасным + Данный Код доступа может быть легко взломан + Код доступа + Подключиться + Верификация клиента + Подтвердите свою личность + Установить Код доступа + Зарегистрироваться + Верифицировать (Utorg) + Обновить + Подключите свою карту + Подтвердите свою личность + Подтверждение личности в процессе + Подключите вашу карту к децентрализованной платежной системе + Для начала работы с картой вам необходимо завершить процесс подтверждения личности + Пожалуйста дождитесь завершения процесса подтверждения личности. Вы будете уведомлены через e-mail. Обычно это занимает не более часа. Вы можете закрыть приложение и вернуться позже. + Код доступа + Установите Код доступа для вашей SaltPay карты + Чат поддержки + Пожалуйста, удерживайте карту до завершения операции + Для начала процесса бэкапа вам необходимо добавить Tangem карту + Бэкап карта не добавлена + Бэкап карта создана + Завершите процесс бэкапа создав код доступа + Приготовьте SaltPay карту + Приложите SaltPay карту + Приложите Tangem карту + Чат + Запросить %s + Для начала работы просто запросите начисление wxDai на свой кошелек + Запросить + Поздравляем! Ваша платежная крипто карта теперь активирована! + Запрашивается + Это займет несколько секунд + Что-то пошло не так + Более подробная информация отправлена на ваш адрес электронной почты. + Вы хотите выйти из процесса активации? + В этом случае вам будет необходимо начать процесс заново. + Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. + Реферальная программа + Приведи друга в Tangem + Вы + Получите + на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг + Ваш друг + Получит + %s скидку + при покупке карточки на сайте tangem.com + Ваши друзья купили + Ваш персональный код + Участвовать + условия участия + Нажимая на эту кнопку вы принимаете + Вы приняли + в реферальной программе + Внутренняя ошибка: не удается найти менеджер кошельков + Баланс: %s + Поделиться + Копировать + Успешно + Дать разрешение + Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s + Количество %s + Ваш кошелек + Отправитель + Подтвердить + Обмен %s на + Обмен + Недостаточно средств + Дать разрешение + Разрешить и обменять + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. + Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. + Персональный код скопирован! + Купи Tangem Wallet со скидкой!\n%s + Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. + Неверный адрес + Этот кошелек уже был сохранен, вы можете добавить другой + Удалить + Privacy policy + Включите биометрическую аутентификацию + Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков + Включить + %d выбрано + биометрическую аутентификацию + биометрией Tangem Сохранить изменения Предупреждение @@ -97,7 +315,6 @@ Не могу отправить транзакцию Причина: %s У вас возникли трудности со сканированием карты? - Отмена Обратиться в поддержку Отправить отзыв Очень круто! @@ -226,6 +443,12 @@ Ошибка Ок Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже. + Вы хотите использовать биометрию? + Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты + Использовать биометрию + Включите биометрическую аутентификацию + Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App + Отсканируйте карту %s diff --git a/core/res/src/main/res/values-ru/strings_final.xml b/core/res/src/main/res/values-ru/strings_final.xml deleted file mode 100644 index c56fea1dcc..0000000000 --- a/core/res/src/main/res/values-ru/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 карты - 2 карты - Доставка - Бесплатно - У меня есть промо-код… - Итого - Другие способы оплаты - Купить сейчас - Встречайте\nTangem - Покупайте - Храните - Отправляйте - Расплачивайтесь - Обменивайте - Вкладывайте - Занимайте - Революционный аппаратный кошелек - Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. - Все ключи в безопасности - До - трех карт - с одним кошельком - Тысячи криптовалют - Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте - Поддержка DeFi - Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах - Кошелек для каждого - Используйте его на ходу, в любом месте, в любое время. Без проводов и батареек. Как только понадобится крипта, просто приложите карту к телефону. - Купить - Поиск токенов - Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие. - Эта функция недоступна в демонстрационном режиме - Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек. - Доступные сети - Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. - Внимание - Внимание! Валюты на разных сетях имеют разные адреса. Убедитесь, что адрес соответствует сети, в которой вы отправляете средства. - Токены в сети Solana не поддерживаются этой картой из-за ограничений прошивки. - Адрес контракта скопирован! - Адрес контракта - Обязательное поле - Пожалуйста, выберите сеть - Количество знаков после запятой должно быть корректным числом не больше %d - Адрес контракта некорректен - Путь деривации некорректен - Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить - Этот токен/сеть уже находится в вашем списке - Знаков после запятой - Сеть - Не выбрано - Например, USD Coin - Название токена - Например, USDC - Символ токена - Деривация по BIP44 - По-умолчанию - Сервер недоступен, повторите попытку позднее - Баланс - В сумме учтены не все монеты - Токены - Управление токенами - Нет цены - Сеть недоступна - Скрыть токен - Скрыть %s - Скрыть - Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами. - Невозможно скрыть %s - Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. - Бэкап кошелька не был произведен - Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру - Повторить - Чат - Сеть %s - Я понял - Да - Нет - Карты банков РФ в данный момент не принимаются - У вас есть карта банка другой страны или платежной системы UnionPay? - Сеть не поддерживается. Пожалуйста, выберите другую сеть. - Настройки карты - Тип безопасности - Смена кода доступа - Код доступа будет изменен только на данной карте - Продолжить - Tangem Bot - Приготовьте свою карту - Отсканируйте карту, чтобы изменить ее настройки. Изменения затронут только ту карту, которую вы отсканировали, и не повлияют на другие карты, привязанные к вашему кошельку. - Сканировать - Настройки приложения - Cохранение кошелька - Подключите функцию привязки карты в приложении, а также возможность биометрической аутентификации. Подпись транзакции все так же потребует карту. - Сохранение кода доступа - Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой вместо кода доступа будет запрашиваться биометрическая аутентификация. - При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены. - Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. - WalletConnect - Подключение к Dapps - Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа - Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку - Сбросить карту - Сброс к заводским настройкам - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. - Выберите сеть - Отсканируйте карту - Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту - Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации. - C возвращением! - Используйте %s или код доступа для входа в приложение - Войти с %s - Сканировать карту - Сохраните ваш кошелек - Вы хотите использовать %s? - Вы хотите использовать биометрию? - Доступ в приложение - Войдите в приложение и следите за своим балансом без сканирования карты - Код доступа - Для операций с вашим кошельком будет запрашиваться %s вместо кода доступа карты - Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты - Обратите внимание, что для совершения транзакции с вашими средствами по-прежнему потребуется ваша карта - Использовать %s - Использовать биометрию - Новый функционал - Мои кошельки - Мультивалютные - Одновалютные - Добавить новый кошелек - Переименование кошелька - Имя кошелька - Разблокировать все с %s - Недопустимый Tag. Он не будет добавлен в транзакцию. - Недопустимый Memo. Он не будет добавлен в транзакцию. - Tag - Memo - Внимание - Приложите карту с логотипом Visa - Недостаточно средств для активации - Пожалуйста обратитесь в службу поддержки - Ввод одинаковых цифр является не безопасным - Данный Код доступа может быть легко взломан - Код доступа - Подключиться - Верификация клиента - Подтвердите свою личность - Установить Код доступа - Зарегистрироваться - Верифицировать (Utorg) - Обновить - Подключите свою карту - Подтвердите свою личность - Подтверждение личности в процессе - Подключите вашу карту к децентрализованной платежной системе - Для начала работы с картой вам необходимо завершить процесс подтверждения личности - Пожалуйста дождитесь завершения процесса подтверждения личности. Вы будете уведомлены через e-mail. Обычно это занимает не более часа. Вы можете закрыть приложение и вернуться позже. - Код доступа - Установите Код доступа для вашей SaltPay карты - Чат поддержки - Пожалуйста, удерживайте карту до завершения операции - Для начала процесса бэкапа вам необходимо добавить Tangem карту - Бэкап карта не добавлена - Бэкап карта создана - Завершите процесс бэкапа создав код доступа - Приготовьте SaltPay карту - Приложите SaltPay карту - Приложите Tangem карту - Чат - Запросить %s - Для начала работы просто запросите начисление wxDai на свой кошелек - Запросить - Поздравляем! Ваша платежная крипто карта теперь активирована! - Запрашивается - Это займет несколько секунд - Что-то пошло не так - Более подробная информация отправлена на ваш адрес электронной почты. - Вы хотите выйти из процесса активации? - В этом случае вам будет необходимо начать процесс заново. - Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком. - Реферальная программа - Приведи друга в Tangem - Вы - Получите - на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг - Ваш друг - Получит - %s скидку - при покупке карточки на сайте tangem.com - Ваши друзья купили - Ваш персональный код - Участвовать - условия участия - Нажимая на эту кнопку вы принимаете - Вы приняли - в реферальной программе - Внутренняя ошибка: не удается найти менеджер кошельков - Баланс: %s - Поделиться - Копировать - Успешно - Дать разрешение - Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s - Количество %s - Ваш кошелек - Отправитель - Подтвердить - Обмен %s на - Обмен - Недостаточно средств - Дать разрешение - Разрешить и обменять - Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. - Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. - Персональный код скопирован! - Купи Tangem Wallet со скидкой!\n%s - Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. - Неверный адрес - Этот кошелек уже был сохранен, вы можете добавить другой - Удалить - Privacy policy - Включите биометрическую аутентификацию - Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков - Включить - Функция сохранения кошелька позволяет вам использовать свой кошелек с биометрической аутентификацией, не прикладывая карту к телефону для получения доступа. - %d выбрано - биометрическую аутентификацию - биометрией - Включите биометрическую аутентификацию - Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App - Отсканируйте карту - diff --git a/core/res/src/main/res/values/strings-app.xml b/core/res/src/main/res/values/strings.xml similarity index 51% rename from core/res/src/main/res/values/strings-app.xml rename to core/res/src/main/res/values/strings.xml index 80bfd3967b..4027017a83 100644 --- a/core/res/src/main/res/values/strings-app.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,5 +1,223 @@ - + + Tangem Wallet + 3 cards + 2 cards + Shipping + Free + I have a promo code… + Total + Other payment methods + Buy now + Meet\nTangem + Buy + Store + Send + Pay + Exchange + Lend + Borrow + Revolutionary Hardware Wallet + Store your crypto assets secure while keeping private keys contained in your card + Ultra Secure Backup + Up to + 3 physical cards + to one wallet + Thousands of Currencies + A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card + DeFi Compatible + Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services + The Wallet for Everyone + Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. + Order + Search tokens + You are currently running in Demo mode. All funds are not real. + This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Available networks + %s network not found. Please, add it first and try again. + Attention + Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. + Tokens in Solana network are not supported by this card due to firmware limitation. + Contract address copied! + Contract address + Required field + Please select the network + Decimal number must be a valid integer, no higher than %d + Contract address is invalid + Derivation path is invalid + Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. + This token/network has already been added to your list + Decimals + Network + Not selected + E.g. USD Coin + Name + E.g. USDC + Token symbol + BIP44 coin type + Default + The server is not available, please try again later + Total balance + The amount does not include some of your funds + Tokens + Manage tokens + No rate + Network is unreachable + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Retry + Chat + %s network + I understand + Yes + No + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + This network is not supported. Please select another network. + Card Settings + Security Mode + Change Access Code + Access code will be changed on this card only + Continue + Tangem Bot + Get your card ready! + Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. + Scan Card + App Settings + Keep the wallet in the app + Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. + Save Access Code + Biometric authentication will be requested instead of the access code for interactions with your card. + Removing the saved card deletes all the saved wallets and their access codes. + This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. + WalletConnect + Connect to Dapps + This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + I understand that after performing this action, I will no longer have access to the current wallet + Reset the Card + Reset to Factory Settings + Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Select network + Scan your card + To access all the networks you need to scan the card + Connection with this Dapp cannot be established due to its technical implementation. + Welcome back! + Use %s or scan a card to access the app + Log in with %s + Scan card + Access the app + Log into the app and check your balance without scanning the card + Access code + Note that making a transaction with your funds will still require your card + My Wallets + Multi-currency + Single-currency + Add new wallet + Rename Wallet + Wallet name + Unlock all with %s + Invalid Tag. It won\'t be added to the transaction. + Invalid Memo. It won\'t be added to the transaction. + Tag + Memo + Attention + Tap the card with the visa logo + No funds for activation + Please contact support + Four identical digits isn\'t safe + Such a PIN can be brute-forced easily + Pin code + Connect + KYC + Verify your identity + Set PIN code + Register + Verify via Utorg + Refresh + Connect your card + Verify your identity + KYC is in progress + Connect your card to the decentralized payment system + To start using your card you have to pass the KYC process + Please wait until the verification is completed. You\'ll be notified via email. Usually it takes up to 1 hour. You can close the app and come back later. + PIN Code + Set PIN code for your SaltPay card + Chat with support + Please hold the card until the operation complete + To start the backup process you have to add Tangem card as your backup + No backup card + Backup card ready + Finalize the backup process by creating an access code + Prepare the SaltPay card + Tap the SaltPay card + Tap the Tangem card + Support + Claim %s + To get started, simply claim wxDAI to your wallet + Claim + Congratulations! Your first payment crypto card has been activated! + Claiming + It will take a few seconds + Something went wrong + Please check your email for further instructions + Do you want to exit the activation process? + In this case, you will need to start from the beginning. + You have used a card from another wallet. Tap the card associated with this wallet + Referral program + Refer your friends to Tangem + You + Will get + for each wallet bought by your friend on your %s network address%s + Your friend + Will get a + %s discount + when buying a card on tangem.com + Your friends bought + Your personal code + Participate + terms and conditions + By tapping this button you accept + You\'ve accepted + of the referral program + Internal error: wallet manager not found + Balance: %s + Share + Copy + Success + Give Permission + To continue you need to allow 1inch smart contracts to use your %s + Amount %s + Your Wallet + Spender + Approve + Swap of %s to + Swap + Insufficient funds + Give Permission + Permit and Swap + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! + Buy Tangem Wallet with discount!\n%s + %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. + Invalid address + This wallet has already been saved, you can add another one + Delete + Privacy policy + Enable biometric authorization + It looks like you have biometric authentication disabled, it is necessary to save wallets + Enable + %d selected + biometric authentication + biometrics Tangem Save changes Warning @@ -54,7 +272,7 @@ Long Tap This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. Passcode - Before executing any command entailing a change of the card state you will have to enter the passcode. + Before executing any command entailing a change of the card state, you will have to enter the passcode. Access code You will have to submit the correct access code before scanning the card This card has been already topped up and signed transactions in the past. Consider immediate withdrawal of all funds if you have received this card from an untrusted source. If it\'s your card, there is nothing to worry about. @@ -97,7 +315,6 @@ Can\'t send a transaction Reason: %s Are you having difficulty scanning your card? - I\'m okay Request support Send feedback Really cool! @@ -216,7 +433,7 @@ Tangem feedback Feedback Tell us what functions you are missing, and we will try to help you. - Please tell us what card do you have? + Please tell us what card do you have Please tell us more about your issue. Every small detail can help. Hi support team, The following information is optional. You can erase it if you don\'t want to share it. @@ -226,6 +443,12 @@ Error OK Failed to establish WalletConnect session. Please, try again later. + Would you like to use biometrics? + Biometrics will be requested instead of the access code for interactions with your wallet + Allow to use biometrics + Enable biometric authentication + Go to settings to enable biometric authentication in the Tangem App + Scan the card %s diff --git a/core/res/src/main/res/values/strings_final.xml b/core/res/src/main/res/values/strings_final.xml deleted file mode 100644 index 673ced5d90..0000000000 --- a/core/res/src/main/res/values/strings_final.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - Tangem Wallet - 3 cards - 2 cards - Shipping - Free - I have a promo code… - Total - Other payment methods - Buy now - Meet\nTangem - Buy - Store - Send - Pay - Exchange - Lend - Borrow - Revolutionary Hardware Wallet - Store your crypto assets secure while keeping private keys contained in your card - Ultra Secure Backup - Up to - 3 physical cards - to one wallet - Thousands of Currencies - A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card - DeFi Compatible - Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services - The Wallet for Everyone - Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order - Search tokens - You are currently running in Demo mode. All funds are not real. - This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. - Available networks - %s network not found. Please, add it first and try again. - Attention - Note that tokens on different networks have different addresses. Double check that your address matches the network when you transfer funds. - Tokens in Solana network are not supported by this card due to firmware limitation. - Contract address copied! - Contract address - Required field - Please select the network - Decimal number must be a valid integer, no higher than %d - Contract address is invalid - Derivation path is invalid - Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing. - This token/network has already been added to your list - Decimals - Network - Not selected - E.g. USD Coin - Name - E.g. USDC - Token symbol - BIP44 coin type - Default - The server is not available, please try again later - Total balance - The amount does not include some of your funds - Tokens - Manage tokens - No rate - Network is unreachable - Hide token - Hide %s - Hide - You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. - Unable to hide %s - The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. - Your wallet has not been backed up - To protect your assets, we advise you to carry out this procedure - Retry - Chat - %s network - I understand - Yes - No - Russian bank cards are not accepted at the moment - Do you have a bank card of another country or a UnionPay card? - This network is not supported. Please select another network. - Card Settings - Security Mode - Change Access Code - Access code will be changed on this card only - Continue - Tangem Bot - Get your card ready! - Scan the card to change its settings. The changes will impact only the card you\'ve scanned and will not affect other cards tied to your wallet. - Scan Card - App Settings - Keep the wallet in the app - Enable to link all the wallets to Tangem app. Biometric authentication will be required for unlocking the app. Transaction signing requires tapping your Tangem card. - Save Access Code - Biometric authentication will be requested instead of the access code for interactions with your card. - Removing the saved card deletes all the saved wallets and their access codes. - This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. - WalletConnect - Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - I understand that after performing this action, I will no longer have access to the current wallet - Reset the Card - Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. - Select network - Scan your card - To access all the networks you need to scan the card - Connection with this Dapp cannot be established due to its technical implementation. - Welcome back! - Use %s or scan a card to access the app - Log in with %s - Scan card - Save your wallet - Would you like to use %s? - Would you like to use biometrics? - Access the app - Log into the app and check your balance without scanning the card - Access code - %s will be requested instead of the access code for interactions with your wallet - Biometrics will be requested instead of the access code for interactions with your wallet - Note that making a transaction with your funds will still require your card - Allow to use %s - Allow to use biometrics - New feature - My Wallets - Multi-currency - Single-currency - Add new wallet - Rename Wallet - Wallet name - Unlock all with %s - Invalid Tag. It won\'t be added to the transaction. - Invalid Memo. It won\'t be added to the transaction. - Tag - Memo - Attention - Tap the card with the visa logo - No funds for activation - Please contact support - Four identical digits isn\'t safe - Such a PIN can be brute-forced easily - Pin code - Connect - KYC - Verify your identity - Set PIN code - Register - Verify via Utorg - Refresh - Connect your card - Verify your identity - KYC is in progress - Connect your card to the decentralized payment system - To start using your card you have to pass the KYC process - Please wait until the verification is completed. Usually it takes up to 1 hour. You can close the app and come back later. - PIN Code - Set PIN code for your SaltPay card - Chat with support - Please hold the card until the operation complete - To start the backup process you have to add Tangem card as your backup - No backup card - Backup card ready - Finalize the backup process by creating an access code - Prepare the SaltPay card - Tap the SaltPay card - Tap the Tangem card - Support - Claim %s - To get started, simply claim wxDAI to your wallet - Claim - Congratulations! Your first payment crypto card has been activated - Claiming - It will take a few seconds - Something went wrong - Please check your email for further instructions - Do you want to exit the activation process? - In this case, you will need to start from the beginning. - You have used a card from another wallet. Tap the card associated with this wallet - Referral program - Refer your friends to Tangem - You - Will get - for each wallet bought by your friend on your %s network address%s - Your friend - Will get a - %s discount - when buying a card on tangem.com - Your friends bought - Your personal code - Participate - terms and conditions - By tapping this button you accept - You\'ve accepted - of the referral program - Internal error: wallet manager not found - Balance: %s - Share - Copy - Success - Give Permission - To continue you need to allow 1inch smart contracts to use your %s - Amount %s - Your Wallet - Spender - Approve - Swap of %s to - Swap - Insufficient funds - Give Permission - Permit and Swap - Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. - Personal code copied! - Buy Tangem Wallet with discount!\n%s - %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. - Invalid address - This wallet has already been saved, you can add another one - Delete - Privacy policy - Enable biometric authorization - It looks like you have biometric authentication disabled, it is necessary to save wallets - Enable - Save your Wallet feature allows you to use your wallet with biometric auth without tapping your card to the phone to gain access. - %d selected - biometric authentication - biometrics - Enable biometric authentication - Go to settings to enable biometric authentication in the Tangem App - Scan the card - diff --git a/core/res/src/main/res/values/strings_plurals_final.xml b/core/res/src/main/res/values/strings_plurals.xml similarity index 100% rename from core/res/src/main/res/values/strings_plurals_final.xml rename to core/res/src/main/res/values/strings_plurals.xml From a0d7410df266d4da85ed92a64481e3b3bb139d4b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 20 Dec 2022 11:37:56 +0300 Subject: [PATCH 173/176] Updated on 2026-08-14 --- .../cardsettings/CardSettingsScreenState.kt | 19 ++--- .../ui/cardsettings/CardSettingsViewModel.kt | 13 ++- .../details/ui/resetcard/ResetCardScreen.kt | 79 ++++++++----------- .../ui/resetcard/ResetCardScreenState.kt | 3 + .../ui/resetcard/ResetCardViewModel.kt | 6 ++ core/res/src/main/res/values-de/strings.xml | 6 +- core/res/src/main/res/values-fr/strings.xml | 6 +- core/res/src/main/res/values-it/strings.xml | 6 +- core/res/src/main/res/values-ru/strings.xml | 10 +-- core/res/src/main/res/values/strings.xml | 10 +-- 10 files changed, 83 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt index e7354df5c5..927780750e 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreenState.kt @@ -15,18 +15,18 @@ data class CardSettingsScreenState( ) sealed class CardInfo( - val titleRes: TextReference, val subtitle: TextReference, val clickable: Boolean = false, + val titleRes: TextReference, + val subtitle: TextReference, + val clickable: Boolean = false, ) { class CardId(subtitle: String) : CardInfo( titleRes = TextReference.Res(R.string.details_row_title_cid), - subtitle = TextReference - .Str(subtitle), + subtitle = TextReference.Str(subtitle), ) class Issuer(subtitle: String) : CardInfo( titleRes = TextReference.Res(R.string.details_row_title_issuer), - subtitle = TextReference - .Str(subtitle), + subtitle = TextReference.Str(subtitle), ) class SignedHashes(hashes: String) : CardInfo( @@ -34,10 +34,7 @@ sealed class CardInfo( subtitle = TextReference.Res(R.string.details_row_subtitle_signed_hashes_format, hashes), ) - class SecurityMode( - securityOption: SecurityOption, - clickable: Boolean, - ) : CardInfo( + class SecurityMode(securityOption: SecurityOption, clickable: Boolean) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_security_mode), subtitle = TextReference.Res(securityOption.toTitleRes()), clickable = clickable, @@ -49,9 +46,9 @@ sealed class CardInfo( clickable = true, ) - object ResetToFactorySettings : CardInfo( + class ResetToFactorySettings(subtitle: TextReference.Res) : CardInfo( titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory), - subtitle = TextReference.Res(R.string.card_settings_reset_card_to_factory_footer), + subtitle = subtitle, clickable = true, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index 44f5cdb8d2..ce9da7f8b0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -8,6 +8,7 @@ import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.wallet.R import org.rekotlin.Store class CardSettingsViewModel(private val store: Store) { @@ -46,7 +47,17 @@ class CardSettingsViewModel(private val store: Store) { cardDetails.add(CardInfo.ChangeAccessCode) } if (state.resetCardAllowed) { - cardDetails.add(CardInfo.ResetToFactorySettings) + cardDetails.add( + CardInfo.ResetToFactorySettings( + subtitle = TextReference.Res( + if (state.card.backupStatus?.isActive == true) { + R.string.reset_card_with_backup_to_factory_message + } else { + R.string.reset_card_without_backup_to_factory_message + }, + ), + ), + ) } CardSettingsScreenState( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 85d53a13eb..944e4c715f 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -32,77 +32,64 @@ import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R @Composable -fun ResetCardScreen( - state: ResetCardScreenState, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { - SettingsScreensScaffold( - content = { ResetCardView(state = state, modifier = modifier) }, - onBackClick = onBackPressed, - backgroundColor = Color.Transparent, - ) +fun ResetCardScreen(state: ResetCardScreenState, onBackPressed: () -> Unit) { + SettingsScreensScaffold( + content = { ResetCardView(state = state) }, + onBackClick = onBackPressed, + backgroundColor = Color.Transparent, + ) } @Composable -fun ResetCardView( - state: ResetCardScreenState, - modifier: Modifier = Modifier, -) { +fun ResetCardView(state: ResetCardScreenState) { Column( - modifier = modifier + modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.SpaceBetween, ) { - Box( - modifier = modifier, - ) { + Box { Image( painter = painterResource(id = R.drawable.ic_reset_background), - contentDescription = "", - modifier = modifier.offset(y = (-82).dp), + contentDescription = null, + modifier = Modifier.offset(y = (-82).dp), ) ScreenTitle(titleRes = R.string.card_settings_reset_card_to_factory) } - Spacer( - modifier = modifier.weight(1f), - ) + Spacer(modifier = Modifier.weight(1f)) Column( - modifier = modifier - .offset(y = (-32).dp), + modifier = Modifier.offset(y = (-32).dp), verticalArrangement = Arrangement.Bottom, ) { Text( text = stringResource(id = R.string.common_attention), - modifier = modifier.padding(start = 20.dp, end = 20.dp), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), style = TangemTypography.headline3, color = colorResource(id = R.color.text_primary_1), ) - Spacer(modifier = modifier.size(24.dp)) + Spacer(modifier = Modifier.size(24.dp)) Text( - text = stringResource(id = R.string.reset_card_to_factory_message), - modifier = modifier - .padding(start = 20.dp, end = 20.dp), + text = stringResource(id = state.descriptionResId), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), style = TangemTypography.body1, color = colorResource(id = R.color.text_secondary), ) - Spacer(modifier = modifier.size(28.dp)) + Spacer(modifier = Modifier.size(28.dp)) Row( - modifier = modifier + modifier = Modifier .fillMaxWidth() .clickable( - onClick = { state.onAcceptWarningToggleClick(!state.accepted) }, - ) + onClick = { state.onAcceptWarningToggleClick(!state.accepted) }, + ) .padding(top = 16.dp, bottom = 16.dp), ) { IconToggleButton( checked = state.accepted, onCheckedChange = state.onAcceptWarningToggleClick, - modifier = modifier.padding(start = 20.dp, end = 20.dp), + modifier = Modifier.padding(start = 20.dp, end = 20.dp), ) { Icon( painter = painterResource( @@ -124,15 +111,13 @@ fun ResetCardView( text = stringResource(id = R.string.reset_card_to_factory_warning_message), style = TangemTypography.body2, color = colorResource(id = R.color.text_secondary), - modifier = modifier - .padding(end = 20.dp), + modifier = Modifier.padding(end = 20.dp), ) } - Spacer(modifier = modifier.size(16.dp)) + Spacer(modifier = Modifier.size(16.dp)) Box( - modifier = modifier - .padding(start = 16.dp, end = 16.dp, bottom = 32.dp), + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 32.dp), ) { DetailsMainButton( title = stringResource(id = R.string.reset_card_to_factory_button_title), @@ -146,8 +131,14 @@ fun ResetCardView( @Composable @Preview -fun ResetCardScreenPreview( - -) { - ResetCardScreen(state = ResetCardScreenState(onAcceptWarningToggleClick = {}, accepted = true) {}, {}) +fun ResetCardScreenPreview() { + ResetCardScreen( + state = ResetCardScreenState( + descriptionResId = R.string.reset_card_without_backup_to_factory_message, + accepted = false, + onAcceptWarningToggleClick = {}, + onResetButtonClick = {}, + ), + onBackPressed = {}, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt index f658eac463..54693e7557 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt @@ -1,6 +1,9 @@ package com.tangem.tap.features.details.ui.resetcard +import androidx.annotation.StringRes + data class ResetCardScreenState( + @StringRes val descriptionResId: Int, val accepted: Boolean = false, val onAcceptWarningToggleClick: (Boolean) -> Unit, val onResetButtonClick: () -> Unit, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index 15f891dba3..1606631d68 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -3,12 +3,18 @@ package com.tangem.tap.features.details.ui.resetcard import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.wallet.R import org.rekotlin.Store class ResetCardViewModel(private val store: Store) { fun updateState(state: CardSettingsState?): ResetCardScreenState { return ResetCardScreenState( + descriptionResId = if (state?.card?.backupStatus?.isActive == true) { + R.string.reset_card_with_backup_to_factory_message + } else { + R.string.reset_card_without_backup_to_factory_message + }, accepted = state?.resetConfirmed ?: false, onAcceptWarningToggleClick = { store.dispatch(DetailsAction.ResetToFactory.Confirm(it)) }, onResetButtonClick = { store.dispatch(DetailsAction.ResetToFactory.Proceed) }, diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 9f2035db71..0bee218f2b 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,7 +279,7 @@ Diese Karte ist für die Zusammenarbeit mit Tangem nicht geeignet Die von Ihnen gescannte Karte ist eine Entwicklungskarte. Akzeptieren Sie sie nicht als Zahlungsmittel. Tippen um zu signieren - To create wallet, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation Tippen Sie um den Zugangscode zu ändern Tippen Sie um den Passcode zu ändern Nutzungsbedingungen diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index cf498616e2..237e0965f6 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,7 +279,7 @@ Cette carte n\'est pas conçue pour fonctionner avec Tangem La carte que vous avez scannée est une carte de développement. Ne l\'acceptez pas comme paiement. Touchez pour signer - To create wallet, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation Touchez, pour modifier le code d\'accès Touchez, pour modifier le mot de passe Conditions d\'utilisation diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index af7ce360a9..d6b0c63ee0 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,7 +279,7 @@ Questa carta non è progettata per funzionare con Tangem La carta che hai scansionato è una carta di sviluppo. Non utilizzarla come strumento di pagamento. Avvicina per firmare - To create wallet, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation Avvicina per modificare il codice di accesso Avvicina per modificare la password Termini del servizio diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index a715487104..177687671b 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -99,11 +99,11 @@ Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком. WalletConnect Подключение к Dapps - Это действие приведет к полному удалению кошелька на этой карте. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа + Сброс к заводским настройкам приведет к полному удалению кошелька на этой карте, а также отвязыванию карты из приложения. Кошелек невозможно будет восстановить. + Сброс к заводским настройкам приведет к полному удалению кошелька на этой карте, а также отвязыванию карты из приложения. Кошелек невозможно будет восстановить или использовать данную карту для восстановления кода доступа. Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку Сбросить карту Сброс к заводским настройкам - Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа. Выберите сеть Отсканируйте карту Чтобы получить доступ ко всем сетям, вам необходимо отсканировать карту @@ -279,9 +279,9 @@ Эта карта не предназначена для работы с этим приложением Карта, которую вы отсканировали, является картой разработчика. Не принимайте её в качестве оплаты. Нажмите, чтобы подписать - Чтобы создать кошелек, соедините телефон и карту в точности, как показано выше. - Чтобы изменить код доступа, соедините телефон и карту в точности, как показано выше. - Чтобы изменить пароль, соедините телефон и карту в точности, как показано выше. + Чтобы создать кошелек, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить код доступа, приложите карту как показано выше и не убирайте до окончания операции + Чтобы изменить пароль, приложите карту как показано выше и не убирайте до окончания операции Условия использования Нет соединения с интернетом PayString не поддерживается блокчейном diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 4027017a83..f2d00c18af 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -99,11 +99,11 @@ This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code. WalletConnect Connect to Dapps - This action will completely remove the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet. + Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet or use the card to recover the access code. I understand that after performing this action, I will no longer have access to the current wallet Reset the Card Reset to Factory Settings - Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code. Select network Scan your card To access all the networks you need to scan the card @@ -279,9 +279,9 @@ This card is not designed to work with this app The card you scanned is a development card. Don\'t accept it as a payment. Tap to sign - To create wallet, connect your phone and the card exactly as it shown above - To change the access code, connect your phone and the card exactly as it shown above - To change the passcode, connect your phone and the card exactly as it shown above + To create the wallet tap the card as shown above and do not remove until the end of the operation + To change the access code tap the card as shown above and do not remove until the end of the operation + To change the passcode tap the card as shown above and do not remove until the end of the operation Terms of Service No internet connection PayString unsupported by blockchain From 3e0385dc7f5977eb2d09c18344e41704e5ce42af Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 15 Dec 2022 10:40:02 +0300 Subject: [PATCH 174/176] Updated on 2026-08-14 --- .../redux/reducers/MultiWalletReducer.kt | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 7cb35da559..afb523a5ca 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -1,12 +1,15 @@ package com.tangem.tap.features.wallet.redux.reducers +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager -import com.tangem.common.extensions.guard +import com.tangem.tap.common.extensions.dispatchToastNotification import com.tangem.tap.common.extensions.toFiatString import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency @@ -25,6 +28,8 @@ import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.store +import com.tangem.tap.userWalletsListManager +import com.tangem.wallet.R import java.math.BigDecimal class MultiWalletReducer { @@ -106,16 +111,22 @@ class MultiWalletReducer { newState } } - is WalletAction.MultiWallet.AddTokens -> { - addTokens(action.tokens, action.blockchain, state) - } - is WalletAction.MultiWallet.AddToken -> { - addTokens(listOf(action.token), action.blockchain, state) - } + is WalletAction.MultiWallet.AddTokens -> addTokens(action.tokens, action.blockchain, state) + is WalletAction.MultiWallet.AddToken -> addTokens(listOf(action.token), action.blockchain, state) is WalletAction.MultiWallet.TokenLoaded -> { val currency = Currency.fromBlockchainNetwork(action.blockchain, action.token) - val walletManager = state.getWalletManager(currency).guard { - throw NullPointerException("MultiWallet.TokenLoaded: WalletManager must be not NULL") + val walletManager = state.getWalletManager(currency) + if (walletManager == null) { + if (userWalletsListManager.hasSavedUserWallets) { + store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Welcome)) + } else { + store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home)) + } + FirebaseCrashlytics.getInstance().recordException( + IllegalStateException("MultiWallet.TokenLoaded: walletManager is null"), + ) + store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found) + return state } val wallet = walletManager.wallet val pendingTransactions = wallet.getPendingTransactions() @@ -170,41 +181,28 @@ class MultiWalletReducer { action.currencies.forEach { updatedState = updatedState.removeWalletData(state.getWalletData(it)) } updatedState } - is WalletAction.MultiWallet.SetPrimaryBlockchain -> - state.copy(primaryBlockchain = action.blockchain) - - is WalletAction.MultiWallet.SetPrimaryToken -> - state.copy(primaryToken = action.token) + is WalletAction.MultiWallet.SetPrimaryBlockchain -> state.copy(primaryBlockchain = action.blockchain) + is WalletAction.MultiWallet.SetPrimaryToken -> state.copy(primaryToken = action.token) is WalletAction.MultiWallet.SaveCurrencies -> state - is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy( - showBackupWarning = action.show, - ) - is WalletAction.MultiWallet.AddMissingDerivations -> state.copy( - missingDerivations = action.blockchains, - ) + is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy(showBackupWarning = action.show) + is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(missingDerivations = action.blockchains) is WalletAction.MultiWallet.BackupWallet -> state - is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy( - state = ProgressState.Loading, - ) + is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy(state = ProgressState.Loading) } } private fun findWalletRent(walletStore: WalletStore?): WalletRent? { - return walletStore?.walletsData?.firstOrNull { - it.walletRent != null - }?.walletRent + return walletStore?.walletsData?.firstOrNull { it.walletRent != null }?.walletRent } private fun getExistentialDeposit(walletManager: WalletManager?): String? { return (walletManager as? ExistentialDepositProvider)?.getExistentialDeposit()?.toPlainString() } -} -private fun addTokens( - tokens: List, blockchain: BlockchainNetwork, state: WalletState, -): WalletState { - val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) } - return state.updateWalletsData(wallets) + private fun addTokens(tokens: List, blockchain: BlockchainNetwork, state: WalletState): WalletState { + val wallets = tokens.mapNotNull { token -> token.toWallet(state, blockchain) } + return state.updateWalletsData(wallets) + } } fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletData? { From 7d56fc1efa8ccc56ff5c80a049bc975fe1a5702c Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 15:28:55 +0300 Subject: [PATCH 175/176] Updated on 2026-08-14 --- .../tap/common/redux/navigation/NavigationMiddleware.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 871ef1ea5c..28357c1efd 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -57,11 +57,8 @@ val navigationMiddleware: Middleware = { _, state -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { Settings.ACTION_BIOMETRIC_ENROLL } - Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> { - Settings.ACTION_FINGERPRINT_ENROLL - } else -> { - Settings.ACTION_SETTINGS + Settings.ACTION_SECURITY_SETTINGS } } val intent = Intent(settingsAction).apply { From 92b721ecad128e62d3aa03d35b8e52b3ec4a421e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Dec 2022 16:29:09 +0400 Subject: [PATCH 176/176] Updated on 2026-08-14 --- .../tangem/tap/features/wallet/ui/WalletFragment.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 8a67196de1..f01805dd39 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.wallet.ui +import android.content.Context import android.os.Bundle import android.view.Menu import android.view.MenuInflater @@ -9,6 +10,7 @@ import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.transition.TransitionInflater @@ -60,6 +62,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber() + override fun onAttach(context: Context) { + super.onAttach(context) + activity?.lifecycleScope?.launchWhenCreated { + viewModel.launch() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -82,7 +91,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber