diff --git a/app/build.gradle b/app/build.gradle
index 791cb96330..3f12b8e345 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -144,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/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/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..dccb805723
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt
@@ -0,0 +1,113 @@
+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.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)
+ }
+
+ 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 a82d5ba94b..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,14 +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 org.rekotlin.Action
sealed class GlobalAction : Action {
@@ -73,7 +77,8 @@ 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 ExchangeManager : GlobalAction() {
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt
index 7f61d63022..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
@@ -79,8 +79,11 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
}
}
- is GlobalAction.SendFeedback -> {
- store.state.globalState.feedbackManager?.send(action.emailData)
+ 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))
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 8588256411..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
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 52037f828c..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
/**
@@ -20,7 +20,8 @@ data class Config(
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(
@@ -33,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 ->
@@ -94,6 +95,7 @@ class ConfigManager(
),
appsFlyerDevKey = values.appsFlyerDevKey,
shopify = values.shopifyShop,
+ zendesk = values.zendesk,
)
defaultConfig = defaultConfig.copy(
coinMarketCapKey = values.coinMarketCapKey,
@@ -109,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 f9e053ee98..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]
@@ -24,7 +25,8 @@ class ConfigValueModel(
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/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
index 2789b0069d..71b6baa710 100644
--- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt
@@ -9,6 +9,8 @@ import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.domain.common.getTwinCardIdForUser
import com.tangem.tap.common.extensions.show
+import com.tangem.tap.common.feedback.FeedbackEmail
+import com.tangem.tap.common.feedback.SupportInfo
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
@@ -16,7 +18,6 @@ import com.tangem.tap.domain.extensions.isMultiwalletAllowed
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.SecurityOption
-import com.tangem.tap.features.feedback.FeedbackEmail
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
@@ -124,7 +125,7 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber Unit)? = null) {
- if (!this::activity.isInitialized) return
-
- emailData.prepare(infoHolder)
- val fileLog = if (emailData is ScanFailsEmail) createLogFile() else null
- activity.sendEmail(
- email = getSupportEmail(),
- subject = activity.getString(emailData.subjectResId),
- message = emailData.joinTogether(activity, infoHolder),
- file = fileLog,
- onFail = onFail
- )
- }
-
- private fun getSupportEmail(): String {
- return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) {
- S2C_SUPPORT_EMAIL
- } else {
- DEFAULT_SUPPORT_EMAIL
- }
- }
-
- private fun createLogFile(): File? {
- return try {
- val file = File(activity.filesDir, "logs.txt")
- file.delete()
- file.createNewFile()
-
- val stringWriter = StringWriter()
- logCollector.getLogs().forEach { stringWriter.append(it) }
- val fileWriter = FileWriter(file)
- fileWriter.write(stringWriter.toString())
- fileWriter.close()
- logCollector.clearLogs()
- file
- } catch (ex: Exception) {
- Timber.e(ex, "Can't create a file for email attachment")
- null
- }
- }
-
- companion object {
- const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
- const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
- }
-}
-
-class TangemLogCollector : 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/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/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt
index 9014d5d4ff..f77b716bc7 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
@@ -16,10 +16,10 @@ 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
@@ -135,7 +135,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/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/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml
index 58ac038846..2171b91e61 100644
--- a/app/src/main/res/layout/fragment_details.xml
+++ b/app/src/main/res/layout/fragment_details.xml
@@ -302,8 +302,22 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_send_feedback" />
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml
index 0deae6285f..d2028c922e 100644
--- a/app/src/main/res/values-ru/strings_final.xml
+++ b/app/src/main/res/values-ru/strings_final.xml
@@ -72,4 +72,6 @@
Карты банков РФ в данный момент не принимаются
У вас есть карта банка другой страны или платежной системы UnionPay?
Да
+
+ Задать вопрос
diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml
index f5dfbc7abd..adbf1e7604 100644
--- a/app/src/main/res/values/strings_final.xml
+++ b/app/src/main/res/values/strings_final.xml
@@ -74,4 +74,6 @@
Do you have a bank card of another country or a UnionPay card?
Yes
No
+
+ Ask a question
diff --git a/app/src/main/res/values/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' }
}
}