diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d8cf7443af..7f647efbd5 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -9,17 +9,28 @@ plugins {
id("configuration")
}
-//conflict of dependencies when adding WalletConnectV2.0 library
-configurations {
- all {
- exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
- resolutionStrategy {
- force("org.bouncycastle:bcpkix-jdk15on:1.70")
+configurations.all {
+ exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
+ exclude(group = "com.github.komputing.kethereum")
+
+ resolutionStrategy {
+ dependencySubstitution {
+ substitute(module("com.facebook.react:react-native"))
+ .using(module("com.facebook.react:react-android:0.72.4"))
+
+ substitute(module("com.facebook.react:hermes-engine"))
+ .using(module("com.facebook.react:hermes-android:0.72.4"))
}
- exclude(group = "com.github.komputing.kethereum")
+
+ force(
+ "org.bouncycastle:bcpkix-jdk15on:1.70",
+ "com.facebook.react:react-android:0.72.4",
+ "com.facebook.react:hermes-android:0.72.4",
+ )
}
}
+
dependencies {
implementation(files("libs/walletconnect-1.5.6.aar"))
implementation(projects.domain.legacy)
@@ -148,8 +159,6 @@ dependencies {
implementation(deps.appsflyer)
implementation(deps.amplitude)
implementation(deps.kotsonGson)
- implementation(deps.zendesk.chat)
- implementation(deps.zendesk.messaging)
implementation(deps.spongecastle.core)
implementation(deps.lottie)
implementation(deps.shopify.buy) {
@@ -167,6 +176,10 @@ dependencies {
implementation(deps.walletConnectCore)
implementation(deps.walletConnectWeb3)
implementation(deps.prettyLogger)
+ implementation("com.facebook.react:react-android:0.72.4")
+ implementation(deps.sprClient) {
+ exclude(group = "com.github.stephenc.jcip")
+ }
/** Testing libraries */
testImplementation(deps.test.junit)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6e5584b665..3eb4bc059b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,7 +12,16 @@
-
+
+
+
+
+
@@ -40,7 +49,7 @@
android:extractNativeLibs="true"
android:supportsRtl="true"
tools:ignore="GoogleAppIndexingWarning"
- tools:replace="android:allowBackup, android:fullBackupContent">
+ tools:replace="android:allowBackup, android:fullBackupContent, android:label">
-
-
-
-
diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config
index b84b1e1ab1..2b49c08b92 160000
--- a/app/src/main/assets/tangem-app-config
+++ b/app/src/main/assets/tangem-app-config
@@ -1 +1 @@
-Subproject commit b84b1e1ab11b0eb0f6ca6e28f551f9e2ca6809fe
+Subproject commit 2b49c08b924c03322f35542a7482e4e9fb1928d3
diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json
index e10caf1e7f..dd90c8b04c 100644
--- a/app/src/main/assets/testnet_tokens.json
+++ b/app/src/main/assets/testnet_tokens.json
@@ -517,6 +517,17 @@
"networkId": "aleph-zero/test"
}
]
+ },
+ {
+ "id": "near",
+ "symbol": "NEAR",
+ "name": "NEAR",
+ "networks":
+ [
+ {
+ "networkId": "near-protocol/test"
+ }
+ ]
}
]
}
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index 8beeb3858c..b5a7b75c92 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -46,6 +46,7 @@ import com.tangem.tap.common.shop.googlepay.GooglePayService
import com.tangem.tap.common.shop.googlepay.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE
import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient
import com.tangem.tap.domain.TangemSdkManager
+import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.features.intentHandler.IntentProcessor
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
@@ -276,6 +277,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
setDefaultNightMode(mode)
+ delegate.localNightMode = mode
}
private fun isDarkTheme(): Boolean {
@@ -366,7 +368,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
- if (store.state.globalState.userWalletsListManager?.hasUserWallets == true) {
+ val canSaveWallets = userWalletsListManager is BiometricUserWalletsListManager
+ val hasSavedWallets = userWalletsListManager.hasUserWallets
+
+ if (canSaveWallets && hasSavedWallets) {
store.dispatch(
NavigationAction.NavigateTo(
screen = AppScreen.Welcome,
diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt
index c585fe7012..c67f289238 100644
--- a/app/src/main/java/com/tangem/tap/TapApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TapApplication.kt
@@ -350,7 +350,7 @@ internal class TapApplication : Application(), ImageLoaderFactory {
private fun initWithConfigDependency(config: Config) {
shopService = TangemShopService(this, config.shopify!!)
initAnalytics(this, config)
- initFeedbackManager(this, preferencesStorage, foregroundActivityObserver, store)
+ initFeedbackManager(this, foregroundActivityObserver, store)
}
private fun initAnalytics(application: Application, config: Config) {
@@ -372,7 +372,6 @@ internal class TapApplication : Application(), ImageLoaderFactory {
private fun initFeedbackManager(
context: Context,
- preferencesStorage: PreferencesDataSource,
foregroundActivityObserver: ForegroundActivityObserver,
store: Store,
) {
@@ -411,7 +410,7 @@ internal class TapApplication : Application(), ImageLoaderFactory {
val feedbackManager = FeedbackManager(
infoHolder = additionalFeedbackInfo,
logCollector = tangemLogCollector,
- chatManager = ChatManager(preferencesStorage, foregroundActivityObserver, store),
+ chatManager = ChatManager(foregroundActivityObserver),
)
store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager))
}
diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
index 2089abefa6..1579ca569f 100644
--- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.common
import android.app.Dialog
import android.content.Context
+import com.tangem.core.navigation.StateDialog
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.ui.SimpleAlertDialog
@@ -53,7 +54,7 @@ class DialogManager : StoreSubscriber {
is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context)
is AppDialog.SimpleOkErrorDialog -> SimpleOkDialog.create(state.dialog, context)
is AppDialog.SimpleOkWarningDialog -> SimpleOkDialog.create(state.dialog, context)
- is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
+ is StateDialog.ScanFailsDialog -> ScanFailsDialog.create(context)
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
is OnboardingDialog.TwinningProcessNotCompleted -> TwinningProcessNotCompletedDialog.create(context)
@@ -105,7 +106,7 @@ class DialogManager : StoreSubscriber {
context.getString(R.string.wallet_connect_scanner_error_unsupported_network)
} else {
context.getString(R.string.wallet_connect_error_unsupported_blockchains) +
- state.dialog.networks
+ state.dialog.networks.joinToString()
}
SimpleAlertDialog.create(
titleRes = R.string.wallet_connect_title,
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 e7e51bdd4c..2d1bd09138 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
@@ -21,8 +21,9 @@ sealed class AnalyticsParam {
sealed class CardBalanceState(val value: String) {
object Empty : CardBalanceState("Empty")
object Full : CardBalanceState("Full")
- object CustomToken : CardBalanceState("Custom token")
- object BlockchainError : CardBalanceState("Blockchain error")
+ object CustomToken : CardBalanceState("Custom Token")
+ object BlockchainError : CardBalanceState("Blockchain Error")
+ object NoRate : CardBalanceState("No Rate")
companion object
}
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt
index 7208d9943a..772c85c8b1 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt
@@ -80,7 +80,7 @@ sealed class Settings(
object ButtonEnableBiometricAuthentication : AppSettings(event = "Button - Enable Biometric Authentication")
- class MainCurrencyChanged(currencyType: String) : MainScreen(
+ class MainCurrencyChanged(currencyType: String) : AppSettings(
event = "Main Currency Changed",
params = mapOf("Currency Type" to currencyType),
)
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt
index 6db15ac882..cf97ded034 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt
@@ -46,6 +46,7 @@ class CardContextInterceptor(
ProductType.Twins -> "Twin"
ProductType.Wallet -> "Wallet"
ProductType.Wallet2 -> "Wallet 2.0"
+ ProductType.Ring -> "Ring"
ProductType.Start2Coin -> "Start2Coin"
else -> if (DemoHelper.isDemoCard(scanResponse)) {
if (DemoHelper.isTestDemoCard(scanResponse)) {
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt b/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt
index b23f4a5b54..b32af636df 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt
@@ -141,6 +141,8 @@ class TopUpController(
DataSourceTopupInfo.CardBalanceState.Empty
AnalyticsParam.CardBalanceState.Full ->
DataSourceTopupInfo.CardBalanceState.Full
+ AnalyticsParam.CardBalanceState.NoRate ->
+ DataSourceTopupInfo.CardBalanceState.NoRate
},
)
topupWalletStorage.save(topupInfo)
diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt
index 56ded693ad..5bbe3fc679 100644
--- a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt
@@ -1,57 +1,23 @@
package com.tangem.tap.common.chat
import android.content.Context
-import android.os.Build
import com.tangem.datasource.config.models.ChatConfig
import com.tangem.datasource.config.models.SprinklrConfig
-import com.tangem.datasource.config.models.ZendeskConfig
-import com.tangem.data.source.preferences.PreferencesDataSource
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 org.rekotlin.Store
import java.io.File
-class ChatManager(
- private val preferencesStorage: PreferencesDataSource,
- private val foregroundActivityObserver: ForegroundActivityObserver,
- private val store: Store,
-) {
+class ChatManager(private val foregroundActivityObserver: ForegroundActivityObserver) {
private val openers = mutableMapOf()
fun open(config: ChatConfig, createLogsFile: (Context) -> File?, createFeedbackFile: (Context) -> File?) {
val opener = openers.getOrPut(config) {
when (config) {
- is SprinklrConfig -> SprinklrChatOpener(getSprinklrUserId(), config, store, foregroundActivityObserver)
- is ZendeskConfig -> ZendeskChatOpener(getZendeskUserId(), config, foregroundActivityObserver)
+ is SprinklrConfig -> SprinklrChatOpener(config, foregroundActivityObserver)
}
}
- opener.open(
- createFeedbackFile = createFeedbackFile,
- createLogsFile = createLogsFile,
- )
- }
-
- 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()
+ opener.open(createFeedbackFile, createLogsFile)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt
index f8936d6abd..0fcb2a4cf2 100644
--- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt
+++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt
@@ -1,28 +1,57 @@
package com.tangem.tap.common.chat.opener.implementation
+import android.annotation.SuppressLint
+import android.app.Application
import android.content.Context
-import android.content.Intent
+import android.provider.Settings
+import com.spr.messengerclient.config.SPRMessenger
+import com.spr.messengerclient.config.bean.SPRMessengerConfig
+import com.tangem.common.extensions.guard
import com.tangem.datasource.config.models.SprinklrConfig
import com.tangem.tap.ForegroundActivityObserver
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
+import timber.log.Timber
import java.io.File
+import java.util.Locale
internal class SprinklrChatOpener(
- private val userId: String,
private val config: SprinklrConfig,
- private val store: Store,
private val foregroundActivityObserver: ForegroundActivityObserver,
) : ChatOpener {
+
override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) {
- store.dispatch(SprinklrAction.Init(userId, config))
- foregroundActivityObserver.withForegroundActivity { activity ->
- val intent = Intent(activity, SprinklrActivity::class.java)
- activity.startActivity(intent)
+ val messenger = SPRMessenger.shared()
+
+ if (messenger.config == null) {
+ initSprConfig(messenger)
+ }
+
+ messenger.startApplication()
+ }
+
+ private fun initSprConfig(messenger: SPRMessenger) {
+ val application = foregroundActivityObserver.foregroundActivity?.application.guard {
+ Timber.e("The SPR chat cannot be opened because there are no activities in foreground")
+ return
+ }
+
+ messenger.takeOff(application, createSprConfig(application, config))
+ }
+
+ @SuppressLint("HardwareIds")
+ private fun createSprConfig(application: Application, config: SprinklrConfig): SPRMessengerConfig {
+ return SPRMessengerConfig().apply {
+ appId = config.appId
+ appKey = CHAT_APP_KEY
+ deviceId = Settings.Secure.getString(application.contentResolver, Settings.Secure.ANDROID_ID)
+ environment = config.environment
+ skin = CHAT_SKIN
+ locale = Locale.getDefault().language
}
}
+
+ private companion object {
+ const val CHAT_APP_KEY = "com.sprinklr.messenger.release"
+ const val CHAT_SKIN = "MODERN"
+ }
}
\ 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
deleted file mode 100644
index a65749ce62..0000000000
--- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.tangem.tap.common.chat.opener.implementation
-
-import android.content.Context
-import com.tangem.core.analytics.Analytics
-import com.tangem.datasource.config.models.ZendeskConfig
-import com.tangem.domain.common.LogConfig
-import com.tangem.tap.ForegroundActivityObserver
-import com.tangem.tap.common.chat.opener.ChatOpener
-import com.tangem.tap.withForegroundActivity
-import com.tangem.wallet.R
-import com.zendesk.logger.Logger
-import timber.log.Timber
-import zendesk.chat.*
-import zendesk.configurations.Configuration
-import zendesk.messaging.MessagingActivity
-import java.io.File
-
-internal class ZendeskChatOpener(
- private val userId: String,
- private val config: ZendeskConfig,
- private val foregroundActivityObserver: ForegroundActivityObserver,
-) : ChatOpener {
-
- private var isInitialized = false
- private var isFilesSent = false
-
- override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) {
- foregroundActivityObserver.withForegroundActivity { activity ->
- initZendeskIfNeeded(activity.applicationContext)
- setChatVisitorInfo()
- showMessagingActivity(activity)
- sendFeedbackFile(createFeedbackFile(activity))
- sendLogsFile(createLogsFile(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 sendFeedbackFile(feedbackFile: File?) {
- if (isInitialized && feedbackFile != null && !isFilesSent) {
- Chat.INSTANCE.providers()?.chatProvider()?.sendFile(feedbackFile) { _, bytesUploaded, _ ->
- Timber.d("Log file sent", "bytesUploaded: $bytesUploaded")
- }
- isFilesSent = true
- }
- }
-
- private fun sendLogsFile(logsFile: File?) {
- if (isInitialized && logsFile != null && !isFilesSent) {
- Chat.INSTANCE.providers()?.chatProvider()?.sendFile(logsFile) { _, bytesUploaded, _ ->
- Timber.d("Log file sent", "bytesUploaded: $bytesUploaded")
- }
- isFilesSent = true
- }
- }
-
- private fun showMessagingActivity(context: Context) {
- Analytics.send(com.tangem.tap.common.analytics.events.Chat.ScreenOpened())
- MessagingActivity.builder()
- .withMultilineResponseOptionsEnabled(false)
- .withBotLabelStringRes(R.string.chat_bot_name)
- .withBotAvatarDrawable(R.mipmap.ic_launcher)
- .withEngines(ChatEngine.engine())
- .show(context, buildChatConfig())
- }
-
- private fun buildChatConfig(): Configuration {
- return ChatConfiguration.builder()
- .withOfflineFormEnabled(true)
- .withAgentAvailabilityEnabled(true)
- .withPreChatFormEnabled(false)
- .build()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt
index 8676043cd2..bcd2445a5f 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt
@@ -1,10 +1,10 @@
package com.tangem.tap.common.extensions
import com.tangem.core.navigation.NavigationAction
+import com.tangem.core.navigation.StateDialog
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.scope
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 096f5c96bb..0427435795 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
@@ -26,10 +26,8 @@ import timber.log.Timber
"or WalletCurrenciesManager.update(...) (to update only one user wallet blockchain and its tokens) instead",
)
@Suppress("MagicNumber")
-suspend fun WalletManager.safeUpdate(): Result = try {
- val scanResponse = store.state.globalState.scanResponse
-
- if (scanResponse?.isDemoCard() == true || TestActions.testAmountInjectionForWalletManagerEnabled) {
+suspend fun WalletManager.safeUpdate(isDemoCard: Boolean): Result = try {
+ if (isDemoCard || TestActions.testAmountInjectionForWalletManagerEnabled) {
delay(500)
TestActions.testAmountInjectionForWalletManagerEnabled = false
Result.Success(wallet)
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
index e1d67df755..860f1e3374 100644
--- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
+++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
@@ -7,17 +7,24 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.tap.common.extensions.stripZeroPlainString
-import kotlinx.coroutines.flow.*
+import java.util.concurrent.CopyOnWriteArrayList
class AdditionalFeedbackInfo {
class EmailWalletInfo(
- var blockchain: Blockchain = Blockchain.Unknown,
- var derivationPath: String = "",
- var outputsCount: String? = null,
- var host: String = "",
- var addresses: String = "",
- var explorerLink: String = "",
+ val blockchain: Blockchain = Blockchain.Unknown,
+ val derivationPath: String = "",
+ val outputsCount: String? = null,
+ val host: String = "",
+ val addresses: String = "",
+ val explorerLink: String = "",
+ val tokens: List = emptyList(),
+ )
+
+ class EmailTokenInfo(
+ val id: String?,
+ val name: String,
+ val contractAddress: String,
)
var appVersion: String = ""
@@ -30,20 +37,27 @@ class AdditionalFeedbackInfo {
var userWalletId: String = ""
// wallets
- internal val walletsInfo = mutableListOf()
- internal val tokens = mutableMapOf>()
- internal var onSendErrorWalletInfo: EmailWalletInfo? = null
+ val walletsInfo = CopyOnWriteArrayList()
+ var onSendErrorWalletInfo: EmailWalletInfo? = null
+ private set
var signedHashesCount: String = ""
+ private set
// device
var phoneModel: String = Build.MODEL
+ private set
var osVersion: String = Build.VERSION.SDK_INT.toString()
+ private set
// send error
var destinationAddress: String = ""
+ private set
var amount: String = ""
+ private set
var fee: String = ""
+ private set
var token: String = ""
+ private set
private val Address.name: String
get() = type.javaClass.simpleName
@@ -60,26 +74,26 @@ class AdditionalFeedbackInfo {
@Deprecated("Don't use it directly")
fun setWalletsInfo(walletManagers: List) {
- walletsInfo.clear()
- tokens.clear()
- walletManagers.forEach { manager ->
- walletsInfo.add(createEmailWalletInfo(manager))
- if (manager.cardTokens.isNotEmpty()) {
- tokens[manager.wallet.blockchain] = manager.cardTokens
- }
+ synchronized(walletsInfo) {
+ walletsInfo.clear()
+ walletsInfo.addAll(elements = walletManagers.map(::createEmailWalletInfo))
}
}
fun updateOnSendError(
walletManager: WalletManager,
amountToSend: Amount,
- feeAmount: Amount,
+ feeAmount: Amount?,
destinationAddress: String,
) {
onSendErrorWalletInfo = createEmailWalletInfo(walletManager)
this.destinationAddress = destinationAddress
amount = amountToSend.value?.stripZeroPlainString() ?: "0"
- fee = feeAmount.value?.stripZeroPlainString() ?: "0"
+ fee = if (feeAmount != null) {
+ feeAmount.value?.stripZeroPlainString() ?: "0"
+ } else {
+ "Unable to receive"
+ }
token = if (amountToSend.type is AmountType.Token) amountToSend.currencySymbol else ""
}
@@ -91,6 +105,9 @@ class AdditionalFeedbackInfo {
host = walletManager.currentHost,
addresses = formatAddresses(walletManager.wallet),
explorerLink = formatExploreUrls(walletManager.wallet),
+ tokens = walletManager.cardTokens.map { token ->
+ EmailTokenInfo(token.id, token.name, token.contractAddress)
+ },
)
}
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
index 4baf39c3e2..7ad4477154 100644
--- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
@@ -33,10 +33,10 @@ class FeedbackDataBuilder(
builder.appendKeyValue("Derivation path", it.derivationPath)
builder.appendKeyValue("Outputs count", it.outputsCount)
- infoHolder.tokens[it.blockchain]?.let { tokens ->
+ if (it.tokens.isNotEmpty()) {
builder.append("Tokens:")
breakLine()
- tokens.forEach { token ->
+ it.tokens.forEach { token ->
builder.appendKeyValue("ID", token.id ?: "[custom token]")
builder.appendKeyValue("Name", token.name)
builder.appendKeyValue("Contract address", token.contractAddress)
diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt
similarity index 95%
rename from app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
rename to app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt
index 5d8ad6ed1e..38479e6a1f 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.common.redux
import com.tangem.common.extensions.VoidCallback
+import com.tangem.core.navigation.StateDialog
import com.tangem.tap.common.TestAction
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.wallet.models.Currency
@@ -8,8 +9,6 @@ import com.tangem.tap.features.wallet.models.Currency
/**
[REDACTED_AUTHOR]
*/
-interface StateDialog
-
sealed class AppDialog : StateDialog {
data class SimpleOkDialog(val header: String, val message: String, val onOk: VoidCallback? = null) : AppDialog()
data class SimpleOkErrorDialog(val message: String, val onOk: VoidCallback? = null) : AppDialog()
@@ -33,8 +32,6 @@ sealed class AppDialog : StateDialog {
val action: VoidCallback? = null,
)
- object ScanFailsDialog : AppDialog()
-
data class AddressInfoDialog(
val currency: Currency,
val addressData: WalletDataModel.AddressData,
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 7210599e41..8175888c08 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt
@@ -14,7 +14,6 @@ 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.signin.redux.SignInReducer
-import com.tangem.tap.features.sprinklr.redux.SprinklrReducer
import com.tangem.tap.features.tokens.legacy.redux.TokensReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
import com.tangem.tap.features.walletSelector.redux.WalletSelectorReducer
@@ -45,7 +44,6 @@ 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),
signInState = SignInReducer.reduce(action, state),
daggerGraphState = DaggerGraphReducer.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 4acd6af6ef..7b9b9f2bd4 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
@@ -33,8 +33,6 @@ import com.tangem.tap.features.shop.redux.ShopMiddleware
import com.tangem.tap.features.shop.redux.ShopState
import com.tangem.tap.features.signin.redux.SignInMiddleware
import com.tangem.tap.features.signin.redux.SignInState
-import com.tangem.tap.features.sprinklr.redux.SprinklrMiddleware
-import com.tangem.tap.features.sprinklr.redux.SprinklrState
import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
import com.tangem.tap.features.tokens.legacy.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
@@ -67,7 +65,6 @@ data class AppState(
val welcomeState: WelcomeState = WelcomeState(),
val saveWalletState: SaveWalletState = SaveWalletState(),
val walletSelectorState: WalletSelectorState = WalletSelectorState(),
- val sprinklrState: SprinklrState = SprinklrState(),
val signInState: SignInState = SignInState(),
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
) : StateType {
@@ -109,7 +106,6 @@ data class AppState(
WalletSelectorMiddleware().middleware,
LockUserWalletsTimerMiddleware().middleware,
AccessCodeRequestPolicyMiddleware().middleware,
- SprinklrMiddleware().middleware,
SignInMiddleware.middleware,
DaggerGraphMiddleware.daggerGraphMiddleware,
LegacyMiddleware.legacyMiddleware,
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 15227361e9..13a9ee6ac8 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
@@ -2,6 +2,7 @@ package com.tangem.tap.common.redux.global
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
+import com.tangem.core.navigation.StateDialog
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.config.models.ChatConfig
import com.tangem.domain.apptheme.model.AppThemeMode
@@ -11,7 +12,10 @@ import com.tangem.tap.common.analytics.topup.TopUpController
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.feedback.FeedbackData
import com.tangem.tap.common.feedback.FeedbackManager
-import com.tangem.tap.common.redux.*
+import com.tangem.tap.common.redux.DebugErrorAction
+import com.tangem.tap.common.redux.ErrorAction
+import com.tangem.tap.common.redux.NotificationAction
+import com.tangem.tap.common.redux.ToastNotificationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
index 3ce7fcd492..4d30db8c36 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
@@ -4,17 +4,18 @@ import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
+import com.tangem.core.navigation.StateDialog
import com.tangem.datasource.config.models.Config
import com.tangem.domain.common.LogConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
+import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.entities.FiatCurrency
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.extensions.dispatchWithMain
-import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.WalletAction
@@ -25,16 +26,11 @@ 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.preferencesStorage
import com.tangem.tap.proxy.redux.DaggerGraphState
-import com.tangem.tap.scope
-import com.tangem.tap.store
-import com.tangem.tap.walletCurrenciesManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.rekotlin.Action
-import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
import java.util.Locale
@@ -45,14 +41,14 @@ object GlobalMiddleware {
private val globalMiddlewareHandler: Middleware = { dispatch, appState ->
{ nextDispatch ->
{ action ->
- handleAction(action, appState, dispatch)
+ handleAction(action, appState)
nextDispatch(action)
}
}
}
@Suppress("LongMethod", "ComplexMethod")
-private fun handleAction(action: Action, appState: () -> AppState?, dispatch: DispatchFunction) {
+private fun handleAction(action: Action, appState: () -> AppState?) {
when (action) {
is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
when (action.result) {
@@ -61,7 +57,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
if (action.result.error is TangemSdkError.UserCancelled) {
store.dispatch(GlobalAction.ScanFailsCounter.Increment)
if (store.state.globalState.scanCardFailsCounter >= 2) {
- store.dispatchDialogShow(AppDialog.ScanFailsDialog)
+ store.dispatchDialogShow(StateDialog.ScanFailsDialog)
}
} else {
store.dispatch(GlobalAction.ScanFailsCounter.Reset)
@@ -110,10 +106,10 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
// if config not set -> try to get it based on a scanResponse.productType
- val unsafeChatConfig = action.chatConfig ?: config.zendesk
+ val unsafeChatConfig = action.chatConfig ?: config.sprinklr
val chatConfig = unsafeChatConfig.guard {
- store.dispatchDebugErrorNotification("ZendeskConfig not initialized")
+ store.dispatchDebugErrorNotification("The chat config is not initialized")
return
}
feedbackManager.openChat(chatConfig, action.feedbackData)
@@ -131,8 +127,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
scope.launch {
val scanResponseProvider: () -> ScanResponse? = {
- store.state.globalState.scanResponse
- ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
+ userWalletsListManager.selectedUserWalletSync?.scanResponse
}
val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card }
@@ -177,22 +172,25 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
walletCurrenciesManager.addListener(action.topUpController)
}
is GlobalAction.UpdateUserWalletsListManager -> {
+ val walletManagersFacade = store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)
+
/*
- * If UserWalletsListManager's implementation is changed,
- * then all selectedUserWallet's observers is became irrelevant
+ * If implementation of the UserWalletsListManager is changed,
+ * then all observers of selectedUserWallet become irrelevant.
*/
action.manager.selectedUserWallet
.distinctUntilChanged()
.onEach { userWallet ->
- Analytics.send(event = Basic.WalletOpened())
+ Analytics.send(Basic.WalletOpened())
store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder ->
- infoHolder.setCardInfo(data = userWallet.scanResponse)
+ infoHolder.setCardInfo(userWallet.scanResponse)
- store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)
- .getAll(userWalletId = userWallet.walletId)
+ walletManagersFacade
+ .getAll(userWallet.walletId)
+ .distinctUntilChanged()
.onEach(infoHolder::setWalletsInfo)
- .launchIn(scope)
+ .launchIn(mainScope)
}
}
.flowOn(Dispatchers.IO)
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
index f5a30ba673..c20fbea0f9 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.common.redux.global
+import com.tangem.core.navigation.StateDialog
import com.tangem.datasource.config.ConfigManager
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.scan.ScanResponse
@@ -7,7 +8,6 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.tap.common.analytics.topup.TopUpController
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.feedback.FeedbackManager
-import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.onboarding.OnboardingManager
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
index 6be8513880..ad2b8f3178 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
@@ -8,10 +8,7 @@ import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.tap.activityResultCaller
import com.tangem.tap.common.CustomTabsManager
-import com.tangem.tap.common.extensions.dispatchOnMain
-import com.tangem.tap.common.extensions.openFragment
-import com.tangem.tap.common.extensions.popBackTo
-import com.tangem.tap.common.extensions.shareText
+import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.store
import org.rekotlin.Middleware
@@ -60,6 +57,7 @@ val navigationMiddleware: Middleware = { _, state ->
intent.data = action.url
navState?.activity?.get()?.startActivity(intent)
}
+ is NavigationAction.OpenDialog -> store.dispatchDialogShow(action.stateDialog)
is NavigationAction.OpenBiometricsSettings -> {
val settingsAction = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
index e9a9087e06..d66c81c65f 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
@@ -63,9 +63,9 @@ internal object TokensDomainModule {
@ViewModelScoped
fun provideRemoveCurrencyUseCase(
currenciesRepository: CurrenciesRepository,
- dispatchers: CoroutineDispatcherProvider,
+ walletManagersFacade: WalletManagersFacade,
): RemoveCurrencyUseCase {
- return RemoveCurrencyUseCase(currenciesRepository, dispatchers)
+ return RemoveCurrencyUseCase(currenciesRepository, walletManagersFacade)
}
@Provides
@@ -167,9 +167,19 @@ internal object TokensDomainModule {
fun provideGetCryptoCurrencyActionsUseCase(
rampStateManager: RampStateManager,
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
+ currenciesRepository: CurrenciesRepository,
+ quotesRepository: QuotesRepository,
+ networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetCryptoCurrencyActionsUseCase {
- return GetCryptoCurrencyActionsUseCase(rampStateManager, marketCryptoCurrencyRepository, dispatchers)
+ return GetCryptoCurrencyActionsUseCase(
+ rampManager = rampStateManager,
+ marketCryptoCurrencyRepository = marketCryptoCurrencyRepository,
+ currenciesRepository = currenciesRepository,
+ quotesRepository = quotesRepository,
+ networksRepository = networksRepository,
+ dispatchers = dispatchers,
+ )
}
@Provides
diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt
index 1c4bc06044..63afaaeaf2 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/WalletManagersFacadeModule.kt
@@ -1,6 +1,9 @@
package com.tangem.tap.di.domain
+import com.squareup.moshi.Moshi
+import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.config.ConfigManager
+import com.tangem.datasource.di.SdkMoshi
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.walletmanager.DefaultWalletManagersFacade
@@ -21,7 +24,15 @@ internal object WalletManagersFacadeModule {
walletManagersStore: WalletManagersStore,
userWalletsStore: UserWalletsStore,
configManager: ConfigManager,
+ assetReader: AssetReader,
+ @SdkMoshi moshi: Moshi,
): WalletManagersFacade {
- return DefaultWalletManagersFacade(walletManagersStore, userWalletsStore, configManager)
+ return DefaultWalletManagersFacade(
+ walletManagersStore = walletManagersStore,
+ userWalletsStore = userWalletsStore,
+ configManager = configManager,
+ assetReader = assetReader,
+ moshi = moshi,
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt
index 3634bdc1b8..236f6e9b8f 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.di.domain
+import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.repository.WalletsRepository
@@ -58,8 +59,11 @@ internal object WalletsDomainModule {
@Provides
@ViewModelScoped
- fun providesSelectWalletUseCase(walletsStateHolder: WalletsStateHolder): SelectWalletUseCase {
- return SelectWalletUseCase(walletsStateHolder = walletsStateHolder)
+ fun providesSelectWalletUseCase(
+ walletsStateHolder: WalletsStateHolder,
+ reduxStateHolder: ReduxStateHolder,
+ ): SelectWalletUseCase {
+ return SelectWalletUseCase(walletsStateHolder, reduxStateHolder)
}
@Provides
diff --git a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt b/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt
deleted file mode 100644
index 3d2a5764e2..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/RatesRepository.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.tangem.tap.domain
-
-import com.tangem.common.services.Result
-import com.tangem.datasource.api.tangemTech.TangemTechApi
-import com.tangem.domain.common.ThrottlerWithValues
-import com.tangem.tap.features.wallet.models.Currency
-import com.tangem.utils.coroutines.CoroutineDispatcherProvider
-import kotlinx.coroutines.withContext
-import java.math.BigDecimal
-
-class RatesRepository(
- private val tangemTechApi: TangemTechApi,
- private val dispatchers: CoroutineDispatcherProvider,
-) {
- private val throttler = ThrottlerWithValues?>(60000)
-
- suspend fun loadFiatRate(currencyId: String, coinsList: List): Result =
- withContext(dispatchers.io) {
- // get and submit previous result of equivalents.
- val throttledResult = coinsList.filter { throttler.isStillThrottled(it) }.map {
- Pair(it, throttler.geValue(it))
- }
-
- val currenciesToUpdate = coinsList.filter { !throttler.isStillThrottled(it) }
- val coinIds = currenciesToUpdate.mapNotNull { it.coinId }.distinct()
- if (coinIds.isEmpty()) return@withContext handleFiatRatesResult(throttledResult.toMap())
-
- runCatching { tangemTechApi.getRates(currencyId.lowercase(), coinIds.joinToString(",")) }
- .onSuccess { response ->
- val ratesResultList: Map> = response.rates.mapValues {
- Result.Success(it.value.toBigDecimal())
- }
- val updatedCurrencies = throttledResult.toMap().toMutableMap()
- coinsList.forEach { currency ->
- ratesResultList[currency.coinId]?.let {
- updatedCurrencies[currency] = it
- throttler.updateThrottlingTo(currency)
- throttler.setValue(currency, it)
- }
- }
- return@withContext handleFiatRatesResult(updatedCurrencies)
- }
- .onFailure { Result.Failure(it) }
-
- error("Unreachable code because runCatching must return result")
- }
-
- private fun handleFiatRatesResult(rates: Map?>): Result.Success {
- val success = mutableMapOf()
- val failures = mutableMapOf()
-
- rates.mapNotNull { (currency, priceResult) ->
- when (priceResult) {
- is Result.Success -> success[currency] = priceResult.data
- is Result.Failure -> failures[currency] = priceResult.error
- else -> null
- }
- }
-
- return Result.Success(success to failures)
- }
-
- fun clear() {
- throttler.clear()
- }
-}
-
-typealias RatesResult = Pair, MutableMap>
-
-val RatesResult.loadedRates
- get() = this.first
-
-val RatesResult.failedRates
- get() = this.second
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
index 7b5691b79d..ea5bd2e3aa 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.domain
import android.content.res.Resources
+import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.tangem.Message
import com.tangem.TangemSdk
@@ -87,12 +88,13 @@ class TangemSdkManager(
suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult {
return runTaskAsync(
- CreateProductWalletTask(
+ runnable = CreateProductWalletTask(
cardTypesResolver = scanResponse.cardTypesResolver,
derivationStyleProvider = scanResponse.derivationStyleProvider,
),
- scanResponse.card.cardId,
- Message(resources.getString(R.string.initial_message_create_wallet_body)),
+ cardId = scanResponse.card.cardId,
+ initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)),
+ iconScanRes = if (scanResponse.cardTypesResolver.isRing()) R.drawable.img_hand_scan_ring else null,
)
}
@@ -100,7 +102,7 @@ class TangemSdkManager(
scanResponse: ScanResponse,
mnemonic: String,
): CompletionResult {
- val mnemonic = try {
+ val defaultMnemonic = try {
DefaultMnemonic(mnemonic, tangemSdk.wordlist)
} catch (e: TangemSdkError.MnemonicException) {
return CompletionResult.Failure(e)
@@ -109,7 +111,7 @@ class TangemSdkManager(
CreateProductWalletTask(
scanResponse.cardTypesResolver,
derivationStyleProvider = scanResponse.derivationStyleProvider,
- mnemonic,
+ defaultMnemonic,
),
scanResponse.card.cardId,
Message(resources.getString(R.string.initial_message_create_wallet_body)),
@@ -221,9 +223,10 @@ class TangemSdkManager(
cardId: String? = null,
initialMessage: Message? = null,
accessCode: String? = null,
+ @DrawableRes iconScanRes: Int? = null,
): CompletionResult = withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
- tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode) { result ->
+ tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode, iconScanRes) { result ->
if (continuation.isActive) continuation.resume(result)
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
index e6be2ff4c2..a3fac64bc1 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt
@@ -209,9 +209,7 @@ private class ScanWalletProcessor(
session: CardSession,
callback: (result: CompletionResult) -> Unit,
) {
- val isWallet2 = card.settings.isKeysImportAllowed || card.firmwareVersion >= FirmwareVersion.KeysImportAvailable
-
- val productType = if (isWallet2) ProductType.Wallet2 else ProductType.Wallet
+ val productType = getWalletProductType(card)
val config = CardConfig.createConfig(card)
scope.launch {
val scanResponse = ScanResponse(
@@ -239,6 +237,19 @@ private class ScanWalletProcessor(
}
}
+ private fun getWalletProductType(card: CardDTO): ProductType {
+ if (card.batchId == CardDTO.RING_BATCH_ID) {
+ return ProductType.Ring
+ }
+ return if (card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
+ card.settings.isKeysImportAllowed
+ ) {
+ ProductType.Wallet2
+ } else {
+ ProductType.Wallet
+ }
+ }
+
private suspend fun collectDerivations(
card: CardDTO,
config: CardConfig,
diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt
index c3cfe566f3..514b75eb10 100644
--- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt
@@ -108,9 +108,9 @@ class UserTokensRepository(
.fold(
onSuccess = { response ->
response.getOrThrow()
+ .also { storageService.saveUserTokens(userWalletId, it) }
.tokens
.mapNotNull(Currency.Companion::fromTokenResponse)
- .also { storageService.saveUserTokens(userWalletId, it.toUserTokensResponse()) }
.distinct()
},
onFailure = { handleGetUserTokensFailure(userWalletId = userWalletId, error = it) },
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt
index f4c32feb54..af472b3608 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt
@@ -1,7 +1,6 @@
package com.tangem.tap.domain.userWalletList.implementation
import com.tangem.common.*
-import com.tangem.common.extensions.guard
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
@@ -16,6 +15,7 @@ import com.tangem.tap.domain.userWalletList.utils.toUserWallets
import com.tangem.tap.domain.userWalletList.utils.updateWith
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
+import timber.log.Timber
@OptIn(ExperimentalCoroutinesApi::class)
internal class BiometricUserWalletsListManager(
@@ -56,12 +56,30 @@ internal class BiometricUserWalletsListManager(
override val walletsCount: Int
get() = state.value.userWallets.size
- override suspend fun unlock(): CompletionResult {
- return unlockWithBiometryInternal().mapUnlockResult()
- }
+ override suspend fun unlock(throwIfNotAllWalletsUnlocked: Boolean): CompletionResult {
+ return unlockWithBiometryInternal()
+ .mapFailure { error ->
+ Timber.e(error, "Unable to unlock user wallets")
+ if (error is UserWalletsListError) {
+ error
+ } else {
+ UserWalletsListError.UnableToUnlockUserWallets(error)
+ }
+ }
+ .map {
+ if (throwIfNotAllWalletsUnlocked && state.value.userWallets.any(UserWallet::isLocked)) {
+ Timber.e("Not all user wallets have been unlocked")
+ throw UserWalletsListError.NotAllUserWalletsUnlocked
+ }
- override suspend fun unlockAndSelect(selectedWalletId: UserWalletId): CompletionResult {
- return unlockWithBiometryInternal(selectedWalletId = selectedWalletId).mapUnlockResult()
+ val selectedUserWallet = selectedUserWalletSync
+ if (selectedUserWallet == null || selectedUserWallet.isLocked) {
+ Timber.e("Unable to find selected user wallet")
+ throw UserWalletsListError.NoUserWalletSelected
+ } else {
+ selectedUserWallet
+ }
+ }
}
override fun lock() {
@@ -86,7 +104,7 @@ internal class BiometricUserWalletsListManager(
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult {
return if (canOverride) {
- saveInternal(userWallet, changeSelectedUserWallet = true)
+ saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false)
} else {
val isWalletSaved = state.value.userWallets
.any {
@@ -96,7 +114,7 @@ internal class BiometricUserWalletsListManager(
if (isWalletSaved) {
CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved)
} else {
- saveInternal(userWallet, changeSelectedUserWallet = true)
+ saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false)
}
}
}
@@ -110,7 +128,7 @@ internal class BiometricUserWalletsListManager(
update(storedUserWallet)
}
.flatMap { updatedUserWallet ->
- saveInternal(updatedUserWallet, changeSelectedUserWallet = false)
+ saveInternal(updatedUserWallet, changeSelectedUserWallet = false, canOverridePublicInfo = true)
}
.flatMap {
get(userWalletId)
@@ -164,10 +182,11 @@ internal class BiometricUserWalletsListManager(
private suspend fun saveInternal(
userWallet: UserWallet,
changeSelectedUserWallet: Boolean,
+ canOverridePublicInfo: Boolean,
): CompletionResult {
return saveEncryptionKeyIfNotNull(userWallet)
.flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = it) }
- .flatMap { publicInformationRepository.save(userWallet) }
+ .flatMap { publicInformationRepository.save(userWallet, canOverridePublicInfo) }
.map {
if (changeSelectedUserWallet) {
selectedUserWalletRepository.set(userWallet.walletId)
@@ -188,7 +207,7 @@ internal class BiometricUserWalletsListManager(
}
}
- private suspend fun unlockWithBiometryInternal(selectedWalletId: UserWalletId? = null): CompletionResult {
+ private suspend fun unlockWithBiometryInternal(): CompletionResult {
return keysRepository.getAll()
.map { keys ->
state.update { prevState ->
@@ -197,7 +216,7 @@ internal class BiometricUserWalletsListManager(
)
}
}
- .flatMap { loadModels(selectedWalletId = selectedWalletId) }
+ .flatMap { loadModels() }
.map {
state.update { prevState ->
val hasLockedUserWallets = prevState.userWallets.any { it.isLocked }
@@ -206,24 +225,6 @@ internal class BiometricUserWalletsListManager(
}
}
- private fun CompletionResult.mapUnlockResult(): CompletionResult {
- return this
- .mapFailure { error ->
- if (error is UserWalletsListError) {
- error
- } else {
- UserWalletsListError.UnableToUnlockUserWallets(cause = error)
- }
- }
- .map {
- selectedUserWalletSync.guard {
- throw UserWalletsListError.UnableToUnlockUserWallets(
- cause = IllegalStateException("No user wallet selected"),
- )
- }
- }
- }
-
private suspend fun saveEncryptionKeyIfNotNull(userWallet: UserWallet): CompletionResult {
val encryptionKey = userWallet.scanResponse.card.encryptionKey
?.let { UserWalletEncryptionKey(userWallet.walletId, it) }
@@ -245,7 +246,7 @@ internal class BiometricUserWalletsListManager(
}
}
- private suspend fun loadModels(selectedWalletId: UserWalletId? = null): CompletionResult {
+ private suspend fun loadModels(): CompletionResult {
return getSavedUserWallets()
.map { userWallets ->
if (userWallets.isNotEmpty()) {
@@ -255,7 +256,7 @@ internal class BiometricUserWalletsListManager(
prevState.copy(
userWallets = wallets,
selectedUserWalletId = findOrSetSelectedUserWalletId(
- prevSelectedWalletId = selectedWalletId ?: prevState.selectedUserWalletId,
+ prevSelectedWalletId = prevState.selectedUserWalletId,
userWallets = wallets,
),
)
@@ -316,7 +317,7 @@ internal class BiometricUserWalletsListManager(
private fun findSelectedUserWallet(userWallets: List = state.value.userWallets): UserWallet? {
return userWallets.firstOrNull {
- it.walletId == state.value.selectedUserWalletId && !it.isLocked
+ it.walletId == state.value.selectedUserWalletId
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt
index 6b40cf721e..383807f8fa 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt
@@ -6,7 +6,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
internal interface UserWalletsPublicInformationRepository {
- suspend fun save(userWallet: UserWallet): CompletionResult
+ suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult
suspend fun getAll(): CompletionResult>
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt
index bb40308916..17c733dd35 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt
@@ -38,7 +38,7 @@ internal class BiometricUserWalletsKeysRepository(
is TangemSdkError.AuthenticationPermanentLockout ->
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = true)
is TangemSdkError.KeystoreInvalidated ->
- UserWalletsListError.EncryptionKeyInvalidated
+ UserWalletsListError.AllKeysInvalidated
is TangemSdkError.AuthenticationUnavailable ->
UserWalletsListError.BiometricsAuthenticationDisabled
else -> error
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt
index 1f64334fd7..9ee2498fa8 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt
@@ -24,18 +24,23 @@ internal class DefaultUserWalletsPublicInformationRepository(
Types.newParameterizedType(List::class.java, UserWalletPublicInformation::class.java),
)
- override suspend fun save(userWallet: UserWallet): CompletionResult {
+ override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult {
return withContext(Dispatchers.IO) {
- getAll()
- .flatMap { savedInformation ->
- val infoToSave = withContext(Dispatchers.Default) {
- savedInformation.addOrReplace(userWallet.publicInformation) {
- userWallet.walletId == it.walletId
- }
+ getAll().flatMap { savedWalletsInformation ->
+ val infoToSave = if (canOverride) {
+ savedWalletsInformation.addOrReplace(userWallet.publicInformation) {
+ it.walletId == userWallet.walletId
+ }
+ } else {
+ if (savedWalletsInformation.none { it.walletId == userWallet.walletId }) {
+ savedWalletsInformation + userWallet.publicInformation
+ } else {
+ return@withContext CompletionResult.Success(Unit)
}
-
- save(infoToSave)
}
+
+ save(infoToSave)
+ }
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
index f84ad537dd..e9f267c509 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt
@@ -16,7 +16,6 @@ import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toDecompressedPublicKey
import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.Analytics
-import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.events.AnalyticsParam
@@ -31,12 +30,14 @@ import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
+import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.details.redux.walletconnect.*
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
+import com.tangem.tap.userWalletsListManager
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.*
import timber.log.Timber
import java.math.BigDecimal
@@ -49,7 +50,7 @@ class WalletConnectSdkHelper {
val blockchain = Blockchain.fromNetworkId(data.networkId) ?: return null
val walletManager = getWalletManager(blockchain, data.rawDerivationPath) ?: return null
- walletManager.safeUpdate()
+ walletManager.safeUpdate(isDemoCard())
val wallet = walletManager.wallet
val balance = wallet.amounts[AmountType.Coin]?.value ?: return null
@@ -108,13 +109,20 @@ class WalletConnectSdkHelper {
)
}
- private fun getWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager? {
- val blockchainNetwork = BlockchainNetwork(
+ fun isDemoCard(): Boolean {
+ val userWallet = userWalletsListManager.selectedUserWalletSync ?: return false
+ return userWallet.scanResponse.isDemoCard()
+ }
+
+ private suspend fun getWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager? {
+ val userWallet = userWalletsListManager.selectedUserWalletSync ?: return null
+ val walletManagerFacade = store.state.daggerGraphState
+ .get(DaggerGraphState::walletManagersFacade)
+ return walletManagerFacade.getOrCreateWalletManager(
+ userWalletId = userWallet.walletId,
blockchain = blockchain,
derivationPath = derivationPath,
- tokens = emptyList(),
)
- return store.state.walletState.getWalletManager(blockchainNetwork)
}
suspend fun completeTransaction(data: WcTransactionData, cardId: String?): String? {
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt
index 6a584a0548..3b69331ae2 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt
@@ -1,23 +1,28 @@
package com.tangem.tap.domain.walletconnect2.app
-import com.tangem.blockchain.common.*
-import com.tangem.domain.common.extensions.*
-import com.tangem.tap.domain.walletconnect2.domain.*
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.domain.common.extensions.fromNetworkId
+import com.tangem.domain.common.extensions.toNetworkId
+import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper
class TangemWcBlockchainHelper : WcBlockchainHelper {
override fun chainIdToNetworkIdOrNull(chainId: String): String? {
- val parsed = chainId.split(CHAIN_SEPARATOR)
- if (parsed.size != 2) return null
- when {
- parsed.first() == EVM_NAMESPACE -> {
- val chainIdInt = parsed[1].toIntOrNull() ?: return null
- return Blockchain.fromChainId(chainIdInt)?.toNetworkId()
+ val parsedId = chainId.parseId() ?: return null
+ val blockchain = parsedId.chainIdToBlockchain()
+
+ return blockchain?.toNetworkId()
+ }
+
+ override fun chainIdToMissingNetworkNameOrNull(chainId: String): String? {
+ val parsedId = chainId.parseId() ?: return null
+ val blockchain = parsedId.chainIdToBlockchain()
+
+ return blockchain?.fullName
+ ?: if (parsedId.first == EVM_NAMESPACE) {
+ chainId
+ } else {
+ parsedId.first.replaceFirstChar(Char::titlecase)
}
- else -> {
- val blockchain = Blockchain.fromNetworkId(parsed.first())
- return if (supportedNonEvmBlockchains.contains(blockchain)) blockchain?.toNetworkId() else null
- }
- }
}
override fun networkIdToChainIdOrNull(networkId: String): String? {
@@ -57,6 +62,26 @@ private fun Blockchain.getCaip2Namespace(): String? {
}
}
+private fun String.parseId(): Pair? {
+ val parsed = this.split(CHAIN_SEPARATOR)
+ if (parsed.size != 2) return null
+
+ return parsed[0] to parsed[1]
+}
+
+private fun Pair.chainIdToBlockchain(): Blockchain? {
+ return when (first) {
+ EVM_NAMESPACE -> {
+ second.toIntOrNull()
+ ?.let(Blockchain::fromChainId)
+ }
+ else -> {
+ Blockchain.fromNetworkId(networkId = first)
+ .takeIf(supportedNonEvmBlockchains::contains)
+ }
+ }
+}
+
private val supportedNonEvmBlockchains = emptySet() // TODO: add supported networks
private const val EVM_NAMESPACE = "eip155"
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt
index d95640dc2b..c3af721e25 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt
@@ -75,9 +75,11 @@ class WalletConnectInteractor(
is WalletConnectEvents.SessionApprovalError -> {
val error = when (wcEvent.error) {
is WalletConnectError.ApprovalErrorMissingNetworks -> {
- val missingNetworks = wcEvent.error.missingChains
- .map { blockchainHelper.chainIdToNetworkIdOrNull(it) }
- WalletConnectError.ApprovalErrorAddNetwork(missingNetworks.filterNotNull())
+ val missingNetworks = wcEvent.error.missingChains.mapNotNull {
+ blockchainHelper.chainIdToMissingNetworkNameOrNull(it)
+ }
+
+ WalletConnectError.ApprovalErrorAddNetwork(missingNetworks)
}
else -> wcEvent.error
}
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt
index 06f0843223..489d086af0 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt
@@ -3,6 +3,8 @@ package com.tangem.tap.domain.walletconnect2.domain
interface WcBlockchainHelper {
fun chainIdToNetworkIdOrNull(chainId: String): String?
+ fun chainIdToMissingNetworkNameOrNull(chainId: String): String?
+
fun networkIdToChainIdOrNull(networkId: String): String?
fun getNamespaceFromFullChainIdOrNull(chainId: String): String?
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt
index 3b70cf9f06..16a0cb98a5 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.features.customtoken.impl.domain
import com.tangem.blockchain.common.Blockchain
+import com.tangem.common.core.TangemError
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
@@ -15,5 +16,6 @@ interface CustomTokenInteractor {
suspend fun findToken(address: String, blockchain: Blockchain): FoundToken
/** Save token [customCurrency] */
+ @Throws(TangemError::class)
suspend fun saveToken(customCurrency: CustomCurrency)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt
index a046330f83..7de4ee238b 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt
@@ -4,6 +4,7 @@ import com.tangem.blockchain.blockchains.cardano.CardanoUtils
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
+import com.tangem.common.core.TangemError
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
@@ -16,6 +17,7 @@ import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.models.scan.ScanResponse
+import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
@@ -47,6 +49,14 @@ class DefaultCustomTokenInteractor(
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
) : CustomTokenInteractor {
+ // TODO: Move to DI
+ private val addCryptoCurrenciesUseCase by lazy(LazyThreadSafetyMode.NONE) {
+ val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
+ val networksRepository = store.state.daggerGraphState.get(DaggerGraphState::networksRepository)
+
+ AddCryptoCurrenciesUseCase(currenciesRepository, networksRepository)
+ }
+
override suspend fun findToken(address: String, blockchain: Blockchain): FoundToken {
return featureRepository.findToken(
address = address,
@@ -60,8 +70,12 @@ class DefaultCustomTokenInteractor(
val currency = Currency.fromCustomCurrency(customCurrency)
val isNeedToDerive = isNeedToDerive(userWallet, currency)
if (isNeedToDerive) {
- deriveMissingBlockchains(userWallet = userWallet, currencyList = listOf(currency)) {
- submitAdd(userWallet = userWallet, currency = currency)
+ deriveMissingBlockchains(
+ userWallet = userWallet,
+ currencyList = listOf(currency),
+ onSuccess = { submitAdd(userWallet = userWallet.copy(scanResponse = it), currency = currency) },
+ ) {
+ throw it
}
} else {
submitAdd(userWallet, currency)
@@ -77,6 +91,7 @@ class DefaultCustomTokenInteractor(
userWallet: UserWallet,
currencyList: List,
onSuccess: suspend (ScanResponse) -> Unit,
+ onFailure: suspend (TangemError) -> Unit,
) {
val scanResponse = userWallet.scanResponse
val config = CardConfig.createConfig(scanResponse.card)
@@ -121,6 +136,7 @@ class DefaultCustomTokenInteractor(
onSuccess(updatedScanResponse)
}
is CompletionResult.Failure -> {
+ onFailure.invoke(result.error)
store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens"))
}
}
@@ -219,21 +235,13 @@ class DefaultCustomTokenInteractor(
updatedScanResponse: ScanResponse,
currencyList: List,
) {
- val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
- val networksRepository = store.state.daggerGraphState.get(DaggerGraphState::networksRepository)
scope.launch {
userWalletsListManager.update(
userWalletId = userWalletId,
update = { it.copy(scanResponse = updatedScanResponse) },
)
- currenciesRepository.addCurrencies(userWalletId = userWalletId, currencies = currencyList)
- val networks = currencyList.map { it.network }.toSet()
- networksRepository.getNetworkStatusesSync(
- userWalletId = userWalletId,
- networks = networks,
- refresh = true,
- )
+ addCryptoCurrenciesUseCase(userWalletId, currencyList)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt
index d69f1c08fa..ba881741b0 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt
@@ -55,7 +55,7 @@ internal object AddCustomTokenPreviewData {
value = "",
onValueChange = {},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
- label = TextReference.Res(R.string.custom_token_token_symbol_input_title),
+ label = TextReference.Res(R.string.custom_token_token_symbol_input_title_old),
placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder),
isEnabled = false,
),
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
index cc84a579e0..605b1318e7 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
@@ -270,7 +270,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
value = "",
onValueChange = actionsHandler::onTokenSymbolValueChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
- label = TextReference.Res(R.string.custom_token_token_symbol_input_title),
+ label = TextReference.Res(R.string.custom_token_token_symbol_input_title_old),
placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder),
isEnabled = false,
)
@@ -898,7 +898,9 @@ internal class AddCustomTokenViewModel @Inject constructor(
viewModelScope.launch(dispatchers.io) {
runCatching { featureInteractor.saveToken(currency) }
.onSuccess { featureRouter.openWalletScreen() }
- .onFailure(Timber::e)
+ .onFailure {
+ Timber.e(it)
+ }
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
index dbc83c5dfa..4504a0bb43 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
@@ -558,15 +558,10 @@ class DetailsMiddleware {
return userWalletsListManager.save(userWallet)
.doOnSuccess {
+ store.onUserWalletSelected(userWallet)
+
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
-
- val walletFeatureToggles = store.state.daggerGraphState
- .get(DaggerGraphState::walletFeatureToggles)
-
- if (!walletFeatureToggles.isRedesignedScreenEnabled) {
- store.onUserWalletSelected(userWallet)
- }
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
index ff94b5cc7a..5c5815107f 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
@@ -93,6 +93,8 @@ private fun handlePrepareCardSettingsScreen(
cardTypesResolver: CardTypesResolver,
state: DetailsState,
): DetailsState {
+ val isTangemWallet = cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()
+ val isShowPasswordResetRadioButton = isTangemWallet && card.backupStatus is CardDTO.BackupStatus.Active
val cardSettingsState = CardSettingsState(
cardInfo = card.toCardInfo(cardTypesResolver),
manageSecurityState = prepareSecurityOptions(card, cardTypesResolver),
@@ -110,6 +112,7 @@ private fun handlePrepareCardSettingsScreen(
} else {
null
},
+ isShowPasswordResetRadioButton = isShowPasswordResetRadioButton,
)
return state.copy(cardSettingsState = cardSettingsState)
}
@@ -152,22 +155,28 @@ private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: Card
}
private fun handleEraseWallet(action: DetailsAction.ResetToFactory, state: DetailsState): DetailsState {
+ val cardSettingsState = state.cardSettingsState
return when (action) {
is DetailsAction.ResetToFactory.AcceptCondition1 -> {
val warning1Checked = action.accepted
+ val resetButtonEnabled = if (cardSettingsState?.isShowPasswordResetRadioButton == true) {
+ warning1Checked && cardSettingsState.condition2Checked
+ } else {
+ warning1Checked
+ }
state.copy(
- cardSettingsState = state.cardSettingsState?.copy(
+ cardSettingsState = cardSettingsState?.copy(
condition1Checked = action.accepted,
- resetButtonEnabled = warning1Checked && state.cardSettingsState.condition2Checked,
+ resetButtonEnabled = resetButtonEnabled,
),
)
}
is DetailsAction.ResetToFactory.AcceptCondition2 -> {
val warning2Checked = action.accepted
state.copy(
- cardSettingsState = state.cardSettingsState?.copy(
+ cardSettingsState = cardSettingsState?.copy(
condition2Checked = action.accepted,
- resetButtonEnabled = warning2Checked && state.cardSettingsState.condition1Checked,
+ resetButtonEnabled = warning2Checked && cardSettingsState.condition1Checked,
),
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
index 3e49b20905..c17950e504 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsState.kt
@@ -45,6 +45,7 @@ data class CardSettingsState(
val condition1Checked: Boolean,
val condition2Checked: Boolean,
val accessCodeRecovery: AccessCodeRecoveryState? = null,
+ val isShowPasswordResetRadioButton: Boolean,
)
data class ManageSecurityState(
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
index 4ed140a246..3b96ded384 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
@@ -8,7 +8,6 @@ import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.DerivationStyleProvider
-import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.cardTypesResolver
@@ -324,10 +323,9 @@ class WalletConnectMiddleware {
is WalletConnectAction.SessionRejected -> {
when (action.error) {
is WalletConnectError.ApprovalErrorAddNetwork -> {
- val blockchains = action.error.networks.mapNotNull { Blockchain.fromNetworkId(it)?.fullName }
store.dispatchOnMain(
GlobalAction.ShowDialog(
- WalletConnectDialog.AddNetwork(blockchains.toString()),
+ WalletConnectDialog.UnsupportedNetwork(action.error.networks),
),
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
index 0446b1ca82..93f000c3d5 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
@@ -5,9 +5,9 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.derivation.DerivationStyle
+import com.tangem.core.navigation.StateDialog
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.models.scan.ScanResponse
-import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt
index 759e966ed5..b528123bb7 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsScreen.kt
@@ -6,16 +6,13 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import coil.compose.SubcomposeAsyncImage
-import coil.request.ImageRequest
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.domain.userwallets.Artwork
import com.tangem.tap.features.details.ui.common.DetailsMainButton
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
import com.tangem.wallet.R
@@ -32,7 +29,7 @@ internal fun CardSettingsScreen(
modifier = modifier,
content = {
if (needReadCard) {
- CardSettingsReadCard(state.onScanCardClick, state.cardImage)
+ CardSettingsReadCard(state.onScanCardClick)
} else {
CardSettings(state = state)
}
@@ -44,39 +41,39 @@ internal fun CardSettingsScreen(
@Suppress("MagicNumber")
@Composable
-private fun CardSettingsReadCard(onScanCardClick: () -> Unit, cardArtwork: Artwork?) {
+private fun CardSettingsReadCard(onScanCardClick: () -> Unit) {
Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(top = TangemTheme.dimens.size70),
+ modifier = Modifier.fillMaxSize(),
) {
Box(
modifier = Modifier
.fillMaxWidth()
- .padding(start = TangemTheme.dimens.size16, end = TangemTheme.dimens.size16),
+ .padding(bottom = TangemTheme.dimens.spacing40),
) {
- val circleColor = TangemTheme.colors.stroke.primary
- Canvas(
+ Image(
modifier = Modifier
- .size(300.dp)
- .align(Alignment.Center),
- ) {
- drawCircle(
- color = circleColor,
- radius = size.minDimension / 2.0f,
- )
- }
- SubcomposeAsyncImage(
- model = ImageRequest.Builder(context = LocalContext.current)
- .data(cardArtwork?.artworkId)
- .crossfade(enable = true)
- .build(),
- modifier = Modifier
- .align(Alignment.Center)
- .fillMaxWidth(),
- loading = { /* no-op */ },
- error = { /* no-op */ },
+ .fillMaxWidth()
+ .padding(
+ start = TangemTheme.dimens.spacing80,
+ end = TangemTheme.dimens.spacing80,
+ top = TangemTheme.dimens.spacing70,
+ )
+ .rotate(-15f),
+ painter = painterResource(id = R.drawable.card_placeholder_secondary),
contentDescription = null,
+ contentScale = ContentScale.FillWidth,
+ )
+ Image(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ start = TangemTheme.dimens.spacing60,
+ end = TangemTheme.dimens.spacing60,
+ )
+ .rotate(-1f),
+ painter = painterResource(id = R.drawable.card_placeholder_black),
+ contentDescription = null,
+ contentScale = ContentScale.FillWidth,
)
}
Spacer(modifier = Modifier.weight(1f))
@@ -84,9 +81,9 @@ private fun CardSettingsReadCard(onScanCardClick: () -> Unit, cardArtwork: Artwo
modifier = Modifier
.fillMaxWidth()
.padding(
- start = TangemTheme.dimens.size16,
- end = TangemTheme.dimens.size16,
- bottom = TangemTheme.dimens.size32,
+ start = TangemTheme.dimens.spacing16,
+ end = TangemTheme.dimens.spacing16,
+ bottom = TangemTheme.dimens.spacing32,
),
) {
Text(
@@ -103,7 +100,7 @@ private fun CardSettingsReadCard(onScanCardClick: () -> Unit, cardArtwork: Artwo
.verticalScroll(rememberScrollState())
.weight(weight = 1f, fill = false),
)
- Spacer(modifier = Modifier.size(TangemTheme.dimens.size28))
+ Spacer(modifier = Modifier.size(TangemTheme.dimens.size32))
DetailsMainButton(
title = stringResource(id = R.string.scan_card_settings_button),
onClick = onScanCardClick,
@@ -122,21 +119,21 @@ private fun CardSettings(state: CardSettingsScreenState) {
) {
items(state.cardDetails) {
val paddingBottom = when (it) {
- is CardInfo.CardId, is CardInfo.Issuer -> 12.dp
- is CardInfo.SignedHashes -> 14.dp
- is CardInfo.SecurityMode -> 16.dp
- is CardInfo.ChangeAccessCode -> 16.dp
- is CardInfo.AccessCodeRecovery -> 16.dp
- is CardInfo.ResetToFactorySettings -> 28.dp
+ is CardInfo.CardId, is CardInfo.Issuer -> TangemTheme.dimens.spacing12
+ is CardInfo.SignedHashes -> TangemTheme.dimens.spacing14
+ is CardInfo.SecurityMode -> TangemTheme.dimens.spacing16
+ is CardInfo.ChangeAccessCode -> TangemTheme.dimens.spacing16
+ is CardInfo.AccessCodeRecovery -> TangemTheme.dimens.spacing16
+ is CardInfo.ResetToFactorySettings -> TangemTheme.dimens.spacing28
}
val paddingTop = when (it) {
- is CardInfo.CardId -> 0.dp
- is CardInfo.Issuer -> 12.dp
- is CardInfo.SignedHashes -> 12.dp
- is CardInfo.SecurityMode -> 14.dp
- is CardInfo.ChangeAccessCode -> 16.dp
- is CardInfo.AccessCodeRecovery -> 16.dp
- is CardInfo.ResetToFactorySettings -> 16.dp
+ is CardInfo.CardId -> TangemTheme.dimens.spacing0
+ is CardInfo.Issuer -> TangemTheme.dimens.spacing12
+ is CardInfo.SignedHashes -> TangemTheme.dimens.spacing12
+ is CardInfo.SecurityMode -> TangemTheme.dimens.spacing14
+ is CardInfo.ChangeAccessCode -> TangemTheme.dimens.spacing16
+ is CardInfo.AccessCodeRecovery -> TangemTheme.dimens.spacing16
+ is CardInfo.ResetToFactorySettings -> TangemTheme.dimens.spacing16
}
Column(
modifier = Modifier
@@ -145,7 +142,12 @@ private fun CardSettings(state: CardSettingsScreenState) {
enabled = it.clickable,
onClick = { state.onElementClick(it) },
)
- .padding(start = 20.dp, end = 20.dp, bottom = paddingBottom, top = paddingTop),
+ .padding(
+ start = TangemTheme.dimens.spacing20,
+ end = TangemTheme.dimens.spacing20,
+ bottom = paddingBottom,
+ top = paddingTop,
+ ),
) {
val titleColor = if (it.clickable) {
TangemTheme.colors.text.primary1
@@ -162,7 +164,7 @@ private fun CardSettings(state: CardSettingsScreenState) {
color = titleColor,
style = TangemTheme.typography.subtitle1,
)
- Spacer(modifier = Modifier.size(4.dp))
+ Spacer(modifier = Modifier.size(TangemTheme.dimens.size4))
Text(
text = it.subtitle.resolveReference(),
color = subtitleColor,
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt
index cb50141a2c..3fa69eebb7 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/coderecovery/AccessCodeRecoveryFragment.kt
@@ -1,48 +1,35 @@
package com.tangem.tap.features.details.ui.cardsettings.coderecovery
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.platform.ComposeView
-import androidx.fragment.app.Fragment
-import androidx.transition.TransitionInflater
+import androidx.compose.ui.Modifier
import com.tangem.core.navigation.NavigationAction
-import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.screen.ComposeFragment
+import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store
-import com.tangem.wallet.R
+import dagger.hilt.android.AndroidEntryPoint
import org.rekotlin.StoreSubscriber
+import javax.inject.Inject
-class AccessCodeRecoveryFragment : Fragment(), StoreSubscriber {
+@AndroidEntryPoint
+class AccessCodeRecoveryFragment : ComposeFragment(), StoreSubscriber {
private val viewModel = AccessCodeRecoveryViewModel(store)
private var screenState: MutableState =
mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState?.accessCodeRecovery))
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
+ @Inject
+ override lateinit var appThemeModeHolder: AppThemeModeHolder
- val inflater = TransitionInflater.from(requireContext())
- enterTransition = inflater.inflateTransition(R.transition.fade)
- exitTransition = inflater.inflateTransition(R.transition.fade)
- }
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- return ComposeView(requireContext()).apply {
- setContent {
- isTransitionGroup = true
- TangemTheme {
- AccessCodeRecoveryScreen(
- state = screenState.value,
- onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
- )
- }
- }
- }
+ @Composable
+ override fun ScreenContent(modifier: Modifier) {
+ AccessCodeRecoveryScreen(
+ state = screenState.value,
+ onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
+ )
}
override fun onStart() {
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt
index de0784dce0..6675322128 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt
@@ -5,7 +5,6 @@ import androidx.compose.runtime.Immutable
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
-import com.tangem.core.ui.extensions.stringReference
import com.tangem.wallet.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -47,7 +46,7 @@ internal sealed class SettingsItem(
override val onClick: () -> Unit,
) : SettingsItem(
iconResId = R.drawable.ic_plus_24,
- title = stringReference(value = "Add new wallet"),
+ title = resourceReference(R.string.user_wallet_list_add_button),
)
data class ScanWallet(
@@ -55,7 +54,7 @@ internal sealed class SettingsItem(
override val onClick: () -> Unit,
) : SettingsItem(
iconResId = R.drawable.ic_plus_24,
- title = stringReference(value = "Scan new wallet"),
+ title = resourceReference(R.string.scan_card_settings_button),
)
data class LinkMoreCards(
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt
index 8e9fb4ce18..5f12982afd 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt
@@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings
+import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.feedback.FeedbackEmail
import com.tangem.tap.common.feedback.SupportInfo
@@ -128,51 +129,51 @@ internal class DetailsViewModel(
}
private fun navigateToToS() {
- store.dispatch(DisclaimerAction.Show(AppScreen.Details))
+ store.dispatchOnMain(DisclaimerAction.Show(AppScreen.Details))
}
private fun navigateToReferralProgram() {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.ReferralProgram))
+ store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.ReferralProgram))
}
private fun sendFeedback() {
Analytics.send(Settings.ButtonSendFeedback())
- store.dispatch(GlobalAction.SendEmail(FeedbackEmail()))
+ store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail()))
}
private fun navigateToChat() {
Analytics.send(Settings.ButtonChat())
- store.dispatch(GlobalAction.OpenChat(SupportInfo()))
+ store.dispatchOnMain(GlobalAction.OpenChat(SupportInfo()))
}
private fun navigateToAppSettings() {
Analytics.send(Settings.ButtonAppSettings())
- store.dispatch(NavigationAction.NavigateTo(AppScreen.AppSettings))
+ store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.AppSettings))
}
private fun navigateToCardSettings() {
Analytics.send(Settings.ButtonCardSettings())
- store.dispatch(NavigationAction.NavigateTo(AppScreen.CardSettings))
+ store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.CardSettings))
}
private fun linkMoreCards() {
Analytics.send(Settings.ButtonCreateBackup())
- store.dispatch(WalletAction.MultiWallet.BackupWallet)
+ store.dispatchOnMain(WalletAction.MultiWallet.BackupWallet)
}
private fun scanAndSaveUserWallet() {
Analytics.send(Settings.ScanNewCard)
- store.dispatch(DetailsAction.ScanAndSaveUserWallet)
+ store.dispatchOnMain(DetailsAction.ScanAndSaveUserWallet)
}
private fun navigateToWalletConnect() {
Analytics.send(Settings.ButtonWalletConnect())
- store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
+ store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
}
private fun handleSocialNetworkClick(link: SocialNetworkLink) {
Analytics.send(Settings.ButtonSocialNetwork(link.network))
- store.dispatch(NavigationAction.OpenUrl(link.url))
+ store.dispatchOnMain(NavigationAction.OpenUrl(link.url))
}
private fun getSocialLinks(): ImmutableList {
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt
index 9ac4c1f3d3..c0ffda2914 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardFragment.kt
@@ -22,7 +22,7 @@ internal class ResetCardFragment : ComposeFragment(), StoreSubscriber =
- mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState))
+ mutableStateOf(ResetCardScreenState.InitialState)
@Composable
override fun ScreenContent(modifier: Modifier) {
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt
index aa76d59a80..cc1918907c 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt
@@ -1,7 +1,6 @@
package com.tangem.tap.features.details.ui.resetcard
import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@@ -30,15 +29,23 @@ import com.tangem.wallet.R
internal fun ResetCardScreen(state: ResetCardScreenState, onBackClick: () -> Unit, modifier: Modifier = Modifier) {
SettingsScreensScaffold(
modifier = modifier,
- content = { ResetCardView(state = state) },
+ content = {
+ when (state) {
+ is ResetCardScreenState.ResetCardScreenContent -> {
+ ResetCardView(state = state)
+ }
+ ResetCardScreenState.InitialState -> {
+ // do nothing for now, just white screen
+ }
+ }
+ },
onBackClick = onBackClick,
)
}
-@OptIn(ExperimentalAnimationApi::class)
@Suppress("LongMethod", "MagicNumber")
@Composable
-private fun ResetCardView(state: ResetCardScreenState) {
+private fun ResetCardView(state: ResetCardScreenState.ResetCardScreenContent) {
Column(
modifier = Modifier
.fillMaxSize()
@@ -80,17 +87,24 @@ private fun ResetCardView(state: ResetCardScreenState) {
Spacer(modifier = Modifier.size(28.dp))
- ConditionCheckBox(
- checkedState = state.acceptCondition1Checked,
- onCheckedChange = state.onAcceptCondition1ToggleClick,
- description = TextReference.Res(R.string.reset_card_to_factory_condition_1),
- )
-
- ConditionCheckBox(
- checkedState = state.acceptCondition2Checked,
- onCheckedChange = state.onAcceptCondition2ToggleClick,
- description = TextReference.Res(R.string.reset_card_to_factory_condition_2),
- )
+ state.warningsToShow.forEach {
+ when (it) {
+ ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS -> {
+ ConditionCheckBox(
+ checkedState = state.acceptCondition1Checked,
+ onCheckedChange = state.onAcceptCondition1ToggleClick,
+ description = TextReference.Res(R.string.reset_card_to_factory_condition_1),
+ )
+ }
+ ResetCardScreenState.WarningsToReset.LOST_PASSWORD_RESTORE -> {
+ ConditionCheckBox(
+ checkedState = state.acceptCondition2Checked,
+ onCheckedChange = state.onAcceptCondition2ToggleClick,
+ description = TextReference.Res(R.string.reset_card_to_factory_condition_2),
+ )
+ }
+ }
+ }
Spacer(modifier = Modifier.size(16.dp))
Box(
@@ -111,7 +125,6 @@ private fun ResetCardView(state: ResetCardScreenState) {
}
}
-@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun ConditionCheckBox(checkedState: Boolean, onCheckedChange: (Boolean) -> Unit, description: TextReference) {
Row(
@@ -162,8 +175,9 @@ private fun ResetCardScreenSample(modifier: Modifier = Modifier) {
.background(TangemTheme.colors.background.secondary),
) {
ResetCardScreen(
- state = ResetCardScreenState(
+ state = ResetCardScreenState.ResetCardScreenContent(
accepted = true,
+ warningsToShow = listOf(ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS),
descriptionText = TextReference.Res(R.string.reset_card_with_backup_to_factory_message),
onAcceptCondition1ToggleClick = {},
onAcceptCondition2ToggleClick = {},
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt
index 97f7a9776e..a12decbeef 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreenState.kt
@@ -2,15 +2,25 @@ package com.tangem.tap.features.details.ui.resetcard
import com.tangem.tap.features.details.ui.cardsettings.TextReference
-internal data class ResetCardScreenState(
- val accepted: Boolean = false,
- val descriptionText: TextReference,
- val acceptCondition1Checked: Boolean = false,
- val acceptCondition2Checked: Boolean = false,
- val onAcceptCondition1ToggleClick: (Boolean) -> Unit,
- val onAcceptCondition2ToggleClick: (Boolean) -> Unit,
- val onResetButtonClick: () -> Unit,
-) {
- val resetButtonEnabled: Boolean
- get() = accepted
+internal sealed class ResetCardScreenState {
+
+ object InitialState : ResetCardScreenState()
+
+ data class ResetCardScreenContent(
+ val accepted: Boolean = false,
+ val descriptionText: TextReference,
+ val warningsToShow: List,
+ val acceptCondition1Checked: Boolean = false,
+ val acceptCondition2Checked: Boolean = false,
+ val onAcceptCondition1ToggleClick: (Boolean) -> Unit,
+ val onAcceptCondition2ToggleClick: (Boolean) -> Unit,
+ val onResetButtonClick: () -> Unit,
+ ) : ResetCardScreenState() {
+ val resetButtonEnabled: Boolean
+ get() = accepted
+ }
+
+ internal enum class WarningsToReset {
+ LOST_WALLET_ACCESS, LOST_PASSWORD_RESTORE
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt
index 00d9f515e2..62218c3752 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt
@@ -9,14 +9,23 @@ import org.rekotlin.Store
internal class ResetCardViewModel(private val store: Store) {
- fun updateState(state: CardSettingsState?): ResetCardScreenState {
+ fun updateState(state: CardSettingsState?): ResetCardScreenState.ResetCardScreenContent {
val descriptionText = state?.cardInfo
?.toResetCardDescriptionText()
?: TextReference.Str(value = "")
- return ResetCardScreenState(
+ val warningsToShow = buildList {
+ add(ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS)
+
+ if (state?.isShowPasswordResetRadioButton == true) {
+ add(ResetCardScreenState.WarningsToReset.LOST_PASSWORD_RESTORE)
+ }
+ }
+
+ return ResetCardScreenState.ResetCardScreenContent(
accepted = state?.resetButtonEnabled ?: false,
descriptionText = descriptionText,
+ warningsToShow = warningsToShow,
acceptCondition1Checked = state?.condition1Checked ?: false,
acceptCondition2Checked = state?.condition2Checked ?: false,
onAcceptCondition1ToggleClick = { store.dispatch(DetailsAction.ResetToFactory.AcceptCondition1(it)) },
diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt
index bef592e97e..6c2205ca01 100644
--- a/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/disclaimer/redux/DisclaimerReducer.kt
@@ -27,6 +27,5 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState {
is DisclaimerAction.OnProgressStateChanged -> disclaimerState.copy(
progressState = action.state,
)
- else -> disclaimerState
}
}
\ 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 799cb82fa4..6e8db8a3d0 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
@@ -1,36 +1,36 @@
package com.tangem.tap.features.home
import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.ComposeView
import androidx.core.view.WindowCompat
-import androidx.core.view.WindowInsetsControllerCompat
-import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.components.SystemBarsEffect
-import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.screen.ComposeFragment
+import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.domain.tokens.TokensAction
import com.tangem.tap.common.analytics.events.IntroductionProcess
+import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.launch
import org.rekotlin.StoreSubscriber
+import javax.inject.Inject
@AndroidEntryPoint
-class HomeFragment : Fragment(), StoreSubscriber {
+class HomeFragment : ComposeFragment(), StoreSubscriber {
+
+ @Inject
+ override lateinit var appThemeModeHolder: AppThemeModeHolder
private var homeState: MutableState = mutableStateOf(store.state.homeState)
@@ -42,38 +42,28 @@ class HomeFragment : Fragment(), StoreSubscriber {
store.dispatch(HomeAction.Init)
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ @Composable
+ override fun ScreenContent(modifier: Modifier) {
// sync adding story before screen creation
// if (learn2earnViewModel.uiState.storyScreenState.isVisible) {
// store.dispatch(HomeAction.InsertStory(position = 0, Stories.OneInchPromo))
// homeState.value = store.state.homeState
// }
- return ComposeView(inflater.context).apply {
- setContent {
- TangemTheme {
- BackHandler {
- requireActivity().finish()
- }
- SystemBarsEffect {
- setSystemBarsColor(
- color = Color.Transparent,
- darkIcons = false,
- )
- }
- ScreenContent()
- }
- }
+ BackHandler(onBack = requireActivity()::finish)
+ SystemBarsEffect {
+ setSystemBarsColor(color = Color.Transparent, darkIcons = false)
}
+ ScreenContent()
}
override fun onStart() {
super.onStart()
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, false) }
- store.subscribe(this) { state ->
- state.skipRepeats { oldState, newState ->
- oldState.homeState == newState.homeState
- }.select { it.homeState }
+ store.subscribe(subscriber = this) { state ->
+ state
+ .skipRepeats { oldState, newState -> oldState.homeState == newState.homeState }
+ .select(AppState::homeState)
}
}
@@ -82,11 +72,6 @@ class HomeFragment : Fragment(), StoreSubscriber {
store.unsubscribe(this)
}
- override fun onDestroyView() {
- super.onDestroyView()
- rollbackStatusBarIconsColor()
- }
-
override fun newState(state: HomeState) {
if (activity == null || view == null) return
@@ -101,11 +86,7 @@ class HomeFragment : Fragment(), StoreSubscriber {
onLearn2earnClick = {}, // learn2earnViewModel.uiState.storyScreenState.onClick,
onScanButtonClick = {
Analytics.send(IntroductionProcess.ButtonScanCard())
- lifecycleScope.launch {
- store.dispatch(
- HomeAction.ReadCard(scope = this),
- )
- }
+ store.dispatch(action = HomeAction.ReadCard(scope = requireActivity().lifecycleScope))
},
onShopButtonClick = {
Analytics.send(IntroductionProcess.ButtonBuyCards())
@@ -118,17 +99,4 @@ class HomeFragment : Fragment(), StoreSubscriber {
},
)
}
-
- /*
- * !!! Workaround !!!
- * Used to roll back the color of icons in the system bars after the stories screen
- */
- private fun rollbackStatusBarIconsColor() {
- val windowInsetsController = WindowInsetsControllerCompat(
- activity?.window ?: return,
- view ?: return,
- )
- windowInsetsController.isAppearanceLightStatusBars = true
- windowInsetsController.isAppearanceLightNavigationBars = true
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt
index 76ef1a643e..5265c91db4 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesAnimation.kt
@@ -96,6 +96,8 @@ fun StoriesTextAnimation(
@Composable
fun StoriesBottomImageAnimation(
initialScale: Float = 2.5f,
+ secondStageScale: Float = SCALE_SWITCH_BARRIER,
+ targetScale: Float = 1.0f,
firstStepDuration: Int,
totalDuration: Int,
content: @Composable (Modifier) -> Unit,
@@ -117,7 +119,7 @@ fun StoriesBottomImageAnimation(
)
},
label = "Appearing scale",
- ) { value -> if (value) SCALE_SWITCH_BARRIER else initialScale }
+ ) { value -> if (value) secondStageScale else initialScale }
val secondTransition = updateTransition(
targetState = isSecondStepLaunched.value,
@@ -131,14 +133,14 @@ fun StoriesBottomImageAnimation(
)
},
label = "Outgoing scale",
- ) { value -> if (value) 1f else SCALE_SWITCH_BARRIER }
+ ) { value -> if (value) targetScale else secondStageScale }
val fadeIn = firstTransition.animateFloat(
transitionSpec = { tween(durationMillis = 400) },
label = "Fade in on start",
) { value -> if (value) 1f else 0f }
- if (firstScaleStep.value == SCALE_SWITCH_BARRIER) {
+ if (firstScaleStep.value == secondStageScale) {
isSecondStepLaunched.value = true
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt
index db1c2d34ee..198f6930da 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/Content.kt
@@ -105,6 +105,9 @@ fun StoriesWalletForEveryone(stepDuration: Int) {
SpacerH32()
BoxWithGradient {
StoriesBottomImageAnimation(
+ initialScale = 2.6f,
+ secondStageScale = 1.2f,
+ targetScale = 1.1f,
totalDuration = stepDuration,
firstStepDuration = 500,
) { modifier ->
@@ -184,8 +187,8 @@ private fun StoriesImage(@DrawableRes drawableResId: Int, modifier: Modifier = M
Image(
painter = painterResource(id = drawableResId),
contentDescription = null,
- contentScale = ContentScale.FillWidth,
- modifier = modifier.fillMaxWidth(),
+ contentScale = ContentScale.Inside,
+ modifier = modifier.fillMaxSize(),
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt
index 71f3e52cf3..1577eecad8 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/content/FirstStoriesContent.kt
@@ -4,8 +4,8 @@ import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -54,34 +54,30 @@ fun FirstStoriesContent(isPaused: Boolean, duration: Int) {
textAlign = TextAlign.Center,
)
- Box {
- Column(
- modifier = Modifier
- .fillMaxWidth(),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- SpacerH(TangemTheme.dimens.spacing94)
- StoriesTextAnimation(
- slideInDuration = 500,
- slideInDelay = 150,
- ) { modifier ->
- Text(
- modifier = modifier
- .weight(1f),
- text = stringResource(R.string.story_meet_title),
- style = style,
- color = TangemColorPalette.White,
- textAlign = TextAlign.Center,
- )
- }
- SpacerH(TangemTheme.dimens.spacing46)
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ SpacerH(TangemTheme.dimens.spacing94)
+ StoriesTextAnimation(
+ slideInDuration = 500,
+ slideInDelay = 150,
+ ) { modifier ->
+ Text(
+ modifier = modifier,
+ text = stringResource(R.string.story_meet_title),
+ style = style,
+ color = TangemColorPalette.White,
+ textAlign = TextAlign.Center,
+ )
}
+ SpacerH(TangemTheme.dimens.spacing46)
Image(
modifier = Modifier
- .align(Alignment.BottomCenter)
.fillMaxWidth(),
painter = painterResource(R.drawable.img_meet_tangem),
- contentScale = ContentScale.FillWidth,
+ contentScale = ContentScale.Inside,
contentDescription = "Tangem Wallet card",
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt
index cd53fdd0d3..a8a1fd7bc1 100644
--- a/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/StoriesProgressBar.kt
@@ -36,24 +36,26 @@ fun StoriesProgressBar(
val animatorSpeed = Settings.Global.getFloat(
context.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
- 0f,
+ 1f,
)
- LaunchedEffect(paused, currentStep) {
+ LaunchedEffect(paused, currentStep, animatorSpeed) {
if (paused) {
progress.stop()
} else {
if (animatorSpeed == 0f) {
+ progress.snapTo(1f)
delay(STORIES_ANIMATION_SPEED_ZERO_DURATION)
+ } else {
+ progress.animateTo(
+ targetValue = 1f,
+ animationSpec = tween(
+ durationMillis = (stepDuration * (1f - progress.value)).toInt(),
+ easing = LinearEasing,
+ ),
+ )
+ progress.snapTo(0f)
}
- progress.animateTo(
- targetValue = 1f,
- animationSpec = tween(
- durationMillis = (stepDuration * (1f - progress.value)).toInt(),
- easing = LinearEasing,
- ),
- )
- progress.snapTo(0f)
onStepFinish()
}
}
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 c15ba54403..4b83396c14 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
@@ -17,9 +17,8 @@ sealed class HomeAction : Action {
/**
* Action for scanning card
*
- * @property analyticsEvent analytics event
- * @property lifecycleCoroutineScope lifecycle scope. It will be canceled when lifecycle-aware component is
- * destroyed.
+ * @property analyticsEvent analytics event
+ * @property scope lifecycle scope. It will be canceled when lifecycle-aware component is destroyed
*/
data class ReadCard(
val analyticsEvent: AnalyticsEvent? = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Introduction),
diff --git a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt
index 04c138ce0e..e0c509b3ff 100644
--- a/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt
+++ b/app/src/main/java/com/tangem/tap/features/intentHandler/handlers/BuyCurrencyIntentHandler.kt
@@ -3,7 +3,7 @@ package com.tangem.tap.features.intentHandler.handlers
import android.content.Intent
import android.net.Uri
import com.tangem.core.analytics.Analytics
-import com.tangem.feature.tokendetails.presentation.tokendetails.analytics.TokenScreenEvent
+import com.tangem.domain.tokens.models.analytics.TokenScreenAnalyticsEvent
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.features.intentHandler.IntentHandler
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
@@ -21,7 +21,7 @@ class BuyCurrencyIntentHandler : IntentHandler {
val successUri = Uri.parse(ExchangeUrlBuilder.SUCCESS_URL)
return if (data.host == successUri.host && data.authority == successUri.authority) {
val currencyType = AnalyticsParam.CurrencyType.Currency(currency)
- Analytics.send(TokenScreenEvent.Bought(currencyType.value))
+ Analytics.send(TokenScreenAnalyticsEvent.Bought(currencyType.value))
true
} else {
false
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt
index 7773489a2f..901014d44b 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt
@@ -1,7 +1,7 @@
package com.tangem.tap.features.onboarding
import com.tangem.common.extensions.VoidCallback
-import com.tangem.tap.common.redux.StateDialog
+import com.tangem.core.navigation.StateDialog
/**
[REDACTED_AUTHOR]
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt
index 6369010887..947a437152 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt
@@ -38,7 +38,7 @@ object OnboardingHelper {
}
}
- response.cardTypesResolver.isWallet2() -> {
+ response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> {
val emptyWallets = response.card.wallets.isEmpty()
val activationInProgress = cardInfoStorage.isActivationInProgress(cardId)
val backupNotActive = response.card.backupStatus?.isActive != true
@@ -53,7 +53,10 @@ object OnboardingHelper {
fun whereToNavigate(scanResponse: ScanResponse): AppScreen {
return when (scanResponse.productType) {
ProductType.Note -> AppScreen.OnboardingNote
- ProductType.Wallet, ProductType.Wallet2 -> if (scanResponse.card.settings.isBackupAllowed) {
+ ProductType.Wallet,
+ ProductType.Wallet2,
+ ProductType.Ring,
+ -> if (scanResponse.card.settings.isBackupAllowed) {
AppScreen.OnboardingWallet
} else {
AppScreen.OnboardingOther
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
index bf80a5b458..c7dfdae94d 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
@@ -16,6 +16,7 @@ import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
+import com.tangem.tap.features.demo.isDemoCard
import timber.log.Timber
import java.math.BigDecimal
@@ -38,7 +39,8 @@ class OnboardingManager(
}
suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance {
- val balance = when (val result = walletManager.safeUpdate()) {
+ val isDemoCard = scanResponse.isDemoCard()
+ val balance = when (val result = walletManager.safeUpdate(isDemoCard)) {
is Result.Success -> {
val wallet = walletManager.wallet
val valueOfAmount = wallet.amounts[AmountType.Coin]?.value
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 9446f718c1..ce2c8784a8 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
@@ -1,11 +1,11 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import android.net.Uri
-import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.common.CompletionResult
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
+import kotlinx.coroutines.CoroutineScope
import org.rekotlin.Action
sealed class OnboardingWalletAction : Action {
@@ -18,7 +18,7 @@ sealed class OnboardingWalletAction : Action {
) : OnboardingWalletAction()
object Done : OnboardingWalletAction()
- data class FinishOnboarding(val lifecycleCoroutineScope: LifecycleCoroutineScope) : OnboardingWalletAction()
+ data class FinishOnboarding(val scope: CoroutineScope) : OnboardingWalletAction()
object ResumeBackup : OnboardingWalletAction()
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt
index b825a98b28..e5238a1788 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
@@ -5,6 +5,8 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.ifNotNull
+import com.tangem.common.extensions.toHexString
+import com.tangem.common.services.Result
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
@@ -18,6 +20,7 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.Artwork
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
+import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.operations.backup.BackupService
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
@@ -33,6 +36,7 @@ import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.wallet.models.toCurrencies
import com.tangem.tap.proxy.redux.DaggerGraphState
+import com.tangem.wallet.R
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
@@ -92,7 +96,21 @@ private fun handleWalletAction(action: Action) {
is OnboardingWalletAction.LoadArtwork -> {
scope.launch {
val cardArtwork = when (onboardingManager) {
- null -> action.cardArtworkUriForUnfinishedBackup
+ null -> {
+ // onboardingManager is null when backup started from previously interrupted
+ val primaryCardId = backupService.primaryCardId
+ val cardPublicKey = backupService.primaryPublicKey
+ if (primaryCardId != null && cardPublicKey != null) {
+ // uses when no scanResponse and backup state restored
+ loadArtworkForUnfinishedBackup(
+ cardId = primaryCardId,
+ cardPublicKey = cardPublicKey,
+ defaultArtwork = action.cardArtworkUriForUnfinishedBackup,
+ )
+ } else {
+ action.cardArtworkUriForUnfinishedBackup
+ }
+ }
else -> onboardingManager.loadArtworkUrl()
.takeIf { it != Artwork.DEFAULT_IMG_URL }
?.let { Uri.parse(it) }
@@ -152,7 +170,7 @@ private fun handleWalletAction(action: Action) {
if (scanResponse == null) {
store.dispatch(NavigationAction.PopBackTo())
- store.dispatch(HomeAction.ReadCard(scope = action.lifecycleCoroutineScope))
+ store.dispatch(HomeAction.ReadCard(scope = action.scope))
} else {
val backupState = store.state.onboardingWalletState.backupState
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
@@ -185,6 +203,24 @@ private fun handleWalletAction(action: Action) {
}
}
+private suspend fun loadArtworkForUnfinishedBackup(
+ cardId: String,
+ cardPublicKey: ByteArray,
+ defaultArtwork: Uri?,
+): Uri {
+ return when (val cardInfo = OnlineCardVerifier().getCardInfo(cardId, cardPublicKey)) {
+ is Result.Success -> {
+ val artworkId = cardInfo.data.artwork?.id
+ if (artworkId.isNullOrEmpty()) {
+ defaultArtwork ?: Uri.EMPTY
+ } else {
+ Uri.parse(OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId))
+ }
+ }
+ is Result.Failure -> defaultArtwork ?: Uri.EMPTY
+ }
+}
+
@Suppress("LongMethod", "ComplexMethod")
private fun handleWallet2Action(action: OnboardingWallet2Action) {
val globalState = store.state.globalState
@@ -331,7 +367,12 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
}
}
is BackupAction.ScanPrimaryCard -> {
- backupService.readPrimaryCard(cardId = card?.cardId) { result ->
+ val iconScanRes = if (scanResponse?.cardTypesResolver?.isRing() == true) {
+ R.drawable.img_hand_scan_ring
+ } else {
+ null
+ }
+ backupService.readPrimaryCard(iconScanRes = iconScanRes, cardId = card?.cardId) { result ->
when (result) {
is CompletionResult.Success -> {
store.dispatchOnMain(BackupAction.StartAddingBackupCards)
@@ -398,7 +439,12 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
}
}
is BackupAction.WritePrimaryCard -> {
- backupService.proceedBackup { result ->
+ val iconScanRes = if (scanResponse?.cardTypesResolver?.isRing() == true) {
+ R.drawable.img_hand_scan_ring
+ } else {
+ null
+ }
+ backupService.proceedBackup(iconScanRes = iconScanRes) { result ->
when (result) {
is CompletionResult.Success -> {
store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(1))
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 077e4b82aa..1e6dc646b8 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
@@ -1,5 +1,6 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
+import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.tap.backupService
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
@@ -40,6 +41,7 @@ private object ReducerForGlobalAction {
maxBackupCards = MAX_BACKUP_CARDS,
canSkipBackup = action.canSkipBackup,
),
+ isRingOnboarding = action.scanResponse.cardTypesResolver.isRing(),
)
}
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
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 cc59a4fb61..757196999f 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
@@ -2,7 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
import android.graphics.Bitmap
import android.net.Uri
-import com.tangem.tap.common.redux.StateDialog
+import com.tangem.core.navigation.StateDialog
import org.rekotlin.StateType
/**
@@ -14,6 +14,7 @@ data class OnboardingWalletState(
val backupState: BackupState = BackupState(),
val cardArtworkUri: Uri? = null,
val showConfetti: Boolean = false,
+ val isRingOnboarding: Boolean = false,
) : StateType {
@Suppress("MagicNumber")
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt
index aa714f2c28..67a15ded35 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt
@@ -177,13 +177,21 @@ class OnboardingWalletFragment :
seedPhraseStateHandler.newState(this, state, seedPhraseViewModel)
state.cardArtworkUri?.let {
seedPhraseViewModel.setCardArtworkUri(it.toString())
- loadImageIntoImageView(it, binding.imvFrontCard)
+ if (state.isRingOnboarding) {
+ binding.imvFrontCard.load(R.drawable.img_ring_placeholder)
+ } else {
+ loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
+ }
loadImageIntoImageView(it, binding.imvFirstBackupCard)
loadImageIntoImageView(it, binding.imvSecondBackupCard)
}
}
else -> {
- loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
+ if (state.isRingOnboarding) {
+ binding.imvFrontCard.load(R.drawable.img_ring_placeholder)
+ } else {
+ loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard)
+ }
loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard)
loadImageIntoImageView(state.cardArtworkUri, binding.imvSecondBackupCard)
handleOnboardingStep(state)
@@ -390,9 +398,9 @@ class OnboardingWalletFragment :
}
private fun showWriteBackupCard(state: BackupState) = with(binding) {
+ reInitCardsWidgetIfNeeded(state.backupCardsNumber)
prepareViewForFinalizeStep()
- reInitCardsWidgetIfNeeded(state.backupCardsNumber)
val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
tvHeader.text = getString(R.string.onboarding_title_backup_card_format, cardNumber)
@@ -427,7 +435,7 @@ class OnboardingWalletFragment :
layoutButtonsCommon.btnWalletAlternativeAction.hide()
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
showConfetti(false)
- store.dispatch(OnboardingWalletAction.FinishOnboarding(lifecycleCoroutineScope = lifecycleScope))
+ store.dispatch(OnboardingWalletAction.FinishOnboarding(scope = requireActivity().lifecycleScope))
}
animator.showSuccess {
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
index 094136bb80..07d0d822b7 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
@@ -6,8 +6,8 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.core.TangemSdkError
+import com.tangem.core.navigation.StateDialog
import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered
-import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.ToastNotificationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt
index 576497222e..96cf7c9c6f 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt
@@ -2,6 +2,7 @@ package com.tangem.tap.features.send.redux.middlewares
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered
import com.tangem.tap.common.redux.AppState
@@ -9,6 +10,10 @@ import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification.SetAddressError
import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification.SetWalletAddress
import com.tangem.tap.features.send.redux.AddressVerifyAction.Error
+import com.tangem.tap.mainScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
@@ -17,6 +22,8 @@ import org.rekotlin.DispatchFunction
*/
internal class AddressMiddleware {
+ private val addressValidator = AddressValidator()
+
fun handle(action: AddressActionUi, appState: AppState?, dispatch: (Action) -> Unit) {
when (action) {
is AddressActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch)
@@ -54,13 +61,13 @@ internal class AddressMiddleware {
dispatch: (Action) -> Unit,
) {
val sendState = appState?.sendState ?: return
- val wallet = sendState.walletManager?.wallet ?: return
+ val walletManager = sendState.walletManager ?: return
val address = sendState.addressState.normalFieldValue ?: return
val isUserInput = sendState.addressState.viewFieldValue.isFromUserInput
verifyAddress(
address = address,
- wallet = wallet,
+ walletManager = walletManager,
isUserInput = isUserInput,
dispatch = dispatch,
sourceType = sourceType,
@@ -68,6 +75,38 @@ internal class AddressMiddleware {
}
private fun verifyAddress(
+ address: String,
+ walletManager: WalletManager,
+ sourceType: AddressEntered.SourceType?,
+ isUserInput: Boolean,
+ dispatch: (Action) -> Unit,
+ ) {
+ val wallet = walletManager.wallet
+
+ mainScope.launch {
+ val failReason = withContext(Dispatchers.IO) {
+ addressValidator.validateAddress(walletManager, address)
+ }
+
+ if (failReason == null) {
+ dispatchSuccessValidationActions(
+ address = address,
+ wallet = wallet,
+ sourceType = sourceType,
+ isUserInput = isUserInput,
+ dispatch = dispatch,
+ )
+ } else {
+ dispatchFailedValidationActions(
+ failReason = failReason,
+ sourceType = sourceType,
+ dispatch = dispatch,
+ )
+ }
+ }
+ }
+
+ private fun dispatchSuccessValidationActions(
address: String,
wallet: Wallet,
sourceType: AddressEntered.SourceType?,
@@ -113,57 +152,49 @@ internal class AddressMiddleware {
val supposedAddress = noSchemeAddress.removeShareUriQuery() // TODO: parse query?
- val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress)
- if (failReason == null) {
- noSchemeAddress.getQueryParameter("amount")?.toBigDecimalOrNull()?.let {
- dispatch(AmountAction.SetAmount(it, false))
- dispatch(AmountActionUi.CheckAmountToSend)
- }
- dispatch(SetWalletAddress(supposedAddress, isUserInput))
- dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, null))
- dispatch(FeeAction.RequestFee)
- sourceType?.let {
- Analytics.send(
- event = AddressEntered(
- sourceType = sourceType,
- validationResult = AddressEntered.ValidationResult.Success,
- ),
- )
- }
- } else {
- dispatch(SetAddressError(failReason))
- dispatch(TransactionExtrasAction.Release)
- sourceType?.let {
- Analytics.send(
- event = AddressEntered(
- sourceType = sourceType,
- validationResult = AddressEntered.ValidationResult.Fail,
- ),
- )
- }
+ noSchemeAddress.getQueryParameter("amount")?.toBigDecimalOrNull()?.let {
+ dispatch(AmountAction.SetAmount(it, false))
+ dispatch(AmountActionUi.CheckAmountToSend)
+ }
+ dispatch(SetWalletAddress(supposedAddress, isUserInput))
+ dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, null))
+ dispatch(FeeAction.RequestFee)
+ sourceType?.let {
+ Analytics.send(
+ event = AddressEntered(
+ sourceType = sourceType,
+ validationResult = AddressEntered.ValidationResult.Success,
+ ),
+ )
}
}
- private fun isValidBlockchainAddressAndNotTheSameAsWallet(wallet: Wallet, address: String): Error? {
- return if (wallet.blockchain.validateAddress(address)) {
- if (wallet.addresses.all { it.value != address }) {
- null
- } else {
- Error.ADDRESS_SAME_AS_WALLET
- }
- } else {
- Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN
+ private fun dispatchFailedValidationActions(
+ failReason: Error,
+ sourceType: AddressEntered.SourceType?,
+ dispatch: (Action) -> Unit,
+ ) {
+ dispatch(SetAddressError(failReason))
+ dispatch(TransactionExtrasAction.Release)
+ sourceType?.let {
+ Analytics.send(
+ event = AddressEntered(
+ sourceType = sourceType,
+ validationResult = AddressEntered.ValidationResult.Fail,
+ ),
+ )
}
}
private fun String.removeShareUriQuery(): String = this.substringBefore("?")
+
private fun String.getQueryParameter(name: String): String? {
return this.substringAfter("?").splitToMap("&", "=")[name]
}
private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) {
val address = input ?: return
- val wallet = appState?.sendState?.walletManager?.wallet ?: return
+ val walletManager = appState?.sendState?.walletManager ?: return
val internalDispatcher: (Action) -> Unit = {
when (it) {
@@ -178,7 +209,7 @@ internal class AddressMiddleware {
verifyAddress(
address = address,
- wallet = wallet,
+ walletManager = walletManager,
sourceType = null,
isUserInput = false,
dispatch = internalDispatcher,
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressValidator.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressValidator.kt
new file mode 100644
index 0000000000..4c99c1e5c8
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressValidator.kt
@@ -0,0 +1,52 @@
+package com.tangem.tap.features.send.redux.middlewares
+
+import com.tangem.blockchain.blockchains.near.NearWalletManager
+import com.tangem.blockchain.blockchains.near.network.NearAccount
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.extensions.Result
+import com.tangem.tap.features.send.redux.AddressVerifyAction
+
+internal class AddressValidator {
+
+ suspend fun validateAddress(walletManager: WalletManager, address: String): AddressVerifyAction.Error? {
+ val blockchain = walletManager.wallet.blockchain
+ val wallet = walletManager.wallet
+ return if ((blockchain == Blockchain.Near || blockchain == Blockchain.NearTestnet) &&
+ address.length != NEAR_IMPLICIT_ADDRESS_LENGTH
+ ) {
+ validateNearAddress(walletManager, address)
+ } else {
+ validateAddress(wallet, address)
+ }
+ }
+
+ private suspend fun validateNearAddress(
+ walletManager: WalletManager,
+ address: String,
+ ): AddressVerifyAction.Error? {
+ val result = (walletManager as? NearWalletManager)?.getAccount(address)
+ return if (result is Result.Success && result.data is NearAccount.Full) {
+ null
+ } else {
+ AddressVerifyAction.Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN
+ }
+ }
+
+ private fun validateAddress(wallet: Wallet, address: String): AddressVerifyAction.Error? {
+ return if (wallet.blockchain.validateAddress(address)) {
+ if (wallet.addresses.all { it.value != address }) {
+ null
+ } else {
+ AddressVerifyAction.Error.ADDRESS_SAME_AS_WALLET
+ }
+ } else {
+ AddressVerifyAction.Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN
+ }
+ }
+
+ companion object {
+ private const val NEAR_IMPLICIT_ADDRESS_LENGTH = 64
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
index efdd59f799..2cb96aa486 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
@@ -15,6 +15,7 @@ import com.tangem.tap.features.send.redux.ReceiptAction
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.scope
+import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -72,6 +73,13 @@ class RequestFeeMiddleware {
dispatch(FeeAction.FeeCalculation.ClearResult)
dispatch(FeeAction.ChangeLayoutVisibility(main = false))
+ store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
+ walletManager = walletManager,
+ amountToSend = destinationAmount,
+ feeAmount = null,
+ destinationAddress = destinationAddress,
+ )
+
val blockchainSdkError = feeResult.error as? BlockchainSdkError ?: return@withContext
dispatch(
SendAction.Dialog.RequestFeeError(
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
index 34553ec931..c6284ad71d 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
@@ -4,10 +4,10 @@ import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.isZero
+import com.tangem.core.navigation.StateDialog
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.entities.IndeterminateProgressButton
-import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
@@ -57,7 +57,7 @@ data class SendState(
return if (!this.coinIsConvertible()) value else coinConverter!!.toCrypto(value)
}
- fun convertFiatToToken(value: BigDecimal): BigDecimal {
+ private fun convertFiatToToken(value: BigDecimal): BigDecimal {
return if (!this.tokenIsConvertible()) value else tokenConverter!!.toCrypto(value)
}
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
deleted file mode 100644
index b781ee102f..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrAction.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.tangem.tap.features.sprinklr.redux
-
-import com.tangem.datasource.config.models.SprinklrConfig
-import org.rekotlin.Action
-
-sealed interface SprinklrAction : Action {
- data class Init(val userId: String, val config: SprinklrConfig) : SprinklrAction
- data class UpdateUrl(val url: String) : SprinklrAction
- data class UpdateSprinklrDomains(val domains: List) : SprinklrAction
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt
deleted file mode 100644
index 922e3a2e67..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrMiddleware.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.tangem.tap.features.sprinklr.redux
-
-import com.tangem.datasource.config.models.SprinklrConfig
-import com.tangem.tap.common.extensions.dispatchOnMain
-import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.features.sprinklr.redux.model.SprinklrUrl
-import com.tangem.tap.store
-import org.rekotlin.Middleware
-
-internal class SprinklrMiddleware {
- val middleware: Middleware = { _, appStateProvider ->
- { next ->
- { action ->
- val appState = appStateProvider()
- if (action is SprinklrAction && appState != null) {
- handleAction(action)
- }
- next(action)
- }
- }
- }
-
- private fun handleAction(action: SprinklrAction) {
- when (action) {
- is SprinklrAction.Init -> updateUrl(action.userId, action.config)
- is SprinklrAction.UpdateUrl,
- is SprinklrAction.UpdateSprinklrDomains,
- -> Unit
- }
- }
-
- private fun updateUrl(userId: String, config: SprinklrConfig) {
- updateSprinklrDomains(config.baseUrl)
- val url = SprinklrUrl.Prod(userId, config.baseUrl, config.appId).url
- store.dispatchOnMain(SprinklrAction.UpdateUrl(url))
- }
-
- private fun updateSprinklrDomains(baseUrl: String) {
- val domains = listOf(baseUrl, SprinklrUrl.Static.url)
- store.dispatchOnMain(SprinklrAction.UpdateSprinklrDomains(domains))
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt b/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt
deleted file mode 100644
index a27d16d2db..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrReducer.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-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
deleted file mode 100644
index 12ba5b8672..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/SprinklrState.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-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
deleted file mode 100644
index 2d1eb1be87..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/redux/model/SprinklrUrl.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-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
deleted file mode 100644
index 7ddbf49c68..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrActivity.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-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.getValue
-import androidx.compose.ui.Modifier
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.tangem.core.analytics.Analytics
-import com.tangem.core.ui.components.SystemBarsEffect
-import com.tangem.core.ui.res.TangemTheme
-import com.tangem.core.ui.screen.ComposeActivity
-import com.tangem.core.ui.theme.AppThemeModeHolder
-import com.tangem.tap.common.analytics.events.Chat
-import dagger.hilt.android.AndroidEntryPoint
-import javax.inject.Inject
-
-@AndroidEntryPoint
-internal class SprinklrActivity : ComposeActivity() {
-
- @Inject
- override lateinit var appThemeModeHolder: AppThemeModeHolder
-
- private val viewModel by viewModels()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Analytics.send(Chat.ScreenOpened())
- viewModel.setNavigateBackCallback {
- this.finish()
- }
- }
-
- @Composable
- override fun ScreenContent(modifier: Modifier) {
- val state by viewModel.state.collectAsStateWithLifecycle()
-
- val systemBarsColor = TangemTheme.colors.background.primary
- SystemBarsEffect {
- setSystemBarsColor(systemBarsColor)
- }
-
- 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
deleted file mode 100644
index 5277ba0cea..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenContent.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-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
deleted file mode 100644
index 8757f6e167..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrScreenState.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-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
deleted file mode 100644
index a6cf3abe13..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/SprinklrViewModel.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.tangem.tap.features.sprinklr.ui
-
-import androidx.lifecycle.ViewModel
-import com.google.accompanist.web.WebContent
-import com.tangem.core.navigation.NavigationAction
-import com.tangem.tap.common.extensions.dispatchOnMain
-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
deleted file mode 100644
index 2f2487c88a..0000000000
--- a/app/src/main/java/com/tangem/tap/features/sprinklr/ui/webview/SprinklrWebViewClient.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-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/impl/presentation/ui/TokensListScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt
index cf7f9c448b..3dff613b36 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt
@@ -1,9 +1,7 @@
package com.tangem.tap.features.tokens.impl.presentation.ui
import androidx.activity.compose.BackHandler
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
+import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -38,6 +36,8 @@ import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState
import com.tangem.tap.features.tokens.impl.presentation.states.TokensListStateHolder
import com.tangem.tap.features.tokens.impl.presentation.states.TokensListToolbarState
import com.tangem.wallet.R
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
@@ -82,14 +82,14 @@ internal fun TokensListScreen(stateHolder: TokensListStateHolder, modifier: Modi
bottomMarginDp = floatingButtonHeight,
)
- stateHolder.onTokensLoadStateChanged(tokens.loadState.refresh)
+ Crossfade(targetState = stateHolder.isLoading, label = "Update progress bar visibility") {
+ if (it) {
+ LoadingContent()
+ }
+ }
- AnimatedVisibility(
- visible = stateHolder.isLoading,
- enter = fadeIn(),
- exit = fadeOut(),
- ) {
- LoadingContent()
+ LaunchedEffect(key1 = tokens.loadState.refresh) {
+ stateHolder.onTokensLoadStateChanged(tokens.loadState.refresh)
}
}
}
@@ -116,19 +116,19 @@ private fun TokensListContent(
) {
val state = rememberLazyListState()
- if (state.isScrollInProgress) {
- LocalSoftwareKeyboardController.current?.hide()
- }
-
LazyColumn(
modifier = Modifier
+ .imePadding()
.fillMaxSize()
.padding(scaffoldPadding),
state = state,
contentPadding = PaddingValues(bottom = bottomMarginDp),
) {
- if (isDifferentAddressesBlockVisible) {
- item { DifferentAddressesWarning() }
+ item(
+ key = "DifferentAddressesWarning$isDifferentAddressesBlockVisible",
+ contentType = "DifferentAddressesWarning$isDifferentAddressesBlockVisible",
+ ) {
+ if (isDifferentAddressesBlockVisible) DifferentAddressesWarning()
}
tokens.itemKey(TokenItemState::composedId)
@@ -138,6 +138,13 @@ private fun TokensListContent(
TokenItem(model = it)
}
}
+
+ val keyboardController = LocalSoftwareKeyboardController.current
+ LaunchedEffect(key1 = state) {
+ snapshotFlow(state::isScrollInProgress)
+ .distinctUntilChanged()
+ .collectLatest { if (it) keyboardController?.hide() }
+ }
}
@Composable
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt
index 22ca47c998..a03a0aef85 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt
@@ -1,15 +1,11 @@
package com.tangem.tap.features.tokens.impl.presentation.viewmodels
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import androidx.paging.LoadState
-import androidx.paging.PagingData
-import androidx.paging.map
+import androidx.lifecycle.*
+import androidx.paging.*
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.extensions.getActiveIconRes
@@ -41,8 +37,7 @@ import com.tangem.utils.coroutines.Debouncer
import com.tangem.wallet.R
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import timber.log.Timber
@@ -102,6 +97,8 @@ internal class TokensListViewModel @Inject constructor(
changedBlockchainList = currentCoins.toMutableList()
changedTokensList = currentTokens.toMutableList()
+
+ uiState = uiState.copySealed(tokens = getTokensListBySearchText(text = ""))
}
}
@@ -154,14 +151,13 @@ internal class TokensListViewModel @Inject constructor(
)
}
- private fun getInitialTokensList(searchText: String = ""): Flow> {
- if (searchText.isNotEmpty()) analyticsSender.sendWhenTokenSearched()
-
- return interactor.getTokensList(searchText = searchText).map {
- it.map { token ->
- if (isManageAccess) createManageTokenContent(token) else createReadTokenContent(token)
- }
- }
+ private fun getInitialTokensList(): Flow> {
+ return flowOf(
+ value = PagingData.empty(
+ sourceLoadStates = LoadStates(LoadState.Loading, LoadState.Loading, LoadState.Loading),
+ mediatorLoadStates = LoadStates(LoadState.Loading, LoadState.Loading, LoadState.Loading),
+ ),
+ )
}
private fun createManageTokenContent(token: Token): TokenItemState.ManageContent {
@@ -181,9 +177,7 @@ internal class TokensListViewModel @Inject constructor(
return NetworkItemState.ManageContent(
name = network.blockchain.fullNameWithoutTestnet.uppercase(),
protocolName = getNetworkProtocolName(network),
- iconResId = mutableStateOf(
- getNetworkIconResId(network.address, network.blockchain),
- ),
+ iconResId = mutableIntStateOf(value = getNetworkIconResId(network.address, network.blockchain)),
isMainNetwork = isMainNetwork(network),
isAdded = mutableStateOf(
isAdded(address = network.address, blockchain = network.blockchain),
@@ -212,7 +206,7 @@ internal class TokensListViewModel @Inject constructor(
return NetworkItemState.ReadContent(
name = network.blockchain.fullNameWithoutTestnet.uppercase(),
protocolName = getNetworkProtocolName(network),
- iconResId = mutableStateOf(getNetworkIconResId(network.address, network.blockchain)),
+ iconResId = mutableIntStateOf(value = getNetworkIconResId(network.address, network.blockchain)),
isMainNetwork = isMainNetwork(network),
)
}
@@ -247,6 +241,20 @@ internal class TokensListViewModel @Inject constructor(
}
}
+ private fun getTokensListBySearchText(text: String): Flow> {
+ return interactor.getTokensList(searchText = text)
+ .mapToTokenItemState()
+ .cachedIn(viewModelScope)
+ }
+
+ private fun Flow>.mapToTokenItemState(): Flow> {
+ return map { pagingData ->
+ pagingData.map { token ->
+ if (isManageAccess) createManageTokenContent(token) else createReadTokenContent(token)
+ }
+ }
+ }
+
private inner class ActionsHandler(
private val router: TokensListRouter,
private val debouncer: Debouncer,
@@ -306,10 +314,15 @@ internal class TokensListViewModel @Inject constructor(
uiState = uiState.copySealed(toolbarState = state.copy(value = newValue))
debouncer.debounce(waitMs = 800L, coroutineScope = viewModelScope + dispatchers.io) {
- uiState = uiState.copySealed(tokens = getInitialTokensList(newValue))
+ uiState = uiState.copySealed(tokens = getSearchedTokensList(newValue))
}
}
+ private fun getSearchedTokensList(searchText: String): Flow> {
+ analyticsSender.sendWhenTokenSearched()
+ return getTokensListBySearchText(text = searchText)
+ }
+
private fun onCleanButtonClick() {
val state = requireNotNull(uiState.toolbarState as? TokensListToolbarState.InputField)
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt
index c108b2602f..1ec868ce0e 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt
@@ -5,6 +5,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
+import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
@@ -15,6 +16,7 @@ import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.supportsHdWallet
import com.tangem.domain.models.scan.ScanResponse
+import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.TokenWithBlockchain
import com.tangem.domain.tokens.TokensAction
import com.tangem.domain.tokens.model.CryptoCurrency
@@ -37,6 +39,14 @@ import timber.log.Timber
@Suppress("LargeClass")
object TokensMiddleware {
+ // TODO: Move to DI
+ private val addCryptoCurrenciesUseCase by lazy(LazyThreadSafetyMode.NONE) {
+ val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
+ val networksRepository = store.state.daggerGraphState.get(DaggerGraphState::networksRepository)
+
+ AddCryptoCurrenciesUseCase(currenciesRepository, networksRepository)
+ }
+
val tokensMiddleware: Middleware = { _, _ ->
{ next ->
{ action ->
@@ -84,11 +94,9 @@ object TokensMiddleware {
updatedScanResponse = it,
currencyList = currencyList,
)
- store.dispatchOnMain(NavigationAction.PopBackTo())
}
} else {
submitNewAdd(userWalletId = action.userWallet.walletId, scanResponse, currencyList = currencyList)
- store.dispatchOnMain(NavigationAction.PopBackTo())
}
}
}
@@ -262,7 +270,6 @@ object TokensMiddleware {
val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys)
store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
- delay(DELAY_SDK_DIALOG_CLOSE)
onSuccess(updatedScanResponse)
}
@@ -372,23 +379,15 @@ object TokensMiddleware {
updatedScanResponse: ScanResponse,
currencyList: List,
) {
- val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
- val networksRepository = store.state.daggerGraphState.get(DaggerGraphState::networksRepository)
-
scope.launch {
userWalletsListManager.update(
userWalletId = userWalletId,
update = { it.copy(scanResponse = updatedScanResponse) },
- )
-
- currenciesRepository.addCurrencies(userWalletId = userWalletId, currencies = currencyList)
-
- networksRepository.getNetworkStatusesSync(
- userWalletId = userWalletId,
- networks = currencyList.map(CryptoCurrency::network).toSet(),
- refresh = true,
- )
+ ).doOnSuccess {
+ addCryptoCurrenciesUseCase(userWalletId, currencyList)
+ }
}
+ store.dispatchOnMain(NavigationAction.PopBackTo())
}
private suspend fun removeLegacyCurrenciesIfNeeded(currencies: List) {
@@ -403,7 +402,19 @@ object TokensMiddleware {
private suspend fun removeNewCurrenciesIfNeeded(userWalletId: UserWalletId, currencies: List) {
if (currencies.isEmpty()) return
val currenciesRepository = store.state.daggerGraphState.get(DaggerGraphState::currenciesRepository)
+ val walletManagersFacade = store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)
currenciesRepository.removeCurrencies(userWalletId = userWalletId, currencies = currencies)
+
+ walletManagersFacade.remove(
+ userWalletId = userWalletId,
+ networks = currencies
+ .filterIsInstance()
+ .mapTo(hashSetOf(), CryptoCurrency::network),
+ )
+ walletManagersFacade.removeTokens(
+ userWalletId = userWalletId,
+ tokens = currencies.filterIsInstance().toSet(),
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
index adca4f96f4..30b0aaadce 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
@@ -19,6 +19,7 @@ import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.wallet.R
+import kotlinx.coroutines.CoroutineScope
import org.rekotlin.Action
sealed class WalletAction : Action {
@@ -76,7 +77,7 @@ sealed class WalletAction : Action {
data class Scan(
val onScanSuccessEvent: AnalyticsEvent?,
- val lifecycleScope: LifecycleCoroutineScope,
+ val scope: CoroutineScope,
) : WalletAction()
data class Send(val amount: Amount? = null) : WalletAction()
@@ -113,7 +114,7 @@ sealed class WalletAction : Action {
data class ExploreAddress(val exploreUrl: String, val context: Context) : WalletAction()
object CreateWallet : WalletAction()
- data class ChangeWallet(val lifecycleScope: LifecycleCoroutineScope) : WalletAction()
+ data class ChangeWallet(val scope: LifecycleCoroutineScope) : WalletAction()
object ShowSaveWalletIfNeeded : WalletAction()
sealed class TradeCryptoAction : WalletAction() {
@@ -121,14 +122,6 @@ sealed class WalletAction : Action {
data class Buy(val checkUserLocation: Boolean = true) : TradeCryptoAction()
- data class FinishSelling(val transactionId: String) : TradeCryptoAction()
- data class SendCrypto(
- val currencyId: String,
- val amount: String,
- val destinationAddress: String,
- val transactionId: String,
- ) : TradeCryptoAction()
-
object Swap : TradeCryptoAction()
}
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 40b4c0c73c..c3092a3714 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
@@ -74,6 +74,7 @@ class TradeCryptoMiddleware {
}
}
+ @Deprecated("Use proceedNewBuyAction instead")
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
val currency = chooseAppropriateCurrency(store.state.walletState) ?: return
@@ -331,8 +332,7 @@ class TradeCryptoMiddleware {
}
private fun handleNewSendToken(action: TradeCryptoAction.New.SendToken) {
- val cryptoStatus = action.tokenStatus
- val currency = cryptoStatus.currency
+ val currency = action.tokenCurrency
val blockchain = Blockchain.fromId(currency.network.id.value)
scope.launch {
@@ -351,21 +351,21 @@ class TradeCryptoMiddleware {
return@launch
}
- val sendableAmounts = walletManager.wallet.amounts.values.filter { it.type is AmountType.Token }
- when (currency) {
- is CryptoCurrency.Coin -> error("Action.tokenStatus.currency is Coin")
- is CryptoCurrency.Token -> {
- store.dispatchOnMain(
- action = PrepareSendScreen(
- walletManager = walletManager,
- coinAmount = walletManager.wallet.amounts[AmountType.Coin],
- coinRate = action.coinFiatRate,
- tokenAmount = sendableAmounts.first(),
- tokenRate = cryptoStatus.value.fiatRate,
- ),
- )
- }
+ val sendableAmount = walletManager.wallet.amounts.values.firstOrNull {
+ val amountType = it.type
+ amountType is AmountType.Token && amountType.token.contractAddress == currency.contractAddress
}
+
+ store.dispatchOnMain(
+ action = PrepareSendScreen(
+ walletManager = walletManager,
+ coinAmount = walletManager.wallet.amounts[AmountType.Coin],
+ coinRate = action.coinFiatRate,
+ tokenAmount = sendableAmount,
+ tokenRate = action.tokenFiatRate,
+ ),
+ )
+
val bundle = bundleOf(
SendRouter.CRYPTO_CURRENCY_KEY to currency,
SendRouter.USER_WALLET_ID_KEY to action.userWallet.walletId.stringValue,
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 889dab19e8..ce93801220 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
@@ -116,9 +116,9 @@ class WalletMiddleware {
}
is WalletAction.Scan -> {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
- action.lifecycleScope.launch {
+ action.scope.launch {
delay(timeMillis = 700)
- store.dispatchOnMain(HomeAction.ReadCard(action.onScanSuccessEvent, action.lifecycleScope))
+ store.dispatchOnMain(HomeAction.ReadCard(action.onScanSuccessEvent, action.scope))
}
}
is WalletAction.LoadData,
@@ -180,7 +180,7 @@ class WalletMiddleware {
}
if (walletState.isMultiwalletAllowed) {
- val amountToSend = sendableAmounts.find { it.currencySymbol == currency.currencySymbol }
+ val amountToSend = findAmountToSend(currency = currency, amounts = sendableAmounts)
if (amountToSend == null) {
val error = TapError.UnsupportedState("WalletAction.Send: Amount to send is null")
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
@@ -227,7 +227,7 @@ class WalletMiddleware {
showSaveWalletIfNeeded()
}
is WalletAction.ChangeWallet -> {
- changeWallet(walletState, action.lifecycleScope)
+ changeWallet(walletState, action.scope)
}
is WalletAction.UserWalletChanged -> Unit
is WalletAction.WalletStoresChanged -> {
@@ -277,6 +277,18 @@ class WalletMiddleware {
}
}
+ private fun findAmountToSend(currency: Currency, amounts: List): Amount? {
+ return amounts.find { amount ->
+ val amountType = amount.type
+ if (amountType is AmountType.Token && currency is Currency.Token) {
+ val token = amountType.token
+ token.symbol == currency.currencySymbol && token.contractAddress == currency.token.contractAddress
+ } else {
+ amount.currencySymbol == currency.currencySymbol
+ }
+ }
+ }
+
private fun makeInitSendStateActionByAmount(
amount: Amount,
walletStore: WalletStoreModel,
@@ -391,7 +403,7 @@ class WalletMiddleware {
store.dispatch(
WalletAction.Scan(
onScanSuccessEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main),
- lifecycleScope = lifecycleScope,
+ scope = lifecycleScope,
),
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt
index 88fbb86932..c8b5747a94 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt
@@ -1,8 +1,8 @@
package com.tangem.tap.features.wallet.redux.models
import com.tangem.blockchain.common.Amount
+import com.tangem.core.navigation.StateDialog
import com.tangem.tap.common.entities.FiatCurrency
-import com.tangem.tap.common.redux.StateDialog
import com.tangem.wallet.R
sealed interface WalletDialog : StateDialog {
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 e5c632d8eb..a02a218d62 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
@@ -133,9 +133,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber WarningModel.KeyInvalidatedWarning(
+ is UserWalletsListError.AllKeysInvalidated,
+ is UserWalletsListError.NoUserWalletSelected,
+ -> WarningModel.KeyInvalidatedWarning(
onDismiss = this::dismissWarningDialog,
)
is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning(
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt
index fbd107685e..1f4b9e650e 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt
@@ -92,7 +92,9 @@ internal class WelcomeViewModel @Inject constructor(
isPermanent = error.isPermanent,
onDismiss = this::dismissWarning,
)
- is UserWalletsListError.EncryptionKeyInvalidated -> WarningModel.KeyInvalidatedWarning(
+ is UserWalletsListError.AllKeysInvalidated,
+ is UserWalletsListError.NoUserWalletSelected,
+ -> WarningModel.KeyInvalidatedWarning(
onDismiss = this::dismissWarning,
)
is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning(
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 037b139a49..09274aa9ac 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
@@ -12,6 +12,7 @@ import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
+import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
@@ -89,7 +90,7 @@ class CurrencyExchangeManager(
}
suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, destinationAddress: String) {
- walletManager.safeUpdate()
+ walletManager.safeUpdate(card.isDemoCard())
val amountToSend = Amount(walletManager.wallet.blockchain)
diff --git a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt
index 7b669beb49..c1262e0e1f 100644
--- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt
+++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt
@@ -8,8 +8,10 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.WalletsStateHolder
+import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.tokens.UserTokensRepository
@@ -66,4 +68,8 @@ class AppStateHolder @Inject constructor() : WalletsStateHolder, ReduxNavControl
override fun dispatch(action: Action) {
mainStore?.dispatch(action)
}
+
+ override suspend fun onUserWalletSelected(userWallet: UserWallet) {
+ mainStore?.onUserWalletSelected(userWallet)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt
index 763768d7ad..47c99bfb4b 100644
--- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt
+++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt
@@ -18,6 +18,7 @@ import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.models.scan.ScanResponse
+import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
@@ -46,6 +47,11 @@ class DerivationManagerImpl(
private val networksRepository: NetworksRepository,
) : DerivationManager {
+ // TODO: Move to DI
+ private val addCryptoCurrenciesUseCase by lazy(LazyThreadSafetyMode.NONE) {
+ AddCryptoCurrenciesUseCase(currenciesRepository, networksRepository)
+ }
+
override suspend fun deriveMissingBlockchains(currency: Currency) = suspendCoroutine { continuation ->
val blockchain = Blockchain.fromNetworkId(currency.networkId)
val card = appStateHolder.getActualCard()
@@ -96,6 +102,7 @@ class DerivationManagerImpl(
derivationPath = derivationPath,
derivationStyleProvider = derivationStyleProvider,
)
+ continuation.resumeWith(Result.success(derivationPath))
}
} else {
val blockchainNetwork = BlockchainNetwork(blockchain, scanResponse.derivationStyleProvider)
@@ -163,15 +170,8 @@ class DerivationManagerImpl(
derivationPath = derivationPath,
derivationStyleProvider = derivationStyleProvider,
)
- currenciesRepository.addCurrencies(
- userWalletId,
- listOf(cryptoCurrency),
- )
- networksRepository.getNetworkStatusesSync(
- userWalletId = userWalletId,
- networks = setOf(cryptoCurrency.network),
- refresh = true,
- )
+
+ addCryptoCurrenciesUseCase(userWalletId, cryptoCurrency)
}
private fun convertCurrency(
@@ -191,9 +191,11 @@ class DerivationManagerImpl(
}
is NonNativeToken -> {
val sdkToken = Token(
+ name = currency.name,
symbol = currency.symbol,
contractAddress = currency.contractAddress,
decimals = currency.decimalCount,
+ id = currency.id,
)
cryptoCurrencyFactory.createToken(
sdkToken = sdkToken,
@@ -233,7 +235,7 @@ class DerivationManagerImpl(
}
scope.launch {
- val selectedUserWallet = appStateHolder.userWalletsListManager?.selectedUserWalletSync
+ val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
val result = appStateHolder.tangemSdkManager?.derivePublicKeys(
cardId = null, // always ignore cardId in derive task
@@ -255,12 +257,10 @@ class DerivationManagerImpl(
derivedKeys = updatedDerivedKeys,
)
if (selectedUserWallet != null) {
- val userWallet = selectedUserWallet.copy(
- scanResponse = updatedScanResponse,
+ userWalletsListManager.update(
+ userWalletId = selectedUserWallet.walletId,
+ update = { it.copy(scanResponse = updatedScanResponse) },
)
-
- appStateHolder.userWalletsListManager?.save(userWallet, canOverride = true)
- appStateHolder.walletStoresManager?.fetch(userWallet, true)
}
appStateHolder.mainStore?.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
delay(DELAY_SDK_DIALOG_CLOSE)
diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt
index cfce94b52b..b05033931a 100644
--- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt
+++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt
@@ -27,6 +27,7 @@ import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.Basic.TransactionSent.MemoType
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
+import com.tangem.tap.userWalletsListManager
import java.math.BigDecimal
import java.math.BigInteger
import java.math.MathContext
@@ -416,7 +417,7 @@ class TransactionManagerImpl(
private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
val walletManager = if (walletFeatureToggles.isRedesignedScreenEnabled) {
val selectedUserWallet = requireNotNull(
- appStateHolder.userWalletsListManager?.selectedUserWalletSync,
+ userWalletsListManager.selectedUserWalletSync,
) { "userWallet or userWalletsListManager is null" }
walletManagersFacade.getOrCreateWalletManager(
selectedUserWallet.walletId,
diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt
index 3640c46412..74fce7b587 100644
--- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt
+++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt
@@ -10,7 +10,6 @@ import com.tangem.domain.common.BlockchainNetwork
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
-import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.lib.crypto.UserWalletManager
@@ -90,11 +89,10 @@ class UserWalletManagerImpl(
}
override fun getWalletId(): String {
- return appStateHolder.getActualCard()?.let {
- UserWalletIdBuilder.card(it)
- .build()
- ?.stringValue
- } ?: ""
+ val selectedUserWallet = requireNotNull(
+ userWalletsListManager.selectedUserWalletSync,
+ ) { "selectedUserWallet shouldn't be null" }
+ return selectedUserWallet.walletId.stringValue
}
override suspend fun isTokenAdded(currency: Currency, derivationPath: String?): Boolean {
@@ -156,7 +154,7 @@ class UserWalletManagerImpl(
val walletManager = getActualWalletManager(blockchain, derivationPath)
return walletManager.wallet.recentTransactions
.lastOrNull { it.hash?.isNotEmpty() == true }
- ?.hash?.let { HEX_PREFIX + it }
+ ?.hash
}
override suspend fun getCurrentWalletTokensBalance(
@@ -227,7 +225,7 @@ class UserWalletManagerImpl(
private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
val walletManager = if (walletFeatureToggles.isRedesignedScreenEnabled) {
val selectedUserWallet = requireNotNull(
- appStateHolder.userWalletsListManager?.selectedUserWalletSync,
+ userWalletsListManager.selectedUserWalletSync,
) { "userWallet or userWalletsListManager is null" }
walletManagersFacade.getOrCreateWalletManager(
selectedUserWallet.walletId,
@@ -244,10 +242,6 @@ class UserWalletManagerImpl(
"No wallet manager found"
}
}
-
- companion object {
- private const val HEX_PREFIX = "0x"
- }
}
private fun NonNativeToken.toSdkToken(): Token {
diff --git a/app/src/main/res/drawable/ic_near_no_color.xml b/app/src/main/res/drawable/ic_near_no_color.xml
new file mode 100644
index 0000000000..573a4ef9be
--- /dev/null
+++ b/app/src/main/res/drawable/ic_near_no_color.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/img_hand_scan_ring.xml b/app/src/main/res/drawable/img_hand_scan_ring.xml
new file mode 100644
index 0000000000..d9fd7d42f1
--- /dev/null
+++ b/app/src/main/res/drawable/img_hand_scan_ring.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/img_ring_placeholder.png b/app/src/main/res/drawable/img_ring_placeholder.png
new file mode 100644
index 0000000000..b39d9f9f76
Binary files /dev/null and b/app/src/main/res/drawable/img_ring_placeholder.png differ
diff --git a/app/src/main/res/layout/layout_onboarding_container_top.xml b/app/src/main/res/layout/layout_onboarding_container_top.xml
index 9c4e9abe83..d3708a8bfc 100644
--- a/app/src/main/res/layout/layout_onboarding_container_top.xml
+++ b/app/src/main/res/layout/layout_onboarding_container_top.xml
@@ -111,8 +111,8 @@
-
-
-
diff --git a/app/src/main/res/values/zendesk_styles.xml b/app/src/main/res/values/zendesk_styles.xml
deleted file mode 100644
index 2513114d19..0000000000
--- a/app/src/main/res/values/zendesk_styles.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt
index feb88dfba6..91b8eb6880 100644
--- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt
+++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt
@@ -5,8 +5,9 @@ sealed class AnalyticsParam {
sealed class CardBalanceState(val value: String) {
object Empty : CardBalanceState("Empty")
object Full : CardBalanceState("Full")
- object CustomToken : CardBalanceState("Custom token")
- object BlockchainError : CardBalanceState("Blockchain error")
+ object CustomToken : CardBalanceState("Custom Token")
+ object BlockchainError : CardBalanceState("Blockchain Error")
+ object NoRate : CardBalanceState("No Rate")
companion object
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/QuotesResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/QuotesResponse.kt
index e3cd9723b9..2155d0bf77 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/QuotesResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/QuotesResponse.kt
@@ -10,10 +10,8 @@ data class QuotesResponse(
data class Quote(
@Json(name = "price")
- val price: BigDecimal,
+ val price: BigDecimal?,
@Json(name = "priceChange24h")
- val priceChange: BigDecimal,
- @Json(name = "lastUpdatedAt")
- val lastUpdated: String,
+ val priceChange: BigDecimal?,
)
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt
index 493d6b2e9e..6bf2505649 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt
@@ -102,7 +102,7 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
appsFlyerDevKey = configValues.appsFlyer.appsFlyerDevKey,
amplitudeApiKey = configValues.amplitudeApiKey,
shopify = configValues.shopifyShop,
- zendesk = configValues.zendesk,
+ sprinklr = configValues.sprinklr,
swapReferrerAccount = configValues.swapReferrerAccount,
walletConnectProjectId = configValues.walletConnectProjectId,
tangemComAuthorization = configValues.tangemComAuthorization,
diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/ChatConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/ChatConfig.kt
index d34275da0f..067c8572f3 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/models/ChatConfig.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/ChatConfig.kt
@@ -5,24 +5,9 @@ import com.squareup.moshi.JsonClass
sealed interface ChatConfig
-@JsonClass(generateAdapter = true)
-data class ZendeskConfig(
- @Json(name = "zendeskApiKey")
- val apiKey: String,
- @Json(name = "zendeskAppId")
- val appId: String,
- @Json(name = "zendeskClientId")
- val clientId: String,
- @Json(name = "zendeskAccountKey")
- val accountKey: String,
- @Json(name = "zendeskUrl")
- val url: String,
-) : ChatConfig
-
@JsonClass(generateAdapter = true)
data class SprinklrConfig(
- @Json(name = "appID")
- val appId: String,
- @Json(name = "baseURL")
- val baseUrl: String,
+ @Json(name = "appID") val appId: String,
+ @Json(name = "apiKey") val apiKey: String,
+ @Json(name = "environment") val environment: String,
) : ChatConfig
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt
index 3d0bf5be7f..2980e22216 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt
@@ -15,7 +15,7 @@ data class Config(
@Deprecated("Not relevant since version 3.23")
val isCreatingTwinCardsAllowed: Boolean = false,
val shopify: ShopifyShop? = null,
- val zendesk: ZendeskConfig? = null,
+ val sprinklr: SprinklrConfig? = null,
val swapReferrerAccount: SwapReferrerAccount? = null,
val walletConnectProjectId: String = "",
val tangemComAuthorization: String? = null,
diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt
index 83ffe5d09a..77d2c0808f 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt
@@ -31,7 +31,7 @@ class ConfigValueModel(
val infuraProjectId: String?,
val appsFlyer: AppsFlyer,
val shopifyShop: ShopifyShop?,
- val zendesk: ZendeskConfig?,
+ val sprinklr: SprinklrConfig?,
val tronGridApiKey: String,
val amplitudeApiKey: String,
val swapReferrerAccount: SwapReferrerAccount?,
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt
index 276587c5f5..88b241c3ff 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/network/DefaultNetworksStatusesStore.kt
@@ -5,20 +5,26 @@ import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.extensions.addOrReplace
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
internal class DefaultNetworksStatusesStore(
dataStore: StringKeyDataStore>,
) : NetworksStatusesStore, StringKeyDataStoreDecorator>(dataStore) {
+ private val mutex = Mutex()
+
override fun provideStringKey(key: UserWalletId): String {
return key.stringValue
}
override suspend fun store(key: UserWalletId, value: NetworkStatus) {
- val newValues = getSyncOrNull(key)
- ?.addOrReplace(value) { it.network == value.network }
- ?: setOf(value)
+ mutex.withLock {
+ val newValues = getSyncOrNull(key)
+ ?.addOrReplace(value) { it.network == value.network }
+ ?: setOf(value)
- store(key, newValues)
+ store(key, newValues)
+ }
}
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt
index 667cb0f4b1..2b8de0afd9 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/quote/DefaultQuotesStore.kt
@@ -4,9 +4,8 @@ import com.tangem.datasource.api.tangemTech.models.QuotesResponse
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.quote.model.StoredQuote
import com.tangem.domain.tokens.model.CryptoCurrency
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.channelFlow
-import kotlinx.coroutines.flow.combine
+import com.tangem.utils.extensions.addOrReplace
+import kotlinx.coroutines.flow.*
internal class DefaultQuotesStore(
private val dataStore: StringKeyDataStore,
@@ -22,7 +21,12 @@ internal class DefaultQuotesStore(
send(emptySet())
}
- combine(flows) { quotes -> quotes.toSet() }.collect(::send)
+ merge(*flows.toTypedArray())
+ .scan>(emptySet()) { acc, quote ->
+ acc.addOrReplace(quote) { it.rawCurrencyId == quote.rawCurrencyId }
+ }
+ .filter(Set::isNotEmpty)
+ .collect(::send)
}
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensStore.kt
index 43e895bc89..23cea085d6 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensStore.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensStore.kt
@@ -10,6 +10,6 @@ internal class DefaultUserTokensStore(
) : UserTokensStore, StringKeyDataStoreDecorator(dataStore) {
override fun provideStringKey(key: UserWalletId): String {
- return key.stringValue
+ return "user_tokens_${key.stringValue}"
}
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt
index f99db26624..c009ade118 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/DefaultWalletManagersStore.kt
@@ -6,6 +6,7 @@ import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.extensions.addOrReplace
+import com.tangem.utils.extensions.removeBy
import kotlinx.coroutines.flow.Flow
internal class DefaultWalletManagersStore(
@@ -43,10 +44,18 @@ internal class DefaultWalletManagersStore(
val updatedWalletManagers = walletManagers
?.addOrReplace(walletManager) {
it.wallet.blockchain == walletManager.wallet.blockchain &&
- it.wallet.publicKey == walletManager.wallet.publicKey
+ it.wallet.publicKey.derivationPath == walletManager.wallet.publicKey.derivationPath
}
?: listOf(walletManager)
store(userWalletId, updatedWalletManagers)
}
+
+ override suspend fun remove(userWalletId: UserWalletId, predicate: (WalletManager) -> Boolean) {
+ val walletManagers = getSyncOrNull(userWalletId)?.toMutableList() ?: return
+
+ walletManagers.removeBy(predicate)
+
+ store(userWalletId, walletManagers)
+ }
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/WalletManagersStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/WalletManagersStore.kt
index a820b1d7c9..58ddc2d942 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/WalletManagersStore.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/walletmanager/WalletManagersStore.kt
@@ -19,5 +19,7 @@ interface WalletManagersStore {
suspend fun store(userWalletId: UserWalletId, walletManager: WalletManager)
+ suspend fun remove(userWalletId: UserWalletId, predicate: (WalletManager) -> Boolean)
+
suspend fun clear()
}
\ No newline at end of file
diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
index 0a8d0acd66..9da64e96a0 100644
--- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
+++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
@@ -9,7 +9,7 @@
},
{
"name": "REDESIGNED_WALLET_SCREEN_ENABLED",
- "version": "undefined"
+ "version": "5.0.0"
},
{
"name": "1INCH_LEARN_2_EARN_ENABLED",
@@ -21,11 +21,11 @@
},
{
"name": "REDESIGNED_APP_CURRENCY_SELECTOR_ENABLED",
- "version": "undefined"
+ "version": "5.0.0"
},
{
"name": "DARK_THEME_ENABLED",
- "version": "undefined"
+ "version": "5.0.0"
},
{
"name": "REDESIGNED_MANAGE_TOKENS_SCREEN_ENABLED",
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationAction.kt b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationAction.kt
index 082ada57ae..676958ef92 100644
--- a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationAction.kt
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationAction.kt
@@ -23,6 +23,8 @@ sealed class NavigationAction : Action {
object OpenBiometricsSettings : NavigationAction()
+ data class OpenDialog(val stateDialog: StateDialog) : NavigationAction()
+
data class Share(val data: String) : NavigationAction()
data class ActivityCreated(val activity: WeakReference) : NavigationAction()
diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt b/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt
new file mode 100644
index 0000000000..416307b644
--- /dev/null
+++ b/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt
@@ -0,0 +1,6 @@
+package com.tangem.core.navigation
+
+interface StateDialog {
+
+ object ScanFailsDialog : StateDialog
+}
\ No newline at end of file
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index a19b13376c..0d96639f41 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -142,7 +142,8 @@
Не выбрано
Сеть
Например, USDC
- Символ токена
+ Символ
+ Символ токена
Этот токен/сеть уже находится в вашем списке
Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить
Чат
@@ -153,11 +154,13 @@
Пароль
Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль.
Реферальная программа
+ Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы
Privacy policy
%s хэшей
Номер карты
Добавить еще карты
Валюта приложения
+ Скрывать балансы жестом переворота
Эмитент
Отправить отзыв
Подписано
@@ -319,6 +322,7 @@
Список
%1$s %2$s адрес в сети %3$s
%1$s (%2$s) в сети %3$s
+ Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств.
Участвовать
Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже.
Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже.
@@ -357,7 +361,7 @@
Пожалуйста, удерживайте карту до завершения операции
Сбросить карту
Я понимаю, что после выполнения этого действия у меня больше не будет доступа к текущему кошельку
- Я понимаю, что не смогу этой картой восстановить пароль на остальных картах, если я его забуду
+ Я понимаю, что не смогу этой картой восстановить пароль на остальных картах этого кошелька, если я его забуду
Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать данную карту для восстановления кода доступа.
Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек.
У вас есть карта банка другой страны, а также вид на жительство или регистрация вне РФ?
@@ -415,13 +419,6 @@
Кошелек для каждого
Пройдите 3 урока, получите скидку на покупку Tangem Wallet и 1INCH токены
Пройти обучение
- Занимайте
- Покупайте
- Обменивайте
- Вкладывайте
- Расплачивайтесь
- Отправляйте
- Храните
Встречайте Tangem
Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах
Поддержка Web 3.0
@@ -433,7 +430,7 @@
Сильные колебания цены!
Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму.
Недостаточно средств
- Недостаточно средств для оплаты на вашем %1$s кошельке для создания транзакции. Сначала пополните свой %2$s кошелек.
+ Недостаточно средств для оплаты комиссии на вашем %1$s кошельке для создания транзакции. Сначала пополните свой %2$s кошелек.
Транзакция в процессе…
Подождите
Подтвердить
@@ -474,6 +471,7 @@
Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.
Несколько адресов
История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе.
+ Операция
от: %s
на: %s
В процессе…
@@ -601,10 +599,11 @@
%1$s - это токен в сети %2$s. Для совершения транзакции %3$s, вам необходимо внести депозит в размере %4$s (%5$s), чтобы покрыть комиссию сети.
Недостаточно %1$s для оплаты комиссии сети
Отправка средств станет доступной после завершения транзакции %s
+ Транзакция в обработке
Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату.
Некоторые сети в настоящее время недоступны. Пожалуйста, повторите попытку позже.
Некоторые сети недоступны
- Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки.
+ Это Testnet карта. Она не может обрабатывать транзакции и используется только в целях тестирования и разработки.
Только для целей тестирования
Отказаться
Вы не закончили резервное копирование. Хотите продолжить?
diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml
index 54935d32ea..b80f9ce410 100644
--- a/core/res/src/main/res/values-zh-rTW/strings.xml
+++ b/core/res/src/main/res/values-zh-rTW/strings.xml
@@ -112,7 +112,7 @@
未選擇
網路
如 USDC
- 代幣符號
+ 代幣符號
此代幣/網路已被加入
請注意代幣可以被任何人創造。小心詐騙
交談
@@ -148,7 +148,6 @@
反饋
Tangem反饋
無法發送交易
- 訂購
掃描卡片
要更改訪問密碼,請完全按照上圖所示連接手機和卡片
要更改密碼,請完全按照上圖所示連接手機和卡
@@ -324,16 +323,9 @@
千種虛擬貨幣
隨時隨地使用它,不需要接線或電池,只需要你的卡片和手機
適合每個人的冷錢包
- 借入
- 購買
- 交易
- 借出
- 付款
- 發送
- 保存
認識Tangem
交易、購買NFT、自由穿梭於100+去中心化服務
- 兼容DeFi
+ 兼容Web3.0
批准被視為所有去中心化交易所的行業標準,並保護您的錢包在未經您許可的情況下不被智能合約訪問。按照設計,智能合約無法訪問您的代幣,除非您從您的終端批准訪問。通過“解鎖”您的代幣,您將獲得 1inch 智能合約使用您的資產的權限。網絡的礦工將獲得Gas Fee(由您支付)作為補償,以在區塊鏈上記錄此操作。一旦獲得許可,您就可以交易您的代幣。
批准
錯誤: %s
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 0bb572349d..5c971d0845 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -146,7 +146,8 @@
Token network
You can manually add a token that is not natively supported by Tangem
E.g. USDC
- Token symbol
+ Symbol
+ Token symbol
This token/network has already been added to your list
Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing.
Chat
@@ -210,7 +211,6 @@
Scan your card
Tokens
Add
- Custom
Edit
Blockchain the cryptocurrency was initially created
Native network
@@ -360,7 +360,7 @@
Please hold the card until the operation complete
Reset the Card
I understand that after performing this action, I will no longer have access to the current wallet
- I realize that I can\'t use this card to recover my access code on the other cards.
+ I realize that I can\'t use this card to recover my access code on the other cards of the current wallet
Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.
Factory Reset will completely delete the wallet from the selected card and remove it from the app. You will not be able to restore the current wallet.
Do you have a bank card from another country and a residence permit or registration outside the Russian Federation?
@@ -420,16 +420,9 @@
The Wallet for Everyone
Take three lessons, get a discount on your Tangem Wallet, and receive 1INCH tokens to your wallet
Learn
- Borrow
- Buy
- Exchange
- Lend
- Pay
- Send
- Store
Meet Tangem
Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services
- Web 3.0 compatible
+ Web 3.0 Compatible
Approvals are considered an industry standard across all decentralized exchanges and protect your wallet from being accessed by a smart contract without your permission. By design, smart contracts can\'t access your tokens unless you approve access from your end. By \"unlocking\" your tokens, you are give permission to the 1inch smart contract to spend your assets. The miners of the network are compensated with a gas fee (paid by you) to record this action on the blockchain. Once permission has been granted you will be able to swap your token.
Approve
Error: %s
@@ -464,7 +457,6 @@
- %d token
- %d tokens
- Choose address
Hide
You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.
Hide %s
@@ -478,6 +470,7 @@
Failed to load transaction history.\nClick on reload button to update the information.
Multiple addresses
Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer.
+ Operation
from: %s
to: %s
In progress…
@@ -603,6 +596,7 @@
%1$s is a token in the %2$s network. To make a %3$s transaction, you must deposit some %4$s (%5$s) to cover the network fee.
Insufficient %1$s to cover network fee
Sending funds will be available once the %s transaction is complete
+ Transaction pending
Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free.
Some networks currently are unreachable. Please try again later.
Some networks are unreachable
diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts
index 8cf16b6848..5868a39017 100644
--- a/core/ui/build.gradle.kts
+++ b/core/ui/build.gradle.kts
@@ -27,6 +27,7 @@ dependencies {
implementation(deps.compose.material3)
implementation(deps.compose.paging)
implementation(deps.compose.ui.tooling)
+ implementation(deps.compose.ui.utils)
implementation(deps.compose.coil)
/** Other libraries */
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt
index 952e17a142..a9eddb52f7 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt
@@ -76,6 +76,7 @@ fun PrimaryButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
+ size: TangemButtonSize = TangemButtonSize.Default,
showProgress: Boolean = false,
enabled: Boolean = true,
) {
@@ -87,6 +88,7 @@ fun PrimaryButton(
colors = TangemButtonsDefaults.primaryButtonColors,
enabled = enabled,
showProgress = showProgress,
+ size = size,
)
}
@@ -99,6 +101,7 @@ fun PrimaryButtonIconEnd(
@DrawableRes iconResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
+ size: TangemButtonSize = TangemButtonSize.Default,
showProgress: Boolean = false,
enabled: Boolean = true,
) {
@@ -110,6 +113,7 @@ fun PrimaryButtonIconEnd(
colors = TangemButtonsDefaults.primaryButtonColors,
enabled = enabled,
showProgress = showProgress,
+ size = size,
)
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/SettingsRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/SettingsRow.kt
index 823a93b8e3..6b497b4af0 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/SettingsRow.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/SettingsRow.kt
@@ -7,8 +7,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -24,6 +23,7 @@ fun SimpleSettingsRow(
@DrawableRes icon: Int,
onItemsClick: () -> Unit,
modifier: Modifier = Modifier,
+ rowColors: RowColors = getDefaultRowColors(),
enabled: Boolean = true,
subtitle: String? = null,
) {
@@ -41,24 +41,17 @@ fun SimpleSettingsRow(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
) {
- val textColor: Color by animateColorAsState(
- targetValue = if (enabled) {
- TangemTheme.colors.text.primary1
- } else {
- TangemTheme.colors.text.secondary
- },
- )
Icon(
painter = painterResource(id = icon),
contentDescription = null,
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing20),
- tint = textColor,
+ tint = rowColors.iconColor(enabled = enabled).value,
)
Column(modifier = Modifier.padding(end = TangemTheme.dimens.spacing20)) {
Text(
text = title,
style = TangemTheme.typography.subtitle1,
- color = textColor,
+ color = rowColors.titleColor(enabled = enabled).value,
)
AnimatedVisibility(
visible = !subtitle.isNullOrEmpty(),
@@ -66,9 +59,75 @@ fun SimpleSettingsRow(
Text(
text = subtitle ?: "",
style = TangemTheme.typography.body2,
- color = TangemTheme.colors.text.secondary,
+ color = rowColors.subtitleColor(enabled = enabled).value,
)
}
}
}
+}
+
+@Composable
+fun getWarningRowColors(): RowColors = DefaultRowColors(
+ titleColor = TangemTheme.colors.text.warning,
+ iconColor = TangemTheme.colors.text.warning,
+ disabledTitleColor = TangemTheme.colors.text.secondary,
+ disabledIconColor = TangemTheme.colors.text.secondary,
+)
+
+@Composable
+fun getDefaultRowColors(): RowColors = DefaultRowColors(
+ titleColor = TangemTheme.colors.text.primary1,
+ iconColor = TangemTheme.colors.text.primary1,
+ disabledTitleColor = TangemTheme.colors.text.secondary,
+ disabledIconColor = TangemTheme.colors.text.secondary,
+)
+
+@Immutable
+private class DefaultRowColors(
+ private val titleColor: Color,
+ private val iconColor: Color,
+ private val disabledTitleColor: Color,
+ private val disabledIconColor: Color,
+) : RowColors {
+ @Composable
+ override fun titleColor(enabled: Boolean): State {
+ return animateColorAsState(
+ targetValue = if (enabled) {
+ titleColor
+ } else {
+ disabledTitleColor
+ },
+ )
+ }
+
+ @Composable
+ override fun subtitleColor(enabled: Boolean): State {
+ return rememberUpdatedState(newValue = TangemTheme.colors.text.secondary)
+ }
+
+ @Composable
+ override fun iconColor(enabled: Boolean): State {
+ return animateColorAsState(
+ targetValue = if (enabled) {
+ iconColor
+ } else {
+ disabledIconColor
+ },
+ )
+ }
+}
+
+@Stable
+interface RowColors {
+ @Suppress("TopLevelComposableFunctions")
+ @Composable
+ fun titleColor(enabled: Boolean): State
+
+ @Suppress("TopLevelComposableFunctions")
+ @Composable
+ fun subtitleColor(enabled: Boolean): State
+
+ @Suppress("TopLevelComposableFunctions")
+ @Composable
+ fun iconColor(enabled: Boolean): State
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt
index b8b5086187..2d9d85b164 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt
@@ -3,29 +3,52 @@ package com.tangem.core.ui.components.bottomsheets
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberModalBottomSheetState
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.*
import com.tangem.core.ui.res.TangemTheme
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
/**
* Tangem bottom sheet with custom draggable header and config
*
- * @param config data model containing logic and ui models
+ * @param config data model containing logic and ui models
* @param content custom bottom sheet content
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun TangemBottomSheet(
+inline fun TangemBottomSheet(
config: TangemBottomSheetConfig,
- content: @Composable ColumnScope.(TangemBottomSheetConfigContent) -> Unit,
+ crossinline content: @Composable ColumnScope.(T) -> Unit,
) {
- ModalBottomSheet(
- onDismissRequest = config.onDismissRequest,
- sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
- containerColor = TangemTheme.colors.background.primary,
- shape = TangemTheme.shapes.bottomSheetLarge,
- dragHandle = { TangemBottomSheetDraggableHeader() },
- ) {
- content(config.content)
+ var isVisible by remember { mutableStateOf(value = config.isShow) }
+
+ val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+ if (isVisible && config.content is T) {
+ ModalBottomSheet(
+ onDismissRequest = config.onDismissRequest,
+ sheetState = sheetState,
+ containerColor = TangemTheme.colors.background.primary,
+ shape = TangemTheme.shapes.bottomSheetLarge,
+ dragHandle = { TangemBottomSheetDraggableHeader() },
+ ) {
+ content(config.content)
+ }
+ }
+
+ LaunchedEffect(key1 = config.isShow) {
+ if (config.isShow) {
+ isVisible = true
+ } else {
+ sheetState.collapse { isVisible = false }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+suspend fun SheetState.collapse(onCollapsed: () -> Unit) {
+ coroutineScope {
+ launch { hide() }.invokeOnCompletion { onCollapsed() }
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt
index 812d593533..409e1430d0 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheetConfigContent.kt
@@ -1,6 +1,9 @@
package com.tangem.core.ui.components.bottomsheets
+import androidx.compose.runtime.Immutable
+
/**
* General interface for bottom sheet config model
*/
+@Immutable
interface TangemBottomSheetConfigContent
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheet.kt
index 5b41ed7fe6..4bec68b142 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheet.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheet.kt
@@ -12,12 +12,8 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun ChooseAddressBottomSheet(config: TangemBottomSheetConfig) {
- if (config.content is ChooseAddressBottomSheetConfig && config.isShow) {
- TangemBottomSheet(config) { content ->
- ChooseAddressBottomSheetContent(
- content = content as ChooseAddressBottomSheetConfig,
- )
- }
+ TangemBottomSheet(config) { content: ChooseAddressBottomSheetConfig ->
+ ChooseAddressBottomSheetContent(content = content)
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheetConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheetConfig.kt
index 27984d9852..2bbbe6c79e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheetConfig.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/chooseaddress/ChooseAddressBottomSheetConfig.kt
@@ -2,8 +2,9 @@ package com.tangem.core.ui.components.bottomsheets.chooseaddress
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
+import kotlinx.collections.immutable.ImmutableList
class ChooseAddressBottomSheetConfig(
- val addressModels: List,
+ val addressModels: ImmutableList,
val onClick: (AddressModel) -> Unit,
) : TangemBottomSheetConfigContent
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt
index 417b9fcb35..b9dc31d04f 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/tokenreceive/TokenReceiveBottomSheet.kt
@@ -1,5 +1,7 @@
package com.tangem.core.ui.components.bottomsheets.tokenreceive
+import android.widget.Toast
+import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -22,7 +24,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.R
-import com.tangem.core.ui.components.MiddleEllipsisText
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
@@ -32,12 +33,8 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun TokenReceiveBottomSheet(config: TangemBottomSheetConfig) {
- if (config.content is TokenReceiveBottomSheetConfig && config.isShow) {
- TangemBottomSheet(config) { content ->
- TokenReceiveBottomSheetContent(
- content = content as TokenReceiveBottomSheetConfig,
- )
- }
+ TangemBottomSheet(config) { content: TokenReceiveBottomSheetConfig ->
+ TokenReceiveBottomSheetContent(content = content)
}
}
@@ -79,6 +76,7 @@ private fun TokenReceiveBottomSheetContent(content: TokenReceiveBottomSheetConfi
content.onCopyClick.invoke()
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
clipboardManager.setText(AnnotatedString(selectedAddress.value))
+ Toast.makeText(context, R.string.wallet_notification_address_copied, Toast.LENGTH_SHORT).show()
},
)
SecondaryButtonIconStart(
@@ -115,6 +113,7 @@ private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChang
state = pagerState,
) { currentPage ->
Column(
+ modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
) {
@@ -136,7 +135,7 @@ private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChang
modifier = Modifier
.size(TangemTheme.dimens.size248),
)
- MiddleEllipsisText(
+ Text(
text = content.addresses[currentPage].value,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
@@ -158,11 +157,9 @@ private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChang
) {
repeat(pagerState.pageCount) { iteration ->
item(key = iteration) {
- val color = if (pagerState.currentPage == iteration) {
- selectedColor
- } else {
- unselectedColor
- }
+ val color by animateColorAsState(
+ if (pagerState.currentPage == iteration) selectedColor else unselectedColor,
+ )
Box(
modifier = Modifier
.padding(
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt
index 09452682dc..0370fe0efe 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt
@@ -14,6 +14,7 @@ enum class TangemButtonSize {
Selector,
Action,
RoundedAction,
+ WideAction,
TwoLines,
}
@@ -26,12 +27,15 @@ internal fun TangemButtonSize.toHeightDp(): Dp = when (this) {
TangemButtonSize.Action,
TangemButtonSize.RoundedAction,
-> TangemTheme.dimens.size36
+ TangemButtonSize.WideAction -> TangemTheme.dimens.size40
}
@Composable
@ReadOnlyComposable
internal fun TangemButtonSize.toShape(): Shape = when (this) {
- TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
+ TangemButtonSize.Default,
+ TangemButtonSize.WideAction,
+ -> TangemTheme.shapes.roundedCornersMedium
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium
@@ -42,7 +46,10 @@ internal fun TangemButtonSize.toShape(): Shape = when (this) {
@Composable
@ReadOnlyComposable
internal fun TangemButtonSize.toIconPadding(): Dp = when (this) {
- TangemButtonSize.Default, TangemButtonSize.TwoLines -> TangemTheme.dimens.spacing4
+ TangemButtonSize.Default,
+ TangemButtonSize.WideAction,
+ TangemButtonSize.TwoLines,
+ -> TangemTheme.dimens.spacing4
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
TangemButtonSize.Selector -> 0.dp
TangemButtonSize.Action,
@@ -82,6 +89,12 @@ internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition):
start = horizontalPadding.first,
end = horizontalPadding.second,
)
+ TangemButtonSize.WideAction -> PaddingValues(
+ top = TangemTheme.dimens.spacing10,
+ bottom = TangemTheme.dimens.spacing10,
+ start = horizontalPadding.first,
+ end = horizontalPadding.second,
+ )
TangemButtonSize.TwoLines -> PaddingValues(
top = TangemTheme.dimens.spacing6,
bottom = TangemTheme.dimens.spacing6,
@@ -95,8 +108,10 @@ internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition):
@ReadOnlyComposable
internal fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIconPosition): Pair {
return when (this) {
- TangemButtonSize.Default, TangemButtonSize.TwoLines ->
- TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
+ TangemButtonSize.Default,
+ TangemButtonSize.WideAction,
+ TangemButtonSize.TwoLines,
+ -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
TangemButtonSize.Text -> when (icon) {
is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt
index 40cb551701..3ff8fbb6fd 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt
@@ -1,7 +1,6 @@
package com.tangem.core.ui.components.marketprice
import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -36,7 +35,7 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
*/
@Composable
fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier) {
- var rootWidth by remember { mutableStateOf(value = 0) }
+ var rootWidth by remember { mutableIntStateOf(value = 0) }
Column(
modifier = modifier
@@ -66,7 +65,6 @@ private fun Title(currencyName: String) {
)
}
-@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun Content(state: MarketPriceBlockState, rootWidth: Int) {
AnimatedContent(targetState = state, label = "Update the content") { marketPriceBlockState ->
@@ -96,7 +94,6 @@ private fun PriceContent(state: MarketPriceBlockState, priceWidthDp: Dp) {
}
}
-@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun PriceBlock(state: MarketPriceBlockState, priceWidthDp: Dp) {
val priceModifier = Modifier.widthIn(max = priceWidthDp)
@@ -128,7 +125,6 @@ private fun Price(price: String, modifier: Modifier = Modifier) {
)
}
-@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun PriceChangeInPercent(config: PriceChangeState.Content) {
AnimatedContent(targetState = config.type, label = "Update price change") { type ->
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt
index 56b449ec3a..2f30c95eba 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt
@@ -24,6 +24,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.*
+import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@@ -178,6 +179,7 @@ private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButt
text = config.text.resolveReference(),
onClick = config.onClick,
modifier = Modifier.fillMaxWidth(),
+ size = TangemButtonSize.WideAction,
)
}
@@ -189,12 +191,14 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo
iconResId = config.iconResId,
onClick = config.onClick,
modifier = Modifier.fillMaxWidth(),
+ size = TangemButtonSize.WideAction,
)
} else {
PrimaryButton(
text = config.text.resolveReference(),
onClick = config.onClick,
modifier = Modifier.fillMaxWidth(),
+ size = TangemButtonSize.WideAction,
)
}
}
@@ -206,12 +210,14 @@ private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) {
text = config.secondaryText.resolveReference(),
onClick = config.onSecondaryClick,
modifier = Modifier.weight(weight = 1f),
+ size = TangemButtonSize.WideAction,
)
PrimaryButton(
text = config.primaryText.resolveReference(),
onClick = config.onPrimaryClick,
modifier = Modifier.weight(weight = 1f),
+ size = TangemButtonSize.WideAction,
)
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
index e1947a8843..3e863a35f6 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
@@ -29,6 +29,7 @@ import com.tangem.core.ui.components.transactions.state.TransactionState.Content
import com.tangem.core.ui.components.transactions.state.TransactionState.Content.Status
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import java.util.UUID
@@ -137,22 +138,7 @@ private fun Icon(state: TransactionState, modifier: Modifier = Modifier) {
),
) {
Icon(
- painter = painterResource(
- id = when (state.status) {
- is Status.Failed -> R.drawable.ic_close_24
- else -> when (state) {
- is TransactionState.Transfer -> {
- when (state.direction) {
- Direction.OUTGOING -> R.drawable.ic_arrow_up_24
- Direction.INCOMING -> R.drawable.ic_arrow_down_24
- }
- }
- is TransactionState.Approve -> R.drawable.ic_doc_24
- is TransactionState.Swap -> R.drawable.ic_exchange_vertical_24
- is TransactionState.Custom -> R.drawable.ic_exchange_vertical_24
- }
- },
- ),
+ painter = painterResource(id = state.iconRes),
contentDescription = null,
modifier = Modifier
.size(TangemTheme.dimens.size20)
@@ -189,12 +175,7 @@ private fun Title(state: TransactionState, modifier: Modifier = Modifier) {
is TransactionState.Content -> {
Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) {
Text(
- text = when (state) {
- is TransactionState.Approve -> stringResource(R.string.common_approval)
- is TransactionState.Transfer -> stringResource(R.string.common_transfer)
- is TransactionState.Swap -> stringResource(R.string.common_swap)
- is TransactionState.Custom -> state.title.resolveReference()
- },
+ text = state.title.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
)
@@ -228,35 +209,7 @@ private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
Text(
text = when (state.status) {
is Status.Failed -> stringResource(id = R.string.common_transaction_failed)
- else -> when (state) {
- is TransactionState.Transfer -> {
- when (state.direction) {
- Direction.OUTGOING -> stringResource(
- id = R.string.transaction_history_transaction_to_address,
- state.address.resolveReference(),
- )
- Direction.INCOMING -> stringResource(
- id = R.string.transaction_history_transaction_from_address,
- state.address.resolveReference(),
- )
- }
- }
- is TransactionState.Approve -> stringResource(
- id = R.string.transaction_history_transaction_from_address,
- state.address.resolveReference(),
- )
- is TransactionState.Swap -> stringResource(
- id = R.string.transaction_history_contract_address,
- state.address.resolveReference(),
- )
- is TransactionState.Custom -> stringResource(
- id = when (state.direction) {
- Direction.OUTGOING -> R.string.transaction_history_transaction_to_address
- Direction.INCOMING -> R.string.transaction_history_transaction_from_address
- },
- state.subtitle.resolveReference(),
- )
- }
+ else -> state.subtitle.resolveReference()
},
modifier = modifier,
textAlign = TextAlign.Start,
@@ -285,9 +238,12 @@ private fun Amount(state: TransactionState, isBalanceHidden: Boolean, modifier:
text = if (isBalanceHidden) Strings.STARS else state.amount,
modifier = modifier,
textAlign = TextAlign.End,
- color = when (state.direction) {
- Direction.INCOMING -> TangemTheme.colors.text.accent
- Direction.OUTGOING -> TangemTheme.colors.text.primary1
+ color = when (state.status) {
+ Status.Failed -> TangemTheme.colors.text.warning
+ else -> when (state.direction) {
+ Direction.INCOMING -> TangemTheme.colors.text.accent
+ Direction.OUTGOING -> TangemTheme.colors.text.primary1
+ }
},
style = TangemTheme.typography.body2,
)
@@ -362,78 +318,90 @@ private fun Preview_TransactionItem_DarkTheme(
private class TransactionItemStateProvider : CollectionPreviewParameterProvider(
collection = listOf(
- TransactionState.Transfer(
+ TransactionState.Content(
txHash = UUID.randomUUID().toString(),
- address = TextReference.Str("33BddS...ga2B"),
amount = "-0.500913 BTC",
timestamp = "8:41",
status = Status.Confirmed,
direction = Direction.OUTGOING,
+ iconRes = R.drawable.ic_arrow_up_24,
+ title = resourceReference(R.string.common_transfer),
+ subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {},
),
- TransactionState.Transfer(
+ TransactionState.Content(
txHash = UUID.randomUUID().toString(),
- address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC",
timestamp = "8:41",
status = Status.Unconfirmed,
direction = Direction.INCOMING,
+ iconRes = R.drawable.ic_arrow_down_24,
+ title = resourceReference(R.string.common_transfer),
+ subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {},
),
- TransactionState.Approve(
+ TransactionState.Content(
txHash = UUID.randomUUID().toString(),
- address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC",
timestamp = "8:41",
status = Status.Unconfirmed,
direction = Direction.OUTGOING,
+ iconRes = R.drawable.ic_doc_24,
+ title = resourceReference(R.string.common_approval),
+ subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {},
),
- TransactionState.Approve(
+ TransactionState.Content(
txHash = UUID.randomUUID().toString(),
- address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC",
timestamp = "8:41",
status = Status.Failed,
direction = Direction.OUTGOING,
+ iconRes = R.drawable.ic_doc_24,
+ title = resourceReference(R.string.common_approval),
+ subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {},
),
- TransactionState.Approve(
+ TransactionState.Content(
txHash = UUID.randomUUID().toString(),
- address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC",
timestamp = "8:41",
status = Status.Confirmed,
direction = Direction.OUTGOING,
+ iconRes = R.drawable.ic_doc_24,
+ title = resourceReference(R.string.common_approval),
+ subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {},
),
- TransactionState.Swap(
+ TransactionState.Content(
txHash = UUID.randomUUID().toString(),
- address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC",
timestamp = "8:41",
status = Status.Unconfirmed,
direction = Direction.INCOMING,
+ iconRes = R.drawable.ic_arrow_down_24,
+ title = resourceReference(R.string.common_swap),
+ subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {},
),
- TransactionState.Custom(
+ TransactionState.Content(
txHash = UUID.randomUUID().toString(),
- address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC",
timestamp = "8:41",
status = Status.Confirmed,
direction = Direction.INCOMING,
+ iconRes = R.drawable.ic_doc_24,
title = TextReference.Str("Submit"),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {},
),
- TransactionState.Custom(
+ TransactionState.Content(
txHash = UUID.randomUUID().toString(),
- address = TextReference.Str("33BddS...ga2B"),
amount = "+0.500913 BTC",
timestamp = "8:41",
status = Status.Confirmed,
direction = Direction.OUTGOING,
+ iconRes = R.drawable.ic_arrow_up_24,
title = TextReference.Str("Submit"),
subtitle = TextReference.Str("33BddS...ga2B"),
onClick = {},
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt
index 3314333a5e..cd00ab76f2 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionList.kt
@@ -1,18 +1,27 @@
package com.tangem.core.ui.components.transactions
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.util.fastForEach
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionBlock
import com.tangem.core.ui.components.transactions.empty.EmptyTransactionsBlockState
+import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.res.TangemTheme
+import kotlinx.collections.immutable.ImmutableList
/**
* LazyList extension for transactions history [TxHistoryState]
@@ -45,6 +54,12 @@ fun LazyListScope.txHistoryItems(
)
}
is TxHistoryState.NotSupported -> {
+ if (state.pendingTransactions.isNotEmpty()) {
+ item(key = "PendingTxsBlock", contentType = "PendingTxsBlock") {
+ PendingTxsBlock(pendingTxs = state.pendingTransactions, isBalanceHidden = isBalanceHidden)
+ }
+ }
+
nonContentItem(
state = EmptyTransactionsBlockState.NotImplemented(onClick = state.onExploreClick),
modifier = modifier,
@@ -86,6 +101,21 @@ private fun LazyListScope.contentItems(
)
}
+@Composable
+private fun PendingTxsBlock(pendingTxs: ImmutableList, isBalanceHidden: Boolean) {
+ Column(
+ modifier = Modifier
+ .padding(top = TangemTheme.dimens.spacing12)
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .clip(shape = TangemTheme.shapes.roundedCornersXMedium)
+ .background(color = TangemTheme.colors.background.primary),
+ verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
+ horizontalAlignment = Alignment.Start,
+ ) {
+ pendingTxs.fastForEach { Transaction(state = it, isBalanceHidden = isBalanceHidden) }
+ }
+}
+
@OptIn(ExperimentalFoundationApi::class)
private fun LazyListScope.nonContentItem(state: EmptyTransactionsBlockState, modifier: Modifier = Modifier) {
item(key = state::class.java, contentType = state::class.java) {
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionState.kt
index c513c83fba..c18e43f28e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionState.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionState.kt
@@ -1,5 +1,6 @@
package com.tangem.core.ui.components.transactions.state
+import androidx.annotation.DrawableRes
import com.tangem.core.ui.extensions.TextReference
/**
@@ -15,37 +16,23 @@ sealed interface TransactionState {
/**
* Content state
*
- * @property address address
* @property amount amount
* @property timestamp timestamp
* @property status transaction status
* @property direction transaction direction
* @property onClick Lambda be invoked when manage button is clicked
*/
- sealed class Content : TransactionState {
-
- abstract val address: TextReference
- abstract val amount: String
- abstract val timestamp: String
- abstract val status: Status
- abstract val direction: Direction
- abstract val onClick: () -> Unit
-
- fun copySealed(
- txHash: String = this.txHash,
- address: TextReference = this.address,
- amount: String = this.amount,
- timestamp: String = this.timestamp,
- status: Status = this.status,
- direction: Direction = this.direction,
- ): Content {
- return when (this) {
- is Approve -> copy(txHash, address, amount, timestamp, status, direction)
- is Transfer -> copy(txHash, address, amount, timestamp, status, direction)
- is Swap -> copy(txHash, address, amount, timestamp, status, direction)
- is Custom -> copy(txHash, address, amount, timestamp, status, direction)
- }
- }
+ data class Content(
+ override val txHash: String,
+ val amount: String,
+ val timestamp: String,
+ val status: Status,
+ val direction: Direction,
+ val onClick: () -> Unit,
+ @DrawableRes val iconRes: Int,
+ val title: TextReference,
+ val subtitle: TextReference,
+ ) : TransactionState {
sealed class Status {
object Failed : Status()
@@ -59,75 +46,6 @@ sealed interface TransactionState {
}
}
- /**
- * Completed sending transaction state
- *
- * @property txHash transaction hash
- * @property address address
- * @property amount amount
- * @property timestamp timestamp
- * @property direction transaction direction
- */
- data class Transfer(
- override val txHash: String,
- override val address: TextReference,
- override val amount: String,
- override val timestamp: String,
- override val status: Status,
- override val direction: Direction,
- override val onClick: () -> Unit,
- ) : Content()
-
- /**
- * Completed approving transaction state
- *
- * @property txHash transaction hash
- * @property address address
- * @property amount amount
- * @property timestamp timestamp
- * @property direction transaction direction
- */
- data class Approve(
- override val txHash: String,
- override val address: TextReference,
- override val amount: String,
- override val timestamp: String,
- override val status: Status,
- override val direction: Direction,
- override val onClick: () -> Unit,
- ) : Content()
-
- /**
- * Completed swapping transaction state
- *
- * @property txHash transaction hash
- * @property address address
- * @property amount amount
- * @property timestamp timestamp
- * @property direction transaction direction
- */
- data class Swap(
- override val txHash: String,
- override val address: TextReference,
- override val amount: String,
- override val timestamp: String,
- override val status: Status,
- override val direction: Direction,
- override val onClick: () -> Unit,
- ) : Content()
-
- data class Custom(
- override val txHash: String,
- override val address: TextReference,
- override val amount: String,
- override val timestamp: String,
- override val status: Status,
- override val direction: Direction,
- override val onClick: () -> Unit,
- val title: TextReference,
- val subtitle: TextReference,
- ) : Content()
-
/**
* Loading state
*
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TxHistoryState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TxHistoryState.kt
index c6a42aacbb..c5cac65a07 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TxHistoryState.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TxHistoryState.kt
@@ -1,6 +1,7 @@
package com.tangem.core.ui.components.transactions.state
import androidx.paging.PagingData
+import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
/** Wallet transaction history state */
@@ -19,9 +20,13 @@ sealed interface TxHistoryState {
/**
* Not supported tx history state
*
- * @property onExploreClick lambda be invoke when explore button was clicked
+ * @property pendingTransactions pending transactions
+ * @property onExploreClick lambda be invoke when explore button was clicked
*/
- data class NotSupported(val onExploreClick: () -> Unit) : TxHistoryState
+ data class NotSupported(
+ val pendingTransactions: ImmutableList,
+ val onExploreClick: () -> Unit,
+ ) : TxHistoryState
/**
* Error state
diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt
index df100a2f03..8b6efaed02 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt
@@ -28,6 +28,7 @@ fun getActiveIconRes(blockchainId: String): Int {
"GNO" -> R.drawable.img_gnosis_22
"ETH-Pow", "ETH-Pow/test" -> R.drawable.img_eth_pow_22
"ETH-Fair" -> R.drawable.img_eth_fair_22
+ "NEAR", "NEAR/test" -> R.drawable.img_near_22
"Polkadot", "Polkadot/test" -> R.drawable.img_polkadot_22
"Kusama" -> R.drawable.img_kusama_22
"OPTIMISM", "OPTIMISM/test" -> R.drawable.img_optimism_22
@@ -50,19 +51,14 @@ fun getActiveIconRes(blockchainId: String): Int {
@Suppress("ComplexMethod")
@DrawableRes
-fun getActiveIconResByCoinId(coinId: String, networkId: String): Int {
+fun getActiveIconResByCoinId(coinId: String): Int {
return when (coinId) {
"binancecoin" -> R.drawable.img_bsc_22
"bitcoin" -> R.drawable.img_btc_22
"bitcoin-cash" -> R.drawable.img_btc_cash_22
- "ethereum" -> {
- when (networkId) {
- "ethereum" -> R.drawable.img_eth_22
- "arbitrum-one" -> R.drawable.img_arbitrum_22
- "optimistic-ethereum" -> R.drawable.img_optimism_22
- else -> R.drawable.ic_alert_24
- }
- }
+ "ethereum" -> R.drawable.img_eth_22
+ "arbitrum-one" -> R.drawable.img_arbitrum_22
+ "optimistic-ethereum" -> R.drawable.img_optimism_22
"ethereum-classic" -> R.drawable.img_eth_classic_22
"stellar" -> R.drawable.img_stellar_22
"cardano" -> R.drawable.img_cardano_22
@@ -91,6 +87,7 @@ fun getActiveIconResByCoinId(coinId: String, networkId: String): Int {
"telos" -> R.drawable.img_telos_22
"octaspace" -> R.drawable.img_octaspace_22
"chia" -> R.drawable.img_chia_22
+ "near" -> R.drawable.img_near_22
else -> R.drawable.ic_alert_24
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
index a258741454..b3820d111f 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
@@ -119,7 +119,9 @@ data class TangemDimens internal constructor(
val spacing52: Dp = 52.dp,
val spacing54: Dp = 54.dp,
val spacing56: Dp = 56.dp,
+ val spacing60: Dp = 60.dp,
val spacing70: Dp = 70.dp,
+ val spacing80: Dp = 80.dp,
val spacing92: Dp = 92.dp,
val spacing94: Dp = 94.dp,
val spacing96: Dp = 96.dp,
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt
index 0187255098..f2572e21ff 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt
@@ -78,7 +78,7 @@ data class TangemTypography internal constructor(
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp),
- lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp),
+ lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp),
),
val caption1: TextStyle = TextStyle(
fontFamily = RobotoFamily,
diff --git a/core/ui/src/main/res/drawable/ic_hide_24.xml b/core/ui/src/main/res/drawable/ic_hide_24.xml
new file mode 100644
index 0000000000..64b5a4c393
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_hide_24.xml
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/img_near_22.xml b/core/ui/src/main/res/drawable/img_near_22.xml
new file mode 100644
index 0000000000..6688dfd747
--- /dev/null
+++ b/core/ui/src/main/res/drawable/img_near_22.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/List.kt b/core/utils/src/main/java/com/tangem/utils/extensions/List.kt
index 6ecaef28b8..e0fbdb5d90 100644
--- a/core/utils/src/main/java/com/tangem/utils/extensions/List.kt
+++ b/core/utils/src/main/java/com/tangem/utils/extensions/List.kt
@@ -1,7 +1,7 @@
package com.tangem.utils.extensions
/**
- * Removes an element from the collection based on the provided predicate.
+ * Removes elements from the collection based on the provided predicate.
*
* !!!This function is not thread-safe!!!
*
diff --git a/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt
index 11dc95e2b1..a093b24ba0 100644
--- a/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt
+++ b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt
@@ -49,8 +49,9 @@ internal class DefaultCardSdkConfigRepository(
ProductType.Twins -> CardIdDisplayFormat.LastLuhn(numbers = 4)
ProductType.Note,
ProductType.Wallet,
- ProductType.Start2Coin,
ProductType.Wallet2,
+ ProductType.Start2Coin,
+ ProductType.Ring,
-> CardIdDisplayFormat.Full
}
}
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt
index 218863ac9a..5da77acb5b 100644
--- a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt
@@ -42,10 +42,6 @@ class PreferencesDataSource @Inject internal constructor(applicationContext: Con
toppedUpWalletStorage = ToppedUpWalletStorage(preferences, moshiConverter)
}
- 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) }
@@ -93,7 +89,6 @@ class PreferencesDataSource @Inject internal constructor(applicationContext: Con
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 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_ACCESS_CODES_KEY = "saveAccessCodes"
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceTopupInfo.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceTopupInfo.kt
index 0088d25b3e..9cd85eca4d 100644
--- a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceTopupInfo.kt
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceTopupInfo.kt
@@ -9,5 +9,6 @@ data class DataSourceTopupInfo(
Full(serializedName = "Full"),
CustomToken(serializedName = "Custom token"),
BlockchainError(serializedName = "Blockchain error"),
+ NoRate(serializedName = "No rate"),
}
}
\ No newline at end of file
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt
index 3f623010d5..cc7c128143 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt
@@ -17,6 +17,7 @@ import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.core.error.DataError
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency
+import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWallet
@@ -38,9 +39,10 @@ internal class DefaultCurrenciesRepository(
) : CurrenciesRepository {
private val demoConfig = DemoConfig()
- private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(demoConfig)
+ private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory()
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig)
private val userTokensResponseFactory = UserTokensResponseFactory()
+ private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
override suspend fun saveTokens(
userWalletId: UserWalletId,
@@ -84,7 +86,7 @@ internal class DefaultCurrenciesRepository(
savedCurrencies = savedCurrencies.tokens,
)
- val newCurrencies = newCoins + filteredCurrencies
+ val newCurrencies = (newCoins + filteredCurrencies).distinct()
storeAndPushTokens(
userWalletId = userWalletId,
@@ -213,6 +215,7 @@ internal class DefaultCurrenciesRepository(
override suspend fun getMultiCurrencyWalletCurrency(
userWalletId: UserWalletId,
id: CryptoCurrency.ID,
+ contractAddress: String?,
derivationPath: Network.DerivationPath,
): CryptoCurrency = withContext(dispatchers.io) {
val userWallet = getUserWallet(userWalletId)
@@ -222,7 +225,13 @@ internal class DefaultCurrenciesRepository(
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
- responseCurrenciesFactory.createCurrency(id, response, userWallet.scanResponse, derivationPath.value)
+ responseCurrenciesFactory.createCurrency(
+ currencyId = id,
+ contractAddress = contractAddress,
+ response = response,
+ scanResponse = userWallet.scanResponse,
+ derivationPath = derivationPath.value,
+ )
}
override suspend fun getNetworkCoin(
@@ -294,6 +303,20 @@ internal class DefaultCurrenciesRepository(
}
}
+ override fun hasPendingTransactions(
+ cryptoCurrencyStatus: CryptoCurrencyStatus,
+ coinStatus: CryptoCurrencyStatus?,
+ ): Boolean {
+ val blockchain = Blockchain.fromId(cryptoCurrencyStatus.currency.network.id.value)
+ val isBitcoinBlockchain = blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet
+ return if (cryptoCurrencyStatus.currency is CryptoCurrency.Coin && isBitcoinBlockchain) {
+ val outgoingTransactions = cryptoCurrencyStatus.value.pendingTransactions.filter { it.isOutgoing }
+ outgoingTransactions.isNotEmpty()
+ } else {
+ coinStatus?.value?.hasCurrentNetworkTransactions == true
+ }
+ }
+
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow> {
return userTokensStore.get(userWallet.walletId).map { storedTokens ->
responseCurrenciesFactory.createCurrencies(
@@ -314,6 +337,18 @@ internal class DefaultCurrenciesRepository(
private suspend fun fetchTokens(userWallet: UserWallet) {
val userWalletId = userWallet.walletId
+ if (demoConfig.isDemoCardId(userWallet.cardId) && userTokensStore.getSyncOrNull(key = userWalletId) == null) {
+ userTokensStore.store(
+ key = userWalletId,
+ value = userTokensResponseFactory.createUserTokensResponse(
+ currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
+ isGroupedByNetwork = false,
+ isSortedByBalance = false,
+ ),
+ )
+ return
+ }
+
val response = safeApiCall(
call = {
tangemTechApi.getUserTokens(userWalletId.stringValue).bind().let {
@@ -323,12 +358,14 @@ internal class DefaultCurrenciesRepository(
onError = { handleFetchTokensError(userWallet, it) },
)
- userTokensStore.store(userWallet.walletId, response)
- fetchExchangeableUserMarketCoinsByIds(userWalletId, response)
+ val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
+ userTokensStore.store(userWallet.walletId, compatibleUserTokensResponse)
+ fetchExchangeableUserMarketCoinsByIds(userWalletId, compatibleUserTokensResponse)
}
private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
- userTokensStore.store(userWalletId, response)
+ val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
+ userTokensStore.store(userWalletId, compatibleUserTokensResponse)
try {
tangemTechApi.saveUserTokens(userWalletId.stringValue, response)
} catch (e: Throwable) {
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt
index 84456d84c7..fa7d68bd98 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt
@@ -7,6 +7,7 @@ import com.tangem.data.tokens.utils.ResponseCryptoCurrenciesFactory
import com.tangem.datasource.local.network.NetworksStatusesStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
+import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
@@ -34,7 +35,7 @@ internal class DefaultNetworksRepository(
private val demoConfig by lazy { DemoConfig() }
private val cardCurrenciesFactory by lazy { CardCryptoCurrenciesFactory(demoConfig) }
- private val responseCurrenciesFactory by lazy { ResponseCryptoCurrenciesFactory(demoConfig) }
+ private val responseCurrenciesFactory by lazy { ResponseCryptoCurrenciesFactory() }
private val networkStatusFactory by lazy { NetworkStatusFactory() }
override fun getNetworkStatusesUpdates(
@@ -162,9 +163,14 @@ internal class DefaultNetworksRepository(
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence()
} else {
- val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
+ if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
+ cardCurrenciesFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
+ .asSequence()
+ } else {
+ val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
- sequenceOf(currency)
+ sequenceOf(currency)
+ }
}
return currencies.filter { it.network == network }
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt
index 4584643457..37a89fafd0 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt
@@ -3,6 +3,7 @@ package com.tangem.data.tokens.repository
import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.utils.QuotesConverter
+import com.tangem.data.tokens.utils.QuotesUnsupportedCurrenciesIdAdapter
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.local.quote.QuotesStore
@@ -23,6 +24,7 @@ internal class DefaultQuotesRepository(
) : QuotesRepository {
private val quotesConverter = QuotesConverter()
+ private val quotesUnsupportedCurrenciesAdapter = QuotesUnsupportedCurrenciesIdAdapter()
@Volatile
private var quotesFetchedForAppCurrency: String? = null
@@ -71,9 +73,10 @@ internal class DefaultQuotesRepository(
}
private suspend fun fetchQuotes(rawCurrenciesIds: Set, appCurrencyId: String) {
+ val replacementIdsResult = quotesUnsupportedCurrenciesAdapter.replaceUnsupportedCurrencies(rawCurrenciesIds)
val response = safeApiCall(
call = {
- val coinIds = rawCurrenciesIds.joinToString(separator = ",")
+ val coinIds = replacementIdsResult.idsForRequest.joinToString(separator = ",")
tangemTechApi.getQuotes(appCurrencyId, coinIds).bind()
},
onError = {
@@ -83,7 +86,11 @@ internal class DefaultQuotesRepository(
)
if (response != null) {
- quotesStore.store(response)
+ val updatedResponse = quotesUnsupportedCurrenciesAdapter.getResponseWithUnsupportedCurrencies(
+ response,
+ replacementIdsResult.idsFiltered,
+ )
+ quotesStore.store(updatedResponse)
}
}
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt
index 3f0be2bd6f..47979ec3a4 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkStatusFactory.kt
@@ -1,9 +1,6 @@
package com.tangem.data.tokens.utils
-import com.tangem.domain.tokens.model.CryptoCurrency
-import com.tangem.domain.tokens.model.Network
-import com.tangem.domain.tokens.model.NetworkAddress
-import com.tangem.domain.tokens.model.NetworkStatus
+import com.tangem.domain.tokens.model.*
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction
@@ -42,7 +39,7 @@ internal class NetworkStatusFactory {
private fun formatAmounts(
amounts: Set,
currencies: Set,
- ): Map {
+ ): Map {
return currencies.associate { currency ->
val amount = when (currency) {
is CryptoCurrency.Coin -> {
@@ -56,11 +53,12 @@ internal class NetworkStatusFactory {
}
}
}
+
if (amount == null) {
- Timber.e("Unable to find amount for cryptoCurrency: $currency")
- currency.id to NetworkStatus.UnreachableAmount
+ Timber.w("Unable to find amount for cryptocurrency: $currency")
+ currency.id to CryptoCurrencyAmountStatus.NotFound
} else {
- currency.id to NetworkStatus.LoadedAmount(amount.value)
+ currency.id to CryptoCurrencyAmountStatus.Loaded(amount.value)
}
}
}
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesConverter.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesConverter.kt
index 145eaf9800..d76681efa4 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesConverter.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesConverter.kt
@@ -3,6 +3,7 @@ package com.tangem.data.tokens.utils
import com.tangem.datasource.local.quote.model.StoredQuote
import com.tangem.domain.tokens.model.Quote
import com.tangem.utils.converter.Converter
+import java.math.BigDecimal
internal class QuotesConverter : Converter {
@@ -11,8 +12,8 @@ internal class QuotesConverter : Converter {
return Quote(
rawCurrencyId = rawCurrencyId,
- fiatRate = responseQuote.price,
- priceChange = responseQuote.priceChange.movePointLeft(2),
+ fiatRate = responseQuote.price ?: BigDecimal.ZERO,
+ priceChange = (responseQuote.priceChange ?: BigDecimal.ZERO).movePointLeft(2),
)
}
}
\ No newline at end of file
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt
new file mode 100644
index 0000000000..a038b0be78
--- /dev/null
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/QuotesUnsupportedCurrenciesIdAdapter.kt
@@ -0,0 +1,56 @@
+package com.tangem.data.tokens.utils
+
+import com.tangem.datasource.api.tangemTech.models.QuotesResponse
+
+/**
+ * Adapter to replace unsupported currencies for quotes request if it necessary
+ */
+internal class QuotesUnsupportedCurrenciesIdAdapter {
+
+ /**
+ * Replaces unsupported currencies id to it replacements for request
+ */
+ fun replaceUnsupportedCurrencies(currenciesIds: Set): ReplacementResult {
+ val idsForRequest = mutableSetOf()
+ val idsFiltered = mutableSetOf()
+ currenciesIds.forEach { currencyId ->
+ val replacementId = UNSUPPORTED_IDS_WITH_REPLACEMENTS[currencyId]
+ if (replacementId != null) {
+ idsFiltered.add(currencyId)
+ idsForRequest.add(replacementId)
+ } else {
+ idsForRequest.add(currencyId)
+ }
+ }
+ return ReplacementResult(idsForRequest = idsForRequest, idsFiltered = idsFiltered)
+ }
+
+ /**
+ * Recover previously replaced currencies to response that uses in application
+ */
+ fun getResponseWithUnsupportedCurrencies(response: QuotesResponse, filteredIds: Set): QuotesResponse {
+ val updatedQuotes = mutableMapOf()
+ filteredIds.forEach { filteredId ->
+ val replacementId = UNSUPPORTED_IDS_WITH_REPLACEMENTS[filteredId]
+ if (replacementId != null) {
+ val quoteReplacement = response.quotes[replacementId]
+ if (quoteReplacement != null) {
+ updatedQuotes[filteredId] = quoteReplacement
+ }
+ }
+ }
+ return response.copy(quotes = updatedQuotes + response.quotes)
+ }
+
+ data class ReplacementResult(val idsForRequest: Set, val idsFiltered: Set)
+
+ companion object {
+ /**
+ * Map that contains unsupported currencies and their replacement for request
+ */
+ private val UNSUPPORTED_IDS_WITH_REPLACEMENTS = mapOf(
+ "optimistic-ethereum" to "ethereum",
+ "arbitrum-one" to "ethereum",
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt
index 3274fb2221..1f10b3cdf5 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/ResponseCryptoCurrenciesFactory.kt
@@ -4,20 +4,21 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.common.DerivationStyleProvider
+import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.util.derivationStyleProvider
-import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
-internal class ResponseCryptoCurrenciesFactory(private val demoConfig: DemoConfig) {
+internal class ResponseCryptoCurrenciesFactory {
fun createCurrency(
currencyId: CryptoCurrency.ID,
+ contractAddress: String?,
response: UserTokensResponse,
scanResponse: ScanResponse,
derivationPath: String?,
@@ -27,7 +28,10 @@ internal class ResponseCryptoCurrenciesFactory(private val demoConfig: DemoConfi
val token = requireNotNull(
value = response.tokens
- .find { it.id == responseTokenId && it.networkId == networkId && it.derivationPath == derivationPath },
+ .find {
+ it.id == responseTokenId && it.networkId == networkId && it.derivationPath == derivationPath &&
+ it.contractAddress == contractAddress
+ },
lazyMessage = { "Unable find a token with provided TokenID($responseTokenId) and NetworkID($networkId)" },
)
@@ -54,7 +58,7 @@ internal class ResponseCryptoCurrenciesFactory(private val demoConfig: DemoConfi
val cardDerivationStyleProvider = scanResponse.derivationStyleProvider
val card = scanResponse.card
- if (demoConfig.isDemoCardId(card.cardId)) {
+ if (card.isTestCard) {
blockchain = blockchain.getTestnetVersion() ?: blockchain
}
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/TokensOperations.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/TokensOperations.kt
index be82d11c84..13dcd79a67 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/TokensOperations.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/TokensOperations.kt
@@ -36,7 +36,7 @@ internal fun getTokenId(network: Network, sdkToken: SdkToken): ID {
val suffix = if (sdkTokenId == null) {
CustomCurrencyIdSuffix(contractAddress = sdkToken.contractAddress)
} else {
- CurrencyIdSuffix(rawId = sdkTokenId)
+ CurrencyIdSuffix(rawId = sdkTokenId, contractAddress = sdkToken.contractAddress)
}
return ID(TOKEN_ID_PREFIX, getCurrencyIdBody(network), suffix)
@@ -55,7 +55,7 @@ internal fun getTokenIconUrl(blockchain: Blockchain, token: SdkToken): String? {
internal fun getCoinIconUrl(blockchain: Blockchain): String? {
val coinId = when (blockchain) {
Blockchain.Unknown -> null
- Blockchain.TerraV1, Blockchain.TerraV2 -> blockchain.toCoinId()
+ Blockchain.TerraV1, Blockchain.TerraV2, Blockchain.Near -> blockchain.toCoinId()
else -> blockchain.toNetworkId()
}
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt
new file mode 100644
index 0000000000..db1650880f
--- /dev/null
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/UserTokensBackwardCompatibility.kt
@@ -0,0 +1,37 @@
+package com.tangem.data.tokens.utils
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
+import com.tangem.domain.common.extensions.fromNetworkId
+import com.tangem.domain.common.extensions.toCoinId
+
+/**
+ * Helper to apply compatibility changes for [UserTokensResponse] to support old saved tokens
+ * in new application with new IDs
+ */
+internal class UserTokensBackwardCompatibility {
+
+ fun applyCompatibilityAndGetUpdated(userTokensResponse: UserTokensResponse): UserTokensResponse {
+ return userTokensResponse.copy(
+ tokens = userTokensResponse.tokens.map { token ->
+ val oldSavedId = NETWORKS_TO_OLD_SAVED_IDS[token.networkId]
+ if (oldSavedId != null && token.id == oldSavedId) {
+ Blockchain.fromNetworkId(token.networkId)?.let { blockchain ->
+ token.copy(
+ id = blockchain.toCoinId(),
+ )
+ } ?: token
+ } else {
+ token
+ }
+ },
+ )
+ }
+
+ companion object {
+ private val NETWORKS_TO_OLD_SAVED_IDS = mapOf(
+ "optimistic-ethereum" to "ethereum",
+ "arbitrum-one" to "ethereum",
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt
index 0e2cd53343..c36eb493ed 100644
--- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt
+++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt
@@ -26,11 +26,11 @@ class DefaultTxHistoryRepository(
private val txHistoryItemsStore: TxHistoryItemsStore,
) : TxHistoryRepository {
- override suspend fun getTxHistoryItemsCount(userWalletId: UserWalletId, network: Network): Int {
+ override suspend fun getTxHistoryItemsCount(userWalletId: UserWalletId, currency: CryptoCurrency): Int {
val userWallet = getUserWallet(userWalletId)
val state = walletManagersFacade.getTxHistoryState(
userWalletId = userWallet.walletId,
- network = network,
+ currency = currency,
)
return when (state) {
is TxHistoryState.Failed.FetchError -> throw TxHistoryStateError.DataError(state.exception)
diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts
index 69f443892c..9a4c2225c2 100644
--- a/domain/legacy/build.gradle.kts
+++ b/domain/legacy/build.gradle.kts
@@ -25,12 +25,13 @@ dependencies {
}
/** Other libraries */
- implementation(deps.reKotlin)
+ implementation(deps.arrow.core)
+ implementation(deps.jodatime)
+ implementation(deps.kotlin.coroutines)
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
+ implementation(deps.reKotlin)
implementation(deps.timber)
- implementation(deps.kotlin.coroutines)
- implementation(deps.jodatime)
/** Testing libraries */
testImplementation(deps.test.junit)
diff --git a/domain/legacy/src/main/assets/contract_methods.json b/domain/legacy/src/main/assets/contract_methods.json
new file mode 100644
index 0000000000..e07b98657c
--- /dev/null
+++ b/domain/legacy/src/main/assets/contract_methods.json
@@ -0,0 +1,125 @@
+{
+ "0xa9059cbb":{
+ "info":"ERC20",
+ "source":"https://eips.ethereum.org/EIPS/eip-20",
+ "name":"transfer"
+ },
+ "0x095ea7b3":{
+ "info":"ERC20",
+ "source":"https://eips.ethereum.org/EIPS/eip-20",
+ "name":"approve"
+ },
+ "0x357a150b":{
+ "info":"ERC721",
+ "source":"https://eips.ethereum.org/EIPS/eip-721",
+ "name":"run"
+ },
+ "0x23b872dd":{
+ "info":"ERC721",
+ "source":"https://eips.ethereum.org/EIPS/eip-721",
+ "name":"transferFrom"
+ },
+ "0x87201b41":{
+ "info":"ERC721",
+ "source":"https://eips.ethereum.org/EIPS/eip-721",
+ "name":"fulfillAvailableAdvancedOrders"
+ },
+ "0xe449f341":{
+ "info":"ERC721",
+ "source":"https://eips.ethereum.org/EIPS/eip-721",
+ "name":"unstake"
+ },
+ "0x84bb1e42":{
+ "info":"ERC721",
+ "source":"https://eips.ethereum.org/EIPS/eip-721",
+ "name":"claim"
+ },
+ "0xd48c03e5":{
+ "info":"ERC1155",
+ "source":"https://eips.ethereum.org/EIPS/eip-1155",
+ "name":"mint"
+ },
+ "0xe3afe0a3":{
+ "info":"Lido",
+ "source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
+ "name":"withdrawal"
+ },
+ "0xd6681042":{
+ "info":"Lido",
+ "source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
+ "name":"withdrawal"
+ },
+ "0x19aa6257":{
+ "info":"Lido",
+ "source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
+ "name":"withdrawal"
+ },
+ "0xacf41e4d":{
+ "info":"Lido",
+ "source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
+ "name":"withdrawal"
+ },
+ "0x7951b76f":{
+ "info":"Lido",
+ "source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
+ "name":"withdrawal"
+ },
+ "0xa1903eab":{
+ "info":"Lido",
+ "source":"https://github.com/LedgerHQ/app-plugin-lido#smart-contracts",
+ "name":"submit"
+ },
+ "0x617ba037":{
+ "info":"AAVE",
+ "source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
+ "name":"supply"
+ },
+ "0x69328dec":{
+ "info":"AAVE",
+ "source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
+ "name":"withdraw"
+ },
+ "0x3ccfd60b":{
+ "info":"AAVE",
+ "source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
+ "name":"withdraw"
+ },
+ "0x573ade81":{
+ "info":"AAVE",
+ "source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
+ "name":"repay"
+ },
+ "0xa415bcad":{
+ "info":"AAVE",
+ "source":"https://etherscan.io/address/0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
+ "name":"borrow"
+ },
+ "0x12aa3caf":{
+ "info":"1inch v5",
+ "source":"https://github.com/LedgerHQ/app-plugin-1inch#functions-implemented",
+ "name":"swap"
+ },
+ "0x7c025200":{
+ "info":"1inch",
+ "source":"https://github.com/LedgerHQ/app-plugin-1inch#functions-implemented",
+ "name":"swap"
+ },
+ "0x2e95b6c8":{
+ "info":"1inch",
+ "source":"https://github.com/LedgerHQ/app-plugin-1inch#functions-implemented",
+ "name":"unoswap"
+ },
+ "0x0502b1c5":{
+ "info":"1inch",
+ "source":"https://github.com/LedgerHQ/app-plugin-1inch#functions-implemented",
+ "name":"unoswap"
+ },
+ "0x3593564c": {
+ "info":"unoswap",
+ "source":"https://etherscan.io/address/0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
+ "name":"execute"
+ },
+ "0xd0e30db0": {
+ "name":"deposit"
+ }
+}
diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/CardTypesResolver.kt b/domain/legacy/src/main/java/com/tangem/domain/common/CardTypesResolver.kt
index 4b409286eb..4afc2621f7 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/common/CardTypesResolver.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/common/CardTypesResolver.kt
@@ -3,6 +3,7 @@ package com.tangem.domain.common
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
+@Suppress("TooManyFunctions")
interface CardTypesResolver {
fun isTangemNote(): Boolean
@@ -11,16 +12,26 @@ interface CardTypesResolver {
fun isShibaWallet(): Boolean
+ fun isTronWallet(): Boolean
+
+ fun isKaspaWallet(): Boolean
+
+ fun isBadWallet(): Boolean
+
fun isWhiteWallet(): Boolean
fun isWallet2(): Boolean
+ fun isRing(): Boolean
+
fun isTangemTwins(): Boolean
fun isStart2Coin(): Boolean
fun isDevKit(): Boolean
+ fun isSingleWalletWithToken(): Boolean
+
fun isMultiwalletAllowed(): Boolean
fun getBlockchain(): Blockchain
diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/LogConfig.kt b/domain/legacy/src/main/java/com/tangem/domain/common/LogConfig.kt
index 6ad07afbf2..2e115068d1 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/common/LogConfig.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/common/LogConfig.kt
@@ -5,7 +5,6 @@ import com.tangem.domain.features.BuildConfig
object LogConfig {
const val imageLoader: Boolean = false
val storeAction: Boolean = BuildConfig.DEBUG
- const val zendesk: Boolean = false
val network: NetworkLogConfig = NetworkLogConfig
val analyticsHandlers: AnalyticsHandlersLogConfig = AnalyticsHandlersLogConfig
}
diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt
index 35164b37d6..0349661db4 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/common/TangemCardTypesResolver.kt
@@ -12,6 +12,7 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.operations.attestation.Attestation
+@Suppress("TooManyFunctions")
internal class TangemCardTypesResolver(
private val card: CardDTO,
private val productType: ProductType,
@@ -29,12 +30,23 @@ internal class TangemCardTypesResolver(
return card.firmwareVersion.compareTo(FirmwareVersion.KeysImportAvailable) == 0
}
+ override fun isTronWallet(): Boolean = card.batchId == TRON_WALLET_BATCH_ID
+
+ override fun isKaspaWallet(): Boolean = card.batchId == KASPA_WALLET_BATCH_ID
+
+ override fun isBadWallet(): Boolean = card.batchId == BAD_WALLET_BATCH_ID
+
override fun isWhiteWallet(): Boolean {
return walletData == null && card.firmwareVersion <= FirmwareVersion.HDWalletAvailable
}
override fun isWallet2(): Boolean {
- return card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available && card.settings.isKeysImportAllowed
+ return card.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available &&
+ card.settings.isKeysImportAllowed
+ }
+
+ override fun isRing(): Boolean {
+ return productType == ProductType.Ring
}
override fun isTangemTwins(): Boolean = productType == ProductType.Twins
@@ -43,6 +55,8 @@ internal class TangemCardTypesResolver(
override fun isDevKit(): Boolean = card.batchId == DEV_KIT_CARD_BATCH_ID
+ override fun isSingleWalletWithToken(): Boolean = walletData?.token != null && !isMultiwalletAllowed()
+
override fun isMultiwalletAllowed(): Boolean {
return !isTangemTwins() && !card.isStart2Coin && !isTangemNote() &&
(multiWalletAvailable() || card.wallets.firstOrNull()?.curve == EllipticCurve.Secp256k1)
@@ -113,5 +127,8 @@ internal class TangemCardTypesResolver(
private companion object {
const val DEV_KIT_CARD_BATCH_ID = "CB83"
+ const val TRON_WALLET_BATCH_ID = "AF07"
+ const val KASPA_WALLET_BATCH_ID = "AF08"
+ const val BAD_WALLET_BATCH_ID = "AF09"
}
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
index 8c4877ad37..e5c093146e 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
@@ -70,8 +70,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
"octaspace/test" -> Blockchain.OctaSpaceTestnet
"chia" -> Blockchain.Chia
"chia/test" -> Blockchain.ChiaTestnet
- "near" -> Blockchain.Near
- "near/test" -> Blockchain.NearTestnet
+ "near-protocol" -> Blockchain.Near
+ "near-protocol/test" -> Blockchain.NearTestnet
else -> null
}
}
@@ -143,8 +143,8 @@ fun Blockchain.toNetworkId(): String {
Blockchain.OctaSpaceTestnet -> "octaspace/test"
Blockchain.Chia -> "chia"
Blockchain.ChiaTestnet -> "chia/test"
- Blockchain.Near -> "near"
- Blockchain.NearTestnet -> "near/test"
+ Blockchain.Near -> "near-protocol"
+ Blockchain.NearTestnet -> "near-protocol/test"
}
}
@@ -159,7 +159,7 @@ fun Blockchain.toCoinId(): String {
Blockchain.Stellar, Blockchain.StellarTestnet -> "stellar"
Blockchain.Cardano -> "cardano"
Blockchain.Polygon, Blockchain.PolygonTestnet -> "matic-network"
- Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> "ethereum"
+ Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> "arbitrum-one"
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> "avalanche-2"
Blockchain.Solana, Blockchain.SolanaTestnet -> "solana"
Blockchain.Fantom, Blockchain.FantomTestnet -> "fantom"
@@ -175,7 +175,7 @@ fun Blockchain.toCoinId(): String {
Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> "ethereum-pow-iou"
Blockchain.EthereumFair -> "ethereumfair"
Blockchain.Kusama -> "kusama"
- Blockchain.Optimism, Blockchain.OptimismTestnet -> "ethereum"
+ Blockchain.Optimism, Blockchain.OptimismTestnet -> "optimistic-ethereum"
Blockchain.Dash -> "dash"
Blockchain.Kaspa -> "kaspa"
Blockchain.TON, Blockchain.TONTestnet -> "the-open-network"
@@ -203,6 +203,7 @@ fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? {
return when (this) {
Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(NODL_AMOUNT_TO_CREATE_ACCOUNT) else BigDecimal.ONE
Blockchain.XRP -> BigDecimal.TEN
+ Blockchain.Near, Blockchain.NearTestnet -> 0.00182.toBigDecimal()
else -> null
}
}
@@ -217,4 +218,8 @@ private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5
private val excludedBlockchains = listOf(
Blockchain.Unknown,
Blockchain.Ducatus,
+ Blockchain.Telos,
+ Blockchain.TelosTestnet,
+ Blockchain.Near,
+ Blockchain.NearTestnet,
)
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt
index 4d43b6da00..61170a1927 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/redux/ReduxStateHolder.kt
@@ -1,8 +1,11 @@
package com.tangem.domain.redux
+import com.tangem.domain.wallets.models.UserWallet
import org.rekotlin.Action
interface ReduxStateHolder {
fun dispatch(action: Action)
+
+ suspend fun onUserWalletSelected(userWallet: UserWallet)
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt b/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt
index 29890a9abd..72d4684786 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt
@@ -20,7 +20,10 @@ class UserWalletBuilder(
ProductType.Note -> "Note"
ProductType.Twins -> "Twin"
ProductType.Start2Coin -> "Start2Coin"
- ProductType.Wallet -> when {
+ ProductType.Wallet,
+ ProductType.Wallet2,
+ ProductType.Ring,
+ -> when {
card.isBackupNotAllowed -> "Tangem card"
cardTypesResolver.isStart2Coin() -> "Start2Coin"
else -> "Wallet"
diff --git a/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt b/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt
index 40ac39de3c..a9fab74fb9 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt
@@ -61,6 +61,7 @@ class UserWalletIdBuilder private constructor(
ProductType.Note,
ProductType.Wallet,
ProductType.Wallet2,
+ ProductType.Ring,
ProductType.Start2Coin,
-> null
},
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
index 1f4caa5690..f9bc744566 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt
@@ -1,16 +1,20 @@
package com.tangem.domain.walletmanager
+import arrow.core.Either
+import arrow.core.raise.either
+import arrow.core.raise.ensureNotNull
+import arrow.core.right
+import com.squareup.moshi.Moshi
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.blockchains.solana.RentProvider
-import com.tangem.blockchain.common.AmountType
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.BlockchainSdkError
-import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest
import com.tangem.blockchain.extensions.Result
+import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.crypto.hdWallet.DerivationPath
+import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
@@ -24,18 +28,22 @@ import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
import com.tangem.domain.walletmanager.utils.*
+import com.tangem.domain.walletmanager.utils.WalletManagerFactory
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import timber.log.Timber
import java.math.BigDecimal
+@Suppress("LargeClass")
// FIXME: Move to its own module and make internal
@Deprecated("Inject the WalletManagerFacade interface using DI instead")
class DefaultWalletManagersFacade(
private val walletManagersStore: WalletManagersStore,
private val userWalletsStore: UserWalletsStore,
configManager: ConfigManager,
+ assetReader: AssetReader,
+ moshi: Moshi,
) : WalletManagersFacade {
private val demoConfig by lazy { DemoConfig() }
@@ -43,7 +51,7 @@ class DefaultWalletManagersFacade(
private val walletManagerFactory by lazy { WalletManagerFactory(configManager) }
private val sdkTokenConverter by lazy { SdkTokenConverter() }
private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() }
- private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter() }
+ private val txHistoryItemConverter by lazy { SdkTransactionHistoryItemConverter(assetReader, moshi) }
override suspend fun update(
userWalletId: UserWalletId,
@@ -57,6 +65,50 @@ class DefaultWalletManagersFacade(
return getAndUpdateWalletManager(userWallet, blockchain, derivationPath, extraTokens)
}
+ override suspend fun remove(userWalletId: UserWalletId, networks: Set) {
+ if (networks.isEmpty()) return
+
+ val blockchainsToDerivationPaths = networks.map {
+ Blockchain.fromId(it.id.value) to it.derivationPath.value
+ }
+
+ walletManagersStore.remove(userWalletId) { walletManager ->
+ val wallet = walletManager.wallet
+ val blockchainToDerivationPath = wallet.blockchain to wallet.publicKey.derivationPath?.rawPath
+
+ blockchainToDerivationPath in blockchainsToDerivationPaths
+ }
+ }
+
+ override suspend fun removeTokens(userWalletId: UserWalletId, tokens: Set) {
+ if (tokens.isEmpty()) return
+
+ tokens
+ .groupBy(CryptoCurrency.Token::network)
+ .forEach { (network, networkTokens) ->
+ removeTokens(userWalletId, network, networkTokens)
+ }
+ }
+
+ private suspend fun removeTokens(
+ userWalletId: UserWalletId,
+ network: Network,
+ networkTokens: List,
+ ) {
+ val walletManager = walletManagersStore.getSyncOrNull(
+ userWalletId = userWalletId,
+ blockchain = Blockchain.fromId(network.id.value),
+ derivationPath = network.derivationPath.value,
+ ) ?: return
+ val tokensToRemove = sdkTokenConverter.convertList(networkTokens)
+
+ tokensToRemove.forEach { token ->
+ walletManager.removeToken(token)
+ }
+
+ walletManagersStore.store(userWalletId, walletManager)
+ }
+
override suspend fun updatePendingTransactions(
userWalletId: UserWalletId,
network: Network,
@@ -83,6 +135,7 @@ class DefaultWalletManagersFacade(
userWalletId: UserWalletId,
network: Network,
addressType: AddressType,
+ contractAddress: String?,
): String {
val blockchain = Blockchain.fromId(network.id.value)
@@ -96,16 +149,20 @@ class DefaultWalletManagersFacade(
"Unable to get a wallet manager for blockchain: $blockchain"
}
- val address = walletManager.wallet.addresses.find { it.type == addressType }?.value
- return walletManager.wallet.getExploreUrl(address)
+ val address = walletManager
+ .wallet
+ .addresses
+ .find { it.type == addressType }
+ ?.value ?: walletManager.wallet.address
+ return blockchain.getExploreUrl(address, contractAddress)
}
- override suspend fun getTxHistoryState(userWalletId: UserWalletId, network: Network): TxHistoryState {
- val blockchain = Blockchain.fromId(network.id.value)
+ override suspend fun getTxHistoryState(userWalletId: UserWalletId, currency: CryptoCurrency): TxHistoryState {
+ val blockchain = Blockchain.fromId(currency.network.id.value)
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
- derivationPath = network.derivationPath.value,
+ derivationPath = currency.network.derivationPath.value,
)
requireNotNull(walletManager) {
@@ -113,7 +170,13 @@ class DefaultWalletManagersFacade(
}
return walletManager
- .getTransactionHistoryState(walletManager.wallet.address)
+ .getTransactionHistoryState(
+ address = walletManager.wallet.address,
+ filterType = when (currency) {
+ is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
+ is CryptoCurrency.Token -> TransactionHistoryRequest.FilterType.Contract(currency.contractAddress)
+ },
+ )
.let(txHistoryStateConverter::convert)
}
@@ -286,7 +349,11 @@ class DefaultWalletManagersFacade(
is Result.Success -> {
val balance = manager.wallet.fundsAvailable(AmountType.Coin)
val outgoingTxs = manager.wallet.recentTransactions
- .filter { it.sourceAddress == manager.wallet.address && it.amount.type == AmountType.Coin }
+ .filter {
+ it.sourceAddress == manager.wallet.address &&
+ it.amount.type == AmountType.Coin &&
+ it.status != TransactionStatus.Confirmed
+ }
val rentExempt = result.data
val setRent = if (outgoingTxs.isEmpty()) {
@@ -318,6 +385,29 @@ class DefaultWalletManagersFacade(
return walletManagersStore.getAll(userWalletId)
}
+ override suspend fun validateSignatureCount(
+ userWalletId: UserWalletId,
+ network: Network,
+ signedHashes: Int,
+ ): Either {
+ return either {
+ val walletManager = getOrCreateWalletManager(
+ userWalletId = userWalletId,
+ blockchain = Blockchain.fromId(network.id.value),
+ derivationPath = network.derivationPath.value,
+ )
+
+ val validator = ensureNotNull(walletManager as? SignatureCountValidator) {
+ raise(IllegalStateException("Wallet manager is not a SignatureCountValidator"))
+ }
+
+ when (val result = validator.validateSignatureCount(signedHashes)) {
+ is SimpleResult.Failure -> raise(result.error)
+ is SimpleResult.Success -> Unit.right()
+ }
+ }
+ }
+
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set) {
if (tokens.isEmpty()) return
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
index 19e400b067..e4817280a7 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt
@@ -1,5 +1,6 @@
package com.tangem.domain.walletmanager
+import arrow.core.Either
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
@@ -36,6 +37,16 @@ interface WalletManagersFacade {
extraTokens: Set,
): UpdateWalletManagerResult
+ /**
+ * Removes the wallet managers associated with a user's wallet and networks.
+ *
+ * @param userWalletId The ID of the user's wallet.
+ * @param networks Set of networks
+ * */
+ suspend fun remove(userWalletId: UserWalletId, networks: Set)
+
+ suspend fun removeTokens(userWalletId: UserWalletId, tokens: Set)
+
/**
* Returns [UpdateWalletManagerResult] with last pending transactions
*
@@ -51,18 +62,24 @@ interface WalletManagersFacade {
* @param userWalletId The ID of the user's wallet.
* @param network The network.
* @param addressType Address type.
+ * @param contractAddress Contract address if currency is Token.
*
* @return The network explorer URL, maybe empty if the wallet manager was not found.
* */
- suspend fun getExploreUrl(userWalletId: UserWalletId, network: Network, addressType: AddressType): String
+ suspend fun getExploreUrl(
+ userWalletId: UserWalletId,
+ network: Network,
+ addressType: AddressType,
+ contractAddress: String?,
+ ): String
/**
* Returns transactions count
*
* @param userWalletId The ID of the user's wallet.
- * @param network The network.
+ * @param currency currency.
*/
- suspend fun getTxHistoryState(userWalletId: UserWalletId, network: Network): TxHistoryState
+ suspend fun getTxHistoryState(userWalletId: UserWalletId, currency: CryptoCurrency): TxHistoryState
/**
* Returns transaction history items wrapped to pagination
@@ -79,7 +96,7 @@ interface WalletManagersFacade {
pageSize: Int,
): PaginationWrapper
- // TODO: Remove after refactoring
+ @Deprecated("Will be removed in future")
suspend fun getOrCreateWalletManager(
userWalletId: UserWalletId,
blockchain: Blockchain,
@@ -111,5 +128,12 @@ interface WalletManagersFacade {
*/
suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal?
+ @Deprecated("Will be removed in future")
fun getAll(userWalletId: UserWalletId): Flow>
+
+ suspend fun validateSignatureCount(
+ userWalletId: UserWalletId,
+ network: Network,
+ signedHashes: Int,
+ ): Either
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/SmartContractMethod.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/SmartContractMethod.kt
new file mode 100644
index 0000000000..58996b7fd3
--- /dev/null
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/model/SmartContractMethod.kt
@@ -0,0 +1,11 @@
+package com.tangem.domain.walletmanager.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class SmartContractMethod(
+ @Json(name = "info") val info: String?,
+ @Json(name = "source") val source: String?,
+ @Json(name = "name") val name: String,
+)
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt
index d0f0ef7d65..2a8c4248b7 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt
@@ -1,44 +1,93 @@
package com.tangem.domain.walletmanager.utils
+import com.squareup.moshi.Moshi
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
+import com.tangem.datasource.asset.AssetReader
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem as SdkTransactionHistoryItem
-internal class SdkTransactionHistoryItemConverter : Converter {
+internal class SdkTransactionHistoryItemConverter(
+ assetReader: AssetReader,
+ moshi: Moshi,
+) : Converter {
+
+ private val typeConverter by lazy {
+ SdkTransactionTypeConverter(assetReader, moshi)
+ }
override fun convert(value: SdkTransactionHistoryItem): TxHistoryItem = TxHistoryItem(
txHash = value.txHash,
timestampInMillis = value.timestamp,
- direction = value.direction.toDomain(),
+ isOutgoing = value.isOutgoing,
+ destinationType = value.destinationType.toDomain(),
+ sourceType = value.sourceType.toDomain(),
+ interactionAddressType = value.extractInteractionAddressType(),
status = when (value.status) {
- TransactionHistoryItem.TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed
- TransactionHistoryItem.TransactionStatus.Failed -> TxHistoryItem.TransactionStatus.Failed
- TransactionHistoryItem.TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed
- },
- type = when (val type = value.type) {
- TransactionHistoryItem.TransactionType.Transfer -> TxHistoryItem.TransactionType.Transfer
- TransactionHistoryItem.TransactionType.Approve -> TxHistoryItem.TransactionType.Approve
- is TransactionHistoryItem.TransactionType.Custom -> TxHistoryItem.TransactionType.Custom(type.id)
- TransactionHistoryItem.TransactionType.Deposit -> TxHistoryItem.TransactionType.Deposit
- TransactionHistoryItem.TransactionType.Submit -> TxHistoryItem.TransactionType.Submit
- TransactionHistoryItem.TransactionType.Supply -> TxHistoryItem.TransactionType.Supply
- TransactionHistoryItem.TransactionType.Swap -> TxHistoryItem.TransactionType.Swap
- TransactionHistoryItem.TransactionType.Unoswap -> TxHistoryItem.TransactionType.Unoswap
- TransactionHistoryItem.TransactionType.Withdraw -> TxHistoryItem.TransactionType.Withdraw
+ SdkTransactionHistoryItem.TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed
+ SdkTransactionHistoryItem.TransactionStatus.Failed -> TxHistoryItem.TransactionStatus.Failed
+ SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed
},
+ type = typeConverter.convert(value.type),
amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" },
)
- private fun TransactionHistoryItem.TransactionDirection.toDomain() = when (this) {
- is TransactionHistoryItem.TransactionDirection.Incoming ->
- TxHistoryItem.TransactionDirection.Incoming(address.toDomain())
- is TransactionHistoryItem.TransactionDirection.Outgoing ->
- TxHistoryItem.TransactionDirection.Outgoing(address.toDomain())
+ private fun SdkTransactionHistoryItem.SourceType.toDomain(): TxHistoryItem.SourceType = when (this) {
+ is TransactionHistoryItem.SourceType.Single -> TxHistoryItem.SourceType.Single(address)
+ is TransactionHistoryItem.SourceType.Multiple -> TxHistoryItem.SourceType.Multiple(addresses)
}
- private fun TransactionHistoryItem.Address.toDomain(): TxHistoryItem.Address = when (this) {
- TransactionHistoryItem.Address.Multiple -> TxHistoryItem.Address.Multiple
- is TransactionHistoryItem.Address.Single -> TxHistoryItem.Address.Single(rawAddress = rawAddress)
+ private fun SdkTransactionHistoryItem.DestinationType.toDomain(): TxHistoryItem.DestinationType = when (this) {
+ is SdkTransactionHistoryItem.DestinationType.Single -> TxHistoryItem.DestinationType.Single(
+ addressType.toDomain(),
+ )
+ is SdkTransactionHistoryItem.DestinationType.Multiple -> TxHistoryItem.DestinationType.Multiple(
+ addressTypes.map { it.toDomain() },
+ )
+ }
+
+ private fun SdkTransactionHistoryItem.AddressType.toDomain(): TxHistoryItem.AddressType = when (this) {
+ is SdkTransactionHistoryItem.AddressType.Contract -> TxHistoryItem.AddressType.Contract(address)
+ is SdkTransactionHistoryItem.AddressType.User -> TxHistoryItem.AddressType.User(address)
+ }
+
+ private fun SdkTransactionHistoryItem.extractInteractionAddressType(): TxHistoryItem.InteractionAddressType {
+ return when (type) {
+ SdkTransactionHistoryItem.TransactionType.Transfer -> if (isOutgoing) {
+ mapToInteractionAddressType(destinationType = destinationType)
+ } else {
+ mapToInteractionAddressType(sourceType = sourceType)
+ }
+ is SdkTransactionHistoryItem.TransactionType.ContractMethod -> mapToInteractionAddressType(destinationType)
+ }
+ }
+
+ private fun mapToInteractionAddressType(
+ destinationType: SdkTransactionHistoryItem.DestinationType,
+ ): TxHistoryItem.InteractionAddressType {
+ return when (destinationType) {
+ is TransactionHistoryItem.DestinationType.Multiple -> TxHistoryItem.InteractionAddressType.Multiple(
+ destinationType.addressTypes.map { it.address },
+ )
+ is TransactionHistoryItem.DestinationType.Single -> when (destinationType.addressType) {
+ is TransactionHistoryItem.AddressType.Contract -> TxHistoryItem.InteractionAddressType.Contract(
+ destinationType.addressType.address,
+ )
+ is TransactionHistoryItem.AddressType.User -> TxHistoryItem.InteractionAddressType.User(
+ destinationType.addressType.address,
+ )
+ }
+ }
+ }
+
+ private fun mapToInteractionAddressType(
+ sourceType: SdkTransactionHistoryItem.SourceType,
+ ): TxHistoryItem.InteractionAddressType {
+ return when (sourceType) {
+ is TransactionHistoryItem.SourceType.Multiple -> TxHistoryItem.InteractionAddressType.Multiple(
+ sourceType.addresses,
+ )
+ is TransactionHistoryItem.SourceType.Single -> TxHistoryItem.InteractionAddressType.User(sourceType.address)
+ }
}
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt
new file mode 100644
index 0000000000..67019252a1
--- /dev/null
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt
@@ -0,0 +1,47 @@
+package com.tangem.domain.walletmanager.utils
+
+import com.squareup.moshi.JsonAdapter
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.Types
+import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
+import com.tangem.datasource.asset.AssetReader
+import com.tangem.domain.txhistory.models.TxHistoryItem
+import com.tangem.domain.walletmanager.model.SmartContractMethod
+import com.tangem.utils.converter.Converter
+
+class SdkTransactionTypeConverter(
+ private val assetReader: AssetReader,
+ private val moshi: Moshi,
+) : Converter {
+
+ private val adapter: JsonAdapter