diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1c44bd0851..1fff3a2a88 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -196,6 +196,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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5fa1e04d9d..93e65b1cc3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -139,9 +139,12 @@
-
+
+
@@ -146,7 +146,11 @@ 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,
@@ -174,7 +178,7 @@ class TapApplication : Application(), ImageLoaderFactory {
private fun initWithConfigDependency(config: Config) {
shopService = TangemShopService(this, config.shopify!!)
initAnalytics(this, config)
- initFeedbackManager(this, preferencesStorage)
+ initFeedbackManager(this, preferencesStorage, foregroundActivityObserver, store)
}
private fun initAnalytics(application: Application, config: Config) {
@@ -196,7 +200,12 @@ class TapApplication : Application(), ImageLoaderFactory {
factory.build(Analytics, buildData)
}
- private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage) {
+ private fun initFeedbackManager(
+ context: Context,
+ preferencesStorage: PreferencesStorage,
+ foregroundActivityObserver: ForegroundActivityObserver,
+ store: Store,
+ ) {
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
appVersion = try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
@@ -229,12 +238,8 @@ class TapApplication : Application(), ImageLoaderFactory {
val feedbackManager = FeedbackManager(
infoHolder = additionalFeedbackInfo,
logCollector = tangemLogCollector,
- preferencesStorage = preferencesStorage,
+ chatManager = ChatManager(preferencesStorage, foregroundActivityObserver, store),
)
- 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/ShimmerRecyclerAdapter.kt b/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt
new file mode 100644
index 0000000000..ff09e08de7
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt
@@ -0,0 +1,34 @@
+package com.tangem.tap.common
+
+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)
+
+@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/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt
index 1f227fc391..79ce7f700a 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
@@ -114,19 +114,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
}
}
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 2e5a925560..cebc4c405e 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
@@ -10,7 +10,7 @@ sealed class AnalyticsParam {
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
class FiatCurrency(
fiatCurrency: com.tangem.tap.common.entities.FiatCurrency,
- ) : CurrencyType(fiatCurrency.symbol)
+ ) : CurrencyType(fiatCurrency.code)
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
}
@@ -63,8 +63,18 @@ 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 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 0d9a529469..465cfe98bf 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,
@@ -19,14 +28,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/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..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
@@ -13,8 +13,8 @@ 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 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/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/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt
index 4ec37c4806..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
@@ -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")
@@ -67,16 +67,26 @@ sealed class Onboarding(
class SetupFinished : Twins("Twin Setup Finished")
}
- class PinCodeSet : Onboarding("Onboarding", "PIN code set")
+ class PinScreenOpened : Onboarding("Onboarding", "PIN screen opened")
+ class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code")
+ 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 NotEnoughGasError : Onboarding("Onboarding", "Not Enough Gas Error")
+ class CardNotPassedError : Onboarding("Onboarding", "Card Not Passed Error")
+
class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding(
category = "Onboarding / Biometric",
event = "Enable Biometric",
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/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/BatchIdParamsInterceptor.kt
deleted file mode 100644
index b7e7ec6fbf..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.api.ParamsInterceptor
-import com.tangem.core.analytics.AnalyticsEvent
-import com.tangem.tap.common.analytics.events.AnalyticsParam
-
-/**
-[REDACTED_AUTHOR]
- */
-class BatchIdParamsInterceptor(
- val batchId: String,
-) : ParamsInterceptor {
-
- override fun id(): String = this::class.java.simpleName
-
- override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
-
- override fun intercept(params: MutableMap) {
- params[AnalyticsParam.BatchId] = batchId
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt
new file mode 100644
index 0000000000..5c087a4f20
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt
@@ -0,0 +1,72 @@
+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.common.analytics.events.IntroductionProcess
+import com.tangem.tap.common.analytics.events.MainScreen
+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 {
+ return when (event) {
+ is IntroductionProcess.ButtonScanCard, is MainScreen.ButtonScanCard -> false
+ else -> 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.cardTypesResolver)?.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 -> if (scanResponse.cardTypesResolver.isSaltPayVisa()) {
+ "Visa"
+ } else {
+ "Visa Backup"
+ }
+ ProductType.Start2Coin -> "Start2Coin"
+ 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"
+ }
+ }
+ }
+
+ companion object {
+ fun id(): String = CardContextInterceptor::class.java.simpleName
+ }
+}
\ No newline at end of file
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/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt
new file mode 100644
index 0000000000..bc69217fa1
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt
@@ -0,0 +1,53 @@
+package com.tangem.tap.common.chat
+
+import android.content.Context
+import android.os.Build
+import com.tangem.tap.ForegroundActivityObserver
+import com.tangem.datasource.config.models.ChatConfig
+import com.tangem.tap.common.chat.opener.ChatOpener
+import com.tangem.datasource.config.models.SprinklrConfig
+import com.tangem.datasource.config.models.ZendeskConfig
+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(getSprinklrUserId(), config, store, foregroundActivityObserver)
+ 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/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..c9b8fdb4f9
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt
@@ -0,0 +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.opener.ChatOpener
+import com.tangem.datasource.config.models.SprinklrConfig
+import com.tangem.tap.common.redux.AppState
+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))
+ 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/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..0e5d38b0da
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt
@@ -0,0 +1,72 @@
+package com.tangem.tap.common.chat.opener.implementation
+
+import android.content.Context
+import com.tangem.core.analytics.Analytics
+import com.tangem.domain.common.LogConfig
+import com.tangem.tap.ForegroundActivityObserver
+import com.tangem.tap.common.chat.opener.ChatOpener
+import com.tangem.datasource.config.models.ZendeskConfig
+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 userId: String,
+ private val config: ZendeskConfig,
+ 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() {
+ val visitorInfo = VisitorInfo.builder().withName("User $userId").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/extensions/Analytics.kt b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt
new file mode 100644
index 0000000000..8d3ce8e5ad
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt
@@ -0,0 +1,43 @@
+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.LinkedCardContextInterceptor
+
+/**
+[REDACTED_AUTHOR]
+ */
+
+/**
+ * Sets the new context
+ */
+fun Analytics.setContext(scanResponse: ScanResponse) {
+ addParamsInterceptor(LinkedCardContextInterceptor(scanResponse))
+}
+
+/**
+ * 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/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/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
index 97997ba97f..1426f3a118 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
@@ -31,6 +35,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? {
return if (addressDataList.isEmpty()) null 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/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
index c6513c9188..8de0b8e686 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.ChatManager
+import com.tangem.datasource.config.models.ChatConfig
import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.log.TangemLogCollector
-import com.tangem.datasource.config.models.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/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 820f5b3d3f..e9b253d90d 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
@@ -61,6 +63,7 @@ data class AppState(
val welcomeState: WelcomeState = WelcomeState(),
val saveWalletState: SaveWalletState = SaveWalletState(),
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
+ val sprinklrState: SprinklrState = SprinklrState(),
) : StateType {
val domainState: DomainState
@@ -101,6 +104,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/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
index 4017f9b32f..c4c8a33527 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
@@ -4,6 +4,8 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
+import com.tangem.datasource.config.ConfigManager
+import com.tangem.datasource.config.models.ChatConfig
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.feedback.FeedbackData
@@ -13,9 +15,7 @@ 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.datasource.config.models.ZendeskConfig
import com.tangem.tap.domain.TapError
-import com.tangem.datasource.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.details.redux.SecurityOption
@@ -36,18 +36,20 @@ 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
*/
- data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding()
+ data class StartForUnfinishedBackup(
+ val addedBackupCardsCount: Int,
+ val isSaltPayVisa: Boolean,
+ ) : Onboarding()
+
object Stop : Onboarding()
}
@@ -87,7 +89,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/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
index b880c71872..b7fd1db3fe 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
@@ -3,8 +3,11 @@ 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.datasource.config.models.Config
+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
@@ -14,16 +17,22 @@ import com.tangem.tap.common.redux.AppState
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
@@ -98,17 +107,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?.cardTypesResolver?.isSaltPay() == true -> config.saltPayConfig?.zendesk
+ val unsafeChatConfig = action.chatConfig ?: when {
+ scanResponse?.cardTypesResolver?.isSaltPay() == true -> config.saltPayConfig?.sprinklr
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))
@@ -119,38 +127,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 -> {}
@@ -202,4 +195,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/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
index 7bbfdc4f1d..24c1384e66 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.isTestCard
@@ -18,6 +19,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.datasource.config.ConfigManager
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
@@ -100,6 +102,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/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
index 07e92a313f..59647cbde5 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
@@ -138,24 +134,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.cardTypesResolver.getBlockchain()
- if (blockchain != Blockchain.SaltPay) {
- error("WalletManager for the SaltPay can be created based only on Blockchain.SaltPay")
- }
-
- val token = SaltPayWorkaround.tokenFrom(blockchain)
- val cardWallet = scanResponse.card.wallets.firstOrNull().guard {
- error("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/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/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt
index a2c699667a..b74821a988 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
@@ -4,6 +4,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
@@ -77,6 +78,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/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt
index b48ae715a2..aef5c1ea76 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/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt
index 36f4635c4a..f7c823369f 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.analytics.paramsInterceptor.BatchIdParamsInterceptor
+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
@@ -27,10 +29,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
@@ -75,7 +77,7 @@ object ScanCardProcessor {
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
onScanStateChange(false)
- sendAnalytics(analyticsEvent, scanResponse.card.batchId)
+ sendAnalytics(analyticsEvent, scanResponse)
checkForUnfinishedBackupForSaltPay(
backupService = backupService,
@@ -103,10 +105,17 @@ object ScanCardProcessor {
private fun sendAnalytics(
analyticsEvent: AnalyticsEvent?,
- batchId: String,
+ scanResponse: ScanResponse,
) {
- Analytics.addParamsInterceptor(BatchIdParamsInterceptor(batchId))
- 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)
+ }
}
/**
@@ -188,20 +197,25 @@ object ScanCardProcessor {
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse))
- if (scanResponse.cardTypesResolver.isSaltPay()) {
- if (scanResponse.cardTypesResolver.isSaltPayVisa()) {
- val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
+ val cardTypesResolver = scanResponse.cardTypesResolver
+ if (cardTypesResolver.isSaltPay()) {
+ if (cardTypesResolver.isSaltPayVisa()) {
+ val manager = SaltPayActivationManagerFactory(
+ blockchain = scanResponse.cardTypesResolver.getBlockchain(),
+ card = scanResponse.card,
+ ).create()
val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager)
delay(500)
withMainContext {
when (result) {
is Result.Success -> {
+ Analytics.addContext(scanResponse)
val isOnboardingCase = result.data
if (isOnboardingCase) {
onWalletNotCreated()
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
- store.dispatch(OnboardingSaltPayAction.SetDependencies(manager, config))
- store.dispatch(OnboardingSaltPayAction.Update)
+ store.dispatch(OnboardingSaltPayAction.SetDependencies(manager))
+ store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false))
navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) }
} else {
delay(DELAY_SDK_DIALOG_CLOSE)
@@ -222,16 +236,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/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt
index a7dc0a853d..c6519653d2 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
@@ -19,6 +19,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
@@ -31,6 +32,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
@@ -204,7 +206,7 @@ internal class DefaultWalletAmountsRepository(
updateWalletStoreWithUnreachable(walletStore)
}
else -> {
- withInternetConnection { walletManager.update() }
+ withInternetConnection { walletManager.safeUpdate() }
.map { updateWalletManagerWithAmounts(userWalletId, walletManager) }
.flatMap {
updateWalletStoreWithAmounts(
@@ -319,7 +321,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 47ef539e1f..d052401b0c 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/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
index d763e26339..8ac99d6590 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
@@ -48,6 +48,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()
@@ -118,9 +122,6 @@ class DemoConfig {
@Suppress("ClassOrdering")
private val releaseDemoCardIds = mutableListOf(
// === Not from the Google Sheet table ===
- "FB10000000000196", // Note BTC
- "FB20000000000186", // Note ETH
- "FB30000000000176", // Wallet
"AC01000000041225",
"AC01000000041472",
"AB01000000046498",
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 6e64e29b98..6fe638daca 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
@@ -24,8 +24,6 @@ sealed class DetailsAction : Action {
object Success : ResetToFactory()
}
- object CreateBackup : DetailsAction()
-
object ScanCard : DetailsAction()
data class PrepareCardSettingsData(val card: CardDTO, val cardTypesResolver: CardTypesResolver) : 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 203af20870..f8626aa139 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
@@ -66,12 +66,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 2a3c6aea7d..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
@@ -8,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
@@ -86,7 +85,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))
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 f01fdaeb7c..b32b6a6ab2 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..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,16 +1,18 @@
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
sealed class HomeAction : Action {
+ object OnCreate : HomeAction()
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/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
index 902bb0ef47..72c24acdb4 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.eraseContext
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
@@ -49,6 +51,10 @@ private val homeMiddleware: Middleware = { _, _ ->
private fun handleHomeAction(action: Action) {
when (action) {
+ is HomeAction.OnCreate -> {
+ Analytics.eraseContext()
+ 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/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt
index 5ed844549d..e0d51ccbf7 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
@@ -55,6 +58,7 @@ object 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 {
@@ -92,4 +96,8 @@ object 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/OnboardingSaltPayHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt
index b9aa35f096..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
@@ -13,16 +13,20 @@ import com.tangem.tap.preferencesStorage
[REDACTED_AUTHOR]
*/
object OnboardingSaltPayHelper {
- suspend fun isOnboardingCase(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result {
+
+ suspend fun isOnboardingCase(
+ scanResponse: ScanResponse,
+ 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) {
@@ -31,4 +35,16 @@ object OnboardingSaltPayHelper {
Result.Failure(error)
}
}
+
+ @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/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt
index 169e5dfb7f..28d62cf134 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
@@ -196,6 +196,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
store.dispatchDialogShow(
OnboardingDialog.InterruptOnboarding(
onOk = {
+ OnboardingHelper.onInterrupted()
store.dispatch(NavigationAction.PopBackTo())
},
),
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 a0a6b8b76b..5080265552 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 d6d15cec4c..ac58136a05 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
@@ -3,8 +3,8 @@ package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.Message
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.VoidCallback
-import com.tangem.domain.common.ScanResponse
import com.tangem.datasource.utils.AssetReader
+import com.tangem.domain.common.ScanResponse
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
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 df7d042f2b..de2c204c65 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
@@ -320,6 +320,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
twinCardsState.currentStep != TwinCardsStep.TopUpWallet &&
twinCardsState.currentStep != TwinCardsStep.Done
+ OnboardingHelper.onInterrupted()
store.dispatch(TwinCardsAction.CardsManager.Release)
action.shouldResetTwinCardsWidget(shouldReturnCardBack) {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt
index 86fb3f8c57..dbc7e3672d 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
@@ -411,7 +411,11 @@ class TwinsCardsFragment : BaseOnboardingFragment() {
TwinCardsAction.OnBackPressed { should, popAction ->
store.dispatch(TwinCardsAction.Confetti.Hide)
showConfetti(false)
- if (should) switchToCard(TwinCardNumber.First, true, popAction) else popAction()
+ if (should) {
+ switchToCard(TwinCardNumber.First, true, popAction)
+ } else {
+ popAction()
+ }
},
)
}
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 eabcd70384..df5882a35c 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
@@ -31,9 +31,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()
@@ -65,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 e240879094..1ea9f428aa 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
@@ -24,8 +24,8 @@ import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.onboarding.OnboardingDialog
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
@@ -72,7 +72,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 -> {
@@ -87,10 +87,11 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
scanResponse.cardTypesResolver.isSaltPay() -> {
store.dispatch(OnboardingWalletAction.GetToSaltPayStep)
- store.dispatch(BackupAction.FinishBackup)
+ store.dispatch(BackupAction.FinishBackup(withAnalytics = false))
+ store.dispatch(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess)
}
- else -> store.dispatch(BackupAction.FinishBackup)
+ else -> store.dispatch(BackupAction.FinishBackup())
}
}
else -> {
@@ -317,7 +318,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))
}
@@ -328,7 +329,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)
@@ -345,18 +348,23 @@ 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(backupService.addedBackupCardsCount))
+ store.dispatch(
+ GlobalAction.Onboarding.StartForUnfinishedBackup(
+ addedBackupCardsCount = backupService.addedBackupCardsCount,
+ isSaltPayVisa = backupService.primaryCardIsSaltPayVisa(),
+ ),
+ )
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
}
is BackupAction.DismissBackup -> {
Analytics.send(Onboarding.Backup.Skipped())
if (onboardingWalletState.isSaltPay) throw UnsupportedOperationException()
- store.dispatch(BackupAction.FinishBackup)
+ store.dispatch(BackupAction.FinishBackup())
}
else -> {}
}
@@ -395,14 +403,18 @@ private fun finishCardsActivationForDiscardedUnfinishedBackup(cardId: String) {
*/
private fun initSaltPayOnBackupFinishedIfNeeded(
scanResponse: ScanResponse?,
- onboardingWalletState: OnboardingWalletState,
+ state: OnboardingWalletState,
) {
- if (onboardingWalletState.isSaltPay && onboardingWalletState.onboardingSaltPayState == null) {
- if (scanResponse == null) error("scanning response is null")
+ if (state.backupState.isInterruptedBackup) return
+ if (scanResponse == null) return
- val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
- store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config))
- store.dispatchOnMain(OnboardingSaltPayAction.Update)
+ if (state.isSaltPay && state.onboardingSaltPayState == null) {
+ val manager = SaltPayActivationManagerFactory(
+ blockchain = scanResponse.cardTypesResolver.getBlockchain(),
+ card = scanResponse.card,
+ ).create()
+ store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager))
+ store.dispatchOnMain(OnboardingSaltPayAction.Update(false))
}
}
@@ -420,6 +432,7 @@ private fun handleOnBackPressed(state: OnboardingWalletState) {
if (state.isSaltPay) {
showInterruptOnboardingDialog()
} else {
+ OnboardingHelper.onInterrupted()
store.dispatch(NavigationAction.PopBackTo())
}
}
@@ -430,6 +443,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/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt
index 68099fff3f..5359efde9b 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
@@ -38,13 +38,13 @@ private object ReducerForGlobalAction {
fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState {
return when (action) {
is GlobalAction.Onboarding.Start -> {
+ val isSaltPay = action.scanResponse.cardTypesResolver.isSaltPay()
OnboardingWalletState(
backupState = state.backupState.copy(
- maxBackupCards = if (action.scanResponse.cardTypesResolver.isSaltPay()) 1 else 2,
- canSkipBackup =
- if (action.scanResponse.cardTypesResolver.isSaltPay()) false else action.canSkipBackup,
+ maxBackupCards = maxBackupCards(isSaltPay),
+ canSkipBackup = if (isSaltPay) false else action.canSkipBackup,
),
- isSaltPay = action.scanResponse.cardTypesResolver.isSaltPay(),
+ isSaltPay = isSaltPay,
)
}
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
@@ -52,13 +52,16 @@ private object ReducerForGlobalAction {
backupState = state.backupState.copy(
maxBackupCards = action.addedBackupCardsCount,
canSkipBackup = false,
+ isInterruptedBackup = true,
),
- isSaltPay = false,
+ isSaltPay = action.isSaltPayVisa,
)
}
else -> state
}
}
+
+ private fun maxBackupCards(isSaltPay: Boolean): Int = if (isSaltPay) 1 else 2
}
private object BackupReducer {
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 64af52ecac..d2ef761e70 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
@@ -62,7 +62,7 @@ data class OnboardingWalletState(
-> 9
SaltPayActivationStep.Claim -> 10
SaltPayActivationStep.ClaimInProgress -> 11
- SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success, SaltPayActivationStep.Finished,
+ SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success,
-> getMaxProgress()
}
}
@@ -87,6 +87,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/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt
index 8882281ca9..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
@@ -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")
}
@@ -47,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))
}
@@ -145,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)
}
@@ -154,6 +159,13 @@ class GnosisRegistrator(
return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error))
}
}
+
+ // return transferFromStandardWay(amountToClaim, signer)
+ 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)
.extractFeeAmount().successOr { return it }
@@ -164,6 +176,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)
+ }
+ }
+
@Suppress("MagicNumber")
private fun Result>.extractFeeAmount(): Result {
return when (this) {
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 e99c85ae58..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,14 +1,22 @@
+@file:Suppress("MaximumLineLength")
+
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.extensions.toHexString
import com.tangem.common.services.Result
@@ -23,15 +31,28 @@ import com.tangem.datasource.api.paymentology.models.response.RegistrationRespon
import com.tangem.datasource.api.paymentology.models.response.tryExtractError
import com.tangem.datasource.config.models.KYCProvider
import com.tangem.datasource.config.models.SaltPayConfig
+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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
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
/**
@@ -227,4 +248,97 @@ 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,
+ )
+ }
+
+ @Suppress("UnusedPrivateMember")
+ 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/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt
index 6236bf847e..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
@@ -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
@@ -12,12 +14,20 @@ import com.tangem.tap.store
[REDACTED_AUTHOR]
*/
object SaltPayExceptionHandler {
+
fun handle(throwable: Throwable) {
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/dialog/NoFundsForActivationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt
index 8280d87aab..5121db94db 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
@@ -14,16 +14,20 @@ import com.tangem.wallet.R
[REDACTED_AUTHOR]
*/
object NoFundsForActivationDialog {
+
fun create(context: Context): Dialog {
return AlertDialog.Builder(context).apply {
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 sprinklrConfig = store.state.globalState.configManager?.config
+ ?.saltPayConfig
+ ?.sprinklr
+ .guard {
+ store.dispatchDebugErrorNotification("SaltPayConfig not initialized")
+ return@setPositiveButton
+ }
+ store.dispatch(GlobalAction.OpenChat(SupportInfo(), sprinklrConfig))
}
setNegativeButton(R.string.common_cancel) { _, _ ->
}
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 ea8b58f85c..91b791925d 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.datasource.config.models.SaltPayConfig
import org.rekotlin.Action
import java.math.BigDecimal
@@ -12,9 +11,13 @@ 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,
- val saltPayConfig: SaltPayConfig,
) : OnboardingSaltPayAction()
data class SetInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction()
@@ -22,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()
@@ -33,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()
}
\ No newline at end of file
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 90ebec0825..57b1f17f3c 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
@@ -78,6 +78,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
@@ -89,7 +92,18 @@ 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
+ val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr {
+ SaltPayExceptionHandler.handle(it.error)
+ handleClaimRefreshInProgress = false
+ return@launch
+ }
+ dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue))
+ handleClaimRefreshInProgress = false
+ }
}
}
is OnboardingSaltPayAction.RegisterCard -> {
@@ -136,8 +150,16 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
return@launch
}
+ 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 -> {
@@ -159,13 +181,13 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
}
handleInProgress = false
- dispatchOnMain(OnboardingSaltPayAction.Update)
+ dispatchOnMain(OnboardingSaltPayAction.Update())
}
}
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) {
@@ -219,54 +241,48 @@ 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 {
+ // dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess))
+ }
}
}
is OnboardingSaltPayAction.SetStep -> {
- when (action.newStep) {
- SaltPayActivationStep.KycStart -> Analytics.send(Onboarding.KYCStarted())
- SaltPayActivationStep.KycWaiting -> Analytics.send(Onboarding.KYCInProgress())
- SaltPayActivationStep.KycReject -> Analytics.send(Onboarding.KYCRejected())
- SaltPayActivationStep.Claim -> Analytics.send(Onboarding.ClaimScreenOpened())
- SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success -> {
- Analytics.send(Onboarding.Finished())
- val onboardingManager = getOnboardingManager().guard {
- // Null is possible if it is started from a standard pending backup and in this case
- // it is impossible to get here
- store.dispatchDebugErrorNotification("OnboardingManager can't be NULL")
- return
- }
- finishCardActivation(getOnboardingWalletState().backupState, onboardingManager.scanResponse.card)
+ 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 -> {
@@ -275,6 +291,22 @@ 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.CardRegistration -> Analytics.send(Onboarding.CardConnectionScreenOpened())
+ 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?,
@@ -362,6 +394,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/redux/OnboardingSaltPayReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt
index 9d4e8774c0..ad3c493336 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
@@ -20,7 +20,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 344834807f..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
@@ -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.datasource.api.paymentology.PaymentologyApiService
import com.tangem.domain.common.SaltPayWorkaround
-import com.tangem.domain.common.ScanResponse
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.datasource.config.models.KYCProvider
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
-import com.tangem.datasource.config.models.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,
@@ -38,54 +28,6 @@ data class OnboardingSaltPayState(
get() = if (inProgress) ProgressState.Loading else ProgressState.Done
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.cardTypesResolver.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 {
@@ -101,5 +43,4 @@ enum class SaltPayActivationStep {
ClaimInProgress,
ClaimSuccess,
Success,
- Finished;
}
\ No newline at end of file
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 d4549041e1..394e03adb4 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,9 +80,10 @@ 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)
} else {
// if not -> back to the standard backup process
walletFragment.handleOnboardingStep(state)
@@ -103,7 +104,7 @@ internal class OnboardingSaltPayView(
val uri = Uri.parse(state.onboardingSaltPayState.saltPayCardArtworkUrl)
walletFragment.loadImageIntoImageView(uri, 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
@@ -233,7 +234,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)
}
@@ -249,7 +250,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)
}
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..b781ee102f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt
@@ -0,0 +1,10 @@
+package com.tangem.tap.features.sprinklr.redux
+
+import com.tangem.datasource.config.models.SprinklrConfig
+import org.rekotlin.Action
+
+sealed interface SprinklrAction : Action {
+ 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
new file mode 100644
index 0000000000..922e3a2e67
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt
@@ -0,0 +1,42 @@
+package com.tangem.tap.features.sprinklr.redux
+
+import com.tangem.datasource.config.models.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
+
+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.Init -> updateUrl(action.userId, action.config)
+ is SprinklrAction.UpdateUrl,
+ is SprinklrAction.UpdateSprinklrDomains,
+ -> Unit
+ }
+ }
+
+ 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
new file mode 100644
index 0000000000..8ec0fc117c
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt
@@ -0,0 +1,20 @@
+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.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
new file mode 100644
index 0000000000..12ba5b8672
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt
@@ -0,0 +1,6 @@
+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..2d1eb1be87
--- /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/SprinklrActivity.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrActivity.kt
new file mode 100644
index 0000000000..685a52d600
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrActivity.kt
@@ -0,0 +1,45 @@
+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 com.tangem.core.analytics.Analytics
+import com.tangem.core.ui.fragments.ComposeActivity
+import com.tangem.tap.common.analytics.events.Chat
+
+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
+ override fun provideState(): State {
+ return viewModel.state.collectAsState()
+ }
+
+ @Composable
+ override fun ScreenContent(
+ state: SprinklrScreenState,
+ modifier: Modifier,
+ ) {
+ 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..f80a4b43f3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt
@@ -0,0 +1,28 @@
+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
+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
+ }
+ },
+ 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
new file mode 100644
index 0000000000..8757f6e167
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt
@@ -0,0 +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
new file mode 100644
index 0000000000..989b813d19
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt
@@ -0,0 +1,64 @@
+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
+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,
+ sprinklrDomains = state.sprinklrDomains,
+ onNewUrl = { updateWebContentOrOpenExternalUrl(it) },
+ )
+ }
+ }
+
+ override fun onCleared() {
+ 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 }
+ .select { it.sprinklrState }
+ }
+ }
+
+ 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/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt
index 1d57e7472d..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
@@ -57,6 +57,12 @@ import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCasesList
import com.tangem.wallet.R
import kotlinx.coroutines.launch
+/**
+[REDACTED_AUTHOR]
+ */
+@Suppress("UnusedPrivateClass")
+private class AddCustomTokenScreen // for simple search
+
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AddCustomTokenScreen(
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt
index a2fc562b3a..b6ed172e1a 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletData.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.wallet.redux
+import com.tangem.blockchain.common.TransactionData
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.feature.swap.domain.SwapInteractor
@@ -14,6 +15,7 @@ import java.math.BigDecimal
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/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
index 0f4f29e860..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
@@ -81,7 +81,7 @@ data class WalletState(
walletsStores[0]
}
- private val primaryWalletManager: WalletManager?
+ val primaryWalletManager: WalletManager?
get() = primaryWalletStore?.walletManager
val primaryWalletData: WalletData?
@@ -317,6 +317,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/wallet/redux/middlewares/Mapper.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt
index 8b8f7ec97d..84bb0f1212 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
@@ -117,6 +117,7 @@ private fun WalletDataModel.mapToReduxModel(
?.toString(),
errorMessage = status.errorMessage,
),
+ historyTransactions = historyTransactions,
)
}
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 1f322a6152..b3743b0360 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
@@ -169,6 +170,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/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
index 7d257fb896..96967b136a 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
@@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux.middlewares
import androidx.core.os.bundleOf
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.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
@@ -22,6 +23,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
@@ -49,10 +51,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
@@ -96,12 +97,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(
@@ -116,6 +117,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/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
index c68b906b9d..41a28fa9c9 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
@@ -249,6 +250,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) }
@@ -380,7 +383,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/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
index 3a535e0375..7cb9f0b6a6 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
@@ -54,6 +56,7 @@ class MultiWalletReducer {
blockchain.derivationPath,
),
existentialDepositString = getExistentialDeposit(walletManager),
+ historyTransactions = walletManager?.getBlockchainTxHistory(),
)
WalletStore(
@@ -89,6 +92,7 @@ class MultiWalletReducer {
action.blockchain.derivationPath,
),
existentialDepositString = getExistentialDeposit(walletManager),
+ historyTransactions = walletManager?.getBlockchainTxHistory(),
)
val walletStore = WalletStore(
walletManager = walletManager,
@@ -153,6 +157,7 @@ class MultiWalletReducer {
derivationPath = action.blockchain.derivationPath,
),
walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)),
+ historyTransactions = walletManager.getTokenTxHistory(action.token),
)
state.updateWalletData(newTokenWalletData)
}
@@ -206,8 +211,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(
@@ -218,5 +223,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 1e15f2460a..d5865ab287 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 1dfbfbd164..c1157544cb 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
@@ -348,6 +348,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 ae7efb71bf..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
@@ -40,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
@@ -155,19 +154,22 @@ 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)
}
@@ -185,7 +187,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(store.state.globalState.exchangeManager))
- 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 393f1ea97d..27b263ae5e 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
@@ -6,10 +6,12 @@ import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.databinding.FragmentWalletBinding
abstract class WalletView {
+
var swapInteractor: SwapInteractor? = null
protected var fragment: WalletFragment? = null
protected var binding: FragmentWalletBinding? = null
+
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
@@ -20,6 +22,10 @@ abstract class WalletView {
binding = null
}
+ open fun onViewDestroy() {
+ removeFragment()
+ }
+
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..e73d4ab074
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt
@@ -0,0 +1,254 @@
+package com.tangem.tap.features.wallet.ui.wallet.saltPay
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+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
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.recyclerView.SpaceItemDecoration
+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.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.network.exchangeServices.CurrencyExchangeManager
+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.launch
+import org.rekotlin.Action
+import timber.log.Timber
+
+/**
+[REDACTED_AUTHOR]
+ */
+class SaltPayWalletView : WalletView() {
+
+ private var saltPayBinding: LayoutSaltPayWalletBinding? = null
+
+ private val actionDebouncer = debounce(500, mainScope) { store.dispatch(it) }
+
+ private val balanceWidget: LayoutSaltPayBalanceBinding?
+ get() = saltPayBinding?.lSaltPayBalance
+
+ private val txWidget: LayoutSaltPayTxHistoryBinding?
+ get() = saltPayBinding?.lSaltPayTxHistory
+
+ private val itemDecorator = SpaceItemDecoration.vertical(10F)
+
+ override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
+ Timber.d("changeWalletView")
+ saltPayBinding = binding.lSaltPayWallet
+ showSaltPayView(binding)
+ onViewCreated()
+ }
+
+ override fun onViewCreated() {
+ Timber.d("onViewCreated")
+ prepareTxRecyclers(txWidget!!)
+ }
+
+ private fun showSaltPayView(binding: FragmentWalletBinding) = with(binding) {
+ Timber.d("showSaltPayView")
+ 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(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)
+ rvTxHistoryShimmer.adapter = adapter
+ rvTxHistoryShimmer.addItemDecoration(itemDecorator)
+ adapter.submitList(
+ listOf(
+ ShimmerData(),
+ ShimmerData(),
+ ShimmerData(),
+ ),
+ )
+
+ rvTxHistory.adapter = TxHistoryAdapter()
+ rvTxHistory.addItemDecoration(itemDecorator)
+ }
+
+ override fun onNewState(state: WalletState) {
+ val balanceWidget = balanceWidget ?: return
+ val txWidget = txWidget ?: return
+
+ val exchangeManager = store.state.globalState.exchangeManager
+ setupBalanceWidget(balanceWidget, exchangeManager, state)
+ setupTxHistoryWidget(txWidget, state)
+ }
+
+ private fun setupBalanceWidget(
+ balanceWidget: LayoutSaltPayBalanceBinding,
+ exchangeManager: CurrencyExchangeManager,
+ state: WalletState,
+ ) = with(balanceWidget) {
+ Timber.d("setupBalanceWidget")
+ val tokenData = state.primaryTokenData ?: return
+
+ 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) {
+ veilBalance.veil()
+ 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(exchangeManager))
+ btnBuy.setOnClickListener {
+ Analytics.send(MainScreen.ButtonBuy())
+ store.dispatch(WalletAction.TradeCryptoAction.Buy(false))
+ }
+ }
+
+ 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 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 handleTxLoading(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) {
+ Timber.d("handleTxLoading")
+ groupEmpty.hide()
+ groupError.hide()
+ groupSuccess.hide()
+ // root.beginDelayedTransition()
+ groupShimmer.show()
+ }
+
+ private fun handleTxSuccess(
+ txWidget: LayoutSaltPayTxHistoryBinding,
+ dataList: List,
+ ) = with(txWidget) {
+ Timber.d("handleTxSuccess")
+ groupShimmer.hide()
+ groupEmpty.hide()
+ groupError.hide()
+ // root.beginDelayedTransition()
+ updateTxHistoryWidget(txWidget, dataList)
+ groupSuccess.show()
+ }
+
+ private fun handleTxEmpty(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) {
+ Timber.d("handleTxEmpty")
+ groupShimmer.hide()
+ groupError.hide()
+ groupSuccess.hide()
+ // root.beginDelayedTransition()
+ updateTxHistoryWidget(txWidget, emptyList())
+ groupEmpty.show()
+ }
+
+ private fun handleTxError(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) {
+ Timber.d("handleTxError")
+ groupShimmer.hide()
+ groupEmpty.hide()
+ groupSuccess.hide()
+ // root.beginDelayedTransition()
+ updateTxHistoryWidget(txWidget, emptyList())
+ groupError.show()
+ }
+
+ 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()
+ }
+}
\ 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..7f194e047e
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt
@@ -0,0 +1,79 @@
+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.address.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 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)
+ } ?: "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
+ }
+
+ @Suppress("MagicNumber")
+ 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
new file mode 100644
index 0000000000..0363cffefe
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt
@@ -0,0 +1,113 @@
+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 = 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.address
+ }
+
+ 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
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 33e8bcae4b..828150fa17 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,8 +9,9 @@ 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.analytics.paramsInterceptor.BatchIdParamsInterceptor
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
@@ -140,7 +141,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)
@@ -209,9 +210,6 @@ internal class WalletSelectorMiddleware {
.doOnSuccess {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
if (selectedUserWallet != null) {
- val batchId = selectedUserWallet.scanResponse.card.batchId
- Analytics.addParamsInterceptor(BatchIdParamsInterceptor(batchId))
-
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
store.onUserWalletSelected(selectedUserWallet)
}
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 696367fcd0..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
@@ -47,7 +47,10 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment {
+ Analytics.eraseContext()
+ Analytics.send(SignIn.ScreenOpened())
+ }
is WelcomeAction.ProceedWithBiometrics -> {
proceedWithBiometrics(state)
}
@@ -107,7 +115,7 @@ internal class WelcomeMiddleware {
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
)
ScanCardProcessor.scan(
- analyticsEvent = SignIn.CardWasScanned(),
+ analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.SignIn),
onSuccess = { scanResponse ->
scope.launch { onCardScanned(scanResponse) }
},
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 d7d71b7ce0..f44447f2a9 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 0ae230fba6..b80d65ce7a 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,14 +19,14 @@ 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.BiometricsLockoutDialog
import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent
+import com.tangem.tap.store
import com.tangem.wallet.R
internal class WelcomeFragment : ComposeFragment() {
@@ -34,7 +34,7 @@ internal class WelcomeFragment : ComposeFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- Analytics.send(SignIn.ScreenOpened())
+ store.dispatch(WelcomeAction.OnCreate)
}
@Composable
@@ -43,7 +43,10 @@ internal class WelcomeFragment : ComposeFragment() {
}
@Composable
- override fun ScreenContent(state: WelcomeScreenState, modifier: Modifier) {
+ override fun ScreenContent(
+ state: WelcomeScreenState,
+ modifier: Modifier,
+ ) {
val snackbarHostState = remember { SnackbarHostState() }
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
val biometricsLockoutDialog by rememberUpdatedState(newValue = state.biometricsLockoutDialog)
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 671c45af11..f7beab3570 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()
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 0309ce9ee9..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
@@ -11,7 +11,7 @@ interface Exchanger {
fun availableForSell(currency: Currency): Boolean
}
-interface ExchangeService : Feature, Exchanger {
+interface ExchangeService : Feature, Exchanger, ExchangeUrlBuilder {
suspend fun update()
companion object {
@@ -22,6 +22,18 @@ 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
}
}
}
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 48700b18ea..9561664182 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>()
@@ -35,7 +28,7 @@ class MercuryoService(
@Suppress("NestedBlockDepth")
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 == RESPONSE_SUCCESS_STATUS_CODE) {
@@ -116,7 +109,7 @@ class MercuryoService(
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)
@@ -129,7 +122,7 @@ class MercuryoService(
}
private fun signature(address: String): String {
- return (address + secret).calculateSha512().toHexString().lowercase()
+ return (address + environment.secret).calculateSha512().toHexString().lowercase()
}
override fun getSellCryptoReceiptUrl(
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 e6752f1272..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
@@ -12,7 +12,6 @@ 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()
@@ -148,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..820751a14b
--- /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 {
+ 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)
+ // if we set the paymentCurrency, then the Utorg widget didn't work
+ // .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/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
index a6ff52c139..3f5c655ec2 100644
--- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
+++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
@@ -35,9 +35,13 @@ class PreferencesStorage(applicationContext: Application) {
toppedUpWalletStorage = ToppedUpWalletStorage(preferences, moshiConverter)
}
- 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)
@@ -88,7 +92,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"
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..5e41bb38d1
--- /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..a21c2dbd6e
--- /dev/null
+++ b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
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..8ce65d0b6b 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"
@@ -53,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"
@@ -65,7 +62,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 +78,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,10 +88,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/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/Versions.kt b/buildSrc/src/main/java/Versions.kt
index 946bc67130..32ba2da6ab 100644
--- a/buildSrc/src/main/java/Versions.kt
+++ b/buildSrc/src/main/java/Versions.kt
@@ -24,7 +24,7 @@ object Versions {
// endregion Compose
// region Other libraries
- const val accompanist = "0.27.1"
+ const val accompanist = "0.28.0"
const val amplitude = "2.36.1"
const val appsflyer = "6.5.1"
const val armadillo = "0.9.0"
@@ -61,7 +61,7 @@ object Versions {
// endregion Other libraries
// region Tangem
- const val tangemBlockchainSdk = "develop-159"
+ const val tangemBlockchainSdk = "develop-168"
// const val tangemBlockchainSdk = "0.0.1" // Keep it! - used for local builds
const val tangemCardSdk = "develop-191"
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/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt
index e7a9d62ab0..63e503173e 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt
@@ -2,6 +2,7 @@ package com.tangem.datasource.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
@@ -82,6 +83,10 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
authToken = configValues.blockchairAuthorizationToken,
),
blockcypherTokens = configValues.blockcypherTokens,
+ blockscoutCredentials = BlockscoutCredentials(
+ userName = configValues.saltPay.blockscoutCredentials.user,
+ password = configValues.saltPay.blockscoutCredentials.password,
+ ),
quickNodeSolanaCredentials = QuickNodeCredentials(
apiKey = configValues.quiknodeApiKey,
subdomain = configValues.quiknodeSubdomain,
@@ -94,7 +99,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
tronGridApiKey = configValues.tronGridApiKey,
saltPayAuthToken = configValues.saltPay.credentials.token,
nowNodeCredentials = NowNodeCredentials(configValues.nowNodesApiKey),
- getBlockCredentials = GetBlockCredentials(configValues.getBlockApiKey)
+ getBlockCredentials = GetBlockCredentials(configValues.getBlockApiKey),
),
appsFlyerDevKey = configValues.appsFlyer.appsFlyerDevKey,
amplitudeApiKey = configValues.amplitudeApiKey,
diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/ZendeskConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/ChatConfig.kt
similarity index 67%
rename from core/datasource/src/main/java/com/tangem/datasource/config/models/ZendeskConfig.kt
rename to core/datasource/src/main/java/com/tangem/datasource/config/models/ChatConfig.kt
index 84e9f9c786..d34275da0f 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/models/ZendeskConfig.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/ChatConfig.kt
@@ -3,6 +3,8 @@ package com.tangem.datasource.config.models
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,12 @@ data class ZendeskConfig(
val accountKey: String,
@Json(name = "zendeskUrl")
val url: String,
-)
\ No newline at end of file
+) : 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/core/datasource/src/main/java/com/tangem/datasource/config/models/SaltPayConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/SaltPayConfig.kt
index 65efeb65d7..f8148bcf9e 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/models/SaltPayConfig.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/SaltPayConfig.kt
@@ -6,16 +6,18 @@ import org.spongycastle.util.encoders.Base64.toBase64String
[REDACTED_AUTHOR]
*/
data class SaltPayConfig(
- val zendesk: ZendeskConfig,
+ val sprinklr: SprinklrConfig,
val kycProvider: KYCProvider,
val credentials: Credentials,
+ val blockscoutCredentials: Credentials,
) {
companion object {
fun stub(): SaltPayConfig {
return SaltPayConfig(
- zendesk = ZendeskConfig("", "", "", "", ""),
+ sprinklr = SprinklrConfig("", ""),
kycProvider = KYCProvider("", "", "", ""),
credentials = Credentials("", ""),
+ blockscoutCredentials = Credentials("", ""),
)
}
}
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 f4759a6ed8..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
@@ -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,15 +65,4 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragme
}
}
}
-
- @Suppress("TopLevelComposableFunctions")
- @Composable
- protected abstract fun provideState(): State
-
- @Suppress("TopLevelComposableFunctions")
- @Composable
- protected abstract fun ScreenContent(
- state: ScreenState,
- modifier: Modifier,
- )
}
\ 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 6b6865094a..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,34 +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(
- state = provideState().value,
- modifier = Modifier
- .fillMaxSize()
- .background(color = backgroundColor),
- )
- }
- }
- }
- }
-
- @Suppress("TopLevelComposableFunctions")
- @Composable
- protected abstract fun provideState(): State
-
- @Suppress("TopLevelComposableFunctions")
- @Composable
- protected abstract fun ScreenContent(state: ScreenState, modifier: Modifier)
}
\ 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..20d6bf7bf1
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/fragments/ComposeScreen.kt
@@ -0,0 +1,47 @@
+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(
+ state = provideState().value,
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = backgroundColor),
+ )
+ }
+ }
+ }
+ }
+
+ @Suppress("TopLevelComposableFunctions")
+ @Composable
+ fun provideState(): State
+
+ @Suppress("TopLevelComposableFunctions")
+ @Composable
+ fun ScreenContent(
+ state: ScreenState,
+ modifier: Modifier,
+ )
+}
\ 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 f0626bf715..6ad07afbf2 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,9 @@ object LogConfig {
object NetworkLogConfig {
const val mercuryoService: Boolean = false
const val moonPayService: Boolean = false
+ const val utorgService: Boolean = false
+ val tangemTechService: Boolean = BuildConfig.DEBUG
+ val paymentologyApiService: Boolean = BuildConfig.DEBUG
val blockchainSdkNetwork: Boolean = BuildConfig.DEBUG
}
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 58c03985a6..b18597f282 100644
--- a/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt
+++ b/domain/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt
@@ -6,8 +6,12 @@ import com.tangem.common.CardIdRange
import com.tangem.common.contains
object SaltPayWorkaround {
+
@Suppress("MagicNumber")
- val visaBatches = listOf("AE02", "AE03") + attachTestVisaBatches()
+ val visaBatches = listOf(
+ "AE02",
+ "AE03",
+ ) + attachTestVisaBatches()
val walletCardIds = listOf(
"AC01000000033503",
@@ -57,21 +61,24 @@ object SaltPayWorkaround {
}
}
+ @Suppress("MagicNumber")
+ fun isSaltPayCardId(cardId: String): Boolean = isVisaBatchId(cardId.take(4)) || isWalletCardId(cardId)
+
fun isVisaBatchId(batchId: String): Boolean = visaBatches.contains(batchId)
fun isWalletCardId(cardId: String): Boolean {
return if (walletCardIds.contains(cardId)) true else walletCardIdRanges.contains(cardId)
}
- private fun attachTestVisaBatches(): List {
- return listOf("FF03")
- }
+ private fun attachTestVisaBatches(): List = listOf(
+ "FF03",
+ )
- private fun attachTestWalletCardIds(): List {
- return listOf("FF04000000000232")
- }
+ private fun attachTestWalletCardIds(): List = listOf(
+ "FF04000000000232",
+ )
- private fun attachTestWalletCardIdRanges(): List {
- return listOf(CardIdRange("FF04000000000000", "FF04999999999999")!!)
- }
+ private fun attachTestWalletCardIdRanges(): List = listOf(
+ CardIdRange("FF04000000000000", "FF04999999999999")!!,
+ )
}
\ No newline at end of file