From ee1ad7a2c347774a47c25b67a2c07976a5b7a480 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 12 Feb 2023 18:25:51 +0300 Subject: [PATCH 01/46] Updated on 2026-08-14 --- .../common/redux/global/GlobalMidlleware.kt | 92 +- .../middlewares/TradeCryptoMiddleware.kt | 18 +- .../tap/features/wallet/ui/BalanceWidget.kt | 3 +- .../wallet/ui/WalletDetailsFragment.kt | 3 +- .../exchangeServices/BuyExchangeService.kt | 62 + .../CurrencyExchangeManager.kt | 15 +- .../exchangeServices/ExchangeService.kt | 20 +- .../exchangeServices/mercuryo/MercuryoApi.kt | 7 +- .../mercuryo/MercuryoEnvironment.kt | 36 + .../mercuryo/MercuryoService.kt | 24 +- .../moonpay/MoonPayService.kt | 10 +- .../utorg/UtorgEnvironment.kt | 88 ++ .../utorg/UtorgExchangeService.kt | 116 ++ .../exchangeServices/utorg/api/UtorgApi.kt | 67 ++ .../utorg/api/model/UtorgCurrencyResponse.kt | 25 + .../utorg/mock/MockUtorgApi.kt | 58 + .../mock/MockUtorgSuccessDataResponse.kt | 12 + .../utorg/mock/json/CurrencyResponseJson.kt | 1025 +++++++++++++++++ .../com/tangem/domain/common/LogConfig.kt | 1 + 19 files changed, 1607 insertions(+), 75 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt create mode 100644 app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt 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 dfcdf942bf..0887023b32 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 @@ -3,28 +3,37 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard -import com.tangem.common.extensions.ifNotNull import com.tangem.common.services.Result +import com.tangem.domain.common.CardDTO import com.tangem.domain.common.LogConfig +import com.tangem.domain.common.ProductType +import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState +import com.tangem.tap.domain.configurable.config.Config import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.network.exchangeServices.BuyExchangeService import com.tangem.tap.network.exchangeServices.CardExchangeRules import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoApi +import com.tangem.tap.network.exchangeServices.ExchangeService +import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService +import com.tangem.tap.network.exchangeServices.utorg.UtorgAuthProvider +import com.tangem.tap.network.exchangeServices.utorg.UtorgEnvironment +import com.tangem.tap.network.exchangeServices.utorg.UtorgExchangeService 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.BuildConfig import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.DispatchFunction @@ -121,38 +130,23 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di } is GlobalAction.ExchangeManager.Init -> { val appStateSafe = appState() ?: return - val config = appStateSafe.globalState.configManager?.config - ifNotNull( - config?.mercuryoWidgetId, - config?.mercuryoSecret, - config?.moonPayApiKey, - config?.moonPayApiSecretKey, - ) { mercuryoWidgetId, mercuryoSecret, moonPayKey, moonPaySecretKey -> - scope.launch { - val buyService = MercuryoService( - apiVersion = MercuryoApi.API_VERSION, - mercuryoWidgetId = mercuryoWidgetId, - secret = mercuryoSecret, - logEnabled = LogConfig.network.mercuryoService, - ) - val sellService = MoonPayService( - apiKey = moonPayKey, - secretKey = moonPaySecretKey, - logEnabled = LogConfig.network.moonPayService, - ) - val cardProvider = { - store.state.globalState.scanResponse?.card - ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse?.card - } + val config = appStateSafe.globalState.configManager?.config ?: return - val exchangeManager = CurrencyExchangeManager( - buyService = buyService, - sellService = sellService, - primaryRules = CardExchangeRules(cardProvider), - ) - store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager)) - store.dispatchOnMain(GlobalAction.ExchangeManager.Update) + scope.launch { + val scanResponseProvider: () -> ScanResponse? = { + store.state.globalState.scanResponse + ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse } + val productTypeProvider: () -> ProductType? = { scanResponseProvider.invoke()?.productType } + val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card } + + val exchangeManager = CurrencyExchangeManager( + buyService = makeBuyExchangeService(config, productTypeProvider), + sellService = makeSellExchangeService(config), + primaryRules = CardExchangeRules(cardProvider), + ) + store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager)) + store.dispatchOnMain(GlobalAction.ExchangeManager.Update) } } is GlobalAction.ExchangeManager.Init.Success -> {} @@ -207,4 +201,38 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di } } } +} + +private fun makeSellExchangeService(config: Config): ExchangeService { + return MoonPayService( + apiKey = config.moonPayApiKey, + secretKey = config.moonPayApiSecretKey, + logEnabled = LogConfig.network.moonPayService, + ) +} + +private fun makeBuyExchangeService(config: Config, productTypeProvider: () -> ProductType?): ExchangeService { + return BuyExchangeService( + productTypeProvider = productTypeProvider, + mercuryoService = makeMercuryoExchangeService(config), + utorgService = makeUtorgExchangeService(config), + ) +} + +private fun makeMercuryoExchangeService(config: Config): MercuryoService { + val mercuryoEnvironment = MercuryoEnvironment.prod(config.mercuryoWidgetId, config.mercuryoSecret) + return MercuryoService(mercuryoEnvironment) +} + +private fun makeUtorgExchangeService(config: Config): UtorgExchangeService { + val saltPayConfig = requireNotNull(config.saltPayConfig) + + val utorgAuthProvider = UtorgAuthProvider(saltPayConfig.kycProvider.sidValue) + val utorgEnvironment = if (BuildConfig.DEBUG) { + UtorgEnvironment.stage(utorgAuthProvider, LogConfig.network.utorgService) + // UtorgEnvironment.mock() + } else { + UtorgEnvironment.prod(utorgAuthProvider) + } + return UtorgExchangeService(utorgEnvironment) } \ No newline at end of file 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 33b7f54788..e896251b09 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,7 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.AmountType +import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token @@ -17,6 +18,7 @@ import com.tangem.tap.features.send.redux.PrepareSendScreen import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens import com.tangem.tap.scope @@ -40,10 +42,9 @@ class TradeCryptoMiddleware { action: WalletAction.TradeCryptoAction.Buy, ) { val selectedWalletData = store.state.walletState.selectedWalletData ?: return + val currency = chooseAppropriateCurrency(store.state.walletState) ?: return - val currency = selectedWalletData.currency Analytics.send(Token.ButtonBuy(AnalyticsParam.CurrencyType.Currency(currency))) - if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) { store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog()) return @@ -87,12 +88,12 @@ class TradeCryptoMiddleware { private fun proceedSellAction() { val selectedWalletData = store.state.walletState.selectedWalletData ?: return + val currency = chooseAppropriateCurrency(store.state.walletState) ?: return val appCurrency = store.state.globalState.appCurrency val addresses = selectedWalletData.walletAddresses?.list.orEmpty() if (addresses.isEmpty()) return - val currency = selectedWalletData.currency Analytics.send(Token.ButtonSell(AnalyticsParam.CurrencyType.Currency(currency))) store.state.globalState.exchangeManager.getUrl( @@ -107,6 +108,17 @@ class TradeCryptoMiddleware { } } + private fun chooseAppropriateCurrency(walletState: WalletState): Currency? { + return if (walletState.primaryTokenData == null) { + walletState.selectedWalletData?.currency + } else { + walletState.primaryTokenData?.currency as? Currency.Token + }.guard { + store.dispatchDebugErrorNotification("Can't select an appropriate currency for a Trade action") + return null + } + } + private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { val selectedWalletData = store.state.walletState.selectedWalletData ?: return 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 53d576c61d..9ffd48f58e 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 @@ -119,7 +119,8 @@ class BalanceWidget( tvErrorDescriptions.text = fragment.getString( R.string.no_account_generic, - data.amountToCreateAccount, data.currencySymbol, + data.amountToCreateAccount, + data.currencySymbol, ) } BalanceStatus.UnknownBlockchain -> with(binding.lBalanceError) { 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 e38d9d947b..ba1fd0b30f 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 @@ -435,7 +435,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalanceError.tvErrorDescriptions.text = getString( R.string.no_account_generic, - data.amountToCreateAccount, data.currencySymbol, + data.amountToCreateAccount, + data.currencySymbol, ) } } diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt new file mode 100644 index 0000000000..d77245b45c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt @@ -0,0 +1,62 @@ +package com.tangem.tap.network.exchangeServices + +import com.tangem.blockchain.common.Blockchain +import com.tangem.domain.common.ProductType +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService +import com.tangem.tap.network.exchangeServices.utorg.UtorgExchangeService +import com.tangem.tap.scope +import kotlinx.coroutines.launch + +/** +[REDACTED_AUTHOR] + * Temporary wrapper for the buy services. Service switches based on selected product type. + * Just now - UtorgService used only for the SaltPay cards + */ +class BuyExchangeService( + private val productTypeProvider: () -> ProductType?, + private val mercuryoService: MercuryoService, + private val utorgService: UtorgExchangeService, +) : ExchangeService, ExchangeUrlBuilder { + + init { + scope.launch { + mercuryoService.update() + utorgService.update() + } + } + + private val currentService: ExchangeService + get() = when (productTypeProvider.invoke()) { + ProductType.SaltPay -> utorgService + else -> mercuryoService + } + + override suspend fun update() { + currentService.update() + } + + override fun featureIsSwitchedOn(): Boolean = currentService.featureIsSwitchedOn() + + override fun isBuyAllowed(): Boolean = currentService.isBuyAllowed() + + override fun isSellAllowed(): Boolean = currentService.isSellAllowed() + + override fun availableForBuy(currency: Currency): Boolean = currentService.availableForBuy(currency) + + override fun availableForSell(currency: Currency): Boolean = currentService.availableForSell(currency) + + override fun getUrl( + action: CurrencyExchangeManager.Action, + blockchain: Blockchain, + cryptoCurrencyName: String, + fiatCurrencyName: String, + walletAddress: String, + ): String? { + return currentService.getUrl(action, blockchain, cryptoCurrencyName, fiatCurrencyName, walletAddress) + } + + override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? { + return currentService.getSellCryptoReceiptUrl(action, transactionId) + } +} \ No newline at end of file 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 3f61d82801..656343a092 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 @@ -24,7 +24,7 @@ class CurrencyExchangeManager( private val buyService: ExchangeService, private val sellService: ExchangeService, private val primaryRules: ExchangeRules, -) : ExchangeService, ExchangeUrlBuilder { +) : ExchangeService { override fun featureIsSwitchedOn(): Boolean = primaryRules.featureIsSwitchedOn() @@ -59,7 +59,7 @@ class CurrencyExchangeManager( blockchain, cryptoCurrencyName, fiatCurrencyName, - walletAddress + walletAddress, ) } @@ -97,8 +97,11 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( val amountToSend = Amount(walletManager.wallet.blockchain) val destinationAddress = token.contractAddress - val feeResult = walletManager.getFee(amountToSend, - destinationAddress) as? Result.Success ?: return + val feeResult = + walletManager.getFee( + amountToSend, + destinationAddress, + ) as? Result.Success ?: return val fee = feeResult.data[0] if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) { @@ -116,8 +119,8 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( GlobalAction.UpdateWalletSignedHashes( walletSignedHashes = signResponse.totalSignedHashes, walletPublicKey = walletManager.wallet.publicKey.seedKey, - remainingSignatures = signResponse.remainingSignatures - ) + remainingSignatures = signResponse.remainingSignatures, + ), ) } walletManager.send(transaction, signer) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt index 8d6e8e2187..f38a8c64a1 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt @@ -7,11 +7,11 @@ import com.tangem.tap.features.wallet.models.Currency interface Exchanger { fun isBuyAllowed(): Boolean fun isSellAllowed(): Boolean - fun availableForBuy(currency: Currency):Boolean - fun availableForSell(currency: Currency):Boolean + fun availableForBuy(currency: Currency): Boolean + fun availableForSell(currency: Currency): Boolean } -interface ExchangeService: Feature, Exchanger { +interface ExchangeService : Feature, Exchanger, ExchangeUrlBuilder { suspend fun update() companion object { @@ -22,11 +22,23 @@ interface ExchangeService: Feature, Exchanger { override fun isSellAllowed(): Boolean = false override fun availableForBuy(currency: Currency): Boolean = false override fun availableForSell(currency: Currency): Boolean = false + override fun getUrl( + action: CurrencyExchangeManager.Action, + blockchain: Blockchain, + cryptoCurrencyName: String, + fiatCurrencyName: String, + walletAddress: String, + ): String? = null + + override fun getSellCryptoReceiptUrl( + action: CurrencyExchangeManager.Action, + transactionId: String, + ): String? = null } } } -interface ExchangeRules: Feature, Exchanger { +interface ExchangeRules : Feature, Exchanger { companion object { fun dummy(): ExchangeRules = object : ExchangeRules { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt index 87565c3640..89308da1ce 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt @@ -11,11 +11,6 @@ interface MercuryoApi { suspend fun currencies( @Path("apiVersion") apiVersion: String, ): MercuryoCurrenciesResponse - - companion object { - const val BASE_URL = "https://api.mercuryo.io/" - const val API_VERSION = "v1.6" - } } data class MercuryoCurrenciesResponse( @@ -25,7 +20,7 @@ data class MercuryoCurrenciesResponse( data class Data( val fiat: List, val crypto: List, - val config: Config + val config: Config, ) data class Config( diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt new file mode 100644 index 0000000000..af3cda029a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt @@ -0,0 +1,36 @@ +package com.tangem.tap.network.exchangeServices.mercuryo + +import com.tangem.datasource.api.common.createRetrofitInstance + +/** +[REDACTED_AUTHOR] + */ +data class MercuryoEnvironment( + val baseUrl: String, + val apiVersion: String, + val widgetId: String, + val secret: String, + val mercuryoApi: MercuryoApi, +) { + companion object { + private const val BASE_URL = "https://api.mercuryo.io/" + private const val API_VERSION = "v1.6" + + fun prod( + widgetId: String, + secret: String, + apiVersion: String = API_VERSION, + ): MercuryoEnvironment { + return MercuryoEnvironment( + baseUrl = BASE_URL, + apiVersion = apiVersion, + widgetId = widgetId, + secret = secret, + mercuryoApi = createRetrofitInstance( + baseUrl = BASE_URL, + logEnabled = false, + ).create(MercuryoApi::class.java), + ) + } + } +} \ No newline at end of file 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 9dd2b9746f..64f2ae121a 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,6 @@ 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.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 @@ -17,16 +16,10 @@ import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder [REDACTED_AUTHOR] */ class MercuryoService( - private val apiVersion: String, - private val mercuryoWidgetId: String, - private val secret: String, - private val logEnabled: Boolean, -) : ExchangeService, ExchangeUrlBuilder { + private val environment: MercuryoEnvironment, +) : ExchangeService { - private val api: MercuryoApi = createRetrofitInstance( - baseUrl = MercuryoApi.BASE_URL, - logEnabled = logEnabled, - ).create(MercuryoApi::class.java) + private val api: MercuryoApi = environment.mercuryoApi private val blockchainsAvailableToBuy = mutableListOf() private val tokensAvailableToBy = mutableMapOf>() @@ -34,7 +27,7 @@ class MercuryoService( override fun featureIsSwitchedOn(): Boolean = true override suspend fun update() { - when (val result = performRequest { api.currencies(apiVersion) }) { + when (val result = performRequest { api.currencies(environment.apiVersion) }) { is Result.Success -> { val response = result.data if (response.status == 200) { @@ -108,14 +101,14 @@ class MercuryoService( blockchain: Blockchain, cryptoCurrencyName: CryptoCurrencyName, fiatCurrencyName: String, - walletAddress: String + walletAddress: String, ): String { if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException() val builder = Uri.Builder() .scheme(ExchangeUrlBuilder.SCHEME) .authority("exchange.mercuryo.io") - .appendQueryParameter("widget_id", mercuryoWidgetId) + .appendQueryParameter("widget_id", environment.widgetId) .appendQueryParameter("type", action.name.lowercase()) .appendQueryParameter("currency", cryptoCurrencyName) .appendQueryParameter("address", walletAddress) @@ -128,13 +121,12 @@ class MercuryoService( } private fun signature(address: String): String { - return (address + secret).calculateSha512().toHexString().lowercase() + return (address + environment.secret).calculateSha512().toHexString().lowercase() } - override fun getSellCryptoReceiptUrl( action: CurrencyExchangeManager.Action, - transactionId: String + transactionId: String, ): String? = null private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) { 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 645d5182ed..0754f6c305 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 @@ -5,14 +5,13 @@ import android.util.Base64 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.datasource.api.common.createRetrofitInstance +import com.tangem.domain.common.extensions.withIOContext import com.tangem.tap.common.extensions.urlEncode import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.ExchangeService -import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec @@ -21,7 +20,7 @@ class MoonPayService( private val apiKey: String, private val secretKey: String, private val logEnabled: Boolean, -) : ExchangeService, ExchangeUrlBuilder { +) : ExchangeService { private val api: MoonPayApi by lazy { createRetrofitInstance( @@ -103,7 +102,7 @@ class MoonPayService( blockchain: Blockchain, cryptoCurrencyName: CryptoCurrencyName, fatCurrency: String, - walletAddress: String + walletAddress: String, ): String? { if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException() @@ -130,7 +129,6 @@ class MoonPayService( .appendPath("transaction_receipt") .appendQueryParameter("transactionId", transactionId).build().toString() return url - } private fun createSignature(data: String): String { @@ -149,5 +147,5 @@ class MoonPayService( private data class MoonPayStatus( val availableForSell: List, val responseUserStatus: MoonPayUserStatus, - val responseCurrencies: List + val responseCurrencies: List, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt new file mode 100644 index 0000000000..b31e8aa8bf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt @@ -0,0 +1,88 @@ +package com.tangem.tap.network.exchangeServices.utorg + +import android.net.Uri +import com.tangem.datasource.api.common.createRetrofitInstance +import com.tangem.tap.network.exchangeServices.utorg.api.UtorgApi +import com.tangem.tap.network.exchangeServices.utorg.mock.MockUtorgApi +import okhttp3.Interceptor +import okhttp3.Response +import java.util.* + +/** +[REDACTED_AUTHOR] + */ +data class UtorgEnvironment( + val baseUri: Uri, + val sidValue: String, + val apiVersion: String, + val utorgApi: UtorgApi, + val successUrl: String = "https://success.tangem.com", +) { + companion object { + private val PROD_BASE_URL = Uri.parse("https://app.utorg.pro") + private const val PROD_VERSION = "v1" + + private val STAGE_BASE_URL = Uri.parse("https://app-stage.utorg.pro") + private const val STAGE_VERSION = "v1" + + fun prod( + authProvider: UtorgAuthProvider, + apiVersion: String = PROD_VERSION, + ): UtorgEnvironment = UtorgEnvironment( + baseUri = PROD_BASE_URL, + sidValue = authProvider.sidValue, + apiVersion = apiVersion, + utorgApi = createApi(PROD_BASE_URL, authProvider, false), + ) + + fun stage( + authProvider: UtorgAuthProvider, + logEnabled: Boolean, + apiVersion: String = STAGE_VERSION, + ): UtorgEnvironment = UtorgEnvironment( + baseUri = STAGE_BASE_URL, + sidValue = authProvider.sidValue, + apiVersion = apiVersion, + utorgApi = createApi(STAGE_BASE_URL, authProvider, logEnabled), + ) + + fun mock(): UtorgEnvironment = UtorgEnvironment( + baseUri = Uri.EMPTY, + sidValue = "", + apiVersion = "", + utorgApi = MockUtorgApi(), + ) + + private fun createApi(baseUri: Uri, authProvider: UtorgAuthProvider, logEnabled: Boolean): UtorgApi { + return createRetrofitInstance( + baseUrl = "$baseUri/", + interceptors = listOf(UtorgAuthHeaderInterceptor(authProvider)), + logEnabled = logEnabled, + ).create(UtorgApi::class.java) + } + } +} + +data class UtorgAuthProvider( + val sidValue: String, + val headerSidKey: String = "X-AUTH-SID", + val headerNonceKey: String = "X-AUTH-NONCE", +) { + val clientNonce: String + get() = UUID.randomUUID().toString() +} + +private class UtorgAuthHeaderInterceptor( + private val authProvider: UtorgAuthProvider, +) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + .newBuilder() + .addHeader(authProvider.headerSidKey, authProvider.sidValue) + .addHeader(authProvider.headerNonceKey, authProvider.clientNonce) + .build() + + return chain.proceed(request) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt new file mode 100644 index 0000000000..0366471b64 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt @@ -0,0 +1,116 @@ +package com.tangem.tap.network.exchangeServices.utorg + +import android.net.Uri +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.services.Result +import com.tangem.common.services.performRequest +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager +import com.tangem.tap.network.exchangeServices.ExchangeService +import com.tangem.tap.network.exchangeServices.utorg.api.RequestSuccessUrl +import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyData +import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyType +import com.tangem.utils.converter.Converter + +/** +[REDACTED_AUTHOR] + */ +class UtorgExchangeService( + private val environment: UtorgEnvironment, + private val currencyConverter: Converter = ChainToBlockchainConverter(), +) : ExchangeService { + + private val api = environment.utorgApi + + private val utorgCurrencies = mutableListOf() + + override fun featureIsSwitchedOn(): Boolean = true + + override suspend fun update() { + when (val result = performRequest { api.getCurrency(environment.apiVersion) }) { + is Result.Success -> { + if (!result.data.isSuccess()) return + + val utorgCryptos = result.data.toSuccess().data.filter { + it.enabled && it.type == UtorgCurrencyType.CRYPTO + } + utorgCurrencies.clear() + utorgCurrencies.addAll(utorgCryptos) + + performRequest { + api.setSuccessUrl(environment.apiVersion, RequestSuccessUrl(environment.successUrl)) + } + } + is Result.Failure -> { + utorgCurrencies.clear() + } + } + } + + override fun isBuyAllowed(): Boolean = true + + override fun isSellAllowed(): Boolean = false + + override fun availableForBuy(currency: Currency): Boolean { + return true + if (!isBuyAllowed()) return false + + val foundUtorgCurrency = utorgCurrencies.firstOrNull { utorgCurrency -> + val utorgBlockchain = currencyConverter.convert(utorgCurrency) ?: return@firstOrNull false + + val isSameBlockchain = utorgBlockchain == currency.blockchain + val isSameSymbol = utorgCurrency.symbol.lowercase() == currency.currencySymbol.lowercase() + isSameBlockchain && isSameSymbol + } + + return foundUtorgCurrency != null + } + + override fun availableForSell(currency: Currency): Boolean = false + + override fun getUrl( + action: CurrencyExchangeManager.Action, + blockchain: Blockchain, + cryptoCurrencyName: CryptoCurrencyName, + fiatCurrencyName: String, + walletAddress: String, + ): String { + if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException() + + // https://app-stage.utorg.pro/direct/testSID/mvmeaWyWiuVYdZdySgofAt6CmKhJbRhaWA/?¤cy=BTC + val builder = Uri.Builder() + .scheme(environment.baseUri.scheme) + .authority(environment.baseUri.authority) + .appendPath("direct") + .appendPath(environment.sidValue) + .appendPath(walletAddress) + .appendQueryParameter("currency", cryptoCurrencyName) + .appendQueryParameter("paymentCurrency", "USD") + + val url = builder.build().toString() + return url + } + + override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null +} + +private class ChainToBlockchainConverter : Converter { + override fun convert(value: UtorgCurrencyData): Blockchain? { + return when (value.chain?.uppercase()) { + "ARBITRUM" -> Blockchain.Arbitrum + "AVALANCHE" -> Blockchain.Avalanche + "BINANCE_SMART_CHAIN" -> Blockchain.BSC + "BITCOIN" -> Blockchain.Bitcoin + "BNB" -> Blockchain.Binance + "ETHEREUM" -> Blockchain.Ethereum + "GNOSIS" -> Blockchain.Gnosis + "POLYGON" -> Blockchain.Polygon + "RIPPLE" -> Blockchain.XRP + "RSK" -> Blockchain.RSK + "SOLANA" -> Blockchain.Solana + else -> null + // "APTOS", "ATOM", "NEAR", "ZKSYNC", "VECHAIN", "VELAS", "VELAS_EVM" -> null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt new file mode 100644 index 0000000000..6244d39a5e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt @@ -0,0 +1,67 @@ +package com.tangem.tap.network.exchangeServices.utorg.api + +import com.squareup.moshi.Json +import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyResponse +import retrofit2.http.Body +import retrofit2.http.POST +import retrofit2.http.Path + +/** +[REDACTED_AUTHOR] + */ +interface UtorgApi { + + @POST("api/merchant/{apiVersion}/settings/currency") + suspend fun getCurrency( + @Path("apiVersion") apiVersion: String, + ): UtorgCurrencyResponse + + @POST("api/merchant/{apiVersion}/settings/successUrl") + suspend fun setSuccessUrl( + @Path("apiVersion") apiVersion: String, + @Body request: RequestSuccessUrl, + ) +} + +data class RequestSuccessUrl( + val url: String, +) + +interface UtorgResponse { + val success: Boolean + val timestamp: Long + val data: Data? + val error: UtorgErrorResponse? + + fun isSuccess(): Boolean = success && data != null && error == null + + @Throws(NullPointerException::class) + fun toSuccess(): UtorgSuccessResponse { + val internalTimestamp = this.timestamp + val internalData = this.data + + return object : UtorgSuccessResponse { + override val timestamp: Long = internalTimestamp + override val data: Data = internalData!! + } + } + + @Throws(NullPointerException::class) + fun toError(): UtorgErrorResponse = error!! +} + +interface UtorgSuccessResponse { + val timestamp: Long + val data: T +} + +data class UtorgErrorResponse( + @Json(name = "message") val message: String?, + @Json(name = "type") val type: UtorgErrorType, +) + +enum class UtorgErrorType { + UNAUTHORIZED, + UNKNOWN_ERROR, + BAD_REQUEST, +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt new file mode 100644 index 0000000000..5df98687d8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.network.exchangeServices.utorg.api.model + +import com.squareup.moshi.Json +import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorResponse +import com.tangem.tap.network.exchangeServices.utorg.api.UtorgResponse + +class UtorgCurrencyResponse( + @Json(name = "success") override val success: Boolean, + @Json(name = "timestamp") override val timestamp: Long, + @Json(name = "data") override val data: List?, + @Json(name = "error") override val error: UtorgErrorResponse?, +) : UtorgResponse> + +data class UtorgCurrencyData( + @Json(name = "currency") val currency: String, + @Json(name = "symbol") val symbol: String, + @Json(name = "enabled") val enabled: Boolean, + @Json(name = "type") val type: UtorgCurrencyType, + @Json(name = "caption") val caption: String?, + @Json(name = "chain") val chain: String?, +) + +enum class UtorgCurrencyType { + FIAT, CRYPTO +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt new file mode 100644 index 0000000000..e50b8b8833 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt @@ -0,0 +1,58 @@ +package com.tangem.tap.network.exchangeServices.utorg.mock + +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.tap.network.exchangeServices.utorg.api.RequestSuccessUrl +import com.tangem.tap.network.exchangeServices.utorg.api.UtorgApi +import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorResponse +import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorType +import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyResponse +import kotlinx.coroutines.delay + +/** +[REDACTED_AUTHOR] + */ +class MockUtorgApi : UtorgApi { + + var nextDelay = 500L + + var nextResponseType = ResponseType.Success + var nextErrorType = UtorgErrorType.UNKNOWN_ERROR + + override suspend fun getCurrency(apiVersion: String): UtorgCurrencyResponse { + delay(nextDelay) + return UtorgCurrencyResponse( + success = createIsSuccess(), + timestamp = createTime(), + data = createData(MockUtorgSuccessDataResponse.GetCurrency), + error = createError(), + ) + } + + override suspend fun setSuccessUrl(apiVersion: String, request: RequestSuccessUrl) { + delay(nextDelay) + } + + private fun createIsSuccess(): Boolean = when (nextResponseType) { + ResponseType.Success -> true + ResponseType.Error -> false + } + + private fun createTime(): Long = System.currentTimeMillis() + + private inline fun createData(response: MockUtorgSuccessDataResponse): T? = when (nextResponseType) { + ResponseType.Success -> MoshiJsonConverter.INSTANCE.fromJson(response.json)!! + ResponseType.Error -> null + } + + private fun createError(): UtorgErrorResponse? = when (nextResponseType) { + ResponseType.Success -> null + ResponseType.Error -> UtorgErrorResponse( + message = "Optional message", + type = nextErrorType, + ) + } + + enum class ResponseType { + Success, Error + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt new file mode 100644 index 0000000000..959e65a4d5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt @@ -0,0 +1,12 @@ +package com.tangem.tap.network.exchangeServices.utorg.mock + +import com.tangem.tap.network.exchangeServices.utorg.mock.json.getCurrencyFull + +/** +[REDACTED_AUTHOR] + */ +sealed class MockUtorgSuccessDataResponse( + val json: String, +) { + object GetCurrency : MockUtorgSuccessDataResponse(getCurrencyFull) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt new file mode 100644 index 0000000000..3d50462305 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt @@ -0,0 +1,1025 @@ +package com.tangem.tap.network.exchangeServices.utorg.mock.json + +/** +[REDACTED_AUTHOR] + */ +internal val getCurrencyFull = """ +[ + { + "currency": "APT", + "symbol": "APT", + "chain": "APTOS", + "display": "APT", + "caption": "Aptos", + "explorerTx": "https://aptoscan.com/version/", + "explorerAddr": "https://aptoscan.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 1.8492, + "depositMax": 739.6673, + "withdrawalMin": 1.8492, + "withdrawalMax": 739.6673, + "addressValidator": "APT", + "precision": 4, + "allowTag": false + }, + { + "currency": "ATOM", + "symbol": "ATOM", + "chain": "ATOM", + "display": "ATOM", + "caption": "Cosmos", + "explorerTx": "https://atomscan.com/transactions/", + "explorerAddr": "https://atomscan.com/accounts/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 1.8020, + "depositMax": 720.7911, + "withdrawalMin": 1.8020, + "withdrawalMax": 720.7911, + "addressValidator": "COSMOS", + "precision": 4, + "allowTag": true, + "udKey": "crypto.ATOM.address" + }, + { + "currency": "AUD", + "symbol": "AUD", + "display": "AUD", + "type": "FIAT", + "enabled": true, + "depositMin": 43.73, + "depositMax": 15013.68, + "withdrawalMin": 43.73, + "withdrawalMax": 15013.68, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "AVAXAVA", + "symbol": "AVAX", + "chain": "AVALANCHE", + "display": "Avax", + "caption": "Avalanche", + "explorerTx": "", + "explorerAddr": "", + "type": "CRYPTO", + "enabled": true, + "depositMin": 1.337428, + "depositMax": 534.971102, + "withdrawalMin": 1.337428, + "withdrawalMax": 534.971102, + "addressValidator": "ETH", + "precision": 6, + "allowTag": false, + "udKey": "crypto.AVAX.address" + }, + { + "currency": "BNB", + "symbol": "BNB", + "chain": "BNB", + "display": "BNB", + "caption": "BEP2", + "explorerTx": "https://explorer.binance.org/tx/", + "explorerAddr": "https://explorer.binance.org/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 0.082738, + "depositMax": 33.095138, + "withdrawalMin": 0.082738, + "withdrawalMax": 33.095138, + "addressValidator": "BNB", + "precision": 6, + "allowTag": true, + "udKey": "crypto.BNB.address" + }, + { + "currency": "BNBBSC", + "symbol": "BNB", + "chain": "BINANCE_SMART_CHAIN", + "display": "BNB", + "caption": "BEP20", + "explorerTx": "https://www.bscscan.com/tx/", + "explorerAddr": "https://www.bscscan.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 0.0828, + "depositMax": 33.0951, + "withdrawalMin": 0.0828, + "withdrawalMax": 33.0951, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "BRL", + "symbol": "BRL", + "display": "BRL", + "type": "FIAT", + "enabled": true, + "depositMin": 158.08, + "depositMax": 54293.21, + "withdrawalMin": 158.08, + "withdrawalMax": 54293.21, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "BTC", + "symbol": "BTC", + "chain": "BITCOIN", + "display": "BTC", + "caption": "", + "explorerTx": "https://blockstream.info/tx/", + "explorerAddr": "https://blockstream.info/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 528764.805415, + "depositMax": 211505922.165820, + "withdrawalMin": 528764.805415, + "withdrawalMax": 211505922.165820, + "addressValidator": "BTC", + "precision": 6, + "allowTag": false, + "udKey": "crypto.BTC.address" + }, + { + "currency": "BUSDAVA", + "symbol": "BUSD", + "chain": "AVALANCHE", + "display": "BUSD", + "caption": "on Avalanche", + "explorerTx": "https://snowtrace.io/tx/", + "explorerAddr": "https://snowtrace.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6382, + "depositMax": 10655.2775, + "withdrawalMin": 26.6382, + "withdrawalMax": 10655.2775, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "BUSDBSC", + "symbol": "BUSD", + "chain": "BINANCE_SMART_CHAIN", + "display": "BUSD", + "caption": "BEP20", + "explorerTx": "https://www.bscscan.com/tx/", + "explorerAddr": "https://www.bscscan.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6414, + "depositMax": 10656.5563, + "withdrawalMin": 26.6414, + "withdrawalMax": 10656.5563, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false, + "udKey": "crypto.BUSD.version.BEP20.address" + }, + { + "currency": "BUSDETH", + "symbol": "BUSD", + "chain": "ETHEREUM", + "display": "BUSD", + "caption": "ERC20", + "explorerTx": "https://etherscan.io/tx/", + "explorerAddr": "https://etherscan.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6414, + "depositMax": 10656.5563, + "withdrawalMin": 26.6414, + "withdrawalMax": 10656.5563, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false, + "udKey": "crypto.BUSD.version.ERC20.address" + }, + { + "currency": "CAD", + "symbol": "CAD", + "display": "CAD", + "type": "FIAT", + "enabled": true, + "depositMin": 40.85, + "depositMax": 14024.33, + "withdrawalMin": 40.85, + "withdrawalMax": 14024.33, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "CZK", + "symbol": "CZK", + "display": "CZK", + "type": "FIAT", + "enabled": true, + "depositMin": 672.71, + "depositMax": 231068.12, + "withdrawalMin": 672.71, + "withdrawalMax": 231068.12, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "DAIBSC", + "symbol": "DAI", + "chain": "BINANCE_SMART_CHAIN", + "display": "DAI", + "caption": "BEP20", + "explorerTx": "https://www.bscscan.com/tx/", + "explorerAddr": "https://www.bscscan.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6462, + "depositMax": 10658.4634, + "withdrawalMin": 26.6462, + "withdrawalMax": 10658.4634, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "DAIEAVA", + "symbol": "DAIE", + "chain": "AVALANCHE", + "display": "DAI.e", + "caption": "on Avalanche", + "explorerTx": "https://snowtrace.io/tx/", + "explorerAddr": "https://snowtrace.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6052, + "depositMax": 10642.0513, + "withdrawalMin": 26.6052, + "withdrawalMax": 10642.0513, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "DAIETH", + "symbol": "DAI", + "chain": "ETHEREUM", + "display": "DAI", + "caption": "ERC20", + "explorerTx": "https://etherscan.io/tx/", + "explorerAddr": "https://etherscan.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6436, + "depositMax": 10657.4090, + "withdrawalMin": 26.6436, + "withdrawalMax": 10657.4090, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false, + "udKey": "crypto.DAI.address" + }, + { + "currency": "DAIPOL", + "symbol": "DAI", + "chain": "POLYGON", + "display": "DAI", + "caption": "on Polygon", + "explorerTx": "https://polygonscan.com/tx/", + "explorerAddr": "https://polygonscan.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6436, + "depositMax": 10657.4090, + "withdrawalMin": 26.6436, + "withdrawalMax": 10657.4090, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "DKK", + "symbol": "DKK", + "display": "DKK", + "type": "FIAT", + "enabled": true, + "depositMin": 210.64, + "depositMax": 72348.14, + "withdrawalMin": 210.64, + "withdrawalMax": 72348.14, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "ETH", + "symbol": "ETH", + "chain": "ETHEREUM", + "display": "ETH", + "caption": "", + "explorerTx": "https://ropsten.etherscan.io/tx/", + "explorerAddr": "https://ropsten.etherscan.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 37688.767964, + "depositMax": 15075507.185288, + "withdrawalMin": 37688.767964, + "withdrawalMax": 15075507.185288, + "addressValidator": "ETH", + "precision": 6, + "allowTag": false, + "udKey": "crypto.ETH.address" + }, + { + "currency": "EUR", + "symbol": "EUR", + "display": "EUR", + "caption": "", + "type": "FIAT", + "enabled": true, + "depositMin": 27.24, + "depositMax": 9349.53, + "withdrawalMin": 27.24, + "withdrawalMax": 9349.53, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "GBP", + "symbol": "GBP", + "display": "GBP", + "type": "FIAT", + "enabled": true, + "depositMin": 25.14, + "depositMax": 8630.60, + "withdrawalMin": 25.14, + "withdrawalMax": 8630.60, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "GMTBSC", + "symbol": "GMT", + "chain": "BINANCE_SMART_CHAIN", + "display": "GMT", + "caption": "BEP20", + "explorerTx": "https://bscscan.com/tx/", + "explorerAddr": "https://bscscan.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 53.9899, + "depositMax": 21595.9496, + "withdrawalMin": 53.9899, + "withdrawalMax": 21595.9496, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "GNOETH", + "symbol": "GNO", + "chain": "ETHEREUM", + "display": "GNO", + "caption": "ERC20", + "explorerTx": "https://etherscan.io/tx/", + "explorerAddr": "https://etherscan.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 0.2250, + "depositMax": 89.9938, + "withdrawalMin": 0.2250, + "withdrawalMax": 89.9938, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false, + "udKey": "crypto.GNO.address" + }, + { + "currency": "HAIBSC", + "symbol": "HAI", + "chain": "BINANCE_SMART_CHAIN", + "display": "HAI", + "caption": "on BSC", + "explorerTx": "https://bscscan.com/tx/", + "explorerAddr": "https://bscscan.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 937.548948, + "depositMax": 375019.579098, + "withdrawalMin": 937.548948, + "withdrawalMax": 375019.579098, + "addressValidator": "ETH", + "precision": 6, + "allowTag": false + }, + { + "currency": "HAIVET", + "symbol": "HAI", + "chain": "VECHAIN", + "display": "HAI", + "caption": "VET", + "explorerTx": "https://vechainstats.com/transaction/", + "explorerAddr": "https://vechainstats.com/account/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 949.3974, + "depositMax": 379758.9569, + "withdrawalMin": 949.3974, + "withdrawalMax": 379758.9569, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "IDR", + "symbol": "IDR", + "display": "IDR", + "type": "FIAT", + "enabled": true, + "depositMin": 459558, + "depositMax": 157855988, + "withdrawalMin": 459558, + "withdrawalMax": 157855988, + "addressValidator": "LUHN", + "precision": 0, + "allowTag": false + }, + { + "currency": "INR", + "symbol": "INR", + "display": "INR", + "type": "FIAT", + "enabled": true, + "depositMin": 2511.35, + "depositMax": 862635.95, + "withdrawalMin": 2511.35, + "withdrawalMax": 862635.95, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "JPY", + "symbol": "JPY", + "display": "JPY", + "type": "FIAT", + "enabled": true, + "depositMin": 3992, + "depositMax": 1370884, + "withdrawalMin": 3992, + "withdrawalMax": 1370884, + "addressValidator": "LUHN", + "precision": 0, + "allowTag": false + }, + { + "currency": "KZT", + "symbol": "KZT", + "display": "KZT", + "type": "FIAT", + "enabled": true, + "depositMin": 13843.18, + "depositMax": 4755080.66, + "withdrawalMin": 13843.18, + "withdrawalMax": 4755080.66, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "MH3ETH", + "symbol": "MH3", + "chain": "ETHEREUM", + "display": "NFT", + "caption": "Meta History", + "explorerTx": "https://rinkeby.etherscan.io/tx/", + "explorerAddr": "https://rinkeby.etherscan.io/address/", + "type": "CRYPTO", + "enabled": false, + "depositMin": 0, + "depositMax": 0, + "withdrawalMin": 0, + "withdrawalMax": 0, + "addressValidator": "ETH", + "precision": 0, + "allowTag": false, + "nftContract": "0xf0192b97135abaa218bcff255b8f4b22171a7590" + }, + { + "currency": "MXN", + "symbol": "MXN", + "display": "MXN", + "type": "FIAT", + "enabled": true, + "depositMin": 574.83, + "depositMax": 197445.63, + "withdrawalMin": 574.83, + "withdrawalMax": 197445.63, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "MYR", + "symbol": "MYR", + "display": "MYR", + "type": "FIAT", + "enabled": true, + "depositMin": 131.04, + "depositMax": 45006.86, + "withdrawalMin": 131.04, + "withdrawalMax": 45006.86, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "NEAR", + "symbol": "NEAR", + "chain": "NEAR", + "display": "NEAR", + "caption": "", + "explorerTx": "", + "explorerAddr": "", + "type": "CRYPTO", + "enabled": true, + "depositMin": 10.560163, + "depositMax": 4224.064906, + "withdrawalMin": 10.560163, + "withdrawalMax": 4224.064906, + "addressValidator": "NEAR", + "precision": 6, + "allowTag": false, + "udKey": "crypto.NEAR.address" + }, + { + "currency": "NOK", + "symbol": "NOK", + "display": "NOK", + "type": "FIAT", + "enabled": true, + "depositMin": 312.23, + "depositMax": 107245.60, + "withdrawalMin": 312.23, + "withdrawalMax": 107245.60, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "NXUSDAVA", + "symbol": "NXUSD", + "chain": "AVALANCHE", + "display": "NXUSD", + "caption": "Avalanche", + "explorerTx": "https://snowtrace.io/tx/", + "explorerAddr": "https://snowtrace.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.8766, + "depositMax": 10750.6217, + "withdrawalMin": 26.8766, + "withdrawalMax": 10750.6217, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "NZD", + "symbol": "NZD", + "display": "NZD", + "type": "FIAT", + "enabled": true, + "depositMin": 47.90, + "depositMax": 16448.49, + "withdrawalMin": 47.90, + "withdrawalMax": 16448.49, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "PLN", + "symbol": "PLN", + "display": "PLN", + "type": "FIAT", + "enabled": true, + "depositMin": 134.06, + "depositMax": 46047.85, + "withdrawalMin": 134.06, + "withdrawalMax": 46047.85, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "RIF", + "symbol": "RIF", + "chain": "RSK", + "display": "RIF", + "caption": "RSK", + "explorerTx": "https://explorer.rsk.co/tx/", + "explorerAddr": "https://explorer.rsk.co/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 429.65, + "depositMax": 171859.31, + "withdrawalMin": 429.65, + "withdrawalMax": 171859.31, + "addressValidator": "ETH", + "precision": 2, + "allowTag": false, + "udKey": "crypto.RIF.address" + }, + { + "currency": "RUB", + "symbol": "RUB", + "display": "RUB", + "type": "FIAT", + "enabled": true, + "depositMin": 2217.40, + "depositMax": 761666.68, + "withdrawalMin": 2217.40, + "withdrawalMax": 761666.68, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "SOL", + "symbol": "SOL", + "chain": "SOLANA", + "display": "SOL", + "caption": "Solana", + "explorerTx": "https://explorer.solana.com/tx/", + "explorerAddr": "https://explorer.solana.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 1.155094, + "depositMax": 462.037519, + "withdrawalMin": 1.155094, + "withdrawalMax": 462.037519, + "addressValidator": "SOLANA", + "precision": 6, + "allowTag": false, + "udKey": "crypto.SOL.address" + }, + { + "currency": "TRY", + "symbol": "TRY", + "display": "TRY", + "type": "FIAT", + "enabled": true, + "depositMin": 572.28, + "depositMax": 196571.08, + "withdrawalMin": 572.28, + "withdrawalMax": 196571.08, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "TWTBNB", + "symbol": "TWT", + "chain": "BNB", + "display": "TWT", + "caption": "BEP2", + "explorerTx": "https://explorer.binance.org/tx/", + "explorerAddr": "https://explorer.binance.org/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 17.4457, + "depositMax": 6978.2637, + "withdrawalMin": 17.4457, + "withdrawalMax": 6978.2637, + "addressValidator": "BNB", + "precision": 4, + "allowTag": true, + "udKey": "crypto.TWT.address" + }, + { + "currency": "UAH", + "symbol": "UAH", + "display": "UAH", + "caption": "", + "type": "FIAT", + "enabled": true, + "depositMin": 1118.00, + "depositMax": 14005.59, + "withdrawalMin": 1118.00, + "withdrawalMax": 14005.59, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "USD", + "symbol": "USD", + "display": "USD", + "type": "FIAT", + "enabled": true, + "depositMin": 30.57, + "depositMax": 10382.96, + "withdrawalMin": 30.57, + "withdrawalMax": 10382.96, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + }, + { + "currency": "USDCARB", + "symbol": "USDC", + "chain": "ARBITRUM", + "display": "USDC", + "caption": "on arbitrum", + "explorerTx": "https://arbiscan.io/tx/", + "explorerAddr": "https://arbiscan.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6382, + "depositMax": 10655.2775, + "withdrawalMin": 26.6382, + "withdrawalMax": 10655.2775, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "USDCBSC", + "symbol": "USDC", + "chain": "BINANCE_SMART_CHAIN", + "display": "USDC", + "caption": "BEP20", + "explorerTx": "https://www.bscscan.com/tx/", + "explorerAddr": "https://www.bscscan.com/address/", + "type": "CRYPTO", + "enabled": false, + "depositMin": 0, + "depositMax": 0, + "withdrawalMin": 0, + "withdrawalMax": 0, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "USDCEAVA", + "symbol": "USDCE", + "chain": "AVALANCHE", + "display": "USDC.e", + "caption": "on Avalanche", + "explorerTx": "", + "explorerAddr": "", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6078, + "depositMax": 10643.1155, + "withdrawalMin": 26.6078, + "withdrawalMax": 10643.1155, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "USDCETH", + "symbol": "USDC", + "chain": "ETHEREUM", + "display": "USDC", + "caption": "ERC20", + "explorerTx": "https://etherscan.io/tx/", + "explorerAddr": "https://etherscan.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6382, + "depositMax": 10655.2775, + "withdrawalMin": 26.6382, + "withdrawalMax": 10655.2775, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false, + "udKey": "crypto.USDC.address" + }, + { + "currency": "USDCZK", + "symbol": "USDC", + "chain": "ZKSYNC", + "display": "USDC", + "caption": "on zkSync", + "explorerTx": "https://zkscan.io/explorer/transactions/", + "explorerAddr": "https://zkscan.io/explorer/accounts/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6078, + "depositMax": 10643.1155, + "withdrawalMin": 26.6078, + "withdrawalMax": 10643.1155, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "USDTAVA", + "symbol": "USDT", + "chain": "AVALANCHE", + "display": "USDT", + "caption": "on Avalanche", + "explorerTx": "https://snowtrace.io/tx/", + "explorerAddr": "https://snowtrace.io/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6649, + "depositMax": 10665.9328, + "withdrawalMin": 26.6649, + "withdrawalMax": 10665.9328, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "USDTE", + "symbol": "USDT", + "chain": "ETHEREUM", + "display": "USDT", + "caption": "", + "explorerTx": "", + "explorerAddr": "", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.66483213, + "depositMax": 10665.93285048, + "withdrawalMin": 26.66483213, + "withdrawalMax": 10665.93285048, + "addressValidator": "ETH", + "precision": 8, + "allowTag": false, + "udKey": "crypto.USDT.version.ERC20.address" + }, + { + "currency": "USDTPOL", + "symbol": "USDT", + "chain": "POLYGON", + "display": "USDT", + "caption": "on Polygon", + "explorerTx": "https://polygonscan.com/tx/", + "explorerAddr": "https://polygonscan.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6649, + "depositMax": 10665.9328, + "withdrawalMin": 26.6649, + "withdrawalMax": 10665.9328, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "USDVEL", + "symbol": "USDV", + "chain": "VELAS_EVM", + "display": "USDV", + "caption": "Velas EVM", + "explorerTx": "https://evmexplorer.velas.com/tx/", + "explorerAddr": "https://evmexplorer.velas.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26611608.908400, + "depositMax": 10644643563.359833, + "withdrawalMin": 26611608.908400, + "withdrawalMax": 10644643563.359833, + "addressValidator": "ETH", + "precision": 6, + "allowTag": false + }, + { + "currency": "USNNEAR", + "symbol": "USN", + "chain": "NEAR", + "display": "USN", + "caption": "Near", + "explorerTx": "https://explorer.near.org/transactions/", + "explorerAddr": "https://explorer.near.org/accounts/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6969, + "depositMax": 10678.7473, + "withdrawalMin": 26.6969, + "withdrawalMax": 10678.7473, + "addressValidator": "NEAR", + "precision": 4, + "allowTag": false + }, + { + "currency": "VLX", + "symbol": "VLX", + "chain": "VELAS", + "display": "VLX", + "caption": "Velas", + "explorerTx": "https://explorer.velas.com/tx/", + "explorerAddr": "https://explorer.velas.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 1021.951187, + "depositMax": 408780.474783, + "withdrawalMin": 1021.951187, + "withdrawalMax": 408780.474783, + "addressValidator": "SOLANA", + "precision": 6, + "allowTag": false, + "udKey": "crypto.VLX.address" + }, + { + "currency": "VLXETH", + "symbol": "VLX", + "chain": "VELAS_EVM", + "display": "VLX", + "caption": "Velas EVM", + "explorerTx": "https://evmexplorer.velas.com/tx/", + "explorerAddr": "https://evmexplorer.velas.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 1021.9512, + "depositMax": 408780.4747, + "withdrawalMin": 1021.9512, + "withdrawalMax": 408780.4747, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "WLKNSOL", + "symbol": "WLKN", + "chain": "SOLANA", + "display": "WLKN", + "caption": "Solana", + "explorerTx": "https://explorer.solana.com/tx/", + "explorerAddr": "https://explorer.solana.com/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 531.89, + "depositMax": 212755.45, + "withdrawalMin": 531.89, + "withdrawalMax": 212755.45, + "addressValidator": "SOLANA", + "precision": 2, + "allowTag": false + }, + { + "currency": "XDAIGNO", + "symbol": "xDAI", + "chain": "GNOSIS", + "display": "xDAI", + "caption": "Gnosis chain", + "explorerTx": "https://blockscout.com/xdai/mainnet/tx/", + "explorerAddr": "https://blockscout.com/xdai/mainnet/address/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 26.6782, + "depositMax": 10671.2684, + "withdrawalMin": 26.6782, + "withdrawalMax": 10671.2684, + "addressValidator": "ETH", + "precision": 4, + "allowTag": false + }, + { + "currency": "XRP", + "symbol": "XRP", + "chain": "RIPPLE", + "display": "XRP", + "caption": "Ripple", + "explorerTx": "https://xrpscan.com/tx/", + "explorerAddr": "https://xrpscan.com/account/", + "type": "CRYPTO", + "enabled": true, + "depositMin": 67.7645, + "depositMax": 27105.7684, + "withdrawalMin": 67.7645, + "withdrawalMax": 27105.7684, + "addressValidator": "XRP", + "precision": 4, + "allowTag": true, + "udKey": "crypto.XRP.address" + }, + { + "currency": "ZAR", + "symbol": "ZAR", + "display": "ZAR", + "type": "FIAT", + "enabled": true, + "depositMin": 538.27, + "depositMax": 184892.03, + "withdrawalMin": 538.27, + "withdrawalMax": 184892.03, + "addressValidator": "LUHN", + "precision": 2, + "allowTag": false + } + ] +""".trimIndent() \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt index c4f96f4b55..a0046af552 100644 --- a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt +++ b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt @@ -13,6 +13,7 @@ object LogConfig { object NetworkLogConfig { val mercuryoService: Boolean = false val moonPayService: Boolean = false + val utorgService: Boolean = false val tangemTechService: Boolean = BuildConfig.DEBUG val paymentologyApiService: Boolean = BuildConfig.DEBUG val blockchainSdkNetwork: Boolean = BuildConfig.DEBUG From 5e4d972b29cfe4c61ded9817a0af3d56e995ebe5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Feb 2023 18:02:48 +0300 Subject: [PATCH 02/46] Updated on 2026-08-14 --- .../tap/network/exchangeServices/utorg/UtorgExchangeService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt index 0366471b64..21c8c48424 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt @@ -86,7 +86,8 @@ class UtorgExchangeService( .appendPath(environment.sidValue) .appendPath(walletAddress) .appendQueryParameter("currency", cryptoCurrencyName) - .appendQueryParameter("paymentCurrency", "USD") + // if we set the paymentCurrency, then the Utorg widget didn't work + // .appendQueryParameter("paymentCurrency", "USD") val url = builder.build().toString() return url From bcd08fa0fc117ff2ac7bf794468dcf5b06830c8d Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Feb 2023 18:21:21 +0300 Subject: [PATCH 03/46] Updated on 2026-08-14 --- .../com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt | 1 - 1 file changed, 1 deletion(-) 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 index 91bf93586e..9433c8b6e7 100644 --- 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 @@ -111,7 +111,6 @@ private fun WalletDataModel.mapToReduxModel( amountFormatted = amountFormatted, fiatAmount = fiatAmount, fiatAmountFormatted = fiatAmountFormatted, - // token = null, amountToCreateAccount = (status as? WalletDataModel.NoAccount) ?.amountToCreateAccount ?.toString(), From 291a07c4355349c2614e87111d94f29e7fd81b0d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Feb 2023 17:33:48 +0300 Subject: [PATCH 04/46] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../java/com/tangem/tap/TapApplication.kt | 17 ++-- .../ZendeskConfig.kt => chat/ChatConfig.kt} | 10 ++- .../com/tangem/tap/common/chat/ChatManager.kt | 26 ++++++ .../tap/common/chat/opener/ChatOpener.kt | 7 ++ .../implementation/SprinklrChatOpener.kt | 10 +++ .../implementation/ZendeskChatOpener.kt | 83 +++++++++++++++++++ .../tap/common/feedback/FeedbackManager.kt | 80 ++---------------- .../tap/common/redux/global/GlobalAction.kt | 4 +- .../common/redux/global/GlobalMidlleware.kt | 10 +-- .../configurable/config/ConfigManager.kt | 2 +- .../domain/configurable/config/JsonModels.kt | 2 +- .../products/wallet/saltPay/SaltPayConfig.kt | 5 +- .../dialog/NoFundsForActivationDialog.kt | 14 ++-- 14 files changed, 172 insertions(+), 100 deletions(-) rename app/src/main/java/com/tangem/tap/common/{zendesk/ZendeskConfig.kt => chat/ChatConfig.kt} (74%) create mode 100644 app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt create mode 100644 app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt create mode 100644 app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt create mode 100644 app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 93150ef69d..2b8aa19f58 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 93150ef69daf244a92266d7cdfbc9294b745b4e2 +Subproject commit 2b8aa19f58e17a78015629943d49df0da0b4fd4b diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index d77a6e9c1b..78cf3956f2 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -25,6 +25,7 @@ import com.tangem.tap.common.analytics.filters.BasicTopUpFilter 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.chat.ChatManager import com.tangem.tap.common.feedback.AdditionalFeedbackInfo import com.tangem.tap.common.feedback.FeedbackManager import com.tangem.tap.common.images.createCoilImageLoader @@ -55,11 +56,9 @@ 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 lateinit var store: Store @@ -188,7 +187,7 @@ class TapApplication : Application(), ImageLoaderFactory { private fun initWithConfigDependency(config: Config) { shopService = TangemShopService(this, config.shopify!!) initAnalytics(this, config) - initFeedbackManager(this, preferencesStorage, config) + initFeedbackManager(this, preferencesStorage, foregroundActivityObserver) } private fun initAnalytics(application: Application, config: Config) { @@ -210,7 +209,11 @@ class TapApplication : Application(), ImageLoaderFactory { factory.build(Analytics, buildData) } - private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) { + private fun initFeedbackManager( + context: Context, + preferencesStorage: PreferencesStorage, + foregroundActivityObserver: ForegroundActivityObserver, + ) { fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply { appVersion = try { val pInfo = context.packageManager.getPackageInfo(context.packageName, 0) @@ -243,12 +246,8 @@ class TapApplication : Application(), ImageLoaderFactory { val feedbackManager = FeedbackManager( infoHolder = additionalFeedbackInfo, logCollector = tangemLogCollector, - preferencesStorage = preferencesStorage, + chatManager = ChatManager(preferencesStorage, foregroundActivityObserver), ) - feedbackManager.chatInitializer = { zendeskConfig -> - Chat.INSTANCE.init(context, zendeskConfig.accountKey, zendeskConfig.appId) - Logger.setLoggable(LogConfig.zendesk) - } store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager)) } diff --git a/app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt similarity index 74% rename from app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt rename to app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt index 66569ee1b7..32aab191db 100644 --- a/app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt @@ -1,8 +1,10 @@ -package com.tangem.tap.common.zendesk +package com.tangem.tap.common.chat import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +sealed interface ChatConfig + @JsonClass(generateAdapter = true) data class ZendeskConfig( @Json(name = "zendeskApiKey") @@ -15,4 +17,8 @@ data class ZendeskConfig( val accountKey: String, @Json(name = "zendeskUrl") val url: String, -) \ No newline at end of file +) : ChatConfig + +data class SprinklrConfig( + val appId: String, +) : ChatConfig \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt new file mode 100644 index 0000000000..951cf75294 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.common.chat + +import android.content.Context +import com.tangem.tap.ForegroundActivityObserver +import com.tangem.tap.common.chat.opener.ChatOpener +import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener +import com.tangem.tap.common.chat.opener.implementation.ZendeskChatOpener +import com.tangem.tap.persistence.PreferencesStorage + +class ChatManager( + private val preferencesStorage: PreferencesStorage, + private val foregroundActivityObserver: ForegroundActivityObserver, +) { + private val openers = mutableMapOf() + + fun open(config: ChatConfig, feedbackDataBuilder: (Context) -> String) { + val opener = openers.getOrPut(config) { + when (config) { + is SprinklrConfig -> SprinklrChatOpener() + is ZendeskConfig -> ZendeskChatOpener(config, preferencesStorage, foregroundActivityObserver) + } + } + + opener.open(feedbackDataBuilder) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt new file mode 100644 index 0000000000..e3757286e7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt @@ -0,0 +1,7 @@ +package com.tangem.tap.common.chat.opener + +import android.content.Context + +internal interface ChatOpener { + fun open(feedbackDataBuilder: (Context) -> String) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt new file mode 100644 index 0000000000..a231a4816f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.common.chat.opener.implementation + +import android.content.Context +import com.tangem.tap.common.chat.opener.ChatOpener + +internal class SprinklrChatOpener : ChatOpener { + override fun open(feedbackDataBuilder: (Context) -> String) { + // TODO: Open activity with Sprinklr WebView, will be in further MRs + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt new file mode 100644 index 0000000000..db828b9619 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt @@ -0,0 +1,83 @@ +package com.tangem.tap.common.chat.opener.implementation + +import android.content.Context +import android.os.Build +import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.LogConfig +import com.tangem.tap.ForegroundActivityObserver +import com.tangem.tap.common.chat.ZendeskConfig +import com.tangem.tap.common.chat.opener.ChatOpener +import com.tangem.tap.persistence.PreferencesStorage +import com.tangem.tap.withForegroundActivity +import com.tangem.wallet.R +import com.zendesk.logger.Logger +import zendesk.chat.Chat +import zendesk.chat.ChatConfiguration +import zendesk.chat.ChatEngine +import zendesk.chat.ChatProvidersConfiguration +import zendesk.chat.VisitorInfo +import zendesk.configurations.Configuration +import zendesk.messaging.MessagingActivity + +internal class ZendeskChatOpener( + private val config: ZendeskConfig, + private val preferencesStorage: PreferencesStorage, + private val foregroundActivityObserver: ForegroundActivityObserver, +) : ChatOpener { + private var isInitialized = false + + override fun open(feedbackDataBuilder: (Context) -> String) { + foregroundActivityObserver.withForegroundActivity { activity -> + initZendeskIfNeeded(activity.applicationContext) + setChatVisitorInfo() + setChatVisitorNote(feedbackDataBuilder(activity)) + showMessagingActivity(activity) + } + } + + private fun initZendeskIfNeeded(context: Context) { + if (isInitialized) return + isInitialized = true + + Chat.INSTANCE.init(context, config.accountKey, config.appId) + Logger.setLoggable(LogConfig.zendesk) + } + + private fun setChatVisitorInfo() { + if (preferencesStorage.chatFirstLaunchTime == null) { + preferencesStorage.chatFirstLaunchTime = System.currentTimeMillis() + } + val chatUserId = (preferencesStorage.chatFirstLaunchTime.toString() + Build.MODEL).hashCode() + val visitorInfo = VisitorInfo.builder() + .withName("User $chatUserId") + .build() + + Chat.INSTANCE.chatProvidersConfiguration = ChatProvidersConfiguration.builder() + .withVisitorInfo(visitorInfo) + .build() + } + + private fun setChatVisitorNote(note: String) { + Chat.INSTANCE.providers() + ?.profileProvider() + ?.setVisitorNote(note) + } + + private fun showMessagingActivity(context: Context) { + Analytics.send(com.tangem.tap.common.analytics.events.Chat.ScreenOpened()) + MessagingActivity.builder() + .withMultilineResponseOptionsEnabled(false) + .withBotLabelStringRes(R.string.chat_bot_name) + .withBotAvatarDrawable(R.mipmap.ic_launcher) + .withEngines(ChatEngine.engine()) + .show(context, buildChatConfig()) + } + + private fun buildChatConfig(): Configuration { + return ChatConfiguration.builder() + .withOfflineFormEnabled(true) + .withAgentAvailabilityEnabled(true) + .withPreChatFormEnabled(false) + .build() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt index b44db4004b..666d79ee81 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt @@ -1,24 +1,14 @@ package com.tangem.tap.common.feedback import android.content.Context -import android.os.Build -import com.tangem.core.analytics.Analytics import com.tangem.domain.common.TapWorkarounds +import com.tangem.tap.common.chat.ChatConfig +import com.tangem.tap.common.chat.ChatManager import com.tangem.tap.common.extensions.sendEmail import com.tangem.tap.common.log.TangemLogCollector -import com.tangem.tap.common.zendesk.ZendeskConfig import com.tangem.tap.foregroundActivityObserver -import com.tangem.tap.persistence.PreferencesStorage import com.tangem.tap.withForegroundActivity -import com.tangem.wallet.R import timber.log.Timber -import zendesk.chat.Chat -import zendesk.chat.ChatConfiguration -import zendesk.chat.ChatEngine -import zendesk.chat.ChatProvidersConfiguration -import zendesk.chat.VisitorInfo -import zendesk.configurations.Configuration -import zendesk.messaging.MessagingActivity import java.io.File import java.io.FileWriter import java.io.StringWriter @@ -29,19 +19,8 @@ import java.io.StringWriter class FeedbackManager( val infoHolder: AdditionalFeedbackInfo, private val logCollector: TangemLogCollector, - private val preferencesStorage: PreferencesStorage, + private val chatManager: ChatManager, ) { - private var lastUsedConfigForInitialization: ZendeskConfig? = null - - var chatInitializer: ((ZendeskConfig) -> Unit)? = null - - fun initChat(zendeskConfig: ZendeskConfig) { - // prevent double initialization with the same config - if (lastUsedConfigForInitialization == zendeskConfig) return - - lastUsedConfigForInitialization = zendeskConfig - chatInitializer?.invoke(zendeskConfig) - } fun sendEmail(feedbackData: FeedbackData, onFail: ((Exception) -> Unit)? = null) { feedbackData.prepare(infoHolder) @@ -57,12 +36,12 @@ class FeedbackManager( } } - fun openChat(feedbackData: FeedbackData) { - feedbackData.prepare(infoHolder) - foregroundActivityObserver.withForegroundActivity { activity -> - setChatVisitorInfo() - setChatVisitorNote(activity, feedbackData) - showMessagingActivity(activity) + fun openChat(config: ChatConfig, feedbackData: FeedbackData) { + chatManager.open(config) { context -> + feedbackData.run { + prepare(infoHolder) + joinTogether(context, infoHolder) + } } } @@ -85,47 +64,6 @@ class FeedbackManager( } } - private fun setChatVisitorInfo() { - if (preferencesStorage.chatFirstLaunchTime == null) { - preferencesStorage.chatFirstLaunchTime = System.currentTimeMillis() - } - val chatUserId = (preferencesStorage.chatFirstLaunchTime.toString() + Build.MODEL).hashCode() - val visitorInfo = VisitorInfo.builder() - .withName("User $chatUserId") - .build() - - Chat.INSTANCE.chatProvidersConfiguration = ChatProvidersConfiguration.builder() - .withVisitorInfo(visitorInfo) - .build() - } - - private fun setChatVisitorNote( - context: Context, - feedbackData: FeedbackData, - ) { - Chat.INSTANCE.providers() - ?.profileProvider() - ?.setVisitorNote(feedbackData.joinTogether(context, infoHolder)) - } - - private fun showMessagingActivity(context: Context) { - Analytics.send(com.tangem.tap.common.analytics.events.Chat.ScreenOpened()) - MessagingActivity.builder() - .withMultilineResponseOptionsEnabled(false) - .withBotLabelStringRes(R.string.chat_bot_name) - .withBotAvatarDrawable(R.mipmap.ic_launcher) - .withEngines(ChatEngine.engine()) - .show(context, buildChatConfig()) - } - - private fun buildChatConfig(): Configuration { - return ChatConfiguration.builder() - .withOfflineFormEnabled(true) - .withAgentAvailabilityEnabled(true) - .withPreChatFormEnabled(false) - .build() - } - private fun getSupportEmail(): String { return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) { S2C_SUPPORT_EMAIL 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 1f51374cae..d9281b3754 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,6 +5,7 @@ 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.chat.ChatConfig import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.feedback.FeedbackData import com.tangem.tap.common.feedback.FeedbackManager @@ -13,7 +14,6 @@ import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.ToastNotificationAction -import com.tangem.tap.common.zendesk.ZendeskConfig import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessage @@ -87,7 +87,7 @@ sealed class GlobalAction : Action { data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction() data class SendEmail(val feedbackData: FeedbackData) : GlobalAction() - data class OpenChat(val feedbackData: FeedbackData, val zendeskConfig: ZendeskConfig? = null) : GlobalAction() + data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction() data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction() object ExchangeManager : 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 0887023b32..ed9a9e3850 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 @@ -9,6 +9,7 @@ import com.tangem.domain.common.LogConfig import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext +import com.tangem.tap.common.chat.SprinklrConfig import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -109,17 +110,16 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di val scanResponse = globalState.scanResponse ?: globalState.onboardingState.onboardingManager?.scanResponse // if config not set -> try to get it based on a scanResponse.productType - val unsafeZendeskConfig = action.zendeskConfig ?: when { - scanResponse?.isSaltPay() == true -> config.saltPayConfig?.zendesk + val unsafeChatConfig = action.chatConfig ?: when { + scanResponse?.isSaltPay() == true -> config.saltPayConfig?.sprinklrAppID?.let(::SprinklrConfig) else -> config.zendesk } - val zendeskConfig = unsafeZendeskConfig.guard { + val chatConfig = unsafeChatConfig.guard { store.dispatchDebugErrorNotification("ZendeskConfig not initialized") return } - feedbackManager.initChat(zendeskConfig) - feedbackManager.openChat(action.feedbackData) + feedbackManager.openChat(chatConfig, action.feedbackData) } is GlobalAction.UpdateWalletSignedHashes -> { store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures)) 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 8a2a6032cc..2b661de566 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 @@ -5,8 +5,8 @@ import com.tangem.blockchain.common.BlockchairCredentials import com.tangem.blockchain.common.GetBlockCredentials import com.tangem.blockchain.common.NowNodeCredentials import com.tangem.blockchain.common.QuickNodeCredentials +import com.tangem.tap.common.chat.ZendeskConfig import com.tangem.tap.common.shop.shopify.ShopifyShop -import com.tangem.tap.common.zendesk.ZendeskConfig import com.tangem.tap.domain.configurable.Loader import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index aa57ec09f7..defa61e170 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -1,7 +1,7 @@ package com.tangem.tap.domain.configurable.config +import com.tangem.tap.common.chat.ZendeskConfig import com.tangem.tap.common.shop.shopify.ShopifyShop -import com.tangem.tap.common.zendesk.ZendeskConfig import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig /** diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt index 19048168de..412087154d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt @@ -1,20 +1,19 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay -import com.tangem.tap.common.zendesk.ZendeskConfig import org.spongycastle.util.encoders.Base64.toBase64String /** [REDACTED_AUTHOR] */ data class SaltPayConfig( - val zendesk: ZendeskConfig, + val sprinklrAppID: String, val kycProvider: KYCProvider, val credentials: Credentials, ) { companion object { fun stub(): SaltPayConfig { return SaltPayConfig( - zendesk = ZendeskConfig("", "", "", "", ""), + sprinklrAppID = "", kycProvider = KYCProvider("", "", "", ""), credentials = Credentials("", ""), ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt index e1c2021b61..edaf0567a1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt @@ -4,6 +4,7 @@ import android.app.Dialog import android.content.Context import androidx.appcompat.app.AlertDialog import com.tangem.common.extensions.guard +import com.tangem.tap.common.chat.SprinklrConfig import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.feedback.SupportInfo import com.tangem.tap.common.redux.global.GlobalAction @@ -20,11 +21,14 @@ class NoFundsForActivationDialog { setTitle(R.string.saltpay_error_no_gas_title) setMessage(R.string.saltpay_error_no_gas_message) setPositiveButton(R.string.chat_button_title) { _, _ -> - val config = store.state.globalState.configManager?.config?.saltPayConfig?.zendesk.guard { - store.dispatchDebugErrorNotification("SaltPayConfig not initialized") - return@setPositiveButton - } - store.dispatch(GlobalAction.OpenChat(SupportInfo(), config)) + val sprinklrAppId = store.state.globalState.configManager?.config + ?.saltPayConfig + ?.sprinklrAppID + .guard { + store.dispatchDebugErrorNotification("SaltPayConfig not initialized") + return@setPositiveButton + } + store.dispatch(GlobalAction.OpenChat(SupportInfo(), SprinklrConfig(sprinklrAppId))) } setNegativeButton(R.string.common_cancel) { _, _ -> } From 9278297133284b3dee9c6d85811811bf8cc63e79 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Feb 2023 21:32:17 +0300 Subject: [PATCH 05/46] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + app/src/main/assets/tangem-app-config | 2 +- .../java/com/tangem/tap/TapApplication.kt | 5 +- .../com/tangem/tap/common/chat/ChatConfig.kt | 4 ++ .../com/tangem/tap/common/chat/ChatManager.kt | 5 +- .../implementation/SprinklrChatOpener.kt | 15 +++++- .../com/tangem/tap/common/compose/Button.kt | 14 ------ .../com/tangem/tap/common/compose/Theme.kt | 17 ------- .../tap/common/extensions/Navigation.kt | 2 + .../com/tangem/tap/common/redux/AppReducer.kt | 4 +- .../com/tangem/tap/common/redux/AppState.kt | 4 ++ .../common/redux/global/GlobalMidlleware.kt | 3 +- .../redux/navigation/NavigationState.kt | 1 + .../products/wallet/saltPay/SaltPayConfig.kt | 5 +- .../dialog/NoFundsForActivationDialog.kt | 7 ++- .../features/sprinklr/redux/SprinklrAction.kt | 9 ++++ .../sprinklr/redux/SprinklrMiddleware.kt | 34 ++++++++++++++ .../sprinklr/redux/SprinklrReducer.kt | 19 ++++++++ .../features/sprinklr/redux/SprinklrState.kt | 5 ++ .../features/sprinklr/ui/SprinklrFragment.kt | 31 ++++++++++++ .../sprinklr/ui/SprinklrScreenContent.kt | 25 ++++++++++ .../sprinklr/ui/SprinklrScreenState.kt | 6 +++ .../features/sprinklr/ui/SprinklrViewModel.kt | 44 +++++++++++++++++ .../compose/AddCustomTokenScreen.kt | 47 +++++++++---------- .../main/res/xml/network_security_config.xml | 4 +- buildSrc/src/main/java/Dependency.kt | 1 + buildSrc/src/main/java/Versions.kt | 2 +- 27 files changed, 244 insertions(+), 72 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/compose/Theme.kt create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 34d26a35ae..b9af199725 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -194,6 +194,7 @@ dependencies { } implementation(Library.accompanistAppCompatTheme) implementation(Library.accompanistSystemUiController) + implementation(Library.accompanistWebView) implementation(Library.xmlShimmer) implementation(Library.viewBindingDelegate) implementation(Library.armadillo) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 2b8aa19f58..f5f6ac9548 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 2b8aa19f58e17a78015629943d49df0da0b4fd4b +Subproject commit f5f6ac954894ef66331db8fe383b9773feb69ef4 diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 78cf3956f2..8f6a026ae5 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -187,7 +187,7 @@ class TapApplication : Application(), ImageLoaderFactory { private fun initWithConfigDependency(config: Config) { shopService = TangemShopService(this, config.shopify!!) initAnalytics(this, config) - initFeedbackManager(this, preferencesStorage, foregroundActivityObserver) + initFeedbackManager(this, preferencesStorage, foregroundActivityObserver, store) } private fun initAnalytics(application: Application, config: Config) { @@ -213,6 +213,7 @@ class TapApplication : Application(), ImageLoaderFactory { context: Context, preferencesStorage: PreferencesStorage, foregroundActivityObserver: ForegroundActivityObserver, + store: Store, ) { fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply { appVersion = try { @@ -246,7 +247,7 @@ class TapApplication : Application(), ImageLoaderFactory { val feedbackManager = FeedbackManager( infoHolder = additionalFeedbackInfo, logCollector = tangemLogCollector, - chatManager = ChatManager(preferencesStorage, foregroundActivityObserver), + chatManager = ChatManager(preferencesStorage, foregroundActivityObserver, store), ) store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager)) } diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt index 32aab191db..01b80c8cc6 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt @@ -19,6 +19,10 @@ data class ZendeskConfig( val url: String, ) : ChatConfig +@JsonClass(generateAdapter = true) data class SprinklrConfig( + @Json(name = "appID") val appId: String, + @Json(name = "baseURL") + val baseUrl: String, ) : ChatConfig \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt index 951cf75294..324b6c9095 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt @@ -5,18 +5,21 @@ import com.tangem.tap.ForegroundActivityObserver import com.tangem.tap.common.chat.opener.ChatOpener import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener import com.tangem.tap.common.chat.opener.implementation.ZendeskChatOpener +import com.tangem.tap.common.redux.AppState import com.tangem.tap.persistence.PreferencesStorage +import org.rekotlin.Store class ChatManager( private val preferencesStorage: PreferencesStorage, private val foregroundActivityObserver: ForegroundActivityObserver, + private val store: Store, ) { private val openers = mutableMapOf() fun open(config: ChatConfig, feedbackDataBuilder: (Context) -> String) { val opener = openers.getOrPut(config) { when (config) { - is SprinklrConfig -> SprinklrChatOpener() + is SprinklrConfig -> SprinklrChatOpener(config, store) is ZendeskConfig -> ZendeskChatOpener(config, preferencesStorage, foregroundActivityObserver) } } diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt index a231a4816f..0b58d26a14 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt @@ -1,10 +1,21 @@ package com.tangem.tap.common.chat.opener.implementation import android.content.Context +import com.tangem.tap.common.chat.SprinklrConfig import com.tangem.tap.common.chat.opener.ChatOpener +import com.tangem.tap.common.extensions.dispatchOnMain +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.features.sprinklr.redux.SprinklrAction +import org.rekotlin.Store -internal class SprinklrChatOpener : ChatOpener { +internal class SprinklrChatOpener( + private val config: SprinklrConfig, + private val store: Store, +) : ChatOpener { override fun open(feedbackDataBuilder: (Context) -> String) { - // TODO: Open activity with Sprinklr WebView, will be in further MRs + store.dispatch(SprinklrAction.SetConfig(config)) + store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Sprinklr)) } } \ No newline at end of file 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 b20f7470b8..fe5aa7979a 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 @@ -12,9 +12,7 @@ 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 import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -133,18 +131,6 @@ fun ClearButton( } } -/** - * Used for disable ripple if button is enable = false - */ -@Composable -fun ToggledRippleTheme( - isEnabled: Boolean, - content: @Composable () -> Unit, -) { - val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme() - CompositionLocalProvider(theme) { content() } -} - @Preview @Composable fun ButtonTest() { diff --git a/app/src/main/java/com/tangem/tap/common/compose/Theme.kt b/app/src/main/java/com/tangem/tap/common/compose/Theme.kt deleted file mode 100644 index f0afe076ba..0000000000 --- a/app/src/main/java/com/tangem/tap/common/compose/Theme.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.tap.common.compose - -import androidx.compose.material.ripple.RippleAlpha -import androidx.compose.material.ripple.RippleTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color - -/** -[REDACTED_AUTHOR] - */ -class NoRippleTheme : RippleTheme { - @Composable - override fun defaultColor() = Color.Unspecified - - @Composable - override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f) -} \ No newline at end of file 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 711e7ed172..21dd970381 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 @@ -25,6 +25,7 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFra 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.sprinklr.ui.SprinklrFragment import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment import com.tangem.tap.features.tokens.ui.AddTokensFragment import com.tangem.tap.features.wallet.ui.WalletDetailsFragment @@ -120,5 +121,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.Welcome -> WelcomeFragment() AppScreen.SaveWallet -> SaveWalletBottomSheetFragment() AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment() + AppScreen.Sprinklr -> SprinklrFragment() } } \ No newline at end of file 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 f03dfc70ca..3bcb47a092 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 @@ -13,11 +13,12 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWallet 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.sprinklr.redux.SprinklrReducer 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 com.tangem.tap.proxy.AppStateHolder import org.rekotlin.Action fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder): AppState { @@ -42,6 +43,7 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder) welcomeState = WelcomeReducer.reduce(action, state), saveWalletState = SaveWalletReducer.reduce(action, state), walletSelectorState = WalletSelectorReducer.reduce(action, state), + sprinklrState = SprinklrReducer.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 9f7c34b90f..3e0d617fb2 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 @@ -31,6 +31,8 @@ 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 import com.tangem.tap.features.shop.redux.ShopState +import com.tangem.tap.features.sprinklr.redux.SprinklrMiddleware +import com.tangem.tap.features.sprinklr.redux.SprinklrState import com.tangem.tap.features.tokens.redux.TokensMiddleware import com.tangem.tap.features.tokens.redux.TokensState import com.tangem.tap.features.wallet.redux.WalletState @@ -60,6 +62,7 @@ data class AppState( val welcomeState: WelcomeState = WelcomeState(), val saveWalletState: SaveWalletState = SaveWalletState(), val walletSelectorState: WalletSelectorState = WalletSelectorState(), + val sprinklrState: SprinklrState = SprinklrState(), ) : StateType { val domainState: DomainState @@ -94,6 +97,7 @@ data class AppState( WalletSelectorMiddleware().middleware, LockUserWalletsTimerMiddleware().middleware, AccessCodeRequestPolicyMiddleware().middleware, + SprinklrMiddleware().middleware, ) } } 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 ed9a9e3850..4696b4a6f8 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 @@ -9,7 +9,6 @@ import com.tangem.domain.common.LogConfig import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.common.chat.SprinklrConfig import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -111,7 +110,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di // if config not set -> try to get it based on a scanResponse.productType val unsafeChatConfig = action.chatConfig ?: when { - scanResponse?.isSaltPay() == true -> config.saltPayConfig?.sprinklrAppID?.let(::SprinklrConfig) + scanResponse?.isSaltPay() == true -> config.saltPayConfig?.sprinklr else -> config.zendesk } 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 cf60dc79f6..3f313ef210 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 @@ -26,4 +26,5 @@ enum class AppScreen( Welcome, SaveWallet(isDialogFragment = true), WalletSelector(isDialogFragment = true), + Sprinklr, } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt index 412087154d..8d1ee55a33 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt @@ -1,19 +1,20 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay +import com.tangem.tap.common.chat.SprinklrConfig import org.spongycastle.util.encoders.Base64.toBase64String /** [REDACTED_AUTHOR] */ data class SaltPayConfig( - val sprinklrAppID: String, + val sprinklr: SprinklrConfig, val kycProvider: KYCProvider, val credentials: Credentials, ) { companion object { fun stub(): SaltPayConfig { return SaltPayConfig( - sprinklrAppID = "", + sprinklr = SprinklrConfig("", ""), kycProvider = KYCProvider("", "", "", ""), credentials = Credentials("", ""), ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt index edaf0567a1..a75c0e9e5c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt @@ -4,7 +4,6 @@ import android.app.Dialog import android.content.Context import androidx.appcompat.app.AlertDialog import com.tangem.common.extensions.guard -import com.tangem.tap.common.chat.SprinklrConfig import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.feedback.SupportInfo import com.tangem.tap.common.redux.global.GlobalAction @@ -21,14 +20,14 @@ class NoFundsForActivationDialog { setTitle(R.string.saltpay_error_no_gas_title) setMessage(R.string.saltpay_error_no_gas_message) setPositiveButton(R.string.chat_button_title) { _, _ -> - val sprinklrAppId = store.state.globalState.configManager?.config + val sprinklrConfig = store.state.globalState.configManager?.config ?.saltPayConfig - ?.sprinklrAppID + ?.sprinklr .guard { store.dispatchDebugErrorNotification("SaltPayConfig not initialized") return@setPositiveButton } - store.dispatch(GlobalAction.OpenChat(SupportInfo(), SprinklrConfig(sprinklrAppId))) + store.dispatch(GlobalAction.OpenChat(SupportInfo(), sprinklrConfig)) } setNegativeButton(R.string.common_cancel) { _, _ -> } diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt new file mode 100644 index 0000000000..3c5571db9a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt @@ -0,0 +1,9 @@ +package com.tangem.tap.features.sprinklr.redux + +import com.tangem.tap.common.chat.SprinklrConfig +import org.rekotlin.Action + +sealed interface SprinklrAction : Action { + data class SetConfig(val config: SprinklrConfig) : SprinklrAction + data class UpdateUrl(val url: String) : SprinklrAction +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt new file mode 100644 index 0000000000..8fd0798a25 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt @@ -0,0 +1,34 @@ +package com.tangem.tap.features.sprinklr.redux + +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.AppState +import com.tangem.tap.store +import org.rekotlin.Middleware + +internal class SprinklrMiddleware { + val middleware: Middleware = { _, appStateProvider -> + { next -> + { action -> + val appState = appStateProvider() + if (action is SprinklrAction && appState != null) { + handleAction(action) + } + next(action) + } + } + } + + private fun handleAction(action: SprinklrAction) { + when (action) { + is SprinklrAction.SetConfig -> createUrl() + is SprinklrAction.UpdateUrl -> Unit + } + } + + private fun createUrl() { + // TODO: Create Sprinklr URL, will be in further MRs + val url = "https://prod-live-chat.sprinklr.com/" + + "page?appId=60c1d169c96beb5bf5a326f3_app_950954&device=MOBILE&enableClose=true&zoom=false" + store.dispatchOnMain(SprinklrAction.UpdateUrl(url)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt new file mode 100644 index 0000000000..44e2e33785 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt @@ -0,0 +1,19 @@ +package com.tangem.tap.features.sprinklr.redux + +import com.tangem.tap.common.redux.AppState +import org.rekotlin.Action + +internal object SprinklrReducer { + fun reduce(action: Action, state: AppState): SprinklrState { + return if (action is SprinklrAction) { + internalReduce(action, state.sprinklrState) + } else state.sprinklrState + } + + private fun internalReduce(action: SprinklrAction, state: SprinklrState): SprinklrState { + return when (action) { + is SprinklrAction.SetConfig -> state + is SprinklrAction.UpdateUrl -> state.copy(url = action.url) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt new file mode 100644 index 0000000000..8323b35fd0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt @@ -0,0 +1,5 @@ +package com.tangem.tap.features.sprinklr.redux + +data class SprinklrState( + val url: String = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt new file mode 100644 index 0000000000..f029d4fa9b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt @@ -0,0 +1,31 @@ +package com.tangem.tap.features.sprinklr.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.fragment.app.viewModels +import com.tangem.core.ui.fragments.ComposeFragment + +internal class SprinklrFragment : ComposeFragment() { + private val viewModel by viewModels() + + @Composable + override fun provideState(): State { + return viewModel.state.collectAsState() + } + + @Composable + override fun ScreenContent(modifier: Modifier, state: SprinklrScreenState) { + BackHandler(onBack = state.onNavigateBack) + SprinklrScreenContent( + modifier = modifier + .systemBarsPadding() + .imePadding(), + state = state, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt new file mode 100644 index 0000000000..3cc36f08f7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.features.sprinklr.ui + +import android.annotation.SuppressLint +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.google.accompanist.web.WebView +import com.google.accompanist.web.rememberWebViewState + +@SuppressLint("SetJavaScriptEnabled") +@Composable +internal fun SprinklrScreenContent( + state: SprinklrScreenState, + modifier: Modifier = Modifier, +) { + WebView( + modifier = modifier, + state = rememberWebViewState(url = state.initialUrl), + onCreated = { webView -> + with(webView.settings) { + javaScriptEnabled = true + domStorageEnabled = true + } + }, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt new file mode 100644 index 0000000000..c1b5540b10 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt @@ -0,0 +1,6 @@ +package com.tangem.tap.features.sprinklr.ui + +internal data class SprinklrScreenState( + val initialUrl: String = "", + val onNavigateBack: () -> Unit = {}, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt new file mode 100644 index 0000000000..2da7f8c31d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.features.sprinklr.ui + +import androidx.lifecycle.ViewModel +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.sprinklr.redux.SprinklrState +import com.tangem.tap.store +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import org.rekotlin.StoreSubscriber + +internal class SprinklrViewModel : ViewModel(), StoreSubscriber { + private val stateInternal = MutableStateFlow(SprinklrScreenState()) + val state = stateInternal.asStateFlow() + + init { + subscribeToStoreChanges() + } + + override fun newState(state: SprinklrState) { + stateInternal.update { prevState -> + prevState.copy( + initialUrl = state.url, + onNavigateBack = this::navigateBack, + ) + } + } + + override fun onCleared() { + store.unsubscribe(this) + } + + private fun subscribeToStoreChanges() { + store.subscribe(this) { appState -> + appState.skip { old, new -> old.sprinklrState == new.sprinklrState } + .select { it.sprinklrState } + } + } + + private fun navigateBack() { + store.dispatchOnMain(NavigationAction.PopBackTo()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt index 93066fc926..2fbbfe4ca5 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt @@ -55,7 +55,6 @@ import com.tangem.domain.redux.domainStore import com.tangem.tap.common.compose.AddCustomTokenWarning import com.tangem.tap.common.compose.ClosePopupTrigger import com.tangem.tap.common.compose.ComposeDialogManager -import com.tangem.tap.common.compose.ToggledRippleTheme import com.tangem.tap.common.compose.keyboardAsState import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCase @@ -66,7 +65,7 @@ import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] */ -private class AddCustomTokenScreen {} // for simple search +private class AddCustomTokenScreen // for simple search @OptIn(ExperimentalMaterialApi::class) @Composable @@ -91,11 +90,13 @@ fun AddCustomTokenScreen( }, sheetPeekHeight = 0.dp, ) { - Column() { - TestCasesList(onItemClick = { - selectedTestCase.value = it - toggleBottomSheet() - }) + Column { + TestCasesList( + onItemClick = { + selectedTestCase.value = it + toggleBottomSheet() + }, + ) ScreenContent(state, closePopupTrigger) } } @@ -228,23 +229,21 @@ private fun AddCustomTokenFab( FloatingActionButtonDefaults.elevation() } - ToggledRippleTheme(isEnabled) { - ExtendedFloatingActionButton( - modifier = modifier, - icon = { - Icon( - imageVector = Icons.Filled.Add, - tint = contentColor, - contentDescription = "Add", - ) - }, - text = { Text(text = stringResource(id = R.string.common_add)) }, - onClick = { if (isEnabled) onClick() }, - backgroundColor = backgroundColor, - contentColor = contentColor, - elevation = elevation, - ) - } + ExtendedFloatingActionButton( + modifier = modifier, + icon = { + Icon( + imageVector = Icons.Filled.Add, + tint = contentColor, + contentDescription = "Add", + ) + }, + text = { Text(text = stringResource(id = R.string.common_add)) }, + onClick = { if (isEnabled) onClick() }, + backgroundColor = backgroundColor, + contentColor = contentColor, + elevation = elevation, + ) } data class ScreenFieldData( diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 2bbb4f525b..d5f3e649d4 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -16,4 +16,6 @@ - \ No newline at end of file + + + diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt index 5e9f38e8c8..fbf55749b4 100644 --- a/buildSrc/src/main/java/Dependency.kt +++ b/buildSrc/src/main/java/Dependency.kt @@ -30,6 +30,7 @@ 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 accompanistWebView = "com.google.accompanist:accompanist-webview:" + 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 diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 3ea750baf4..e587a75fd6 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -23,7 +23,7 @@ object Versions { // endregion Compose // region Other libraries - const val accompanist = "0.23.0" + const val accompanist = "0.28.0" const val amplitude = "2.36.1" const val appsflyer = "6.5.1" const val armadillo = "0.9.0" From e645edfe8c68256b43cbe7d2a611e86053e0d6e5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Feb 2023 16:02:36 +0300 Subject: [PATCH 06/46] Updated on 2026-08-14 --- .../com/tangem/tap/common/chat/ChatManager.kt | 25 +++++++++++++++-- .../implementation/SprinklrChatOpener.kt | 3 ++- .../implementation/ZendeskChatOpener.kt | 10 ++----- .../features/sprinklr/redux/SprinklrAction.kt | 3 ++- .../sprinklr/redux/SprinklrMiddleware.kt | 20 +++++++++----- .../sprinklr/redux/SprinklrReducer.kt | 3 ++- .../features/sprinklr/redux/SprinklrState.kt | 1 + .../sprinklr/redux/model/SprinklrUrl.kt | 27 +++++++++++++++++++ .../sprinklr/ui/SprinklrScreenContent.kt | 3 +++ .../sprinklr/ui/SprinklrScreenState.kt | 4 +++ .../features/sprinklr/ui/SprinklrViewModel.kt | 17 ++++++++++++ .../ui/webview/SprinklrWebViewClient.kt | 24 +++++++++++++++++ .../tap/persistence/PreferencesStorage.kt | 13 ++++++--- 13 files changed, 130 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt create mode 100644 app/src/main/java/com/tangem/tap/features/sprinklr/ui/webview/SprinklrWebViewClient.kt diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt index 324b6c9095..565e5db800 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.chat import android.content.Context +import android.os.Build import com.tangem.tap.ForegroundActivityObserver import com.tangem.tap.common.chat.opener.ChatOpener import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener @@ -19,11 +20,31 @@ class ChatManager( fun open(config: ChatConfig, feedbackDataBuilder: (Context) -> String) { val opener = openers.getOrPut(config) { when (config) { - is SprinklrConfig -> SprinklrChatOpener(config, store) - is ZendeskConfig -> ZendeskChatOpener(config, preferencesStorage, foregroundActivityObserver) + is SprinklrConfig -> SprinklrChatOpener(getSprinklrUserId(), config, store) + is ZendeskConfig -> ZendeskChatOpener(getZendeskUserId(), config, foregroundActivityObserver) } } opener.open(feedbackDataBuilder) } + + private fun getZendeskUserId(): String { + if (preferencesStorage.zendeskFirstLaunchTime == null) { + preferencesStorage.zendeskFirstLaunchTime = System.currentTimeMillis() + } + + return getChatUserId(preferencesStorage.zendeskFirstLaunchTime!!) + } + + private fun getSprinklrUserId(): String { + if (preferencesStorage.sprinklrFirstLaunchTime == null) { + preferencesStorage.sprinklrFirstLaunchTime = System.currentTimeMillis() + } + + return getChatUserId(preferencesStorage.sprinklrFirstLaunchTime!!) + } + + private fun getChatUserId(firstLaunchTimeMillis: Long): String { + return "${firstLaunchTimeMillis}${Build.MODEL}".hashCode().toString() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt index 0b58d26a14..e7be1411f6 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt @@ -11,11 +11,12 @@ import com.tangem.tap.features.sprinklr.redux.SprinklrAction import org.rekotlin.Store internal class SprinklrChatOpener( + private val userId: String, private val config: SprinklrConfig, private val store: Store, ) : ChatOpener { override fun open(feedbackDataBuilder: (Context) -> String) { - store.dispatch(SprinklrAction.SetConfig(config)) + store.dispatch(SprinklrAction.Init(userId, config)) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Sprinklr)) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt index db828b9619..1c07657555 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt @@ -1,13 +1,11 @@ package com.tangem.tap.common.chat.opener.implementation import android.content.Context -import android.os.Build import com.tangem.core.analytics.Analytics import com.tangem.domain.common.LogConfig import com.tangem.tap.ForegroundActivityObserver import com.tangem.tap.common.chat.ZendeskConfig import com.tangem.tap.common.chat.opener.ChatOpener -import com.tangem.tap.persistence.PreferencesStorage import com.tangem.tap.withForegroundActivity import com.tangem.wallet.R import com.zendesk.logger.Logger @@ -20,8 +18,8 @@ import zendesk.configurations.Configuration import zendesk.messaging.MessagingActivity internal class ZendeskChatOpener( + private val userId: String, private val config: ZendeskConfig, - private val preferencesStorage: PreferencesStorage, private val foregroundActivityObserver: ForegroundActivityObserver, ) : ChatOpener { private var isInitialized = false @@ -44,12 +42,8 @@ internal class ZendeskChatOpener( } private fun setChatVisitorInfo() { - if (preferencesStorage.chatFirstLaunchTime == null) { - preferencesStorage.chatFirstLaunchTime = System.currentTimeMillis() - } - val chatUserId = (preferencesStorage.chatFirstLaunchTime.toString() + Build.MODEL).hashCode() val visitorInfo = VisitorInfo.builder() - .withName("User $chatUserId") + .withName("User $userId") .build() Chat.INSTANCE.chatProvidersConfiguration = ChatProvidersConfiguration.builder() diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt index 3c5571db9a..1505a42f3b 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt @@ -4,6 +4,7 @@ import com.tangem.tap.common.chat.SprinklrConfig import org.rekotlin.Action sealed interface SprinklrAction : Action { - data class SetConfig(val config: SprinklrConfig) : SprinklrAction + data class Init(val userId: String, val config: SprinklrConfig) : SprinklrAction data class UpdateUrl(val url: String) : SprinklrAction + data class UpdateSprinklrDomains(val domains: List) : SprinklrAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt index 8fd0798a25..b9b2b41cde 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt @@ -1,7 +1,9 @@ package com.tangem.tap.features.sprinklr.redux +import com.tangem.tap.common.chat.SprinklrConfig import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.sprinklr.redux.model.SprinklrUrl import com.tangem.tap.store import org.rekotlin.Middleware @@ -20,15 +22,21 @@ internal class SprinklrMiddleware { private fun handleAction(action: SprinklrAction) { when (action) { - is SprinklrAction.SetConfig -> createUrl() - is SprinklrAction.UpdateUrl -> Unit + is SprinklrAction.Init -> updateUrl(action.userId, action.config) + is SprinklrAction.UpdateUrl, + is SprinklrAction.UpdateSprinklrDomains, + -> Unit } } - private fun createUrl() { - // TODO: Create Sprinklr URL, will be in further MRs - val url = "https://prod-live-chat.sprinklr.com/" + - "page?appId=60c1d169c96beb5bf5a326f3_app_950954&device=MOBILE&enableClose=true&zoom=false" + private fun updateUrl(userId: String, config: SprinklrConfig) { + updateSprinklrDomains(config.baseUrl) + val url = SprinklrUrl.Prod(userId, config.baseUrl, config.appId).url store.dispatchOnMain(SprinklrAction.UpdateUrl(url)) } + + private fun updateSprinklrDomains(baseUrl: String) { + val domains = listOf(baseUrl, SprinklrUrl.Static.url) + store.dispatchOnMain(SprinklrAction.UpdateSprinklrDomains(domains)) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt index 44e2e33785..8ec0fc117c 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt @@ -12,8 +12,9 @@ internal object SprinklrReducer { private fun internalReduce(action: SprinklrAction, state: SprinklrState): SprinklrState { return when (action) { - is SprinklrAction.SetConfig -> state + is SprinklrAction.Init -> state is SprinklrAction.UpdateUrl -> state.copy(url = action.url) + is SprinklrAction.UpdateSprinklrDomains -> state.copy(sprinklrDomains = action.domains) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt index 8323b35fd0..12ba5b8672 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt @@ -2,4 +2,5 @@ package com.tangem.tap.features.sprinklr.redux data class SprinklrState( val url: String = "", + val sprinklrDomains: List = emptyList(), ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt new file mode 100644 index 0000000000..2a76172029 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.features.sprinklr.redux.model + +internal sealed class SprinklrUrl { + abstract val url: String + + class Prod(userId: String, baseUrl: String, appId: String) : SprinklrUrl() { + private val locale = java.util.Locale.getDefault().language + + override val url: String = "$baseUrl/page?" + + "appId=$appId" + + "&device=$Device" + + "&enableClose=$CloseEnabled" + + "&zoom=$ZoomEnabled" + + "&locale=$locale" + + "&user_id=$userId" + + companion object { + private const val Device = "MOBILE" + private const val CloseEnabled = true + private const val ZoomEnabled = false + } + } + + object Static : SprinklrUrl() { + override val url: String = "live-chat-static.sprinklr.com" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt index 3cc36f08f7..f80a4b43f3 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt @@ -2,9 +2,11 @@ package com.tangem.tap.features.sprinklr.ui import android.annotation.SuppressLint import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.google.accompanist.web.WebView import com.google.accompanist.web.rememberWebViewState +import com.tangem.tap.features.sprinklr.ui.webview.SprinklrWebViewClient @SuppressLint("SetJavaScriptEnabled") @Composable @@ -21,5 +23,6 @@ internal fun SprinklrScreenContent( domStorageEnabled = true } }, + client = remember { SprinklrWebViewClient(state.onNewUrl) }, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt index c1b5540b10..8757f6e167 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt @@ -1,6 +1,10 @@ package com.tangem.tap.features.sprinklr.ui +import com.google.accompanist.web.WebContent + internal data class SprinklrScreenState( val initialUrl: String = "", + val sprinklrDomains: List = emptyList(), val onNavigateBack: () -> Unit = {}, + val onNewUrl: WebContent.(String) -> WebContent = { this }, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt index 2da7f8c31d..1c23eef584 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.sprinklr.ui import androidx.lifecycle.ViewModel +import com.google.accompanist.web.WebContent import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.sprinklr.redux.SprinklrState @@ -22,7 +23,9 @@ internal class SprinklrViewModel : ViewModel(), StoreSubscriber { stateInternal.update { prevState -> prevState.copy( initialUrl = state.url, + sprinklrDomains = state.sprinklrDomains, onNavigateBack = this::navigateBack, + onNewUrl = { updateWebContentOrOpenExternalUrl(it) }, ) } } @@ -41,4 +44,18 @@ internal class SprinklrViewModel : ViewModel(), StoreSubscriber { private fun navigateBack() { store.dispatchOnMain(NavigationAction.PopBackTo()) } + + private fun WebContent.updateWebContentOrOpenExternalUrl(url: String): WebContent { + return if (isExternalUrl(url)) { + store.dispatchOnMain(NavigationAction.OpenUrl(url)) + this + } else when (this) { + is WebContent.Url -> copy(url = url) + else -> WebContent.Url(url) + } + } + + private fun isExternalUrl(url: String): Boolean { + return state.value.sprinklrDomains.none { url.contains(it) } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/webview/SprinklrWebViewClient.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/webview/SprinklrWebViewClient.kt new file mode 100644 index 0000000000..2f2487c88a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/webview/SprinklrWebViewClient.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.features.sprinklr.ui.webview + +import android.webkit.WebResourceRequest +import android.webkit.WebView +import com.google.accompanist.web.AccompanistWebViewClient +import com.google.accompanist.web.WebContent + +internal class SprinklrWebViewClient( + private val onNewUrl: WebContent.(String) -> WebContent, +) : AccompanistWebViewClient() { + + override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { + // If the url hasn't changed, this is probably an internal event like + // a javascript reload. We should let it happen. + if (view?.url == request?.url.toString()) { + return false + } + + request?.let { + state.content = onNewUrl(state.content, it.url.toString()) + } + return true + } +} \ 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 d419aafffc..7999268acb 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -29,9 +29,13 @@ class PreferencesStorage(applicationContext: Application) { toppedUpWalletStorage = ToppedUpWalletStorage(preferences, MoshiConverter.INSTANCE) } - var chatFirstLaunchTime: Long? - get() = preferences.getLong(CHAT_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L } - set(value) = preferences.edit { putLong(CHAT_FIRST_LAUNCH_KEY, value ?: 0) } + var zendeskFirstLaunchTime: Long? + get() = preferences.getLong(ZENDESK_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L } + set(value) = preferences.edit { putLong(ZENDESK_FIRST_LAUNCH_KEY, value ?: 0) } + + var sprinklrFirstLaunchTime: Long? + get() = preferences.getLong(SPRINKLR_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L } + set(value) = preferences.edit { putLong(SPRINKLR_FIRST_LAUNCH_KEY, value ?: 0) } var shouldShowSaveUserWalletScreen: Boolean get() = preferences.getBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, true) @@ -82,7 +86,8 @@ class PreferencesStorage(applicationContext: Application) { private const val PREFERENCES_NAME = "tapPrefs" private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown" private const val APP_LAUNCH_COUNT_KEY = "launchCount" - private const val CHAT_FIRST_LAUNCH_KEY = "chatFirstLaunchKey" + private const val ZENDESK_FIRST_LAUNCH_KEY = "chatFirstLaunchKey" + private const val SPRINKLR_FIRST_LAUNCH_KEY = "sprinklrFirstLaunch" 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" From 306a9bb0ec861ee126114af373378970ed7f3cf5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Feb 2023 17:14:00 +0300 Subject: [PATCH 07/46] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../java/com/tangem/tap/TapApplication.kt | 8 +- .../configurable/config/ConfigManager.kt | 5 + .../domain/configurable/config/JsonModels.kt | 7 + .../products/wallet/saltPay/SaltPayConfig.kt | 2 + .../tap/features/wallet/redux/WalletState.kt | 2 +- .../redux/middlewares/WalletMiddleware.kt | 2 + .../tap/features/wallet/ui/WalletFragment.kt | 13 +- .../wallet/ui/wallet/SaltPayWalletView.kt | 88 ------- .../features/wallet/ui/wallet/WalletView.kt | 10 + .../wallet/saltPay/HistoryTransactionData.kt | 21 ++ .../ui/wallet/saltPay/SaltPayWalletView.kt | 218 ++++++++++++++++++ .../saltPay/rv/ShimmerRecyclerAdapter.kt | 33 +++ .../saltPay/rv/TransactionHistoryAdapter.kt | 103 +++++++++ .../utorg/UtorgExchangeService.kt | 1 - app/src/main/res/drawable/ic_accept_coin.xml | 57 +++++ app/src/main/res/drawable/ic_tx_explore.xml | 9 + app/src/main/res/drawable/ic_tx_incoming.xml | 14 ++ app/src/main/res/drawable/ic_tx_outgoing.xml | 14 ++ .../res/layout/item_salt_pay_tx_history.xml | 89 +++++++ .../layout/item_salt_pay_tx_history_date.xml | 9 + .../item_salt_pay_tx_history_shimmer.xml | 60 +++++ .../res/layout/layout_salt_pay_balance.xml | 21 +- .../res/layout/layout_salt_pay_tx_history.xml | 136 +++++++++++ .../res/layout/layout_salt_pay_wallet.xml | 10 + buildSrc/src/main/java/Versions.kt | 4 +- 26 files changed, 828 insertions(+), 110 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayWalletView.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt create mode 100644 app/src/main/res/drawable/ic_accept_coin.xml create mode 100644 app/src/main/res/drawable/ic_tx_explore.xml create mode 100644 app/src/main/res/drawable/ic_tx_incoming.xml create mode 100644 app/src/main/res/drawable/ic_tx_outgoing.xml create mode 100644 app/src/main/res/layout/item_salt_pay_tx_history.xml create mode 100644 app/src/main/res/layout/item_salt_pay_tx_history_date.xml create mode 100644 app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml create mode 100644 app/src/main/res/layout/layout_salt_pay_tx_history.xml diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 2b8aa19f58..f5f6ac9548 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 2b8aa19f58e17a78015629943d49df0da0b4fd4b +Subproject commit f5f6ac954894ef66331db8fe383b9773feb69ef4 diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 78cf3956f2..097ee1994f 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -57,6 +57,7 @@ import com.tangem.tap.persistence.PreferencesStorage import com.tangem.tap.proxy.AppStateHolder import com.tangem.wallet.BuildConfig import dagger.hilt.android.HiltAndroidApp +import okhttp3.logging.HttpLoggingInterceptor import org.rekotlin.Store import timber.log.Timber import javax.inject.Inject @@ -146,7 +147,12 @@ class TapApplication : Application(), ImageLoaderFactory { initConfigManager(configLoader, ::initWithConfigDependency) initWarningMessagesManager() - BlockchainSdkRetrofitBuilder.enableNetworkLogging = LogConfig.network.blockchainSdkNetwork + if (LogConfig.network.blockchainSdkNetwork) { + BlockchainSdkRetrofitBuilder.interceptors = listOf( + HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }, + ) + } + userTokensRepository = UserTokensRepository.init( context = this, 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 2b661de566..a45fc5c289 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 @@ -2,6 +2,7 @@ package com.tangem.tap.domain.configurable.config import com.tangem.blockchain.common.BlockchainSdkConfig import com.tangem.blockchain.common.BlockchairCredentials +import com.tangem.blockchain.common.BlockscoutCredentials import com.tangem.blockchain.common.GetBlockCredentials import com.tangem.blockchain.common.NowNodeCredentials import com.tangem.blockchain.common.QuickNodeCredentials @@ -93,6 +94,10 @@ class ConfigManager { authToken = values.blockchairAuthorizationToken, ), blockcypherTokens = values.blockcypherTokens, + blockscoutCredentials = BlockscoutCredentials( + userName = values.saltPay.blockscoutCredentials.user, + password = values.saltPay.blockscoutCredentials.password, + ), quickNodeSolanaCredentials = QuickNodeCredentials( apiKey = values.quiknodeApiKey, subdomain = values.quiknodeSubdomain, diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index defa61e170..4de75aae58 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -15,6 +15,7 @@ class FeatureModel( val isCreatingTwinCardsAllowed: Boolean, ) +@Suppress("LongParameterList") class ConfigValueModel( val coinMarketCapKey: String, val mercuryoWidgetId: String, @@ -37,6 +38,7 @@ class ConfigValueModel( val saltPay: SaltPayConfig, val tronGridApiKey: String, val amplitudeApiKey: String, + val swapReferrerAccount: SwapReferrerAccount?, ) data class AppsFlyer( @@ -44,6 +46,11 @@ data class AppsFlyer( val appsFlyerAppID: String, ) +data class SwapReferrerAccount( + val address: String, + val fee: String, +) + class ConfigModel( val features: FeatureModel?, val configValues: ConfigValueModel?, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt index 412087154d..9a0b4e1f64 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt @@ -9,6 +9,7 @@ data class SaltPayConfig( val sprinklrAppID: String, val kycProvider: KYCProvider, val credentials: Credentials, + val blockscoutCredentials: Credentials, ) { companion object { fun stub(): SaltPayConfig { @@ -16,6 +17,7 @@ data class SaltPayConfig( sprinklrAppID = "", kycProvider = KYCProvider("", "", "", ""), credentials = Credentials("", ""), + blockscoutCredentials = Credentials("", ""), ) } } 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 1b2a47181a..7e37e27a5a 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 @@ -84,7 +84,7 @@ data class WalletState( get() = if (isMultiwalletAllowed || walletsStores.isEmpty() || walletsStores.size > 1) null else walletsStores[0] - private val primaryWalletManager: WalletManager? + val primaryWalletManager: WalletManager? get() = primaryWalletStore?.walletManager val primaryWalletData: WalletData? 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 b49a33c1f1..f75992fe2b 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 @@ -249,6 +249,8 @@ class WalletMiddleware { } is NetworkStateChanged -> { store.dispatch(WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline) + if (!action.isOnline) return + val selectedUserWallet = userWalletsListManagerSafe?.selectedUserWalletSync if (selectedUserWallet != null) { scope.launch { globalState.tapWalletManager.loadData(selectedUserWallet) } 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 85de69de54..31f63630c1 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 @@ -39,9 +39,9 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState 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.SaltPayWalletView import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView import com.tangem.tap.features.wallet.ui.wallet.WalletView +import com.tangem.tap.features.wallet.ui.wallet.saltPay.SaltPayWalletView import com.tangem.tap.store import com.tangem.tap.userWalletsListManager import com.tangem.wallet.BuildConfig @@ -150,15 +150,18 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + walletView.onViewDestroy() walletView = SaltPayWalletView() walletView.changeWalletView(this, binding) } state.isMultiwalletAllowed && state.primaryWalletData?.currencyData?.status != BalanceStatus.EmptyCard && walletView !is MultiWalletView -> { + walletView.onViewDestroy() walletView = MultiWalletView() walletView.changeWalletView(this, binding) } !state.isMultiwalletAllowed && !isSaltPay && walletView !is SingleWalletView -> { + walletView.onViewDestroy() walletView = SingleWalletView() walletView.changeWalletView(this, binding) } @@ -174,7 +177,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber(500, mainScope) { store.dispatch(it) } - - override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) { - saltPayBinding = binding.lSaltPayWallet - setFragment(fragment, binding) - onViewCreated() - showSaltPayView(binding) - } - - private fun showSaltPayView(binding: FragmentWalletBinding) = with(binding) { - rvWarningMessages.hide() - rvPendingTransaction.hide() - rvMultiwallet.hide() - tvTwinCardNumber.hide() - lCardBalance.root.hide() - lSingleWalletBalance.root.hide() - lAddress.root.hide() - rowButtons.hide() - btnAddToken.hide() - pbLoadingUserTokens.hide() - lSaltPayWallet.root.show() - } - - override fun onViewCreated() { - } - - override fun onNewState(state: WalletState) { - setupBalanceWidget(state) - } - - private fun setupBalanceWidget(state: WalletState) = with(saltPayBinding.lSaltPayBalance) { - val tokenData = state.primaryTokenData ?: return@with - - val appCurrency = store.state.globalState.appCurrency - val mainProgressState = state.state - - if (mainProgressState == ProgressState.Loading) { - veilBalance.veil() - veilBalanceCrypto.veil() - } else { - veilBalanceCrypto.unVeil() - } - - tvProcessing.animateVisibility(show = mainProgressState == ProgressState.Error) - veilBalanceCrypto.animateVisibility(show = mainProgressState != ProgressState.Error) - - if (tokenData.currencyData.fiatAmount == null) { - actionDebouncer(WalletAction.LoadFiatRate()) - } else { - veilBalance.unVeil() - tvBalance.text = tokenData.currencyData.fiatAmount.formatAmountAsSpannedString( - currencySymbol = appCurrency.symbol, - ) - } - - tvBalanceCrypto.text = tokenData.currencyData.amountFormatted - - tvCurrencyName.text = appCurrency.code - tvCurrencyName.setOnClickListener { - store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) - } - - btnBuy.show(tokenData.isAvailableToBuy) - btnBuy.setOnClickListener { - store.dispatch(WalletAction.TradeCryptoAction.Buy(false)) - } - } -} \ No newline at end of file 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 d7d7cbb405..6bcdb961e5 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 @@ -5,8 +5,13 @@ import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.wallet.databinding.FragmentWalletBinding abstract class WalletView { + + //TODO: blueprint + var pullToRefreshListener: (() -> Unit)? = null + protected var fragment: WalletFragment? = null protected var binding: FragmentWalletBinding? = null + fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) { this.fragment = fragment this.binding = binding @@ -17,6 +22,11 @@ abstract class WalletView { binding = null } + //TODO: blueprint + open fun onViewDestroy() { + pullToRefreshListener = null + } + open fun onDestroyFragment() {} abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt new file mode 100644 index 0000000000..50b80f0b37 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.features.wallet.ui.wallet.saltPay + +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionStatus +import com.tangem.tap.features.wallet.models.PendingTransactionType + +/** +[REDACTED_AUTHOR] + */ +data class HistoryTransactionData( + val transactionData: TransactionData, + private val walletAddress: String, +) { + fun isInProgress(): Boolean = transactionData.status == TransactionStatus.Unconfirmed + + fun getTransactionType(): PendingTransactionType = when { + transactionData.sourceAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Outgoing + transactionData.destinationAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Incoming + else -> PendingTransactionType.Unknown + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt new file mode 100644 index 0000000000..2a05484270 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt @@ -0,0 +1,218 @@ +package com.tangem.tap.features.wallet.ui.wallet.saltPay + +import android.view.LayoutInflater +import android.view.ViewGroup +import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager +import com.tangem.blockchain.extensions.Result +import com.tangem.domain.common.extensions.debounce +import com.tangem.domain.common.extensions.withMainContext +import com.tangem.tap.common.extensions.animateVisibility +import com.tangem.tap.common.extensions.beginDelayedTransition +import com.tangem.tap.common.extensions.formatAmountAsSpannedString +import com.tangem.tap.common.extensions.hide +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.recyclerView.SpaceItemDecoration +import com.tangem.tap.domain.getFirstToken +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.ui.WalletFragment +import com.tangem.tap.features.wallet.ui.wallet.WalletView +import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.ShimmerData +import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.ShimmerRecyclerAdapter +import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.TransactionHistoryAdapter +import com.tangem.tap.mainScope +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.wallet.databinding.FragmentWalletBinding +import com.tangem.wallet.databinding.ItemSaltPayTxHistoryShimmerBinding +import com.tangem.wallet.databinding.LayoutSaltPayBalanceBinding +import com.tangem.wallet.databinding.LayoutSaltPayTxHistoryBinding +import com.tangem.wallet.databinding.LayoutSaltPayWalletBinding +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.rekotlin.Action +import java.util.concurrent.atomic.AtomicBoolean + +/** +[REDACTED_AUTHOR] + */ +class SaltPayWalletView : WalletView() { + + private lateinit var saltPayBinding: LayoutSaltPayWalletBinding + private val actionDebouncer = debounce(500, mainScope) { store.dispatch(it) } + + private val balanceWidget: LayoutSaltPayBalanceBinding + get() = saltPayBinding.lSaltPayBalance + + private val txWidget: LayoutSaltPayTxHistoryBinding + get() = saltPayBinding.lSaltPayTxHistory + + private var initialized = AtomicBoolean(false) + + override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) { + saltPayBinding = binding.lSaltPayWallet + setFragment(fragment, binding) + onViewCreated() + showSaltPayView(binding) + } + + override fun onViewCreated() { + this.pullToRefreshListener = this::handlePullToRefresh + prepareTxRecyclers() + } + + private fun showSaltPayView(binding: FragmentWalletBinding) = with(binding) { + rvWarningMessages.hide() + rvPendingTransaction.hide() + rvMultiwallet.hide() + tvTwinCardNumber.hide() + lCardBalance.root.hide() + lSingleWalletBalance.root.hide() + lAddress.root.hide() + rowButtons.hide() + btnAddToken.hide() + pbLoadingUserTokens.hide() + lSaltPayWallet.root.show() + } + + private fun prepareTxRecyclers() { + val vhViewFactory: (ViewGroup) -> ViewGroup = { + val inflater = LayoutInflater.from(it.context) + ItemSaltPayTxHistoryShimmerBinding.inflate(inflater, it, false).root + } + val adapter = ShimmerRecyclerAdapter(vhViewFactory) + txWidget.rvTxHistoryShimmer.adapter = adapter + txWidget.rvTxHistoryShimmer.addItemDecoration(SpaceItemDecoration.vertical(10F)) + + txWidget.rvTxHistory.adapter = TransactionHistoryAdapter() + txWidget.rvTxHistory.addItemDecoration(SpaceItemDecoration.vertical(10F)) + + handleTxInit() + } + + private fun handlePullToRefresh() { + requestTxHistory(store.state.walletState) + } + + override fun onNewState(state: WalletState) { + if (!initialized.getAndSet(true)) requestTxHistory(state) + setupBalanceWidget(state) + } + + private fun setupBalanceWidget(state: WalletState) = with(balanceWidget) { + val tokenData = state.primaryTokenData ?: return@with + + val appCurrency = store.state.globalState.appCurrency + val mainProgressState = state.state + + if (mainProgressState == ProgressState.Loading) { + veilBalance.veil() + veilBalanceCrypto.veil() + } else { + veilBalanceCrypto.unVeil() + } + + tvUnreachable.animateVisibility(show = mainProgressState == ProgressState.Error) + veilBalanceCrypto.animateVisibility(show = mainProgressState != ProgressState.Error) + + if (tokenData.currencyData.fiatAmount == null) { + actionDebouncer(WalletAction.LoadFiatRate()) + } else { + veilBalance.unVeil() + tvBalance.text = tokenData.currencyData.fiatAmount.formatAmountAsSpannedString( + currencySymbol = appCurrency.symbol, + ) + } + + tvBalanceCrypto.text = tokenData.currencyData.amountFormatted + + tvCurrencyName.text = appCurrency.code + tvCurrencyName.setOnClickListener { + store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) + } + + btnBuy.show(tokenData.isAvailableToBuy) + btnBuy.setOnClickListener { + store.dispatch(WalletAction.TradeCryptoAction.Buy(false)) + } + } + + private fun requestTxHistory(state: WalletState) { + scope.launch { + val walletManager = state.primaryWalletManager as? EthereumWalletManager ?: return@launch + val wallet = walletManager.wallet + val token = wallet.getFirstToken() ?: return@launch + // val walletAddress = wallet.address + val walletAddress = "0xDA94Aae02a4Db0e09E1Cf240E3a0973ba89052cf" + when (val result = walletManager.getTransactionHistory(walletAddress, wallet.blockchain, setOf(token))) { + is Result.Success -> { + val tokensHistory = result.data + .filter { it.contractAddress == token.contractAddress } + .sortedBy { it.date?.timeInMillis ?: 0 } + .map { HistoryTransactionData(it, walletAddress) } + // .toMutableList().apply { clear() } + + delay(300) + withMainContext { + if (tokensHistory.isEmpty()) { + handleTxEmpty() + } else { + handleTxSuccess(tokensHistory) + } + } + } + is Result.Failure -> { + delay(300) + withMainContext { handleTxError() } + } + } + } + } + + private fun handleTxInit() = with(txWidget) { + (rvTxHistoryShimmer.adapter as? ShimmerRecyclerAdapter)?.submitList( + listOf( + ShimmerData(), + ShimmerData(), + ShimmerData(), + ), + ) + groupEmpty.hide() + groupError.hide() + groupSuccess.hide() + root.beginDelayedTransition() + txWidget.groupShimmer.show() + } + + private fun handleTxSuccess(dataList: List) = with(txWidget) { + groupShimmer.hide() + groupEmpty.hide() + groupError.hide() + root.beginDelayedTransition() + updateTxHistoryWidget(dataList) + groupSuccess.show() + } + + private fun handleTxEmpty() = with(txWidget) { + groupShimmer.hide() + groupError.hide() + groupSuccess.hide() + root.beginDelayedTransition() + updateTxHistoryWidget(emptyList()) + groupEmpty.show() + } + + private fun handleTxError() = with(txWidget) { + groupShimmer.hide() + groupEmpty.hide() + groupSuccess.hide() + root.beginDelayedTransition() + updateTxHistoryWidget(emptyList()) + groupError.show() + } + + private fun updateTxHistoryWidget(dataList: List) = with(txWidget) { + (rvTxHistory.adapter as TransactionHistoryAdapter).submitList(dataList) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt new file mode 100644 index 0000000000..fc59d49155 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView + +/** +[REDACTED_AUTHOR] + */ +open class ShimmerRecyclerAdapter( + private val viewHolderViewFactory: (ViewGroup) -> ViewGroup, +) : ListAdapter(DiffUtilCallback) { + + override fun getItemId(position: Int): Long { + return if (currentList.isEmpty()) 0 else currentList[position].hashCode().toLong() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShimmerVH { + return ShimmerVH(viewHolderViewFactory.invoke(parent)) + } + + override fun onBindViewHolder(holder: ShimmerVH, position: Int) {} + + object DiffUtilCallback : DiffUtil.ItemCallback() { + override fun areContentsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem + override fun areItemsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem + } +} + +class ShimmerVH(viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup) + +data class ShimmerData(private val any: String = "") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt new file mode 100644 index 0000000000..bc5f923aff --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt @@ -0,0 +1,103 @@ +package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv + +import android.graphics.PorterDuff +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.tangem.tap.common.extensions.getColor +import com.tangem.tap.common.extensions.setDrawable +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.features.wallet.models.PendingTransactionType +import com.tangem.tap.features.wallet.ui.wallet.saltPay.HistoryTransactionData +import com.tangem.wallet.R +import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding + +/** +[REDACTED_AUTHOR] + */ +internal class TransactionHistoryAdapter : ListAdapter(DiffUtilCallback) { + + override fun getItemId(position: Int): Long { + return if (currentList.isEmpty()) 0 + else currentList[position].transactionData.hash.hashCode().toLong() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionItemVH { + val inflater = LayoutInflater.from(parent.context) + val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false) + return TransactionItemVH(layout) + } + + override fun onBindViewHolder(holder: TransactionItemVH, position: Int) { + holder.bind(currentList[position]) + } + + object DiffUtilCallback : DiffUtil.ItemCallback() { + override fun areContentsTheSame( + oldItem: HistoryTransactionData, + newItem: HistoryTransactionData, + ) = oldItem == newItem + + override fun areItemsTheSame( + oldItem: HistoryTransactionData, + newItem: HistoryTransactionData, + ) = oldItem == newItem + } +} + +internal class TransactionItemVH( + private val binding: ItemSaltPayTxHistoryBinding, +) : RecyclerView.ViewHolder(binding.root) { + + fun bind(data: HistoryTransactionData) = with(binding) { + setupImage(data) + setupTitle(data) + setupSubtitle(data) + setupBalance(data) + } + + private fun setupImage(data: HistoryTransactionData) = with(binding) { + val drawable = when (data.getTransactionType()) { + PendingTransactionType.Incoming -> R.drawable.ic_tx_incoming + PendingTransactionType.Outgoing -> R.drawable.ic_tx_outgoing + PendingTransactionType.Unknown -> null + } + drawable?.let { imvTx.setDrawable(it) } + + if (data.isInProgress()) { + imvTx.setColorFilter(imvTx.getColor(R.color.icon_attention), PorterDuff.Mode.MULTIPLY) + } else { + imvTx.clearColorFilter() + } + } + + private fun setupTitle(data: HistoryTransactionData) = with(binding) { + val hash = data.transactionData.hash ?: return@with + + val formattedHash = "${hash.substring(0..5)}...${hash.substring(hash.length - 4)}" + tvTxHash.text = formattedHash + } + + private fun setupSubtitle(data: HistoryTransactionData) = with(binding) { + val inProgress = data.isInProgress() + tvTxStatus.show(inProgress) + tvTxTime.show(!inProgress) + + val time = data.transactionData.date?.timeInMillis.toString() ?: "none" + tvTxTime.text = time + } + + private fun setupBalance(data: HistoryTransactionData) = with(binding) { + val sign = when (data.getTransactionType()) { + PendingTransactionType.Incoming -> "+" + PendingTransactionType.Outgoing -> "-" + PendingTransactionType.Unknown -> "" + } + val amount = data.transactionData.amount + tvTxAmountSign.text = sign + tvTxAmountValue.text = amount.value?.toFormattedCurrencyString(8, amount.currencySymbol) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt index 21c8c48424..820751a14b 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt @@ -53,7 +53,6 @@ class UtorgExchangeService( override fun isSellAllowed(): Boolean = false override fun availableForBuy(currency: Currency): Boolean { - return true if (!isBuyAllowed()) return false val foundUtorgCurrency = utorgCurrencies.firstOrNull { utorgCurrency -> diff --git a/app/src/main/res/drawable/ic_accept_coin.xml b/app/src/main/res/drawable/ic_accept_coin.xml new file mode 100644 index 0000000000..6df78b4b77 --- /dev/null +++ b/app/src/main/res/drawable/ic_accept_coin.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_tx_explore.xml b/app/src/main/res/drawable/ic_tx_explore.xml new file mode 100644 index 0000000000..0c0c7403f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_tx_explore.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_tx_incoming.xml b/app/src/main/res/drawable/ic_tx_incoming.xml new file mode 100644 index 0000000000..1f8d16c0ff --- /dev/null +++ b/app/src/main/res/drawable/ic_tx_incoming.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_tx_outgoing.xml b/app/src/main/res/drawable/ic_tx_outgoing.xml new file mode 100644 index 0000000000..e6b391e594 --- /dev/null +++ b/app/src/main/res/drawable/ic_tx_outgoing.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/layout/item_salt_pay_tx_history.xml b/app/src/main/res/layout/item_salt_pay_tx_history.xml new file mode 100644 index 0000000000..bde605f95d --- /dev/null +++ b/app/src/main/res/layout/item_salt_pay_tx_history.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml new file mode 100644 index 0000000000..8e45e44e0f --- /dev/null +++ b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml b/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml new file mode 100644 index 0000000000..72aff47fa0 --- /dev/null +++ b/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_salt_pay_balance.xml b/app/src/main/res/layout/layout_salt_pay_balance.xml index ff1772d79b..cdf2d00c6a 100644 --- a/app/src/main/res/layout/layout_salt_pay_balance.xml +++ b/app/src/main/res/layout/layout_salt_pay_balance.xml @@ -21,12 +21,10 @@ android:id="@+id/tv_title" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="8dp" android:text="@string/onboarding_balance_title" android:textColor="@color/text_tertiary" android:textSize="14sp" android:textStyle="bold" - app:layout_constraintEnd_toStartOf="@id/tv_currency_name" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -34,8 +32,8 @@ android:id="@+id/veil_balance" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="4dp" - app:layout_constraintBottom_toTopOf="@id/tv_processing" + android:layout_marginTop="6dp" + app:layout_constraintBottom_toTopOf="@id/tv_unreachable" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_title" @@ -65,7 +63,7 @@ android:id="@+id/veil_balance_crypto" android:layout_width="wrap_content" android:layout_height="18dp" - android:layout_marginTop="4dp" + android:layout_marginTop="6dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/veil_balance" app:veilLayout_baseColor="@color/lightGray0" @@ -81,7 +79,6 @@ android:id="@+id/tv_balance_crypto" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="2dp" android:maxLines="1" android:minWidth="152dp" android:textColor="@color/text_tertiary" @@ -92,14 +89,14 @@ @@ -108,7 +105,7 @@ style="@style/BaseSaltPayButton" android:layout_width="0dp" android:layout_height="40dp" - android:layout_marginTop="12dp" + android:layout_marginTop="76dp" android:text="@string/wallet_button_buy" android:visibility="gone" app:icon="@drawable/ic_add" @@ -116,7 +113,7 @@ app:iconTint="@color/selector_saltpay_btn_text" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/veil_balance_crypto" + app:layout_constraintTop_toBottomOf="@+id/tv_title" tools:visibility="visible" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_salt_pay_wallet.xml b/app/src/main/res/layout/layout_salt_pay_wallet.xml index c27f1b8c8a..4712e76937 100644 --- a/app/src/main/res/layout/layout_salt_pay_wallet.xml +++ b/app/src/main/res/layout/layout_salt_pay_wallet.xml @@ -19,4 +19,14 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 3ea750baf4..3fd7975b23 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -59,8 +59,8 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-152-hotfix-4.0.1-156" - // const val tangemBlockchainSdk = "0.0.1" + // const val tangemBlockchainSdk = "develop-152-hotfix-4.0.1-156" + const val tangemBlockchainSdk = "0.0.1" const val tangemCardSdk = "develop-179-hotfix-4.0.1-189" // endregion Tangem From 4c3d7613f00984e65cde01d55a9538fc52493fd0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Feb 2023 13:02:01 +0300 Subject: [PATCH 08/46] Updated on 2026-08-14 --- .../features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt index 2a05484270..e1d9cbb642 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt @@ -143,8 +143,8 @@ class SaltPayWalletView : WalletView() { val walletManager = state.primaryWalletManager as? EthereumWalletManager ?: return@launch val wallet = walletManager.wallet val token = wallet.getFirstToken() ?: return@launch - // val walletAddress = wallet.address - val walletAddress = "0xDA94Aae02a4Db0e09E1Cf240E3a0973ba89052cf" + val walletAddress = wallet.address + // val walletAddress = "0xDA94Aae02a4Db0e09E1Cf240E3a0973ba89052cf" when (val result = walletManager.getTransactionHistory(walletAddress, wallet.blockchain, setOf(token))) { is Result.Success -> { val tokensHistory = result.data From 44b1c289c0117e712460174c662ab0d42615f661 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Feb 2023 13:03:17 +0300 Subject: [PATCH 09/46] Updated on 2026-08-14 --- .../wallet/saltPay/GnosisRegistrator.kt | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index ddf80bdc07..8a6403aa27 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -23,6 +23,8 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import java.math.BigDecimal import java.math.BigInteger +import java.math.MathContext +import java.math.RoundingMode import java.util.concurrent.atomic.AtomicLong /** @@ -33,11 +35,12 @@ class GnosisRegistrator( ) { private val walletAddress = walletManager.wallet.address + private val blockchain = walletManager.wallet.blockchain private val token = walletManager.wallet.getFirstToken().guard { throw NullPointerException("WalletManager for GnosisRegistrator must contain token") } - private val otpProcessorContractAddress: String = when (walletManager.wallet.blockchain) { + private val otpProcessorContractAddress: String = when (blockchain) { Blockchain.SaltPay -> "0xc659f4FEd7A84a188F54cBA4A7a49D77c1a20522" else -> throw IllegalArgumentException("GnosisRegistrator supports only the SaltPay blockchain") } @@ -154,6 +157,12 @@ class GnosisRegistrator( return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error)) } } + + // return transferFromStandardWay(amountToClaim, signer) + return transferFromHardcodeWay(amountToClaim, signer) + } + + private suspend fun transferFromStandardWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result { val amount = Amount(token, amountToClaim) val feeAmount = walletManager.getFeeToTransferFrom(amount, addressTreasureSafe) .extractFeeAmount().successOr { return it } @@ -164,6 +173,34 @@ class GnosisRegistrator( return Result.Success(Unit) } + private suspend fun transferFromHardcodeWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result { + val amount = Amount(token, amountToClaim) + val gasPrice = walletManager.getGasPrice().successOr { return it } + + val hardcodeGasLimit = BigInteger("300000") + val hardcodeFeeValue = (hardcodeGasLimit * gasPrice).toBigDecimal( + scale = blockchain.decimals(), + mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN), + ).movePointLeft(blockchain.decimals()) + val hardcodeFeeAmount = Amount(hardcodeFeeValue, blockchain) + + val transactionData = walletManager.createTransferFromTransaction( + amount = amount, + fee = hardcodeFeeAmount, + source = addressTreasureSafe, + ) + val transactionToSign = walletManager.transactionBuilder.buildTransferFromToSign( + transactionData, + walletManager.txCount.toBigInteger(), + hardcodeGasLimit, + ) ?: return Result.Failure(BlockchainSdkError.CustomError("Not enough data")) + + return when (val result = walletManager.signAndSend(transactionToSign, signer)) { + SimpleResult.Success -> Result.Success(Unit) + is SimpleResult.Failure -> Result.Failure(result.error) + } + } + private fun Result>.extractFeeAmount(): Result { return when (this) { is Result.Success -> { From 24edaba88f1cc66da0ed01bcdb79565fdd365e39 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Feb 2023 16:36:59 +0300 Subject: [PATCH 10/46] Updated on 2026-08-14 --- .../redux/OnboardingSaltPayMiddleware.kt | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 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 dfdfb619b5..00575f7e6e 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 @@ -91,6 +91,17 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App handleInProgress = false dispatchOnMain(OnboardingSaltPayAction.SetStep(updateStep)) + + if (updateStep == SaltPayActivationStep.Claim) { + handleClaimRefreshInProgress = true + val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr { + SaltPayExceptionHandler.handle(it.error) + handleClaimRefreshInProgress = false + return@launch + } + dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue)) + handleClaimRefreshInProgress = false + } } } is OnboardingSaltPayAction.RegisterCard -> { @@ -220,35 +231,30 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App val state = getState() scope.launch { - when (val amountToClaimResult = state.saltPayManager.getAmountToClaim()) { - is Result.Success -> { - // need to re claim - handleClaimRefreshInProgress = false - dispatchOnMain(OnboardingSaltPayAction.SetAmountToClaim(amountToClaimResult.data)) - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.Claim)) - } - is Result.Failure -> { - when (amountToClaimResult.error) { - is SaltPayActivationError.NoFundsToClaim -> { - val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr { - SaltPayExceptionHandler.handle(it.error) - handleClaimRefreshInProgress = false - return@launch - } + val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr { + SaltPayExceptionHandler.handle(it.error) + handleClaimRefreshInProgress = false + return@launch + } + dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue)) - handleClaimRefreshInProgress = false - if (tokenAmountValue.isPositive()) { - dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue)) - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) - } - } - else -> { - handleClaimRefreshInProgress = false - SaltPayExceptionHandler.handle(amountToClaimResult.error) - } - } + val amountToClaim = state.saltPayManager.getAmountToClaim().successOr { + if (it.error !is SaltPayActivationError.NoFundsToClaim) { + handleClaimRefreshInProgress = false + SaltPayExceptionHandler.handle(it.error) + return@launch + } else { + null } } + handleClaimRefreshInProgress = false + + if (tokenAmountValue.isPositive() && amountToClaim == null) { + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) + } else { + // mb in the next steps we decide to claim again, but now we going to ClaimSuccess step + dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) + } } } is OnboardingSaltPayAction.SetStep -> { From 26fe4e24b79def24b306644b53e7f437928a3f8d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 07:34:32 +0300 Subject: [PATCH 11/46] Updated on 2026-08-14 --- .../wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt | 3 +-- 1 file changed, 1 insertion(+), 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 00575f7e6e..5f39361096 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 @@ -252,8 +252,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App if (tokenAmountValue.isPositive() && amountToClaim == null) { dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) } else { - // mb in the next steps we decide to claim again, but now we going to ClaimSuccess step - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) + // dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) } } } From d6b583e5fd957b97ecc1f9cee60484bfea08946f Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 07:53:30 +0300 Subject: [PATCH 12/46] Updated on 2026-08-14 --- .../domain/extensions/WalletManagerFactory.kt | 23 ---- .../tap/domain/scanCard/ScanCardProcessor.kt | 9 +- .../redux/OnboardingWalletMiddleware.kt | 11 +- .../saltPay/SaltPayActivationManager.kt | 101 ++++++++++++++++++ .../saltPay/redux/OnboardingSaltPayAction.kt | 2 - .../saltPay/redux/OnboardingSaltPayReducer.kt | 1 - .../saltPay/redux/OnboardingSaltPayState.kt | 60 ----------- 7 files changed, 114 insertions(+), 93 deletions(-) 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 989793e59a..a2801c5a8f 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 @@ -1,19 +1,15 @@ package com.tangem.tap.domain.extensions -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.DerivationParams 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.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 import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation @@ -137,23 +133,4 @@ private fun selectWallet(wallets: List): CardDTO.Wallet? { 1 -> wallets[0] else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0] } -} - -fun WalletManagerFactory.makeSaltPayWalletManager( - scanResponse: ScanResponse, -): EthereumWalletManager { - val blockchain = scanResponse.getBlockchain() - if (blockchain != Blockchain.SaltPay) - throw IllegalArgumentException() - - val token = SaltPayWorkaround.tokenFrom(blockchain) - val cardWallet = scanResponse.card.wallets.firstOrNull().guard { - throw NullPointerException("SaltPay card must have one wallet at least") - } - - return makeWalletManager( - blockchain = blockchain, - publicKey = Wallet.PublicKey(cardWallet.publicKey, null, null), - tokens = listOf(token), - ) as EthereumWalletManager } \ 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 6caafef969..acf77e3bdb 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 @@ -27,10 +27,10 @@ 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.SaltPayActivationManagerFactory import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError 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 @@ -187,7 +187,10 @@ object ScanCardProcessor { if (scanResponse.isSaltPay()) { if (scanResponse.isSaltPayVisa()) { - val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) + val manager = SaltPayActivationManagerFactory( + blockchain = scanResponse.getBlockchain(), + card = scanResponse.card, + ).create() val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) delay(500) withMainContext { @@ -197,7 +200,7 @@ object ScanCardProcessor { if (isOnboardingCase) { onWalletNotCreated() store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) - store.dispatch(OnboardingSaltPayAction.SetDependencies(manager, config)) + store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) store.dispatch(OnboardingSaltPayAction.Update) navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } } else { 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 939236339c..39b9347a99 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,13 +3,13 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import com.tangem.blockchain.common.Blockchain import com.tangem.common.CompletionResult import com.tangem.common.extensions.ifNotNull +import com.tangem.core.analytics.Analytics 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 import com.tangem.operations.backup.BackupService import com.tangem.tap.backupService -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain @@ -22,8 +22,8 @@ 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.SaltPayActivationManagerFactory 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.wallet.redux.Artwork import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.preferencesStorage @@ -396,8 +396,11 @@ private fun initSaltPayOnBackupFinishedIfNeeded( if (onboardingWalletState.isSaltPay && onboardingWalletState.onboardingSaltPayState == null) { if (scanResponse == null) throw IllegalArgumentException() - val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse) - store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config)) + val manager = SaltPayActivationManagerFactory( + blockchain = scanResponse.getBlockchain(), + card = scanResponse.card, + ).create() + store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager)) store.dispatchOnMain(OnboardingSaltPayAction.Update) } } 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 ff6ec003d6..1ae879918a 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 @@ -1,14 +1,20 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay import android.net.Uri +import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.TransactionSigner +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.extensions.successOr import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard +import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.isZero import com.tangem.common.services.Result import com.tangem.datasource.api.paymentology.AttestationResponse @@ -18,12 +24,15 @@ 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.domain.common.CardDTO +import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.extensions.successOr import com.tangem.operations.attestation.AttestWalletKeyResponse import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import com.tangem.tap.store import java.math.BigDecimal /** @@ -200,4 +209,96 @@ class KYCUrlProvider( .appendQueryParameter(kycProvider.externalIdParameterKey, kycRefId) .build().toString() } +} + +class SaltPayActivationManagerFactory( + private val blockchain: Blockchain, + private val card: CardDTO, +) { + + fun create(): SaltPayActivationManager { + val wallet = card.wallets.first() + val saltPayConfig = store.state.globalState.configManager?.config?.saltPayConfig.guard { + throw NullPointerException("SaltPayConfig is not initialized") + } + val wmFactory = store.state.globalState.tapWalletManager.walletManagerFactory + val paymentologyService = store.state.domainNetworks.paymentologyService + + // return createDummyActivationManager(saltPayConfig, wmFactory, paymentologyService) + return createActivationPayManager( + cardId = card.cardId, + cardPublicKey = card.cardPublicKey, + kycProvider = saltPayConfig.kycProvider, + gnosisRegistrator = GnosisRegistrator( + walletManager = makeSaltPayWalletManager( + blockchain = blockchain, + walletPublicKey = wallet.publicKey, + wmFactory = wmFactory, + ), + ), + paymentologyService = paymentologyService, + ) + } + + private fun makeSaltPayWalletManager( + blockchain: Blockchain, + walletPublicKey: ByteArray, + wmFactory: WalletManagerFactory, + ): EthereumWalletManager { + return wmFactory.makeWalletManager( + blockchain = blockchain, + publicKey = Wallet.PublicKey(walletPublicKey, null, null), + tokens = listOf(SaltPayWorkaround.tokenFrom(blockchain)), + ) as EthereumWalletManager + } + + private fun createActivationPayManager( + cardId: String, + cardPublicKey: ByteArray, + kycProvider: KYCProvider, + gnosisRegistrator: GnosisRegistrator, + paymentologyService: PaymentologyApiService, + ): SaltPayActivationManager { + return SaltPayActivationManager( + cardId = cardId, + cardPublicKey = cardPublicKey, + kycProvider = kycProvider, + paymentologyService = paymentologyService, + gnosisRegistrator = gnosisRegistrator, + ) + } + + private fun createDummyActivationManager( + saltPayConfig: SaltPayConfig, + wmFactory: WalletManagerFactory, + paymentologyService: PaymentologyApiService, + ): SaltPayActivationManager { + val fakeSource = DummySaltPayCardSource() + + val walletManager = wmFactory.makeWalletManager( + blockchain = fakeSource.blockchain, + publicKey = fakeSource.blockchainPublicKey, + tokens = listOf(fakeSource.token), + ) as EthereumWalletManager + + return createActivationPayManager( + cardId = fakeSource.cardId, + cardPublicKey = fakeSource.cardPublicKey, + kycProvider = saltPayConfig.kycProvider, + gnosisRegistrator = GnosisRegistrator(walletManager), + paymentologyService = paymentologyService, + ) + } + + private data class DummySaltPayCardSource( + val blockchain: Blockchain = Blockchain.SaltPay, + val cardId: String = "FF03000001001057", + private val cardPublicKeyString: String = "039C2D2F9B68003766BFC29766761F55F4084E48B8B012BF439E115A79AB122D48", + private val walletPublicKeyString: String = "0399B2DF5B129FBF69097DAA7C44FB5849E578CCFC22228A409870C74B56C4C3D1", + ) { + val cardPublicKey: ByteArray = cardPublicKeyString.hexToBytes() + val walletPublicKey: ByteArray = walletPublicKeyString.hexToBytes() + val blockchainPublicKey = Wallet.PublicKey(walletPublicKey, null, null) + val token: Token = SaltPayWorkaround.tokenFrom(blockchain) + } } \ No newline at end of file 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 a2194e9568..73c1700fc6 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 @@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux import com.tangem.blockchain.common.Amount import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig import org.rekotlin.Action import java.math.BigDecimal @@ -14,7 +13,6 @@ sealed class OnboardingSaltPayAction : Action { data class SetDependencies( val registrationManager: SaltPayActivationManager, - val saltPayConfig: SaltPayConfig, ) : OnboardingSaltPayAction() data class SetInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction() 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 71570fd52e..e6ea4af732 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 @@ -21,7 +21,6 @@ private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingW return onboardingWalletState.copy( onboardingSaltPayState = OnboardingSaltPayState( saltPayManager = action.registrationManager, - saltPayConfig = action.saltPayConfig, ), ) } 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 a14ad10252..9bedb49011 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 @@ -2,19 +2,10 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain -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.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 -import com.tangem.tap.features.onboarding.products.wallet.saltPay.KYCProvider import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.store import java.math.BigDecimal /** @@ -23,7 +14,6 @@ import java.math.BigDecimal data class OnboardingSaltPayState( @Transient val saltPayManager: SaltPayActivationManager = SaltPayActivationManager.stub(), - val saltPayConfig: SaltPayConfig = SaltPayConfig.stub(), val pinCode: String? = null, val accessCode: String? = null, val amountToClaim: Amount? = null, @@ -37,57 +27,7 @@ data class OnboardingSaltPayState( val mainButtonState: WidgetState get() = if (inProgress) ProgressState.Loading else ProgressState.Done - fun readyToClaim(): Boolean = amountToClaim != null - val pinLength: Int = 4 - - companion object { - fun initDependency(scanResponse: ScanResponse): Pair { - val globalState = store.state.globalState - val saltPayConfig = globalState.configManager?.config?.saltPayConfig.guard { - throw NullPointerException("SaltPayConfig is not initialized") - } - val gnosisRegistrator = makeGnosisRegistrator( - scanResponse = scanResponse, - wmFactory = globalState.tapWalletManager.walletManagerFactory, - ) - val registrationManager = makeSaltPayRegistrationManager( - scanResponse = scanResponse, - gnosisRegistrator = gnosisRegistrator, - paymentologyService = store.state.domainNetworks.paymentologyService, - kycProvider = saltPayConfig.kycProvider, - ) - return registrationManager to saltPayConfig - } - - fun makeSaltPayRegistrationManager( - scanResponse: ScanResponse, - gnosisRegistrator: GnosisRegistrator, - paymentologyService: PaymentologyApiService, - kycProvider: KYCProvider, - ): SaltPayActivationManager { - if (!scanResponse.isSaltPay()) { - throw IllegalArgumentException("Can't initialize the OnboardingSaltPayMiddleware if card is not SalPay") - } - - return SaltPayActivationManager( - cardId = scanResponse.card.cardId, - cardPublicKey = scanResponse.card.cardPublicKey, - kycProvider = kycProvider, - paymentologyService = paymentologyService, - gnosisRegistrator = gnosisRegistrator, - ) - } - - fun makeGnosisRegistrator( - scanResponse: ScanResponse, - wmFactory: WalletManagerFactory, - ): GnosisRegistrator { - return GnosisRegistrator( - walletManager = wmFactory.makeSaltPayWalletManager(scanResponse), - ) - } - } } enum class SaltPayActivationStep { From 28d40bd7e7eb9a40f3cec5b78e2fd9af1639ac80 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 10:22:48 +0300 Subject: [PATCH 13/46] Updated on 2026-08-14 --- app/src/main/res/layout/item_salt_pay_tx_history.xml | 4 ++-- app/src/main/res/layout/layout_salt_pay_tx_history.xml | 6 +++--- core/res/src/main/res/values-de/strings.xml | 4 ++++ core/res/src/main/res/values-fr/strings.xml | 4 ++++ core/res/src/main/res/values-it/strings.xml | 4 ++++ core/res/src/main/res/values-ru/strings.xml | 4 ++++ core/res/src/main/res/values/strings.xml | 4 ++++ 7 files changed, 25 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/layout/item_salt_pay_tx_history.xml b/app/src/main/res/layout/item_salt_pay_tx_history.xml index bde605f95d..5e41bb38d1 100644 --- a/app/src/main/res/layout/item_salt_pay_tx_history.xml +++ b/app/src/main/res/layout/item_salt_pay_tx_history.xml @@ -53,13 +53,13 @@ android:ellipsize="end" android:letterSpacing="0.03" android:singleLine="true" + android:text="@string/transaction_history_tx_in_progress" android:textColor="@color/text_attention" android:textSize="12sp" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/tv_tx_amount_sign" - app:layout_constraintStart_toStartOf="@+id/tv_tx_hash" - tools:text="In progress..." /> + app:layout_constraintStart_toStartOf="@+id/tv_tx_hash" /> Go to settings to enable biometric authentication in the Tangem App Scan the card Check your internet connection or switch to a different network + In progress… + Failed to load transactions + You don\'t have any transactions yet + Transactions %s diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index c243f24360..8080d5b398 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -449,6 +449,10 @@ Go to settings to enable biometric authentication in the Tangem App Scan the card Check your internet connection or switch to a different network + In progress… + Failed to load transactions + You don\'t have any transactions yet + Transactions %s diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index d643c5f258..1fb5d0c132 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -449,6 +449,10 @@ Go to settings to enable biometric authentication in the Tangem App Scan the card Check your internet connection or switch to a different network + In progress… + Failed to load transactions + You don\'t have any transactions yet + Transactions %s diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 62a7e8b559..d94ea6208e 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -449,6 +449,10 @@ Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem App Отсканируйте карту Проверьте подключение с интернетом или переключитесь на другую сеть + В процессе… + Не удалось загрузить транзакции + У вас еще нет транзакций + Транзакции %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index db08b93d2b..483c6f6caf 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -450,6 +450,10 @@ Go to settings to enable biometric authentication in the Tangem App Scan the card Check your internet connection or switch to a different network + In progress… + Failed to load transactions + You don\'t have any transactions yet + Transactions %s From e2ddd446d291177b2d5b9866b805e7885ea3fdb4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 10:23:15 +0300 Subject: [PATCH 14/46] Updated on 2026-08-14 --- .../layout/item_salt_pay_tx_history_date.xml | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml index 8e45e44e0f..a21c2dbd6e 100644 --- a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml +++ b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml @@ -1,9 +1,20 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + + + From 73c1f6988d735b00dd328dd9bb02566f987e3e96 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 10:25:07 +0300 Subject: [PATCH 15/46] Updated on 2026-08-14 --- .../rv => common}/ShimmerRecyclerAdapter.kt | 2 +- .../wallet/saltPay/HistoryTransactionData.kt | 21 ---- .../ui/wallet/saltPay/SaltPayWalletView.kt | 39 ++++-- .../ui/wallet/saltPay/rv/HistoryItemData.kt | 74 ++++++++++++ .../saltPay/rv/TransactionHistoryAdapter.kt | 103 ---------------- .../ui/wallet/saltPay/rv/TxHistoryAdapter.kt | 112 ++++++++++++++++++ 6 files changed, 214 insertions(+), 137 deletions(-) rename app/src/main/java/com/tangem/tap/{features/wallet/ui/wallet/saltPay/rv => common}/ShimmerRecyclerAdapter.kt (95%) delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt b/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt similarity index 95% rename from app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt rename to app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt index fc59d49155..4d56172588 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt +++ b/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv +package com.tangem.tap.common import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt deleted file mode 100644 index 50b80f0b37..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay - -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.TransactionStatus -import com.tangem.tap.features.wallet.models.PendingTransactionType - -/** -[REDACTED_AUTHOR] - */ -data class HistoryTransactionData( - val transactionData: TransactionData, - private val walletAddress: String, -) { - fun isInProgress(): Boolean = transactionData.status == TransactionStatus.Unconfirmed - - fun getTransactionType(): PendingTransactionType = when { - transactionData.sourceAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Outgoing - transactionData.destinationAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Incoming - else -> PendingTransactionType.Unknown - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt index e1d9cbb642..62dccf8f6d 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt @@ -6,6 +6,8 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.extensions.Result import com.tangem.domain.common.extensions.debounce import com.tangem.domain.common.extensions.withMainContext +import com.tangem.tap.common.ShimmerData +import com.tangem.tap.common.ShimmerRecyclerAdapter import com.tangem.tap.common.extensions.animateVisibility import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.formatAmountAsSpannedString @@ -18,9 +20,9 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.ui.WalletFragment import com.tangem.tap.features.wallet.ui.wallet.WalletView -import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.ShimmerData -import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.ShimmerRecyclerAdapter -import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.TransactionHistoryAdapter +import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.HistoryItemData +import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.HistoryTransactionData +import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.TxHistoryAdapter import com.tangem.tap.mainScope import com.tangem.tap.scope import com.tangem.tap.store @@ -85,7 +87,7 @@ class SaltPayWalletView : WalletView() { txWidget.rvTxHistoryShimmer.adapter = adapter txWidget.rvTxHistoryShimmer.addItemDecoration(SpaceItemDecoration.vertical(10F)) - txWidget.rvTxHistory.adapter = TransactionHistoryAdapter() + txWidget.rvTxHistory.adapter = TxHistoryAdapter() txWidget.rvTxHistory.addItemDecoration(SpaceItemDecoration.vertical(10F)) handleTxInit() @@ -147,18 +149,31 @@ class SaltPayWalletView : WalletView() { // val walletAddress = "0xDA94Aae02a4Db0e09E1Cf240E3a0973ba89052cf" when (val result = walletManager.getTransactionHistory(walletAddress, wallet.blockchain, setOf(token))) { is Result.Success -> { - val tokensHistory = result.data + val dateAssociatedHistory = mutableMapOf>() + + result.data .filter { it.contractAddress == token.contractAddress } - .sortedBy { it.date?.timeInMillis ?: 0 } - .map { HistoryTransactionData(it, walletAddress) } + .sortedByDescending { it.date?.timeInMillis ?: 0 } + .forEach { + val txData = HistoryTransactionData(it, walletAddress) + val list = dateAssociatedHistory[txData.date] ?: mutableListOf() + list.add(txData) + dateAssociatedHistory[txData.date] = list + } + + val dataList = mutableListOf() + dateAssociatedHistory.forEach { entry -> + dataList.add(HistoryItemData.Date(entry.key)) + entry.value.forEach { dataList.add(HistoryItemData.TransactionData(it)) } + } // .toMutableList().apply { clear() } delay(300) withMainContext { - if (tokensHistory.isEmpty()) { + if (dataList.isEmpty()) { handleTxEmpty() } else { - handleTxSuccess(tokensHistory) + handleTxSuccess(dataList) } } } @@ -185,7 +200,7 @@ class SaltPayWalletView : WalletView() { txWidget.groupShimmer.show() } - private fun handleTxSuccess(dataList: List) = with(txWidget) { + private fun handleTxSuccess(dataList: List) = with(txWidget) { groupShimmer.hide() groupEmpty.hide() groupError.hide() @@ -212,7 +227,7 @@ class SaltPayWalletView : WalletView() { groupError.show() } - private fun updateTxHistoryWidget(dataList: List) = with(txWidget) { - (rvTxHistory.adapter as TransactionHistoryAdapter).submitList(dataList) + private fun updateTxHistoryWidget(dataList: List) = with(txWidget) { + (rvTxHistory.adapter as TxHistoryAdapter).submitList(dataList) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt new file mode 100644 index 0000000000..0aa75abfba --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt @@ -0,0 +1,74 @@ +package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv + +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionStatus +import com.tangem.common.extensions.guard +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.features.wallet.models.PendingTransactionType +import java.text.SimpleDateFormat +import java.util.* + +/** +[REDACTED_AUTHOR] + */ +sealed class HistoryItemData( + val viewType: Int, + val itemId: Long, +) { + data class Date(val date: String) : HistoryItemData(0, date.hashCode().toLong()) + data class TransactionData(val data: HistoryTransactionData) : HistoryItemData(1, data.hash.hashCode().toLong()) +} + +data class HistoryTransactionData( + private val transactionData: TransactionData, + private val walletAddress: String, +) { + + val isInProgress: Boolean = transactionData.status == TransactionStatus.Unconfirmed + + val transactionType: PendingTransactionType = when { + transactionData.sourceAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Outgoing + transactionData.destinationAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Incoming + else -> PendingTransactionType.Unknown + } + + val hash: String = transactionData.hash?.let { + "${it.substring(0..5)}...${it.substring(it.length - 4)}" + } ?: "" + + val time: String = transactionData.date?.let { + SimpleDateFormat("hh:MM").format(it.time) + } ?: "00:00" + + val date: String = calculateDate() + + val txSign: String = when (transactionType) { + PendingTransactionType.Incoming -> "+" + PendingTransactionType.Outgoing -> "-" + PendingTransactionType.Unknown -> "" + } + + val amountValue: String = transactionData.amount.value + ?.toFormattedCurrencyString(8, transactionData.amount.currencySymbol) ?: "0" + + private fun calculateDate(): String { + val calendarTx = transactionData.date.guard { return "Unknown" } + + val calendarNow = Calendar.getInstance() + val oldDateFormatter = SimpleDateFormat("dd.MM.yyyy") + + val date = if (calendarTx.get(Calendar.YEAR) == calendarNow.get(Calendar.YEAR)) { + val dayOfYearTx = calendarTx.get(Calendar.DAY_OF_YEAR) + val dayOfYearNow = calendarNow.get(Calendar.DAY_OF_YEAR) + when { + dayOfYearNow == dayOfYearTx -> "Today" + dayOfYearNow - dayOfYearTx == 1 -> "Tomorrow" + else -> oldDateFormatter.format(calendarTx.time) + } + } else { + oldDateFormatter.format(calendarTx.time) + } + + return date + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt deleted file mode 100644 index bc5f923aff..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv - -import android.graphics.PorterDuff -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.common.extensions.setDrawable -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.extensions.toFormattedCurrencyString -import com.tangem.tap.features.wallet.models.PendingTransactionType -import com.tangem.tap.features.wallet.ui.wallet.saltPay.HistoryTransactionData -import com.tangem.wallet.R -import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding - -/** -[REDACTED_AUTHOR] - */ -internal class TransactionHistoryAdapter : ListAdapter(DiffUtilCallback) { - - override fun getItemId(position: Int): Long { - return if (currentList.isEmpty()) 0 - else currentList[position].transactionData.hash.hashCode().toLong() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionItemVH { - val inflater = LayoutInflater.from(parent.context) - val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false) - return TransactionItemVH(layout) - } - - override fun onBindViewHolder(holder: TransactionItemVH, position: Int) { - holder.bind(currentList[position]) - } - - object DiffUtilCallback : DiffUtil.ItemCallback() { - override fun areContentsTheSame( - oldItem: HistoryTransactionData, - newItem: HistoryTransactionData, - ) = oldItem == newItem - - override fun areItemsTheSame( - oldItem: HistoryTransactionData, - newItem: HistoryTransactionData, - ) = oldItem == newItem - } -} - -internal class TransactionItemVH( - private val binding: ItemSaltPayTxHistoryBinding, -) : RecyclerView.ViewHolder(binding.root) { - - fun bind(data: HistoryTransactionData) = with(binding) { - setupImage(data) - setupTitle(data) - setupSubtitle(data) - setupBalance(data) - } - - private fun setupImage(data: HistoryTransactionData) = with(binding) { - val drawable = when (data.getTransactionType()) { - PendingTransactionType.Incoming -> R.drawable.ic_tx_incoming - PendingTransactionType.Outgoing -> R.drawable.ic_tx_outgoing - PendingTransactionType.Unknown -> null - } - drawable?.let { imvTx.setDrawable(it) } - - if (data.isInProgress()) { - imvTx.setColorFilter(imvTx.getColor(R.color.icon_attention), PorterDuff.Mode.MULTIPLY) - } else { - imvTx.clearColorFilter() - } - } - - private fun setupTitle(data: HistoryTransactionData) = with(binding) { - val hash = data.transactionData.hash ?: return@with - - val formattedHash = "${hash.substring(0..5)}...${hash.substring(hash.length - 4)}" - tvTxHash.text = formattedHash - } - - private fun setupSubtitle(data: HistoryTransactionData) = with(binding) { - val inProgress = data.isInProgress() - tvTxStatus.show(inProgress) - tvTxTime.show(!inProgress) - - val time = data.transactionData.date?.timeInMillis.toString() ?: "none" - tvTxTime.text = time - } - - private fun setupBalance(data: HistoryTransactionData) = with(binding) { - val sign = when (data.getTransactionType()) { - PendingTransactionType.Incoming -> "+" - PendingTransactionType.Outgoing -> "-" - PendingTransactionType.Unknown -> "" - } - val amount = data.transactionData.amount - tvTxAmountSign.text = sign - tvTxAmountValue.text = amount.value?.toFormattedCurrencyString(8, amount.currencySymbol) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt new file mode 100644 index 0000000000..60e9d1dfd7 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt @@ -0,0 +1,112 @@ +package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv + +import android.graphics.PorterDuff +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.tangem.tap.common.extensions.getColor +import com.tangem.tap.common.extensions.setDrawable +import com.tangem.tap.common.extensions.show +import com.tangem.tap.features.wallet.models.PendingTransactionType +import com.tangem.wallet.R +import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding +import com.tangem.wallet.databinding.ItemSaltPayTxHistoryDateBinding + +/** +[REDACTED_AUTHOR] + */ +internal class TxHistoryAdapter : ListAdapter(DiffUtilCallback) { + + override fun getItemId(position: Int): Long { + return if (currentList.isEmpty()) 0L + else currentList[position].itemId + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseTxHistoryVH { + val inflater = LayoutInflater.from(parent.context) + + return when (viewType) { + 0 -> { + val layout = ItemSaltPayTxHistoryDateBinding.inflate(inflater, parent, false) + DateItemVH(layout) + } + 1 -> { + val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false) + TransactionItemVH(layout) + } + else -> throw UnsupportedOperationException() + } + } + + override fun getItemViewType(position: Int): Int = currentList[position].viewType + + override fun onBindViewHolder(holder: BaseTxHistoryVH, position: Int) { + holder.bind(currentList[position]) + } + + private object DiffUtilCallback : DiffUtil.ItemCallback() { + override fun areContentsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem + override fun areItemsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem + } +} + +internal abstract class BaseTxHistoryVH(layout: View) : RecyclerView.ViewHolder(layout) { + abstract fun bind(data: HistoryItemData) +} + +private class DateItemVH( + private val binding: ItemSaltPayTxHistoryDateBinding, +) : BaseTxHistoryVH(binding.root) { + + override fun bind(data: HistoryItemData) = with(binding) { + val vhData = (data as? HistoryItemData.Date)?.date ?: return + + tvTitleDate.text = vhData + } +} + +private class TransactionItemVH( + private val binding: ItemSaltPayTxHistoryBinding, +) : BaseTxHistoryVH(binding.root) { + + override fun bind(data: HistoryItemData) { + val vhData = (data as? HistoryItemData.TransactionData)?.data ?: return + setupImage(vhData) + setupTitle(vhData) + setupSubtitle(vhData) + setupBalance(vhData) + } + + private fun setupImage(data: HistoryTransactionData) = with(binding) { + val drawable = when (data.transactionType) { + PendingTransactionType.Incoming -> R.drawable.ic_tx_incoming + PendingTransactionType.Outgoing -> R.drawable.ic_tx_outgoing + PendingTransactionType.Unknown -> null + } + drawable?.let { imvTx.setDrawable(it) } + + if (data.isInProgress) { + imvTx.setColorFilter(imvTx.getColor(R.color.icon_attention), PorterDuff.Mode.SRC_ATOP) + } else { + imvTx.clearColorFilter() + } + } + + private fun setupTitle(data: HistoryTransactionData) = with(binding) { + tvTxHash.text = data.hash + } + + private fun setupSubtitle(data: HistoryTransactionData) = with(binding) { + tvTxStatus.show(data.isInProgress) + tvTxTime.show(!data.isInProgress) + tvTxTime.text = data.time + } + + private fun setupBalance(data: HistoryTransactionData) = with(binding) { + tvTxAmountSign.text = data.txSign + tvTxAmountValue.text = data.amountValue + } +} \ No newline at end of file From 61bd233942e7dfc8cd5057a48e27c0cc8142157e Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Feb 2023 17:14:00 +0300 Subject: [PATCH 16/46] Updated on 2026-08-14 --- .../wallet/saltPay/HistoryTransactionData.kt | 21 ++++ .../saltPay/rv/ShimmerRecyclerAdapter.kt | 33 ++++++ .../saltPay/rv/TransactionHistoryAdapter.kt | 103 ++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt new file mode 100644 index 0000000000..50b80f0b37 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/HistoryTransactionData.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.features.wallet.ui.wallet.saltPay + +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionStatus +import com.tangem.tap.features.wallet.models.PendingTransactionType + +/** +[REDACTED_AUTHOR] + */ +data class HistoryTransactionData( + val transactionData: TransactionData, + private val walletAddress: String, +) { + fun isInProgress(): Boolean = transactionData.status == TransactionStatus.Unconfirmed + + fun getTransactionType(): PendingTransactionType = when { + transactionData.sourceAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Outgoing + transactionData.destinationAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Incoming + else -> PendingTransactionType.Unknown + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt new file mode 100644 index 0000000000..fc59d49155 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView + +/** +[REDACTED_AUTHOR] + */ +open class ShimmerRecyclerAdapter( + private val viewHolderViewFactory: (ViewGroup) -> ViewGroup, +) : ListAdapter(DiffUtilCallback) { + + override fun getItemId(position: Int): Long { + return if (currentList.isEmpty()) 0 else currentList[position].hashCode().toLong() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShimmerVH { + return ShimmerVH(viewHolderViewFactory.invoke(parent)) + } + + override fun onBindViewHolder(holder: ShimmerVH, position: Int) {} + + object DiffUtilCallback : DiffUtil.ItemCallback() { + override fun areContentsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem + override fun areItemsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem + } +} + +class ShimmerVH(viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup) + +data class ShimmerData(private val any: String = "") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt new file mode 100644 index 0000000000..bc5f923aff --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt @@ -0,0 +1,103 @@ +package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv + +import android.graphics.PorterDuff +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.tangem.tap.common.extensions.getColor +import com.tangem.tap.common.extensions.setDrawable +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.extensions.toFormattedCurrencyString +import com.tangem.tap.features.wallet.models.PendingTransactionType +import com.tangem.tap.features.wallet.ui.wallet.saltPay.HistoryTransactionData +import com.tangem.wallet.R +import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding + +/** +[REDACTED_AUTHOR] + */ +internal class TransactionHistoryAdapter : ListAdapter(DiffUtilCallback) { + + override fun getItemId(position: Int): Long { + return if (currentList.isEmpty()) 0 + else currentList[position].transactionData.hash.hashCode().toLong() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionItemVH { + val inflater = LayoutInflater.from(parent.context) + val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false) + return TransactionItemVH(layout) + } + + override fun onBindViewHolder(holder: TransactionItemVH, position: Int) { + holder.bind(currentList[position]) + } + + object DiffUtilCallback : DiffUtil.ItemCallback() { + override fun areContentsTheSame( + oldItem: HistoryTransactionData, + newItem: HistoryTransactionData, + ) = oldItem == newItem + + override fun areItemsTheSame( + oldItem: HistoryTransactionData, + newItem: HistoryTransactionData, + ) = oldItem == newItem + } +} + +internal class TransactionItemVH( + private val binding: ItemSaltPayTxHistoryBinding, +) : RecyclerView.ViewHolder(binding.root) { + + fun bind(data: HistoryTransactionData) = with(binding) { + setupImage(data) + setupTitle(data) + setupSubtitle(data) + setupBalance(data) + } + + private fun setupImage(data: HistoryTransactionData) = with(binding) { + val drawable = when (data.getTransactionType()) { + PendingTransactionType.Incoming -> R.drawable.ic_tx_incoming + PendingTransactionType.Outgoing -> R.drawable.ic_tx_outgoing + PendingTransactionType.Unknown -> null + } + drawable?.let { imvTx.setDrawable(it) } + + if (data.isInProgress()) { + imvTx.setColorFilter(imvTx.getColor(R.color.icon_attention), PorterDuff.Mode.MULTIPLY) + } else { + imvTx.clearColorFilter() + } + } + + private fun setupTitle(data: HistoryTransactionData) = with(binding) { + val hash = data.transactionData.hash ?: return@with + + val formattedHash = "${hash.substring(0..5)}...${hash.substring(hash.length - 4)}" + tvTxHash.text = formattedHash + } + + private fun setupSubtitle(data: HistoryTransactionData) = with(binding) { + val inProgress = data.isInProgress() + tvTxStatus.show(inProgress) + tvTxTime.show(!inProgress) + + val time = data.transactionData.date?.timeInMillis.toString() ?: "none" + tvTxTime.text = time + } + + private fun setupBalance(data: HistoryTransactionData) = with(binding) { + val sign = when (data.getTransactionType()) { + PendingTransactionType.Incoming -> "+" + PendingTransactionType.Outgoing -> "-" + PendingTransactionType.Unknown -> "" + } + val amount = data.transactionData.amount + tvTxAmountSign.text = sign + tvTxAmountValue.text = amount.value?.toFormattedCurrencyString(8, amount.currencySymbol) + } +} \ No newline at end of file From 69fe86f07c8b6b2aa09cd092961e64b0885dc3ce Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 13:40:03 +0300 Subject: [PATCH 17/46] Updated on 2026-08-14 --- .../common/analytics/events/AnalyticsParam.kt | 5 +- .../tap/common/analytics/events/Basic.kt | 6 +- .../tap/common/analytics/events/Onboarding.kt | 2 +- .../BatchIdParamsInterceptor.kt | 4 +- .../CardContextInterceptor.kt | 56 +++++++++++++++++++ .../com/tangem/core/analytics/Analytics.kt | 4 +- .../analytics/api/ParamsInterceptorApi.kt | 2 +- .../com/tangem/domain/common/LogConfig.kt | 2 +- 8 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt 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 bad36d3f0d..75ab0b8929 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 @@ -61,7 +61,10 @@ sealed class AnalyticsParam { } companion object Key { - const val BatchId = "Batch" + const val Batch = "Batch" + const val ProductType = "Product Type" + const val Firmware = "Firmware" + const val Currency = "Currency" const val ErrorDescription = "Error Description" const val ErrorCode = "Error Code" const val ErrorKey = "Error Key" 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 ecdedd9761..bbae0634fa 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 @@ -19,14 +19,14 @@ sealed class Basic( event = "Signed in", params = mapOf( "State" to state.value, - "Currency" to currency.value, - AnalyticsParam.BatchId to batch, + AnalyticsParam.Currency to currency.value, + AnalyticsParam.Batch to batch, ), ) class ToppedUp(currency: AnalyticsParam.CardCurrency) : Basic( event = "Topped up", - params = mapOf("Currency" to currency.value), + params = mapOf(AnalyticsParam.Currency to currency.value), ) class ScanError(error: Throwable) : Basic( 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 8eb0f6490d..d5283849ef 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 @@ -51,7 +51,7 @@ sealed class Onboarding( class ButtonBuyCrypto(currency: AnalyticsParam.CurrencyType) : Topup( event = "Button - Buy Crypto", - params = mapOf("Currency" to currency.value), + params = mapOf(AnalyticsParam.Currency to currency.value), ) class ButtonShowWalletAddress : Topup("Button - Show the Wallet Address") 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 index b7e7ec6fbf..33701c53d4 100644 --- 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 @@ -1,7 +1,7 @@ package com.tangem.tap.common.analytics.paramsInterceptor -import com.tangem.core.analytics.api.ParamsInterceptor import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.api.ParamsInterceptor import com.tangem.tap.common.analytics.events.AnalyticsParam /** @@ -16,6 +16,6 @@ class BatchIdParamsInterceptor( override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true override fun intercept(params: MutableMap) { - params[AnalyticsParam.BatchId] = batchId + params[AnalyticsParam.Batch] = batchId } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt new file mode 100644 index 0000000000..fe3ccc5898 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -0,0 +1,56 @@ +package com.tangem.tap.common.analytics.paramsInterceptor + +import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.api.ParamsInterceptor +import com.tangem.domain.common.ProductType +import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.features.demo.DemoHelper + +/** +[REDACTED_AUTHOR] + */ +class CardContextInterceptor( + private val scanResponse: ScanResponse?, +) : ParamsInterceptor { + + override fun id(): String = CardContextInterceptor.id() + + override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true + + override fun intercept(params: MutableMap) { + scanResponse ?: return + + val card = scanResponse.card + params[AnalyticsParam.Batch] = card.batchId + params[AnalyticsParam.ProductType] = getProductType(scanResponse) + params[AnalyticsParam.Firmware] = card.firmwareVersion.stringValue + + ParamCardCurrencyConverter().convert(scanResponse)?.let { + params[AnalyticsParam.Currency] = it.value + } + } + + private fun getProductType(scanResponse: ScanResponse): String { + return when (scanResponse.productType) { + ProductType.Note -> "Note" + ProductType.Twins -> "Twin" + ProductType.Wallet -> "Wallet" + ProductType.SaltPay -> when (scanResponse.isSaltPayVisa()) { + true -> "Visa" + else -> "Visa Backup" + } + ProductType.Start2Coin -> "Start2Coin" + else -> when (DemoHelper.isDemoCard(scanResponse)) { + // TODO: separate demo cards + true -> "Demo Wallet | Demo Note" + else -> "Other" + } + } + } + + companion object { + fun id(): String = CardContextInterceptor::class.java.simpleName + } +} \ No newline at end of file diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt index 20fcb92c92..0e0f40b6c9 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt @@ -54,8 +54,8 @@ object Analytics : GlobalAnalyticsEventHandler { paramsInterceptors[interceptor.id()] = interceptor } - override fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor? { - return paramsInterceptors.remove(interceptor.id()) + override fun removeParamsInterceptor(interceptorId: String): ParamsInterceptor? { + return paramsInterceptors.remove(interceptorId) } override fun send(event: AnalyticsEvent) { diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt index e2f9dd5860..683a1c45a0 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt @@ -13,5 +13,5 @@ interface ParamsInterceptor { interface ParamsInterceptorHolder { fun addParamsInterceptor(interceptor: ParamsInterceptor) - fun removeParamsInterceptor(interceptor: ParamsInterceptor): ParamsInterceptor? + fun removeParamsInterceptor(interceptorId: String): ParamsInterceptor? } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt index a0046af552..1d1bf6d11a 100644 --- a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt +++ b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt @@ -22,5 +22,5 @@ object NetworkLogConfig { object AnalyticsHandlersLogConfig { val firebase: Boolean = false val appsFlyer: Boolean = false - val amplitude: Boolean = false + val amplitude: Boolean = true } \ No newline at end of file From e77bfba92a78c1c43cf78bd8ba1f305646f21997 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 14:08:05 +0300 Subject: [PATCH 18/46] Updated on 2026-08-14 --- .../BatchIdParamsInterceptor.kt | 21 ----------------- .../CardContextInterceptor.kt | 23 +++++++++++++------ .../tangem/tap/common/extensions/Analytics.kt | 16 +++++++++++++ .../tap/domain/scanCard/ScanCardProcessor.kt | 8 +++---- .../tangem/tap/features/demo/DemoHelper.kt | 21 ++++++++++------- .../tangem/tap/features/home/HomeFragment.kt | 2 +- .../tap/features/home/redux/HomeAction.kt | 1 + .../tap/features/home/redux/HomeMiddleware.kt | 6 +++++ .../redux/WalletSelectorMiddleware.kt | 5 ++-- .../features/welcome/redux/WelcomeAction.kt | 1 + .../welcome/redux/WelcomeMiddleware.kt | 8 +++++++ .../features/welcome/redux/WelcomeReducer.kt | 1 + .../features/welcome/ui/WelcomeFragment.kt | 6 ++--- 13 files changed, 72 insertions(+), 47 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt create mode 100644 app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt 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 deleted file mode 100644 index 33701c53d4..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.tap.common.analytics.paramsInterceptor - -import com.tangem.core.analytics.AnalyticsEvent -import com.tangem.core.analytics.api.ParamsInterceptor -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.Batch] = batchId - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index fe3ccc5898..99c231e1e4 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -37,15 +37,24 @@ class CardContextInterceptor( ProductType.Note -> "Note" ProductType.Twins -> "Twin" ProductType.Wallet -> "Wallet" - ProductType.SaltPay -> when (scanResponse.isSaltPayVisa()) { - true -> "Visa" - else -> "Visa Backup" + ProductType.SaltPay -> if (scanResponse.isSaltPayVisa()) { + "Visa" + } else { + "Visa Backup" } ProductType.Start2Coin -> "Start2Coin" - else -> when (DemoHelper.isDemoCard(scanResponse)) { - // TODO: separate demo cards - true -> "Demo Wallet | Demo Note" - else -> "Other" + else -> if (DemoHelper.isDemoCard(scanResponse)) { + if (DemoHelper.isTestDemoCard(scanResponse)) { + "Demo Test" + } else { + when (scanResponse.card.cardId.substring(0..1)) { + "AC" -> "Demo Wallet" + "AB" -> "Demo Note" + else -> "Demo Other" + } + } + } else { + "Other" } } } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt new file mode 100644 index 0000000000..3772f86a71 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.common.extensions + +import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.ScanResponse +import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor + +/** +[REDACTED_AUTHOR] + */ +fun Analytics.addCardContext(scanResponse: ScanResponse) { + addParamsInterceptor(CardContextInterceptor(scanResponse)) +} + +fun Analytics.eraseCardContext() { + removeParamsInterceptor(CardContextInterceptor.id()) +} \ 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 acf77e3bdb..21c1cd221a 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 @@ -13,7 +13,7 @@ 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.paramsInterceptor.BatchIdParamsInterceptor +import com.tangem.tap.common.extensions.addCardContext import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa import com.tangem.tap.common.redux.global.GlobalAction @@ -75,7 +75,7 @@ object ScanCardProcessor { tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) onScanStateChange(false) - sendAnalytics(analyticsEvent, scanResponse.card.batchId) + sendAnalytics(analyticsEvent, scanResponse) checkForUnfinishedBackupForSaltPay( backupService = backupService, @@ -103,9 +103,9 @@ object ScanCardProcessor { private fun sendAnalytics( analyticsEvent: AnalyticsEvent?, - batchId: String, + scanResponse: ScanResponse, ) { - Analytics.addParamsInterceptor(BatchIdParamsInterceptor(batchId)) + Analytics.addCardContext(scanResponse) analyticsEvent?.let { Analytics.send(it) } } diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index b0d473cde0..8cf2bf6d49 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -49,6 +49,8 @@ object DemoHelper { fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId) + fun isTestDemoCard(scanResponse: ScanResponse): Boolean = config.isTestDemoCardId(scanResponse.card.cardId) + fun isDemoCardId(cardId: String): Boolean = config.isDemoCardId(cardId) fun tryHandle(appState: () -> AppState?, action: Action): Boolean { @@ -108,6 +110,8 @@ class DemoConfig { fun isDemoCardId(cardId: String): Boolean = demoCardIds.contains(cardId) + fun isTestDemoCardId(cardId: String): Boolean = testDemoCardIds.contains(cardId) + fun getBalance(blockchain: Blockchain): Amount = walletBalances[blockchain]?.copy() ?: Amount(BigDecimal.ZERO, blockchain).copy() @@ -489,17 +493,19 @@ class DemoConfig { class DemoTransactionSender( private val walletManager: WalletManager, - private val sender: TransactionSender = walletManager as TransactionSender + private val sender: TransactionSender = walletManager as TransactionSender, ) : TransactionSender { override suspend fun getFee(amount: Amount, destination: String): Result> { val blockchain = walletManager.wallet.blockchain return when (walletManager) { - is BitcoinWalletManager -> Result.Success(listOf( - Amount(0.0001.toBigDecimal(), blockchain), - Amount(0.0003.toBigDecimal(), blockchain), - Amount(0.00055.toBigDecimal(), blockchain), - )) + is BitcoinWalletManager -> Result.Success( + listOf( + Amount(0.0001.toBigDecimal(), blockchain), + Amount(0.0003.toBigDecimal(), blockchain), + Amount(0.00055.toBigDecimal(), blockchain), + ), + ) else -> sender.getFee(amount, destination) } } @@ -508,7 +514,7 @@ class DemoTransactionSender( val dataToSign = randomString(32).toByteArray() val signerResponse = signer.sign( hash = dataToSign, - publicKey = walletManager.wallet.publicKey + publicKey = walletManager.wallet.publicKey, ) return when (signerResponse) { is CompletionResult.Success -> SimpleResult.Failure(Exception(ID).toBlockchainSdkError()) @@ -529,5 +535,4 @@ class DemoTransactionSender( companion object { val ID = DemoTransactionSender::class.java.simpleName } - } \ No newline at end of file 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 60dc234e4f..3395d8b1f0 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 @@ -29,7 +29,7 @@ class HomeFragment : Fragment(), StoreSubscriber { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Analytics.send(IntroductionProcess.ScreenOpened()) + store.dispatch(HomeAction.OnCreate) store.dispatch(HomeAction.Init) } 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 59e169ee04..e79a74e423 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 @@ -7,6 +7,7 @@ import org.rekotlin.Action sealed class HomeAction : Action { + object OnCreate : HomeAction() object Init : HomeAction() data class ReadCard( 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 76048ed0f4..afcb5c7a14 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 @@ -5,10 +5,12 @@ import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl +import com.tangem.tap.common.extensions.eraseCardContext import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState @@ -51,6 +53,10 @@ private val homeMiddleware: Middleware = { _, _ -> private fun handleHomeAction(action: Action) { when (action) { + is HomeAction.OnCreate -> { + Analytics.eraseCardContext() + Analytics.send(IntroductionProcess.ScreenOpened()) + } is HomeAction.Init -> { store.dispatch(GlobalAction.RestoreAppCurrency) store.dispatch(GlobalAction.ExchangeManager.Init) 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 aa4f559888..cba8631bb6 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 @@ -10,7 +10,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.analytics.events.MyWallets -import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor +import com.tangem.tap.common.extensions.addCardContext import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState @@ -197,8 +197,7 @@ internal class WalletSelectorMiddleware { .doOnSuccess { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { - val batchId = selectedUserWallet.scanResponse.card.batchId - Analytics.addParamsInterceptor(BatchIdParamsInterceptor(batchId)) + Analytics.addCardContext(selectedUserWallet.scanResponse) store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.onUserWalletSelected(selectedUserWallet) 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 681a3cea77..f33cfe9453 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 @@ -5,6 +5,7 @@ import com.tangem.common.core.TangemError import org.rekotlin.Action internal sealed interface WelcomeAction : Action { + object OnCreate: WelcomeAction object ProceedWithBiometrics : WelcomeAction { object Success : WelcomeAction data class Error(val error: TangemError) : WelcomeAction 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 4c39b5cb3c..34ab7b92ab 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 @@ -4,9 +4,12 @@ import android.content.Intent import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.events.SignIn +import com.tangem.tap.common.extensions.addCardContext import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.eraseCardContext import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen @@ -38,6 +41,10 @@ internal class WelcomeMiddleware { private fun handleAction(action: WelcomeAction, state: WelcomeState) { when (action) { + is WelcomeAction.OnCreate -> { + Analytics.eraseCardContext() + Analytics.send(SignIn.ScreenOpened()) + } is WelcomeAction.ProceedWithBiometrics -> { proceedWithBiometry(state) } @@ -62,6 +69,7 @@ internal class WelcomeMiddleware { } .doOnSuccess { selectedUserWallet -> if (selectedUserWallet != null) { + Analytics.addCardContext(selectedUserWallet.scanResponse) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) store.onUserWalletSelected(selectedUserWallet) 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 42a6d4d0e1..e4abfb4208 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 @@ -26,6 +26,7 @@ internal object WelcomeReducer { is WelcomeAction.ProceedWithBiometrics.Success -> state.copy(isUnlockWithBiometricsInProgress = false) is WelcomeAction.ProceedWithCard.Success -> state.copy(isUnlockWithCardInProgress = false) is WelcomeAction.CloseError -> state.copy(error = null) + else -> state } } } \ 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 70477f5ad1..9ca40680ac 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 @@ -19,13 +19,13 @@ 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.analytics.Analytics 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.common.analytics.events.SignIn import com.tangem.tap.features.details.ui.cardsettings.resolveReference +import com.tangem.tap.features.welcome.redux.WelcomeAction import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent +import com.tangem.tap.store import com.tangem.wallet.R internal class WelcomeFragment : ComposeFragment() { @@ -33,7 +33,7 @@ internal class WelcomeFragment : ComposeFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Analytics.send(SignIn.ScreenOpened()) + store.dispatch(WelcomeAction.OnCreate) } @Composable From 6b1c37a91a6e9551d005f5e21e32ccf85172b701 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 14:17:15 +0300 Subject: [PATCH 19/46] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index 8cf2bf6d49..d984e73f13 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -121,9 +121,6 @@ class DemoConfig { private val releaseDemoCardIds = mutableListOf( // === Not from the Google Sheet table === - "FB10000000000196", // Note BTC - "FB20000000000186", // Note ETH - "FB30000000000176", // Wallet "AC01000000041225", "AC01000000041472", "AB01000000046498", From 62e254b0bbe9e9a7bb5fd50d2961bbf5cbef2d9b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 14:29:52 +0300 Subject: [PATCH 20/46] Updated on 2026-08-14 --- .../java/com/tangem/tap/common/analytics/events/Onboarding.kt | 2 ++ .../wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt | 2 ++ 2 files changed, 4 insertions(+) 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 d5283849ef..0b203bab27 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 @@ -67,8 +67,10 @@ sealed class Onboarding( class SetupFinished : Twins("Twin Setup Finished") } + class PinScreenOpened : Onboarding("Onboarding", "PIN screen opened") class PinCodeSet : Onboarding("Onboarding", "PIN code set") class ButtonConnect : Onboarding("Onboarding", "Button - Connect") + class KYCScreenOpened : Onboarding("Onboarding", "KYC screen opened") class KYCStarted : Onboarding("Onboarding", "KYC started") class KYCInProgress : Onboarding("Onboarding", "KYC in progress") class KYCRejected : Onboarding("Onboarding", "KYC rejected") 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 5f39361096..ad903e6dd1 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 @@ -258,6 +258,8 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } is OnboardingSaltPayAction.SetStep -> { when (action.newStep) { + SaltPayActivationStep.NeedPin -> Analytics.send(Onboarding.PinScreenOpened()) + SaltPayActivationStep.KycIntro -> Analytics.send(Onboarding.KYCScreenOpened()) SaltPayActivationStep.KycStart -> Analytics.send(Onboarding.KYCStarted()) SaltPayActivationStep.KycWaiting -> Analytics.send(Onboarding.KYCInProgress()) SaltPayActivationStep.KycReject -> Analytics.send(Onboarding.KYCRejected()) From 7b20b2622ae8f29d734429e9da78d13c033ce6b8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 14:46:26 +0300 Subject: [PATCH 21/46] Updated on 2026-08-14 --- .../tangem/tap/common/analytics/events/AnalyticsParam.kt | 7 +++++++ .../java/com/tangem/tap/common/analytics/events/Basic.kt | 9 +++++++++ .../tap/common/analytics/events/IntroductionProcess.kt | 1 - .../com/tangem/tap/common/analytics/events/MainScreen.kt | 1 - .../com/tangem/tap/common/analytics/events/MyWallets.kt | 1 - .../com/tangem/tap/common/analytics/events/SignIn.kt | 1 - .../com/tangem/tap/features/home/redux/HomeAction.kt | 5 +++-- .../wallet/redux/middlewares/WalletMiddleware.kt | 3 ++- .../walletSelector/redux/WalletSelectorMiddleware.kt | 4 +++- .../tap/features/welcome/redux/WelcomeMiddleware.kt | 4 +++- 10 files changed, 27 insertions(+), 9 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 75ab0b8929..e46716ae0c 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 @@ -60,6 +60,13 @@ sealed class AnalyticsParam { object BlockchainSdk : Error("Blockchain Sdk Error") } + sealed class ScannedFrom(val value: String) { + object Introduction : ScannedFrom("Introduction") + object Main : ScannedFrom("Main") + object SignIn : ScannedFrom("Sign In") + object MyWallets : ScannedFrom("My Wallets") + } + companion object Key { const val Batch = "Batch" const val ProductType = "Product Type" 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 bbae0634fa..7a7e9f245e 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 @@ -11,6 +11,15 @@ sealed class Basic( error: Throwable? = null, ) : AnalyticsEvent("Basic", event, params, error) { + class CardWasScanned( + source: AnalyticsParam.ScannedFrom, + ) : Basic( + event = "Card Was Scanned", + params = mapOf( + "Source" to source.value, + ), + ) + class SignedIn( state: AnalyticsParam.CardBalanceState, currency: AnalyticsParam.CardCurrency, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt index 0d799d379f..602365c0c6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt @@ -14,6 +14,5 @@ sealed class IntroductionProcess( class ButtonTokensList : IntroductionProcess("Button - Tokens List") class ButtonBuyCards : IntroductionProcess("Button - Buy Cards") class ButtonScanCard : IntroductionProcess("Button - Scan Card") - class CardWasScanned : IntroductionProcess("Card Was Scanned") class ButtonRequestSupport : IntroductionProcess("Button - Request Support") } \ No newline at end of file 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 6f718755f0..9ce35483a4 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 @@ -13,7 +13,6 @@ sealed class MainScreen( class ScreenOpened : MainScreen("Screen opened") class ButtonScanCard : MainScreen("Button - Scan Card") - class CardWasScanned : MainScreen("Card Was Scanned") class ButtonMyWallets : MainScreen("Button - My Wallets") class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen( 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 index 4db0526ad2..65ab7937c2 100644 --- 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 @@ -8,7 +8,6 @@ sealed class MyWallets( ) : AnalyticsEvent("My Wallets", event, params) { class MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened") - class CardWasScanned : MyWallets(event = "Card Was Scanned") sealed class Button { class ScanNewCard : MyWallets(event = "Button - Scan New Card") diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt index cad34430d9..a5417d775b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt @@ -12,7 +12,6 @@ sealed class SignIn( ) : AnalyticsEvent("Sign In", event, params, error) { class ScreenOpened : SignIn(event = "Sing In Screen Opened") - class CardWasScanned : SignIn(event = "Card Was Scanned") class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In") class ButtonCardSignIn : SignIn(event = "Button - Card Sign In") 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 e79a74e423..bb9be2edc3 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 @@ -1,7 +1,8 @@ package com.tangem.tap.features.home.redux import com.tangem.core.analytics.AnalyticsEvent -import com.tangem.tap.common.analytics.events.IntroductionProcess +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.entities.IndeterminateProgressButton import org.rekotlin.Action @@ -11,7 +12,7 @@ sealed class HomeAction : Action { object Init : HomeAction() data class ReadCard( - val analyticsEvent: AnalyticsEvent? = IntroductionProcess.CardWasScanned(), + val analyticsEvent: AnalyticsEvent? = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Introduction), ) : HomeAction() data class ScanInProgress(val scanInProgress: Boolean) : HomeAction() 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 f75992fe2b..22aba65cfc 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 @@ -15,6 +15,7 @@ import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.common.analytics.converters.BasicEventsPreChecker import com.tangem.tap.common.analytics.converters.BasicEventsSourceData import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.copyToClipboard @@ -378,7 +379,7 @@ class WalletMiddleware { } else -> { Analytics.send(MainScreen.ButtonScanCard()) - store.dispatch(WalletAction.Scan(MainScreen.CardWasScanned())) + store.dispatch(WalletAction.Scan(Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main))) } } } 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 cba8631bb6..1a98a9c93d 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 @@ -9,6 +9,8 @@ import com.tangem.common.map import com.tangem.core.analytics.Analytics 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.events.MyWallets import com.tangem.tap.common.extensions.addCardContext import com.tangem.tap.common.extensions.dispatchOnMain @@ -136,7 +138,7 @@ internal class WalletSelectorMiddleware { ) ScanCardProcessor.scan( - analyticsEvent = MyWallets.CardWasScanned(), + analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.MyWallets), onWalletNotCreated = { // No need to rollback policy, continue with the policy set before the card scan store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) 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 34ab7b92ab..76c0a2319e 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 @@ -6,6 +6,8 @@ import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.core.analytics.Analytics 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.events.SignIn import com.tangem.tap.common.extensions.addCardContext import com.tangem.tap.common.extensions.dispatchOnMain @@ -109,7 +111,7 @@ internal class WelcomeMiddleware { useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) ScanCardProcessor.scan( - analyticsEvent = SignIn.CardWasScanned(), + analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.SignIn), onSuccess = { scanResponse -> scope.launch { onCardScanned(scanResponse) } }, From ce6028142bcda0e1444fb62383b43991b9b50455 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Feb 2023 15:56:03 +0300 Subject: [PATCH 22/46] Updated on 2026-08-14 --- .../saltPay/rv/TransactionHistoryAdapter.kt | 103 ------------------ buildSrc/src/main/java/Versions.kt | 4 +- 2 files changed, 2 insertions(+), 105 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt deleted file mode 100644 index bc5f923aff..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TransactionHistoryAdapter.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv - -import android.graphics.PorterDuff -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.common.extensions.setDrawable -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.extensions.toFormattedCurrencyString -import com.tangem.tap.features.wallet.models.PendingTransactionType -import com.tangem.tap.features.wallet.ui.wallet.saltPay.HistoryTransactionData -import com.tangem.wallet.R -import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding - -/** -[REDACTED_AUTHOR] - */ -internal class TransactionHistoryAdapter : ListAdapter(DiffUtilCallback) { - - override fun getItemId(position: Int): Long { - return if (currentList.isEmpty()) 0 - else currentList[position].transactionData.hash.hashCode().toLong() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionItemVH { - val inflater = LayoutInflater.from(parent.context) - val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false) - return TransactionItemVH(layout) - } - - override fun onBindViewHolder(holder: TransactionItemVH, position: Int) { - holder.bind(currentList[position]) - } - - object DiffUtilCallback : DiffUtil.ItemCallback() { - override fun areContentsTheSame( - oldItem: HistoryTransactionData, - newItem: HistoryTransactionData, - ) = oldItem == newItem - - override fun areItemsTheSame( - oldItem: HistoryTransactionData, - newItem: HistoryTransactionData, - ) = oldItem == newItem - } -} - -internal class TransactionItemVH( - private val binding: ItemSaltPayTxHistoryBinding, -) : RecyclerView.ViewHolder(binding.root) { - - fun bind(data: HistoryTransactionData) = with(binding) { - setupImage(data) - setupTitle(data) - setupSubtitle(data) - setupBalance(data) - } - - private fun setupImage(data: HistoryTransactionData) = with(binding) { - val drawable = when (data.getTransactionType()) { - PendingTransactionType.Incoming -> R.drawable.ic_tx_incoming - PendingTransactionType.Outgoing -> R.drawable.ic_tx_outgoing - PendingTransactionType.Unknown -> null - } - drawable?.let { imvTx.setDrawable(it) } - - if (data.isInProgress()) { - imvTx.setColorFilter(imvTx.getColor(R.color.icon_attention), PorterDuff.Mode.MULTIPLY) - } else { - imvTx.clearColorFilter() - } - } - - private fun setupTitle(data: HistoryTransactionData) = with(binding) { - val hash = data.transactionData.hash ?: return@with - - val formattedHash = "${hash.substring(0..5)}...${hash.substring(hash.length - 4)}" - tvTxHash.text = formattedHash - } - - private fun setupSubtitle(data: HistoryTransactionData) = with(binding) { - val inProgress = data.isInProgress() - tvTxStatus.show(inProgress) - tvTxTime.show(!inProgress) - - val time = data.transactionData.date?.timeInMillis.toString() ?: "none" - tvTxTime.text = time - } - - private fun setupBalance(data: HistoryTransactionData) = with(binding) { - val sign = when (data.getTransactionType()) { - PendingTransactionType.Incoming -> "+" - PendingTransactionType.Outgoing -> "-" - PendingTransactionType.Unknown -> "" - } - val amount = data.transactionData.amount - tvTxAmountSign.text = sign - tvTxAmountValue.text = amount.value?.toFormattedCurrencyString(8, amount.currencySymbol) - } -} \ No newline at end of file diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 04aded817b..aa2ad57abd 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -59,8 +59,8 @@ object Versions { // endregion Other libraries // region Tangem - // const val tangemBlockchainSdk = "develop-152-hotfix-4.0.1-156" - const val tangemBlockchainSdk = "0.0.1" + const val tangemBlockchainSdk = "develop-152-hotfix-4.0.1-4.0.2-161" + // const val tangemBlockchainSdk = "0.0.1" const val tangemCardSdk = "develop-179-hotfix-4.0.1-189" // endregion Tangem From 7e2b094f13cfc4592b4e961daaaf9dc3bd1dfcd6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 18 Feb 2023 18:07:01 +0300 Subject: [PATCH 23/46] Updated on 2026-08-14 --- .../tap/common/extensions/WalletManager.kt | 30 +++ .../tap/domain/model/WalletDataModel.kt | 2 + .../model/builders/WalletStoreBuilder.kt | 6 + .../DefaultWalletAmountsRepository.kt | 6 +- .../utils/WalletDataOperations.kt | 31 ++- .../utils/WalletStoreOperations.kt | 8 + .../tap/features/wallet/redux/WalletState.kt | 2 + .../wallet/redux/middlewares/Mapper.kt | 1 + .../redux/reducers/MultiWalletReducer.kt | 10 +- .../redux/reducers/TotalBalanceOperations.kt | 4 +- .../wallet/redux/reducers/WalletReducer.kt | 1 + .../tap/features/wallet/ui/WalletFragment.kt | 2 - .../features/wallet/ui/wallet/WalletView.kt | 6 +- .../ui/wallet/saltPay/SaltPayWalletView.kt | 201 ++++++++++-------- .../res/layout/layout_salt_pay_balance.xml | 3 +- buildSrc/src/main/java/Versions.kt | 2 +- 16 files changed, 204 insertions(+), 111 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 cda489f536..a07ef080da 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 @@ -2,6 +2,9 @@ package com.tangem.tap.common.extensions import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionHistoryProvider import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.common.services.Result @@ -10,6 +13,7 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.amountToCreateAccount import com.tangem.tap.domain.getFirstToken import com.tangem.tap.features.demo.isDemoCard +import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.features.wallet.redux.reducers.createAddressesData import com.tangem.tap.network.NetworkConnectivity @@ -30,6 +34,9 @@ suspend fun WalletManager.safeUpdate(): Result = try { Result.Success(wallet) } else { update() + if (wallet.blockchain == Blockchain.SaltPay && this is TransactionHistoryProvider) { + this.getTransactionHistory(wallet.address, wallet.blockchain, wallet.getTokens()) + } Result.Success(wallet) } } catch (exception: Exception) { @@ -76,10 +83,33 @@ fun WalletManager?.getAddressData(): AddressData? { else addressDataList[0] } +fun WalletManager.getTxHistory(currency: Currency): List = wallet.getTxHistory(currency) + +fun WalletManager.getBlockchainTxHistory(): List = wallet.getBlockchainTxHistory() + +fun WalletManager.getTokenTxHistory(token: Token): List = wallet.getTokenTxHistory(token) + fun WalletManager.Companion.stub(): T { val wallet = Wallet(Blockchain.Unknown, setOf(), Wallet.PublicKey(byteArrayOf(), null, null), setOf()) return object : WalletManager(wallet) { override val currentHost: String = "" override suspend fun update() {} } as T +} + +fun Wallet.getTxHistory(currency: Currency): List { + return (currency as? Currency.Token)?.let { this.getTokenTxHistory(it.token) } + ?: getBlockchainTxHistory() +} + +fun Wallet.getBlockchainTxHistory(): List { + return historyTransactions.filter { + it.contractAddress.isNullOrEmpty() + } +} + +fun Wallet.getTokenTxHistory(token: Token): List { + return historyTransactions.filter { + it.contractAddress == token.contractAddress + } } \ 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 3429afa07c..b9e83390a0 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 @@ -1,5 +1,6 @@ package com.tangem.tap.domain.model +import com.tangem.blockchain.common.TransactionData import com.tangem.tap.domain.model.WalletDataModel.Status import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction @@ -23,6 +24,7 @@ data class WalletDataModel( val existentialDeposit: BigDecimal?, val fiatRate: BigDecimal?, val isCardSingleToken: Boolean, + val historyTransactions: List?, ) { /** 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 651ab25018..5b4f90aea3 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 @@ -6,6 +6,8 @@ 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.common.extensions.getBlockchainTxHistory +import com.tangem.tap.common.extensions.getTokenTxHistory import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.tokens.models.BlockchainNetwork @@ -98,6 +100,7 @@ private fun BlockchainNetwork.getBlockchainWalletData(walletManager: WalletManag existentialDeposit = getExistentialDeposit(walletManager), fiatRate = null, isCardSingleToken = false, + historyTransactions = walletManager?.getBlockchainTxHistory(), ) } @@ -115,6 +118,7 @@ private fun BlockchainNetwork.getTokensWalletsData(walletManager: WalletManager? existentialDeposit = getExistentialDeposit(walletManager), fiatRate = null, isCardSingleToken = walletManager?.cardTokens?.contains(token) ?: false, + historyTransactions = walletManager?.getTokenTxHistory(token), ) } } @@ -131,6 +135,7 @@ private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): Wal existentialDeposit = getExistentialDeposit(walletManager), fiatRate = null, isCardSingleToken = false, + historyTransactions = walletManager.getBlockchainTxHistory(), ) } @@ -147,6 +152,7 @@ private fun Token.toTokenWalletData(walletManager: WalletManager): WalletDataMod existentialDeposit = getExistentialDeposit(walletManager), fiatRate = null, isCardSingleToken = walletManager.cardTokens.contains(this), + historyTransactions = walletManager.getTokenTxHistory(this), ) } 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 93c70be9ef..66c90b9f9e 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 @@ -20,6 +20,7 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.replaceByOrAdd +import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.walletStores.WalletStoresError @@ -32,6 +33,7 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update 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.updateWithTxHistories 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 @@ -203,7 +205,7 @@ internal class DefaultWalletAmountsRepository( updateWalletStoreWithUnreachable(walletStore) } else -> { - withInternetConnection { walletManager.update() } + withInternetConnection { walletManager.safeUpdate() } .map { updateWalletManagerWithAmounts(userWalletId, walletManager) } .flatMap { updateWalletStoreWithAmounts( @@ -318,7 +320,7 @@ internal class DefaultWalletAmountsRepository( it.updateWithDemoAmounts(wallet = updatedWallet) } else { it.updateWithAmounts(wallet = updatedWallet) - } + }.updateWithTxHistories(wallet = updatedWallet) }, ) } 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 cb49fe33c1..48b0c4d71e 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 @@ -4,6 +4,8 @@ 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.common.extensions.getBlockchainTxHistory +import com.tangem.tap.common.extensions.getTokenTxHistory import com.tangem.tap.domain.extensions.amountToCreateAccount import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.model.WalletDataModel @@ -29,6 +31,29 @@ internal fun List.updateWithFiatRates( } } +internal fun WalletDataModel.updateWithTxHistory(wallet: Wallet): WalletDataModel { + return this.copy( + historyTransactions = when (currency) { + is Currency.Blockchain -> wallet.getBlockchainTxHistory() + is Currency.Token -> wallet.getTokenTxHistory(currency.token) + }, + ) +} + +internal fun List.updateWithTxHistories( + wallet: Wallet, +): List { + return this.map { walletData -> + walletData.updateWithTxHistory(wallet) + } +} + +internal fun List.updateWithAmounts(wallet: Wallet): List { + return this.map { walletData -> + walletData.updateWithAmount(wallet) + } +} + internal fun WalletDataModel.updateWithAmount(wallet: Wallet): WalletDataModel { val pendingTransactions = wallet.getPendingTransactions() return this.copy( @@ -82,12 +107,6 @@ internal fun WalletDataModel.updateWithDemoAmount(wallet: Wallet): WalletDataMod ) } -internal fun List.updateWithAmounts(wallet: Wallet): List { - return this.map { walletData -> - walletData.updateWithAmount(wallet) - } -} - internal fun List.updateWithDemoAmounts(wallet: Wallet): List { return this.map { walletData -> walletData.updateWithDemoAmount(wallet) 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 0c3e47861c..4bf5394553 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 @@ -41,6 +41,14 @@ internal fun WalletStoreModel.updateWithError( ) } +internal fun WalletStoreModel.updateWithTxHistories( + wallet: Wallet, +): WalletStoreModel { + return this.copy( + walletsData = walletsData.updateWithTxHistories(wallet = wallet), + ) +} + internal fun WalletStoreModel.updateWithAmounts( wallet: Wallet, ): WalletStoreModel { 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 7e37e27a5a..93bc2c36fd 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 @@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux import android.graphics.Bitmap import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType @@ -325,6 +326,7 @@ data class Artwork( data class WalletData( val pendingTransactions: List = emptyList(), + val historyTransactions: List? = null, val hashesCountVerified: Boolean? = null, val walletAddresses: WalletAddresses? = null, val currencyData: BalanceWidgetData = BalanceWidgetData(), 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 index 9433c8b6e7..518b4aba41 100644 --- 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 @@ -116,6 +116,7 @@ private fun WalletDataModel.mapToReduxModel( ?.toString(), errorMessage = status.errorMessage, ), + historyTransactions = historyTransactions, ) } 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 408cd883d5..9c4b0f8cfd 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 @@ -6,6 +6,8 @@ import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.WalletManager import com.tangem.tap.common.extensions.dispatchToastNotification +import com.tangem.tap.common.extensions.getBlockchainTxHistory +import com.tangem.tap.common.extensions.getTokenTxHistory import com.tangem.tap.common.extensions.toFiatString import com.tangem.tap.common.extensions.toFormattedCurrencyString import com.tangem.tap.common.redux.navigation.AppScreen @@ -53,6 +55,7 @@ class MultiWalletReducer { blockchain.derivationPath, ), existentialDepositString = getExistentialDeposit(walletManager), + historyTransactions = walletManager?.getBlockchainTxHistory(), ) WalletStore( @@ -88,6 +91,7 @@ class MultiWalletReducer { action.blockchain.derivationPath, ), existentialDepositString = getExistentialDeposit(walletManager), + historyTransactions = walletManager?.getBlockchainTxHistory(), ) val walletStore = WalletStore( walletManager = walletManager, @@ -151,6 +155,7 @@ class MultiWalletReducer { derivationPath = action.blockchain.derivationPath, ), walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)), + historyTransactions = walletManager.getTokenTxHistory(action.token) ) state.updateWalletData(newTokenWalletData) } @@ -204,8 +209,8 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat val currency = Currency.fromBlockchainNetwork(blockchain, this) if (state.currencies.contains(currency)) return null - val walletManager = state.getWalletManager(currency)?.wallet - val walletAddresses = createAddressList(walletManager) + val walletManager = state.getWalletManager(currency) + val walletAddresses = createAddressList(walletManager?.wallet) return WalletData( currencyData = BalanceWidgetData( @@ -216,5 +221,6 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat walletAddresses = walletAddresses, mainButton = WalletMainButton.SendButton(false), currency = currency, + historyTransactions = walletManager?.getTokenTxHistory(this), ) } \ 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 08a7def4bd..9a8f4ded04 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 @@ -5,7 +5,9 @@ import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.ui.BalanceStatus import java.math.BigDecimal -fun List.findProgressState(): ProgressState { +fun List.findProgressState(initialState: ProgressState = ProgressState.Done): ProgressState { + if (this.isEmpty()) return initialState + return this .mapToProgressState() .reduce(ProgressState::or) 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 f3366b882f..2653083ecf 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 @@ -350,6 +350,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS } is WalletAction.WalletStoresChanged.UpdateWalletStores -> { newState = newState.copy( + state = action.reduxWalletStores.flatMap { it.walletsData }.findProgressState(newState.state), walletsStores = action.reduxWalletStores, ) } 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 31f63630c1..ab946481ec 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 @@ -186,8 +186,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber Unit)? = null - protected var fragment: WalletFragment? = null protected var binding: FragmentWalletBinding? = null @@ -22,9 +19,8 @@ abstract class WalletView { binding = null } - //TODO: blueprint open fun onViewDestroy() { - pullToRefreshListener = null + removeFragment() } open fun onDestroyFragment() {} diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt index 62dccf8f6d..b1d18042d6 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt @@ -2,19 +2,16 @@ package com.tangem.tap.features.wallet.ui.wallet.saltPay import android.view.LayoutInflater import android.view.ViewGroup -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.extensions.Result +import com.skydoves.androidveil.VeilLayout import com.tangem.domain.common.extensions.debounce import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.ShimmerData import com.tangem.tap.common.ShimmerRecyclerAdapter import com.tangem.tap.common.extensions.animateVisibility -import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.formatAmountAsSpannedString import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.common.recyclerView.SpaceItemDecoration -import com.tangem.tap.domain.getFirstToken import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState @@ -31,40 +28,41 @@ import com.tangem.wallet.databinding.ItemSaltPayTxHistoryShimmerBinding import com.tangem.wallet.databinding.LayoutSaltPayBalanceBinding import com.tangem.wallet.databinding.LayoutSaltPayTxHistoryBinding import com.tangem.wallet.databinding.LayoutSaltPayWalletBinding -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action -import java.util.concurrent.atomic.AtomicBoolean +import timber.log.Timber /** [REDACTED_AUTHOR] */ class SaltPayWalletView : WalletView() { - private lateinit var saltPayBinding: LayoutSaltPayWalletBinding + private var saltPayBinding: LayoutSaltPayWalletBinding? = null + private val actionDebouncer = debounce(500, mainScope) { store.dispatch(it) } - private val balanceWidget: LayoutSaltPayBalanceBinding - get() = saltPayBinding.lSaltPayBalance + private val balanceWidget: LayoutSaltPayBalanceBinding? + get() = saltPayBinding?.lSaltPayBalance - private val txWidget: LayoutSaltPayTxHistoryBinding - get() = saltPayBinding.lSaltPayTxHistory + private val txWidget: LayoutSaltPayTxHistoryBinding? + get() = saltPayBinding?.lSaltPayTxHistory - private var initialized = AtomicBoolean(false) + private val itemDecorator = SpaceItemDecoration.vertical(10F) override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) { + Timber.d("changeWalletView") saltPayBinding = binding.lSaltPayWallet - setFragment(fragment, binding) - onViewCreated() showSaltPayView(binding) + onViewCreated() } override fun onViewCreated() { - this.pullToRefreshListener = this::handlePullToRefresh - prepareTxRecyclers() + Timber.d("onViewCreated") + prepareTxRecyclers(txWidget!!) } private fun showSaltPayView(binding: FragmentWalletBinding) = with(binding) { + Timber.d("showSaltPayView") rvWarningMessages.hide() rvPendingTransaction.hide() rvMultiwallet.hide() @@ -78,32 +76,41 @@ class SaltPayWalletView : WalletView() { lSaltPayWallet.root.show() } - private fun prepareTxRecyclers() { + private fun prepareTxRecyclers(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { + Timber.d("prepareTxRecyclers") val vhViewFactory: (ViewGroup) -> ViewGroup = { val inflater = LayoutInflater.from(it.context) ItemSaltPayTxHistoryShimmerBinding.inflate(inflater, it, false).root } val adapter = ShimmerRecyclerAdapter(vhViewFactory) - txWidget.rvTxHistoryShimmer.adapter = adapter - txWidget.rvTxHistoryShimmer.addItemDecoration(SpaceItemDecoration.vertical(10F)) + rvTxHistoryShimmer.adapter = adapter + rvTxHistoryShimmer.addItemDecoration(itemDecorator) + adapter.submitList( + listOf( + ShimmerData(), + ShimmerData(), + ShimmerData(), + ), + ) - txWidget.rvTxHistory.adapter = TxHistoryAdapter() - txWidget.rvTxHistory.addItemDecoration(SpaceItemDecoration.vertical(10F)) - - handleTxInit() - } - - private fun handlePullToRefresh() { - requestTxHistory(store.state.walletState) + rvTxHistory.adapter = TxHistoryAdapter() + rvTxHistory.addItemDecoration(itemDecorator) } override fun onNewState(state: WalletState) { - if (!initialized.getAndSet(true)) requestTxHistory(state) - setupBalanceWidget(state) + val balanceWidget = balanceWidget ?: return + val txWidget = txWidget ?: return + + setupBalanceWidget(balanceWidget, state) + setupTxHistoryWidget(txWidget, state) } - private fun setupBalanceWidget(state: WalletState) = with(balanceWidget) { - val tokenData = state.primaryTokenData ?: return@with + private fun setupBalanceWidget( + balanceWidget: LayoutSaltPayBalanceBinding, + state: WalletState, + ) = with(balanceWidget) { + Timber.d("setupBalanceWidget") + val tokenData = state.primaryTokenData ?: return val appCurrency = store.state.globalState.appCurrency val mainProgressState = state.state @@ -119,6 +126,7 @@ class SaltPayWalletView : WalletView() { veilBalanceCrypto.animateVisibility(show = mainProgressState != ProgressState.Error) if (tokenData.currencyData.fiatAmount == null) { + veilBalance.veil() actionDebouncer(WalletAction.LoadFiatRate()) } else { veilBalance.unVeil() @@ -140,94 +148,107 @@ class SaltPayWalletView : WalletView() { } } - private fun requestTxHistory(state: WalletState) { + private fun setupTxHistoryWidget(txWidget: LayoutSaltPayTxHistoryBinding, state: WalletState) { + Timber.d("setupTxHistoryWidget") + if (state.state == ProgressState.Loading) { + handleTxLoading(txWidget) + return + } + if (state.state == ProgressState.Error) { + handleTxError(txWidget) + return + } + if (state.state != ProgressState.Done) return + val walletAddress = state.primaryWalletManager?.wallet?.address ?: return + val tokenData = state.primaryTokenData ?: return + val historyTransactions = tokenData.historyTransactions ?: return + if (historyTransactions.isEmpty()) { + handleTxEmpty(txWidget) + return + } + scope.launch { - val walletManager = state.primaryWalletManager as? EthereumWalletManager ?: return@launch - val wallet = walletManager.wallet - val token = wallet.getFirstToken() ?: return@launch - val walletAddress = wallet.address - // val walletAddress = "0xDA94Aae02a4Db0e09E1Cf240E3a0973ba89052cf" - when (val result = walletManager.getTransactionHistory(walletAddress, wallet.blockchain, setOf(token))) { - is Result.Success -> { - val dateAssociatedHistory = mutableMapOf>() - - result.data - .filter { it.contractAddress == token.contractAddress } - .sortedByDescending { it.date?.timeInMillis ?: 0 } - .forEach { - val txData = HistoryTransactionData(it, walletAddress) - val list = dateAssociatedHistory[txData.date] ?: mutableListOf() - list.add(txData) - dateAssociatedHistory[txData.date] = list - } - - val dataList = mutableListOf() - dateAssociatedHistory.forEach { entry -> - dataList.add(HistoryItemData.Date(entry.key)) - entry.value.forEach { dataList.add(HistoryItemData.TransactionData(it)) } - } - // .toMutableList().apply { clear() } - - delay(300) - withMainContext { - if (dataList.isEmpty()) { - handleTxEmpty() - } else { - handleTxSuccess(dataList) - } - } - } - is Result.Failure -> { - delay(300) - withMainContext { handleTxError() } + val dateAssociatedHistory = mutableListOf>>() + historyTransactions.forEach { tx -> + val txHistoryData = HistoryTransactionData(tx, walletAddress) + val item = dateAssociatedHistory.firstOrNull { it.first == txHistoryData.date } + if (item == null) { + dateAssociatedHistory.add(Pair(txHistoryData.date, mutableListOf(txHistoryData))) + } else { + item.second.add(txHistoryData) } } + + val rvDataList = mutableListOf() + dateAssociatedHistory.forEach { (txData, txHistoryData) -> + rvDataList.add(HistoryItemData.Date(txData)) + rvDataList.addAll(txHistoryData.map { HistoryItemData.TransactionData(it) }) + } + + withMainContext { handleTxSuccess(txWidget, rvDataList) } } } - private fun handleTxInit() = with(txWidget) { - (rvTxHistoryShimmer.adapter as? ShimmerRecyclerAdapter)?.submitList( - listOf( - ShimmerData(), - ShimmerData(), - ShimmerData(), - ), - ) + private fun handleTxLoading(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { + Timber.d("handleTxLoading") groupEmpty.hide() groupError.hide() groupSuccess.hide() - root.beginDelayedTransition() - txWidget.groupShimmer.show() + // root.beginDelayedTransition() + groupShimmer.show() } - private fun handleTxSuccess(dataList: List) = with(txWidget) { + private fun handleTxSuccess( + txWidget: LayoutSaltPayTxHistoryBinding, + dataList: List, + ) = with(txWidget) { + Timber.d("handleTxSuccess") groupShimmer.hide() groupEmpty.hide() groupError.hide() - root.beginDelayedTransition() - updateTxHistoryWidget(dataList) + // root.beginDelayedTransition() + updateTxHistoryWidget(txWidget, dataList) groupSuccess.show() } - private fun handleTxEmpty() = with(txWidget) { + private fun handleTxEmpty(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { + Timber.d("handleTxEmpty") groupShimmer.hide() groupError.hide() groupSuccess.hide() - root.beginDelayedTransition() - updateTxHistoryWidget(emptyList()) + // root.beginDelayedTransition() + updateTxHistoryWidget(txWidget, emptyList()) groupEmpty.show() } - private fun handleTxError() = with(txWidget) { + private fun handleTxError(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { + Timber.d("handleTxError") groupShimmer.hide() groupEmpty.hide() groupSuccess.hide() - root.beginDelayedTransition() - updateTxHistoryWidget(emptyList()) + // root.beginDelayedTransition() + updateTxHistoryWidget(txWidget, emptyList()) groupError.show() } - private fun updateTxHistoryWidget(dataList: List) = with(txWidget) { + private fun updateTxHistoryWidget( + txWidget: LayoutSaltPayTxHistoryBinding, + dataList: List, + ) = with(txWidget) { (rvTxHistory.adapter as TxHistoryAdapter).submitList(dataList) } + + override fun onViewDestroy() { + Timber.d("onViewDestroy") + txWidget?.apply { + rvTxHistoryShimmer.removeItemDecoration(itemDecorator) + rvTxHistory.removeItemDecoration(itemDecorator) + } + super.onViewDestroy() + } +} + +private fun VeilLayout.changeVeilState(show: Boolean) { + if (show) this.veil() + else this.unVeil() } \ No newline at end of file diff --git a/app/src/main/res/layout/layout_salt_pay_balance.xml b/app/src/main/res/layout/layout_salt_pay_balance.xml index cdf2d00c6a..8ce65d0b6b 100644 --- a/app/src/main/res/layout/layout_salt_pay_balance.xml +++ b/app/src/main/res/layout/layout_salt_pay_balance.xml @@ -51,7 +51,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="1" - android:minWidth="152dp" android:textColor="@color/text_primary_1" android:textSize="24sp" android:textStyle="bold" @@ -96,7 +95,7 @@ android:text="@string/wallet_balance_blockchain_unreachable" android:textColor="@color/warning" android:textSize="12sp" - android:visibility="visible" + android:visibility="gone" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/veil_balance" /> diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index aa2ad57abd..73387ff687 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -59,7 +59,7 @@ object Versions { // endregion Other libraries // region Tangem - const val tangemBlockchainSdk = "develop-152-hotfix-4.0.1-4.0.2-161" + const val tangemBlockchainSdk = "develop-152-hotfix-4.0.1-4.0.2-162" // const val tangemBlockchainSdk = "0.0.1" const val tangemCardSdk = "develop-179-hotfix-4.0.1-189" From 53e179e0918f6384e0f07c3ded3d647ba7b2709e Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 18 Feb 2023 19:32:22 +0300 Subject: [PATCH 24/46] Updated on 2026-08-14 --- .../tap/common/extensions/ImageRequest.kt | 32 +++++++++++++++++++ .../model/builders/UserWalletBuilder.kt | 2 ++ .../tap/features/wallet/redux/WalletState.kt | 1 + .../ui/components/WalletItem.kt | 13 ++++++-- .../tangem/domain/common/SaltPayWorkaround.kt | 2 ++ 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt b/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt new file mode 100644 index 0000000000..9da1448d9c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt @@ -0,0 +1,32 @@ +package com.tangem.tap.common.extensions + +import android.widget.ImageView +import coil.ImageLoader +import coil.imageLoader +import coil.load +import coil.request.Disposable +import coil.request.ImageRequest +import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +fun ImageRequest.Builder.cardImageData(any: Any?): ImageRequest.Builder = apply { + data(any?.parseData()) +} + +fun ImageView.loadCardImage( + data: Any?, + imageLoader: ImageLoader = context.imageLoader, + builder: ImageRequest.Builder.() -> Unit = {}, +): Disposable = this.load( + data = data?.parseData(), + imageLoader = imageLoader, + builder = builder, +) + +private fun Any?.parseData(): Any? = when { + this is String && this == Artwork.SALT_PAY_URL -> R.drawable.img_salt_pay_visa + else -> this +} \ 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 22cc834b2a..b5dff3de1f 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 @@ -6,6 +6,7 @@ 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.SaltPayWorkaround import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TwinCardNumber @@ -96,6 +97,7 @@ class UserWalletBuilder( return when { cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL + SaltPayWorkaround.isSaltPayCardId(cardId) -> Artwork.SALT_PAY_URL else -> when (TwinsHelper.getTwinCardNumber(cardId)) { TwinCardNumber.First -> Artwork.TWIN_CARD_1 TwinCardNumber.Second -> Artwork.TWIN_CARD_2 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 93bc2c36fd..7e7f8cc3c2 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 @@ -321,6 +321,7 @@ data class Artwork( const val MARTA_CARD_ID = "BC02" const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png" const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png" + const val SALT_PAY_URL = "key_for_switch_url_to_drawableId_of_salt_pay_card" } } 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 6e67669815..cc02374081 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 @@ -4,7 +4,15 @@ 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.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 @@ -30,6 +38,7 @@ 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.common.extensions.cardImageData 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 @@ -107,7 +116,7 @@ private fun WalletCardImage( SubcomposeAsyncImage( modifier = cardImageModifier, model = ImageRequest.Builder(LocalContext.current) - .data(cardImageUrl) + .cardImageData(cardImageUrl) .crossfade(true) .build(), loading = { CardImageShimmer(modifier = cardImageModifier) }, diff --git a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt index 1328643534..4e7be3e411 100644 --- a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt +++ b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt @@ -19,6 +19,8 @@ object SaltPayWorkaround { } } + fun isSaltPayCardId(cardId: String): Boolean = isVisaBatchId(cardId.take(4)) || isWalletCardId(cardId) + fun isVisaBatchId(batchId: String): Boolean = visaBatches.contains(batchId) fun isWalletCardId(cardId: String): Boolean { From e8640b4865b5a61713b783506bc494abfa905148 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Feb 2023 10:51:58 +0300 Subject: [PATCH 25/46] Updated on 2026-08-14 --- .../com/tangem/tap/common/analytics/events/AnalyticsParam.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e46716ae0c..4a07f6a669 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,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) + class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.code) class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol) } From 2726774a70aa4472c655960542b10381d997316a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Feb 2023 10:52:39 +0300 Subject: [PATCH 26/46] Updated on 2026-08-14 --- .../compose/AddCustomTokenScreen.kt | 16 ++++++++-------- .../wallet/redux/reducers/MultiWalletReducer.kt | 2 +- .../tap/features/welcome/redux/WelcomeAction.kt | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt index 2fbbfe4ca5..aa6d1ea2d8 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt @@ -76,7 +76,7 @@ fun AddCustomTokenScreen( val selectedTestCase = remember { mutableStateOf(TestCase.ContractAddress) } val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( - bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed) + bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed), ) val coroutineScope = rememberCoroutineScope() val toggleBottomSheet = { coroutineScope.launch { bottomSheetScaffoldState.toggle() } } @@ -145,7 +145,7 @@ private fun ScreenContent( Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp) + .padding(16.dp), ) { FormFields(state, closePopupTrigger) } @@ -196,7 +196,7 @@ fun Warnings(warnings: List) { AddCustomTokenWarning( modifier = modifier.fillMaxWidth(), warning = item, - converter = warningConverter + converter = warningConverter, ) } } @@ -207,7 +207,7 @@ private fun AddButton(state: MutableState) { AddCustomTokenFab( modifier = Modifier .widthIn(210.dp, 280.dp), - isEnabled = state.value.screenState.addButton.isEnabled + isEnabled = state.value.screenState.addButton.isEnabled, ) { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) } } @@ -215,7 +215,7 @@ private fun AddButton(state: MutableState) { private fun AddCustomTokenFab( modifier: Modifier = Modifier, isEnabled: Boolean = true, - onClick: () -> Unit + onClick: () -> Unit, ) { val contentColor = Color.White val backgroundColor = if (isEnabled) { @@ -250,19 +250,19 @@ data class ScreenFieldData( val field: DataField<*>, val error: AddCustomTokenError?, val errorConverter: ModuleMessageConverter, - val viewState: ViewStates.TokenField + val viewState: ViewStates.TokenField, ) { companion object { fun fromState( field: DataField<*>, state: AddCustomTokenState, - errorConverter: ModuleMessageConverter + errorConverter: ModuleMessageConverter, ): ScreenFieldData { return ScreenFieldData( field = field, error = state.getError(field.id), errorConverter = errorConverter, - viewState = selectField(field.id, state.screenState) + viewState = selectField(field.id, state.screenState), ) } 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 9c4b0f8cfd..7fb4fb8c8a 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 @@ -155,7 +155,7 @@ class MultiWalletReducer { derivationPath = action.blockchain.derivationPath, ), walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)), - historyTransactions = walletManager.getTokenTxHistory(action.token) + historyTransactions = walletManager.getTokenTxHistory(action.token), ) state.updateWalletData(newTokenWalletData) } 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 f33cfe9453..9f117cb782 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 @@ -5,7 +5,7 @@ import com.tangem.common.core.TangemError import org.rekotlin.Action internal sealed interface WelcomeAction : Action { - object OnCreate: WelcomeAction + object OnCreate : WelcomeAction object ProceedWithBiometrics : WelcomeAction { object Success : WelcomeAction data class Error(val error: TangemError) : WelcomeAction From 7934ed13d0f41ff0fe7b8aecdae7fabdd803f598 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Feb 2023 12:11:56 +0300 Subject: [PATCH 27/46] Updated on 2026-08-14 --- .../tap/features/onboarding/OnboardingSaltPayHelper.kt | 8 ++++---- .../products/wallet/redux/OnboardingWalletState.kt | 2 +- .../wallet/saltPay/redux/OnboardingSaltPayState.kt | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt index 209fc789eb..8c88cc7a84 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt @@ -20,14 +20,14 @@ class OnboardingSaltPayHelper { manager: SaltPayActivationManager, ): Result { return try { - var updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } + val updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } val cardStorage = preferencesStorage.usedCardsPrefStorage val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId) - if (updatedStep == SaltPayActivationStep.Success && activationIsFinished) { - updatedStep = SaltPayActivationStep.Finished + if (updatedStep == SaltPayActivationStep.Success && !activationIsFinished) { + cardStorage.activationFinished(scanResponse.card.cardId) } - val isActivationCase = updatedStep != SaltPayActivationStep.Finished + val isActivationCase = updatedStep != SaltPayActivationStep.Success val isBackupCase = scanResponse.card.backupStatus?.isActive == false Result.Success(isActivationCase || isBackupCase) } catch (ex: Exception) { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 4e3cad3657..314d4ea06b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -60,7 +60,7 @@ data class OnboardingWalletState( -> 9 SaltPayActivationStep.Claim -> 10 SaltPayActivationStep.ClaimInProgress -> 11 - SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success, SaltPayActivationStep.Finished, + SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success, -> getMaxProgress() } } 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 9bedb49011..7b68518634 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 @@ -43,5 +43,4 @@ enum class SaltPayActivationStep { ClaimInProgress, ClaimSuccess, Success, - Finished; } \ No newline at end of file From 44509cd614ab022eb4f23db622468f8a5b6d587a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Feb 2023 14:51:41 +0300 Subject: [PATCH 28/46] Updated on 2026-08-14 --- domain/src/main/java/com/tangem/domain/common/LogConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt index 1d1bf6d11a..a0046af552 100644 --- a/domain/src/main/java/com/tangem/domain/common/LogConfig.kt +++ b/domain/src/main/java/com/tangem/domain/common/LogConfig.kt @@ -22,5 +22,5 @@ object NetworkLogConfig { object AnalyticsHandlersLogConfig { val firebase: Boolean = false val appsFlyer: Boolean = false - val amplitude: Boolean = true + val amplitude: Boolean = false } \ No newline at end of file From 451fb80c830909c4f4fa6605e2bbbf3dfdf2101c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Feb 2023 17:09:07 +0300 Subject: [PATCH 29/46] Updated on 2026-08-14 --- .../tangem/tap/common/redux/global/GlobalAction.kt | 12 +++++------- .../products/wallet/redux/OnboardingWalletAction.kt | 5 ++--- .../wallet/redux/OnboardingWalletMiddleware.kt | 10 +++++++--- .../products/wallet/redux/OnboardingWalletReducer.kt | 8 +++++--- 4 files changed, 19 insertions(+), 16 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 d9281b3754..dbd831e752 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 @@ -36,18 +36,16 @@ sealed class GlobalAction : Action { sealed class Onboarding : GlobalAction() { /** * Initiate an onboarding process. - * For SaltPay cards it's additionally checks for unfinished backup. - * For resuming unfinished backup for standard Wallet cards see CheckForUnfinishedBackup and - * StartForUnfinishedBackup + * For resuming unfinished backup of standard Wallet and SaltPay cards see + * BackupAction.CheckForUnfinishedBackup, GlobalAction.Onboarding.StartForUnfinishedBackup */ data class Start(val scanResponse: ScanResponse, val canSkipBackup: Boolean = true) : Onboarding() /** - * Initiate resuming of unfinished backup only for standard Wallet cards. - * For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start. - * See more Onboarding.Start, CheckForUnfinishedBackup + * Initiate resuming of unfinished backup for standard Wallet and SaltPay cards. + * See more BackupAction.CheckForUnfinishedBackup */ - object StartForUnfinishedBackup : Onboarding() + data class StartForUnfinishedBackup(val isSaltPayVisa: Boolean) : Onboarding() object Stop : Onboarding() } 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 f3ff070f97..009ede0c8d 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 @@ -30,9 +30,8 @@ sealed class BackupAction : Action { object ScanPrimaryCard : BackupAction() /** - * Check for unfinished backup of standard Wallet cards. - * For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start. - * See more Onboarding.Start, CheckForUnfinishedBackup, StartForUnfinishedBackup + * Check for unfinished backup of standard Wallets and SaltPay cards + * See more GlobalAction.Onboarding.StartForUnfinishedBackup */ object CheckForUnfinishedBackup : BackupAction() 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 39b9347a99..143ad4d35f 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 @@ -71,7 +71,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } when { card == null -> { - // it's possible when found unfinished backup for standard Wallet cards + // it's possible when found unfinished backup store.dispatch(OnboardingWalletAction.ResumeBackup) } card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> { @@ -342,12 +342,16 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) backupService.discardSavedBackup() } is BackupAction.CheckForUnfinishedBackup -> { - if (backupService.hasIncompletedBackup && !backupService.primaryCardIsSaltPayVisa()) { + if (backupService.hasIncompletedBackup) { store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound)) } } is BackupAction.ResumeFoundUnfinishedBackup -> { - store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup) + store.dispatch( + GlobalAction.Onboarding.StartForUnfinishedBackup( + isSaltPayVisa = backupService.primaryCardIsSaltPayVisa(), + ), + ) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } is BackupAction.DismissBackup -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 8cca79e9dd..cb5242af39 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -43,7 +43,7 @@ private class ReducerForGlobalAction { is GlobalAction.Onboarding.Start -> { OnboardingWalletState( backupState = state.backupState.copy( - maxBackupCards = if (action.scanResponse.isSaltPay()) 1 else 2, + maxBackupCards = maxBackupCards(action.scanResponse.isSaltPay()), canSkipBackup = if (action.scanResponse.isSaltPay()) false else action.canSkipBackup, ), isSaltPay = action.scanResponse.isSaltPay(), @@ -52,15 +52,17 @@ private class ReducerForGlobalAction { is GlobalAction.Onboarding.StartForUnfinishedBackup -> { OnboardingWalletState( backupState = state.backupState.copy( - maxBackupCards = 2, + maxBackupCards = maxBackupCards(action.isSaltPayVisa), canSkipBackup = false, ), - isSaltPay = false, + isSaltPay = action.isSaltPayVisa, ) } else -> state } } + + private fun maxBackupCards(isSaltPay: Boolean): Int = if (isSaltPay) 1 else 2 } } From c745a28baaf446fe3909ac50b77185bb7bbbf337 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 07:25:59 +0300 Subject: [PATCH 30/46] Updated on 2026-08-14 --- .../java/com/tangem/tap/common/analytics/events/Onboarding.kt | 1 + .../wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) 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 0b203bab27..1bf7b0bd0b 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 @@ -68,6 +68,7 @@ sealed class Onboarding( } class PinScreenOpened : Onboarding("Onboarding", "PIN screen opened") + class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code") class PinCodeSet : Onboarding("Onboarding", "PIN code set") class ButtonConnect : Onboarding("Onboarding", "Button - Connect") class KYCScreenOpened : Onboarding("Onboarding", "KYC screen opened") 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 ad903e6dd1..41edc6ae31 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 @@ -148,6 +148,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App return@launch } + Analytics.send(Onboarding.PinCodeSet()) handleInProgress = false dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycIntro)) } @@ -177,7 +178,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App is OnboardingSaltPayAction.TrySetPin -> { try { assertPinValid(action.pin, getState().pinLength) - Analytics.send(Onboarding.PinCodeSet()) + Analytics.send(Onboarding.ButtonSetPinCode()) store.dispatch(OnboardingSaltPayAction.SetPin(action.pin)) store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.CardRegistration)) } catch (error: SaltPayActivationError) { From fb93ba0fa00fe4f7a02664be0896357c17ec9a38 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 08:50:05 +0300 Subject: [PATCH 31/46] Updated on 2026-08-14 --- .../wallet/redux/OnboardingWalletAction.kt | 6 +++++- .../wallet/redux/OnboardingWalletMiddleware.kt | 14 ++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) 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 f3ff070f97..cff498d196 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 @@ -64,7 +64,11 @@ sealed class BackupAction : Action { data class PrepareToWriteBackupCard(val cardNumber: Int) : BackupAction() data class WriteBackupCard(val cardNumber: Int) : BackupAction() - object FinishBackup : BackupAction() + /** + * It always calls for the SaltPay cards when resuming the activation process. + */ + data class FinishBackup(val withAnalytics: Boolean = true) : BackupAction() + object DiscardBackup : BackupAction() object DiscardSavedBackup : BackupAction() object ResumeFoundUnfinishedBackup : BackupAction() 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 39b9347a99..03f10c1e97 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 @@ -86,10 +86,10 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: scanResponse.isSaltPay() -> { store.dispatch(OnboardingWalletAction.GetToSaltPayStep) - store.dispatch(BackupAction.FinishBackup) + store.dispatch(BackupAction.FinishBackup(withAnalytics = false)) } - else -> store.dispatch(BackupAction.FinishBackup) + else -> store.dispatch(BackupAction.FinishBackup()) } } else -> { @@ -314,7 +314,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) is CompletionResult.Success -> { if (backupService.currentState == BackupService.State.Finished) { initSaltPayOnBackupFinishedIfNeeded(scanResponse, onboardingWalletState) - store.dispatchOnMain(BackupAction.FinishBackup) + store.dispatchOnMain(BackupAction.FinishBackup()) } else { store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1)) } @@ -325,7 +325,9 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.FinishBackup -> { - Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber)) + if (action.withAnalytics){ + Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber)) + } if (!onboardingWalletState.isSaltPay) { Analytics.send(Onboarding.Finished()) finishCardActivation(backupState, card) @@ -353,7 +355,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) is BackupAction.DismissBackup -> { Analytics.send(Onboarding.Backup.Skipped()) if (onboardingWalletState.isSaltPay) throw UnsupportedOperationException() - store.dispatch(BackupAction.FinishBackup) + store.dispatch(BackupAction.FinishBackup()) } } } @@ -401,7 +403,7 @@ private fun initSaltPayOnBackupFinishedIfNeeded( card = scanResponse.card, ).create() store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager)) - store.dispatchOnMain(OnboardingSaltPayAction.Update) + store.dispatchOnMain(OnboardingSaltPayAction.Update(false)) } } From 7b1630b933082d15e04eb18798c0d9a000ca5491 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 08:57:45 +0300 Subject: [PATCH 32/46] Updated on 2026-08-14 --- .../tap/domain/scanCard/ScanCardProcessor.kt | 2 +- .../redux/OnboardingWalletMiddleware.kt | 3 +- .../saltPay/redux/OnboardingSaltPayAction.kt | 13 ++++- .../redux/OnboardingSaltPayMiddleware.kt | 57 ++++++++++++------- .../saltPay/ui/OnboardingSaltPayView.kt | 5 +- 5 files changed, 55 insertions(+), 25 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 21c1cd221a..3317a70315 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 @@ -201,7 +201,7 @@ object ScanCardProcessor { onWalletNotCreated() store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) - store.dispatch(OnboardingSaltPayAction.Update) + store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false)) navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } } else { delay(DELAY_SDK_DIALOG_CLOSE) 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 03f10c1e97..26d34fb13e 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 @@ -87,6 +87,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: scanResponse.isSaltPay() -> { store.dispatch(OnboardingWalletAction.GetToSaltPayStep) store.dispatch(BackupAction.FinishBackup(withAnalytics = false)) + store.dispatch(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess) } else -> store.dispatch(BackupAction.FinishBackup()) @@ -325,7 +326,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.FinishBackup -> { - if (action.withAnalytics){ + if (action.withAnalytics) { Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber)) } if (!onboardingWalletState.isSaltPay) { 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 73c1700fc6..1fbb284524 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 @@ -11,6 +11,11 @@ import java.math.BigDecimal sealed class OnboardingSaltPayAction : Action { object Init : OnboardingSaltPayAction() + /** + * Notification about the switching from the Wallet view to the SaltPay view. + */ + object OnSwitchedToSaltPayProcess : OnboardingSaltPayAction() + data class SetDependencies( val registrationManager: SaltPayActivationManager, ) : OnboardingSaltPayAction() @@ -20,7 +25,7 @@ sealed class OnboardingSaltPayAction : Action { data class SetAccessCode(val accessCode: String?) : OnboardingSaltPayAction() - object Update : OnboardingSaltPayAction() + data class Update(val withAnalytics: Boolean = true) : OnboardingSaltPayAction() object RegisterCard : OnboardingSaltPayAction() object OpenUtorgKYC : OnboardingSaltPayAction() object UtorgKYCRedirectSuccess : OnboardingSaltPayAction() @@ -31,7 +36,11 @@ sealed class OnboardingSaltPayAction : Action { data class TrySetPin(val pin: String) : OnboardingSaltPayAction() data class SetPin(val pin: String) : OnboardingSaltPayAction() - data class SetStep(val newStep: SaltPayActivationStep) : OnboardingSaltPayAction() + data class SetStep( + val newStep: SaltPayActivationStep, + val withAnalytics: Boolean = true, + ) : OnboardingSaltPayAction() + data class SetAmountToClaim(val amount: Amount?) : OnboardingSaltPayAction() data class SetTokenBalance(val balanceValue: BigDecimal) : 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 41edc6ae31..8ecae84673 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 @@ -79,6 +79,9 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App onboardingManager.activationStarted(card.cardId) } } + is OnboardingSaltPayAction.OnSwitchedToSaltPayProcess -> { + sendAnalyticsScreenOpened(getState().step, newStep = SaltPayActivationStep.None) + } is OnboardingSaltPayAction.Update -> { handleInProgress = true @@ -90,7 +93,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } handleInProgress = false - dispatchOnMain(OnboardingSaltPayAction.SetStep(updateStep)) + dispatchOnMain(OnboardingSaltPayAction.SetStep(updateStep, action.withAnalytics)) if (updateStep == SaltPayActivationStep.Claim) { handleClaimRefreshInProgress = true @@ -172,7 +175,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } handleInProgress = false - dispatchOnMain(OnboardingSaltPayAction.Update) + dispatchOnMain(OnboardingSaltPayAction.Update()) } } is OnboardingSaltPayAction.TrySetPin -> { @@ -258,24 +261,24 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } is OnboardingSaltPayAction.SetStep -> { - when (action.newStep) { - SaltPayActivationStep.NeedPin -> Analytics.send(Onboarding.PinScreenOpened()) - SaltPayActivationStep.KycIntro -> Analytics.send(Onboarding.KYCScreenOpened()) - 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) + if (action.withAnalytics) { + sendAnalyticsScreenOpened(getState().step, action.newStep) + } + + + if (action.newStep == SaltPayActivationStep.ClaimSuccess || + action.newStep == SaltPayActivationStep.Success + ) { + 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 } - else -> {} + finishCardActivation( + getOnboardingWalletState().backupState, + onboardingManager.scanResponse.card, + ) } } else -> { @@ -284,6 +287,21 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } } +private fun sendAnalyticsScreenOpened(currentStep: SaltPayActivationStep, newStep: SaltPayActivationStep) { + if (currentStep == newStep) return + + when (newStep) { + SaltPayActivationStep.NeedPin -> Analytics.send(Onboarding.PinScreenOpened()) + SaltPayActivationStep.KycIntro -> Analytics.send(Onboarding.KYCScreenOpened()) + 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()) + else -> {} + } +} + suspend fun SaltPayActivationManager.update( currentStep: SaltPayActivationStep, currentAmountToClaim: Amount?, @@ -370,6 +388,7 @@ private suspend fun getAmountToClaimIfNeeded( return amountToClaim } + // return Amount(BigDecimal(0.1), Blockchain.SaltPay) return when (val result = saltPayManager.getAmountToClaim()) { is Result.Success -> { Timber.d("getAmountToClaimIfNeeded: success: %s", result.data) 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 84e012cad9..9ed2b65b03 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 @@ -80,6 +80,7 @@ internal class OnboardingSaltPayView( if (state.backupState.backupStep == BackupStep.Finished) { // if backup finished -> switch OnboardingWalletStep to the SaltPay store.dispatch(OnboardingWalletAction.GetToSaltPayStep) + store.dispatch(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess) } else { // if not -> back to the standard backup process walletFragment.handleOnboardingStep(state) @@ -228,7 +229,7 @@ internal class OnboardingSaltPayView( btnKycAction.text = getText(R.string.onboarding_button_kyc_waiting) btnKycAction.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.Update) + store.dispatch(OnboardingSaltPayAction.Update()) } progressButton = SaltPayProgressButton(root) } @@ -244,7 +245,7 @@ internal class OnboardingSaltPayView( btnOpenSupportChat.hide() btnKycAction.text = getText(R.string.onboarding_button_kyc_start) btnKycAction.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.Update) + store.dispatch(OnboardingSaltPayAction.Update()) } progressButton = SaltPayProgressButton(root) } From e7c38ab85b106a8cea0f58fd5a3fa5c9a9f56e5e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 09:33:47 +0300 Subject: [PATCH 33/46] Updated on 2026-08-14 --- .../paramsInterceptor/CardContextInterceptor.kt | 9 ++++++++- .../products/wallet/saltPay/GnosisRegistrator.kt | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index 99c231e1e4..e723fb4a9e 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -6,6 +6,8 @@ import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.IntroductionProcess +import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.features.demo.DemoHelper /** @@ -17,7 +19,12 @@ class CardContextInterceptor( override fun id(): String = CardContextInterceptor.id() - override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true + override fun canBeAppliedTo(event: AnalyticsEvent): Boolean { + return when (event) { + is IntroductionProcess.ButtonScanCard, is MainScreen.ButtonScanCard -> false + else -> true + } + } override fun intercept(params: MutableMap) { scanResponse ?: return diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index 8a6403aa27..85a64112cb 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -50,6 +50,7 @@ class GnosisRegistrator( private val atomicNonce = AtomicLong() suspend fun checkHasGas(): Result { + // return Result.Success(true) // test val wallet = walletManager.safeUpdate().successOr { return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error)) } @@ -148,6 +149,7 @@ class GnosisRegistrator( } suspend fun getAllowance(): Result { + // return Result.Success(Amount(BigDecimal(0.1), walletManager.wallet.blockchain)) // test return walletManager.getAllowance(addressTreasureSafe, token) } From cf3b99417a6349c1342f8b5d087f83ec2a325a35 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 12:09:19 +0300 Subject: [PATCH 34/46] Updated on 2026-08-14 --- .../tangem/tap/common/analytics/events/MainScreen.kt | 1 + .../tangem/tap/common/analytics/events/Onboarding.kt | 11 +++++++++-- .../wallet/saltPay/SaltPayExceptionHandler.kt | 11 ++++++++++- .../saltPay/redux/OnboardingSaltPayMiddleware.kt | 3 +-- .../tap/features/sprinklr/ui/SprinklrFragment.kt | 8 ++++++++ .../wallet/ui/wallet/saltPay/SaltPayWalletView.kt | 3 +++ 6 files changed, 32 insertions(+), 5 deletions(-) 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 9ce35483a4..6c8580df05 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,7 @@ sealed class MainScreen( class ButtonScanCard : MainScreen("Button - Scan Card") class ButtonMyWallets : MainScreen("Button - My Wallets") + class ButtonBuy : MainScreen("Button - Buy") class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen( event = "Enable Biometric", 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 1bf7b0bd0b..c0d61c52d1 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 @@ -69,16 +69,23 @@ sealed class Onboarding( class PinScreenOpened : Onboarding("Onboarding", "PIN screen opened") class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code") - class PinCodeSet : Onboarding("Onboarding", "PIN code set") + class CardConnectionScreenOpened : Onboarding("Onboarding", "Card Connection Screen Opened") class ButtonConnect : Onboarding("Onboarding", "Button - Connect") + class PinCodeSet : Onboarding("Onboarding", "PIN code set") + class KYCScreenOpened : Onboarding("Onboarding", "KYC screen opened") 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 ButtonChat: Onboarding("Onboarding", "Button - Chat") + + class ButtonChat : Onboarding("Onboarding", "Button - Chat") + + class NotEnoughGasError : Onboarding("Onboarding", "Not Enough Gas Error") + class CardNotPassedError : Onboarding("Onboarding", "Card Not Passed Error") class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding( category = "Onboarding / Biometric", diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt index 0f0b207431..e215cbfe92 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -2,6 +2,8 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.common.core.TangemSdkError +import com.tangem.core.analytics.Analytics +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog @@ -17,8 +19,15 @@ class SaltPayExceptionHandler { when (throwable) { is SaltPayActivationError -> { val dialog = when (throwable) { - is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas + is SaltPayActivationError.NoGas -> { + Analytics.send(Onboarding.NotEnoughGasError()) + SaltPayDialog.Activation.NoGas + } is SaltPayActivationError.PutVisaCard -> SaltPayDialog.Activation.PutVisaCard + is SaltPayActivationError.CardNotPassed -> { + Analytics.send(Onboarding.CardNotPassedError()) + SaltPayDialog.Activation.OnError(throwable) + } else -> SaltPayDialog.Activation.OnError(throwable) } store.dispatchDialogShow(dialog) 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 8ecae84673..33e9d7fa1b 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 @@ -264,8 +264,6 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App if (action.withAnalytics) { sendAnalyticsScreenOpened(getState().step, action.newStep) } - - if (action.newStep == SaltPayActivationStep.ClaimSuccess || action.newStep == SaltPayActivationStep.Success ) { @@ -292,6 +290,7 @@ private fun sendAnalyticsScreenOpened(currentStep: SaltPayActivationStep, newSte when (newStep) { SaltPayActivationStep.NeedPin -> Analytics.send(Onboarding.PinScreenOpened()) + SaltPayActivationStep.CardRegistration -> Analytics.send(Onboarding.CardConnectionScreenOpened()) SaltPayActivationStep.KycIntro -> Analytics.send(Onboarding.KYCScreenOpened()) SaltPayActivationStep.KycStart -> Analytics.send(Onboarding.KYCStarted()) SaltPayActivationStep.KycWaiting -> Analytics.send(Onboarding.KYCInProgress()) diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt index f029d4fa9b..3e7e7b824e 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt @@ -1,5 +1,6 @@ package com.tangem.tap.features.sprinklr.ui +import android.os.Bundle import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.systemBarsPadding @@ -8,11 +9,18 @@ import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels +import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.ComposeFragment +import com.tangem.tap.common.analytics.events.Chat internal class SprinklrFragment : ComposeFragment() { private val viewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Analytics.send(Chat.ScreenOpened()) + } + @Composable override fun provideState(): State { return viewModel.state.collectAsState() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt index b1d18042d6..8d43ff1316 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt @@ -3,10 +3,12 @@ package com.tangem.tap.features.wallet.ui.wallet.saltPay import android.view.LayoutInflater import android.view.ViewGroup import com.skydoves.androidveil.VeilLayout +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.extensions.debounce import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.ShimmerData import com.tangem.tap.common.ShimmerRecyclerAdapter +import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.extensions.animateVisibility import com.tangem.tap.common.extensions.formatAmountAsSpannedString import com.tangem.tap.common.extensions.hide @@ -144,6 +146,7 @@ class SaltPayWalletView : WalletView() { btnBuy.show(tokenData.isAvailableToBuy) btnBuy.setOnClickListener { + Analytics.send(MainScreen.ButtonBuy()) store.dispatch(WalletAction.TradeCryptoAction.Buy(false)) } } From 9709893da71cda30d40475a38a92106af3dd6ba1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 11:35:14 +0300 Subject: [PATCH 35/46] Updated on 2026-08-14 --- app/src/main/AndroidManifest.xml | 5 ++- .../com/tangem/tap/common/chat/ChatManager.kt | 2 +- .../implementation/SprinklrChatOpener.kt | 13 ++++-- .../tap/common/extensions/Navigation.kt | 2 - .../redux/navigation/NavigationState.kt | 1 - .../sprinklr/redux/model/SprinklrUrl.kt | 4 +- ...prinklrFragment.kt => SprinklrActivity.kt} | 9 ++-- .../features/sprinklr/ui/SprinklrViewModel.kt | 13 +++--- .../core/ui/fragments/ComposeActivity.kt | 11 +++++ .../fragments/ComposeBottomSheetFragment.kt | 15 +------ .../core/ui/fragments/ComposeFragment.kt | 42 +---------------- .../tangem/core/ui/fragments/ComposeScreen.kt | 45 +++++++++++++++++++ 12 files changed, 89 insertions(+), 73 deletions(-) rename app/src/main/java/com/tangem/tap/features/sprinklr/ui/{SprinklrFragment.kt => SprinklrActivity.kt} (83%) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeActivity.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeScreen.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f09883d42e..0680d8b5e1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,9 +138,12 @@ - + + String) { val opener = openers.getOrPut(config) { when (config) { - is SprinklrConfig -> SprinklrChatOpener(getSprinklrUserId(), config, store) + is SprinklrConfig -> SprinklrChatOpener(getSprinklrUserId(), config, store, foregroundActivityObserver) is ZendeskConfig -> ZendeskChatOpener(getZendeskUserId(), config, foregroundActivityObserver) } } diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt index e7be1411f6..d5923097f4 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt @@ -1,22 +1,27 @@ package com.tangem.tap.common.chat.opener.implementation import android.content.Context +import android.content.Intent +import com.tangem.tap.ForegroundActivityObserver import com.tangem.tap.common.chat.SprinklrConfig import com.tangem.tap.common.chat.opener.ChatOpener -import com.tangem.tap.common.extensions.dispatchOnMain 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.features.sprinklr.redux.SprinklrAction +import com.tangem.tap.features.sprinklr.ui.SprinklrActivity +import com.tangem.tap.withForegroundActivity import org.rekotlin.Store internal class SprinklrChatOpener( private val userId: String, private val config: SprinklrConfig, private val store: Store, + private val foregroundActivityObserver: ForegroundActivityObserver, ) : ChatOpener { override fun open(feedbackDataBuilder: (Context) -> String) { store.dispatch(SprinklrAction.Init(userId, config)) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Sprinklr)) + foregroundActivityObserver.withForegroundActivity { activity -> + val intent = Intent(activity, SprinklrActivity::class.java) + activity.startActivity(intent) + } } } \ No newline at end of file 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 21dd970381..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 @@ -25,7 +25,6 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFra 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.sprinklr.ui.SprinklrFragment import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment import com.tangem.tap.features.tokens.ui.AddTokensFragment import com.tangem.tap.features.wallet.ui.WalletDetailsFragment @@ -121,6 +120,5 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.Welcome -> WelcomeFragment() AppScreen.SaveWallet -> SaveWalletBottomSheetFragment() AppScreen.WalletSelector -> WalletSelectorBottomSheetFragment() - AppScreen.Sprinklr -> SprinklrFragment() } } \ 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 3f313ef210..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 @@ -26,5 +26,4 @@ enum class AppScreen( Welcome, SaveWallet(isDialogFragment = true), WalletSelector(isDialogFragment = true), - Sprinklr, } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt index 2a76172029..2d1eb1be87 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt @@ -6,8 +6,8 @@ internal sealed class SprinklrUrl { class Prod(userId: String, baseUrl: String, appId: String) : SprinklrUrl() { private val locale = java.util.Locale.getDefault().language - override val url: String = "$baseUrl/page?" + - "appId=$appId" + + override val url: String = "$baseUrl/page" + + "?appId=$appId" + "&device=$Device" + "&enableClose=$CloseEnabled" + "&zoom=$ZoomEnabled" + diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrActivity.kt similarity index 83% rename from app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt rename to app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrActivity.kt index 3e7e7b824e..28b3053441 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrActivity.kt @@ -2,23 +2,26 @@ package com.tangem.tap.features.sprinklr.ui import android.os.Bundle import androidx.activity.compose.BackHandler +import androidx.activity.viewModels import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier -import androidx.fragment.app.viewModels import com.tangem.core.analytics.Analytics -import com.tangem.core.ui.fragments.ComposeFragment +import com.tangem.core.ui.fragments.ComposeActivity import com.tangem.tap.common.analytics.events.Chat -internal class SprinklrFragment : ComposeFragment() { +internal class SprinklrActivity : ComposeActivity() { private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Analytics.send(Chat.ScreenOpened()) + viewModel.setNavigateBackCallback { + this.finish() + } } @Composable diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt index 1c23eef584..989b813d19 100644 --- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt @@ -24,7 +24,6 @@ internal class SprinklrViewModel : ViewModel(), StoreSubscriber { prevState.copy( initialUrl = state.url, sprinklrDomains = state.sprinklrDomains, - onNavigateBack = this::navigateBack, onNewUrl = { updateWebContentOrOpenExternalUrl(it) }, ) } @@ -34,6 +33,14 @@ internal class SprinklrViewModel : ViewModel(), StoreSubscriber { store.unsubscribe(this) } + fun setNavigateBackCallback(callback: () -> Unit) { + stateInternal.update { prevState -> + prevState.copy( + onNavigateBack = callback, + ) + } + } + private fun subscribeToStoreChanges() { store.subscribe(this) { appState -> appState.skip { old, new -> old.sprinklrState == new.sprinklrState } @@ -41,10 +48,6 @@ internal class SprinklrViewModel : ViewModel(), StoreSubscriber { } } - private fun navigateBack() { - store.dispatchOnMain(NavigationAction.PopBackTo()) - } - private fun WebContent.updateWebContentOrOpenExternalUrl(url: String): WebContent { return if (isExternalUrl(url)) { store.dispatchOnMain(NavigationAction.OpenUrl(url)) diff --git a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeActivity.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeActivity.kt new file mode 100644 index 0000000000..b14a8f3c9d --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeActivity.kt @@ -0,0 +1,11 @@ +package com.tangem.core.ui.fragments + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity + +abstract class ComposeActivity : AppCompatActivity(), ComposeScreen { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(createComposeView(context = this)) + } +} \ 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 index 35dab6bca0..3161add9a8 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 @@ -10,8 +10,6 @@ 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 @@ -20,7 +18,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme -abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment() { +abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment(), ComposeScreen { open val initialBottomSheetState = BottomSheetBehavior.STATE_EXPANDED @FloatRange(from = 0.0, to = 1.0) @@ -47,7 +45,7 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme return dialog } - private fun createComposeView(context: Context): View { + override fun createComposeView(context: Context): View { return ComposeView(context).apply { setContent { TangemTheme { @@ -67,13 +65,4 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme } } } - - @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 index 863f22f185..d7ab04a268 100644 --- 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 @@ -1,21 +1,12 @@ 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() { +abstract class ComposeFragment : Fragment(), ComposeScreen { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -23,35 +14,4 @@ abstract class ComposeFragment : Fragment() { ): 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/java/com/tangem/core/ui/fragments/ComposeScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeScreen.kt new file mode 100644 index 0000000000..8404a4867d --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeScreen.kt @@ -0,0 +1,45 @@ +package com.tangem.core.ui.fragments + +import android.content.Context +import android.view.View +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 com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemTheme + +internal interface ComposeScreen { + 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 + fun provideState(): State + + @Composable + fun ScreenContent( + modifier: Modifier, + state: ScreenState, + ) +} \ No newline at end of file From 93ae0d85225657657187981e5be890cf22723c9f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 13:26:30 +0300 Subject: [PATCH 36/46] Updated on 2026-08-14 --- .../tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt index 0aa75abfba..f90b73edb9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt @@ -37,7 +37,7 @@ data class HistoryTransactionData( } ?: "" val time: String = transactionData.date?.let { - SimpleDateFormat("hh:MM").format(it.time) + SimpleDateFormat("HH:mm").format(it.time) } ?: "00:00" val date: String = calculateDate() From d7603c648e38b052239957de6bf0a5866978c1cf Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 13:42:17 +0300 Subject: [PATCH 37/46] Updated on 2026-08-14 --- .../wallet/ui/wallet/saltPay/rv/HistoryItemData.kt | 12 ++++++++---- .../wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt index f90b73edb9..763f6b532e 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt @@ -16,7 +16,7 @@ sealed class HistoryItemData( val itemId: Long, ) { data class Date(val date: String) : HistoryItemData(0, date.hashCode().toLong()) - data class TransactionData(val data: HistoryTransactionData) : HistoryItemData(1, data.hash.hashCode().toLong()) + data class TransactionData(val data: HistoryTransactionData) : HistoryItemData(1, data.address.hashCode().toLong()) } data class HistoryTransactionData( @@ -32,9 +32,11 @@ data class HistoryTransactionData( else -> PendingTransactionType.Unknown } - val hash: String = transactionData.hash?.let { - "${it.substring(0..5)}...${it.substring(it.length - 4)}" - } ?: "" + val address: String = when (transactionType) { + PendingTransactionType.Incoming -> transactionData.sourceAddress.reduceAddress() + PendingTransactionType.Outgoing -> transactionData.destinationAddress.reduceAddress() + PendingTransactionType.Unknown -> "Unknown address" + } val time: String = transactionData.date?.let { SimpleDateFormat("HH:mm").format(it.time) @@ -71,4 +73,6 @@ data class HistoryTransactionData( return date } + + private fun String.reduceAddress(): String = "${substring(0..5)}...${substring(length - 4)}" } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt index 60e9d1dfd7..5ac81ac30a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt @@ -96,7 +96,7 @@ private class TransactionItemVH( } private fun setupTitle(data: HistoryTransactionData) = with(binding) { - tvTxHash.text = data.hash + tvTxHash.text = data.address } private fun setupSubtitle(data: HistoryTransactionData) = with(binding) { From f2e791b9602904d355ea9c3abf9ab976e522d935 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 13:52:49 +0300 Subject: [PATCH 38/46] Updated on 2026-08-14 --- .../products/wallet/saltPay/ui/OnboardingSaltPayView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9ed2b65b03..812151698a 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 @@ -100,7 +100,7 @@ internal class OnboardingSaltPayView( } else { walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) } - imvFirstBackupCard.load(R.drawable.img_salt_pay_visa) + imvFirstBackupCard.load(R.drawable.card_placeholder_wallet) // if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { // //if saltPay url not loaded -> load from resource From e4925ef8ee53c75c1fb77ef4a819a145cb97aaaf Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 21 Feb 2023 17:29:11 +0300 Subject: [PATCH 39/46] Updated on 2026-08-14 --- .../converters/BasicEventConverter.kt | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) 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 1eeda46c6f..dd7c4f4032 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 @@ -111,19 +111,53 @@ data class BasicEventsSourceData( val paramCardBalanceState: AnalyticsParam.CardBalanceState by lazy { calculateAmount().toCardBalanceState() } private fun calculateAmount(): BigDecimal { - return biometricsWalletDataModels?.calculateTotalCryptoAmount() - ?: walletState.walletsDataFromStores.calculateTotalCryptoAmount() + val calculator: IBalanceCalculator = biometricsWalletDataModels + ?.let { BiometricsBalanceCalculator(it) } + ?: BalanceCalculator(walletState) + + return calculator.calculate() } private fun BigDecimal.toCardBalanceState(): AnalyticsParam.CardBalanceState = when { isZero() -> AnalyticsParam.CardBalanceState.Empty else -> AnalyticsParam.CardBalanceState.Full } +} - private fun List.calculateTotalCryptoAmount(): BigDecimal { - return this - .map { it.status.amount } - .reduce(BigDecimal::plus) +private interface IBalanceCalculator { + fun calculate(): BigDecimal +} + +private class BiometricsBalanceCalculator( + private val walletDataModel: List, +) : IBalanceCalculator { + + override fun calculate(): BigDecimal { + val singleToken = walletDataModel + .filter { it.currency.isToken() } + .firstOrNull { it.isCardSingleToken } + + val totalAmount = singleToken?.status?.amount + ?: walletDataModel.calculateTotalCryptoAmount() + + return totalAmount + } + + private fun List.calculateTotalCryptoAmount(): BigDecimal = this + .map { it.status.amount } + .reduce(BigDecimal::plus) +} + +private class BalanceCalculator( + private val walletState: WalletState, +) : IBalanceCalculator { + + override fun calculate(): BigDecimal { + val singleTokenData = walletState.primaryTokenData + val totalAmount = singleTokenData?.currencyData?.amount + ?: walletState.walletsDataFromStores.calculateTotalCryptoAmount() + + return totalAmount } } From cd7899ba3eb8c125ed9a30771b8705d057358324 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 22 Feb 2023 17:03:03 +0300 Subject: [PATCH 40/46] Updated on 2026-08-14 --- .../tangem/tap/features/details/redux/DetailsAction.kt | 2 -- .../tap/features/details/redux/DetailsMiddleware.kt | 8 +------- .../tap/features/details/ui/details/DetailsViewModel.kt | 6 ++---- 3 files changed, 3 insertions(+), 13 deletions(-) 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 5447faf5b0..0774dc4662 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 @@ -23,8 +23,6 @@ sealed class DetailsAction : Action { object Success : ResetToFactory() } - object CreateBackup : DetailsAction() - object ScanCard : DetailsAction() data class PrepareCardSettingsData(val card: CardDTO) : 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 8e9b0ccbb7..13e7b97c3d 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 @@ -5,8 +5,8 @@ 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.core.analytics.Analytics +import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.dispatchDialogShow @@ -65,12 +65,6 @@ class DetailsMiddleware { store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet)) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins)) } - is DetailsAction.CreateBackup -> { - state.scanResponse?.let { - store.dispatch(GlobalAction.Onboarding.Start(it, canSkipBackup = false)) - store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) - } - } DetailsAction.ScanCard -> { scope.launch { tangemSdkManager.scanProduct(userTokensRepository) 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 4c75feefe3..14980799f1 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 @@ -1,8 +1,7 @@ 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.core.analytics.Analytics +import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.feedback.FeedbackEmail import com.tangem.tap.common.feedback.SupportInfo @@ -11,7 +10,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.extensions.isMultiwalletAllowed -import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.home.LocaleRegionProvider @@ -85,7 +83,7 @@ class DetailsViewModel(private val store: Store) { } SettingsElement.LinkMoreCards -> { Analytics.send(Settings.ButtonCreateBackup()) - store.dispatch(DetailsAction.CreateBackup) + store.dispatch(WalletAction.MultiWallet.BackupWallet) } SettingsElement.TermsOfService -> { store.dispatch(DisclaimerAction.Show(AppScreen.Details)) From b119c29d09eebb0f05a64142da9ce20df11c81b3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 22 Feb 2023 17:18:44 +0300 Subject: [PATCH 41/46] Updated on 2026-08-14 --- .../LinkedCardContextInterceptor.kt | 26 +++++++++++++ .../tangem/tap/common/extensions/Analytics.kt | 37 ++++++++++++++++--- .../com/tangem/tap/domain/TapWalletManager.kt | 3 ++ .../tap/domain/scanCard/ScanCardProcessor.kt | 19 ++++++++-- .../tap/features/home/redux/HomeMiddleware.kt | 4 +- .../features/onboarding/OnboardingHelper.kt | 10 ++++- .../products/note/OnboardingNoteFragment.kt | 5 +++ .../note/redux/OnboardingNoteAction.kt | 1 + .../note/redux/OnboardingNoteMiddleware.kt | 5 ++- .../OnboardingOtherCardsFragment.kt | 5 +++ .../redux/OnboardingOtherCardsAction.kt | 1 + .../redux/OnboardingOtherCardsMiddleware.kt | 14 +++---- .../products/twins/redux/TwinCardsAction.kt | 4 +- .../twins/redux/TwinCardsMiddleware.kt | 7 ++-- .../products/twins/ui/TwinsCardsFragment.kt | 4 +- .../redux/OnboardingWalletMiddleware.kt | 2 + .../middlewares/MultiWalletMiddleware.kt | 2 + .../redux/WalletSelectorMiddleware.kt | 3 -- .../welcome/redux/WelcomeMiddleware.kt | 6 +-- 19 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/LinkedCardContextInterceptor.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/LinkedCardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/LinkedCardContextInterceptor.kt new file mode 100644 index 0000000000..9dcfd80714 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/LinkedCardContextInterceptor.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.common.analytics.paramsInterceptor + +import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.api.ParamsInterceptor +import com.tangem.domain.common.ScanResponse + +/** +[REDACTED_AUTHOR] + */ +class LinkedCardContextInterceptor( + private val scanResponse: ScanResponse, + val parent: LinkedCardContextInterceptor? = null, +) : ParamsInterceptor { + + private val contextInterceptor = CardContextInterceptor(scanResponse) + + override fun id(): String = LinkedCardContextInterceptor.id() + + override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = contextInterceptor.canBeAppliedTo(event) + + override fun intercept(params: MutableMap) = contextInterceptor.intercept(params) + + companion object { + fun id(): String = LinkedCardContextInterceptor::class.java.simpleName + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt index 3772f86a71..8d3ce8e5ad 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt @@ -2,15 +2,42 @@ package com.tangem.tap.common.extensions import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse -import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor +import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor /** [REDACTED_AUTHOR] */ -fun Analytics.addCardContext(scanResponse: ScanResponse) { - addParamsInterceptor(CardContextInterceptor(scanResponse)) + +/** + * Sets the new context + */ +fun Analytics.setContext(scanResponse: ScanResponse) { + addParamsInterceptor(LinkedCardContextInterceptor(scanResponse)) } -fun Analytics.eraseCardContext() { - removeParamsInterceptor(CardContextInterceptor.id()) +/** + * Erases the context + */ +fun Analytics.eraseContext() { + removeParamsInterceptor(LinkedCardContextInterceptor.id()) +} + +/** + * Adds a new context and keeps a previous context as the parent of the new one + */ +fun Analytics.addContext(scanResponse: ScanResponse) { + val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor + val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext) + + addParamsInterceptor(newContext) +} + +/** + * Removes the current context and restores the previous one if it was present. + */ +fun Analytics.removeContext() { + val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor + val previousContext = currentContext?.parent ?: return + + addParamsInterceptor(previousContext) } \ 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 df19afc1c4..1800235d68 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -10,6 +10,7 @@ import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isStart2Coin @@ -19,6 +20,7 @@ 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.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.extensions.isMultiwalletAllowed @@ -94,6 +96,7 @@ class TapWalletManager { } suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean) { + Analytics.setContext(userWallet.scanResponse) val scanResponse = userWallet.scanResponse val card = scanResponse.card val attestationFailed = card.attestation.status == Attestation.Status.Failed 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 3317a70315..46a53c4fec 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 @@ -13,9 +13,11 @@ 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.extensions.addCardContext +import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor +import com.tangem.tap.common.extensions.addContext import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa +import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -105,8 +107,15 @@ object ScanCardProcessor { analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse, ) { - Analytics.addCardContext(scanResponse) - analyticsEvent?.let { Analytics.send(it) } + analyticsEvent?.let { + // this workaround needed to send CardWasScannedEvent without adding a context + val interceptor = CardContextInterceptor(scanResponse) + val params = it.params.toMutableMap() + interceptor.intercept(params) + it.params = params.toMap() + + Analytics.send(it) + } } /** @@ -196,6 +205,7 @@ object ScanCardProcessor { withMainContext { when (result) { is Result.Success -> { + Analytics.addContext(scanResponse) val isOnboardingCase = result.data if (isOnboardingCase) { onWalletNotCreated() @@ -222,16 +232,19 @@ object ScanCardProcessor { SaltPayExceptionHandler.handle(error) onFailure(TangemSdkError.ExceptionError(error)) } else { + Analytics.setContext(scanResponse) onSuccess(scanResponse) } } } else { if (OnboardingHelper.isOnboardingCase(scanResponse)) { + Analytics.addContext(scanResponse) onWalletNotCreated() store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) val appScreen = OnboardingHelper.whereToNavigate(scanResponse) navigateTo(appScreen) { onProgressStateChange(it) } } else { + Analytics.setContext(scanResponse) if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { onWalletNotCreated() store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) 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 afcb5c7a14..bfb9dec130 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,7 +10,7 @@ import com.tangem.tap.common.analytics.events.Shop import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl -import com.tangem.tap.common.extensions.eraseCardContext +import com.tangem.tap.common.extensions.eraseContext import com.tangem.tap.common.extensions.onCardScanned import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState @@ -54,7 +54,7 @@ private val homeMiddleware: Middleware = { _, _ -> private fun handleHomeAction(action: Action) { when (action) { is HomeAction.OnCreate -> { - Analytics.eraseCardContext() + Analytics.eraseContext() Analytics.send(IntroductionProcess.ScreenOpened()) } is HomeAction.Init -> { 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 cb01879840..253c05232f 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,9 +1,12 @@ package com.tangem.tap.features.onboarding +import com.tangem.core.analytics.Analytics 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.removeContext +import com.tangem.tap.common.extensions.setContext import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.saveWallet.redux.SaveWalletAction @@ -47,7 +50,7 @@ class OnboardingHelper { ProductType.Twins -> AppScreen.OnboardingTwins ProductType.SaltPay -> AppScreen.OnboardingWallet ProductType.Start2Coin -> throw java.lang.UnsupportedOperationException( - "Onboarding for Start2Coin cards is not supported" + "Onboarding for Start2Coin cards is not supported", ) } } @@ -57,6 +60,7 @@ class OnboardingHelper { accessCode: String? = null, backupCardsIds: List? = null, ) { + Analytics.setContext(scanResponse) when { // When should save user wallets, then save card without navigate to save wallet screen preferencesStorage.shouldSaveUserWallets -> scope.launch { @@ -94,5 +98,9 @@ class OnboardingHelper { store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) } + + fun onInterrupted() { + Analytics.removeContext() + } } } \ 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 c4b34eba62..6ca9b0c26a 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 @@ -210,4 +210,9 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition) } } + + override fun handleOnBackPressed() { + store.dispatch(OnboardingNoteAction.OnBackPressed) + super.handleOnBackPressed() + } } \ No newline at end of file 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 8e7046f114..337aaada28 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 @@ -21,6 +21,7 @@ sealed class OnboardingNoteAction : Action { data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction() object DetermineStepOfScreen : OnboardingNoteAction() object Done : OnboardingNoteAction() + object OnBackPressed : OnboardingNoteAction() data class SetStepOfScreen(val step: OnboardingNoteStep) : 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 c4f112770f..aa64534889 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 @@ -2,9 +2,9 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.core.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 @@ -190,5 +190,8 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch store.dispatch(GlobalAction.Onboarding.Stop) OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse) } + OnboardingNoteAction.OnBackPressed -> { + OnboardingHelper.onInterrupted() + } } } \ No newline at end of file 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 5439b76612..c974a92b72 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 @@ -118,4 +118,9 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment { -// do nothing - } + is CompletionResult.Failure -> Unit } } } @@ -130,9 +128,9 @@ private fun handleOtherCardsAction(action: Action) { store.dispatch(GlobalAction.Onboarding.Stop) OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(onboardingManager.scanResponse) } - is OnboardingOtherCardsAction.Confetti.Hide, - is OnboardingOtherCardsAction.SetArtworkUrl, - is OnboardingOtherCardsAction.Confetti.Show, - -> Unit + OnboardingOtherCardsAction.OnBackPressed -> { + OnboardingHelper.onInterrupted() + } + else -> Unit } } \ No newline at end of file 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 8b25bf8e00..c6f9dcea27 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 @@ -28,9 +28,9 @@ sealed class TwinCardsAction : Action { } sealed class Wallet : TwinCardsAction() { - data class HandleOnBackPressed( + data class OnBackPressed( // this is necessary for the correct animation of the return of cards to the Home screen - val shouldResetTwinCardsWidget: (should: Boolean, popAction: VoidCallback) -> Unit + val shouldResetTwinCardsWidget: (should: Boolean, popAction: VoidCallback) -> Unit, ) : TwinCardsAction() data class ShowInterruptDialog(val onOk: VoidCallback) : TwinCardsAction(), StateDialog 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 6c913f0b1e..5626ea170a 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 @@ -3,10 +3,10 @@ package com.tangem.tap.features.onboarding.products.twins.redux import com.tangem.blockchain.extensions.Result import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.core.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 @@ -26,8 +26,8 @@ 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.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 @@ -121,7 +121,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } } } - is TwinCardsAction.Wallet.HandleOnBackPressed -> { + is TwinCardsAction.Wallet.OnBackPressed -> { val shouldReturnCardBack = twinCardsState.mode == CreateTwinWalletMode.CreateWallet && twinCardsState.currentStep != TwinCardsStep.TopUpWallet && twinCardsState.currentStep != TwinCardsStep.Done @@ -136,6 +136,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { val stateDialog = TwinCardsAction.Wallet.ShowInterruptDialog(onInterruptPrompt) store.dispatchDialogShow(stateDialog) } else { + OnboardingHelper.onInterrupted() store.dispatch(TwinCardsAction.CardsManager.Release) action.shouldResetTwinCardsWidget(shouldReturnCardBack) { store.dispatch(NavigationAction.PopBackTo()) 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 9d7b99a2a6..2dae95f066 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,12 +13,12 @@ import coil.load import com.tangem.Message import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback +import com.tangem.core.analytics.Analytics 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 import com.tangem.tap.common.AndroidAssetReader -import com.tangem.core.analytics.Analytics import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.getDrawableCompat @@ -401,7 +401,7 @@ class TwinsCardsFragment : BaseOnboardingFragment() { override fun handleOnBackPressed() { store.dispatch( - TwinCardsAction.Wallet.HandleOnBackPressed { should, popAction -> + TwinCardsAction.Wallet.OnBackPressed { should, popAction -> store.dispatch(TwinCardsAction.Confetti.Hide) showConfetti(false) if (should) switchToCard(TwinCardNumber.First, true, popAction) 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 3397e56980..83493c41b6 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 @@ -426,6 +426,7 @@ private fun handleOnBackPressed(state: OnboardingWalletState) { if (state.isSaltPay) { showInterruptOnboardingDialog() } else { + OnboardingHelper.onInterrupted() store.dispatch(NavigationAction.PopBackTo()) } } @@ -436,6 +437,7 @@ private fun showInterruptOnboardingDialog() { store.dispatchDialogShow( OnboardingDialog.InterruptOnboarding( onOk = { + OnboardingHelper.onInterrupted() store.dispatch(BackupAction.DiscardBackup) store.dispatch(NavigationAction.PopBackTo()) }, 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 e6798ea6ca..4f56266142 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 @@ -9,6 +9,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken +import com.tangem.tap.common.extensions.addContext import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain @@ -163,6 +164,7 @@ class MultiWalletMiddleware { is WalletAction.MultiWallet.ShowWalletBackupWarning -> Unit is WalletAction.MultiWallet.BackupWallet -> { store.state.globalState.scanResponse?.let { + Analytics.addContext(it) store.dispatch(GlobalAction.Onboarding.Start(it, canSkipBackup = false)) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } 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 1a98a9c93d..505bfc2629 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,7 +12,6 @@ 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.events.MyWallets -import com.tangem.tap.common.extensions.addCardContext import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState @@ -199,8 +198,6 @@ internal class WalletSelectorMiddleware { .doOnSuccess { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { - Analytics.addCardContext(selectedUserWallet.scanResponse) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) store.onUserWalletSelected(selectedUserWallet) } 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 76c0a2319e..166796287b 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 @@ -9,9 +9,8 @@ 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.events.SignIn -import com.tangem.tap.common.extensions.addCardContext import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.eraseCardContext +import com.tangem.tap.common.extensions.eraseContext import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen @@ -44,7 +43,7 @@ internal class WelcomeMiddleware { private fun handleAction(action: WelcomeAction, state: WelcomeState) { when (action) { is WelcomeAction.OnCreate -> { - Analytics.eraseCardContext() + Analytics.eraseContext() Analytics.send(SignIn.ScreenOpened()) } is WelcomeAction.ProceedWithBiometrics -> { @@ -71,7 +70,6 @@ internal class WelcomeMiddleware { } .doOnSuccess { selectedUserWallet -> if (selectedUserWallet != null) { - Analytics.addCardContext(selectedUserWallet.scanResponse) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success) store.onUserWalletSelected(selectedUserWallet) From a677bda4ef007ae28a9a411c2a9716a97c6a4489 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 22 Feb 2023 18:58:47 +0300 Subject: [PATCH 42/46] Updated on 2026-08-14 --- .../wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 33e9d7fa1b..f1f8ec73c1 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 @@ -152,8 +152,15 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App } Analytics.send(Onboarding.PinCodeSet()) + + var nextStep = SaltPayActivationStep.KycIntro + nextStep = saltPayManager.update(nextStep, state.amountToClaim).successOr { + onException(it.error) + return@launch + } + handleInProgress = false - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycIntro)) + dispatchOnMain(OnboardingSaltPayAction.SetStep(nextStep)) } } is OnboardingSaltPayAction.OpenUtorgKYC -> { From d256373ae9e5d9bbd9acc522bf68d59cbcbe3f0c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Feb 2023 13:45:01 +0300 Subject: [PATCH 43/46] Updated on 2026-08-14 --- .../products/wallet/redux/OnboardingWalletMiddleware.kt | 7 ++++--- .../products/wallet/redux/OnboardingWalletReducer.kt | 1 + .../products/wallet/redux/OnboardingWalletState.kt | 1 + .../products/wallet/saltPay/ui/OnboardingSaltPayView.kt | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) 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 83493c41b6..865ec51694 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 @@ -398,11 +398,12 @@ private fun finishCardsActivationForDiscardedUnfinishedBackup(cardId: String) { */ private fun initSaltPayOnBackupFinishedIfNeeded( scanResponse: ScanResponse?, - onboardingWalletState: OnboardingWalletState, + state: OnboardingWalletState, ) { - if (onboardingWalletState.isSaltPay && onboardingWalletState.onboardingSaltPayState == null) { - if (scanResponse == null) throw IllegalArgumentException() + if (state.backupState.isInterruptedBackup) return + if (scanResponse == null) return + if (state.isSaltPay && state.onboardingSaltPayState == null) { val manager = SaltPayActivationManagerFactory( blockchain = scanResponse.getBlockchain(), card = scanResponse.card, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index cb5242af39..12834e7a77 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -54,6 +54,7 @@ private class ReducerForGlobalAction { backupState = state.backupState.copy( maxBackupCards = maxBackupCards(action.isSaltPayVisa), canSkipBackup = false, + isInterruptedBackup = true, ), isSaltPay = action.isSaltPayVisa, ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 314d4ea06b..a67bc426db 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -85,6 +85,7 @@ data class BackupState( val backupStep: BackupStep = BackupStep.InitBackup, val maxBackupCards: Int = 2, val canSkipBackup: Boolean = true, + val isInterruptedBackup: Boolean = false, ) enum class AccessCodeError { 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 812151698a..4cf4ae66d3 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 @@ -77,7 +77,7 @@ internal class OnboardingSaltPayView( when (state.step) { OnboardingWalletStep.SaltPay -> setSaltPayStep(state.onboardingSaltPayState) OnboardingWalletStep.Backup -> { - if (state.backupState.backupStep == BackupStep.Finished) { + if (state.backupState.backupStep == BackupStep.Finished && !state.backupState.isInterruptedBackup) { // if backup finished -> switch OnboardingWalletStep to the SaltPay store.dispatch(OnboardingWalletAction.GetToSaltPayStep) store.dispatch(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess) @@ -100,7 +100,7 @@ internal class OnboardingSaltPayView( } else { walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) } - imvFirstBackupCard.load(R.drawable.card_placeholder_wallet) + imvFirstBackupCard.load(R.drawable.img_salt_pay_visa) // if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { // //if saltPay url not loaded -> load from resource From 0b2843b2424a6f083183a6c2f4cf06a503e8984c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Feb 2023 16:15:05 +0300 Subject: [PATCH 44/46] Updated on 2026-08-14 --- .../products/wallet/saltPay/ui/OnboardingSaltPayView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4cf4ae66d3..f26d57a2a4 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 @@ -100,7 +100,7 @@ internal class OnboardingSaltPayView( } else { walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) } - imvFirstBackupCard.load(R.drawable.img_salt_pay_visa) + imvFirstBackupCard.load(R.drawable.card_placeholder_wallet) // if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { // //if saltPay url not loaded -> load from resource From 90c8ac23b4e5ed7bce529b57cfea0fdc97e8a10d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Feb 2023 20:47:07 +0300 Subject: [PATCH 45/46] 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 73387ff687..b99876e36a 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -62,7 +62,7 @@ object Versions { const val tangemBlockchainSdk = "develop-152-hotfix-4.0.1-4.0.2-162" // const val tangemBlockchainSdk = "0.0.1" - const val tangemCardSdk = "develop-179-hotfix-4.0.1-189" + const val tangemCardSdk = "develop-179-hotfix-4.0.1-4.0.2-193" // endregion Tangem // region Testing From f0e07c4dcd02a7bc742b05457151b0a187af3ac9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Feb 2023 11:15:48 +0300 Subject: [PATCH 46/46] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 3 +- .../tap/common/ShimmerRecyclerAdapter.kt | 1 + .../details/ui/details/DetailsViewModel.kt | 2 - .../onboarding/OnboardingSaltPayHelper.kt | 60 +++++++++---------- .../wallet/saltPay/GnosisRegistrator.kt | 1 + .../saltPay/SaltPayActivationManager.kt | 15 ++++- .../wallet/saltPay/SaltPayExceptionHandler.kt | 2 +- .../ui/SaveWalletBottomSheetFragment.kt | 2 +- .../features/sprinklr/ui/SprinklrActivity.kt | 5 +- .../compose/AddCustomTokenScreen.kt | 3 +- .../tap/features/wallet/redux/WalletState.kt | 1 - .../tap/features/wallet/ui/WalletFragment.kt | 3 +- .../ui/wallet/saltPay/SaltPayWalletView.kt | 6 -- .../ui/wallet/saltPay/rv/HistoryItemData.kt | 1 + .../saltPay/rv/ShimmerRecyclerAdapter.kt | 33 ---------- .../ui/wallet/saltPay/rv/TxHistoryAdapter.kt | 7 ++- .../ui/WalletSelectorBottomSheetFragment.kt | 2 +- .../features/welcome/ui/WelcomeFragment.kt | 2 +- .../fragments/ComposeBottomSheetFragment.kt | 2 +- .../tangem/core/ui/fragments/ComposeScreen.kt | 6 +- .../tangem/domain/common/SaltPayWorkaround.kt | 1 + 21 files changed, 69 insertions(+), 89 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 337849cad3..56b14e2732 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -12,9 +12,9 @@ import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder import com.tangem.core.analytics.Analytics import com.tangem.datasource.api.common.MoshiConverter -import com.tangem.datasource.config.models.Config import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.FeaturesLocalLoader +import com.tangem.datasource.config.models.Config import com.tangem.datasource.utils.AndroidAssetReader import com.tangem.datasource.utils.AssetReader import com.tangem.domain.DomainLayer @@ -152,7 +152,6 @@ class TapApplication : Application(), ImageLoaderFactory { ) } - userTokensRepository = UserTokensRepository.init( context = this, tangemTechService = store.state.domainNetworks.tangemTechService, diff --git a/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt b/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt index 4d56172588..ff09e08de7 100644 --- a/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt +++ b/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt @@ -30,4 +30,5 @@ open class ShimmerRecyclerAdapter( class ShimmerVH(viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup) +@Suppress("UnusedPrivateMember") data class ShimmerData(private val any: String = "") \ 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 c67be8b92e..051a9148f2 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 @@ -1,7 +1,6 @@ package com.tangem.tap.features.details.ui.details import com.tangem.core.analytics.Analytics -import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.feedback.FeedbackEmail import com.tangem.tap.common.feedback.SupportInfo @@ -9,7 +8,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.features.details.redux.DetailsAction import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.home.LocaleRegionProvider diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt index 8c88cc7a84..5a6132c72a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt @@ -12,39 +12,39 @@ import com.tangem.tap.preferencesStorage /** [REDACTED_AUTHOR] */ -class OnboardingSaltPayHelper { - companion object { +object OnboardingSaltPayHelper { - suspend fun isOnboardingCase( - scanResponse: ScanResponse, - manager: SaltPayActivationManager, - ): Result { - return try { - val updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } + suspend fun isOnboardingCase( + scanResponse: ScanResponse, + manager: SaltPayActivationManager, + ): Result { + return try { + val updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } - val cardStorage = preferencesStorage.usedCardsPrefStorage - val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId) - if (updatedStep == SaltPayActivationStep.Success && !activationIsFinished) { - cardStorage.activationFinished(scanResponse.card.cardId) - } - val isActivationCase = updatedStep != SaltPayActivationStep.Success - val isBackupCase = scanResponse.card.backupStatus?.isActive == false - Result.Success(isActivationCase || isBackupCase) - } catch (ex: Exception) { - Result.Failure(ex) - } catch (error: SaltPayActivationError) { - Result.Failure(error) + val cardStorage = preferencesStorage.usedCardsPrefStorage + val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId) + if (updatedStep == SaltPayActivationStep.Success && !activationIsFinished) { + cardStorage.activationFinished(scanResponse.card.cardId) } + val isActivationCase = updatedStep != SaltPayActivationStep.Success + val isBackupCase = scanResponse.card.backupStatus?.isActive == false + Result.Success(isActivationCase || isBackupCase) + } catch (ex: Exception) { + Result.Failure(ex) + } catch (error: SaltPayActivationError) { + Result.Failure(error) } - - fun testProceedToOnboarding( - scanResponse: ScanResponse, - manager: SaltPayActivationManager, - ): Result = Result.Success(true) - - fun testProceedToMainScreen( - scanResponse: ScanResponse, - manager: SaltPayActivationManager, - ): Result = Result.Success(false) } + + @Suppress("UnusedPrivateMember") + fun testProceedToOnboarding( + scanResponse: ScanResponse, + manager: SaltPayActivationManager, + ): Result = Result.Success(true) + + @Suppress("UnusedPrivateMember") + fun testProceedToMainScreen( + scanResponse: ScanResponse, + manager: SaltPayActivationManager, + ): Result = Result.Success(false) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index 985676d259..b5dfe53817 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -164,6 +164,7 @@ class GnosisRegistrator( return transferFromHardcodeWay(amountToClaim, signer) } + @Suppress("UnusedPrivateMember") private suspend fun transferFromStandardWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result { val amount = Amount(token, amountToClaim) val feeAmount = walletManager.getFeeToTransferFrom(amount, addressTreasureSafe) 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 99c41094ea..dcb4044499 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 @@ -1,3 +1,5 @@ +@file:Suppress("MaximumLineLength") + package com.tangem.tap.features.onboarding.products.wallet.saltPay import android.net.Uri @@ -37,10 +39,20 @@ import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError +import com.tangem.tap.store import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import com.tangem.tap.store import java.math.BigDecimal +import kotlin.ByteArray +import kotlin.Exception +import kotlin.IllegalStateException +import kotlin.NullPointerException +import kotlin.String +import kotlin.Suppress +import kotlin.Unit +import kotlin.UnsupportedOperationException +import kotlin.byteArrayOf +import kotlin.minus import com.tangem.blockchain.extensions.Result as BlockchainResult /** @@ -295,6 +307,7 @@ class SaltPayActivationManagerFactory( ) } + @Suppress("UnusedPrivateMember") private fun createDummyActivationManager( saltPayConfig: SaltPayConfig, wmFactory: WalletManagerFactory, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt index 93a4aaf6bf..8d116a7385 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -35,7 +35,7 @@ object SaltPayExceptionHandler { is TangemSdkError -> { when (throwable) { is TangemSdkError.NetworkError -> { - val message = (throwable).customMessage + val message = throwable.customMessage store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) } else -> Unit 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 a41ecd1a84..3f0f4f77c4 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 @@ -40,8 +40,8 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() { } @Composable - override fun ScreenContent(modifier: Modifier, state: SprinklrScreenState) { + override fun ScreenContent( + state: SprinklrScreenState, + modifier: Modifier, + ) { BackHandler(onBack = state.onNavigateBack) SprinklrScreenContent( modifier = modifier diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt index 7289515b4b..3dc5ab9c7b 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt @@ -60,7 +60,8 @@ import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] */ -private class AddCustomTokenScreen // for simple search +@Suppress("UnusedPrivateClass") +private class AddCustomTokenScreen // for simple search @OptIn(ExperimentalMaterialApi::class) @Composable 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 4d40a047ab..73c49d69ef 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 @@ -3,7 +3,6 @@ package com.tangem.tap.features.wallet.redux import android.graphics.Bitmap import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType 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 56442e64d7..b170637f3b 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 @@ -20,7 +20,6 @@ import coil.size.Scale import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.core.ui.utils.OneTouchClickListener -import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.events.MainScreen @@ -226,7 +225,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber ViewGroup, -) : ListAdapter(DiffUtilCallback) { - - override fun getItemId(position: Int): Long { - return if (currentList.isEmpty()) 0 else currentList[position].hashCode().toLong() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShimmerVH { - return ShimmerVH(viewHolderViewFactory.invoke(parent)) - } - - override fun onBindViewHolder(holder: ShimmerVH, position: Int) {} - - object DiffUtilCallback : DiffUtil.ItemCallback() { - override fun areContentsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem - override fun areItemsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem - } -} - -class ShimmerVH(viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup) - -data class ShimmerData(private val any: String = "") \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt index 5ac81ac30a..0363cffefe 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt @@ -20,9 +20,10 @@ import com.tangem.wallet.databinding.ItemSaltPayTxHistoryDateBinding */ internal class TxHistoryAdapter : ListAdapter(DiffUtilCallback) { - override fun getItemId(position: Int): Long { - return if (currentList.isEmpty()) 0L - else currentList[position].itemId + override fun getItemId(position: Int): Long = if (currentList.isEmpty()) { + 0L + } else { + currentList[position].itemId } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseTxHistoryVH { 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 1407a374f9..11fec17c6d 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 @@ -48,8 +48,8 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment() { @Composable override fun ScreenContent( - modifier: Modifier, state: WelcomeScreenState, + modifier: Modifier, ) { val snackbarHostState = remember { SnackbarHostState() } val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference()) 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 3161add9a8..a88b3a2ff7 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 @@ -50,6 +50,7 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme setContent { TangemTheme { ScreenContent( + state = provideState().value, modifier = Modifier .fillMaxWidth() .let { @@ -59,7 +60,6 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme 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/ComposeScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeScreen.kt index 8404a4867d..20d6bf7bf1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeScreen.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeScreen.kt @@ -24,22 +24,24 @@ internal interface ComposeScreen { } ScreenContent( + state = provideState().value, modifier = Modifier .fillMaxSize() .background(color = backgroundColor), - state = provideState().value, ) } } } } + @Suppress("TopLevelComposableFunctions") @Composable fun provideState(): State + @Suppress("TopLevelComposableFunctions") @Composable fun ScreenContent( - modifier: Modifier, state: ScreenState, + modifier: Modifier, ) } \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt index bcfafd0ca6..b18597f282 100644 --- a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt +++ b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt @@ -61,6 +61,7 @@ object SaltPayWorkaround { } } + @Suppress("MagicNumber") fun isSaltPayCardId(cardId: String): Boolean = isVisaBatchId(cardId.take(4)) || isWalletCardId(cardId) fun isVisaBatchId(batchId: String): Boolean = visaBatches.contains(batchId)