diff --git a/app/build.gradle b/app/build.gradle
index abf8cda691..3f12b8e345 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -93,10 +93,9 @@ dependencies {
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
- implementation 'com.tangem:blockchain:develop-93'
-// implementation 'com.tangem:blockchain:0.0.1'
- implementation 'com.tangem.tangem-sdk-kotlin:core:develop-154'
- implementation 'com.tangem.tangem-sdk-kotlin:android:develop-154'
+ implementation "com.tangem:blockchain:${versions.tamgem_blockchain_sdk}"
+ implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tamgem_card_sdk}"
+ implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tamgem_card_sdk}"
// WebView
implementation "androidx.browser:browser:1.3.0"
@@ -145,8 +144,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
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..7593f303e7 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -27,11 +27,11 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.shop.redux.ShopAction
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ActivityMainBinding
+import java.lang.ref.WeakReference
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
-import java.lang.ref.WeakReference
-import kotlin.coroutines.CoroutineContext
lateinit var tangemSdk: TangemSdk
lateinit var tangemSdkManager: TangemSdkManager
@@ -57,7 +57,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)
diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt
index 2e6c7b534b..518ad410c4 100644
--- a/app/src/main/java/com/tangem/tap/TapApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TapApplication.kt
@@ -12,7 +12,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
@@ -24,9 +27,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
@@ -40,6 +40,8 @@ val store = Store(
)
val logConfig = LogConfig()
+lateinit var foregroundActivityObserver: ForegroundActivityObserver
+
lateinit var preferencesStorage: PreferencesStorage
lateinit var currenciesRepository: CurrenciesRepository
lateinit var walletConnectRepository: WalletConnectRepository
@@ -68,6 +70,8 @@ class TapApplication : Application(), ImageLoaderFactory {
)
walletConnectRepository = WalletConnectRepository(this)
+ foregroundActivityObserver = ForegroundActivityObserver()
+ registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
initFeedbackManager()
loadConfigs()
@@ -85,16 +89,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 logWriter = TangemLogCollector()
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 afe0c1863e..a199be957b 100644
--- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
@@ -133,6 +133,8 @@ class DialogManager : StoreSubscriber {
primaryButtonRes = state.dialog.primaryButtonRes,
primaryButtonAction = state.dialog.onOk
)
+ is WalletDialog.RussianCardholdersWarningDialog ->
+ RussianCardholdersWarningBottomSheetDialog(context)
is DetailsDialog.ConfirmDisablingSaving -> {
val messageRes = when (state.dialog.setting) {
PrivacySetting.SAVE_CARDS -> R.string.security_and_privacy_save_card_disable_warning
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..40acdd1b7d 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
@@ -49,11 +49,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/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/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
index 4e751b5ce1..1c5a6e477b 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,12 +9,15 @@ 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
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
@@ -123,7 +126,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 : 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() }
- }
-}
-
-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..c453cdadc4 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
@@ -38,7 +38,9 @@ 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/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..ba70c84e05 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 }
@@ -320,16 +308,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 +350,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..a7ee50446d 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,70 @@ 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 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(walletManager, 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 +109,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/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/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
index 9014d5d4ff..dee8fef67c 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
@@ -1,25 +1,21 @@
package com.tangem.tap.features.wallet.ui.adapters
-import android.content.res.Resources
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.core.os.ConfigurationCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.play.core.review.ReviewManagerFactory
import com.tangem.tap.common.analytics.AnalyticsEvent
-import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.getActivity
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.feedback.RateCanBeBetterEmail
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
-import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
-import com.tangem.tap.features.feedback.RateCanBeBetterEmail
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -89,26 +85,12 @@ class WarningMessageVH(val binding: LayoutWarningCardActionBinding) : RecyclerVi
binding.btnClose.hide()
val buttonAction =
- when {
- warning.titleResId == R.string.warning_important_security_info -> {
+ 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..fb75bc6474 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,12 +1,9 @@
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
@@ -29,57 +26,112 @@ fun loadCurrencyIcon(
token: Token?,
blockchain: Blockchain,
) {
- when {
- token == null -> currencyImageView.loadIcon(
- iconUrl = getIconUrl(blockchain.toNetworkId()),
- placeholderRes = blockchain.getRoundIconRes(),
+ CurrencyIconLoader(
+ currencyImageView = currencyImageView,
+ 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)
@@ -101,9 +153,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/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..811c9c0545 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
@@ -11,6 +11,7 @@ import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
+import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import java.math.BigDecimal
@@ -18,57 +19,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 +60,15 @@ 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(walletManager: EthereumWalletManager, token: Token) {
walletManager.safeUpdate()
val amountToSend = Amount(walletManager.wallet.blockchain)
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/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/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 2a09aecc77..76ddbacba0 100644
--- a/app/src/main/res/layout/fragment_details.xml
+++ b/app/src/main/res/layout/fragment_details.xml
@@ -283,8 +283,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_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml
index 1545257fc9..e77908ee26 100644
--- a/app/src/main/res/layout/fragment_wallet.xml
+++ b/app/src/main/res/layout/fragment_wallet.xml
@@ -174,6 +174,8 @@
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" />
@@ -185,6 +187,8 @@
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/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-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-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-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 051e40e9cc..a7309bed9c 100644
--- a/app/src/main/res/values-ru/strings_final.xml
+++ b/app/src/main/res/values-ru/strings_final.xml
@@ -69,6 +69,12 @@
Бэкап кошелька не был произведен
Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру
+ Карты банков РФ в данный момент не принимаются
+ У вас есть карта банка другой страны или платежной системы 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 7bffbf8513..958f8f3c5e 100644
--- a/app/src/main/res/values/strings_final.xml
+++ b/app/src/main/res/values/strings_final.xml
@@ -70,6 +70,12 @@
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
Security & Privacy
Security
Privacy
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..ebb4fcab6b
--- /dev/null
+++ b/app/src/main/res/values/zendesk_styles.xml
@@ -0,0 +1,7 @@
+
+
+
+
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 d463106208..45200c5dcf 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,10 +1,12 @@
ext.versions = [
- kotlin : '1.6.10',
- build_gradle: '7.1.3',
+ kotlin : '1.6.10',
+ build_gradle : '7.1.3',
+ tamgem_card_sdk : 'develop-154',
+ tamgem_blockchain_sdk: 'develop-93',
]
ext.environmentConfig = [
- environment: "ENVIRONMENT",
+ environment : "ENVIRONMENT",
testActionEnabled: "TEST_ACTION_ENABLED",
logEnabled : "LOG_ENABLED",
]
\ No newline at end of file
diff --git a/domain/build.gradle b/domain/build.gradle
index 5a54411b94..d7a8832fd4 100644
--- a/domain/build.gradle
+++ b/domain/build.gradle
@@ -59,10 +59,9 @@ dependencies {
implementation implementation(project(path: ':network'))
implementation implementation(project(path: ':common'))
- implementation 'com.tangem:blockchain:develop-93'
-// implementation 'com.tangem:blockchain:0.0.1'
- implementation 'com.tangem.tangem-sdk-kotlin:core:develop-154'
- implementation 'com.tangem.tangem-sdk-kotlin:android:develop-154'
+ implementation "com.tangem:blockchain:${versions.tamgem_blockchain_sdk}"
+ implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tamgem_card_sdk}"
+ implementation "com.tangem.tangem-sdk-kotlin:android:${versions.tamgem_card_sdk}"
// Kotlin coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
diff --git a/network/build.gradle b/network/build.gradle
index b90ff59980..dce3040c09 100644
--- a/network/build.gradle
+++ b/network/build.gradle
@@ -12,7 +12,7 @@ dependencies {
implementation implementation(project(path: ':common'))
// Tangem sdk's
- implementation 'com.tangem.tangem-sdk-kotlin:core:develop-142'
+ implementation "com.tangem.tangem-sdk-kotlin:core:${versions.tamgem_card_sdk}"
// Kotlin coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
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() }
}