diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 34d26a35ae..b9af199725 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -194,6 +194,7 @@ dependencies {
}
implementation(Library.accompanistAppCompatTheme)
implementation(Library.accompanistSystemUiController)
+ implementation(Library.accompanistWebView)
implementation(Library.xmlShimmer)
implementation(Library.viewBindingDelegate)
implementation(Library.armadillo)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f09883d42e..0680d8b5e1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -138,9 +138,12 @@
-
+
+
@@ -147,7 +147,12 @@ class TapApplication : Application(), ImageLoaderFactory {
initConfigManager(configLoader, ::initWithConfigDependency)
initWarningMessagesManager()
- BlockchainSdkRetrofitBuilder.enableNetworkLogging = LogConfig.network.blockchainSdkNetwork
+ if (LogConfig.network.blockchainSdkNetwork) {
+ BlockchainSdkRetrofitBuilder.interceptors = listOf(
+ HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY },
+ )
+ }
+
userTokensRepository = UserTokensRepository.init(
context = this,
@@ -188,7 +193,7 @@ class TapApplication : Application(), ImageLoaderFactory {
private fun initWithConfigDependency(config: Config) {
shopService = TangemShopService(this, config.shopify!!)
initAnalytics(this, config)
- initFeedbackManager(this, preferencesStorage, config)
+ initFeedbackManager(this, preferencesStorage, foregroundActivityObserver, store)
}
private fun initAnalytics(application: Application, config: Config) {
@@ -210,7 +215,12 @@ class TapApplication : Application(), ImageLoaderFactory {
factory.build(Analytics, buildData)
}
- private fun initFeedbackManager(context: Context, preferencesStorage: PreferencesStorage, config: Config) {
+ private fun initFeedbackManager(
+ context: Context,
+ preferencesStorage: PreferencesStorage,
+ foregroundActivityObserver: ForegroundActivityObserver,
+ store: Store,
+ ) {
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo = AdditionalFeedbackInfo().apply {
appVersion = try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
@@ -243,12 +253,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..4d56172588
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/ShimmerRecyclerAdapter.kt
@@ -0,0 +1,33 @@
+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)
+
+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/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
index bad36d3f0d..4a07f6a669 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt
@@ -8,7 +8,7 @@ sealed class AnalyticsParam {
class Currency(currency: com.tangem.tap.features.wallet.models.Currency) : CurrencyType(currency.currencySymbol)
class Blockchain(blockchain: com.tangem.blockchain.common.Blockchain) : CurrencyType(blockchain.currency)
class Token(token: com.tangem.blockchain.common.Token) : CurrencyType(token.symbol)
- class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.symbol)
+ class FiatCurrency(fiatCurrency: com.tangem.tap.common.entities.FiatCurrency) : CurrencyType(fiatCurrency.code)
class Amount(amount: com.tangem.blockchain.common.Amount) : CurrencyType(amount.currencySymbol)
}
@@ -60,8 +60,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 ecdedd9761..7a7e9f245e 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt
@@ -11,6 +11,15 @@ sealed class Basic(
error: Throwable? = null,
) : AnalyticsEvent("Basic", event, params, error) {
+ class CardWasScanned(
+ source: AnalyticsParam.ScannedFrom,
+ ) : Basic(
+ event = "Card Was Scanned",
+ params = mapOf(
+ "Source" to source.value,
+ ),
+ )
+
class SignedIn(
state: AnalyticsParam.CardBalanceState,
currency: AnalyticsParam.CardCurrency,
@@ -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 8eb0f6490d..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,15 +67,25 @@ 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 ButtonChat : Onboarding("Onboarding", "Button - Chat")
+
+ class NotEnoughGasError : Onboarding("Onboarding", "Not Enough Gas Error")
+ class CardNotPassedError : Onboarding("Onboarding", "Card Not Passed Error")
class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding(
category = "Onboarding / Biometric",
diff --git a/app/src/main/java/com/tangem/tap/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..e723fb4a9e
--- /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)?.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.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/zendesk/ZendeskConfig.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt
similarity index 62%
rename from app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt
rename to app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt
index 66569ee1b7..01b80c8cc6 100644
--- a/app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt
+++ b/app/src/main/java/com/tangem/tap/common/chat/ChatConfig.kt
@@ -1,8 +1,10 @@
-package com.tangem.tap.common.zendesk
+package com.tangem.tap.common.chat
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
+sealed interface ChatConfig
+
@JsonClass(generateAdapter = true)
data class ZendeskConfig(
@Json(name = "zendeskApiKey")
@@ -15,4 +17,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/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..2646fe74c7
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt
@@ -0,0 +1,50 @@
+package com.tangem.tap.common.chat
+
+import android.content.Context
+import android.os.Build
+import com.tangem.tap.ForegroundActivityObserver
+import com.tangem.tap.common.chat.opener.ChatOpener
+import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener
+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..d5923097f4
--- /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.SprinklrConfig
+import com.tangem.tap.common.chat.opener.ChatOpener
+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..1c07657555
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt
@@ -0,0 +1,77 @@
+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.ZendeskConfig
+import com.tangem.tap.common.chat.opener.ChatOpener
+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/compose/Button.kt b/app/src/main/java/com/tangem/tap/common/compose/Button.kt
index b20f7470b8..fe5aa7979a 100644
--- a/app/src/main/java/com/tangem/tap/common/compose/Button.kt
+++ b/app/src/main/java/com/tangem/tap/common/compose/Button.kt
@@ -12,9 +12,7 @@ import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
-import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -133,18 +131,6 @@ fun ClearButton(
}
}
-/**
- * Used for disable ripple if button is enable = false
- */
-@Composable
-fun ToggledRippleTheme(
- isEnabled: Boolean,
- content: @Composable () -> Unit,
-) {
- val theme = LocalRippleTheme provides if (isEnabled) LocalRippleTheme.current else NoRippleTheme()
- CompositionLocalProvider(theme) { content() }
-}
-
@Preview
@Composable
fun ButtonTest() {
diff --git a/app/src/main/java/com/tangem/tap/common/compose/Theme.kt b/app/src/main/java/com/tangem/tap/common/compose/Theme.kt
deleted file mode 100644
index f0afe076ba..0000000000
--- a/app/src/main/java/com/tangem/tap/common/compose/Theme.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.tangem.tap.common.compose
-
-import androidx.compose.material.ripple.RippleAlpha
-import androidx.compose.material.ripple.RippleTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.Color
-
-/**
-[REDACTED_AUTHOR]
- */
-class NoRippleTheme : RippleTheme {
- @Composable
- override fun defaultColor() = Color.Unspecified
-
- @Composable
- override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt
new file mode 100644
index 0000000000..3772f86a71
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Analytics.kt
@@ -0,0 +1,16 @@
+package com.tangem.tap.common.extensions
+
+import com.tangem.core.analytics.Analytics
+import com.tangem.domain.common.ScanResponse
+import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
+
+/**
+[REDACTED_AUTHOR]
+ */
+fun Analytics.addCardContext(scanResponse: ScanResponse) {
+ addParamsInterceptor(CardContextInterceptor(scanResponse))
+}
+
+fun Analytics.eraseCardContext() {
+ removeParamsInterceptor(CardContextInterceptor.id())
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/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/UI.kt b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
index 9e58542ecb..885fd337e3 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/UI.kt
@@ -210,6 +210,7 @@ fun View.animateVisibility(
hiddenVisibility: Int = View.GONE,
) {
if (show) {
+ if (this.visibility == View.VISIBLE) return
this.animate()
.alpha(1f)
.setDuration(durationMillis)
@@ -218,6 +219,7 @@ fun View.animateVisibility(
this.isVisible = true
}
} else {
+ if (this.visibility == hiddenVisibility) return
this.animate()
.alpha(0f)
.setDuration(durationMillis)
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
index cda489f536..a07ef080da 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt
@@ -2,6 +2,9 @@ package com.tangem.tap.common.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkError
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.TransactionData
+import com.tangem.blockchain.common.TransactionHistoryProvider
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.services.Result
@@ -10,6 +13,7 @@ import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.features.demo.isDemoCard
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
import com.tangem.tap.network.NetworkConnectivity
@@ -30,6 +34,9 @@ suspend fun WalletManager.safeUpdate(): Result = try {
Result.Success(wallet)
} else {
update()
+ if (wallet.blockchain == Blockchain.SaltPay && this is TransactionHistoryProvider) {
+ this.getTransactionHistory(wallet.address, wallet.blockchain, wallet.getTokens())
+ }
Result.Success(wallet)
}
} catch (exception: Exception) {
@@ -76,10 +83,33 @@ fun WalletManager?.getAddressData(): AddressData? {
else addressDataList[0]
}
+fun WalletManager.getTxHistory(currency: Currency): List = wallet.getTxHistory(currency)
+
+fun WalletManager.getBlockchainTxHistory(): List = wallet.getBlockchainTxHistory()
+
+fun WalletManager.getTokenTxHistory(token: Token): List = wallet.getTokenTxHistory(token)
+
fun WalletManager.Companion.stub(): T {
val wallet = Wallet(Blockchain.Unknown, setOf(), Wallet.PublicKey(byteArrayOf(), null, null), setOf())
return object : WalletManager(wallet) {
override val currentHost: String = ""
override suspend fun update() {}
} as T
+}
+
+fun Wallet.getTxHistory(currency: Currency): List {
+ return (currency as? Currency.Token)?.let { this.getTokenTxHistory(it.token) }
+ ?: getBlockchainTxHistory()
+}
+
+fun Wallet.getBlockchainTxHistory(): List {
+ return historyTransactions.filter {
+ it.contractAddress.isNullOrEmpty()
+ }
+}
+
+fun Wallet.getTokenTxHistory(token: Token): List {
+ return historyTransactions.filter {
+ it.contractAddress == token.contractAddress
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
index b44db4004b..666d79ee81 100644
--- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
@@ -1,24 +1,14 @@
package com.tangem.tap.common.feedback
import android.content.Context
-import android.os.Build
-import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.TapWorkarounds
+import com.tangem.tap.common.chat.ChatConfig
+import com.tangem.tap.common.chat.ChatManager
import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.log.TangemLogCollector
-import com.tangem.tap.common.zendesk.ZendeskConfig
import com.tangem.tap.foregroundActivityObserver
-import com.tangem.tap.persistence.PreferencesStorage
import com.tangem.tap.withForegroundActivity
-import com.tangem.wallet.R
import timber.log.Timber
-import zendesk.chat.Chat
-import zendesk.chat.ChatConfiguration
-import zendesk.chat.ChatEngine
-import zendesk.chat.ChatProvidersConfiguration
-import zendesk.chat.VisitorInfo
-import zendesk.configurations.Configuration
-import zendesk.messaging.MessagingActivity
import java.io.File
import java.io.FileWriter
import java.io.StringWriter
@@ -29,19 +19,8 @@ import java.io.StringWriter
class FeedbackManager(
val infoHolder: AdditionalFeedbackInfo,
private val logCollector: TangemLogCollector,
- private val preferencesStorage: PreferencesStorage,
+ private val chatManager: ChatManager,
) {
- private var lastUsedConfigForInitialization: ZendeskConfig? = null
-
- var chatInitializer: ((ZendeskConfig) -> Unit)? = null
-
- fun initChat(zendeskConfig: ZendeskConfig) {
- // prevent double initialization with the same config
- if (lastUsedConfigForInitialization == zendeskConfig) return
-
- lastUsedConfigForInitialization = zendeskConfig
- chatInitializer?.invoke(zendeskConfig)
- }
fun sendEmail(feedbackData: FeedbackData, onFail: ((Exception) -> Unit)? = null) {
feedbackData.prepare(infoHolder)
@@ -57,12 +36,12 @@ class FeedbackManager(
}
}
- fun openChat(feedbackData: FeedbackData) {
- feedbackData.prepare(infoHolder)
- foregroundActivityObserver.withForegroundActivity { activity ->
- setChatVisitorInfo()
- setChatVisitorNote(activity, feedbackData)
- showMessagingActivity(activity)
+ fun openChat(config: ChatConfig, feedbackData: FeedbackData) {
+ chatManager.open(config) { context ->
+ feedbackData.run {
+ prepare(infoHolder)
+ joinTogether(context, infoHolder)
+ }
}
}
@@ -85,47 +64,6 @@ class FeedbackManager(
}
}
- private fun setChatVisitorInfo() {
- if (preferencesStorage.chatFirstLaunchTime == null) {
- preferencesStorage.chatFirstLaunchTime = System.currentTimeMillis()
- }
- val chatUserId = (preferencesStorage.chatFirstLaunchTime.toString() + Build.MODEL).hashCode()
- val visitorInfo = VisitorInfo.builder()
- .withName("User $chatUserId")
- .build()
-
- Chat.INSTANCE.chatProvidersConfiguration = ChatProvidersConfiguration.builder()
- .withVisitorInfo(visitorInfo)
- .build()
- }
-
- private fun setChatVisitorNote(
- context: Context,
- feedbackData: FeedbackData,
- ) {
- Chat.INSTANCE.providers()
- ?.profileProvider()
- ?.setVisitorNote(feedbackData.joinTogether(context, infoHolder))
- }
-
- private fun showMessagingActivity(context: Context) {
- Analytics.send(com.tangem.tap.common.analytics.events.Chat.ScreenOpened())
- MessagingActivity.builder()
- .withMultilineResponseOptionsEnabled(false)
- .withBotLabelStringRes(R.string.chat_bot_name)
- .withBotAvatarDrawable(R.mipmap.ic_launcher)
- .withEngines(ChatEngine.engine())
- .show(context, buildChatConfig())
- }
-
- private fun buildChatConfig(): Configuration {
- return ChatConfiguration.builder()
- .withOfflineFormEnabled(true)
- .withAgentAvailabilityEnabled(true)
- .withPreChatFormEnabled(false)
- .build()
- }
-
private fun getSupportEmail(): String {
return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) {
S2C_SUPPORT_EMAIL
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
index f03dfc70ca..3bcb47a092 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
@@ -13,11 +13,12 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWallet
import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
import com.tangem.tap.features.shop.redux.ShopReducer
+import com.tangem.tap.features.sprinklr.redux.SprinklrReducer
import com.tangem.tap.features.tokens.redux.TokensReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
-import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
import com.tangem.tap.features.welcome.redux.WelcomeReducer
+import com.tangem.tap.proxy.AppStateHolder
import org.rekotlin.Action
fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder): AppState {
@@ -42,6 +43,7 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
welcomeState = WelcomeReducer.reduce(action, state),
saveWalletState = SaveWalletReducer.reduce(action, state),
walletSelectorState = WalletSelectorReducer.reduce(action, state),
+ sprinklrState = SprinklrReducer.reduce(action, state),
)
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
index 9f7c34b90f..3e0d617fb2 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
@@ -31,6 +31,8 @@ import com.tangem.tap.features.send.redux.middlewares.SendMiddleware
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.shop.redux.ShopMiddleware
import com.tangem.tap.features.shop.redux.ShopState
+import com.tangem.tap.features.sprinklr.redux.SprinklrMiddleware
+import com.tangem.tap.features.sprinklr.redux.SprinklrState
import com.tangem.tap.features.tokens.redux.TokensMiddleware
import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
@@ -60,6 +62,7 @@ data class AppState(
val welcomeState: WelcomeState = WelcomeState(),
val saveWalletState: SaveWalletState = SaveWalletState(),
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
+ val sprinklrState: SprinklrState = SprinklrState(),
) : StateType {
val domainState: DomainState
@@ -94,6 +97,7 @@ data class AppState(
WalletSelectorMiddleware().middleware,
LockUserWalletsTimerMiddleware().middleware,
AccessCodeRequestPolicyMiddleware().middleware,
+ SprinklrMiddleware().middleware,
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
index 1f51374cae..dbd831e752 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
@@ -5,6 +5,7 @@ import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.domain.common.ScanResponse
+import com.tangem.tap.common.chat.ChatConfig
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.feedback.FeedbackData
import com.tangem.tap.common.feedback.FeedbackManager
@@ -13,7 +14,6 @@ import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.ToastNotificationAction
-import com.tangem.tap.common.zendesk.ZendeskConfig
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
@@ -36,18 +36,16 @@ sealed class GlobalAction : Action {
sealed class Onboarding : GlobalAction() {
/**
* Initiate an onboarding process.
- * For SaltPay cards it's additionally checks for unfinished backup.
- * For resuming unfinished backup for standard Wallet cards see CheckForUnfinishedBackup and
- * StartForUnfinishedBackup
+ * For resuming unfinished backup of standard Wallet and SaltPay cards see
+ * BackupAction.CheckForUnfinishedBackup, GlobalAction.Onboarding.StartForUnfinishedBackup
*/
data class Start(val scanResponse: ScanResponse, val canSkipBackup: Boolean = true) : Onboarding()
/**
- * Initiate resuming of unfinished backup only for standard Wallet cards.
- * For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start.
- * See more Onboarding.Start, CheckForUnfinishedBackup
+ * Initiate resuming of unfinished backup for standard Wallet and SaltPay cards.
+ * See more BackupAction.CheckForUnfinishedBackup
*/
- object StartForUnfinishedBackup : Onboarding()
+ data class StartForUnfinishedBackup(val isSaltPayVisa: Boolean) : Onboarding()
object Stop : Onboarding()
}
@@ -87,7 +85,7 @@ sealed class GlobalAction : Action {
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
data class SendEmail(val feedbackData: FeedbackData) : GlobalAction()
- data class OpenChat(val feedbackData: FeedbackData, val zendeskConfig: ZendeskConfig? = null) : GlobalAction()
+ data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction()
data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction()
object ExchangeManager : GlobalAction() {
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
index dfcdf942bf..4696b4a6f8 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
@@ -3,28 +3,37 @@ package com.tangem.tap.common.redux.global
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
-import com.tangem.common.extensions.ifNotNull
import com.tangem.common.services.Result
+import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.LogConfig
+import com.tangem.domain.common.ProductType
+import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.domain.configurable.config.Config
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.network.exchangeServices.BuyExchangeService
import com.tangem.tap.network.exchangeServices.CardExchangeRules
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
-import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoApi
+import com.tangem.tap.network.exchangeServices.ExchangeService
+import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
+import com.tangem.tap.network.exchangeServices.utorg.UtorgAuthProvider
+import com.tangem.tap.network.exchangeServices.utorg.UtorgEnvironment
+import com.tangem.tap.network.exchangeServices.utorg.UtorgExchangeService
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userTokensRepository
+import com.tangem.wallet.BuildConfig
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
@@ -100,17 +109,16 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
val scanResponse = globalState.scanResponse ?: globalState.onboardingState.onboardingManager?.scanResponse
// if config not set -> try to get it based on a scanResponse.productType
- val unsafeZendeskConfig = action.zendeskConfig ?: when {
- scanResponse?.isSaltPay() == true -> config.saltPayConfig?.zendesk
+ val unsafeChatConfig = action.chatConfig ?: when {
+ scanResponse?.isSaltPay() == true -> config.saltPayConfig?.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))
@@ -121,38 +129,23 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
is GlobalAction.ExchangeManager.Init -> {
val appStateSafe = appState() ?: return
- val config = appStateSafe.globalState.configManager?.config
- ifNotNull(
- config?.mercuryoWidgetId,
- config?.mercuryoSecret,
- config?.moonPayApiKey,
- config?.moonPayApiSecretKey,
- ) { mercuryoWidgetId, mercuryoSecret, moonPayKey, moonPaySecretKey ->
- scope.launch {
- val buyService = MercuryoService(
- apiVersion = MercuryoApi.API_VERSION,
- mercuryoWidgetId = mercuryoWidgetId,
- secret = mercuryoSecret,
- logEnabled = LogConfig.network.mercuryoService,
- )
- val sellService = MoonPayService(
- apiKey = moonPayKey,
- secretKey = moonPaySecretKey,
- logEnabled = LogConfig.network.moonPayService,
- )
- val cardProvider = {
- store.state.globalState.scanResponse?.card
- ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse?.card
- }
+ val config = appStateSafe.globalState.configManager?.config ?: return
- val exchangeManager = CurrencyExchangeManager(
- buyService = buyService,
- sellService = sellService,
- primaryRules = CardExchangeRules(cardProvider),
- )
- store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
- store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
+ scope.launch {
+ val scanResponseProvider: () -> ScanResponse? = {
+ store.state.globalState.scanResponse
+ ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
}
+ val productTypeProvider: () -> ProductType? = { scanResponseProvider.invoke()?.productType }
+ val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card }
+
+ val exchangeManager = CurrencyExchangeManager(
+ buyService = makeBuyExchangeService(config, productTypeProvider),
+ sellService = makeSellExchangeService(config),
+ primaryRules = CardExchangeRules(cardProvider),
+ )
+ store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
+ store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
}
}
is GlobalAction.ExchangeManager.Init.Success -> {}
@@ -207,4 +200,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 62e990eeff..df19afc1c4 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.domain
+import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.Token
@@ -39,6 +40,7 @@ import com.tangem.tap.userTokensRepository
import com.tangem.tap.walletStoresManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import org.rekotlin.Action
import timber.log.Timber
class TapWalletManager {
@@ -223,20 +225,20 @@ class TapWalletManager {
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
if (blockchain != Blockchain.Unknown && primaryWalletManager != null) {
- val primaryToken = data.getPrimaryToken()
-
- dispatchOnMain(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
- if (primaryToken != null) {
- primaryWalletManager.addToken(primaryToken)
- dispatchOnMain(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
- }
- dispatchOnMain(
+ val blockchainNetwork = BlockchainNetwork.fromWalletManager(primaryWalletManager)
+ val actionsList = listOfNotNull(
WalletAction.MultiWallet.AddBlockchains(
blockchains = listOf(BlockchainNetwork.fromWalletManager(primaryWalletManager)),
walletManagers = listOf(primaryWalletManager),
),
+ data.getPrimaryToken()?.let {
+ primaryWalletManager.addToken(it)
+ primaryWalletManager.wallet.setAmount(Amount(it))
+ WalletAction.MultiWallet.AddToken(it, blockchainNetwork, false)
+ },
WalletAction.LoadFiatRate(),
)
+ dispatchOnMain(*actionsList.toTypedArray())
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
index 8a2a6032cc..a45fc5c289 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
@@ -2,11 +2,12 @@ package com.tangem.tap.domain.configurable.config
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.BlockchairCredentials
+import com.tangem.blockchain.common.BlockscoutCredentials
import com.tangem.blockchain.common.GetBlockCredentials
import com.tangem.blockchain.common.NowNodeCredentials
import com.tangem.blockchain.common.QuickNodeCredentials
+import com.tangem.tap.common.chat.ZendeskConfig
import com.tangem.tap.common.shop.shopify.ShopifyShop
-import com.tangem.tap.common.zendesk.ZendeskConfig
import com.tangem.tap.domain.configurable.Loader
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
@@ -93,6 +94,10 @@ class ConfigManager {
authToken = values.blockchairAuthorizationToken,
),
blockcypherTokens = values.blockcypherTokens,
+ blockscoutCredentials = BlockscoutCredentials(
+ userName = values.saltPay.blockscoutCredentials.user,
+ password = values.saltPay.blockscoutCredentials.password,
+ ),
quickNodeSolanaCredentials = QuickNodeCredentials(
apiKey = values.quiknodeApiKey,
subdomain = values.quiknodeSubdomain,
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
index aa57ec09f7..4de75aae58 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.domain.configurable.config
+import com.tangem.tap.common.chat.ZendeskConfig
import com.tangem.tap.common.shop.shopify.ShopifyShop
-import com.tangem.tap.common.zendesk.ZendeskConfig
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
/**
@@ -15,6 +15,7 @@ class FeatureModel(
val isCreatingTwinCardsAllowed: Boolean,
)
+@Suppress("LongParameterList")
class ConfigValueModel(
val coinMarketCapKey: String,
val mercuryoWidgetId: String,
@@ -37,6 +38,7 @@ class ConfigValueModel(
val saltPay: SaltPayConfig,
val tronGridApiKey: String,
val amplitudeApiKey: String,
+ val swapReferrerAccount: SwapReferrerAccount?,
)
data class AppsFlyer(
@@ -44,6 +46,11 @@ data class AppsFlyer(
val appsFlyerAppID: String,
)
+data class SwapReferrerAccount(
+ val address: String,
+ val fee: String,
+)
+
class ConfigModel(
val features: FeatureModel?,
val configValues: ConfigValueModel?,
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
index 989793e59a..a2801c5a8f 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
@@ -1,19 +1,15 @@
package com.tangem.tap.domain.extensions
-import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.DerivationStyle
-import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.common.card.EllipticCurve
-import com.tangem.common.extensions.guard
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toMapKey
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.CardDTO
-import com.tangem.domain.common.SaltPayWorkaround
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
@@ -137,23 +133,4 @@ private fun selectWallet(wallets: List): CardDTO.Wallet? {
1 -> wallets[0]
else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
}
-}
-
-fun WalletManagerFactory.makeSaltPayWalletManager(
- scanResponse: ScanResponse,
-): EthereumWalletManager {
- val blockchain = scanResponse.getBlockchain()
- if (blockchain != Blockchain.SaltPay)
- throw IllegalArgumentException()
-
- val token = SaltPayWorkaround.tokenFrom(blockchain)
- val cardWallet = scanResponse.card.wallets.firstOrNull().guard {
- throw NullPointerException("SaltPay card must have one wallet at least")
- }
-
- return makeWalletManager(
- blockchain = blockchain,
- publicKey = Wallet.PublicKey(cardWallet.publicKey, null, null),
- tokens = listOf(token),
- ) as EthereumWalletManager
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/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 22cc834b2a..b5dff3de1f 100644
--- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt
+++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt
@@ -6,6 +6,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.ProductType
+import com.tangem.domain.common.SaltPayWorkaround
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TwinCardNumber
@@ -96,6 +97,7 @@ class UserWalletBuilder(
return when {
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
+ SaltPayWorkaround.isSaltPayCardId(cardId) -> Artwork.SALT_PAY_URL
else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
TwinCardNumber.First -> Artwork.TWIN_CARD_1
TwinCardNumber.Second -> Artwork.TWIN_CARD_2
diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt
index 651ab25018..5b4f90aea3 100644
--- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt
+++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt
@@ -6,6 +6,8 @@ import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.util.UserWalletId
+import com.tangem.tap.common.extensions.getBlockchainTxHistory
+import com.tangem.tap.common.extensions.getTokenTxHistory
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
@@ -98,6 +100,7 @@ private fun BlockchainNetwork.getBlockchainWalletData(walletManager: WalletManag
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = false,
+ historyTransactions = walletManager?.getBlockchainTxHistory(),
)
}
@@ -115,6 +118,7 @@ private fun BlockchainNetwork.getTokensWalletsData(walletManager: WalletManager?
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = walletManager?.cardTokens?.contains(token) ?: false,
+ historyTransactions = walletManager?.getTokenTxHistory(token),
)
}
}
@@ -131,6 +135,7 @@ private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): Wal
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = false,
+ historyTransactions = walletManager.getBlockchainTxHistory(),
)
}
@@ -147,6 +152,7 @@ private fun Token.toTokenWalletData(walletManager: WalletManager): WalletDataMod
existentialDeposit = getExistentialDeposit(walletManager),
fiatRate = null,
isCardSingleToken = walletManager.cardTokens.contains(this),
+ historyTransactions = walletManager.getTokenTxHistory(this),
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt
index 6caafef969..3317a70315 100644
--- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt
+++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt
@@ -13,7 +13,7 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.backup.BackupService
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.backupService
-import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor
+import com.tangem.tap.common.extensions.addCardContext
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa
import com.tangem.tap.common.redux.global.GlobalAction
@@ -27,10 +27,10 @@ import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
+import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
-import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
@@ -75,7 +75,7 @@ object ScanCardProcessor {
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
onScanStateChange(false)
- sendAnalytics(analyticsEvent, scanResponse.card.batchId)
+ sendAnalytics(analyticsEvent, scanResponse)
checkForUnfinishedBackupForSaltPay(
backupService = backupService,
@@ -103,9 +103,9 @@ object ScanCardProcessor {
private fun sendAnalytics(
analyticsEvent: AnalyticsEvent?,
- batchId: String,
+ scanResponse: ScanResponse,
) {
- Analytics.addParamsInterceptor(BatchIdParamsInterceptor(batchId))
+ Analytics.addCardContext(scanResponse)
analyticsEvent?.let { Analytics.send(it) }
}
@@ -187,7 +187,10 @@ object ScanCardProcessor {
if (scanResponse.isSaltPay()) {
if (scanResponse.isSaltPayVisa()) {
- val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
+ val manager = SaltPayActivationManagerFactory(
+ blockchain = scanResponse.getBlockchain(),
+ card = scanResponse.card,
+ ).create()
val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager)
delay(500)
withMainContext {
@@ -197,8 +200,8 @@ object ScanCardProcessor {
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)
diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt
index 93c70be9ef..66c90b9f9e 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt
@@ -20,6 +20,7 @@ import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.replaceByOrAdd
+import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.walletStores.WalletStoresError
@@ -32,6 +33,7 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update
import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates
import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithMissedDerivation
import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithRent
+import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithTxHistories
import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithUnreachable
import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage
import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage
@@ -203,7 +205,7 @@ internal class DefaultWalletAmountsRepository(
updateWalletStoreWithUnreachable(walletStore)
}
else -> {
- withInternetConnection { walletManager.update() }
+ withInternetConnection { walletManager.safeUpdate() }
.map { updateWalletManagerWithAmounts(userWalletId, walletManager) }
.flatMap {
updateWalletStoreWithAmounts(
@@ -318,7 +320,7 @@ internal class DefaultWalletAmountsRepository(
it.updateWithDemoAmounts(wallet = updatedWallet)
} else {
it.updateWithAmounts(wallet = updatedWallet)
- }
+ }.updateWithTxHistories(wallet = updatedWallet)
},
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt
index cb49fe33c1..48b0c4d71e 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt
@@ -4,6 +4,8 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.Wallet
import com.tangem.common.core.TangemError
+import com.tangem.tap.common.extensions.getBlockchainTxHistory
+import com.tangem.tap.common.extensions.getTokenTxHistory
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.model.WalletDataModel
@@ -29,6 +31,29 @@ internal fun List.updateWithFiatRates(
}
}
+internal fun WalletDataModel.updateWithTxHistory(wallet: Wallet): WalletDataModel {
+ return this.copy(
+ historyTransactions = when (currency) {
+ is Currency.Blockchain -> wallet.getBlockchainTxHistory()
+ is Currency.Token -> wallet.getTokenTxHistory(currency.token)
+ },
+ )
+}
+
+internal fun List.updateWithTxHistories(
+ wallet: Wallet,
+): List {
+ return this.map { walletData ->
+ walletData.updateWithTxHistory(wallet)
+ }
+}
+
+internal fun List.updateWithAmounts(wallet: Wallet): List {
+ return this.map { walletData ->
+ walletData.updateWithAmount(wallet)
+ }
+}
+
internal fun WalletDataModel.updateWithAmount(wallet: Wallet): WalletDataModel {
val pendingTransactions = wallet.getPendingTransactions()
return this.copy(
@@ -82,12 +107,6 @@ internal fun WalletDataModel.updateWithDemoAmount(wallet: Wallet): WalletDataMod
)
}
-internal fun List.updateWithAmounts(wallet: Wallet): List {
- return this.map { walletData ->
- walletData.updateWithAmount(wallet)
- }
-}
-
internal fun List.updateWithDemoAmounts(wallet: Wallet): List {
return this.map { walletData ->
walletData.updateWithDemoAmount(wallet)
diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt
index 0c3e47861c..4bf5394553 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt
@@ -41,6 +41,14 @@ internal fun WalletStoreModel.updateWithError(
)
}
+internal fun WalletStoreModel.updateWithTxHistories(
+ wallet: Wallet,
+): WalletStoreModel {
+ return this.copy(
+ walletsData = walletsData.updateWithTxHistories(wallet = wallet),
+ )
+}
+
internal fun WalletStoreModel.updateWithAmounts(
wallet: Wallet,
): WalletStoreModel {
diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
index b0d473cde0..d984e73f13 100644
--- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
+++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
@@ -49,6 +49,8 @@ object DemoHelper {
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
+ fun isTestDemoCard(scanResponse: ScanResponse): Boolean = config.isTestDemoCardId(scanResponse.card.cardId)
+
fun isDemoCardId(cardId: String): Boolean = config.isDemoCardId(cardId)
fun tryHandle(appState: () -> AppState?, action: Action): Boolean {
@@ -108,6 +110,8 @@ class DemoConfig {
fun isDemoCardId(cardId: String): Boolean = demoCardIds.contains(cardId)
+ fun isTestDemoCardId(cardId: String): Boolean = testDemoCardIds.contains(cardId)
+
fun getBalance(blockchain: Blockchain): Amount = walletBalances[blockchain]?.copy()
?: Amount(BigDecimal.ZERO, blockchain).copy()
@@ -117,9 +121,6 @@ class DemoConfig {
private val releaseDemoCardIds = mutableListOf(
// === Not from the Google Sheet table ===
- "FB10000000000196", // Note BTC
- "FB20000000000186", // Note ETH
- "FB30000000000176", // Wallet
"AC01000000041225",
"AC01000000041472",
"AB01000000046498",
@@ -489,17 +490,19 @@ class DemoConfig {
class DemoTransactionSender(
private val walletManager: WalletManager,
- private val sender: TransactionSender = walletManager as TransactionSender
+ private val sender: TransactionSender = walletManager as TransactionSender,
) : TransactionSender {
override suspend fun getFee(amount: Amount, destination: String): Result> {
val blockchain = walletManager.wallet.blockchain
return when (walletManager) {
- is BitcoinWalletManager -> Result.Success(listOf(
- Amount(0.0001.toBigDecimal(), blockchain),
- Amount(0.0003.toBigDecimal(), blockchain),
- Amount(0.00055.toBigDecimal(), blockchain),
- ))
+ is BitcoinWalletManager -> Result.Success(
+ listOf(
+ Amount(0.0001.toBigDecimal(), blockchain),
+ Amount(0.0003.toBigDecimal(), blockchain),
+ Amount(0.00055.toBigDecimal(), blockchain),
+ ),
+ )
else -> sender.getFee(amount, destination)
}
}
@@ -508,7 +511,7 @@ class DemoTransactionSender(
val dataToSign = randomString(32).toByteArray()
val signerResponse = signer.sign(
hash = dataToSign,
- publicKey = walletManager.wallet.publicKey
+ publicKey = walletManager.wallet.publicKey,
)
return when (signerResponse) {
is CompletionResult.Success -> SimpleResult.Failure(Exception(ID).toBlockchainSdkError())
@@ -529,5 +532,4 @@ class DemoTransactionSender(
companion object {
val ID = DemoTransactionSender::class.java.simpleName
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
index 60dc234e4f..3395d8b1f0 100644
--- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt
@@ -29,7 +29,7 @@ class HomeFragment : Fragment(), StoreSubscriber {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- Analytics.send(IntroductionProcess.ScreenOpened())
+ store.dispatch(HomeAction.OnCreate)
store.dispatch(HomeAction.Init)
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt
index 59e169ee04..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 76048ed0f4..afcb5c7a14 100644
--- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt
@@ -5,10 +5,12 @@ import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.AnalyticsEvent
+import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
+import com.tangem.tap.common.extensions.eraseCardContext
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
@@ -51,6 +53,10 @@ private val homeMiddleware: Middleware = { _, _ ->
private fun handleHomeAction(action: Action) {
when (action) {
+ is HomeAction.OnCreate -> {
+ Analytics.eraseCardContext()
+ Analytics.send(IntroductionProcess.ScreenOpened())
+ }
is HomeAction.Init -> {
store.dispatch(GlobalAction.RestoreAppCurrency)
store.dispatch(GlobalAction.ExchangeManager.Init)
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt
index 209fc789eb..8c88cc7a84 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt
@@ -20,14 +20,14 @@ class OnboardingSaltPayHelper {
manager: SaltPayActivationManager,
): Result {
return try {
- var updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it }
+ val updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it }
val cardStorage = preferencesStorage.usedCardsPrefStorage
val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId)
- if (updatedStep == SaltPayActivationStep.Success && activationIsFinished) {
- updatedStep = SaltPayActivationStep.Finished
+ if (updatedStep == SaltPayActivationStep.Success && !activationIsFinished) {
+ cardStorage.activationFinished(scanResponse.card.cardId)
}
- val isActivationCase = updatedStep != SaltPayActivationStep.Finished
+ val isActivationCase = updatedStep != SaltPayActivationStep.Success
val isBackupCase = scanResponse.card.backupStatus?.isActive == false
Result.Success(isActivationCase || isBackupCase)
} catch (ex: Exception) {
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt
index f3ff070f97..42848b47b9 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt
@@ -30,9 +30,8 @@ sealed class BackupAction : Action {
object ScanPrimaryCard : BackupAction()
/**
- * Check for unfinished backup of standard Wallet cards.
- * For SaltPay cards unfinished backup resumed after scanning the card on HomeScreen through Onboarding.Start.
- * See more Onboarding.Start, CheckForUnfinishedBackup, StartForUnfinishedBackup
+ * Check for unfinished backup of standard Wallets and SaltPay cards
+ * See more GlobalAction.Onboarding.StartForUnfinishedBackup
*/
object CheckForUnfinishedBackup : BackupAction()
@@ -64,7 +63,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 939236339c..3397e56980 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
@@ -3,13 +3,13 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.ifNotNull
+import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.CardDTO
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.backup.BackupService
import com.tangem.tap.backupService
-import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
@@ -22,8 +22,8 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.onboarding.OnboardingHelper
+import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
-import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.preferencesStorage
@@ -71,7 +71,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
}
when {
card == null -> {
- // it's possible when found unfinished backup for standard Wallet cards
+ // it's possible when found unfinished backup
store.dispatch(OnboardingWalletAction.ResumeBackup)
}
card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> {
@@ -86,10 +86,11 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
scanResponse.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 -> {
@@ -314,7 +315,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
is CompletionResult.Success -> {
if (backupService.currentState == BackupService.State.Finished) {
initSaltPayOnBackupFinishedIfNeeded(scanResponse, onboardingWalletState)
- store.dispatchOnMain(BackupAction.FinishBackup)
+ store.dispatchOnMain(BackupAction.FinishBackup())
} else {
store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1))
}
@@ -325,7 +326,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)
@@ -342,18 +345,22 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
backupService.discardSavedBackup()
}
is BackupAction.CheckForUnfinishedBackup -> {
- if (backupService.hasIncompletedBackup && !backupService.primaryCardIsSaltPayVisa()) {
+ if (backupService.hasIncompletedBackup) {
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound))
}
}
is BackupAction.ResumeFoundUnfinishedBackup -> {
- store.dispatch(GlobalAction.Onboarding.StartForUnfinishedBackup)
+ store.dispatch(
+ GlobalAction.Onboarding.StartForUnfinishedBackup(
+ isSaltPayVisa = backupService.primaryCardIsSaltPayVisa(),
+ ),
+ )
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
}
is BackupAction.DismissBackup -> {
Analytics.send(Onboarding.Backup.Skipped())
if (onboardingWalletState.isSaltPay) throw UnsupportedOperationException()
- store.dispatch(BackupAction.FinishBackup)
+ store.dispatch(BackupAction.FinishBackup())
}
}
}
@@ -396,9 +403,12 @@ private fun initSaltPayOnBackupFinishedIfNeeded(
if (onboardingWalletState.isSaltPay && onboardingWalletState.onboardingSaltPayState == null) {
if (scanResponse == null) throw IllegalArgumentException()
- val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
- store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager, config))
- store.dispatchOnMain(OnboardingSaltPayAction.Update)
+ val manager = SaltPayActivationManagerFactory(
+ blockchain = scanResponse.getBlockchain(),
+ card = scanResponse.card,
+ ).create()
+ store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager))
+ store.dispatchOnMain(OnboardingSaltPayAction.Update(false))
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt
index 8cca79e9dd..cb5242af39 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt
@@ -43,7 +43,7 @@ private class ReducerForGlobalAction {
is GlobalAction.Onboarding.Start -> {
OnboardingWalletState(
backupState = state.backupState.copy(
- maxBackupCards = if (action.scanResponse.isSaltPay()) 1 else 2,
+ maxBackupCards = maxBackupCards(action.scanResponse.isSaltPay()),
canSkipBackup = if (action.scanResponse.isSaltPay()) false else action.canSkipBackup,
),
isSaltPay = action.scanResponse.isSaltPay(),
@@ -52,15 +52,17 @@ private class ReducerForGlobalAction {
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
OnboardingWalletState(
backupState = state.backupState.copy(
- maxBackupCards = 2,
+ maxBackupCards = maxBackupCards(action.isSaltPayVisa),
canSkipBackup = false,
),
- isSaltPay = false,
+ isSaltPay = action.isSaltPayVisa,
)
}
else -> state
}
}
+
+ private fun maxBackupCards(isSaltPay: Boolean): Int = if (isSaltPay) 1 else 2
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt
index 4e3cad3657..314d4ea06b 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt
@@ -60,7 +60,7 @@ data class OnboardingWalletState(
-> 9
SaltPayActivationStep.Claim -> 10
SaltPayActivationStep.ClaimInProgress -> 11
- SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success, SaltPayActivationStep.Finished,
+ SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success,
-> getMaxProgress()
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt
index ddf80bdc07..85a64112cb 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt
@@ -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,12 @@ class GnosisRegistrator(
return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error))
}
}
+
+ // return transferFromStandardWay(amountToClaim, signer)
+ return transferFromHardcodeWay(amountToClaim, signer)
+ }
+
+ private suspend fun transferFromStandardWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result {
val amount = Amount(token, amountToClaim)
val feeAmount = walletManager.getFeeToTransferFrom(amount, addressTreasureSafe)
.extractFeeAmount().successOr { return it }
@@ -164,6 +175,34 @@ class GnosisRegistrator(
return Result.Success(Unit)
}
+ private suspend fun transferFromHardcodeWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result {
+ val amount = Amount(token, amountToClaim)
+ val gasPrice = walletManager.getGasPrice().successOr { return it }
+
+ val hardcodeGasLimit = BigInteger("300000")
+ val hardcodeFeeValue = (hardcodeGasLimit * gasPrice).toBigDecimal(
+ scale = blockchain.decimals(),
+ mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN),
+ ).movePointLeft(blockchain.decimals())
+ val hardcodeFeeAmount = Amount(hardcodeFeeValue, blockchain)
+
+ val transactionData = walletManager.createTransferFromTransaction(
+ amount = amount,
+ fee = hardcodeFeeAmount,
+ source = addressTreasureSafe,
+ )
+ val transactionToSign = walletManager.transactionBuilder.buildTransferFromToSign(
+ transactionData,
+ walletManager.txCount.toBigInteger(),
+ hardcodeGasLimit,
+ ) ?: return Result.Failure(BlockchainSdkError.CustomError("Not enough data"))
+
+ return when (val result = walletManager.signAndSend(transactionToSign, signer)) {
+ SimpleResult.Success -> Result.Success(Unit)
+ is SimpleResult.Failure -> Result.Failure(result.error)
+ }
+ }
+
private fun Result>.extractFeeAmount(): Result {
return when (this) {
is Result.Success -> {
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt
index ff6ec003d6..1ae879918a 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt
@@ -1,14 +1,20 @@
package com.tangem.tap.features.onboarding.products.wallet.saltPay
import android.net.Uri
+import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkError
+import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.TransactionSigner
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.extensions.successOr
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
+import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.isZero
import com.tangem.common.services.Result
import com.tangem.datasource.api.paymentology.AttestationResponse
@@ -18,12 +24,15 @@ import com.tangem.datasource.api.paymentology.RegisterWalletRequest
import com.tangem.datasource.api.paymentology.RegisterWalletResponse
import com.tangem.datasource.api.paymentology.RegistrationResponse
import com.tangem.datasource.api.paymentology.tryExtractError
+import com.tangem.domain.common.CardDTO
+import com.tangem.domain.common.SaltPayWorkaround
import com.tangem.domain.common.extensions.successOr
import com.tangem.operations.attestation.AttestWalletKeyResponse
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
+import com.tangem.tap.store
import java.math.BigDecimal
/**
@@ -200,4 +209,96 @@ class KYCUrlProvider(
.appendQueryParameter(kycProvider.externalIdParameterKey, kycRefId)
.build().toString()
}
+}
+
+class SaltPayActivationManagerFactory(
+ private val blockchain: Blockchain,
+ private val card: CardDTO,
+) {
+
+ fun create(): SaltPayActivationManager {
+ val wallet = card.wallets.first()
+ val saltPayConfig = store.state.globalState.configManager?.config?.saltPayConfig.guard {
+ throw NullPointerException("SaltPayConfig is not initialized")
+ }
+ val wmFactory = store.state.globalState.tapWalletManager.walletManagerFactory
+ val paymentologyService = store.state.domainNetworks.paymentologyService
+
+ // return createDummyActivationManager(saltPayConfig, wmFactory, paymentologyService)
+ return createActivationPayManager(
+ cardId = card.cardId,
+ cardPublicKey = card.cardPublicKey,
+ kycProvider = saltPayConfig.kycProvider,
+ gnosisRegistrator = GnosisRegistrator(
+ walletManager = makeSaltPayWalletManager(
+ blockchain = blockchain,
+ walletPublicKey = wallet.publicKey,
+ wmFactory = wmFactory,
+ ),
+ ),
+ paymentologyService = paymentologyService,
+ )
+ }
+
+ private fun makeSaltPayWalletManager(
+ blockchain: Blockchain,
+ walletPublicKey: ByteArray,
+ wmFactory: WalletManagerFactory,
+ ): EthereumWalletManager {
+ return wmFactory.makeWalletManager(
+ blockchain = blockchain,
+ publicKey = Wallet.PublicKey(walletPublicKey, null, null),
+ tokens = listOf(SaltPayWorkaround.tokenFrom(blockchain)),
+ ) as EthereumWalletManager
+ }
+
+ private fun createActivationPayManager(
+ cardId: String,
+ cardPublicKey: ByteArray,
+ kycProvider: KYCProvider,
+ gnosisRegistrator: GnosisRegistrator,
+ paymentologyService: PaymentologyApiService,
+ ): SaltPayActivationManager {
+ return SaltPayActivationManager(
+ cardId = cardId,
+ cardPublicKey = cardPublicKey,
+ kycProvider = kycProvider,
+ paymentologyService = paymentologyService,
+ gnosisRegistrator = gnosisRegistrator,
+ )
+ }
+
+ private fun createDummyActivationManager(
+ saltPayConfig: SaltPayConfig,
+ wmFactory: WalletManagerFactory,
+ paymentologyService: PaymentologyApiService,
+ ): SaltPayActivationManager {
+ val fakeSource = DummySaltPayCardSource()
+
+ val walletManager = wmFactory.makeWalletManager(
+ blockchain = fakeSource.blockchain,
+ publicKey = fakeSource.blockchainPublicKey,
+ tokens = listOf(fakeSource.token),
+ ) as EthereumWalletManager
+
+ return createActivationPayManager(
+ cardId = fakeSource.cardId,
+ cardPublicKey = fakeSource.cardPublicKey,
+ kycProvider = saltPayConfig.kycProvider,
+ gnosisRegistrator = GnosisRegistrator(walletManager),
+ paymentologyService = paymentologyService,
+ )
+ }
+
+ private data class DummySaltPayCardSource(
+ val blockchain: Blockchain = Blockchain.SaltPay,
+ val cardId: String = "FF03000001001057",
+ private val cardPublicKeyString: String = "039C2D2F9B68003766BFC29766761F55F4084E48B8B012BF439E115A79AB122D48",
+ private val walletPublicKeyString: String = "0399B2DF5B129FBF69097DAA7C44FB5849E578CCFC22228A409870C74B56C4C3D1",
+ ) {
+ val cardPublicKey: ByteArray = cardPublicKeyString.hexToBytes()
+ val walletPublicKey: ByteArray = walletPublicKeyString.hexToBytes()
+ val blockchainPublicKey = Wallet.PublicKey(walletPublicKey, null, null)
+ val token: Token = SaltPayWorkaround.tokenFrom(blockchain)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt
index 19048168de..1bb872eebd 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayConfig.kt
@@ -1,22 +1,24 @@
package com.tangem.tap.features.onboarding.products.wallet.saltPay
-import com.tangem.tap.common.zendesk.ZendeskConfig
+import com.tangem.tap.common.chat.SprinklrConfig
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/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt
index 0f0b207431..e215cbfe92 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt
@@ -2,6 +2,8 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.core.TangemSdkError
+import com.tangem.core.analytics.Analytics
+import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
@@ -17,8 +19,15 @@ class SaltPayExceptionHandler {
when (throwable) {
is SaltPayActivationError -> {
val dialog = when (throwable) {
- is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas
+ is SaltPayActivationError.NoGas -> {
+ Analytics.send(Onboarding.NotEnoughGasError())
+ SaltPayDialog.Activation.NoGas
+ }
is SaltPayActivationError.PutVisaCard -> SaltPayDialog.Activation.PutVisaCard
+ is SaltPayActivationError.CardNotPassed -> {
+ Analytics.send(Onboarding.CardNotPassedError())
+ SaltPayDialog.Activation.OnError(throwable)
+ }
else -> SaltPayDialog.Activation.OnError(throwable)
}
store.dispatchDialogShow(dialog)
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt
index 579f34d614..a75c0e9e5c 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt
@@ -19,13 +19,15 @@ class NoFundsForActivationDialog {
return AlertDialog.Builder(context).apply {
setTitle(R.string.saltpay_error_no_gas_title)
setMessage(R.string.saltpay_error_no_gas_message)
- //TODO: SaltPay: change onboarding_supplement_button_kyc_waiting -> to appropriate string
- setPositiveButton(R.string.onboarding_supplement_button_kyc_waiting) { _, _ ->
- val config = store.state.globalState.configManager?.config?.saltPayConfig?.zendesk.guard {
- store.dispatchDebugErrorNotification("SaltPayConfig not initialized")
- return@setPositiveButton
- }
- store.dispatch(GlobalAction.OpenChat(SupportInfo(), config))
+ setPositiveButton(R.string.chat_button_title) { _, _ ->
+ 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 a2194e9568..1fbb284524 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt
@@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux
import com.tangem.blockchain.common.Amount
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
-import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
import org.rekotlin.Action
import java.math.BigDecimal
@@ -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()
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt
index dfdfb619b5..33e9d7fa1b 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt
@@ -79,6 +79,9 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
onboardingManager.activationStarted(card.cardId)
}
}
+ is OnboardingSaltPayAction.OnSwitchedToSaltPayProcess -> {
+ sendAnalyticsScreenOpened(getState().step, newStep = SaltPayActivationStep.None)
+ }
is OnboardingSaltPayAction.Update -> {
handleInProgress = true
@@ -90,7 +93,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 -> {
@@ -137,6 +151,7 @@ private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> App
return@launch
}
+ Analytics.send(Onboarding.PinCodeSet())
handleInProgress = false
dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycIntro))
}
@@ -160,13 +175,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) {
@@ -220,54 +235,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 -> {
@@ -276,6 +285,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 +387,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 71570fd52e..e6ea4af732 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt
@@ -21,7 +21,6 @@ private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingW
return onboardingWalletState.copy(
onboardingSaltPayState = OnboardingSaltPayState(
saltPayManager = action.registrationManager,
- saltPayConfig = action.saltPayConfig,
),
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt
index a14ad10252..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.domain.common.SaltPayWorkaround
-import com.tangem.domain.common.ScanResponse
-import com.tangem.datasource.api.paymentology.PaymentologyApiService
import com.tangem.tap.common.toggleWidget.WidgetState
-import com.tangem.tap.domain.extensions.makeSaltPayWalletManager
-import com.tangem.tap.features.onboarding.products.wallet.saltPay.GnosisRegistrator
-import com.tangem.tap.features.onboarding.products.wallet.saltPay.KYCProvider
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager
-import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
import com.tangem.tap.features.wallet.redux.ProgressState
-import com.tangem.tap.store
import java.math.BigDecimal
/**
@@ -23,7 +14,6 @@ import java.math.BigDecimal
data class OnboardingSaltPayState(
@Transient
val saltPayManager: SaltPayActivationManager = SaltPayActivationManager.stub(),
- val saltPayConfig: SaltPayConfig = SaltPayConfig.stub(),
val pinCode: String? = null,
val accessCode: String? = null,
val amountToClaim: Amount? = null,
@@ -37,57 +27,7 @@ data class OnboardingSaltPayState(
val mainButtonState: WidgetState
get() = if (inProgress) ProgressState.Loading else ProgressState.Done
- fun readyToClaim(): Boolean = amountToClaim != null
-
val pinLength: Int = 4
-
- companion object {
- fun initDependency(scanResponse: ScanResponse): Pair {
- val globalState = store.state.globalState
- val saltPayConfig = globalState.configManager?.config?.saltPayConfig.guard {
- throw NullPointerException("SaltPayConfig is not initialized")
- }
- val gnosisRegistrator = makeGnosisRegistrator(
- scanResponse = scanResponse,
- wmFactory = globalState.tapWalletManager.walletManagerFactory,
- )
- val registrationManager = makeSaltPayRegistrationManager(
- scanResponse = scanResponse,
- gnosisRegistrator = gnosisRegistrator,
- paymentologyService = store.state.domainNetworks.paymentologyService,
- kycProvider = saltPayConfig.kycProvider,
- )
- return registrationManager to saltPayConfig
- }
-
- fun makeSaltPayRegistrationManager(
- scanResponse: ScanResponse,
- gnosisRegistrator: GnosisRegistrator,
- paymentologyService: PaymentologyApiService,
- kycProvider: KYCProvider,
- ): SaltPayActivationManager {
- if (!scanResponse.isSaltPay()) {
- throw IllegalArgumentException("Can't initialize the OnboardingSaltPayMiddleware if card is not SalPay")
- }
-
- return SaltPayActivationManager(
- cardId = scanResponse.card.cardId,
- cardPublicKey = scanResponse.card.cardPublicKey,
- kycProvider = kycProvider,
- paymentologyService = paymentologyService,
- gnosisRegistrator = gnosisRegistrator,
- )
- }
-
- fun makeGnosisRegistrator(
- scanResponse: ScanResponse,
- wmFactory: WalletManagerFactory,
- ): GnosisRegistrator {
- return GnosisRegistrator(
- walletManager = wmFactory.makeSaltPayWalletManager(scanResponse),
- )
- }
- }
}
enum class SaltPayActivationStep {
@@ -103,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 84e012cad9..812151698a 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayView.kt
@@ -80,6 +80,7 @@ internal class OnboardingSaltPayView(
if (state.backupState.backupStep == BackupStep.Finished) {
// if backup finished -> switch OnboardingWalletStep to the SaltPay
store.dispatch(OnboardingWalletAction.GetToSaltPayStep)
+ store.dispatch(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess)
} else {
// if not -> back to the standard backup process
walletFragment.handleOnboardingStep(state)
@@ -99,7 +100,7 @@ internal class OnboardingSaltPayView(
} else {
walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard)
}
- imvFirstBackupCard.load(R.drawable.img_salt_pay_visa)
+ imvFirstBackupCard.load(R.drawable.card_placeholder_wallet)
// if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) {
// //if saltPay url not loaded -> load from resource
@@ -228,7 +229,7 @@ internal class OnboardingSaltPayView(
btnKycAction.text = getText(R.string.onboarding_button_kyc_waiting)
btnKycAction.setOnClickListener {
- store.dispatch(OnboardingSaltPayAction.Update)
+ store.dispatch(OnboardingSaltPayAction.Update())
}
progressButton = SaltPayProgressButton(root)
}
@@ -244,7 +245,7 @@ internal class OnboardingSaltPayView(
btnOpenSupportChat.hide()
btnKycAction.text = getText(R.string.onboarding_button_kyc_start)
btnKycAction.setOnClickListener {
- store.dispatch(OnboardingSaltPayAction.Update)
+ store.dispatch(OnboardingSaltPayAction.Update())
}
progressButton = SaltPayProgressButton(root)
}
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..1505a42f3b
--- /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.tap.common.chat.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..b9b2b41cde
--- /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.tap.common.chat.SprinklrConfig
+import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.features.sprinklr.redux.model.SprinklrUrl
+import com.tangem.tap.store
+import org.rekotlin.Middleware
+
+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..28b3053441
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrActivity.kt
@@ -0,0 +1,42 @@
+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(modifier: Modifier, state: SprinklrScreenState) {
+ BackHandler(onBack = state.onNavigateBack)
+ SprinklrScreenContent(
+ modifier = modifier
+ .systemBarsPadding()
+ .imePadding(),
+ state = state,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt
new file mode 100644
index 0000000000..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 93066fc926..aa6d1ea2d8 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt
@@ -55,7 +55,6 @@ import com.tangem.domain.redux.domainStore
import com.tangem.tap.common.compose.AddCustomTokenWarning
import com.tangem.tap.common.compose.ClosePopupTrigger
import com.tangem.tap.common.compose.ComposeDialogManager
-import com.tangem.tap.common.compose.ToggledRippleTheme
import com.tangem.tap.common.compose.keyboardAsState
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCase
@@ -66,7 +65,7 @@ import kotlinx.coroutines.launch
/**
[REDACTED_AUTHOR]
*/
-private class AddCustomTokenScreen {} // for simple search
+private class AddCustomTokenScreen // for simple search
@OptIn(ExperimentalMaterialApi::class)
@Composable
@@ -77,7 +76,7 @@ fun AddCustomTokenScreen(
val selectedTestCase = remember { mutableStateOf(TestCase.ContractAddress) }
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
- bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
+ bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed),
)
val coroutineScope = rememberCoroutineScope()
val toggleBottomSheet = { coroutineScope.launch { bottomSheetScaffoldState.toggle() } }
@@ -91,11 +90,13 @@ fun AddCustomTokenScreen(
},
sheetPeekHeight = 0.dp,
) {
- Column() {
- TestCasesList(onItemClick = {
- selectedTestCase.value = it
- toggleBottomSheet()
- })
+ Column {
+ TestCasesList(
+ onItemClick = {
+ selectedTestCase.value = it
+ toggleBottomSheet()
+ },
+ )
ScreenContent(state, closePopupTrigger)
}
}
@@ -144,7 +145,7 @@ private fun ScreenContent(
Column(
modifier = Modifier
.fillMaxWidth()
- .padding(16.dp)
+ .padding(16.dp),
) {
FormFields(state, closePopupTrigger)
}
@@ -195,7 +196,7 @@ fun Warnings(warnings: List) {
AddCustomTokenWarning(
modifier = modifier.fillMaxWidth(),
warning = item,
- converter = warningConverter
+ converter = warningConverter,
)
}
}
@@ -206,7 +207,7 @@ private fun AddButton(state: MutableState) {
AddCustomTokenFab(
modifier = Modifier
.widthIn(210.dp, 280.dp),
- isEnabled = state.value.screenState.addButton.isEnabled
+ isEnabled = state.value.screenState.addButton.isEnabled,
) { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) }
}
@@ -214,7 +215,7 @@ private fun AddButton(state: MutableState) {
private fun AddCustomTokenFab(
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
- onClick: () -> Unit
+ onClick: () -> Unit,
) {
val contentColor = Color.White
val backgroundColor = if (isEnabled) {
@@ -228,42 +229,40 @@ private fun AddCustomTokenFab(
FloatingActionButtonDefaults.elevation()
}
- ToggledRippleTheme(isEnabled) {
- ExtendedFloatingActionButton(
- modifier = modifier,
- icon = {
- Icon(
- imageVector = Icons.Filled.Add,
- tint = contentColor,
- contentDescription = "Add",
- )
- },
- text = { Text(text = stringResource(id = R.string.common_add)) },
- onClick = { if (isEnabled) onClick() },
- backgroundColor = backgroundColor,
- contentColor = contentColor,
- elevation = elevation,
- )
- }
+ ExtendedFloatingActionButton(
+ modifier = modifier,
+ icon = {
+ Icon(
+ imageVector = Icons.Filled.Add,
+ tint = contentColor,
+ contentDescription = "Add",
+ )
+ },
+ text = { Text(text = stringResource(id = R.string.common_add)) },
+ onClick = { if (isEnabled) onClick() },
+ backgroundColor = backgroundColor,
+ contentColor = contentColor,
+ elevation = elevation,
+ )
}
data class ScreenFieldData(
val field: DataField<*>,
val error: AddCustomTokenError?,
val errorConverter: ModuleMessageConverter,
- val viewState: ViewStates.TokenField
+ val viewState: ViewStates.TokenField,
) {
companion object {
fun fromState(
field: DataField<*>,
state: AddCustomTokenState,
- errorConverter: ModuleMessageConverter
+ errorConverter: ModuleMessageConverter,
): ScreenFieldData {
return ScreenFieldData(
field = field,
error = state.getError(field.id),
errorConverter = errorConverter,
- viewState = selectField(field.id, state.screenState)
+ viewState = selectField(field.id, state.screenState),
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
index dc3e074fac..af3edbfebd 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
@@ -2,7 +2,6 @@ package com.tangem.tap.features.wallet.redux
import android.content.Context
import com.tangem.blockchain.common.Amount
-import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
@@ -98,8 +97,6 @@ sealed class WalletAction : Action {
data class RemoveWallet(val currency: Currency) : MultiWallet()
data class RemoveWallets(val currencies: List) : MultiWallet()
- data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
- data class SetPrimaryToken(val token: Token) : MultiWallet()
data class ShowWalletBackupWarning(val show: Boolean) : MultiWallet()
object BackupWallet : MultiWallet()
object ScheduleCheckForMissingDerivation : MultiWallet()
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 0566b5d4a4..7e7f8cc3c2 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
@@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux
import android.graphics.Bitmap
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
@@ -12,6 +13,7 @@ import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.toggleWidget.WidgetState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
+import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
import com.tangem.tap.features.wallet.models.Currency
@@ -43,8 +45,6 @@ data class WalletState(
val isMultiwalletAllowed: Boolean = false,
val cardCurrency: CryptoCurrencyName? = null,
val selectedCurrency: Currency? = null,
- val primaryBlockchain: Blockchain? = null,
- val primaryToken: Token? = null,
val isTestnet: Boolean = false,
val totalBalance: TotalBalance? = null,
val showBackupWarning: Boolean = false,
@@ -81,13 +81,30 @@ data class WalletState(
val walletManagers: List
get() = walletsStores.mapNotNull { it.walletManager }
- val primaryWallet: WalletData? = walletsStores.firstOrNull()?.walletsData?.firstOrNull()
+ private val primaryWalletStore: WalletStore?
+ get() = if (isMultiwalletAllowed || walletsStores.isEmpty() || walletsStores.size > 1) null
+ else walletsStores[0]
- val primaryWalletManager: WalletManager? = if (walletsStores.isNotEmpty()) walletsStores[0].walletManager else null
+ val primaryWalletManager: WalletManager?
+ get() = primaryWalletStore?.walletManager
+
+ val primaryWalletData: WalletData?
+ get() = primaryWalletStore?.walletsData?.firstOrNull()
+
+ val primaryBlockchain: Blockchain?
+ get() = primaryWalletManager?.wallet?.blockchain
+
+ val primaryToken: Token?
+ get() = primaryWalletManager?.wallet?.getFirstToken()
+
+ val primaryTokenData: WalletData?
+ get() = primaryWalletStore?.walletsData?.toMutableList()
+ ?.apply { remove(primaryWalletData) }
+ ?.firstOrNull()
val shouldShowDetails: Boolean =
- primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
- primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
+ primaryWalletData?.currencyData?.status != BalanceStatus.EmptyCard &&
+ primaryWalletData?.currencyData?.status != BalanceStatus.UnknownBlockchain
val hasSavedWallets: Boolean
get() = userWalletsListManager.hasSavedUserWallets
@@ -138,13 +155,6 @@ data class WalletState(
return getWalletStore(currency)?.walletsData?.firstOrNull { it.currency == currency }
}
- private fun isPrimaryCurrency(walletData: WalletData): Boolean {
- return (walletData.currency is Currency.Blockchain &&
- walletData.currency.blockchain == store.state.walletState.primaryBlockchain)
- || (walletData.currency is Currency.Token &&
- walletData.currency.token == store.state.walletState.primaryToken)
- }
-
fun replaceWalletStoreInWalletsStores(wallet: WalletStore?): List {
if (wallet == null) return walletsStores
var changed = false
@@ -311,11 +321,13 @@ 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"
}
}
data class WalletData(
val pendingTransactions: List = emptyList(),
+ val historyTransactions: List? = null,
val hashesCountVerified: Boolean? = null,
val walletAddresses: WalletAddresses? = null,
val currencyData: BalanceWidgetData = BalanceWidgetData(),
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt
index 155ce6ebd7..518b4aba41 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt
@@ -1,6 +1,5 @@
package com.tangem.tap.features.wallet.redux.middlewares
-import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.extensions.toFiatRateString
@@ -19,7 +18,6 @@ import com.tangem.tap.features.wallet.redux.WalletMainButton
import com.tangem.tap.features.wallet.redux.WalletStore
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
-import com.tangem.tap.features.wallet.ui.TokenData
import com.tangem.tap.store
import java.math.BigDecimal
@@ -49,15 +47,6 @@ internal fun WalletStoreModel.mapToReduxModel(): WalletStore {
walletsData = walletsData.mapToReduxModels(walletRent, appCurrencySymbol),
)
.updateTokenModels(blockchainWalletData.status.amount)
- .setupIfHadCardSingleToken(
- blockchain = blockchain,
- walletsDataModel = walletsData,
- appCurrencySymbol = appCurrencySymbol,
- blockchainWalletData = blockchainWalletData.mapToReduxModel(
- walletRent = walletRent,
- appCurrencySymbol = appCurrencySymbol,
- ),
- )
}
private fun List.mapToReduxModels(
@@ -122,12 +111,12 @@ private fun WalletDataModel.mapToReduxModel(
amountFormatted = amountFormatted,
fiatAmount = fiatAmount,
fiatAmountFormatted = fiatAmountFormatted,
- token = null,
amountToCreateAccount = (status as? WalletDataModel.NoAccount)
?.amountToCreateAccount
?.toString(),
errorMessage = status.errorMessage,
),
+ historyTransactions = historyTransactions,
)
}
@@ -146,45 +135,4 @@ private fun WalletStore.updateTokenModels(blockchainAmount: BigDecimal): WalletS
)
}
return updateWallets(updatedTokensWalletData)
-}
-
-private fun WalletStore.setupIfHadCardSingleToken(
- blockchain: Blockchain,
- walletsDataModel: List,
- appCurrencySymbol: String,
- blockchainWalletData: WalletData,
-): WalletStore {
- // Card with single token contains only 2 model - blockchain and token
- if (walletsData.size != 2) return this
-
- val cardSingleTokenWalletData = walletsDataModel.firstOrNull {
- it.currency.isToken() && it.currency.blockchain == blockchain && it.isCardSingleToken
- } ?: return this
-
- val blockchainWalletDataWithSingleToken = blockchainWalletData.copy(
- currencyData = blockchainWalletData.currencyData.copy(
- token = cardSingleTokenWalletData.toTokenData(appCurrencySymbol),
- ),
- )
- return updateWallets(listOf(blockchainWalletDataWithSingleToken))
-}
-
-private fun WalletDataModel.toTokenData(appCurrencySymbol: String): TokenData {
- val amount = status.amount
- val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) }
-
- return TokenData(
- amount = amount,
- amountFormatted = amount.toFormattedCurrencyString(
- decimals = currency.decimals,
- currency = currency.currencySymbol,
- ),
- fiatAmount = fiatAmount,
- fiatAmountFormatted = fiatAmount
- ?.takeIf { !status.isErrorStatus }
- ?.toFormattedFiatValue(appCurrencySymbol),
- tokenSymbol = currency.currencySymbol,
- fiatRate = fiatRate,
- fiatRateString = fiatRate?.toFiatRateString(appCurrencySymbol),
- )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
index 33b7f54788..e896251b09 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.AmountType
+import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Token
@@ -17,6 +18,7 @@ import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
import com.tangem.tap.scope
@@ -40,10 +42,9 @@ class TradeCryptoMiddleware {
action: WalletAction.TradeCryptoAction.Buy,
) {
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
+ val currency = chooseAppropriateCurrency(store.state.walletState) ?: return
- val currency = selectedWalletData.currency
Analytics.send(Token.ButtonBuy(AnalyticsParam.CurrencyType.Currency(currency)))
-
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog())
return
@@ -87,12 +88,12 @@ class TradeCryptoMiddleware {
private fun proceedSellAction() {
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
+ val currency = chooseAppropriateCurrency(store.state.walletState) ?: return
val appCurrency = store.state.globalState.appCurrency
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
if (addresses.isEmpty()) return
- val currency = selectedWalletData.currency
Analytics.send(Token.ButtonSell(AnalyticsParam.CurrencyType.Currency(currency)))
store.state.globalState.exchangeManager.getUrl(
@@ -107,6 +108,17 @@ class TradeCryptoMiddleware {
}
}
+ private fun chooseAppropriateCurrency(walletState: WalletState): Currency? {
+ return if (walletState.primaryTokenData == null) {
+ walletState.selectedWalletData?.currency
+ } else {
+ walletState.primaryTokenData?.currency as? Currency.Token
+ }.guard {
+ store.dispatchDebugErrorNotification("Can't select an appropriate currency for a Trade action")
+ return null
+ }
+ }
+
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
index 03b26092b2..22aba65cfc 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
@@ -15,6 +15,7 @@ import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.analytics.converters.BasicEventsPreChecker
import com.tangem.tap.common.analytics.converters.BasicEventsSourceData
import com.tangem.tap.common.analytics.events.AnalyticsParam
+import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.copyToClipboard
@@ -49,6 +50,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletStore
+import com.tangem.tap.features.wallet.redux.reducers.findSelectedCurrency
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.network.NetworkStateChanged
import com.tangem.tap.preferencesStorage
@@ -153,7 +155,7 @@ class WalletMiddleware {
if (walletState.isMultiwalletAllowed) {
walletState.walletsDataFromStores.map { it.currency }
} else {
- val derivationPath = walletState.primaryWallet?.currency?.derivationPath
+ val derivationPath = walletState.primaryWalletData?.currency?.derivationPath
val primaryBlockchain = walletState.primaryBlockchain
val primaryToken = walletState.primaryToken
listOfNotNull(
@@ -248,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) }
@@ -310,15 +314,14 @@ class WalletMiddleware {
}
}
- private fun updateWalletStores(wallStores: List, state: WalletState) {
+ private fun updateWalletStores(walletsStores: List, state: WalletState) {
scope.launch(Dispatchers.Default) {
+ val reduxWalletStores = walletsStores.mapToReduxModels()
if (!state.isMultiwalletAllowed) {
- wallStores.firstOrNull()?.walletsData?.firstOrNull()?.let {
- store.dispatchOnMain(WalletAction.MultiWallet.SetSingleWalletCurrency(it.currency))
+ findSelectedCurrency(reduxWalletStores, null, false)?.let {
+ store.dispatchOnMain(WalletAction.MultiWallet.SetSingleWalletCurrency(it))
}
}
-
- val reduxWalletStores = wallStores.mapToReduxModels()
store.dispatchOnMain(
WalletAction.WalletStoresChanged.UpdateWalletStores(
reduxWalletStores = reduxWalletStores,
@@ -376,7 +379,7 @@ class WalletMiddleware {
}
else -> {
Analytics.send(MainScreen.ButtonScanCard())
- store.dispatch(WalletAction.Scan(MainScreen.CardWasScanned()))
+ store.dispatch(WalletAction.Scan(Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main)))
}
}
}
@@ -438,11 +441,7 @@ class WalletMiddleware {
walletStore: WalletStore?,
): PrepareSendScreen {
val coinRate = state?.getWalletData(walletStore?.blockchainNetwork)?.fiatRate
- val tokenRate = if (state?.isMultiwalletAllowed == true) {
- selectedWalletData?.fiatRate
- } else {
- selectedWalletData?.currencyData?.token?.fiatRate
- }
+ val tokenRate = selectedWalletData?.fiatRate
val coinAmount = walletStore?.walletManager?.wallet?.amounts?.get(AmountType.Coin)
return PrepareSendScreen(
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 711373d52f..7fb4fb8c8a 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt
@@ -6,11 +6,12 @@ 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
import com.tangem.tap.common.redux.navigation.NavigationAction
-import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.WalletRent
@@ -26,7 +27,6 @@ import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT
import com.tangem.tap.features.wallet.redux.WalletStore
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
-import com.tangem.tap.features.wallet.ui.TokenData
import com.tangem.tap.store
import com.tangem.tap.userWalletsListManager
import com.tangem.wallet.R
@@ -42,17 +42,11 @@ class MultiWalletReducer {
(it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath)
}
val wallet = walletManager?.wallet
- val cardToken = if (!state.isMultiwalletAllowed) {
- wallet?.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) }
- } else {
- null
- }
val walletData = WalletData(
currencyData = BalanceWidgetData(
status = BalanceStatus.Loading,
currency = blockchain.blockchain.fullName,
currencySymbol = blockchain.blockchain.currency,
- token = cardToken,
),
walletAddresses = createAddressList(wallet),
mainButton = WalletMainButton.SendButton(false),
@@ -61,23 +55,23 @@ class MultiWalletReducer {
blockchain.derivationPath,
),
existentialDepositString = getExistentialDeposit(walletManager),
+ historyTransactions = walletManager?.getBlockchainTxHistory(),
)
WalletStore(
walletManager = walletManager,
blockchainNetwork = blockchain,
- walletsData = listOf(walletData),
+ walletsData = listOfNotNull(walletData),
)
}
- val selectedCurrency = if (state.isMultiwalletAllowed) {
- state.selectedCurrency
- } else {
- walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency
- }
state.copy(
walletsStores = walletStores,
- selectedCurrency = selectedCurrency,
+ selectedCurrency = findSelectedCurrency(
+ walletsStores = walletStores,
+ currentSelectedCurrency = state.selectedCurrency,
+ isMultiWalletAllowed = state.isMultiwalletAllowed,
+ ),
)
}
is WalletAction.MultiWallet.AddBlockchain -> {
@@ -97,6 +91,7 @@ class MultiWalletReducer {
action.blockchain.derivationPath,
),
existentialDepositString = getExistentialDeposit(walletManager),
+ historyTransactions = walletManager?.getBlockchainTxHistory(),
)
val walletStore = WalletStore(
walletManager = walletManager,
@@ -160,6 +155,7 @@ class MultiWalletReducer {
derivationPath = action.blockchain.derivationPath,
),
walletRent = findWalletRent(state.getWalletStore(walletManager.wallet)),
+ historyTransactions = walletManager.getTokenTxHistory(action.token),
)
state.updateWalletData(newTokenWalletData)
}
@@ -181,8 +177,6 @@ class MultiWalletReducer {
action.currencies.forEach { updatedState = updatedState.removeWalletData(state.getWalletData(it)) }
updatedState
}
- is WalletAction.MultiWallet.SetPrimaryBlockchain -> state.copy(primaryBlockchain = action.blockchain)
- is WalletAction.MultiWallet.SetPrimaryToken -> state.copy(primaryToken = action.token)
is WalletAction.MultiWallet.SaveCurrencies -> state
is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy(showBackupWarning = action.show)
is WalletAction.MultiWallet.ScheduleCheckForMissingDerivation -> state.copy(
@@ -190,7 +184,7 @@ class MultiWalletReducer {
)
is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(
missingDerivations = action.blockchains,
- derivationsCheckIsScheduled = false
+ derivationsCheckIsScheduled = false,
)
is WalletAction.MultiWallet.BackupWallet -> state
is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy(state = ProgressState.Loading)
@@ -212,12 +206,11 @@ class MultiWalletReducer {
}
fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletData? {
- if (!state.isMultiwalletAllowed) return null
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(
@@ -228,5 +221,6 @@ fun Token.toWallet(state: WalletState, blockchain: BlockchainNetwork): WalletDat
walletAddresses = walletAddresses,
mainButton = WalletMainButton.SendButton(false),
currency = currency,
+ historyTransactions = walletManager?.getTokenTxHistory(this),
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
index 68e6080883..4648ebdd54 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
@@ -3,11 +3,9 @@ package com.tangem.tap.features.wallet.redux.reducers
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Wallet
import com.tangem.common.extensions.isZero
-import com.tangem.tap.common.extensions.toFiatString
import com.tangem.tap.common.extensions.toFiatValue
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.common.extensions.toFormattedFiatValue
-import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.filterByToken
@@ -20,9 +18,7 @@ import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT
import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
-import com.tangem.tap.features.wallet.ui.TokenData
import com.tangem.tap.store
-import java.math.BigDecimal
class OnWalletLoadedReducer {
@@ -37,7 +33,7 @@ class OnWalletLoadedReducer {
private fun onMultiWalletLoaded(
wallet: Wallet,
blockchainNetwork: BlockchainNetwork,
- walletState: WalletState
+ walletState: WalletState,
): WalletState {
val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState
@@ -45,7 +41,7 @@ class OnWalletLoadedReducer {
val coinAmountValue = wallet.amounts[AmountType.Coin]?.value
val formattedAmount = coinAmountValue?.toFormattedCurrencyString(
wallet.blockchain.decimals(),
- wallet.blockchain.currency
+ wallet.blockchain.currency,
)
val pendingTransactions = wallet.getPendingTransactions()
@@ -98,7 +94,7 @@ class OnWalletLoadedReducer {
amount = tokenAmountValue,
amountFormatted = tokenAmountValue?.toFormattedCurrencyString(
token.decimals,
- token.symbol
+ token.symbol,
),
fiatAmount = tokenFiatAmount,
fiatAmountFormatted = tokenFiatAmountFormatted,
@@ -117,33 +113,12 @@ class OnWalletLoadedReducer {
if (wallet.blockchain != walletState.primaryBlockchain) return walletState
val fiatCurrencyName = store.state.globalState.appCurrency.code
- val token = wallet.getFirstToken()
- val tokenData = if (token != null) {
- val tokenAmount = wallet.getTokenAmount(token)
- if (tokenAmount != null) {
- val tokenFiatRate = walletState.primaryWallet?.currencyData?.token?.fiatRate
- val tokenFiatAmount =
- tokenFiatRate?.let { tokenAmount.value?.toFiatString(it, fiatCurrencyName) }
- TokenData(
- amount = tokenAmount.value ?: BigDecimal.ZERO,
- tokenSymbol = tokenAmount.currencySymbol, fiatAmountFormatted = tokenFiatAmount,
- fiatAmount = tokenFiatRate?.let { tokenAmount.value?.toFiatValue(tokenFiatRate) },
- amountFormatted = tokenAmount.value?.toFormattedCurrencyString(
- token.decimals, token.symbol,
- ) ?: "",
- )
- } else {
- null
- }
- } else {
- null
- }
val amount = wallet.amounts[AmountType.Coin]?.value
val formattedAmount = amount?.toFormattedCurrencyString(
wallet.blockchain.decimals(),
- wallet.blockchain.currency
+ wallet.blockchain.currency,
)
- val fiatAmount = walletState.primaryWallet?.fiatRate?.let { amount?.toFiatValue(it) }
+ val fiatAmount = walletState.primaryWalletData?.fiatRate?.let { amount?.toFiatValue(it) }
val fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrencyName) ?: UNKNOWN_AMOUNT_SIGN
val pendingTransactions = wallet.getPendingTransactions()
@@ -153,16 +128,15 @@ class OnWalletLoadedReducer {
} else {
BalanceStatus.VerifiedOnline
}
- val walletData = walletState.primaryWallet?.copy(
+ val walletData = walletState.primaryWalletData?.copy(
currencyData = BalanceWidgetData(
balanceStatus, wallet.blockchain.fullName,
currencySymbol = wallet.blockchain.currency,
- token = tokenData,
blockchainAmount = amount,
amount = amount,
amountFormatted = formattedAmount,
fiatAmount = fiatAmount,
- fiatAmountFormatted = fiatAmountFormatted
+ fiatAmountFormatted = fiatAmountFormatted,
),
pendingTransactions = pendingTransactions.removeUnknownTransactions(),
mainButton = WalletMainButton.SendButton(sendButtonEnabled),
@@ -171,7 +145,8 @@ class OnWalletLoadedReducer {
val updatedStore = walletState.getWalletStore(walletData?.currency)?.updateWallets(wallets)
return walletState.updateWalletStore(updatedStore).copy(
- state = ProgressState.Done, error = null
+ state = ProgressState.Done,
+ error = null,
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt
index 08a7def4bd..9a8f4ded04 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/TotalBalanceOperations.kt
@@ -5,7 +5,9 @@ import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.ui.BalanceStatus
import java.math.BigDecimal
-fun List.findProgressState(): ProgressState {
+fun List.findProgressState(initialState: ProgressState = ProgressState.Done): ProgressState {
+ if (this.isEmpty()) return initialState
+
return this
.mapToProgressState()
.reduce(ProgressState::or)
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
index 2505702c20..2653083ecf 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
@@ -9,7 +9,6 @@ import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.toFiatRateString
-import com.tangem.tap.common.extensions.toFiatString
import com.tangem.tap.common.extensions.toFiatValue
import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.common.extensions.toFormattedFiatValue
@@ -17,7 +16,6 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.getArtworkUrl
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
-import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.WalletRent
@@ -36,7 +34,6 @@ import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.proxy.AppStateHolder
import org.rekotlin.Action
-import timber.log.Timber
import java.math.BigDecimal
class WalletReducer {
@@ -285,8 +282,9 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
newState = newState.copy(cardImage = cardImage)
}
- is WalletAction.LoadFiatRate.Success ->
+ is WalletAction.LoadFiatRate.Success -> {
newState = setNewFiatRate(action.fiatRates, state.globalState.appCurrency, newState)
+ }
is WalletAction.LoadArtwork -> {
val artworkUrl = action.card.getArtworkUrl(action.artworkId)
?: when (state.twinCardsState.cardNumber) {
@@ -352,6 +350,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
}
is WalletAction.WalletStoresChanged.UpdateWalletStores -> {
newState = newState.copy(
+ state = action.reduxWalletStores.flatMap { it.walletsData }.findProgressState(newState.state),
walletsStores = action.reduxWalletStores,
)
}
@@ -361,18 +360,13 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
)
}
is WalletAction.LoadData.Success -> {
- val selectedCurrency = if (newState.isMultiwalletAllowed) {
- newState.selectedCurrency
- } else {
- newState.walletsStores.firstOrNull()
- ?.walletsData
- ?.firstOrNull()
- ?.currency
- }
-
newState = newState.copy(
state = ProgressState.Done,
- selectedCurrency = selectedCurrency,
+ selectedCurrency = findSelectedCurrency(
+ walletsStores = newState.walletsStores,
+ currentSelectedCurrency = newState.selectedCurrency,
+ isMultiWalletAllowed = newState.isMultiwalletAllowed,
+ ),
)
}
else -> Unit
@@ -381,6 +375,19 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS
return newState
}
+fun findSelectedCurrency(
+ walletsStores: List,
+ currentSelectedCurrency: Currency?,
+ isMultiWalletAllowed: Boolean,
+): Currency? = if (isMultiWalletAllowed) {
+ currentSelectedCurrency
+} else {
+ walletsStores.firstOrNull()
+ ?.walletsData
+ ?.firstOrNull()
+ ?.currency
+}
+
private fun CardDTO.findCardsCount(): Int? {
return (this.backupStatus as? CardDTO.BackupStatus.Active)?.cardCount?.inc()
?.takeIf { this.isMultiwalletAllowed }
@@ -439,45 +446,18 @@ private fun setNewFiatRate(
state: WalletState,
): WalletState {
val rateFormatter: (BigDecimal) -> String = { rate: BigDecimal ->
- rate.toFiatRateString(
- fiatCurrencyName = appCurrency.symbol,
- )
+ rate.toFiatRateString(fiatCurrencyName = appCurrency.symbol)
}
- return if (state.isMultiwalletAllowed) {
- setMultiWalletFiatRate(
- fiatRates = fiatRates.mapNotNullValues { it.value },
- rateFormatter = rateFormatter,
- appCurrency = appCurrency,
- state = state,
- )
- } else {
- setSingleWalletFiatRates(
- fiatRates = fiatRates.mapNotNullValues { it.value },
- rateFormatter = rateFormatter,
- appCurrency = appCurrency,
- state = state,
- )
- }
-}
-
-private fun setMultiWalletFiatRate(
- fiatRates: Map,
- rateFormatter: (BigDecimal) -> String,
- appCurrency: FiatCurrency,
- state: WalletState,
-): WalletState {
- val newWalletsData = fiatRates.mapNotNull { (currency, rate) ->
+ val newWalletsData = fiatRates.mapNotNullValues { it.value }.mapNotNull { (currency, rate) ->
val walletStore = state.getWalletStore(currency) ?: return@mapNotNull null
val wallet = walletStore.walletManager?.wallet
val walletData = state.getWalletData(currency) ?: return@mapNotNull null
val currencyData = walletData.currencyData
var fiatAmount = when (currency) {
- is Currency.Blockchain ->
- wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatValue(rate)
- is Currency.Token ->
- wallet?.getTokenAmount(currency.token)?.value?.toFiatValue(rate)
+ is Currency.Blockchain -> wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatValue(rate)
+ is Currency.Token -> wallet?.getTokenAmount(currency.token)?.value?.toFiatValue(rate)
}
if (currencyData.status == BalanceStatus.NoAccount && fiatAmount == null) {
fiatAmount = BigDecimal.ZERO.setScale(2)
@@ -494,96 +474,5 @@ private fun setMultiWalletFiatRate(
)
}
- return state
- .updateWalletsData(newWalletsData)
-}
-
-private fun setSingleWalletFiatRates(
- fiatRates: Map,
- appCurrency: FiatCurrency,
- rateFormatter: (BigDecimal) -> String,
- state: WalletState,
-): WalletState {
- val blockchainFiatRate = fiatRates.entries.firstOrNull { it.key.isBlockchain() }
- val tokenFiatRate = fiatRates.entries.firstOrNull { it.key.isToken() }
- Timber.e("Token Fiat Rate is ${tokenFiatRate ?: "NULL"}")
- val updatedState = updateStateWithFiatRate(blockchainFiatRate?.toPair(), appCurrency, rateFormatter, state)
- return updateStateWithFiatRate(tokenFiatRate?.toPair(), appCurrency, rateFormatter, updatedState)
-}
-
-private fun updateStateWithFiatRate(
- fiatRate: Pair?,
- appCurrency: FiatCurrency,
- rateFormatter: (BigDecimal) -> String,
- state: WalletState,
-): WalletState {
- return if (fiatRate != null) {
- val currency = fiatRate.first
- val rate = fiatRate.second
-
- setSingleWalletFiatRate(
- rate = rate,
- rateFormatted = rateFormatter(rate),
- currency = currency,
- appCurrency = appCurrency,
- state = state,
- )
- } else {
- state
- }
-}
-
-private fun setSingleWalletFiatRate(
- rate: BigDecimal,
- rateFormatted: String,
- currency: Currency,
- appCurrency: FiatCurrency,
- state: WalletState,
-): WalletState {
- val wallet = state.primaryWalletManager?.wallet ?: return state
- val token = wallet.getFirstToken()
- Timber.e("Working with currency: ${currency.currencyName}")
-
- if (currency == state.primaryWallet?.currency) {
- val fiatAmount = wallet.amounts[AmountType.Coin]?.value
- ?.toFiatString(rate, appCurrency.code)
- val walletData = state.primaryWallet.copy(
- currencyData = state.primaryWallet.currencyData.copy(fiatAmountFormatted = fiatAmount),
- fiatRate = rate,
- fiatRateString = rateFormatted,
- )
- return state.updateWalletData(walletData)
- } else if (currency is Currency.Token && currency.token == token) {
- Timber.e("Working with token fiat rate")
-
- val tokenFiatAmount = wallet.getTokenAmount(token)
- ?.value
- val tokenAmountFormatted = tokenFiatAmount?.toFiatString(rate, appCurrency.code)
-
- val tokenData = state.primaryWallet?.currencyData?.token?.copy(
- fiatAmountFormatted = tokenAmountFormatted,
- fiatAmount = tokenFiatAmount,
- fiatRate = rate,
- fiatRateString = rateFormatted,
- )
- // ?: TokenData(
- // fiatAmountFormatted = tokenAmountFormatted,
- // fiatAmount = tokenFiatAmount,
- // fiatRate = rate,
- // fiatRateString = rateFormatted,
- // amount = "",
- // tokenSymbol = currency.currencySymbol
- // )
-
- Timber.e("Token Data is ${tokenData ?: "NULL"}")
-
- val walletData = state.primaryWallet?.copy(
- currencyData = state.primaryWallet.currencyData.copy(
- token = tokenData,
- ),
- )
- Timber.e("Wallet Data is ${walletData ?: "NULL"}")
- return state.updateWalletData(walletData)
- }
- return state
+ return state.updateWalletsData(newWalletsData)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt
index 4afc4c08db..9ffd48f58e 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt
@@ -24,31 +24,20 @@ data class BalanceWidgetData(
val status: BalanceStatus? = null,
val currency: String? = null,
val currencySymbol: String? = null,
- val blockchainAmount: BigDecimal? = BigDecimal.ZERO,
val amount: BigDecimal? = null,
val amountFormatted: String? = null,
val fiatAmount: BigDecimal? = null,
val fiatAmountFormatted: String? = null,
- val token: TokenData? = null,
+ val blockchainAmount: BigDecimal? = BigDecimal.ZERO,
val amountToCreateAccount: String? = null,
- val errorMessage: String? = null
+ val errorMessage: String? = null,
)
-data class TokenData(
- val amountFormatted: String?,
- val amount: BigDecimal? = null,
- val tokenSymbol: String,
- val fiatAmountFormatted: String? = null,
- val fiatAmount: BigDecimal? = null,
- val fiatRateString: String? = null,
- val fiatRate: BigDecimal? = null,
-)
-
-
class BalanceWidget(
private val binding: CardBalanceBinding,
private val fragment: WalletFragment,
private val data: BalanceWidgetData,
+ private val token: BalanceWidgetData?,
private val isTwinCard: Boolean,
) {
@@ -68,7 +57,7 @@ class BalanceWidget(
showStatus(R.id.tv_status_loading)
- if (data.token != null) {
+ if (token != null) {
showBalanceWithToken(data, false)
} else {
showBalanceWithoutToken(data, false)
@@ -87,7 +76,7 @@ class BalanceWidget(
showStatus(statusView)
tvStatusErrorMessage.hide()
- if (data.token != null) {
+ if (token != null) {
showBalanceWithToken(data, true)
} else {
showBalanceWithoutToken(data, true)
@@ -99,7 +88,7 @@ class BalanceWidget(
tvFiatAmount.hide()
groupBaseCurrency.hide()
- val currency = if (data.token != null) data.token.tokenSymbol else data.currency
+ val currency = if (token != null) token.currencySymbol else data.currency
tvCurrency.text = currency
tvAmount.text = ""
@@ -130,7 +119,8 @@ class BalanceWidget(
tvErrorDescriptions.text =
fragment.getString(
R.string.no_account_generic,
- data.amountToCreateAccount, data.currencySymbol
+ data.amountToCreateAccount,
+ data.currencySymbol,
)
}
BalanceStatus.UnknownBlockchain -> with(binding.lBalanceError) {
@@ -152,13 +142,13 @@ class BalanceWidget(
private fun showBalanceWithToken(data: BalanceWidgetData, showAmount: Boolean) = with(binding.lBalance) {
groupBaseCurrency.show()
- tvCurrency.text = data.token?.tokenSymbol
+ tvCurrency.text = token?.currencySymbol
tvBaseCurrency.text = data.currency
- tvAmount.text = if (showAmount) data.token?.amountFormatted else ""
+ tvAmount.text = if (showAmount) token?.amountFormatted else ""
tvBaseAmount.text = if (showAmount) data.amountFormatted else ""
if (showAmount) {
tvFiatAmount.show()
- tvFiatAmount.text = data.token?.fiatAmountFormatted
+ tvFiatAmount.text = token?.fiatAmountFormatted
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
index e38d9d947b..ba1fd0b30f 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
@@ -435,7 +435,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
lBalanceError.tvErrorDescriptions.text =
getString(
R.string.no_account_generic,
- data.amountToCreateAccount, data.currencySymbol,
+ data.amountToCreateAccount,
+ data.currencySymbol,
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
index 15eacf8c99..ab946481ec 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt
@@ -39,9 +39,9 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
-import com.tangem.tap.features.wallet.ui.wallet.SaltPaySingleWalletView
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
@@ -149,16 +149,19 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber {
- walletView = SaltPaySingleWalletView()
+ isSaltPay && (walletView !is SaltPayWalletView) -> {
+ walletView.onViewDestroy()
+ walletView = SaltPayWalletView()
walletView.changeWalletView(this, binding)
}
- state.isMultiwalletAllowed && state.primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
+ state.isMultiwalletAllowed && state.primaryWalletData?.currencyData?.status != BalanceStatus.EmptyCard &&
walletView !is MultiWalletView -> {
+ walletView.onViewDestroy()
walletView = MultiWalletView()
walletView.changeWalletView(this, binding)
}
!state.isMultiwalletAllowed && !isSaltPay && walletView !is SingleWalletView -> {
+ walletView.onViewDestroy()
walletView = SingleWalletView()
walletView.changeWalletView(this, binding)
}
@@ -174,7 +177,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber {
showErrorState(
binding,
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt
deleted file mode 100644
index 49fcb89c16..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPayBalanceWidget.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.tangem.tap.features.wallet.ui.wallet
-
-import com.tangem.domain.common.extensions.debounce
-import com.tangem.tap.common.entities.FiatCurrency
-import com.tangem.tap.common.extensions.animateVisibility
-import com.tangem.tap.common.extensions.formatAmountAsSpannedString
-import com.tangem.tap.features.wallet.redux.ProgressState
-import com.tangem.tap.features.wallet.redux.WalletAction
-import com.tangem.tap.mainScope
-import com.tangem.tap.store
-import com.tangem.wallet.databinding.LayoutSingleWalletBalanceBinding
-import org.rekotlin.Action
-import java.math.BigDecimal
-
-data class SaltPayBalanceWidgetData(
- val state: ProgressState? = null,
- val currencySymbol: String? = null,
- val currency: String? = null,
- val fiatAmount: BigDecimal? = null,
- val fiatCurrency: FiatCurrency? = null,
-)
-
-class SaltPayBalanceWidget(
- private val binding: LayoutSingleWalletBalanceBinding,
- private val data: SaltPayBalanceWidgetData,
-) {
- fun setup(): Unit = with(binding) {
- if (data.state == ProgressState.Loading) {
- veilBalance.veil()
- veilBalanceCrypto.veil()
- } else {
- // veilBalance.unVeil()
- veilBalanceCrypto.unVeil()
- }
- tvProcessing.animateVisibility(
- show = data.state == ProgressState.Error,
- )
- veilBalanceCrypto.animateVisibility(
- show = data.state != ProgressState.Error,
- )
- if (data.fiatAmount == null) {
- //TODO: SaltPay: A tricky solution to the problem with displaying rates
- // If the rates are loaded after the walletManager.update() is completely updated,
- // then this problem can be avoided
- actionDebouncer(WalletAction.LoadFiatRate())
- } else {
- veilBalance.unVeil()
- tvBalance.text = data.fiatAmount?.formatAmountAsSpannedString(
- currencySymbol = data.fiatCurrency?.symbol ?: "",
- )
- }
-
- tvBalanceCrypto.text = data.currency
-
- tvCurrencyName.text = data.fiatCurrency?.code
-
- tvCurrencyName.setOnClickListener {
- store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
- }
- }
-}
-
-private val actionDebouncer = debounce(500, mainScope) { store.dispatch(it) }
-
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt
deleted file mode 100644
index 44ff7329fc..0000000000
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SaltPaySingleWalletView.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.tangem.tap.features.wallet.ui.wallet
-
-import com.tangem.tap.common.extensions.hide
-import com.tangem.tap.common.extensions.show
-import com.tangem.tap.features.wallet.redux.WalletState
-import com.tangem.tap.features.wallet.ui.TokenData
-import com.tangem.tap.features.wallet.ui.WalletFragment
-import com.tangem.tap.store
-import com.tangem.wallet.databinding.FragmentWalletBinding
-import timber.log.Timber
-
-class SaltPaySingleWalletView : WalletView() {
- override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
- setFragment(fragment, binding)
- onViewCreated()
- showSingleWalletView(binding)
- }
-
- private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
- rvMultiwallet.hide()
- btnAddToken.hide()
- rowButtons.hide()
- rvPendingTransaction.hide()
- tvTwinCardNumber.hide()
- pbLoadingUserTokens.hide()
- lCardBalance.root.hide()
- lAddress.root.hide()
- lSingleWalletBalance.root.show()
- }
-
- override fun onViewCreated() {
- }
-
- override fun onNewState(state: WalletState) {
- val binding = binding ?: return
- val tokenData = state.primaryWallet?.currencyData?.token ?: return
-
- setupBalance(state, tokenData, binding)
- }
-
- private fun setupBalance(state: WalletState, tokenData: TokenData, binding: FragmentWalletBinding) {
- binding.lSingleWalletBalance.root.show()
- Timber.e("Current address is ${state.primaryWalletManager?.wallet?.address}")
- SaltPayBalanceWidget(
- binding = binding.lSingleWalletBalance,
- data = SaltPayBalanceWidgetData(
- state = state.state,
- currencySymbol = tokenData.tokenSymbol,
- currency = tokenData.amountFormatted,
- fiatAmount = tokenData.fiatAmount, // TODO: show fiatAmount
- fiatCurrency = store.state.globalState.appCurrency,
- ),
- ).setup()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
index e7587a6983..1723934656 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
@@ -35,6 +35,7 @@ class SingleWalletView : WalletView() {
}
private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
+ lSaltPayWallet.root.hide()
tvTwinCardNumber.hide()
rvMultiwallet.hide()
btnAddToken.hide()
@@ -63,13 +64,13 @@ class SingleWalletView : WalletView() {
override fun onNewState(state: WalletState) {
val binding = binding ?: return
- state.primaryWallet ?: return
+ val primaryWalletData = state.primaryWalletData ?: return
setupTwinCards(state.twinCardsState, binding)
- setupButtons(state.primaryWallet, binding, state.isExchangeServiceFeatureOn)
+ setupButtons(primaryWalletData, binding, state.isExchangeServiceFeatureOn)
setupAddressCard(state, binding)
- showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
- setupBalance(state, state.primaryWallet)
+ showPendingTransactionsIfPresent(primaryWalletData.pendingTransactions)
+ setupBalance(state, primaryWalletData)
}
private fun showPendingTransactionsIfPresent(pendingTransactions: List) {
@@ -88,6 +89,7 @@ class SingleWalletView : WalletView() {
binding = this.lCardBalance,
fragment = fragment,
data = primaryWallet.currencyData,
+ token = state.primaryTokenData?.currencyData,
isTwinCard = state.isTangemTwins,
).setup()
}
@@ -152,7 +154,7 @@ class SingleWalletView : WalletView() {
}
private fun setupAddressCard(state: WalletState, binding: FragmentWalletBinding) = with(binding.lAddress) {
- val primaryWallet = state.primaryWallet
+ val primaryWallet = state.primaryWalletData
if (primaryWallet?.walletAddresses != null && primaryWallet.currency is Currency.Blockchain) {
binding.lAddress.root.show()
if (primaryWallet.shouldShowMultipleAddress()) {
@@ -187,7 +189,7 @@ class SingleWalletView : WalletView() {
private fun setupCardInfo(state: WalletState) {
val textView = binding?.lAddress?.tvInfo
- val blockchain = state.primaryWallet?.currency?.blockchain
+ val blockchain = state.primaryWalletData?.currency?.blockchain
if (textView != null && blockchain != null) {
textView.text = textView.getString(
id = R.string.address_qr_code_message_format,
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
index d7d7cbb405..97c127040a 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/WalletView.kt
@@ -5,8 +5,10 @@ import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.databinding.FragmentWalletBinding
abstract class WalletView {
+
protected var fragment: WalletFragment? = null
protected var binding: FragmentWalletBinding? = null
+
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
@@ -17,6 +19,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..8d43ff1316
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt
@@ -0,0 +1,257 @@
+package com.tangem.tap.features.wallet.ui.wallet.saltPay
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.skydoves.androidveil.VeilLayout
+import com.tangem.core.analytics.Analytics
+import com.tangem.domain.common.extensions.debounce
+import com.tangem.domain.common.extensions.withMainContext
+import com.tangem.tap.common.ShimmerData
+import com.tangem.tap.common.ShimmerRecyclerAdapter
+import com.tangem.tap.common.analytics.events.MainScreen
+import com.tangem.tap.common.extensions.animateVisibility
+import com.tangem.tap.common.extensions.formatAmountAsSpannedString
+import com.tangem.tap.common.extensions.hide
+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.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
+
+ setupBalanceWidget(balanceWidget, state)
+ setupTxHistoryWidget(txWidget, state)
+ }
+
+ private fun setupBalanceWidget(
+ balanceWidget: LayoutSaltPayBalanceBinding,
+ state: WalletState,
+ ) = with(balanceWidget) {
+ Timber.d("setupBalanceWidget")
+ val tokenData = state.primaryTokenData ?: return
+
+ val appCurrency = store.state.globalState.appCurrency
+ val mainProgressState = state.state
+
+ 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)
+ 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()
+ }
+}
+
+private fun VeilLayout.changeVeilState(show: Boolean) {
+ if (show) this.veil()
+ else this.unVeil()
+}
\ No newline at end of file
diff --git a/app/src/main/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..763f6b532e
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt
@@ -0,0 +1,78 @@
+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
+ }
+
+ 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/ShimmerRecyclerAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt
new file mode 100644
index 0000000000..fc59d49155
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/ShimmerRecyclerAdapter.kt
@@ -0,0 +1,33 @@
+package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv
+
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+[REDACTED_AUTHOR]
+ */
+open class ShimmerRecyclerAdapter(
+ private val viewHolderViewFactory: (ViewGroup) -> ViewGroup,
+) : ListAdapter(DiffUtilCallback) {
+
+ override fun getItemId(position: Int): Long {
+ return if (currentList.isEmpty()) 0 else currentList[position].hashCode().toLong()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShimmerVH {
+ return ShimmerVH(viewHolderViewFactory.invoke(parent))
+ }
+
+ override fun onBindViewHolder(holder: ShimmerVH, position: Int) {}
+
+ object DiffUtilCallback : DiffUtil.ItemCallback() {
+ override fun areContentsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
+ override fun areItemsTheSame(oldItem: ShimmerData, newItem: ShimmerData) = oldItem == newItem
+ }
+}
+
+class ShimmerVH(viewGroup: ViewGroup) : RecyclerView.ViewHolder(viewGroup)
+
+data class ShimmerData(private val any: String = "")
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt
new file mode 100644
index 0000000000..5ac81ac30a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt
@@ -0,0 +1,112 @@
+package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv
+
+import android.graphics.PorterDuff
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.tangem.tap.common.extensions.getColor
+import com.tangem.tap.common.extensions.setDrawable
+import com.tangem.tap.common.extensions.show
+import com.tangem.tap.features.wallet.models.PendingTransactionType
+import com.tangem.wallet.R
+import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding
+import com.tangem.wallet.databinding.ItemSaltPayTxHistoryDateBinding
+
+/**
+[REDACTED_AUTHOR]
+ */
+internal class TxHistoryAdapter : ListAdapter(DiffUtilCallback) {
+
+ override fun getItemId(position: Int): Long {
+ return if (currentList.isEmpty()) 0L
+ else currentList[position].itemId
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseTxHistoryVH {
+ val inflater = LayoutInflater.from(parent.context)
+
+ return when (viewType) {
+ 0 -> {
+ val layout = ItemSaltPayTxHistoryDateBinding.inflate(inflater, parent, false)
+ DateItemVH(layout)
+ }
+ 1 -> {
+ val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false)
+ TransactionItemVH(layout)
+ }
+ else -> throw UnsupportedOperationException()
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int = currentList[position].viewType
+
+ override fun onBindViewHolder(holder: BaseTxHistoryVH, position: Int) {
+ holder.bind(currentList[position])
+ }
+
+ private object DiffUtilCallback : DiffUtil.ItemCallback() {
+ override fun areContentsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem
+ override fun areItemsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem
+ }
+}
+
+internal abstract class BaseTxHistoryVH(layout: View) : RecyclerView.ViewHolder(layout) {
+ abstract fun bind(data: HistoryItemData)
+}
+
+private class DateItemVH(
+ private val binding: ItemSaltPayTxHistoryDateBinding,
+) : BaseTxHistoryVH(binding.root) {
+
+ override fun bind(data: HistoryItemData) = with(binding) {
+ val vhData = (data as? HistoryItemData.Date)?.date ?: return
+
+ tvTitleDate.text = vhData
+ }
+}
+
+private class TransactionItemVH(
+ private val binding: ItemSaltPayTxHistoryBinding,
+) : BaseTxHistoryVH(binding.root) {
+
+ override fun bind(data: HistoryItemData) {
+ val vhData = (data as? HistoryItemData.TransactionData)?.data ?: return
+ setupImage(vhData)
+ setupTitle(vhData)
+ setupSubtitle(vhData)
+ setupBalance(vhData)
+ }
+
+ private fun setupImage(data: HistoryTransactionData) = with(binding) {
+ val drawable = when (data.transactionType) {
+ PendingTransactionType.Incoming -> R.drawable.ic_tx_incoming
+ PendingTransactionType.Outgoing -> R.drawable.ic_tx_outgoing
+ PendingTransactionType.Unknown -> null
+ }
+ drawable?.let { imvTx.setDrawable(it) }
+
+ if (data.isInProgress) {
+ imvTx.setColorFilter(imvTx.getColor(R.color.icon_attention), PorterDuff.Mode.SRC_ATOP)
+ } else {
+ imvTx.clearColorFilter()
+ }
+ }
+
+ private fun setupTitle(data: HistoryTransactionData) = with(binding) {
+ tvTxHash.text = data.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 aa4f559888..1a98a9c93d 100644
--- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt
@@ -9,8 +9,10 @@ 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.addCardContext
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
@@ -136,7 +138,7 @@ internal class WalletSelectorMiddleware {
)
ScanCardProcessor.scan(
- analyticsEvent = MyWallets.CardWasScanned(),
+ analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.MyWallets),
onWalletNotCreated = {
// No need to rollback policy, continue with the policy set before the card scan
store.dispatchOnMain(WalletSelectorAction.AddWallet.Success)
@@ -197,8 +199,7 @@ internal class WalletSelectorMiddleware {
.doOnSuccess {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
if (selectedUserWallet != null) {
- val batchId = selectedUserWallet.scanResponse.card.batchId
- Analytics.addParamsInterceptor(BatchIdParamsInterceptor(batchId))
+ Analytics.addCardContext(selectedUserWallet.scanResponse)
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
store.onUserWalletSelected(selectedUserWallet)
diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt
index 6e67669815..cc02374081 100644
--- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt
+++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt
@@ -4,7 +4,15 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
@@ -30,6 +38,7 @@ import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.common.compose.TangemTypography
+import com.tangem.tap.common.extensions.cardImageData
import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem
import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem
import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem
@@ -107,7 +116,7 @@ private fun WalletCardImage(
SubcomposeAsyncImage(
modifier = cardImageModifier,
model = ImageRequest.Builder(LocalContext.current)
- .data(cardImageUrl)
+ .cardImageData(cardImageUrl)
.crossfade(true)
.build(),
loading = { CardImageShimmer(modifier = cardImageModifier) },
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt
index 681a3cea77..9f117cb782 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt
@@ -5,6 +5,7 @@ import com.tangem.common.core.TangemError
import org.rekotlin.Action
internal sealed interface WelcomeAction : Action {
+ object OnCreate : WelcomeAction
object ProceedWithBiometrics : WelcomeAction {
object Success : WelcomeAction
data class Error(val error: TangemError) : WelcomeAction
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt
index 4c39b5cb3c..76c0a2319e 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt
@@ -4,9 +4,14 @@ import android.content.Intent
import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
+import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.ScanResponse
+import com.tangem.tap.common.analytics.events.AnalyticsParam
+import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.SignIn
+import com.tangem.tap.common.extensions.addCardContext
import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.extensions.eraseCardContext
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.navigation.AppScreen
@@ -38,6 +43,10 @@ internal class WelcomeMiddleware {
private fun handleAction(action: WelcomeAction, state: WelcomeState) {
when (action) {
+ is WelcomeAction.OnCreate -> {
+ Analytics.eraseCardContext()
+ Analytics.send(SignIn.ScreenOpened())
+ }
is WelcomeAction.ProceedWithBiometrics -> {
proceedWithBiometry(state)
}
@@ -62,6 +71,7 @@ internal class WelcomeMiddleware {
}
.doOnSuccess { selectedUserWallet ->
if (selectedUserWallet != null) {
+ Analytics.addCardContext(selectedUserWallet.scanResponse)
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
store.onUserWalletSelected(selectedUserWallet)
@@ -101,7 +111,7 @@ internal class WelcomeMiddleware {
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
)
ScanCardProcessor.scan(
- analyticsEvent = SignIn.CardWasScanned(),
+ analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.SignIn),
onSuccess = { scanResponse ->
scope.launch { onCardScanned(scanResponse) }
},
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt
index 42a6d4d0e1..e4abfb4208 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt
@@ -26,6 +26,7 @@ internal object WelcomeReducer {
is WelcomeAction.ProceedWithBiometrics.Success -> state.copy(isUnlockWithBiometricsInProgress = false)
is WelcomeAction.ProceedWithCard.Success -> state.copy(isUnlockWithCardInProgress = false)
is WelcomeAction.CloseError -> state.copy(error = null)
+ else -> state
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt
index 70477f5ad1..9ca40680ac 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt
@@ -19,13 +19,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.fragment.app.viewModels
-import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.fragments.ComposeFragment
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.tap.common.analytics.events.SignIn
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
+import com.tangem.tap.features.welcome.redux.WelcomeAction
import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent
+import com.tangem.tap.store
import com.tangem.wallet.R
internal class WelcomeFragment : ComposeFragment() {
@@ -33,7 +33,7 @@ internal class WelcomeFragment : ComposeFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- Analytics.send(SignIn.ScreenOpened())
+ store.dispatch(WelcomeAction.OnCreate)
}
@Composable
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt
new file mode 100644
index 0000000000..d77245b45c
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt
@@ -0,0 +1,62 @@
+package com.tangem.tap.network.exchangeServices
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.domain.common.ProductType
+import com.tangem.tap.features.wallet.models.Currency
+import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
+import com.tangem.tap.network.exchangeServices.utorg.UtorgExchangeService
+import com.tangem.tap.scope
+import kotlinx.coroutines.launch
+
+/**
+[REDACTED_AUTHOR]
+ * Temporary wrapper for the buy services. Service switches based on selected product type.
+ * Just now - UtorgService used only for the SaltPay cards
+ */
+class BuyExchangeService(
+ private val productTypeProvider: () -> ProductType?,
+ private val mercuryoService: MercuryoService,
+ private val utorgService: UtorgExchangeService,
+) : ExchangeService, ExchangeUrlBuilder {
+
+ init {
+ scope.launch {
+ mercuryoService.update()
+ utorgService.update()
+ }
+ }
+
+ private val currentService: ExchangeService
+ get() = when (productTypeProvider.invoke()) {
+ ProductType.SaltPay -> utorgService
+ else -> mercuryoService
+ }
+
+ override suspend fun update() {
+ currentService.update()
+ }
+
+ override fun featureIsSwitchedOn(): Boolean = currentService.featureIsSwitchedOn()
+
+ override fun isBuyAllowed(): Boolean = currentService.isBuyAllowed()
+
+ override fun isSellAllowed(): Boolean = currentService.isSellAllowed()
+
+ override fun availableForBuy(currency: Currency): Boolean = currentService.availableForBuy(currency)
+
+ override fun availableForSell(currency: Currency): Boolean = currentService.availableForSell(currency)
+
+ override fun getUrl(
+ action: CurrencyExchangeManager.Action,
+ blockchain: Blockchain,
+ cryptoCurrencyName: String,
+ fiatCurrencyName: String,
+ walletAddress: String,
+ ): String? {
+ return currentService.getUrl(action, blockchain, cryptoCurrencyName, fiatCurrencyName, walletAddress)
+ }
+
+ override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? {
+ return currentService.getSellCryptoReceiptUrl(action, transactionId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
index 3f61d82801..656343a092 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
@@ -24,7 +24,7 @@ class CurrencyExchangeManager(
private val buyService: ExchangeService,
private val sellService: ExchangeService,
private val primaryRules: ExchangeRules,
-) : ExchangeService, ExchangeUrlBuilder {
+) : ExchangeService {
override fun featureIsSwitchedOn(): Boolean = primaryRules.featureIsSwitchedOn()
@@ -59,7 +59,7 @@ class CurrencyExchangeManager(
blockchain,
cryptoCurrencyName,
fiatCurrencyName,
- walletAddress
+ walletAddress,
)
}
@@ -97,8 +97,11 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(
val amountToSend = Amount(walletManager.wallet.blockchain)
val destinationAddress = token.contractAddress
- val feeResult = walletManager.getFee(amountToSend,
- destinationAddress) as? Result.Success ?: return
+ val feeResult =
+ walletManager.getFee(
+ amountToSend,
+ destinationAddress,
+ ) as? Result.Success ?: return
val fee = feeResult.data[0]
if ((walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO) < fee.value) {
@@ -116,8 +119,8 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey.seedKey,
- remainingSignatures = signResponse.remainingSignatures
- )
+ remainingSignatures = signResponse.remainingSignatures,
+ ),
)
}
walletManager.send(transaction, signer)
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt
index 8d6e8e2187..f38a8c64a1 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt
@@ -7,11 +7,11 @@ import com.tangem.tap.features.wallet.models.Currency
interface Exchanger {
fun isBuyAllowed(): Boolean
fun isSellAllowed(): Boolean
- fun availableForBuy(currency: Currency):Boolean
- fun availableForSell(currency: Currency):Boolean
+ fun availableForBuy(currency: Currency): Boolean
+ fun availableForSell(currency: Currency): Boolean
}
-interface ExchangeService: Feature, Exchanger {
+interface ExchangeService : Feature, Exchanger, ExchangeUrlBuilder {
suspend fun update()
companion object {
@@ -22,11 +22,23 @@ interface ExchangeService: Feature, Exchanger {
override fun isSellAllowed(): Boolean = false
override fun availableForBuy(currency: Currency): Boolean = false
override fun availableForSell(currency: Currency): Boolean = false
+ override fun getUrl(
+ action: CurrencyExchangeManager.Action,
+ blockchain: Blockchain,
+ cryptoCurrencyName: String,
+ fiatCurrencyName: String,
+ walletAddress: String,
+ ): String? = null
+
+ override fun getSellCryptoReceiptUrl(
+ action: CurrencyExchangeManager.Action,
+ transactionId: String,
+ ): String? = null
}
}
}
-interface ExchangeRules: Feature, Exchanger {
+interface ExchangeRules : Feature, Exchanger {
companion object {
fun dummy(): ExchangeRules = object : ExchangeRules {
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt
index 87565c3640..89308da1ce 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt
@@ -11,11 +11,6 @@ interface MercuryoApi {
suspend fun currencies(
@Path("apiVersion") apiVersion: String,
): MercuryoCurrenciesResponse
-
- companion object {
- const val BASE_URL = "https://api.mercuryo.io/"
- const val API_VERSION = "v1.6"
- }
}
data class MercuryoCurrenciesResponse(
@@ -25,7 +20,7 @@ data class MercuryoCurrenciesResponse(
data class Data(
val fiat: List,
val crypto: List,
- val config: Config
+ val config: Config,
)
data class Config(
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt
new file mode 100644
index 0000000000..af3cda029a
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoEnvironment.kt
@@ -0,0 +1,36 @@
+package com.tangem.tap.network.exchangeServices.mercuryo
+
+import com.tangem.datasource.api.common.createRetrofitInstance
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class MercuryoEnvironment(
+ val baseUrl: String,
+ val apiVersion: String,
+ val widgetId: String,
+ val secret: String,
+ val mercuryoApi: MercuryoApi,
+) {
+ companion object {
+ private const val BASE_URL = "https://api.mercuryo.io/"
+ private const val API_VERSION = "v1.6"
+
+ fun prod(
+ widgetId: String,
+ secret: String,
+ apiVersion: String = API_VERSION,
+ ): MercuryoEnvironment {
+ return MercuryoEnvironment(
+ baseUrl = BASE_URL,
+ apiVersion = apiVersion,
+ widgetId = widgetId,
+ secret = secret,
+ mercuryoApi = createRetrofitInstance(
+ baseUrl = BASE_URL,
+ logEnabled = false,
+ ).create(MercuryoApi::class.java),
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
index 9dd2b9746f..64f2ae121a 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
@@ -6,7 +6,6 @@ import com.tangem.common.extensions.calculateSha512
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
-import com.tangem.datasource.api.common.createRetrofitInstance
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
@@ -17,16 +16,10 @@ import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
[REDACTED_AUTHOR]
*/
class MercuryoService(
- private val apiVersion: String,
- private val mercuryoWidgetId: String,
- private val secret: String,
- private val logEnabled: Boolean,
-) : ExchangeService, ExchangeUrlBuilder {
+ private val environment: MercuryoEnvironment,
+) : ExchangeService {
- private val api: MercuryoApi = createRetrofitInstance(
- baseUrl = MercuryoApi.BASE_URL,
- logEnabled = logEnabled,
- ).create(MercuryoApi::class.java)
+ private val api: MercuryoApi = environment.mercuryoApi
private val blockchainsAvailableToBuy = mutableListOf()
private val tokensAvailableToBy = mutableMapOf>()
@@ -34,7 +27,7 @@ class MercuryoService(
override fun featureIsSwitchedOn(): Boolean = true
override suspend fun update() {
- when (val result = performRequest { api.currencies(apiVersion) }) {
+ when (val result = performRequest { api.currencies(environment.apiVersion) }) {
is Result.Success -> {
val response = result.data
if (response.status == 200) {
@@ -108,14 +101,14 @@ class MercuryoService(
blockchain: Blockchain,
cryptoCurrencyName: CryptoCurrencyName,
fiatCurrencyName: String,
- walletAddress: String
+ walletAddress: String,
): String {
if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException()
val builder = Uri.Builder()
.scheme(ExchangeUrlBuilder.SCHEME)
.authority("exchange.mercuryo.io")
- .appendQueryParameter("widget_id", mercuryoWidgetId)
+ .appendQueryParameter("widget_id", environment.widgetId)
.appendQueryParameter("type", action.name.lowercase())
.appendQueryParameter("currency", cryptoCurrencyName)
.appendQueryParameter("address", walletAddress)
@@ -128,13 +121,12 @@ class MercuryoService(
}
private fun signature(address: String): String {
- return (address + secret).calculateSha512().toHexString().lowercase()
+ return (address + environment.secret).calculateSha512().toHexString().lowercase()
}
-
override fun getSellCryptoReceiptUrl(
action: CurrencyExchangeManager.Action,
- transactionId: String
+ transactionId: String,
): String? = null
private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) {
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
index 645d5182ed..0754f6c305 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
@@ -5,14 +5,13 @@ import android.util.Base64
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
-import com.tangem.domain.common.extensions.withIOContext
import com.tangem.datasource.api.common.createRetrofitInstance
+import com.tangem.domain.common.extensions.withIOContext
import com.tangem.tap.common.extensions.urlEncode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
-import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
@@ -21,7 +20,7 @@ class MoonPayService(
private val apiKey: String,
private val secretKey: String,
private val logEnabled: Boolean,
-) : ExchangeService, ExchangeUrlBuilder {
+) : ExchangeService {
private val api: MoonPayApi by lazy {
createRetrofitInstance(
@@ -103,7 +102,7 @@ class MoonPayService(
blockchain: Blockchain,
cryptoCurrencyName: CryptoCurrencyName,
fatCurrency: String,
- walletAddress: String
+ walletAddress: String,
): String? {
if (action == CurrencyExchangeManager.Action.Buy) throw UnsupportedOperationException()
@@ -130,7 +129,6 @@ class MoonPayService(
.appendPath("transaction_receipt")
.appendQueryParameter("transactionId", transactionId).build().toString()
return url
-
}
private fun createSignature(data: String): String {
@@ -149,5 +147,5 @@ class MoonPayService(
private data class MoonPayStatus(
val availableForSell: List,
val responseUserStatus: MoonPayUserStatus,
- val responseCurrencies: List
+ val responseCurrencies: List,
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt
new file mode 100644
index 0000000000..b31e8aa8bf
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt
@@ -0,0 +1,88 @@
+package com.tangem.tap.network.exchangeServices.utorg
+
+import android.net.Uri
+import com.tangem.datasource.api.common.createRetrofitInstance
+import com.tangem.tap.network.exchangeServices.utorg.api.UtorgApi
+import com.tangem.tap.network.exchangeServices.utorg.mock.MockUtorgApi
+import okhttp3.Interceptor
+import okhttp3.Response
+import java.util.*
+
+/**
+[REDACTED_AUTHOR]
+ */
+data class UtorgEnvironment(
+ val baseUri: Uri,
+ val sidValue: String,
+ val apiVersion: String,
+ val utorgApi: UtorgApi,
+ val successUrl: String = "https://success.tangem.com",
+) {
+ companion object {
+ private val PROD_BASE_URL = Uri.parse("https://app.utorg.pro")
+ private const val PROD_VERSION = "v1"
+
+ private val STAGE_BASE_URL = Uri.parse("https://app-stage.utorg.pro")
+ private const val STAGE_VERSION = "v1"
+
+ fun prod(
+ authProvider: UtorgAuthProvider,
+ apiVersion: String = PROD_VERSION,
+ ): UtorgEnvironment = UtorgEnvironment(
+ baseUri = PROD_BASE_URL,
+ sidValue = authProvider.sidValue,
+ apiVersion = apiVersion,
+ utorgApi = createApi(PROD_BASE_URL, authProvider, false),
+ )
+
+ fun stage(
+ authProvider: UtorgAuthProvider,
+ logEnabled: Boolean,
+ apiVersion: String = STAGE_VERSION,
+ ): UtorgEnvironment = UtorgEnvironment(
+ baseUri = STAGE_BASE_URL,
+ sidValue = authProvider.sidValue,
+ apiVersion = apiVersion,
+ utorgApi = createApi(STAGE_BASE_URL, authProvider, logEnabled),
+ )
+
+ fun mock(): UtorgEnvironment = UtorgEnvironment(
+ baseUri = Uri.EMPTY,
+ sidValue = "",
+ apiVersion = "",
+ utorgApi = MockUtorgApi(),
+ )
+
+ private fun createApi(baseUri: Uri, authProvider: UtorgAuthProvider, logEnabled: Boolean): UtorgApi {
+ return createRetrofitInstance(
+ baseUrl = "$baseUri/",
+ interceptors = listOf(UtorgAuthHeaderInterceptor(authProvider)),
+ logEnabled = logEnabled,
+ ).create(UtorgApi::class.java)
+ }
+ }
+}
+
+data class UtorgAuthProvider(
+ val sidValue: String,
+ val headerSidKey: String = "X-AUTH-SID",
+ val headerNonceKey: String = "X-AUTH-NONCE",
+) {
+ val clientNonce: String
+ get() = UUID.randomUUID().toString()
+}
+
+private class UtorgAuthHeaderInterceptor(
+ private val authProvider: UtorgAuthProvider,
+) : Interceptor {
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val request = chain.request()
+ .newBuilder()
+ .addHeader(authProvider.headerSidKey, authProvider.sidValue)
+ .addHeader(authProvider.headerNonceKey, authProvider.clientNonce)
+ .build()
+
+ return chain.proceed(request)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt
new file mode 100644
index 0000000000..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 d419aafffc..7999268acb 100644
--- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
+++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
@@ -29,9 +29,13 @@ class PreferencesStorage(applicationContext: Application) {
toppedUpWalletStorage = ToppedUpWalletStorage(preferences, MoshiConverter.INSTANCE)
}
- var chatFirstLaunchTime: Long?
- get() = preferences.getLong(CHAT_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L }
- set(value) = preferences.edit { putLong(CHAT_FIRST_LAUNCH_KEY, value ?: 0) }
+ var zendeskFirstLaunchTime: Long?
+ get() = preferences.getLong(ZENDESK_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L }
+ set(value) = preferences.edit { putLong(ZENDESK_FIRST_LAUNCH_KEY, value ?: 0) }
+
+ var sprinklrFirstLaunchTime: Long?
+ get() = preferences.getLong(SPRINKLR_FIRST_LAUNCH_KEY, 0).takeIf { it != 0L }
+ set(value) = preferences.edit { putLong(SPRINKLR_FIRST_LAUNCH_KEY, value ?: 0) }
var shouldShowSaveUserWalletScreen: Boolean
get() = preferences.getBoolean(SAVE_WALLET_DIALOG_SHOWN_KEY, true)
@@ -82,7 +86,8 @@ class PreferencesStorage(applicationContext: Application) {
private const val PREFERENCES_NAME = "tapPrefs"
private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown"
private const val APP_LAUNCH_COUNT_KEY = "launchCount"
- private const val CHAT_FIRST_LAUNCH_KEY = "chatFirstLaunchKey"
+ private const val ZENDESK_FIRST_LAUNCH_KEY = "chatFirstLaunchKey"
+ private const val SPRINKLR_FIRST_LAUNCH_KEY = "sprinklrFirstLaunch"
private const val SAVE_WALLET_DIALOG_SHOWN_KEY = "saveUserWalletShown"
private const val SAVE_USER_WALLETS_KEY = "saveUserWallets"
private const val SAVE_ACCESS_CODES_KEY = "saveAccessCodes"
diff --git a/app/src/main/res/color/selector_saltpay_btn.xml b/app/src/main/res/color/selector_saltpay_btn.xml
new file mode 100644
index 0000000000..98dd6f0aba
--- /dev/null
+++ b/app/src/main/res/color/selector_saltpay_btn.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/selector_saltpay_btn_text.xml b/app/src/main/res/color/selector_saltpay_btn_text.xml
new file mode 100644
index 0000000000..28e2f2cfdd
--- /dev/null
+++ b/app/src/main/res/color/selector_saltpay_btn_text.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
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-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml b/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml
index 4eb63182f8..862e6809ee 100644
--- a/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml
+++ b/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml
@@ -52,7 +52,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="92dp"
- android:text="@string/onboarding_supplement_button_kyc_waiting"
+ android:text="@string/chat_button_title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/btn_container"
app:layout_constraintStart_toStartOf="@id/btn_container" />
diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml
index f09ddc509b..b3dc1e076b 100644
--- a/app/src/main/res/layout/fragment_wallet.xml
+++ b/app/src/main/res/layout/fragment_wallet.xml
@@ -177,6 +177,15 @@
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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_onboarding_saltpay_kyc_in_progress.xml b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml
index 580995fc6f..a5fbee52fa 100644
--- a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml
+++ b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml
@@ -55,7 +55,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/btn_container"
app:layout_constraintStart_toStartOf="@id/btn_container"
- tools:text="@string/onboarding_supplement_button_kyc_waiting" />
+ tools:text="@string/chat_button_title" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_salt_pay_tx_history.xml b/app/src/main/res/layout/layout_salt_pay_tx_history.xml
new file mode 100644
index 0000000000..2aef592910
--- /dev/null
+++ b/app/src/main/res/layout/layout_salt_pay_tx_history.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_salt_pay_wallet.xml b/app/src/main/res/layout/layout_salt_pay_wallet.xml
new file mode 100644
index 0000000000..4712e76937
--- /dev/null
+++ b/app/src/main/res/layout/layout_salt_pay_wallet.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_onboarding.xml b/app/src/main/res/menu/menu_onboarding.xml
index bc09d08fae..28e7e73a09 100644
--- a/app/src/main/res/menu/menu_onboarding.xml
+++ b/app/src/main/res/menu/menu_onboarding.xml
@@ -3,6 +3,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 2bd13d5482..bdd37a99bb 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -131,6 +131,19 @@
- 4sp
+
+