diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000..57188dd623
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+[*.{kt,kts}]
+ktlint_code_style = official
+ktlint_ignore_back_ticked_identifier = true
+
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+
+ij_kotlin_allow_trailing_comma = true
+ij_kotlin_allow_trailing_comma_on_call_site = true
+
+ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
+ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**
+
+max_line_length = 120
diff --git a/.gitignore b/.gitignore
index 4cc00a7e1f..dd64e70b22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,8 @@ local.properties
# User-specific configurations
.idea
+!.idea/codeStyles
+!.idea/inspectionProfiles
# LayoutInspector files
/captures
@@ -26,4 +28,4 @@ local.properties
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
-**/fastlane/test_output
\ No newline at end of file
+**/fastlane/test_output
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000000..ad9c1261c4
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000000..79ee123c2b
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000000..ba5af7a4ce
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 791cb96330..105d4d8323 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,13 +6,13 @@ apply plugin: 'com.google.firebase.crashlytics'
apply from: '../dependencies.gradle'
android {
- compileSdkVersion 31
+ compileSdkVersion 32
defaultConfig {
applicationId "com.tangem.wallet"
versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 1
versionName project.hasProperty('versionName') ? project.property('versionName') : "1.0.0.-SNAPSHOT"
minSdkVersion 21
- targetSdkVersion 31
+ targetSdkVersion 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField 'Boolean', environmentConfig.testActionEnabled, 'false'
@@ -83,12 +83,11 @@ dependencies {
implementation implementation(project(path: ':network'))
implementation implementation(project(path: ':common'))
- implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.google.android.material:material:1.5.0'
- implementation "androidx.core:core-ktx:1.7.0"
implementation 'com.google.android.play:core:1.10.3'
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
@@ -144,8 +143,11 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
-//
-// //Crypto
+ // Support
+ implementation("com.zendesk:support:5.0.9")
+ implementation("com.zendesk:messaging:5.2.4")
+
+ //Crypto
implementation "com.madgag.spongycastle:core:1.58.0.0"
// animation
@@ -161,14 +163,14 @@ dependencies {
implementation 'com.google.android.gms:play-services-wallet:19.1.0'
//Compose
- implementation 'androidx.activity:activity-compose:1.4.0'
+ implementation 'androidx.activity:activity-compose:1.5.0'
implementation 'androidx.compose.material:material:1.1.1'
implementation 'androidx.compose.animation:animation:1.1.1'
- implementation 'androidx.compose.foundation:foundation:1.1.1'
- implementation 'androidx.compose.foundation:foundation-layout:1.1.1'
+ implementation 'androidx.compose.foundation:foundation:1.2.0-rc03'
implementation 'androidx.compose.ui:ui:1.1.1'
implementation 'androidx.compose.ui:ui-tooling:1.1.1'
implementation "com.google.accompanist:accompanist-appcompat-theme:0.23.0"
+ implementation "com.google.accompanist:accompanist-systemuicontroller:0.23.0"
implementation "com.github.skydoves:androidveil:1.1.2"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 22fda180cd..d180642a75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -128,6 +128,21 @@
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config
index f50133547b..59ee0bff06 160000
--- a/app/src/main/assets/tangem-app-config
+++ b/app/src/main/assets/tangem-app-config
@@ -1 +1 @@
-Subproject commit f50133547b5e19fb7ad32e64370573d60f6791fd
+Subproject commit 59ee0bff06ee54f3202421c28f7fb6e3d421dad8
diff --git a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt
new file mode 100644
index 0000000000..806769df5f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt
@@ -0,0 +1,46 @@
+package com.tangem.tap
+
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
+import android.os.Bundle
+import java.util.WeakHashMap
+import kotlin.reflect.KClass
+
+class ForegroundActivityObserver {
+ private val activities = WeakHashMap, Activity>()
+
+ val foregroundActivity: Activity?
+ get() = activities.entries
+ .filterNot { it.value.isDestroyed }
+ .firstOrNull()
+ ?.value
+
+ val callbacks get() = Callbacks()
+
+ inner class Callbacks : ActivityLifecycleCallbacks {
+ override fun onActivityResumed(activity: Activity) {
+ activities[activity::class] = activity
+ }
+
+ override fun onActivityDestroyed(activity: Activity) {
+ activities.remove(activity::class)
+ }
+
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+ }
+ override fun onActivityStarted(activity: Activity) {
+ }
+ override fun onActivityPaused(activity: Activity) {
+ }
+ override fun onActivityStopped(activity: Activity) {
+ }
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
+ }
+ }
+}
+
+fun ForegroundActivityObserver.withForegroundActivity(
+ block: (Activity) -> Unit
+) {
+ foregroundActivity?.let { block(it) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index a663e66e7a..78d55638ca 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -5,6 +5,8 @@ import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsControllerCompat
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.snackbar.Snackbar
import com.tangem.TangemSdk
@@ -57,7 +59,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
systemActions()
- store.state.globalState.feedbackManager?.updateActivity(this)
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
tangemSdk = TangemSdk.init(this, TangemSdkManager.config)
@@ -84,8 +85,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler {
}
private fun systemActions() {
- // makes the status bar text dark
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ WindowInsetsControllerCompat(window, binding.root)
+ .isAppearanceLightStatusBars = true
+
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt
index c84981fa22..2dee2fa397 100644
--- a/app/src/main/java/com/tangem/tap/TapApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TapApplication.kt
@@ -13,7 +13,10 @@ import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.domain.DomainLayer
import com.tangem.network.common.MoshiConverter
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
+import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
+import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.images.createCoilImageLoader
+import com.tangem.tap.common.log.TangemLogCollector
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.common.redux.global.GlobalAction
@@ -25,9 +28,6 @@ import com.tangem.tap.domain.configurable.warningMessage.RemoteWarningLoader
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
-import com.tangem.tap.features.feedback.AdditionalEmailInfo
-import com.tangem.tap.features.feedback.FeedbackManager
-import com.tangem.tap.features.feedback.TangemLogCollector
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.persistence.PreferencesStorage
import com.tangem.wallet.BuildConfig
@@ -41,6 +41,8 @@ val store = Store(
)
val logConfig = LogConfig()
+lateinit var foregroundActivityObserver: ForegroundActivityObserver
+
lateinit var preferencesStorage: PreferencesStorage
lateinit var currenciesRepository: CurrenciesRepository
lateinit var walletConnectRepository: WalletConnectRepository
@@ -69,6 +71,8 @@ class TapApplication : Application(), ImageLoaderFactory {
)
walletConnectRepository = WalletConnectRepository(this)
+ foregroundActivityObserver = ForegroundActivityObserver()
+ registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
initFeedbackManager()
loadConfigs()
@@ -86,16 +90,23 @@ class TapApplication : Application(), ImageLoaderFactory {
val localLoader = FeaturesLocalLoader(this, moshi)
val remoteLoader = FeaturesRemoteLoader(moshi)
val configManager = ConfigManager(localLoader, remoteLoader)
- configManager.load {
+ configManager.load { config ->
store.dispatch(GlobalAction.SetConfigManager(configManager))
- shopService = TangemShopService(this, configManager.config.shopify!!)
+ shopService = TangemShopService(
+ application = this,
+ shopifyShop = config.shopify!!
+ )
+ store.state.globalState.feedbackManager?.initChat(
+ context = this,
+ zendeskConfig = config.zendesk!!
+ )
}
val warningsManager = WarningMessagesManager(RemoteWarningLoader(moshi))
warningsManager.load { store.dispatch(GlobalAction.SetWarningManager(warningsManager)) }
}
private fun initFeedbackManager() {
- val infoHolder = AdditionalEmailInfo()
+ val infoHolder = AdditionalFeedbackInfo()
infoHolder.setAppVersion(this)
val logLevels = listOf(
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 dd24af74c5..a5682e3b5c 100644
--- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
@@ -105,6 +105,12 @@ class DialogManager : StoreSubscriber {
dAppName = state.dialog.dAppName,
context = context
)
+ is WalletConnectDialog.UnsupportedNetwork ->
+ SimpleAlertDialog.create(
+ titleRes = R.string.wallet_connect,
+ messageRes = R.string.wallet_connect_scanner_error_unsupported_network,
+ context = context
+ )
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
@@ -131,6 +137,8 @@ class DialogManager : StoreSubscriber {
primaryButtonRes = state.dialog.primaryButtonRes,
primaryButtonAction = state.dialog.onOk
)
+ is WalletDialog.RussianCardholdersWarningDialog ->
+ RussianCardholdersWarningBottomSheetDialog(context)
else -> null
}
dialog?.show()
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
index 89d9327ed6..f538ff13a3 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
@@ -61,7 +61,11 @@ fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {
}
fun FragmentActivity.getPreviousScreen(): AppScreen? {
- val indexOfLastFragment = this.supportFragmentManager.backStackEntryCount - 1
+ val indexOfLastFragment = if (this.supportFragmentManager.backStackEntryCount > 0) {
+ this.supportFragmentManager.backStackEntryCount - 1
+ } else {
+ 0
+ }
val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount)
this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name
else null
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 d671b60a29..39e0103066 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
@@ -8,7 +8,7 @@ import com.tangem.tap.common.TestActions
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.getFirstToken
-import com.tangem.tap.features.demo.isDemoWallet
+import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.reducers.createAddressesData
import com.tangem.tap.network.NetworkConnectivity
@@ -21,7 +21,10 @@ import timber.log.Timber
[REDACTED_AUTHOR]
*/
suspend fun WalletManager.safeUpdate(): Result = try {
- if (isDemoWallet() || TestActions.testAmountInjectionForWalletManagerEnabled) {
+ val scanResponse = store.state.globalState.scanResponse
+ ?: error("Scan response must not be null")
+
+ if (scanResponse.isDemoCard() || TestActions.testAmountInjectionForWalletManagerEnabled) {
delay(500)
TestActions.testAmountInjectionForWalletManagerEnabled = false
Result.Success(wallet)
@@ -49,11 +52,11 @@ suspend fun WalletManager.safeUpdate(): Result = try {
fun WalletManager?.getToUpUrl(): String? {
val globalState = store.state.globalState
- val currencyExchangeManager = globalState.currencyExchangeManager ?: return null
+ val exchangeManager = globalState.exchangeManager ?: return null
val wallet = this?.wallet ?: return null
val defaultAddress = wallet.address
- return currencyExchangeManager.getUrl(
+ return exchangeManager.getUrl(
action = CurrencyExchangeManager.Action.Buy,
blockchain = wallet.blockchain,
cryptoCurrencyName = wallet.blockchain.currency,
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
new file mode 100644
index 0000000000..ddc7a41b23
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt
@@ -0,0 +1,127 @@
+package com.tangem.tap.common.feedback
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.domain.common.ScanResponse
+import com.tangem.tap.common.extensions.stripZeroPlainString
+
+class AdditionalFeedbackInfo {
+ class EmailWalletInfo(
+ var blockchain: Blockchain = Blockchain.Unknown,
+ var address: String = "",
+ var explorerLink: String = "",
+ var host: String = "",
+ var derivationPath: String = "",
+ )
+
+ var appVersion: String = ""
+
+ // card
+ var cardId: String = ""
+ var cardFirmwareVersion: String = ""
+ var cardIssuer: String = ""
+ var cardBlockchain: String = ""
+
+ // wallets
+ internal val walletsInfo = mutableListOf()
+ internal val tokens = mutableMapOf>()
+ internal var onSendErrorWalletInfo: EmailWalletInfo? = null
+ var signedHashesCount: String = ""
+
+ // device
+ var phoneModel: String = Build.MODEL
+ var osVersion: String = Build.VERSION.SDK_INT.toString()
+
+ // send error
+ var destinationAddress: String = ""
+ var amount: String = ""
+ var fee: String = ""
+ var token: String = ""
+
+ fun setAppVersion(context: Context) {
+ try {
+ val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
+ appVersion = pInfo.versionName
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ }
+ }
+
+ fun setCardInfo(data: ScanResponse) {
+ cardId = data.card.cardId
+ cardBlockchain = data.walletData?.blockchain ?: ""
+ cardFirmwareVersion = data.card.firmwareVersion.stringValue
+ cardIssuer = data.card.issuer.name
+ signedHashesCount = data.card.wallets
+ .joinToString("; ") { "${it.curve.curve} - ${it.totalSignedHashes}" }
+ }
+
+ fun setWalletsInfo(walletManagers: List) {
+ walletsInfo.clear()
+ tokens.clear()
+ walletManagers.forEach { manager ->
+ walletsInfo.add(
+ EmailWalletInfo(
+ blockchain = manager.wallet.blockchain,
+ address = getAddress(manager.wallet),
+ explorerLink = getExploreUri(manager.wallet),
+ host = manager.currentHost,
+ derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: ""
+ )
+ )
+ if (manager.cardTokens.isNotEmpty()) {
+ tokens[manager.wallet.blockchain] = manager.cardTokens
+ }
+ }
+ }
+
+ fun updateOnSendError(
+ wallet: Wallet,
+ host: String,
+ amountToSend: Amount,
+ feeAmount: Amount,
+ destinationAddress: String,
+ ) {
+ onSendErrorWalletInfo = EmailWalletInfo(
+ blockchain = wallet.blockchain,
+ address = getAddress(wallet),
+ explorerLink = getExploreUri(wallet),
+ host = host,
+ derivationPath = wallet.publicKey.derivationPath?.rawPath ?: ""
+ )
+
+ this.destinationAddress = destinationAddress
+ amount = amountToSend.value?.stripZeroPlainString() ?: "0"
+ fee = feeAmount.value?.stripZeroPlainString() ?: "0"
+ token = if (amountToSend.type is AmountType.Token) amountToSend.currencySymbol else ""
+ }
+
+ private fun getAddress(wallet: Wallet): String {
+ return if (wallet.addresses.size == 1) {
+ wallet.address
+ } else {
+ val addresses = wallet.addresses.joinToString(", ") {
+ "${it.type.javaClass.simpleName} - ${it.value}"
+ }
+ "Multiple address: $addresses"
+ }
+ }
+
+ private fun getExploreUri(wallet: Wallet): String {
+ return if (wallet.addresses.size == 1) {
+ wallet.getExploreUrl(wallet.address)
+ } else {
+ val links = wallet.addresses.joinToString(", ") {
+ "${it.type.javaClass.simpleName} - ${wallet.getExploreUrl(it.value)}"
+ }
+ "Multiple explorers links: $links"
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt
new file mode 100644
index 0000000000..dc2a7d07d5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackData.kt
@@ -0,0 +1,108 @@
+package com.tangem.tap.common.feedback
+
+import android.content.Context
+import com.tangem.domain.common.TapWorkarounds
+import com.tangem.wallet.R
+
+interface FeedbackData {
+ val subjectResId: Int
+ val mainMessageResId: Int
+
+ fun getDataCollectionMessageResId(): Int = R.string.feedback_data_collection_message
+
+ fun prepare(infoHolder: AdditionalFeedbackInfo) {}
+
+ fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String
+
+ fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String {
+ return StringBuilder().apply {
+ append(context.getString(mainMessageResId))
+ appendLine(3)
+ append(context.getString(getDataCollectionMessageResId()))
+ appendLine()
+ append(createOptionalMessage(infoHolder))
+ }.toString()
+ }
+}
+
+class RateCanBeBetterEmail : FeedbackData {
+ override val subjectResId: Int = R.string.feedback_subject_rate_negative
+ override val mainMessageResId: Int = R.string.feedback_preface_rate_negative
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
+ .appendCardInfo()
+ .appendLine()
+ .appendPhoneInfo()
+ .build()
+}
+
+class ScanFailsEmail : FeedbackData {
+
+ override val subjectResId: Int = R.string.feedback_subject_scan_failed
+ override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
+
+ override fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String = StringBuilder().apply {
+ append(context.getString(mainMessageResId))
+ appendLine(4)
+ append(createOptionalMessage(infoHolder))
+ }.toString()
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
+ .appendPhoneInfo()
+ .build()
+}
+
+class SendTransactionFailedEmail(
+ val error: String
+) : FeedbackData {
+
+ override val subjectResId: Int = R.string.feedback_subject_tx_failed
+ override val mainMessageResId: Int = R.string.feedback_preface_tx_failed
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
+ .appendCardInfo()
+ .appendDelimiter()
+ .appendTxFailedBlockchainInfo(error)
+ .appendLine()
+ .appendPhoneInfo()
+ .build()
+}
+
+class FeedbackEmail : FeedbackData {
+ override val subjectResId: Int
+ get() = if (isS2CCard) s2cSubject else tangemSubject
+ override val mainMessageResId: Int
+ get() = if (isS2CCard) s2cMainMessage else tangemMainMessage
+
+ private val tangemSubject: Int = R.string.feedback_subject_support_tangem
+ private val tangemMainMessage: Int = R.string.feedback_preface_support
+
+ private val s2cSubject: Int = R.string.feedback_subject_support
+ private val s2cMainMessage: Int = R.string.feedback_preface_support
+
+ private var isS2CCard = false
+
+ override fun prepare(infoHolder: AdditionalFeedbackInfo) {
+ isS2CCard = TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)
+ }
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String = FeedbackDataBuilder(infoHolder)
+ .appendCardInfo()
+ .appendWalletsInfo()
+ .appendLine()
+ .appendPhoneInfo()
+ .build()
+}
+
+class SupportInfo : FeedbackData {
+ override val subjectResId: Int = R.string.details_ask_a_question
+ override val mainMessageResId: Int = R.string.details_ask_a_question
+
+ override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String {
+ return FeedbackDataBuilder(infoHolder)
+ .appendCardInfo()
+ .appendDelimiter()
+ .appendPhoneInfo()
+ .build()
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..9e5378e9e5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackDataBuilder.kt
@@ -0,0 +1,81 @@
+package com.tangem.tap.common.feedback
+
+class FeedbackDataBuilder(
+ private val infoHolder: AdditionalFeedbackInfo
+) {
+ val builder = StringBuilder()
+
+ fun appendDelimiter(): FeedbackDataBuilder {
+ builder.appendDelimiter()
+ return this
+ }
+
+ fun appendLine(count: Int = 1): FeedbackDataBuilder {
+ builder.appendLine(count)
+ return this
+ }
+
+ fun appendCardInfo(): FeedbackDataBuilder {
+ builder.appendKeyValue("Card ID", infoHolder.cardId)
+ builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
+ builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain)
+ builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
+ return this
+ }
+
+ fun appendWalletsInfo(): FeedbackDataBuilder {
+ infoHolder.walletsInfo.forEach {
+ builder.appendDelimiter()
+ builder.appendKeyValue("Blockchain", it.blockchain.fullName)
+ builder.appendKeyValue("Host", it.host)
+ builder.appendKeyValue("Wallet address", it.address)
+ builder.appendKeyValue("Derivation path", it.derivationPath)
+ builder.appendKeyValue("Explorer link", it.explorerLink)
+
+ infoHolder.tokens[it.blockchain]?.let { tokens ->
+ builder.append("Tokens:")
+ appendLine()
+ tokens.forEach { token ->
+ builder.appendKeyValue("Name", token.name)
+ builder.appendKeyValue("ID", token.id ?: "[custom token]")
+ builder.appendKeyValue("Contract address", token.contractAddress)
+ }
+ }
+ }
+ return this
+ }
+
+ fun appendTxFailedBlockchainInfo(error: String): FeedbackDataBuilder {
+ val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalFeedbackInfo.EmailWalletInfo()
+ builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
+ builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
+ builder.appendKeyValue("Host", walletInfo.host)
+ builder.appendKeyValue("Token", infoHolder.token)
+ builder.appendKeyValue("Error", error)
+ builder.appendDelimiter()
+ builder.appendKeyValue("Source address", walletInfo.address)
+ builder.appendKeyValue("Destination address", infoHolder.destinationAddress)
+ builder.appendKeyValue("Amount", infoHolder.amount)
+ builder.appendKeyValue("Fee", infoHolder.fee)
+ return this
+ }
+
+ fun appendPhoneInfo(): FeedbackDataBuilder {
+ builder.appendKeyValue("Phone model", infoHolder.phoneModel)
+ builder.appendKeyValue("OS version", infoHolder.osVersion)
+ builder.appendKeyValue("App version", infoHolder.appVersion)
+ return this
+ }
+
+ fun build(): String = builder.toString()
+}
+
+private fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder {
+ return if (value.isNotBlank()) this.append("$key: $value\n") else this
+}
+
+private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n")
+
+private fun StringBuilder.appendLine(count: Int = 1): StringBuilder {
+ return append(List(count) { "\n" }.joinToString(separator = ""))
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
new file mode 100644
index 0000000000..4e551608e5
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
@@ -0,0 +1,115 @@
+package com.tangem.tap.common.feedback
+
+import android.content.Context
+import com.tangem.domain.common.TapWorkarounds
+import com.tangem.tap.common.extensions.sendEmail
+import com.tangem.tap.common.log.TangemLogCollector
+import com.tangem.tap.common.zendesk.ZendeskConfig
+import com.tangem.tap.foregroundActivityObserver
+import com.tangem.tap.withForegroundActivity
+import java.io.File
+import java.io.FileWriter
+import java.io.StringWriter
+import timber.log.Timber
+import zendesk.configurations.Configuration
+import zendesk.core.AnonymousIdentity
+import zendesk.core.Zendesk
+import zendesk.support.Support
+import zendesk.support.request.RequestConfiguration
+import zendesk.support.requestlist.RequestListActivity
+import zendesk.support.requestlist.RequestListConfiguration
+
+/**
+[REDACTED_AUTHOR]
+ */
+class FeedbackManager(
+ val infoHolder: AdditionalFeedbackInfo,
+ private val logCollector: TangemLogCollector,
+) {
+ fun initChat(
+ context: Context,
+ zendeskConfig: ZendeskConfig,
+ ) {
+ Zendesk.INSTANCE.init(
+ /* context = */ context,
+ /* zendeskUrl = */ zendeskConfig.url,
+ /* applicationId = */ zendeskConfig.appId,
+ /* oauthClientId = */ zendeskConfig.clientId,
+ )
+ Support.INSTANCE.init(Zendesk.INSTANCE)
+ Zendesk.INSTANCE.setIdentity(AnonymousIdentity())
+ }
+
+ fun sendEmail(feedbackData: FeedbackData, onFail: ((Exception) -> Unit)? = null) {
+ feedbackData.prepare(infoHolder)
+ foregroundActivityObserver.withForegroundActivity { activity ->
+ val fileLog = if (feedbackData is ScanFailsEmail) createLogFile(activity) else null
+ activity.sendEmail(
+ email = getSupportEmail(),
+ subject = activity.getString(feedbackData.subjectResId),
+ message = feedbackData.joinTogether(activity, infoHolder),
+ file = fileLog,
+ onFail = onFail
+ )
+ }
+ }
+
+ fun openChat(feedbackData: FeedbackData) {
+ feedbackData.prepare(infoHolder)
+ foregroundActivityObserver.withForegroundActivity { activity ->
+ RequestListActivity.builder()
+ .show(
+ /* context = */ activity,
+ /* configurations = */ buildConfigs(activity, feedbackData)
+ )
+ }
+ }
+
+ private fun createLogFile(context: Context): File? {
+ return try {
+ val file = File(context.filesDir, "logs.txt")
+ file.delete()
+ file.createNewFile()
+
+ val stringWriter = StringWriter()
+ logCollector.getLogs().forEach { stringWriter.append(it) }
+ val fileWriter = FileWriter(file)
+ fileWriter.write(stringWriter.toString())
+ fileWriter.close()
+ logCollector.clearLogs()
+ file
+ } catch (ex: Exception) {
+ Timber.e(ex, "Can't create the logs file")
+ null
+ }
+ }
+
+ private fun buildConfigs(
+ context: Context,
+ feedbackData: FeedbackData,
+ ): List {
+ return listOf(
+ // Request configuration
+ RequestConfiguration.Builder()
+ .withRequestSubject(context.getString(feedbackData.subjectResId))
+ .config(),
+ // Request list configuration
+ RequestListConfiguration.Builder()
+ .withContactUsButtonVisible(true)
+ .config(),
+ )
+ }
+
+ private fun getSupportEmail(): String {
+ return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) {
+ S2C_SUPPORT_EMAIL
+ } else {
+ DEFAULT_SUPPORT_EMAIL
+ }
+ }
+
+ companion object {
+ const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
+ const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/log/TangemLogCollector.kt b/app/src/main/java/com/tangem/tap/common/log/TangemLogCollector.kt
new file mode 100644
index 0000000000..c1ae08ebc2
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/log/TangemLogCollector.kt
@@ -0,0 +1,25 @@
+package com.tangem.tap.common.log
+
+import com.tangem.Log
+import com.tangem.TangemSdkLogger
+import java.text.SimpleDateFormat
+import java.util.Date
+
+class TangemLogCollector : TangemSdkLogger {
+ private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS")
+ private val logs = mutableListOf()
+ private val mutex = Object()
+
+ override fun log(message: () -> String, level: Log.Level) {
+ val time = dateFormatter.format(Date())
+ synchronized(mutex) {
+ logs.add("$time: ${message()}\n")
+ }
+ }
+
+ fun getLogs(): List = synchronized(mutex) { logs.toList() }
+
+ fun clearLogs() {
+ synchronized(mutex) { logs.clear() }
+ }
+}
\ No newline at end of file
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 b871b7853f..4927405ccd 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
@@ -7,15 +7,18 @@ import com.tangem.common.core.TangemError
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.GlobalAnalyticsHandler
import com.tangem.tap.common.entities.FiatCurrency
-import com.tangem.tap.common.redux.*
+import com.tangem.tap.common.feedback.FeedbackData
+import com.tangem.tap.common.feedback.FeedbackManager
+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.StateDialog
+import com.tangem.tap.common.redux.ToastNotificationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.details.redux.SecurityOption
-import com.tangem.tap.features.feedback.EmailData
-import com.tangem.tap.features.feedback.FeedbackManager
-import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import org.rekotlin.Action
sealed class GlobalAction : Action {
@@ -74,10 +77,21 @@ sealed class GlobalAction : Action {
data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction()
data class SetAnanlyticHandlers(val analyticsHandlers: GlobalAnalyticsHandler) : GlobalAction()
- data class SendFeedback(val emailData: EmailData) : GlobalAction()
+ data class SendEmail(val feedbackData: FeedbackData) : GlobalAction()
+ data class OpenChat(val feedbackData: FeedbackData) : GlobalAction()
data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction()
- object InitCurrencyExchangeManager : GlobalAction() {
- data class Success(val exchangeManager: CurrencyExchangeManager) : GlobalAction()
+ object ExchangeManager : GlobalAction() {
+ object Init : GlobalAction() {
+ data class Success(
+ val exchangeManager: com.tangem.tap.network.exchangeServices.CurrencyExchangeManager,
+ ) : GlobalAction()
+ }
+
+ object Update : GlobalAction()
+ }
+
+ object FetchUserCountry : GlobalAction() {
+ data class Success(val countryCode: String) : GlobalAction()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
index 1e104b5dd4..17cb291621 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
@@ -2,20 +2,31 @@ package com.tangem.tap.common.redux.global
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
+import com.tangem.common.extensions.guard
import com.tangem.common.extensions.ifNotNull
+import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.withMainContext
-import com.tangem.tap.*
+import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
+import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoApi
+import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
-import com.tangem.tap.network.exchangeServices.onramper.OnramperService
+import com.tangem.tap.preferencesStorage
+import com.tangem.tap.scope
+import com.tangem.tap.store
+import com.tangem.tap.tangemSdkManager
+import java.util.Locale
import kotlinx.coroutines.launch
+import org.rekotlin.Action
+import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
class GlobalMiddleware {
@@ -24,94 +35,134 @@ class GlobalMiddleware {
}
}
-private val globalMiddlewareHandler: Middleware = { _, appState ->
+private val globalMiddlewareHandler: Middleware = { dispatch, appState ->
{ nextDispatch ->
{ action ->
- when (action) {
- is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
- when (action.result) {
- is CompletionResult.Success -> store.dispatch(GlobalAction.ScanFailsCounter.Reset)
+ handleAction(action, appState, dispatch)
+ nextDispatch(action)
+ }
+ }
+}
+
+private fun handleAction(action: Action, appState: () -> AppState?, dispatch: DispatchFunction) {
+ when (action) {
+ is GlobalAction.ScanFailsCounter.ChooseBehavior -> {
+ when (action.result) {
+ is CompletionResult.Success -> store.dispatch(GlobalAction.ScanFailsCounter.Reset)
+ is CompletionResult.Failure -> {
+ if (action.result.error is TangemSdkError.UserCancelled) {
+ store.dispatch(GlobalAction.ScanFailsCounter.Increment)
+ if (store.state.globalState.scanCardFailsCounter >= 2) {
+ store.dispatchDialogShow(AppDialog.ScanFailsDialog)
+ }
+ } else {
+ store.dispatch(GlobalAction.ScanFailsCounter.Reset)
+ }
+ }
+ }
+ }
+ is GlobalAction.RestoreAppCurrency -> {
+ store.dispatch(GlobalAction.RestoreAppCurrency.Success(
+ preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
+ ))
+ }
+ is GlobalAction.HideWarningMessage -> {
+ store.state.globalState.warningManager?.let {
+ if (it.hideWarning(action.warning)) {
+ if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
+ //TODO: No appropriate warningMessage identification. Make it better later
+ store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
+ }
+
+ store.dispatch(WalletAction.Warnings.Update)
+ store.dispatch(SendAction.Warnings.Update)
+ }
+ }
+ }
+ is GlobalAction.SendEmail -> {
+ store.state.globalState.feedbackManager?.sendEmail(action.feedbackData)
+ }
+ is GlobalAction.OpenChat -> {
+ store.state.globalState.feedbackManager?.openChat(action.feedbackData)
+ }
+ is GlobalAction.UpdateWalletSignedHashes -> {
+ store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures))
+ }
+ is GlobalAction.UpdateFeedbackInfo -> {
+ store.state.globalState.feedbackManager?.infoHolder
+ ?.setWalletsInfo(action.walletManagers)
+ }
+ is GlobalAction.ExchangeManager.Init -> {
+ val config = appState()?.globalState?.configManager?.config
+ ifNotNull(
+ config?.mercuryoWidgetId,
+ config?.mercuryoSecret,
+ config?.moonPayApiKey,
+ config?.moonPayApiSecretKey,
+ ) { mercuryoWidgetId, mercuryoSecret, moonPayKey, moonPaySecretKey ->
+ scope.launch {
+ val buyService = MercuryoService(
+ apiVersion = MercuryoApi.API_VERSION,
+ mercuryoWidgetId = mercuryoWidgetId,
+ secret = mercuryoSecret,
+ )
+ val sellService = MoonPayService(moonPayKey, moonPaySecretKey)
+ val exchangeManager = CurrencyExchangeManager(buyService, sellService)
+ store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
+ store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
+ }
+ }
+ }
+ is GlobalAction.ExchangeManager.Init.Success -> {}
+ is GlobalAction.ExchangeManager.Update -> {
+ val exchangeManager = appState()?.globalState?.exchangeManager.guard {
+ store.dispatchDebugErrorNotification("exchangeManager is not initialized")
+ return
+ }
+ scope.launch { exchangeManager.update() }
+ }
+ is GlobalAction.ScanCard -> {
+ scope.launch {
+ val result = tangemSdkManager.scanProduct(
+ store.state.globalState.analyticsHandlers,
+ currenciesRepository,
+ action.additionalBlockchainsToDerive,
+ action.messageResId
+ )
+ withMainContext {
+ store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
+ when (result) {
+ is CompletionResult.Success -> {
+ tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data)
+ action.onSuccess?.invoke(result.data)
+ }
is CompletionResult.Failure -> {
- if (action.result.error is TangemSdkError.UserCancelled) {
- store.dispatch(GlobalAction.ScanFailsCounter.Increment)
- if (store.state.globalState.scanCardFailsCounter >= 2) {
- store.dispatchDialogShow(AppDialog.ScanFailsDialog)
- }
- } else {
- store.dispatch(GlobalAction.ScanFailsCounter.Reset)
- }
- }
- }
- }
- is GlobalAction.RestoreAppCurrency -> {
- store.dispatch(GlobalAction.RestoreAppCurrency.Success(
- preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
- ))
- }
- is GlobalAction.HideWarningMessage -> {
- store.state.globalState.warningManager?.let {
- if (it.hideWarning(action.warning)) {
- if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) {
- //TODO: No appropriate warningMessage identification. Make it better later
- store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
- }
-
- store.dispatch(WalletAction.Warnings.Update)
- store.dispatch(SendAction.Warnings.Update)
- }
- }
- }
-
- is GlobalAction.SendFeedback -> {
- store.state.globalState.feedbackManager?.send(action.emailData)
- }
- is GlobalAction.UpdateWalletSignedHashes -> {
- store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures))
- }
- is GlobalAction.UpdateFeedbackInfo -> {
- store.state.globalState.feedbackManager?.infoHolder
- ?.setWalletsInfo(action.walletManagers)
- }
- is GlobalAction.InitCurrencyExchangeManager -> {
- val config = appState()?.globalState?.configManager?.config
- ifNotNull(
- config?.onramperApiKey,
- config?.moonPayApiKey,
- config?.moonPayApiSecretKey,
- ) { onramperKey, moonPayKey, moonPaySecretKey ->
- scope.launch {
- val onramper = OnramperService(onramperKey)
- val moonPay = MoonPayService(moonPayKey, moonPaySecretKey)
- val exchangeManager = CurrencyExchangeManager(onramper, moonPay)
- exchangeManager.getStatus()
- store.dispatchOnMain(GlobalAction.InitCurrencyExchangeManager.Success(exchangeManager))
- }
- }
- }
- is GlobalAction.ScanCard -> {
- scope.launch {
- val result = tangemSdkManager.scanProduct(
- store.state.globalState.analyticsHandlers,
- currenciesRepository,
- action.additionalBlockchainsToDerive,
- action.messageResId
- )
- withMainContext {
- store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
- when (result) {
- is CompletionResult.Success -> {
- tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data)
- action.onSuccess?.invoke(result.data)
- }
- is CompletionResult.Failure -> {
- action.onFailure?.invoke(result.error)
- }
- }
+ action.onFailure?.invoke(result.error)
}
}
}
}
- nextDispatch(action)
+ }
+ is GlobalAction.FetchUserCountry -> {
+ scope.launch {
+ val techService = store.state.domainNetworks.tangemTechService
+ when (val result = techService.userCountry()) {
+ is Result.Success -> {
+ store.dispatch(
+ GlobalAction.FetchUserCountry.Success(
+ countryCode = result.data.code.lowercase()
+ )
+ )
+ }
+ is Result.Failure -> {
+ store.dispatch(
+ GlobalAction.FetchUserCountry.Success(
+ countryCode = Locale.getDefault().country.lowercase()
+ )
+ )
+ }
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
index 67e54a6042..449d47ae10 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
@@ -70,12 +70,14 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.HideDialog -> {
globalState.copy(dialog = null)
}
- is GlobalAction.InitCurrencyExchangeManager.Success -> {
- globalState.copy(currencyExchangeManager = action.exchangeManager)
+ is GlobalAction.ExchangeManager.Init.Success -> {
+ globalState.copy(exchangeManager = action.exchangeManager)
}
is GlobalAction.SetIfCardVerifiedOnline ->
globalState.copy(cardVerifiedOnline = action.verified)
-
+ is GlobalAction.FetchUserCountry.Success -> globalState.copy(
+ userCountryCode = action.countryCode
+ )
else -> globalState
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
index 75504f3ef2..62e5217866 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
@@ -3,12 +3,12 @@ package com.tangem.tap.common.redux.global
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.AnalyticsHandler
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.PayIdManager
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
-import com.tangem.tap.features.feedback.FeedbackManager
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import org.rekotlin.StateType
@@ -25,9 +25,10 @@ data class GlobalState(
val appCurrency: FiatCurrency = FiatCurrency.Default,
val scanCardFailsCounter: Int = 0,
val dialog: StateDialog? = null,
- val currencyExchangeManager: CurrencyExchangeManager? = null,
+ val exchangeManager: CurrencyExchangeManager? = null,
val resources: AndroidResources = AndroidResources(),
val analyticsHandlers: AnalyticsHandler? = null,
+ val userCountryCode: String? = null,
) : StateType
diff --git a/app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt b/app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt
new file mode 100644
index 0000000000..aad872b7df
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/zendesk/ZendeskConfig.kt
@@ -0,0 +1,16 @@
+package com.tangem.tap.common.zendesk
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@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 = "zendeskUrl")
+ val url: String,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
index 20d1eda08c..9e22833682 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt
@@ -5,44 +5,25 @@ import com.tangem.TangemSdk
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
-import com.tangem.tap.domain.tasks.SignHashTask
+import com.tangem.common.card.Card
import com.tangem.tap.domain.tasks.SignHashesTask
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class TangemSigner(
+ private val card: Card,
private val tangemSdk: TangemSdk,
private val initialMessage: Message,
private val signerCallback: (TangemSignerResponse) -> Unit,
) : TransactionSigner {
- override suspend fun sign(hash: ByteArray, cardId: String, publicKey: Wallet.PublicKey): CompletionResult {
+ override suspend fun sign(
+ hashes: List,
+ publicKey: Wallet.PublicKey
+ ): CompletionResult> {
return suspendCoroutine { continuation ->
- val command = SignHashTask(hash, publicKey)
- tangemSdk.startSessionWithRunnable(
- runnable = command,
- cardId = cardId,
- initialMessage = initialMessage,
- ) { result ->
- when (result) {
- is CompletionResult.Success -> {
- signerCallback(
- TangemSignerResponse(
- result.data.totalSignedHashes,
- result.data.remainingSignatures
- )
- )
- continuation.resume(CompletionResult.Success(result.data.signature))
- }
- is CompletionResult.Failure ->
- continuation.resume(CompletionResult.Failure(result.error))
- }
- }
- }
- }
+ val cardId = if (card.backupStatus?.isActive == true) null else card.cardId
- override suspend fun sign(hashes: List, cardId: String, publicKey: Wallet.PublicKey): CompletionResult> {
- return suspendCoroutine { continuation ->
val task = SignHashesTask(hashes, publicKey)
tangemSdk.startSessionWithRunnable(
runnable = task,
@@ -65,6 +46,21 @@ class TangemSigner(
}
}
}
+
+ override suspend fun sign(
+ hash: ByteArray,
+ publicKey: Wallet.PublicKey
+ ): CompletionResult {
+ val result = sign(
+ hashes = listOf(hash),
+ publicKey = publicKey
+ )
+
+ return when (result) {
+ is CompletionResult.Success -> CompletionResult.Success(result.data.first())
+ is CompletionResult.Failure -> CompletionResult.Failure(result.error)
+ }
+ }
}
data class TangemSignerResponse(
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
index cb587f277b..795e76fe06 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt
@@ -1,8 +1,8 @@
package com.tangem.tap.domain.configurable.config
import com.tangem.blockchain.common.BlockchainSdkConfig
-import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.shop.shopify.ShopifyShop
+import com.tangem.tap.common.zendesk.ZendeskConfig
import com.tangem.tap.domain.configurable.Loader
/**
@@ -11,15 +11,17 @@ import com.tangem.tap.domain.configurable.Loader
data class Config(
val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de",
val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE",
- val onramperApiKey: String = "pk_test_Ix2aCF3ej_5tcDKkBR7MChIvf5Nb0oPORPQ3Oal5G8I0",
val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C",
+ val mercuryoWidgetId: String = "",
+ val mercuryoSecret: String = "",
val appsFlyerDevKey: String = "",
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
val isSendingToPayIdEnabled: Boolean = true,
val isTopUpEnabled: Boolean = false,
@Deprecated("Not relevant since version 3.23")
val isCreatingTwinCardsAllowed: Boolean = false,
- val shopify: ShopifyShop? = null
+ val shopify: ShopifyShop? = null,
+ val zendesk: ZendeskConfig? = null,
)
class ConfigManager(
@@ -32,11 +34,11 @@ class ConfigManager(
private var defaultConfig = Config()
- fun load(onComplete: VoidCallback? = null) {
- localLoader.load { config ->
- setupFeature(config.features)
- setupKey(config.configValues)
- onComplete?.invoke()
+ fun load(onComplete: ((config: Config) -> Unit)? = null) {
+ localLoader.load { configModel ->
+ setupFeature(configModel.features)
+ setupKey(configModel.configValues)
+ onComplete?.invoke(config)
}
// Uncomment to enable remote config
// remoteLoader.load { config ->
@@ -82,8 +84,9 @@ class ConfigManager(
config = config.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
- onramperApiKey = values.onramperApiKey,
moonPayApiSecretKey = values.moonPayApiSecretKey,
+ mercuryoWidgetId = values.mercuryoWidgetId,
+ mercuryoSecret = values.mercuryoSecret,
blockchainSdkConfig = BlockchainSdkConfig(
blockchairApiKey = values.blockchairApiKey,
blockchairAuthorizationToken = values.blockchairAuthorizationToken,
@@ -92,12 +95,14 @@ class ConfigManager(
),
appsFlyerDevKey = values.appsFlyerDevKey,
shopify = values.shopifyShop,
+ zendesk = values.zendesk,
)
defaultConfig = defaultConfig.copy(
coinMarketCapKey = values.coinMarketCapKey,
moonPayApiKey = values.moonPayApiKey,
- onramperApiKey = values.onramperApiKey,
moonPayApiSecretKey = values.moonPayApiSecretKey,
+ mercuryoWidgetId = values.mercuryoWidgetId,
+ mercuryoSecret = values.mercuryoSecret,
blockchainSdkConfig = BlockchainSdkConfig(
blockchairApiKey = values.blockchairApiKey,
blockchairAuthorizationToken = values.blockchairAuthorizationToken,
@@ -106,6 +111,7 @@ class ConfigManager(
),
appsFlyerDevKey = values.appsFlyerDevKey,
shopify = values.shopifyShop,
+ zendesk = values.zendesk,
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
index 41d1ea2022..fc63ed5e9a 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.domain.configurable.config
import com.tangem.tap.common.shop.shopify.ShopifyShop
+import com.tangem.tap.common.zendesk.ZendeskConfig
/**
[REDACTED_AUTHOR]
@@ -15,15 +16,17 @@ class FeatureModel(
class ConfigValueModel(
val coinMarketCapKey: String,
+ val mercuryoWidgetId: String,
+ val mercuryoSecret: String,
val moonPayApiKey: String,
- val onramperApiKey: String,
val moonPayApiSecretKey: String,
val blockchairApiKey: String?,
val blockchairAuthorizationToken: String?,
val blockcypherTokens: Set?,
val infuraProjectId: String?,
val appsFlyerDevKey: String,
- val shopifyShop: ShopifyShop?
+ val shopifyShop: ShopifyShop?,
+ val zendesk: ZendeskConfig?,
)
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
index ac2d922c0d..10dbcb2cbb 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
@@ -6,7 +6,6 @@ import com.tangem.tap.common.extensions.containsAny
import com.tangem.tap.common.extensions.removeBy
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
-import java.util.*
/**
[REDACTED_AUTHOR]
@@ -188,31 +187,6 @@ class WarningMessagesManager(
WarningMessage.Origin.Local
)
- fun restoreFundsWarning(): WarningMessage = WarningMessage(
- title = "",
- message = "",
- type = WarningMessage.Type.Temporary,
- priority = WarningMessage.Priority.Warning,
- listOf(WarningMessage.Location.MainScreen),
- blockchains = null,
- titleResId = R.string.alert_title,
- messageResId = R.string.alert_funds_restoration_message,
- origin = WarningMessage.Origin.Local,
- buttonTextId = R.string.warning_button_learn_more
- )
-
const val REMAINING_SIGNATURES_WARNING = 10
- private const val RESTORE_FUNDS_GUIDE_URL_RU =
- "https://tangem.com/ru/kak-vosstanovit-tokeny-otpravlennye-ne-na-tot-adres-v-tangem-wallet"
- private const val RESTORE_FUNDS_GUIDE_URL_EN =
- "https://tangem.com/en/how-to-recover-crypto-sent-to-the-wrong-address-in-tangem-wallet"
-
- fun getRestoreFundsGuideUrl(locale: String): String {
- return if (locale == Locale("ru").language) {
- RESTORE_FUNDS_GUIDE_URL_RU
- } else {
- RESTORE_FUNDS_GUIDE_URL_EN
- }
- }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt
deleted file mode 100644
index ca04907f47..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.tangem.tap.domain.extensions
-
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.Blockchain.Arbitrum
-import com.tangem.tap.features.wallet.models.Currency
-import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
-import com.tangem.tap.network.exchangeServices.CurrencyExchangeStatus
-import com.tangem.tap.store
-
-/**
-[REDACTED_AUTHOR]
- */
-fun CurrencyExchangeManager.buyIsAllowed(currency: Currency): Boolean {
- return this.status?.buyIsAllowed(currency) ?: false
-}
-
-fun CurrencyExchangeManager.sellIsAllowed(currency: Currency): Boolean {
- return this.status?.sellIsAllowed(currency) ?: false
-}
-
-fun CurrencyExchangeStatus.buyIsAllowed(currency: Currency): Boolean {
- if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false
- if (currency.blockchain == Arbitrum) return false
- if (!isBuyAllowed) return false
-
- //TODO: temporary, for the 3.32 release, unlock all buy button
- return true
-
- return when (currency) {
- is Currency.Blockchain -> {
- val blockchain = currency.blockchain
- when {
- blockchain.isTestnet() -> blockchain.getTestnetTopUpUrl() != null
- blockchain == Blockchain.Unknown -> false
- else -> availableToBuy.contains(currency.currencySymbol)
- }
- }
- is Currency.Token -> false
- }
-}
-
-fun CurrencyExchangeStatus.sellIsAllowed(currency: Currency): Boolean {
- if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false
- if (!isSellAllowed) return false
-
- return when (currency) {
- is Currency.Blockchain -> {
- val blockchain = currency.blockchain
- when {
- blockchain.isTestnet() -> false
- blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC -> false
- else -> availableToSell.contains(currency.currencySymbol)
- }
- }
- is Currency.Token -> false
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
index e636c4fa07..34b8289884 100644
--- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
+++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt
@@ -32,9 +32,10 @@ fun WalletManagerFactory.makeWalletManagerForApp(
return when {
scanResponse.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> {
makeTwinWalletManager(
- card.cardId,
- wallet.publicKey, scanResponse.secondTwinPublicKey!!.hexToBytes(),
- environmentBlockchain, wallet.curve
+ walletPublicKey = wallet.publicKey,
+ pairPublicKey = scanResponse.secondTwinPublicKey!!.hexToBytes(),
+ blockchain = environmentBlockchain,
+ curve = wallet.curve
)
}
seedKey != null && derivationParams != null -> {
@@ -47,7 +48,6 @@ fun WalletManagerFactory.makeWalletManagerForApp(
?: return null
makeWalletManager(
- cardId = card.cardId,
blockchain = environmentBlockchain,
seedKey = wallet.publicKey,
derivedKey = derivedKey,
@@ -56,7 +56,6 @@ fun WalletManagerFactory.makeWalletManagerForApp(
}
else -> {
makeWalletManager(
- cardId = card.cardId,
blockchain = environmentBlockchain,
walletPublicKey = wallet.publicKey,
curve = wallet.curve
diff --git a/app/src/main/java/com/tangem/tap/domain/statePrinter/WalletStateConverter.kt b/app/src/main/java/com/tangem/tap/domain/statePrinter/WalletStateConverter.kt
index 07ac4886bb..57762aaf51 100644
--- a/app/src/main/java/com/tangem/tap/domain/statePrinter/WalletStateConverter.kt
+++ b/app/src/main/java/com/tangem/tap/domain/statePrinter/WalletStateConverter.kt
@@ -57,7 +57,6 @@ class WalletStateConverter : StringStateConverter {
walletMap["publicKey"] = publicKeyMap
walletMap["amounts"] = amounts
walletMap["addresses"] = wallet.addresses.toString()
- walletMap["cardId"] = wallet.cardId
return walletMap
}
diff --git a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt b/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt
index 032c0b97ca..f05065bc8d 100644
--- a/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt
+++ b/app/src/main/java/com/tangem/tap/domain/termsOfUse/CardTou.kt
@@ -4,16 +4,17 @@ import android.content.res.Resources
import android.net.Uri
import androidx.core.os.ConfigurationCompat
import com.tangem.common.card.Card
-import java.util.*
+import java.util.Locale
/**
[REDACTED_AUTHOR]
*/
class CardTou {
- private val locale: Locale = ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)
+ private val locale: Locale =
+ ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)!!
fun getUrl(card: Card): Uri? {
- val issuerName = card.issuer.name ?: return null
+ val issuerName = card.issuer.name
if (issuerName.lowercase(Locale.getDefault()) != "start2coin") return null
val baseUrl = "https://app.tangem.com/tou/"
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt
index fe8a593787..b366229775 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt
@@ -1,11 +1,13 @@
package com.tangem.tap.domain.walletconnect
import com.github.salomonbrys.kotson.fromJson
+import com.github.salomonbrys.kotson.toMap
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonParser
import com.google.gson.annotations.SerializedName
+import com.tangem.blockchain.common.Blockchain
import com.trustwallet.walletconnect.JSONRPC_VERSION
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
@@ -18,9 +20,11 @@ class EthSignHelper {
.create()
}
- fun tryToParseEthTypedMessage(data: String): WCEthereumSignMessage? {
- val request =
- gson.fromJson(data)
+ fun parseCustomRequest(data: String): CustomJsonRpcRequest {
+ return gson.fromJson(data)
+ }
+
+ fun tryToParseEthTypedMessage(request: CustomJsonRpcRequest): WCEthereumSignMessage? {
return if (request.method == WCMethodExtended.ETH_SIGN_TYPE_DATA_V4) {
WCEthereumSignMessage(
listOf(
@@ -53,12 +57,26 @@ class EthSignHelper {
}
}
-data class CustomJsonRpcRequests(
+data class CustomJsonRpcRequest(
val id: Long,
val jsonrpc: String = JSONRPC_VERSION,
val method: WCMethodExtended?,
val params: JsonArray
-)
+) {
+
+ fun blockchainFromChainId(): Blockchain? {
+ return try {
+ val hex = params[0].asJsonObject.toMap()[CHAIN_ID_KEY]?.asString ?: ""
+ Blockchain.fromChainId(Integer.decode(hex))
+ } catch (exception: Exception) {
+ null
+ }
+ }
+
+ companion object {
+ const val CHAIN_ID_KEY = "chainId"
+ }
+}
enum class WCMethodExtended {
@SerializedName("wc_sessionRequest")
@@ -95,5 +113,10 @@ enum class WCMethodExtended {
GET_ACCOUNTS,
@SerializedName("trust_signTransaction")
- SIGN_TRANSACTION;
+ SIGN_TRANSACTION,
+
+ @SerializedName("wallet_switchEthereumChain")
+ SWITCH_CHAIN,
+
+ ;
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
index dd94266b06..7d36064f1e 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt
@@ -11,14 +11,20 @@ import com.tangem.tap.store
import com.tangem.tap.walletConnectRepository
import com.trustwallet.walletconnect.WCClient
import com.trustwallet.walletconnect.models.WCPeerMeta
-import com.trustwallet.walletconnect.models.binance.*
+import com.trustwallet.walletconnect.models.binance.WCBinanceCancelOrder
+import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
+import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
+import com.trustwallet.walletconnect.models.binance.WCBinanceTxConfirmParam
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
import com.trustwallet.walletconnect.models.session.WCSession
import com.trustwallet.walletconnect.models.session.WCSessionUpdate
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-import okhttp3.*
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.util.*
@@ -71,6 +77,22 @@ class WalletConnectManager {
}
}
+ fun updateBlockchain(session: WalletConnectSession) {
+ sessions[session.session]?.client?.updateSession(
+ accounts = listOfNotNull(session.getAddress()),
+ chainId = session.wallet.blockchain?.getChainId(),
+ approved = true
+ )
+
+
+ val updatedSession = sessions[session.session]?.copy(
+ wallet = session.wallet
+ )
+ if (updatedSession != null) {
+ sessions[session.session] = updatedSession
+ }
+ }
+
private fun setupConnectionTimeoutCheck(session: WCSession) {
scope.launch {
delay(20_000)
@@ -409,23 +431,43 @@ class WalletConnectManager {
Timber.d("Custom Request")
Timber.d(data)
- val message = EthSignHelper.tryToParseEthTypedMessage(data)
- if (message != null) {
- Timber.d("onEthSign_v4: $message")
- store.state.globalState.analyticsHandlers?.logWcEvent(
- Analytics.WcAnalyticsEvent.Action(
- Analytics.WcAction.PersonalSign
+ val request = EthSignHelper.parseCustomRequest(data)
+
+ when (request.method) {
+ WCMethodExtended.ETH_SIGN_TYPE_DATA_V4 -> handleTypedDataV4(request, client, id)
+ WCMethodExtended.SWITCH_CHAIN -> {
+ val blockchain = request.blockchainFromChainId()
+ Timber.d("WC switch chainID\nNew Blockchain: $blockchain")
+ val session = sessions[client.session]?.toWalletConnectSession()
+ if (session != null) {
+ store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session))
+ }
+ }
+ else -> {
+ Timber.d("WC: unrecognized custom request")
+ }
+ }
+ }
+
+ }
+
+ private fun handleTypedDataV4(request: CustomJsonRpcRequest, client: WCClient, id: Long) {
+ val message = EthSignHelper.tryToParseEthTypedMessage(request)
+ if (message != null) {
+ Timber.d("onEthSign_v4: $message")
+ store.state.globalState.analyticsHandlers?.logWcEvent(
+ Analytics.WcAnalyticsEvent.Action(
+ Analytics.WcAction.PersonalSign
+ )
+ )
+ sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
+ store.dispatchOnMain(
+ WalletConnectAction.HandlePersonalSignRequest(
+ message,
+ sessionData,
+ id
)
)
- sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
- store.dispatchOnMain(
- WalletConnectAction.HandlePersonalSignRequest(
- message,
- sessionData,
- id
- )
- )
- }
}
}
}
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 1d0dc4a9e0..a3b0d31a89 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
@@ -5,8 +5,17 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak
-import com.tangem.blockchain.common.*
-import com.tangem.blockchain.extensions.*
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.AmountType
+import com.tangem.blockchain.common.CommonSigner
+import com.tangem.blockchain.common.TransactionData
+import com.tangem.blockchain.common.TransactionSender
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.extensions.Result
+import com.tangem.blockchain.extensions.SimpleResult
+import com.tangem.blockchain.extensions.hexToBigDecimal
+import com.tangem.blockchain.extensions.isAscii
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
@@ -17,7 +26,11 @@ import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
-import com.tangem.tap.features.details.redux.walletconnect.*
+import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
+import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
+import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData
+import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData
+import com.tangem.tap.features.details.redux.walletconnect.WcTransactionType
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.tangem.tap.store
@@ -25,8 +38,8 @@ import com.tangem.tap.tangemSdk
import com.tangem.tap.tangemSdkManager
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
-import timber.log.Timber
import java.math.BigDecimal
+import timber.log.Timber
class WalletConnectSdkHelper {
@@ -108,9 +121,8 @@ class WalletConnectSdkHelper {
)
val blockchain = session.wallet.getBlockchainForSession()
return factory.makeWalletManager(
- session.wallet.cardId,
- blockchain,
- publicKey
+ blockchain = blockchain,
+ publicKey = publicKey
)
}
@@ -124,7 +136,7 @@ class WalletConnectSdkHelper {
private suspend fun sendTransaction(data: WcTransactionData): String? {
val result = (data.walletManager as TransactionSender).send(
transactionData = data.transaction,
- signer = Signer(tangemSdk)
+ signer = CommonSigner(tangemSdk)
)
return when (result) {
SimpleResult.Success -> {
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt
new file mode 100644
index 0000000000..7c978b7b6f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt
@@ -0,0 +1,95 @@
+package com.tangem.tap.domain.walletconnect
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
+import com.tangem.blockchain.common.WalletManagerFactory
+import com.tangem.common.card.EllipticCurve
+import com.tangem.domain.common.ScanResponse
+import com.tangem.domain.common.TapWorkarounds.isTestCard
+import com.tangem.tap.domain.extensions.getPrimaryCurve
+import com.tangem.tap.domain.extensions.makeWalletManagerForApp
+import com.tangem.tap.domain.tokens.CurrenciesRepository
+import com.tangem.tap.domain.tokens.models.BlockchainNetwork
+import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
+import com.tangem.tap.features.wallet.redux.WalletState
+
+class WcWalletManagerFactory(
+ private val factory: WalletManagerFactory,
+ private val currenciesRepository: CurrenciesRepository,
+ ) {
+
+ suspend fun getWalletManager(
+ wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState
+ ): WalletManager? {
+ val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
+ Blockchain.EthereumTestnet
+ } else {
+ blockchain
+ }
+
+ val blockchainNetwork = BlockchainNetwork(
+ blockchain = blockchainToMake,
+ derivationPath = wallet.derivationPath?.rawPath,
+ tokens = emptyList()
+ )
+
+ return if (walletState.cardId == wallet.cardId) {
+ walletState.getWalletManager(blockchainNetwork)
+ } else {
+ val blockchainNetworkWithTokens = currenciesRepository
+ .loadSavedCurrencies(
+ cardId = wallet.cardId,
+ isHdWalletSupported = wallet.derivationPath != null
+ ).firstOrNull { it == blockchainNetwork }
+
+ if (blockchainNetworkWithTokens != null) {
+ factory.makeWalletManager(
+ cardId = wallet.cardId,
+ blockchain = blockchainToMake,
+ publicKey = Wallet.PublicKey(
+ wallet.walletPublicKey!!,
+ wallet.derivedPublicKey,
+ wallet.derivationPath
+ ),
+ tokens = blockchainNetworkWithTokens.tokens,
+ curve = blockchainToMake.getPrimaryCurve() ?: EllipticCurve.Secp256k1
+ )
+ } else {
+ null
+ }
+ }
+ }
+
+ suspend fun getWalletManager(
+ scanResponse: ScanResponse, blockchain: Blockchain, walletState: WalletState
+ ): WalletManager? {
+ val card = scanResponse.card
+ val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
+ Blockchain.EthereumTestnet
+ } else {
+ blockchain
+ }
+
+ val blockchainNetwork = BlockchainNetwork(
+ blockchain = blockchainToMake,
+ card = card
+ )
+
+ return if (walletState.cardId == card.cardId) {
+ walletState.getWalletManager(blockchainNetwork)
+ } else {
+ if (currenciesRepository
+ .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
+ .contains(blockchainNetwork)
+ ) {
+ factory.makeWalletManagerForApp(
+ scanResponse = scanResponse,
+ blockchainNetwork = blockchainNetwork
+ )
+ } else {
+ null
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
index 7fe717e1a5..f7ccf6156a 100644
--- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
+++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt
@@ -109,7 +109,7 @@ class DemoConfig {
testDemoCardIds).distinct()
}
- private val releaseDemoCardIds = mutableListOf(
+ private val releaseDemoCardIds = mutableListOf(
// Tangem Wallet:
"AC01000000041100",
"AC01000000042462",
@@ -284,7 +284,10 @@ class DemoTransactionSender(
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val dataToSign = randomString(32).toByteArray()
- val signerResponse = signer.sign(dataToSign, walletManager.wallet.cardId, walletManager.wallet.publicKey)
+ val signerResponse = signer.sign(
+ hash = dataToSign,
+ publicKey = walletManager.wallet.publicKey
+ )
return when (signerResponse) {
is CompletionResult.Success -> SimpleResult.Failure(Exception(ID))
is CompletionResult.Failure -> SimpleResult.fromTangemSdkError(signerResponse.error)
diff --git a/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt b/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
index 99ecfbc227..66d09bdbf0 100644
--- a/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
+++ b/app/src/main/java/com/tangem/tap/features/demo/Extentions.kt
@@ -1,10 +1,10 @@
package com.tangem.tap.features.demo
-import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.card.Card
import com.tangem.domain.common.ScanResponse
/**
[REDACTED_AUTHOR]
*/
fun ScanResponse.isDemoCard(): Boolean = DemoHelper.isDemoCardId(card.cardId)
-fun WalletManager.isDemoWallet(): Boolean = DemoHelper.isDemoCardId(wallet.cardId)
\ No newline at end of file
+fun Card.isDemoCard(): Boolean = DemoHelper.isDemoCardId(cardId)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
index a180d20d4a..471ccaa44c 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.features.details.redux.walletconnect
import android.app.Activity
+import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.wallet.R
@@ -47,6 +48,16 @@ sealed class WalletConnectAction : Action {
data class Success(val session: WalletConnectSession) : WalletConnectAction()
}
+ data class SwitchBlockchain(
+ val blockchain: Blockchain?,
+ val session: WalletConnectSession
+ ) : WalletConnectAction()
+
+ data class UpdateBlockchain(
+ val updatedSession: WalletConnectSession
+ ) : WalletConnectAction()
+
+
data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction()
data class SetSessionsRestored(val sessions: List) :
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 25c5d373b4..7f7a88748a 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
@@ -1,10 +1,8 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.Blockchain
-import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.guard
import com.tangem.domain.common.ScanResponse
-import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.getFromClipboard
@@ -14,11 +12,10 @@ import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
-import com.tangem.tap.domain.extensions.makeWalletManagerForApp
-import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.domain.walletconnect.BnbHelper
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
+import com.tangem.tap.domain.walletconnect.WcWalletManagerFactory
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.scope
import com.tangem.tap.store
@@ -159,6 +156,45 @@ class WalletConnectMiddleware {
action.id, action.data, action.sessionData
)
}
+ is WalletConnectAction.SwitchBlockchain -> {
+ val blockchain = action.blockchain.guard {
+ store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
+ return
+ }
+
+ val factory = WcWalletManagerFactory(
+ factory = store.state.globalState.tapWalletManager.walletManagerFactory,
+ currenciesRepository = currenciesRepository
+ )
+ val walletState = store.state.walletState
+ scope.launch {
+ val walletManager = factory.getWalletManager(
+ wallet = action.session.wallet,
+ blockchain = blockchain,
+ walletState = walletState
+ ).guard {
+ store.dispatchOnMain(
+ GlobalAction.ShowDialog(
+ WalletConnectDialog.AddNetwork(blockchain.fullName)
+ )
+ )
+ return@launch
+ }
+
+ val updatedWallet = action.session.wallet.copy(
+ walletPublicKey = walletManager.wallet.publicKey.seedKey,
+ derivedPublicKey = walletManager.wallet.publicKey.derivedKey,
+ derivationPath = walletManager.wallet.publicKey.derivationPath,
+ blockchain = action.blockchain
+
+ )
+ val updatedSession = action.session.copy(wallet = updatedWallet)
+ store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession))
+ }
+ }
+ is WalletConnectAction.UpdateBlockchain -> {
+ walletConnectManager.updateBlockchain(action.updatedSession)
+ }
}
}
@@ -189,8 +225,13 @@ class WalletConnectMiddleware {
return
}
+ val factory = WcWalletManagerFactory(
+ factory = store.state.globalState.tapWalletManager.walletManagerFactory,
+ currenciesRepository = currenciesRepository
+ )
+ val walletState = store.state.walletState
scope.launch {
- val walletManager = getWalletManager(scanResponse, blockchain).guard {
+ val walletManager = factory.getWalletManager(scanResponse, blockchain, walletState).guard {
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session))
store.dispatchOnMain(
GlobalAction.ShowDialog(
@@ -232,37 +273,4 @@ class WalletConnectMiddleware {
}
}
-
- private suspend fun getWalletManager(
- scanResponse: ScanResponse, blockchain: Blockchain
- ): WalletManager? {
- val card = scanResponse.card
- val factory = store.state.globalState.tapWalletManager.walletManagerFactory
- val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
- Blockchain.EthereumTestnet
- } else {
- blockchain
- }
-
- val blockchainNetwork = BlockchainNetwork(
- blockchain = blockchainToMake,
- card = card
- )
-
- return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
- store.state.walletState.getWalletManager(blockchainNetwork)
- } else {
- if (currenciesRepository
- .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
- .contains(blockchainNetwork)
- ) {
- factory.makeWalletManagerForApp(
- scanResponse = scanResponse,
- blockchainNetwork = blockchainNetwork
- )
- } else {
- null
- }
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt
index 216367e9ee..191fa7b170 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt
@@ -34,6 +34,11 @@ class WalletConnectReducer {
is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false)
is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false)
is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false)
+ is WalletConnectAction.UpdateBlockchain -> state.copy(
+ sessions = state.sessions
+ .filterNot { it.peerId == action.updatedSession.peerId }
+ + action.updatedSession
+ )
else -> state
}
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 4f5861c67b..fbd95c6393 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
@@ -82,6 +82,7 @@ data class WalletForSession(
sealed class WalletConnectDialog : StateDialog {
data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog()
object UnsupportedCard : WalletConnectDialog()
+ object UnsupportedNetwork : WalletConnectDialog()
data class AddNetwork(val network: String) : WalletConnectDialog()
object OpeningSessionRejected : WalletConnectDialog()
object SessionTimeout : WalletConnectDialog()
diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
index 2789b0069d..71b6baa710 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
@@ -9,6 +9,8 @@ import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.domain.common.getTwinCardIdForUser
import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.feedback.FeedbackEmail
+import com.tangem.tap.common.feedback.SupportInfo
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
@@ -16,7 +18,6 @@ import com.tangem.tap.domain.extensions.isMultiwalletAllowed
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.SecurityOption
-import com.tangem.tap.features.feedback.FeedbackEmail
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -124,7 +125,7 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber Unit)? = null) {
- if (!this::activity.isInitialized) return
-
- emailData.prepare(infoHolder)
- val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null
- activity.sendEmail(
- email = getSupportEmail(),
- subject = activity.getString(emailData.subjectResId),
- message = emailData.joinTogether(activity, infoHolder),
- file = fileLog,
- onFail = onFail
- )
- }
-
- private fun getSupportEmail(): String {
- return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) {
- S2C_SUPPORT_EMAIL
- } else {
- DEFAULT_SUPPORT_EMAIL
- }
- }
-
- private fun createLogFile(): File? {
- return try {
- val file = File(activity.filesDir, "logs.txt")
- file.delete()
- file.createNewFile()
-
- val stringWriter = StringWriter()
- logCollector.getLogs().forEach { stringWriter.append(it) }
- val fileWriter = FileWriter(file)
- fileWriter.write(stringWriter.toString())
- fileWriter.close()
- logCollector.clearLogs()
- file
- } catch (ex: Exception) {
- Timber.e(ex, "Can't create a file for email attachment")
- null
- }
- }
-
- companion object {
- const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
- const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
- }
-}
-
-class TangemLogCollector(
- private val levels: List,
- private val messageFormatter: LogFormat
-) : TangemSdkLogger {
-
- private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS")
- private val logs = mutableListOf()
- private val mutex = Object()
-
- override fun log(message: () -> String, level: Log.Level) {
- if (!levels.contains(level)) return
-
- synchronized(mutex) {
- val formattedMessage = messageFormatter.format(message, level)
- val logMessage = "${dateFormatter.format(Date())}: $formattedMessage"
- logs.add("$logMessage\n")
- }
- }
-
- fun getLogs(): List = synchronized(mutex) { logs.toList() }
-
- fun clearLogs() = synchronized(mutex) { logs.clear() }
-}
-
-class AdditionalEmailInfo {
- class EmailWalletInfo(
- var blockchain: Blockchain = Blockchain.Unknown,
- var address: String = "",
- var explorerLink: String = "",
- var host: String = "",
- var derivationPath: String = "",
- )
-
- var appVersion: String = ""
-
- // card
- var cardId: String = ""
- var cardFirmwareVersion: String = ""
- var cardIssuer: String = ""
- var cardBlockchain: String = ""
-
- // wallets
- internal val walletsInfo = mutableListOf()
- internal val tokens = mutableMapOf>()
- internal var onSendErrorWalletInfo: EmailWalletInfo? = null
- var signedHashesCount: String = ""
-
- // device
- var phoneModel: String = Build.MODEL
- var osVersion: String = Build.VERSION.SDK_INT.toString()
-
- // send error
- var destinationAddress: String = ""
- var amount: String = ""
- var fee: String = ""
- var token: String = ""
-
- fun setAppVersion(context: Context) {
- try {
- val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
- appVersion = pInfo.versionName
- } catch (e: PackageManager.NameNotFoundException) {
- e.printStackTrace()
- }
- }
-
- fun setCardInfo(data: ScanResponse) {
- cardId = data.card.cardId
- cardBlockchain = data.walletData?.blockchain ?: ""
- cardFirmwareVersion = data.card.firmwareVersion.stringValue
- cardIssuer = data.card.issuer.name
- signedHashesCount = data.card.wallets
- .joinToString("; ") { "${it.curve.curve} - ${it.totalSignedHashes}" }
- }
-
- fun setWalletsInfo(walletManagers: List) {
- walletsInfo.clear()
- tokens.clear()
- walletManagers.forEach { manager ->
- walletsInfo.add(
- EmailWalletInfo(
- blockchain = manager.wallet.blockchain,
- address = getAddress(manager.wallet),
- explorerLink = getExploreUri(manager.wallet),
- host = manager.currentHost,
- derivationPath = manager.wallet.publicKey.derivationPath?.rawPath ?: ""
- )
- )
- if (manager.cardTokens.isNotEmpty()) {
- tokens[manager.wallet.blockchain] = manager.cardTokens
- }
- }
- }
-
- fun updateOnSendError(wallet: Wallet, host: String, amountToSend: Amount, feeAmount: Amount, destinationAddress: String) {
- onSendErrorWalletInfo = EmailWalletInfo(
- blockchain = wallet.blockchain,
- address = getAddress(wallet),
- explorerLink = getExploreUri(wallet),
- host = host,
- derivationPath = wallet.publicKey.derivationPath?.rawPath ?: ""
- )
-
- this.destinationAddress = destinationAddress
- amount = amountToSend.value?.stripZeroPlainString() ?: "0"
- fee = feeAmount.value?.stripZeroPlainString() ?: "0"
- token = if (amountToSend.type is AmountType.Token) amountToSend.currencySymbol else ""
- }
-
- private fun getAddress(wallet: Wallet): String {
- return if (wallet.addresses.size == 1) {
- wallet.address
- } else {
- val addresses = wallet.addresses.joinToString(", ") {
- "${it.type.javaClass.simpleName} - ${it.value}"
- }
- "Multiple address: $addresses"
- }
- }
-
- private fun getExploreUri(wallet: Wallet): String {
- return if (wallet.addresses.size == 1) {
- wallet.getExploreUrl(wallet.address)
- } else {
- val links = wallet.addresses.joinToString(", ") {
- "${it.type.javaClass.simpleName} - ${wallet.getExploreUrl(it.value)}"
- }
- "Multiple explorers links: $links"
- }
- }
-}
-
-interface EmailData {
- val subjectResId: Int
- val mainMessageResId: Int
-
- fun getDataCollectionMessageResId(): Int = R.string.feedback_data_collection_message
-
- fun prepare(infoHolder: AdditionalEmailInfo) {}
-
- fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String
-
- fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String {
- return StringBuilder().apply {
- append(context.getString(mainMessageResId))
- appendLine(3)
- append(context.getString(getDataCollectionMessageResId()))
- appendLine()
- append(createOptionalMessage(infoHolder))
- }.toString()
- }
-}
-
-class RateCanBeBetterEmail : EmailData {
- override val subjectResId: Int = R.string.feedback_subject_rate_negative
- override val mainMessageResId: Int = R.string.feedback_preface_rate_negative
-
- override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder)
- .appendCardInfo()
- .appendLine()
- .appendPhoneInfo()
- .build()
-}
-
-class ScanFailsEmail : EmailData {
-
- override val subjectResId: Int = R.string.feedback_subject_scan_failed
- override val mainMessageResId: Int = R.string.feedback_preface_scan_failed
-
- override fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String = StringBuilder().apply {
- append(context.getString(mainMessageResId))
- appendLine(4)
- append(createOptionalMessage(infoHolder))
- }.toString()
-
- override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder)
- .appendPhoneInfo()
- .build()
-}
-
-class SendTransactionFailedEmail(
- val error: String
-) : EmailData {
-
- override val subjectResId: Int = R.string.feedback_subject_tx_failed
- override val mainMessageResId: Int = R.string.feedback_preface_tx_failed
-
- override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder)
- .appendCardInfo()
- .appendDelimiter()
- .appendTxFailedBlockchainInfo(error)
- .appendLine()
- .appendPhoneInfo()
- .build()
-}
-
-class FeedbackEmail : EmailData {
- override val subjectResId: Int
- get() = if (isS2CCard) s2cSubject else tangemSubject
- override val mainMessageResId: Int
- get() = if (isS2CCard) s2cMainMessage else tangemMainMessage
-
- private val tangemSubject: Int = R.string.feedback_subject_support_tangem
- private val tangemMainMessage: Int = R.string.feedback_preface_support
-
- private val s2cSubject: Int = R.string.feedback_subject_support
- private val s2cMainMessage: Int = R.string.feedback_preface_support
-
- private var isS2CCard = false
-
- override fun prepare(infoHolder: AdditionalEmailInfo) {
- isS2CCard = TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)
- }
-
- override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder)
- .appendCardInfo()
- .appendWalletsInfo()
- .appendLine()
- .appendPhoneInfo()
- .build()
-}
-
-
-class EmailDataBuilder(
- private val infoHolder: AdditionalEmailInfo
-) {
- val builder = StringBuilder()
-
- fun appendDelimiter(): EmailDataBuilder {
- builder.appendDelimiter()
- return this
- }
-
- fun appendLine(count: Int = 1): EmailDataBuilder {
- builder.appendLine(count)
- return this
- }
-
- fun appendCardInfo(): EmailDataBuilder {
- builder.appendKeyValue("Card ID", infoHolder.cardId)
- builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
- builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain)
- builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
- return this
- }
-
- fun appendWalletsInfo(): EmailDataBuilder {
- infoHolder.walletsInfo.forEach {
- builder.appendDelimiter()
- builder.appendKeyValue("Blockchain", it.blockchain.fullName)
- builder.appendKeyValue("Host", it.host)
- builder.appendKeyValue("Wallet address", it.address)
- builder.appendKeyValue("Derivation path", it.derivationPath)
- builder.appendKeyValue("Explorer link", it.explorerLink)
-
- infoHolder.tokens[it.blockchain]?.let { tokens ->
- builder.append("Tokens:")
- appendLine()
- tokens.forEach { token ->
- builder.appendKeyValue("Name", token.name)
- builder.appendKeyValue("ID", token.id ?: "[custom token]")
- builder.appendKeyValue("Contract address", token.contractAddress)
- }
- }
- }
- return this
- }
-
- fun appendTxFailedBlockchainInfo(error: String): EmailDataBuilder {
- val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo()
- builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
- builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
- builder.appendKeyValue("Host", walletInfo.host)
- builder.appendKeyValue("Token", infoHolder.token)
- builder.appendKeyValue("Error", error)
- builder.appendDelimiter()
- builder.appendKeyValue("Source address", walletInfo.address)
- builder.appendKeyValue("Destination address", infoHolder.destinationAddress)
- builder.appendKeyValue("Amount", infoHolder.amount)
- builder.appendKeyValue("Fee", infoHolder.fee)
- return this
- }
-
- fun appendPhoneInfo(): EmailDataBuilder {
- builder.appendKeyValue("Phone model", infoHolder.phoneModel)
- builder.appendKeyValue("OS version", infoHolder.osVersion)
- builder.appendKeyValue("App version", infoHolder.appVersion)
- return this
- }
-
- fun build(): String = builder.toString()
-}
-
-private fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder {
- return if (value.isNotBlank()) this.append("$key: $value\n") else this
-}
-
-private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n")
-
-private fun StringBuilder.appendLine(count: Int = 1): StringBuilder {
- return append(List(count) { "\n" }.joinToString(separator = ""))
-}
\ 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 2b26faeeba..f1b3fa3f55 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,10 +1,14 @@
package com.tangem.tap.features.home
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.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import com.google.accompanist.appcompattheme.AppCompatTheme
import com.tangem.tap.common.redux.navigation.AppScreen
@@ -15,46 +19,36 @@ import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.store
-import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
-class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber {
+class HomeFragment : Fragment(), StoreSubscriber {
- var homeState: MutableState = mutableStateOf(store.state.homeState)
+ private var homeState: MutableState = mutableStateOf(store.state.homeState)
+ private var composeView: ComposeView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
store.dispatch(HomeAction.Init)
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View? {
+ val context = container?.context ?: return null
store.dispatch(BackupAction.CheckForUnfinishedBackup)
-
-
- getView()?.findViewById(R.id.cv_stories)?.setContent {
- AppCompatTheme {
- StoriesScreen(
- homeState,
- onScanButtonClick = { store.dispatch(HomeAction.ReadCard) },
- onShopButtonClick = { store.dispatch(HomeAction.GoToShop(getRegionProvider())) },
- onSearchTokensClick = {
- store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
- store.dispatch(TokensAction.AllowToAddTokens(false))
- store.dispatch(TokensAction.LoadCurrencies())
- }
- )
+ composeView = ComposeView(context).apply {
+ setContent {
+ AppCompatTheme {
+ ScreenContent()
+ }
}
}
- }
- private fun getRegionProvider(): RegionProvider = RegionService(
- listOf(
-// TelephonyManagerRegionProvider(requireContext()),
- LocaleRegionProvider()
- )
- )
+ return composeView
+ }
override fun onStart() {
super.onStart()
@@ -70,6 +64,11 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber = { dispatch, state ->
+private val homeMiddleware: Middleware = { _, _ ->
{ next ->
{ action ->
when (action) {
is HomeAction.Init -> {
store.dispatch(GlobalAction.RestoreAppCurrency)
- store.dispatch(GlobalAction.InitCurrencyExchangeManager)
+ store.dispatch(GlobalAction.ExchangeManager.Init)
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
+ store.dispatch(GlobalAction.FetchUserCountry)
}
is HomeAction.ShouldScanCardOnResume -> {
if (action.shouldScanCard) {
@@ -55,8 +57,9 @@ private val homeMiddleware: Middleware = { dispatch, state ->
// store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomTokens))
}
is HomeAction.GoToShop -> {
- when (action.regionProvider.getRegion()?.toLowerCase()) {
- "ru" -> store.dispatchOpenUrl(BUY_WALLET_URL)
+ when (action.userCountryCode) {
+ RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE ->
+ store.dispatchOpenUrl(BUY_WALLET_URL)
else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
}
store.state.globalState.analyticsHandlers?.triggerEvent(
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 26d719d0c6..8284efcd3d 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
@@ -83,7 +83,7 @@ class OnboardingManager(
data class OnboardingWalletBalance(
val value: BigDecimal = BigDecimal.ZERO,
- val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown, null),
+ val currency: Currency = Currency.Blockchain(Blockchain.Unknown, null),
val hasIncomingTransaction: Boolean = false,
val state: ProgressState,
val error: TapError? = null,
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
index 77b12445f7..a9ebb0e757 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt
@@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.note.redux
import com.tangem.blockchain.common.WalletManager
import com.tangem.tap.domain.TapError
-import com.tangem.tap.domain.extensions.buyIsAllowed
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
import com.tangem.tap.store
import org.rekotlin.StateType
@@ -26,8 +25,8 @@ data class OnboardingNoteState(
val progress: Int
get() = steps.indexOf(currentStep)
- val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property ->
- store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false
+ val isBuyAllowed: Boolean by ReadOnlyProperty { _, _ ->
+ store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
index fc24dbd461..19cc8ad307 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt
@@ -3,7 +3,6 @@ package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.blockchain.common.WalletManager
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.domain.TapError
-import com.tangem.tap.domain.extensions.buyIsAllowed
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
import com.tangem.tap.store
@@ -56,8 +55,8 @@ data class TwinCardsState(
val showAlert: Boolean
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
- val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property ->
- store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false
+ val isBuyAllowed: Boolean by ReadOnlyProperty { _, _ ->
+ store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false
}
}
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 88121813ad..391d65cb96 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
@@ -91,7 +91,12 @@ private fun handleWalletAction(action: Action) {
BlockchainNetwork(Blockchain.Bitcoin, result.data.card),
BlockchainNetwork(Blockchain.Ethereum, result.data.card)
)
- store.dispatchOnMain(WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks))
+ store.dispatchOnMain(
+ WalletAction.MultiWallet.SaveCurrencies(
+ blockchainNetworks = blockchainNetworks,
+ cardId = result.data.card.cardId
+ )
+ )
onboardingManager.activationStarted(updatedResponse.card.cardId)
store.dispatch(OnboardingWalletAction.ProceedBackup)
}
@@ -272,7 +277,11 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
BlockchainNetwork(Blockchain.Bitcoin, result.data),
BlockchainNetwork(Blockchain.Ethereum, result.data)
)
- store.dispatchOnMain(WalletAction.MultiWallet.SaveCurrencies(blockchainNetworks))
+ store.dispatchOnMain(
+ WalletAction.MultiWallet.SaveCurrencies(
+ blockchainNetworks = blockchainNetworks, cardId = result.data.cardId
+ )
+ )
if (backupService.currentState == BackupService.State.Finished) {
store.dispatchOnMain(BackupAction.FinishBackup)
} else {
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 29165f8bad..cd4075a0ca 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
@@ -7,7 +7,7 @@ import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.demo.DemoTransactionSender
-import com.tangem.tap.features.demo.isDemoWallet
+import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.ReceiptAction
@@ -28,6 +28,7 @@ class RequestFeeMiddleware {
fun handle(appState: AppState?, dispatch: DispatchFunction) {
val sendState = appState?.sendState ?: return
val walletManager = sendState.walletManager ?: return
+ val scanResponse = appState.globalState.scanResponse ?: return
if (!SendState.isReadyToRequestFee()) {
dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.ADDRESS_OR_AMOUNT_IS_EMPTY))
@@ -40,7 +41,7 @@ class RequestFeeMiddleware {
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress!!
val destinationAmount = Amount(typedAmount, sendState.amountState.amountToSendCrypto)
- val txSender = if (walletManager.isDemoWallet()) {
+ val txSender = if (scanResponse.isDemoCard()) {
DemoTransactionSender(walletManager)
} else {
walletManager as TransactionSender
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
index 1727c81944..4a1f2df785 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt
@@ -4,7 +4,11 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
-import com.tangem.blockchain.common.*
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.BlockchainSdkError
+import com.tangem.blockchain.common.TransactionError
+import com.tangem.blockchain.common.TransactionSender
+import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
@@ -15,7 +19,11 @@ import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
-import com.tangem.tap.common.extensions.*
+import com.tangem.tap.common.extensions.dispatchDialogShow
+import com.tangem.tap.common.extensions.dispatchErrorNotification
+import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.extensions.safeUpdate
+import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
@@ -26,9 +34,15 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.minimalAmount
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.demo.DemoTransactionSender
-import com.tangem.tap.features.demo.isDemoWallet
-import com.tangem.tap.features.send.redux.*
+import com.tangem.tap.features.demo.isDemoCard
+import com.tangem.tap.features.send.redux.AddressPayIdActionUi
+import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
+import com.tangem.tap.features.send.redux.AmountAction
+import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
+import com.tangem.tap.features.send.redux.PrepareSendScreen
+import com.tangem.tap.features.send.redux.SendAction
+import com.tangem.tap.features.send.redux.SendActionUi
import com.tangem.tap.features.send.redux.states.ButtonState
import com.tangem.tap.features.send.redux.states.ExternalTransactionData
import com.tangem.tap.features.send.redux.states.MainCurrencyType
@@ -38,6 +52,7 @@ import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import com.tangem.wallet.R
+import java.util.EnumSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -45,7 +60,6 @@ import kotlinx.coroutines.withContext
import org.rekotlin.Action
import org.rekotlin.Middleware
import timber.log.Timber
-import java.util.*
/**
[REDACTED_AUTHOR]
@@ -161,7 +175,11 @@ private fun sendTransaction(
tangemSdk.config.linkedTerminal = false
}
- val signer = TangemSigner(tangemSdk, action.messageForSigner) { signResponse ->
+ val signer = TangemSigner(
+ card = card,
+ tangemSdk = tangemSdk,
+ initialMessage = action.messageForSigner
+ ) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.totalSignedHashes,
@@ -171,7 +189,7 @@ private fun sendTransaction(
)
}
val sendResult = try {
- if (walletManager.isDemoWallet()) {
+ if (card.isDemoCard()) {
DemoTransactionSender(walletManager).send(txData, signer)
} else {
(walletManager as TransactionSender).send(txData, signer)
@@ -340,5 +358,4 @@ private fun updateWarnings(dispatch: (Action) -> Unit) {
val warnings = warningsManager.getWarnings(WarningMessage.Location.SendScreen, listOf(blockchain))
dispatch(SendAction.Warnings.Set(warnings))
-}
-
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
index 8f4b106e97..6eccfef665 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt
@@ -2,8 +2,8 @@ package com.tangem.tap.features.send.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
+import com.tangem.tap.common.feedback.SendTransactionFailedEmail
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.features.feedback.SendTransactionFailedEmail
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -19,7 +19,7 @@ class SendTransactionFailsDialog {
setTitle(R.string.alert_failed_to_send_transaction_title)
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, dialog.errorMessage))
setNeutralButton(R.string.alert_button_send_feedback) { _, _ ->
- store.dispatch(GlobalAction.SendFeedback(SendTransactionFailedEmail(dialog.errorMessage)))
+ store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(dialog.errorMessage)))
}
setPositiveButton(R.string.common_no) { _, _ -> }
setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) }
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 59556b5893..89d77cd33f 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt
@@ -1,7 +1,11 @@
package com.tangem.tap.features.wallet.redux
import android.content.Context
-import com.tangem.blockchain.common.*
+import com.tangem.blockchain.common.Amount
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.card.Card
import com.tangem.tap.common.entities.FiatCurrency
@@ -13,8 +17,8 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.wallet.R
-import org.rekotlin.Action
import java.math.BigDecimal
+import org.rekotlin.Action
sealed class WalletAction : Action {
@@ -61,7 +65,9 @@ sealed class WalletAction : Action {
MultiWallet()
data class AddToken(val token: Token, val blockchain: BlockchainNetwork) : MultiWallet()
- data class SaveCurrencies(val blockchainNetworks: List) : MultiWallet()
+ data class SaveCurrencies(
+ val blockchainNetworks: List, val cardId: String? = null
+ ) : MultiWallet()
// object FindTokensInUse : MultiWallet()
// object FindBlockchainsInUse : MultiWallet()
@@ -104,8 +110,6 @@ sealed class WalletAction : Action {
}
class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings()
-
- object RestoreFundsWarningClosed : Warnings()
}
data class LoadFiatRate(
@@ -152,6 +156,7 @@ sealed class WalletAction : Action {
object SignedHashesMultiWalletDialog : DialogAction()
object ChooseTradeActionDialog : DialogAction()
data class ChooseCurrency(val amounts: List?) : DialogAction()
+ object RussianCardholdersWarningDialog : DialogAction()
object Hide : DialogAction()
}
@@ -162,8 +167,10 @@ sealed class WalletAction : Action {
object EmptyWallet : WalletAction()
sealed class TradeCryptoAction : WalletAction() {
- object Buy : TradeCryptoAction()
object Sell : TradeCryptoAction()
+ data class Buy(
+ val checkUserLocation: Boolean = true,
+ ) : TradeCryptoAction()
data class FinishSelling(val transactionId: String) : TradeCryptoAction()
data class SendCrypto(
val currencyId: String,
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
index 557b32f260..21489b82f3 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt
@@ -1,7 +1,6 @@
package com.tangem.tap.features.wallet.redux
import android.graphics.Bitmap
-import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
@@ -10,33 +9,22 @@ import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.tap.common.entities.Button
-import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.toQrCode
-import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.toggleWidget.WidgetState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
-import com.tangem.tap.domain.extensions.buyIsAllowed
-import com.tangem.tap.domain.extensions.sellIsAllowed
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
-import com.tangem.tap.features.wallet.models.Currency
-import com.tangem.tap.features.wallet.models.PendingTransaction
-import com.tangem.tap.features.wallet.models.TotalBalance
-import com.tangem.tap.features.wallet.models.WalletRent
-import com.tangem.tap.features.wallet.models.WalletWarning
-import com.tangem.tap.features.wallet.models.hasPendingTransactions
-import com.tangem.tap.features.wallet.models.hasSendableAmounts
-import com.tangem.tap.features.wallet.models.isSendableAmount
+import com.tangem.tap.features.wallet.models.*
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
import com.tangem.tap.features.wallet.redux.reducers.findProgressState
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.store
+import org.rekotlin.StateType
import java.math.BigDecimal
import kotlin.properties.ReadOnlyProperty
-import org.rekotlin.StateType
data class WalletState(
val state: ProgressState = ProgressState.Done,
@@ -71,7 +59,7 @@ data class WalletState(
val shouldShowDetails: Boolean =
primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
- primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
+ primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain
val blockchains: List
get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain }
@@ -85,6 +73,9 @@ data class WalletState(
val walletManagers: List
get() = wallets.mapNotNull { it.walletManager }
+ val cardId: String?
+ get() = wallets.firstOrNull()?.walletManager?.wallet?.cardId
+
fun getWalletManager(currency: Currency?): WalletManager? {
if (currency?.blockchain == null) return null
return getWalletStore(currency)?.walletManager
@@ -320,16 +311,6 @@ data class WalletState(
}
}
-sealed interface WalletDialog : StateDialog {
- data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog
- object SignedHashesMultiWalletDialog : WalletDialog
- object ChooseTradeActionDialog : WalletDialog
- data class CurrencySelectionDialog(
- val currenciesList: List,
- val currentAppCurrency: FiatCurrency,
- ) : WalletDialog
-}
-
enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error }
enum class ErrorType { NoInternetConnection }
@@ -372,18 +353,21 @@ data class Artwork(
}
data class TradeCryptoState(
- val sellingAllowed: Boolean = false,
- val buyingAllowed: Boolean = false,
+ val isAvailableToSell: () -> Boolean = { false },
+ val isAvailableToBuy: () -> Boolean = { false },
) {
companion object {
fun from(
exchangeManager: CurrencyExchangeManager?,
walletData: WalletData
): TradeCryptoState {
- val status = exchangeManager ?: return walletData.tradeCryptoState
+ val exchanger = exchangeManager ?: return walletData.tradeCryptoState
val currency = walletData.currency
- return TradeCryptoState(status.sellIsAllowed(currency), status.buyIsAllowed(currency))
+ return TradeCryptoState(
+ isAvailableToSell = { exchanger.availableForSell(currency) },
+ isAvailableToBuy = { exchanger.availableForBuy(currency) },
+ )
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
index 04bf56dad7..4030bae09b 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt
@@ -77,9 +77,8 @@ class MultiWalletMiddleware {
)
}
is WalletAction.MultiWallet.SaveCurrencies -> {
- globalState.scanResponse?.card?.cardId?.let {
- currenciesRepository.saveCurrencies(it, action.blockchainNetworks)
- }
+ val cardId = action.cardId ?: globalState.scanResponse?.card?.cardId ?: return
+ currenciesRepository.saveCurrencies(cardId, action.blockchainNetworks)
}
is WalletAction.MultiWallet.TryToRemoveWallet -> {
val currency = action.currency
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt
index 9590452975..064647d863 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
@@ -8,12 +8,13 @@ import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.demo.DemoHelper
+import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
-import com.tangem.tap.network.exchangeServices.buyErc20Tokens
+import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.launch
@@ -24,51 +25,77 @@ class TradeCryptoMiddleware {
if (DemoHelper.tryHandle(state, action)) return
when (action) {
- is WalletAction.TradeCryptoAction.Buy -> startExchange(action)
- is WalletAction.TradeCryptoAction.Sell -> startExchange(action)
+ is WalletAction.TradeCryptoAction.Buy -> proceedBuyAction(state, action)
+ is WalletAction.TradeCryptoAction.Sell -> proceedSellAction()
is WalletAction.TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action)
is WalletAction.TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
}
}
- private fun startExchange(action: WalletAction.TradeCryptoAction) {
- val selectedWalletData = store.state.walletState.getSelectedWalletData()
- val exchangeManager = store.state.globalState.currencyExchangeManager ?: return
- val addresses = selectedWalletData?.walletAddresses ?: return
- if (addresses.list.isEmpty()) return
- val appCurrency = store.state.globalState.appCurrency
-
- val defaultAddress = addresses.list[0].address
- val currency = selectedWalletData.currency
- val currencySymbol = selectedWalletData.currency.currencySymbol
-
- val exchangeAction = if (action is WalletAction.TradeCryptoAction.Buy) {
- CurrencyExchangeManager.Action.Buy
- } else {
- CurrencyExchangeManager.Action.Sell
+ private fun proceedBuyAction(
+ state: () -> AppState?,
+ action: WalletAction.TradeCryptoAction.Buy,
+ ) {
+ if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
+ store.dispatchOnMain(
+ WalletAction.DialogAction.RussianCardholdersWarningDialog
+ )
+ return
}
- if (exchangeAction == CurrencyExchangeManager.Action.Buy &&
- currency is Currency.Token && currency.blockchain.isTestnet()
- ) {
+ val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
+ val exchangeManager = store.state.globalState.exchangeManager ?: return
+ val card = store.state.globalState.scanResponse?.card ?: return
+ val appCurrency = store.state.globalState.appCurrency
+
+ val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
+ if (addresses.isEmpty()) return
+
+ val currency = selectedWalletData.currency
+
+ if (currency is Currency.Token && currency.blockchain.isTestnet()) {
val walletManager = store.state.walletState.getWalletManager(currency)
if (walletManager !is EthereumWalletManager) {
- store.dispatchDebugErrorNotification("Testnet tokens available only for the ETH")
+ store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum")
return
}
- scope.launch { exchangeManager.buyErc20Tokens(walletManager, currency.token) }
+ scope.launch {
+ exchangeManager.buyErc20TestnetTokens(
+ card = card,
+ walletManager = walletManager,
+ token = currency.token
+ )
+ }
return
}
exchangeManager.getUrl(
- action = exchangeAction,
+ action = CurrencyExchangeManager.Action.Buy,
blockchain = currency.blockchain,
- cryptoCurrencyName = currencySymbol,
+ cryptoCurrencyName = currency.currencySymbol,
fiatCurrencyName = appCurrency.code,
- walletAddress = defaultAddress
+ walletAddress = addresses[0].address
)?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
+ }
+ private fun proceedSellAction() {
+ val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return
+ val exchangeManager = store.state.globalState.exchangeManager ?: return
+ val appCurrency = store.state.globalState.appCurrency
+
+ val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
+ if (addresses.isEmpty()) return
+
+ val currency = selectedWalletData.currency
+
+ exchangeManager.getUrl(
+ action = CurrencyExchangeManager.Action.Sell,
+ blockchain = currency.blockchain,
+ cryptoCurrencyName = currency.currencySymbol,
+ fiatCurrencyName = appCurrency.code,
+ walletAddress = addresses[0].address
+ )?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) }
}
private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) {
@@ -89,7 +116,7 @@ class TradeCryptoMiddleware {
}
private fun openReceiptUrl(transactionId: String) {
- val exchangeManager = store.state.globalState.currencyExchangeManager ?: return
+ val exchangeManager = store.state.globalState.exchangeManager ?: return
store.dispatchOnMain(NavigationAction.PopBackTo())
exchangeManager.getSellCryptoReceiptUrl(CurrencyExchangeManager.Action.Sell, transactionId)?.let {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt
index 9ae3127317..d53809be9c 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt
@@ -27,10 +27,13 @@ class WalletDialogsMiddleware {
is WalletAction.DialogAction.ChooseCurrency -> {
store.dispatchDialogShow(
WalletDialog.SelectAmountToSendDialog(
- amounts = action.amounts
+ amounts = action.amounts
)
)
}
+ is WalletAction.DialogAction.RussianCardholdersWarningDialog -> {
+ store.dispatchDialogShow(WalletDialog.RussianCardholdersWarningDialog)
+ }
is WalletAction.DialogAction.Hide -> {
store.dispatchDialogHide()
}
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 7a4ac13ea5..7c63b551f9 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt
@@ -1,8 +1,5 @@
package com.tangem.tap.features.wallet.redux.middlewares
-import android.content.Intent
-import android.net.Uri
-import androidx.core.content.ContextCompat
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
@@ -15,12 +12,7 @@ import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.analytics.Analytics
-import com.tangem.tap.common.extensions.copyToClipboard
-import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
-import com.tangem.tap.common.extensions.dispatchOnMain
-import com.tangem.tap.common.extensions.onCardScanned
-import com.tangem.tap.common.extensions.shareText
-import com.tangem.tap.common.extensions.stripZeroPlainString
+import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@@ -233,9 +225,7 @@ class WalletMiddleware {
action.context.shareText(action.address)
}
is WalletAction.ExploreAddress -> {
- val uri = Uri.parse(action.exploreUrl)
- val intent = Intent(Intent.ACTION_VIEW, uri)
- ContextCompat.startActivity(action.context, intent, null)
+ store.dispatchOpenUrl(action.exploreUrl)
}
is WalletAction.Send -> {
val newAction = prepareSendAction(action.amount, store.state.walletState)
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
index 5197928d6e..7aa45f997d 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
@@ -8,7 +8,6 @@ import com.tangem.common.card.Card
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.isTestCard
-import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.isGreaterThan
@@ -26,15 +25,15 @@ import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.wallet.R
+import java.math.BigDecimal
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.math.BigDecimal
class WarningsMiddleware {
fun handle(action: WalletAction.Warnings, globalState: GlobalState?) {
when (action) {
- WalletAction.Warnings.Update -> setWarningMessages()
+ is WalletAction.Warnings.Update -> setWarningMessages()
is WalletAction.Warnings.CheckIfNeeded -> {
showCardWarningsIfNeeded(globalState)
val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow()
@@ -66,9 +65,11 @@ class WarningsMiddleware {
)
}
}
- is WalletAction.Warnings.RestoreFundsWarningClosed -> {
- preferencesStorage.saveRestoreFundsWarningClosed()
- }
+ is WalletAction.Warnings.AppRating,
+ is WalletAction.Warnings.CheckHashesCount,
+ is WalletAction.Warnings.CheckHashesCount.ConfirmHashesCount,
+ is WalletAction.Warnings.CheckHashesCount.NeedToCheckHashesCountOnline,
+ is WalletAction.Warnings.Set -> Unit
}
}
@@ -93,9 +94,6 @@ class WarningsMiddleware {
addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true)
return@let
}
- if (card.useOldStyleDerivation && !preferencesStorage.wasRestoreFundsWarningClosed()) {
- addWarningMessage(warning = WarningMessagesManager.restoreFundsWarning())
- }
showWarningLowRemainingSignaturesIfNeeded(card)
if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) {
@@ -190,6 +188,7 @@ class WarningsMiddleware {
true
)
}
+ null -> Unit
}
}
}
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 a5a54c93f6..68718acd45 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
@@ -30,4 +30,6 @@ sealed interface WalletDialog : StateDialog {
val messageRes: Int = R.string.token_details_unable_hide_alert_message
val titleRes: Int = R.string.token_details_unable_hide_alert_title
}
+
+ object RussianCardholdersWarningDialog : WalletDialog
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
index c3219215ff..482285de76 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt
@@ -38,7 +38,7 @@ class OnWalletLoadedReducer {
val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState
val fiatCurrency = store.state.globalState.appCurrency
- val exchangeManager = store.state.globalState.currencyExchangeManager
+ val exchangeManager = store.state.globalState.exchangeManager
val coinAmountValue = wallet.amounts[AmountType.Coin]?.value
val formattedAmount = coinAmountValue?.toFormattedCurrencyString(
@@ -118,7 +118,7 @@ class OnWalletLoadedReducer {
if (wallet.blockchain != walletState.primaryBlockchain) return walletState
val fiatCurrencyName = store.state.globalState.appCurrency.code
- val exchangeManager = store.state.globalState.currencyExchangeManager
+ val exchangeManager = store.state.globalState.exchangeManager
val token = wallet.getFirstToken()
val tokenData = if (token != null) {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
index 3ab2f769c4..0990cd2e78 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt
@@ -6,17 +6,14 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.common.extensions.mapNotNullValues
import com.tangem.domain.common.TwinCardNumber
import com.tangem.tap.common.entities.FiatCurrency
-import com.tangem.tap.common.extensions.toFiatRateString
-import com.tangem.tap.common.extensions.toFiatString
-import com.tangem.tap.common.extensions.toFiatValue
-import com.tangem.tap.common.extensions.toFormattedCurrencyString
-import com.tangem.tap.common.extensions.toFormattedFiatValue
+import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.getArtworkUrl
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.models.WalletRent
+import com.tangem.tap.features.wallet.redux.*
import com.tangem.tap.features.wallet.redux.AddressData
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.features.wallet.models.Currency
@@ -32,8 +29,8 @@ import com.tangem.tap.features.wallet.redux.WalletStore
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.store
-import java.math.BigDecimal
import org.rekotlin.Action
+import java.math.BigDecimal
class WalletReducer {
companion object {
@@ -49,7 +46,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
if (action !is WalletAction) return state.walletState
- val exchangeManager = store.state.globalState.currencyExchangeManager
+ val exchangeManager = store.state.globalState.exchangeManager
var newState = state.walletState
when (action) {
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
index 0dfa0d5774..73eb9ea393 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt
@@ -92,7 +92,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
private fun setupButtons() = with(binding) {
rowButtons.onBuyClick = {
- store.dispatch(WalletAction.TradeCryptoAction.Buy)
+ store.dispatch(WalletAction.TradeCryptoAction.Buy())
}
rowButtons.onSellClick = {
store.dispatch(WalletAction.TradeCryptoAction.Sell)
@@ -184,8 +184,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
}
rowButtons.updateButtonsVisibility(
- buyAllowed = selectedWallet.tradeCryptoState.buyingAllowed,
- sellAllowed = selectedWallet.tradeCryptoState.sellingAllowed,
+ buyAllowed = selectedWallet.tradeCryptoState.isAvailableToBuy(),
+ sellAllowed = selectedWallet.tradeCryptoState.isAvailableToSell(),
sendAllowed = selectedWallet.mainButton.enabled,
)
}
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 176fa48a14..73f315e29b 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
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import coil.load
+import coil.size.Scale
import com.tangem.tap.MainActivity
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
@@ -166,6 +167,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber {
+ when (warning.titleResId) {
+ R.string.warning_important_security_info -> {
View.OnClickListener {
store.dispatch(WalletAction.DialogAction.SignedHashesMultiWalletDialog)
}
}
- warning.messageResId == R.string.alert_funds_restoration_message -> {
- binding.btnClose.show()
- binding.btnClose.setOnClickListener {
- store.dispatch(GlobalAction.HideWarningMessage(warning))
- store.dispatch(WalletAction.Warnings.RestoreFundsWarningClosed)
- }
- val locale = ConfigurationCompat
- .getLocales(Resources.getSystem().configuration)
- .get(0)
- val url = WarningMessagesManager.getRestoreFundsGuideUrl(locale.language)
- View.OnClickListener {
- store.dispatchOpenUrl(url)
- }
- }
else -> {
View.OnClickListener {
store.dispatch(GlobalAction.HideWarningMessage(warning))
@@ -135,7 +117,7 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE)
store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow)
store.dispatch(GlobalAction.HideWarningMessage(warning))
- store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail()))
+ store.dispatch(GlobalAction.SendEmail(RateCanBeBetterEmail()))
}
binding.btnReallyCool.setOnClickListener {
val activity = binding.root.context.getActivity() ?: return@setOnClickListener
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt
index 1c65315d3a..3fc5905990 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt
@@ -27,12 +27,12 @@ class ChooseTradeActionBottomSheetDialog(context: Context) : BottomSheetDialog(c
}
binding!!.dialogBtnBuy.setOnClickListener {
- store.dispatch(WalletAction.TradeCryptoAction.Buy)
dismiss()
+ store.dispatch(WalletAction.TradeCryptoAction.Buy())
}
binding!!.dialogBtnSell.setOnClickListener {
- store.dispatch(WalletAction.TradeCryptoAction.Sell)
dismiss()
+ store.dispatch(WalletAction.TradeCryptoAction.Sell)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt
new file mode 100644
index 0000000000..a41dce9228
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt
@@ -0,0 +1,44 @@
+package com.tangem.tap.features.wallet.ui.dialogs
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.tangem.tap.common.extensions.dispatchDialogHide
+import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.wallet.redux.WalletAction
+import com.tangem.tap.store
+import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding
+
+class RussianCardholdersWarningBottomSheetDialog(context: Context) : BottomSheetDialog(context) {
+
+ private var binding: DialogRussiansCardholdersWarningBinding? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DialogRussiansCardholdersWarningBinding
+ .inflate(LayoutInflater.from(context))
+ .also { setContentView(it.root) }
+ }
+
+ override fun show() {
+ super.show()
+ setOnDismissListener {
+ binding = null
+ store.dispatchDialogHide()
+ }
+
+ binding?.btnYes?.setOnClickListener {
+ store.dispatch(WalletAction.TradeCryptoAction.Buy(checkUserLocation = false))
+ dismiss()
+ }
+ binding?.btnNo?.setOnClickListener {
+ store.dispatch(NavigationAction.OpenUrl(INSTRUCTION_URL))
+ dismiss()
+ }
+ }
+
+ companion object {
+ private const val INSTRUCTION_URL = "https://tangem.com/howtobuy.html"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
index 496a8ff731..91afcc3819 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ScanFailsDialog.kt
@@ -3,8 +3,8 @@ package com.tangem.tap.features.wallet.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tap.common.extensions.dispatchDialogHide
+import com.tangem.tap.common.feedback.ScanFailsEmail
import com.tangem.tap.common.redux.global.GlobalAction
-import com.tangem.tap.features.feedback.ScanFailsEmail
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -19,7 +19,7 @@ class ScanFailsDialog {
setTitle(context.getString(R.string.common_warning))
setMessage(R.string.alert_troubleshooting_scan_card_title)
setPositiveButton(R.string.alert_button_request_support) { _, _ ->
- store.dispatch(GlobalAction.SendFeedback(ScanFailsEmail()))
+ store.dispatch(GlobalAction.SendEmail(ScanFailsEmail()))
}
setNeutralButton(R.string.alert_troubleshooting_scan_card_ok) { _, _ -> }
setOnDismissListener { store.dispatchDialogHide() }
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt
index 99051bc72d..41076760dc 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt
@@ -1,14 +1,10 @@
package com.tangem.tap.features.wallet.ui.images
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView
import coil.imageLoader
-import coil.load
import coil.request.ImageRequest
-import coil.transform.RoundedCornersTransformation
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.IconsUtil
import com.tangem.blockchain.common.Token
@@ -24,73 +20,120 @@ private const val QCX = "QCX"
private const val VOYR = "VOYRME"
fun loadCurrencyIcon(
- currencyImageView: ImageFilterView,
+ currencyImageView: CurrencyIconView,
currencyTextView: TextView,
token: Token?,
blockchain: Blockchain,
) {
- when {
- token == null -> currencyImageView.loadIcon(
- iconUrl = getIconUrl(blockchain.toNetworkId()),
- placeholderRes = blockchain.getRoundIconRes(),
+ CurrencyIconLoader(
+ currencyImageView = currencyImageView.imageView,
+ currencyTextView = currencyTextView,
+ token = token,
+ blockchain = blockchain
+ )
+ .load()
+}
+
+private class CurrencyIconLoader(
+ private val currencyImageView: ImageFilterView,
+ private val currencyTextView: TextView,
+ private val token: Token?,
+ private val blockchain: Blockchain,
+) {
+ fun load() {
+ when {
+ token == null && blockchain.isTestnet() -> loadTestnetBlockchainIcon()
+ token == null -> loadBlockchainIcon()
+ blockchain.isTestnet() -> loadTestnetTokenIcon()
+ else -> loadTokenIcon()
+ }
+ }
+
+ private fun loadBlockchainIcon() {
+ loadBlockchainIconBase(
onStart = {
- if (blockchain.isTestnet()) {
- currencyImageView.saturation = 0f
- } else {
- currencyImageView.colorFilter = null
- }
+ currencyImageView.colorFilter = null
}
)
- token.symbol == QCX -> currencyImageView.load(R.drawable.ic_qcx)
- token.symbol == VOYR -> currencyImageView.load(R.drawable.ic_voyr)
- else -> currencyImageView.loadIcon(
- iconUrl = getTokenIconUrl(token, blockchain),
+ }
+
+ private fun loadTestnetBlockchainIcon() {
+ loadBlockchainIconBase(
+ onStart = {
+ currencyImageView.saturation = 0f
+ }
+ )
+ }
+
+ private fun loadTokenIcon() {
+ loadTokenIconBase(
+ onStart = {
+ currencyImageView.setColorFilter(it.getColor())
+ },
+ onSuccess = {
+ currencyImageView.colorFilter = null
+ }
+ )
+ }
+
+ private fun loadTestnetTokenIcon() {
+ loadTokenIconBase(
+ onStart = {
+ currencyImageView.saturation = 0f
+ }
+ )
+ }
+
+ private inline fun loadBlockchainIconBase(
+ crossinline onStart: (Blockchain) -> Unit = {},
+ crossinline onSuccess: (Blockchain) -> Unit = {},
+ crossinline onError: (Blockchain) -> Unit = {},
+ ) {
+ currencyImageView.loadIcon(
+ data = getIconUrl(blockchain.toNetworkId()),
+ placeholderRes = blockchain.getRoundIconRes(),
+ onStart = { onStart(blockchain) },
+ onSuccess = { onSuccess(blockchain) },
+ onError = { onError(blockchain) },
+ )
+ }
+
+ private inline fun loadTokenIconBase(
+ crossinline onStart: (Token) -> Unit = {},
+ crossinline onSuccess: (Token) -> Unit = {},
+ crossinline onError: (Token) -> Unit = {},
+ ) {
+ if (token == null) return
+
+ currencyImageView.loadIcon(
+ data = getTokenIcon(token, blockchain),
placeholderRes = R.drawable.shape_circle,
onStart = {
currencyTextView.text = token.symbol.take(1)
currencyTextView.setTextColor(token.getTextColor())
-
- if (blockchain.isTestnet()) {
- currencyImageView.saturation = 0f
- }
- },
- onError = {
- currencyImageView.colorFilter = PorterDuffColorFilter(
- /* color = */
- token.getColor(),
- /* mode = */
- PorterDuff.Mode.SRC_ATOP,
- )
+ onStart(token)
},
onSuccess = {
- if (!blockchain.isTestnet()) {
- currencyImageView.colorFilter = null
- }
- }
+ currencyTextView.text = null
+ onSuccess(token)
+ },
+ onError = { onError(token) },
)
}
}
private inline fun ImageView.loadIcon(
- iconUrl: String?,
+ data: Any?,
placeholderRes: Int,
crossinline onStart: () -> Unit = {},
crossinline onSuccess: () -> Unit = {},
crossinline onError: () -> Unit = {},
) {
ImageRequest.Builder(context)
- .data(iconUrl)
+ .data(data)
.placeholder(placeholderRes)
.error(placeholderRes)
.fallback(placeholderRes)
- .transformations(
- RoundedCornersTransformation(
- topLeft = 32f,
- topRight = 32f,
- bottomLeft = 32f,
- bottomRight = 32f
- )
- )
.listener(
onStart = { onStart() },
onSuccess = { _, _ -> onSuccess() },
@@ -101,9 +144,15 @@ private inline fun ImageView.loadIcon(
.also(context.imageLoader::enqueue)
}
-private fun getTokenIconUrl(token: Token, blockchain: Blockchain): String? {
- return token.id?.let(::getIconUrl)
- ?: token.getCustomIconUrl()
- ?: IconsUtil.getTokenIconUri(blockchain, token)
- ?.toString()
+private fun getTokenIcon(token: Token, blockchain: Blockchain): Any? {
+ return when (token.symbol) {
+ QCX -> R.drawable.ic_qcx
+ VOYR -> R.drawable.ic_voyr
+ else -> {
+ token.id?.let(::getIconUrl)
+ ?: token.getCustomIconUrl()
+ ?: IconsUtil.getTokenIconUri(blockchain, token)
+ ?.toString()
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt
new file mode 100644
index 0000000000..35d89aaa27
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconView.kt
@@ -0,0 +1,30 @@
+package com.tangem.tap.features.wallet.ui.images
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.constraintlayout.utils.widget.ImageFilterView
+import com.google.android.material.card.MaterialCardView
+import com.tangem.tangem_sdk_new.extensions.dpToPx
+import com.tangem.wallet.databinding.ViewCurrencyIconBinding
+
+class CurrencyIconView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : MaterialCardView(context, attrs, defStyleAttr) {
+ private val binding = ViewCurrencyIconBinding.inflate(
+ LayoutInflater.from(context),
+ this
+ )
+
+ val imageView: ImageFilterView
+ get() = binding.iv
+
+ init {
+ elevation = 0f
+ cardElevation = 0f
+ radius = dpToPx(8f)
+ background = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
index 2e0a84785f..dadfaa1bc2 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt
@@ -118,9 +118,8 @@ class SingleWalletView : WalletView {
setupButtonsType(state, binding)
- val btnConfirm = if (state.tradeCryptoState.sellingAllowed ||
- state.tradeCryptoState.buyingAllowed
- ) {
+ val tradeState = state.tradeCryptoState
+ val btnConfirm = if (tradeState.isAvailableToSell() || tradeState.isAvailableToBuy()) {
lButtonsShort.btnConfirm
} else {
lButtonsLong.btnConfirmLong
@@ -148,10 +147,10 @@ class SingleWalletView : WalletView {
}
private fun setupTradeButton(binding: FragmentWalletBinding, tradeCryptoState: TradeCryptoState) {
- val allowedToBuy = tradeCryptoState.buyingAllowed
- val allowedToSell = tradeCryptoState.sellingAllowed
+ val allowedToBuy = tradeCryptoState.isAvailableToBuy()
+ val allowedToSell = tradeCryptoState.isAvailableToSell()
val action = when {
- allowedToBuy && !allowedToSell -> WalletAction.TradeCryptoAction.Buy
+ allowedToBuy && !allowedToSell -> WalletAction.TradeCryptoAction.Buy()
!allowedToBuy && allowedToSell -> WalletAction.TradeCryptoAction.Sell
allowedToBuy && allowedToSell -> WalletAction.DialogAction.ChooseTradeActionDialog
else -> null
@@ -176,9 +175,7 @@ class SingleWalletView : WalletView {
}
private fun setupButtonsType(state: WalletData, binding: FragmentWalletBinding) = with(binding) {
- if (state.tradeCryptoState.sellingAllowed ||
- state.tradeCryptoState.buyingAllowed
- ) {
+ if (state.tradeCryptoState.isAvailableToSell() || state.tradeCryptoState.isAvailableToBuy()) {
lButtonsLong.root.hide()
lButtonsShort.root.show()
} else {
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 7a89edc4b0..8d1085d316 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt
@@ -7,10 +7,12 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.extensions.Result
+import com.tangem.common.card.Card
import com.tangem.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.wallet.models.Currency
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import java.math.BigDecimal
@@ -18,57 +20,20 @@ import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
-interface ExchangeService {
- suspend fun isBuyAllowed(): Boolean
- suspend fun availableToBuy(): List
- suspend fun isSellAllowed(): Boolean
- suspend fun availableToSell(): List
-}
-
-interface ExchangeUrlBuilder {
- fun getUrl(
- action: CurrencyExchangeManager.Action,
- blockchain: Blockchain,
- cryptoCurrencyName: CryptoCurrencyName,
- fiatCurrencyName: String,
- walletAddress: String,
- ): String?
-
- fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String?
-
- companion object {
- const val SCHEME = "https"
- const val URL_SELL = "sell.moonpay.com"
- const val SUCCESS_URL = "tangem://success.tangem.com"
- }
-}
-
class CurrencyExchangeManager(
- private val onramperService: ExchangeService,
- private val moonPayService: ExchangeService,
+ private val buyService: ExchangeService,
+ private val sellService: ExchangeService,
) : ExchangeService, ExchangeUrlBuilder {
- var status: CurrencyExchangeStatus? = null
- private set
-
- suspend fun getStatus(): CurrencyExchangeStatus {
- val isBuyAllowed = isBuyAllowed()
- val isSellAllowed = isSellAllowed()
- val availableToBuy = availableToBuy()
- val availableToSell = availableToSell()
- status = CurrencyExchangeStatus(
- isBuyAllowed,
- isSellAllowed,
- availableToBuy,
- availableToSell,
- )
- return status!!
+ override suspend fun update() {
+ buyService.update()
+ sellService.update()
}
- override suspend fun isBuyAllowed(): Boolean = onramperService.isBuyAllowed()
- override suspend fun availableToBuy(): List = onramperService.availableToBuy()
- override suspend fun isSellAllowed(): Boolean = moonPayService.isSellAllowed()
- override suspend fun availableToSell(): List = moonPayService.availableToSell()
+ override fun isBuyAllowed(): Boolean = buyService.isBuyAllowed()
+ override fun isSellAllowed(): Boolean = sellService.isSellAllowed()
+ override fun availableForBuy(currency: Currency): Boolean = buyService.availableForBuy(currency)
+ override fun availableForSell(currency: Currency): Boolean = sellService.availableForSell(currency)
override fun getUrl(
action: Action,
@@ -96,22 +61,19 @@ class CurrencyExchangeManager(
private fun getExchangeUrlBuilder(action: Action): ExchangeUrlBuilder {
return when (action) {
- Action.Buy -> onramperService
- Action.Sell -> moonPayService
+ Action.Buy -> buyService
+ Action.Sell -> sellService
} as ExchangeUrlBuilder
}
enum class Action { Buy, Sell }
}
-data class CurrencyExchangeStatus(
- val isBuyAllowed: Boolean,
- val isSellAllowed: Boolean,
- val availableToBuy: List,
- val availableToSell: List,
-)
-
-suspend fun CurrencyExchangeManager.buyErc20Tokens(walletManager: EthereumWalletManager, token: Token) {
+suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(
+ card: Card,
+ walletManager: EthereumWalletManager,
+ token: Token,
+) {
walletManager.safeUpdate()
val amountToSend = Amount(walletManager.wallet.blockchain)
@@ -128,7 +90,9 @@ suspend fun CurrencyExchangeManager.buyErc20Tokens(walletManager: EthereumWallet
val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress)
val signer = TangemSigner(
- tangemSdk = tangemSdk, Message()
+ card = card,
+ tangemSdk = tangemSdk,
+ initialMessage = Message(),
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt
new file mode 100644
index 0000000000..712fd2d5e0
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt
@@ -0,0 +1,29 @@
+package com.tangem.tap.network.exchangeServices
+
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tap.features.wallet.models.Currency
+
+interface ExchangeService {
+ suspend fun update()
+ fun isBuyAllowed(): Boolean
+ fun isSellAllowed(): Boolean
+ fun availableForBuy(currency: Currency):Boolean
+ fun availableForSell(currency: Currency):Boolean
+}
+
+interface ExchangeUrlBuilder {
+ fun getUrl(
+ action: CurrencyExchangeManager.Action,
+ blockchain: Blockchain,
+ cryptoCurrencyName: String,
+ fiatCurrencyName: String,
+ walletAddress: String,
+ ): String?
+
+ fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String?
+
+ companion object {
+ const val SCHEME = "https"
+ const val SUCCESS_URL = "tangem://success.tangem.com"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt
new file mode 100644
index 0000000000..45494e0114
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt
@@ -0,0 +1,57 @@
+package com.tangem.tap.network.exchangeServices.mercuryo
+
+import com.squareup.moshi.Json
+import retrofit2.http.GET
+import retrofit2.http.Path
+
+/**
+[REDACTED_AUTHOR]
+ */
+
+
+
+private val CurrenciesUrl = "https://api.mercuryo.io/v1.6/lib/currencies"
+
+
+
+interface MercuryoApi {
+
+ @GET("{apiVersion}/lib/currencies")
+ suspend fun currencies(
+ @Path("apiVersion") apiVersion: String,
+ ): MercuryoCurrenciesResponse
+
+ companion object {
+ const val BASE_URL = "https://api.mercuryo.io/"
+ const val API_VERSION = "v1.6"
+ }
+}
+
+data class MercuryoCurrenciesResponse(
+ val status: Int,
+ val data: Data,
+) {
+ data class Data(
+ val fiat: List,
+ val crypto: List,
+ val config: Config
+ )
+
+ data class Config(
+ val base: Map,
+ @Json(name = "has_withdrawal_fee")
+ val hasWithdrawalFee: Map,
+ @Json(name = "display_options")
+ val displayOptions: Map,
+ val icons: Map,
+ )
+
+ data class DisplayOption(
+ @Json(name = "fullname")
+ val fullName: String,
+ @Json(name = "total_digits")
+ val totalDigits: Int,
+ @Json(name = "display_digits")
+ val displayDigits: Int,
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
new file mode 100644
index 0000000000..97190aa9fd
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
@@ -0,0 +1,135 @@
+package com.tangem.tap.network.exchangeServices.mercuryo
+
+import android.net.Uri
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.common.extensions.calculateSha512
+import com.tangem.common.extensions.toHexString
+import com.tangem.common.services.Result
+import com.tangem.common.services.performRequest
+import com.tangem.network.common.createRetrofitInstance
+import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.features.wallet.models.Currency
+import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
+import com.tangem.tap.network.exchangeServices.ExchangeService
+import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
+
+/**
+[REDACTED_AUTHOR]
+ */
+class MercuryoService(
+ private val apiVersion: String,
+ private val mercuryoWidgetId: String,
+ private val secret: String,
+) : ExchangeService, ExchangeUrlBuilder {
+
+ private val api: MercuryoApi = createRetrofitInstance(MercuryoApi.BASE_URL)
+ .create(MercuryoApi::class.java)
+
+ private val blockchainsAvailableToBuy = mutableListOf()
+ private val tokensAvailableToBy = mutableMapOf>()
+
+ override suspend fun update() {
+ when (val result = performRequest { api.currencies(apiVersion) }) {
+ is Result.Success -> {
+ val response = result.data
+ if (response.status == 200) {
+ // all currencies which can be bought
+ val currenciesAvailableToBy = response.data.crypto
+ // tokens which can be bought only from specific blockchain network
+ val supportedTokensWithNetwork = response.data.config.base
+
+ currenciesAvailableToBy.forEach { currencyName ->
+ val blockchain = blockchainFromCurrencyName(currencyName)
+ if (blockchain == null) {
+ // suppose its a token
+ supportedTokensWithNetwork[currencyName]?.let {
+ blockchainFromCurrencyName(it)
+ }?.let { blockchainNetwork ->
+ val supportedInBlockchainsNetwork = tokensAvailableToBy[currencyName]
+ ?: mutableListOf()
+ supportedInBlockchainsNetwork.add(blockchainNetwork)
+ tokensAvailableToBy[currencyName] = supportedInBlockchainsNetwork
+ }
+ } else {
+ blockchainsAvailableToBuy.add(blockchain)
+ }
+ }
+ }
+ }
+ is Result.Failure -> {
+ blockchainsAvailableToBuy.clear()
+ tokensAvailableToBy.clear()
+ }
+ }
+ }
+
+ override fun isBuyAllowed(): Boolean = true
+
+ override fun isSellAllowed(): Boolean = false
+
+ override fun availableForBuy(currency: Currency): Boolean {
+ if (!isBuyAllowed()) return false
+
+ // blockchains which cant be defined by mercuryo service
+ val unsupportedBlockchains = listOf(Blockchain.Unknown, Blockchain.Binance, Blockchain.Arbitrum)
+ val blockchain = currency.blockchain
+
+ return when (currency) {
+ is Currency.Blockchain -> {
+ when {
+ blockchain.isTestnet() -> blockchain.getTestnetTopUpUrl() != null
+ unsupportedBlockchains.contains(blockchain) -> false
+ else -> {
+ blockchainsAvailableToBuy.contains(currency.blockchain)
+ }
+ }
+ }
+ is Currency.Token -> {
+ val supportedInBlockchains = tokensAvailableToBy[currency.currencySymbol] ?: return false
+ supportedInBlockchains.contains(currency.blockchain)
+ }
+ }
+ }
+
+ override fun availableForSell(currency: Currency): Boolean = false
+
+ override fun getUrl(
+ action: CurrencyExchangeManager.Action,
+ blockchain: Blockchain,
+ cryptoCurrencyName: CryptoCurrencyName,
+ fiatCurrencyName: String,
+ walletAddress: String
+ ): String {
+ if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException()
+
+ val builder = Uri.Builder()
+ .scheme(ExchangeUrlBuilder.SCHEME)
+ .authority("exchange.mercuryo.io")
+ .appendQueryParameter("widget_id", mercuryoWidgetId)
+ .appendQueryParameter("type", action.name.lowercase())
+ .appendQueryParameter("currency", cryptoCurrencyName)
+ .appendQueryParameter("address", walletAddress)
+ .appendQueryParameter("signature", signature(walletAddress))
+ .appendQueryParameter("fix_currency", "true")
+ .appendQueryParameter("return_url", ExchangeUrlBuilder.SUCCESS_URL)
+
+ val url = builder.build().toString()
+ return url
+ }
+
+ private fun signature(address: String): String {
+ return (address + secret).calculateSha512().toHexString().lowercase()
+ }
+
+
+ override fun getSellCryptoReceiptUrl(
+ action: CurrencyExchangeManager.Action,
+ transactionId: String
+ ): String? = null
+
+ private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) {
+ "BNB" -> Blockchain.BSC
+ "ETH" -> Blockchain.Ethereum
+ else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() }
+ }
+}
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
index 54fd9c558e..fa96286e84 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt
@@ -5,15 +5,15 @@ import android.util.Base64
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
+import com.tangem.domain.common.extensions.withIOContext
import com.tangem.network.common.createRetrofitInstance
import com.tangem.tap.common.extensions.urlEncode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
-import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.URL_SELL
-import kotlinx.coroutines.coroutineScope
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
@@ -29,14 +29,14 @@ class MoonPayService(
private var status: MoonPayStatus? = null
- private suspend fun updateStatus() {
- try {
- coroutineScope {
+ override suspend fun update() {
+ withIOContext {
+ performRequest {
val userStatusResult = performRequest { api.getUserStatus(apiKey) }
- if (userStatusResult is Result.Failure) return@coroutineScope userStatusResult
+ if (userStatusResult is Result.Failure) return@performRequest
val currenciesResult = performRequest { api.getCurrencies(apiKey) }
- if (currenciesResult is Result.Failure) return@coroutineScope currenciesResult
+ if (currenciesResult is Result.Failure) return@performRequest
val userStatus = (userStatusResult as Result.Success).data
val currencies = (currenciesResult as Result.Success).data
@@ -65,29 +65,31 @@ class MoonPayService(
status = MoonPayStatus(currenciesToSell, userStatus, currencies)
}
- } catch (error: Error) {
- status = null
- Result.Failure(error)
}
}
- override suspend fun isBuyAllowed(): Boolean = false
+ override fun isBuyAllowed(): Boolean = false
- override suspend fun availableToBuy(): List = listOf()
-
- override suspend fun isSellAllowed(): Boolean {
- refreshStatus()
+ override fun isSellAllowed(): Boolean {
return status?.responseUserStatus?.isSellAllowed ?: false
}
- override suspend fun availableToSell(): List {
- refreshStatus()
- return status?.availableToSell ?: emptyList()
- }
+ override fun availableForBuy(currency: Currency): Boolean = false
- private suspend fun refreshStatus() {
- if (status == null) {
- updateStatus()
+ override fun availableForSell(currency: Currency): Boolean {
+ val availableForSell = status?.availableForSell ?: return false
+ if (!isSellAllowed()) return false
+
+ return when (currency) {
+ is Currency.Blockchain -> {
+ val blockchain = currency.blockchain
+ when {
+ blockchain.isTestnet() -> false
+ blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC -> false
+ else -> availableForSell.contains(currency.currencySymbol)
+ }
+ }
+ is Currency.Token -> false
}
}
@@ -133,10 +135,14 @@ class MoonPayService(
val sha256encoded = sha256Hmac.doFinal(data.toByteArray())
return Base64.encodeToString(sha256encoded, Base64.NO_WRAP)
}
+
+ companion object {
+ const val URL_SELL = "sell.moonpay.com"
+ }
}
private data class MoonPayStatus(
- val availableToSell: List,
+ val availableForSell: List,
val responseUserStatus: MoonPayUserStatus,
val responseCurrencies: List
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt
deleted file mode 100644
index 30a65ed706..0000000000
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.tangem.tap.network.exchangeServices.onramper
-
-import com.squareup.moshi.JsonClass
-import retrofit2.http.GET
-import retrofit2.http.Path
-
-interface OnramperApi {
- @GET("gateways")
- suspend fun gateways(): GatewaysResponse
-
- @GET("rate/{fromCurrency}/{toCurrency}/{paymentMethod}/{amount}")
- suspend fun rate(
- @Path("fromCurrency") fromCurrency: String,
- @Path("toCurrency") toCurrency: String,
- @Path("paymentMethod") paymentMethod: String,
- @Path("amount") amount: Int,
- ): RateResponse
-
- companion object {
- val BASE_URL = "https://onramper.tech/"
- }
-}
-
-@JsonClass(generateAdapter = true)
-data class GatewaysResponse(
- val gateways: List
-)
-
-@JsonClass(generateAdapter = true)
-data class OnramperGateway(
- val identifier: String,
- val paymentMethods: List,
- val fiatCurrencies: List,
- val cryptoCurrencies: List
-)
-
-@JsonClass(generateAdapter = true)
-data class OnramperCurrency(
- val id: String,
- val code: String,
- val precision: Int
-)
-
-@JsonClass(generateAdapter = true)
-data class RateResponse(
- val identifier: String,
- val duration: OnramperDuration,
- val available: Boolean,
- val error: OnramperError? = null,
- val rate: Double? = null,
- val fees: Double? = null,
- val requiredKYC: List? = null,
- val receivedCrypto: Double? = null,
- val nextStep: OnramperNextStep? = null,
-)
-
-@JsonClass(generateAdapter = true)
-data class OnramperNextStep(
- val type: String,
- val url: String,
- val message: String,
- val extraData: List
-)
-
-@JsonClass(generateAdapter = true)
-data class OnramperExtraData(
- val type: String,
- val name: String,
- val humanName: String,
-)
-
-@JsonClass(generateAdapter = true)
-data class OnramperDuration(
- val seconds: Long,
- val message: String
-)
-
-@JsonClass(generateAdapter = true)
-data class OnramperError(
- val type: String,
- val message: String,
- val limit: Double
-)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt
deleted file mode 100644
index 94bdae395e..0000000000
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-package com.tangem.tap.network.exchangeServices.onramper
-
-import android.net.Uri
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.common.services.Result
-import com.tangem.common.services.performRequest
-import com.tangem.network.common.AddHeaderInterceptor
-import com.tangem.network.common.createRetrofitInstance
-import com.tangem.tap.common.extensions.urlEncode
-import com.tangem.tap.common.redux.global.CryptoCurrencyName
-import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
-import com.tangem.tap.network.exchangeServices.ExchangeService
-import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
-import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
-import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SUCCESS_URL
-import kotlinx.coroutines.coroutineScope
-import java.util.*
-
-/**
-[REDACTED_AUTHOR]
- */
-class OnramperService(
- val apiKey: String
-) : ExchangeService, ExchangeUrlBuilder {
-
- private val api: OnramperApi by lazy {
- createRetrofitInstance(
- baseUrl = OnramperApi.BASE_URL,
- interceptors = listOf(
- AddHeaderInterceptor(mapOf("Authorization" to "Basic $apiKey")),
- )
- ).create(OnramperApi::class.java)
- }
-
- private var status: OnramperStatus? = null
-
- private suspend fun updateStatus() {
- try {
- coroutineScope {
- val result = performRequest { api.gateways() }
- if (result is Result.Failure) return@coroutineScope result
-
- val response = (result as Result.Success).data
- val currenciesToBuy = extractCurrenciesToBuy(response).sorted()
- val status = OnramperStatus(currenciesToBuy, response)
- this@OnramperService.status = status
- }
- } catch (error: Error) {
- status = null
- Result.Failure(error)
- }
- }
-
- private fun extractCurrenciesToBuy(response: GatewaysResponse): List {
- return response.gateways.map { gateway ->
- gateway.cryptoCurrencies.map { currency -> currency.code }
- }.flatten().toMutableSet().toList()
- }
-
- override suspend fun isBuyAllowed(): Boolean {
- refreshStatus()
- return status != null
- }
-
- override suspend fun availableToBuy(): List {
- refreshStatus()
- return status?.availableToBuy ?: emptyList()
- }
-
- private suspend fun refreshStatus() {
- if (status == null) {
- updateStatus()
- }
- }
-
- override suspend fun isSellAllowed(): Boolean = false
-
- override suspend fun availableToSell(): List = listOf()
-
- override fun getUrl(
- action: CurrencyExchangeManager.Action,
- blockchain: Blockchain,
- cryptoCurrencyName: CryptoCurrencyName,
- fiatCurrency: String,
- walletAddress: String,
- ): String? {
- var languageCode = Locale.getDefault().language
- if (languageCode.isEmpty()) languageCode = "en"
-
- val builder = Uri.Builder()
- .scheme(SCHEME)
- .authority("widget.onramper.com")
- .appendQueryParameter("apiKey", this.apiKey.urlEncode())
- .appendQueryParameter("defaultCrypto", cryptoCurrencyName)
- .appendQueryParameter("wallets", "${blockchain.currency}:$walletAddress".urlEncode())
- .appendQueryParameter("redirectURL", SUCCESS_URL)
- .appendQueryParameter("defaultFiat", fiatCurrency)
- .appendQueryParameter("language", languageCode)
-
- status?.apply {
- val gateways = responseGateways.gateways.joinToString(",") { it.identifier }.urlEncode()
- builder.appendQueryParameter("onlyGateways", gateways)
-
- }
-
- val url = builder.build().toString()
- return url
- }
-
- override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null
-}
-
-private data class OnramperStatus(
- val availableToBuy: List,
- val responseGateways: GatewaysResponse
-)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
index f676467852..15073fb931 100644
--- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
+++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
@@ -5,7 +5,7 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.tangem.common.json.MoshiJsonConverter
-import java.util.*
+import java.util.Calendar
class PreferencesStorage(applicationContext: Application) {
@@ -53,20 +53,11 @@ class PreferencesStorage(applicationContext: Application) {
preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) }
}
- fun wasRestoreFundsWarningClosed(): Boolean {
- return preferences.getBoolean(RESTORE_FUNDS_CLOSED_KEY, false)
- }
-
- fun saveRestoreFundsWarningClosed() {
- preferences.edit { putBoolean(RESTORE_FUNDS_CLOSED_KEY, true) }
- }
-
companion object {
private const val PREFERENCES_NAME = "tapPrefs"
private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted"
private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown"
private const val APP_LAUNCH_COUNT_KEY = "launchCount"
- private const val RESTORE_FUNDS_CLOSED_KEY = "restoreFundsClosed"
}
}
diff --git a/app/src/main/res/drawable/ic_cross_rounded_24.xml b/app/src/main/res/drawable/ic_cross_rounded_24.xml
new file mode 100644
index 0000000000..ad1cab5a84
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cross_rounded_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_overlay.xml b/app/src/main/res/drawable/ic_overlay.xml
index 586397f855..5487da88f8 100644
--- a/app/src/main/res/drawable/ic_overlay.xml
+++ b/app/src/main/res/drawable/ic_overlay.xml
@@ -4,10 +4,18 @@
-
-
-
+
+
+
diff --git a/app/src/main/res/drawable/img_flag_ru_24.xml b/app/src/main/res/drawable/img_flag_ru_24.xml
new file mode 100644
index 0000000000..deff0dc4bb
--- /dev/null
+++ b/app/src/main/res/drawable/img_flag_ru_24.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_badge_custom_currency.xml b/app/src/main/res/drawable/shape_badge_custom_currency.xml
index 2c3b7f7bd4..cfd740103d 100644
--- a/app/src/main/res/drawable/shape_badge_custom_currency.xml
+++ b/app/src/main/res/drawable/shape_badge_custom_currency.xml
@@ -7,7 +7,7 @@
android:height="12dp" />
diff --git a/app/src/main/res/drawable/shape_rectangle_rounded_8.xml b/app/src/main/res/drawable/shape_rectangle_rounded_8.xml
index a788f39093..4f20cb0e64 100644
--- a/app/src/main/res/drawable/shape_rectangle_rounded_8.xml
+++ b/app/src/main/res/drawable/shape_rectangle_rounded_8.xml
@@ -5,4 +5,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/dialog_russians_cardholders_warning.xml b/app/src/main/res/layout/dialog_russians_cardholders_warning.xml
new file mode 100644
index 0000000000..8f2196c2d5
--- /dev/null
+++ b/app/src/main/res/layout/dialog_russians_cardholders_warning.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml
index 58ac038846..2171b91e61 100644
--- a/app/src/main/res/layout/fragment_details.xml
+++ b/app/src/main/res/layout/fragment_details.xml
@@ -302,8 +302,22 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_send_feedback" />
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
deleted file mode 100644
index 7a95116cd8..0000000000
--- a/app/src/main/res/layout/fragment_home.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml
index 1545257fc9..d9339cbb3a 100644
--- a/app/src/main/res/layout/fragment_wallet.xml
+++ b/app/src/main/res/layout/fragment_wallet.xml
@@ -46,24 +46,24 @@
android:id="@+id/cl_wallet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingBottom="74dp">
+ android:paddingBottom="74dp"
+ android:clipToPadding="false">
+ android:id="@+id/iv_card"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginBottom="12dp"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ android:elevation="3dp"
+ android:maxHeight="215dp"
+ android:contentDescription="@null"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@drawable/card_placeholder_black" />
@@ -185,29 +187,29 @@
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginTop="30dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
app:layout_constraintTop_toBottomOf="@id/l_address"
app:layout_constraintVertical_bias="1"
app:layout_constraintBottom_toBottomOf="parent" />
+
+
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_currency_wallet.xml b/app/src/main/res/layout/item_currency_wallet.xml
index de692cfa83..9ac49b1ad9 100644
--- a/app/src/main/res/layout/item_currency_wallet.xml
+++ b/app/src/main/res/layout/item_currency_wallet.xml
@@ -16,45 +16,62 @@
android:elevation="3dp">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:animateLayoutChanges="true"
+ android:clipToPadding="false">
-
+
+
+
+ android:id="@+id/badge_custom_balance"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="@drawable/shape_badge_custom_currency"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="@id/iv_currency"
+ app:layout_constraintEnd_toEndOf="@id/iv_currency"
+ tools:visibility="visible" />
-
+ app:layout_constraintTop_toTopOf="parent" />
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/view_currency_icon.xml b/app/src/main/res/layout/view_currency_icon.xml
new file mode 100644
index 0000000000..6859d75600
--- /dev/null
+++ b/app/src/main/res/layout/view_currency_icon.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 0596dd08b8..99c83c09c5 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -1,6 +1,5 @@
- Nein
Änderungen speichern
Warnung
Erneut versuchen
diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml
index 4eae54e057..e859d46e47 100644
--- a/app/src/main/res/values-de/strings_final.xml
+++ b/app/src/main/res/values-de/strings_final.xml
@@ -59,5 +59,6 @@
The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.
%s network not found. Please, add it first and try again.
+ This network is not supported. Please select another network.
%s network
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 45a56b13ec..17a6a3cce0 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1,7 +1,6 @@
- Non
Sauvegarder les modifications
Alerte
Réessayer
diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml
index 158ab46c65..c4e1f946db 100644
--- a/app/src/main/res/values-fr/strings_final.xml
+++ b/app/src/main/res/values-fr/strings_final.xml
@@ -59,5 +59,6 @@
The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.
%s network not found. Please, add it first and try again.
+ This network is not supported. Please select another network.
%s network
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 7b8f98b231..4d699600d3 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -1,7 +1,6 @@
- No
Mantieni le modifiche
Avviso
Riprova
diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml
index 158ab46c65..bb1cf7be30 100644
--- a/app/src/main/res/values-it/strings_final.xml
+++ b/app/src/main/res/values-it/strings_final.xml
@@ -59,5 +59,7 @@
The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.
%s network not found. Please, add it first and try again.
+ This network is not supported. Please select another network.
+
%s network
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index ffef45f82e..1fbdfd7a66 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -1,7 +1,6 @@
Tangem
- Нет
Сохранить изменения
Предупреждение
Повторить попытку
@@ -365,4 +364,5 @@
Привет, команда поддержки,
Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая деталь может быть полезной.
Информация ниже не является обязательной. Вы можете стереть её, если хотите.
+ Нет
diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml
index 0370067579..d4ddb9ad8c 100644
--- a/app/src/main/res/values-ru/strings_final.xml
+++ b/app/src/main/res/values-ru/strings_final.xml
@@ -61,11 +61,20 @@
Скрыть
Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно.
Невозможно скрыть %s
- Токен %s является основной валютой в сети %s и не может быть скрыт, до тех пор пока у вас в списке есть другие токены этой сети.
+
+ Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.
Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.
+ Сеть не поддерживается. Пожалуйста, выберите другую сеть.
+
Сеть %s
Бэкап кошелька не был произведен
Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру
+
+ Карты банков РФ в данный момент не принимаются
+ У вас есть карта банка другой страны или платежной системы UnionPay?
+ Да
+
+ Задать вопрос
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9189c797b7..60d0c22f1d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,7 +2,6 @@
Tangem
- No
Save changes
Warning
Retry
diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml
index 25bf984bbe..4c7ec8e0bd 100644
--- a/app/src/main/res/values/strings_final.xml
+++ b/app/src/main/res/values/strings_final.xml
@@ -64,10 +64,18 @@
The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.
%s network not found. Please, add it first and try again.
+ This network is not supported. Please select another network.
+
%s network
Your wallet has not been backed up
To protect your assets, we advise you to carry out this procedure
Remove token
+ Russian bank cards are not accepted at the moment
+ Do you have a bank card of another country or a UnionPay card?
+ Yes
+ No
+
+ Ask a question
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index f36d1cd4bf..09cdff0b35 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -5,10 +5,6 @@
- @color/colorPrimary
- @color/colorSecondary
- @color/darkGray6
- - true
- - true
- - true
- - @null
- @color/menu_accent_color
- @style/Widget.AppTheme.CardView
diff --git a/app/src/main/res/values/zendesk_styles.xml b/app/src/main/res/values/zendesk_styles.xml
new file mode 100644
index 0000000000..0022b241b1
--- /dev/null
+++ b/app/src/main/res/values/zendesk_styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ // Contact us FAB style
+
+
diff --git a/build.gradle b/build.gradle
index 23b9e4d0bc..64f57c2248 100644
--- a/build.gradle
+++ b/build.gradle
@@ -22,6 +22,7 @@ allprojects {
mavenLocal()
maven { url "https://nexus.tangem-tech.com/repository/maven-releases/" }
maven { url 'https://jitpack.io' }
+ maven { url 'https://zendesk.jfrog.io/zendesk/repo' }
}
}
diff --git a/dependencies.gradle b/dependencies.gradle
index ac02ffad75..20dded5f26 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -2,7 +2,7 @@ ext.versions = [
kotlin : '1.6.10',
build_gradle : '7.1.3',
tamgem_card_sdk : 'develop-157',
- tamgem_blockchain_sdk: 'develop-93',
+ tamgem_blockchain_sdk: 'develop-96',
]
ext.environmentConfig = [
diff --git a/domain/build.gradle b/domain/build.gradle
index d7a8832fd4..f4b0183cc8 100644
--- a/domain/build.gradle
+++ b/domain/build.gradle
@@ -85,4 +85,4 @@ dependencies {
testImplementation "com.google.truth:truth:1.1.3"
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
-}
\ No newline at end of file
+}
diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt
index c21fb92726..9a4b2cd8b1 100644
--- a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt
+++ b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt
@@ -43,4 +43,8 @@ data class CurrenciesResponse(val currencies: List) {
val unit: String, // $, €, ₽
val type: String,
) : TangemTechResponse
-}
\ No newline at end of file
+}
+
+data class GeoResponse(
+ val code: String
+) : TangemTechResponse
\ No newline at end of file
diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt
index 0081f4ee24..80558732dd 100644
--- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt
+++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt
@@ -27,4 +27,6 @@ interface TangemTechApi {
@GET("currencies")
suspend fun currencies(): CurrenciesResponse
+ @GET("geo")
+ suspend fun geo(): GeoResponse
}
\ No newline at end of file
diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt
index 5790bdac3a..3bf3bd232e 100644
--- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt
+++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt
@@ -47,6 +47,10 @@ class TangemTechService {
}
}
+ suspend fun userCountry(): Result = withContext(Dispatchers.IO) {
+ performRequest { api.geo() }
+ }
+
suspend fun currencies(): Result = withContext(Dispatchers.IO) {
performRequest { api.currencies() }
}