Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-24 10:58:42 +03:00
parent 1d589158a5
commit ad582bfc1a
21 changed files with 81 additions and 667 deletions

View file

@ -1,147 +0,0 @@
package com.tangem.tap.common.feedback
import android.os.Build
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.crypto.NetworkType
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.tap.common.extensions.stripZeroPlainString
import java.util.concurrent.CopyOnWriteArrayList
class AdditionalFeedbackInfo {
class EmailWalletInfo(
val blockchain: Blockchain = Blockchain.Unknown,
val derivationPath: String = "",
val outputsCount: String? = null,
val host: String = "",
val addresses: String = "",
val explorerLink: String = "",
val tokens: List<EmailTokenInfo> = emptyList(),
)
class EmailTokenInfo(
val id: String?,
val name: String,
val contractAddress: String,
)
var appVersion: String = ""
// card
var cardId: String = ""
var cardFirmwareVersion: String = ""
var cardIssuer: String = ""
var cardBlockchain: String = ""
var userWalletId: String = ""
var extendedPublicKey: String = ""
// wallets
val walletsInfo = CopyOnWriteArrayList<EmailWalletInfo>()
var onSendErrorWalletInfo: EmailWalletInfo? = null
private set
var signedHashesCount: String = ""
private set
// device
var phoneModel: String = Build.MODEL
private set
var osVersion: String = Build.VERSION.SDK_INT.toString()
private set
// send error
var destinationAddress: String = ""
private set
var amount: String = ""
private set
var fee: String = ""
private set
var token: String = ""
private set
private val Address.name: String
get() = type.javaClass.simpleName
@Deprecated("Don't use it directly")
fun setCardInfo(data: ScanResponse) {
cardId = data.card.cardId
cardBlockchain = data.walletData?.blockchain ?: ""
cardFirmwareVersion = data.card.firmwareVersion.stringValue
cardIssuer = data.card.issuer.name
signedHashesCount = formatSignedHashes(data.card.wallets)
userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: ""
extendedPublicKey = runCatching {
data.card.wallets.firstOrNull { it.extendedPublicKey != null }
?.extendedPublicKey
?.serialize(networkType = NetworkType.Mainnet).orEmpty()
}.getOrDefault("couldn't retrieve extended public key")
}
@Deprecated("Don't use it directly")
fun setWalletsInfo(walletManagers: List<WalletManager>) {
synchronized(walletsInfo) {
walletsInfo.clear()
walletsInfo.addAll(elements = walletManagers.map(::createEmailWalletInfo))
}
}
fun updateOnSendError(
walletManager: WalletManager,
amountToSend: Amount?,
feeAmount: Amount?,
destinationAddress: String?,
) {
onSendErrorWalletInfo = createEmailWalletInfo(walletManager)
this.destinationAddress = destinationAddress ?: "0"
amount = amountToSend?.value?.stripZeroPlainString() ?: "0"
fee = if (feeAmount != null) {
feeAmount.value?.stripZeroPlainString() ?: "0"
} else {
"Unable to receive"
}
token = if (amountToSend?.type is AmountType.Token) amountToSend.currencySymbol else ""
}
private fun createEmailWalletInfo(walletManager: WalletManager): EmailWalletInfo {
return EmailWalletInfo(
blockchain = walletManager.wallet.blockchain,
derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath ?: "",
outputsCount = walletManager.outputsCount?.toString(),
host = walletManager.currentHost,
addresses = formatAddresses(walletManager.wallet),
explorerLink = formatExploreUrls(walletManager.wallet),
tokens = walletManager.cardTokens.map { token ->
EmailTokenInfo(token.id, token.name, token.contractAddress)
},
)
}
private fun formatSignedHashes(wallets: List<CardDTO.Wallet>): String {
return wallets.joinToString("\n") { "Signed hashes: ${it.curve.curve} - ${it.totalSignedHashes}" }
}
private fun formatAddresses(wallet: Wallet): String {
return wallet.formatAddressWith("Multiple address:") {
"${it.name} - ${it.value}"
}
}
private fun formatExploreUrls(wallet: Wallet): String {
return wallet.formatAddressWith("Multiple explorers links:") {
"${it.name} - ${wallet.getExploreUrl(it.value)}"
}
}
@Suppress("MagicNumber")
private fun Wallet.formatAddressWith(with: String, mapAddress: (Address) -> String): String {
return if (addresses.size == 1) {
getExploreUrl(address)
} else {
addresses.map { mapAddress(it) }.toMutableList()
.apply { add(0, with) }
.joinToString("\n")
}
}
}

View file

@ -1,12 +0,0 @@
package com.tangem.tap.common.feedback
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
internal class DefaultFeedbackManagerFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : FeedbackManagerFeatureToggles {
override val isLocalLogsEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "LOCAL_USER_LOGS_ENABLED")
}

View file

@ -1,105 +0,0 @@
package com.tangem.tap.common.feedback
import android.content.Context
import com.tangem.domain.common.TapWorkarounds
import com.tangem.tap.common.extensions.breakLine
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
@Suppress("MagicNumber")
fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String {
return StringBuilder().apply {
append(context.getString(mainMessageResId))
breakLine(3)
append(context.getString(getDataCollectionMessageResId()))
breakLine()
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()
.breakLine()
.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
@Suppress("MagicNumber")
override fun joinTogether(context: Context, infoHolder: AdditionalFeedbackInfo): String = StringBuilder().apply {
append(context.getString(mainMessageResId))
breakLine(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)
.breakLine()
.appendPhoneInfo()
.build()
}
class FeedbackEmail : FeedbackData {
override val subjectResId: Int
get() = if (isS2CCard) R.string.feedback_subject_support else R.string.feedback_subject_support_tangem
override val mainMessageResId: 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()
.breakLine()
.appendPhoneInfo()
.build()
}
class SupportInfo : FeedbackData {
override val subjectResId: Int = R.string.details_chat
override val mainMessageResId: Int = R.string.details_chat
override fun createOptionalMessage(infoHolder: AdditionalFeedbackInfo): String {
return FeedbackDataBuilder(infoHolder)
.appendCardInfo()
.appendDelimiter()
.appendPhoneInfo()
.build()
}
}

View file

@ -1,85 +0,0 @@
package com.tangem.tap.common.feedback
import com.tangem.tap.common.extensions.breakLine
class FeedbackDataBuilder(
private val infoHolder: AdditionalFeedbackInfo,
) {
val builder = StringBuilder()
fun appendDelimiter(): FeedbackDataBuilder {
builder.appendDelimiter()
return this
}
fun breakLine(count: Int = 1): FeedbackDataBuilder {
builder.breakLine(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("", infoHolder.signedHashesCount)
builder.appendKeyValue("User Wallet ID", infoHolder.userWalletId)
return this
}
fun appendWalletsInfo(): FeedbackDataBuilder {
infoHolder.walletsInfo.forEach { walletInfo ->
builder.appendDelimiter()
builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName)
builder.appendKeyValue("Derivation path", walletInfo.derivationPath)
builder.appendKeyValue("Outputs count", walletInfo.outputsCount)
if (walletInfo.tokens.isNotEmpty()) {
builder.append("Tokens:")
breakLine()
walletInfo.tokens.forEach { token ->
builder.appendKeyValue("ID", token.id ?: "[custom token]")
builder.appendKeyValue("Name", token.name)
builder.appendKeyValue("Contract address", token.contractAddress)
}
}
builder.appendKeyValue("Host", walletInfo.host)
builder.appendKeyValue("Wallet address", walletInfo.addresses)
builder.appendKeyValue("Explorer link", walletInfo.explorerLink)
}
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.addresses)
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 = when {
value.isNullOrBlank() -> this
key.isBlank() -> this.append("$value\n")
else -> this.append("$key: $value\n")
}
private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n")

View file

@ -1,84 +1,23 @@
package com.tangem.tap.common.feedback
import android.content.Context
import com.tangem.core.navigation.email.EmailSender
import com.tangem.domain.common.TapWorkarounds
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.log.TangemLogCollector
import com.tangem.tap.foregroundActivityObserver
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.withForegroundActivity
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.io.FileWriter
import java.io.StringWriter
/**
[REDACTED_AUTHOR]
*/
class LegacyFeedbackManager(
val infoHolder: AdditionalFeedbackInfo,
private val logCollector: TangemLogCollector,
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase,
) {
private var sessionLogsFile: File? = null
fun sendEmail(feedbackData: FeedbackData, scanResponse: ScanResponse?) {
if (feedbackManagerFeatureToggles.isLocalLogsEnabled) {
scope.launch {
val getCardInfo = suspend {
scanResponse ?: error("ScanResponse must be not null")
store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()
?: error("CardInfo must be not null")
}
val email = getFeedbackEmailUseCase(
type = when (feedbackData) {
is FeedbackEmail -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
is RateCanBeBetterEmail -> FeedbackEmailType.RateCanBeBetter(cardInfo = getCardInfo())
is ScanFailsEmail -> FeedbackEmailType.ScanningProblem
is SendTransactionFailedEmail -> {
FeedbackEmailType.TransactionSendingProblem(cardInfo = getCardInfo())
}
else -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
},
)
store.inject(DaggerGraphState::emailSender).send(
email = EmailSender.Email(
address = email.address,
subject = email.subject,
message = email.message,
attachment = email.file,
),
)
}
} else {
feedbackData.prepare(infoHolder)
foregroundActivityObserver.withForegroundActivity { activity ->
activity.sendEmail(
email = getSupportEmail(),
subject = activity.getString(feedbackData.subjectResId),
message = feedbackData.joinTogether(activity, infoHolder),
file = getLogFile(activity),
)
}
}
}
// TODO: [REDACTED_JIRA]
fun sendEmail(type: FeedbackEmailType) {
if (!feedbackManagerFeatureToggles.isLocalLogsEnabled) error("LOCAL_LOGS feature toggle must be enabled")
scope.launch {
val email = getFeedbackEmailUseCase(type = type)
@ -92,45 +31,4 @@ class LegacyFeedbackManager(
)
}
}
private fun getLogFile(context: Context): File? {
return try {
if (sessionLogsFile != null) {
return sessionLogsFile
}
val file = File(context.filesDir, LOGS_FILE)
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()
if (file.exists()) {
sessionLogsFile = file
sessionLogsFile
} else {
null
}
} catch (ex: Exception) {
Timber.e(ex, "Can't create the logs file")
null
}
}
private fun getSupportEmail(): String {
return if (TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer)) {
S2C_SUPPORT_EMAIL
} else {
DEFAULT_SUPPORT_EMAIL
}
}
private companion object {
const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
const val LOGS_FILE = "logs.txt"
}
}

View file

@ -6,7 +6,6 @@ import com.tangem.datasource.config.ConfigManager
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.feedback.FeedbackData
import com.tangem.tap.common.feedback.LegacyFeedbackManager
import com.tangem.tap.common.redux.DebugErrorAction
import com.tangem.tap.common.redux.ErrorAction
@ -72,8 +71,6 @@ sealed class GlobalAction : Action {
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
data class SetFeedbackManager(val feedbackManager: LegacyFeedbackManager) : GlobalAction()
data class SendEmail(val feedbackData: FeedbackData, val scanResponse: ScanResponse?) : GlobalAction()
object ExchangeManager : GlobalAction() {
object Init : GlobalAction() {
data class Success(

View file

@ -55,12 +55,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
is GlobalAction.RestoreAppCurrency -> {
restoreAppCurrency()
}
is GlobalAction.SendEmail -> {
store.state.globalState.feedbackManager?.sendEmail(
feedbackData = action.feedbackData,
scanResponse = action.scanResponse,
)
}
is GlobalAction.ExchangeManager.Init -> {
val appStateSafe = appState() ?: return
val config = appStateSafe.globalState.configManager?.config ?: return

View file

@ -2,14 +2,14 @@ package com.tangem.tap.common.redux.legacy
import com.tangem.blockchain.common.AmountType
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.CardInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.feedback.FeedbackEmail
import com.tangem.tap.common.feedback.RateCanBeBetterEmail
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.DetailsAction
@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
internal object LegacyMiddleware {
@ -33,68 +32,49 @@ internal object LegacyMiddleware {
{ next ->
{ action ->
when (action) {
is LegacyAction.StartOnboardingProcess -> {
store.dispatch(
GlobalAction.Onboarding.Start(
scanResponse = action.scanResponse,
canSkipBackup = action.canSkipBackup,
),
)
}
is LegacyAction.SendEmailRateCanBeBetter -> {
store.state.globalState.feedbackManager?.sendEmail(
feedbackData = RateCanBeBetterEmail(),
scanResponse = action.scanResponse,
type = FeedbackEmailType.RateCanBeBetter(cardInfo = getCardInfo(action.scanResponse)),
)
}
is LegacyAction.SendEmailSupport -> {
store.state.globalState.feedbackManager?.sendEmail(
feedbackData = FeedbackEmail(),
scanResponse = action.scanResponse,
)
}
is LegacyAction.StartOnboardingProcess -> {
store.dispatch(
GlobalAction.Onboarding.Start(action.scanResponse, canSkipBackup = action.canSkipBackup),
type = FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo(action.scanResponse)),
)
}
is LegacyAction.SendEmailTransactionFailed -> {
if (store.inject(DaggerGraphState::feedbackManagerFeatureToggles).isLocalLogsEnabled) {
val amount = action.amount?.convertToSdkAmount(action.cryptoCurrency)
val amount = action.amount?.convertToSdkAmount(action.cryptoCurrency)
store.inject(DaggerGraphState::saveBlockchainErrorUseCase).invoke(
error = BlockchainErrorInfo(
errorMessage = action.errorMessage,
blockchainId = action.cryptoCurrency.network.id.value,
derivationPath = action.cryptoCurrency.network.derivationPath.value,
destinationAddress = action.destinationAddress.orEmpty(),
tokenSymbol = if (amount?.type is AmountType.Token) {
amount.currencySymbol
} else {
""
},
amount = amount?.value?.stripZeroPlainString() ?: "unknown",
fee = action.fee?.convertToSdkAmount(action.cryptoCurrency)
?.value?.stripZeroPlainString() ?: "unknown",
),
)
store.inject(DaggerGraphState::saveBlockchainErrorUseCase).invoke(
error = BlockchainErrorInfo(
errorMessage = action.errorMessage,
blockchainId = action.cryptoCurrency.network.id.value,
derivationPath = action.cryptoCurrency.network.derivationPath.value,
destinationAddress = action.destinationAddress.orEmpty(),
tokenSymbol = if (amount?.type is AmountType.Token) {
amount.currencySymbol
} else {
""
},
amount = amount?.value?.stripZeroPlainString() ?: "unknown",
fee = action.fee?.convertToSdkAmount(action.cryptoCurrency)
?.value?.stripZeroPlainString() ?: "unknown",
),
)
store.state.globalState.feedbackManager?.sendEmail(
feedbackData = SendTransactionFailedEmail(action.errorMessage),
scanResponse = action.scanResponse,
)
} else {
scope.launch {
store.inject(DaggerGraphState::walletManagersFacade)
.getOrCreateWalletManager(
userWalletId = action.userWalletId,
network = action.cryptoCurrency.network,
)?.let { walletManager ->
store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
walletManager = walletManager,
amountToSend = action.amount?.convertToSdkAmount(action.cryptoCurrency),
feeAmount = action.fee?.convertToSdkAmount(action.cryptoCurrency),
destinationAddress = action.destinationAddress,
)
}
store.state.globalState.feedbackManager?.sendEmail(
feedbackData = SendTransactionFailedEmail(action.errorMessage),
scanResponse = null,
)
}
}
store.state.globalState.feedbackManager?.sendEmail(
type = FeedbackEmailType.TransactionSendingProblem(
cardInfo = getCardInfo(action.scanResponse),
),
)
}
is LegacyAction.PrepareDetailsScreen -> {
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
@ -119,4 +99,10 @@ internal object LegacyMiddleware {
}
}
}
private fun getCardInfo(scanResponse: ScanResponse): CardInfo {
return store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse)
.getOrNull()
?: error("Card info not found")
}
}

View file

@ -8,12 +8,11 @@ import androidx.core.view.isVisible
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.analytics.events.ScanFailsDialogAnalytics
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.feedback.ScanFailsEmail
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.home.LocaleRegionProvider
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.store
@ -65,7 +64,8 @@ internal object ScanFailsDialog {
}
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
Analytics.send(Basic.ButtonSupport(sourceAnalytics))
store.dispatch(GlobalAction.SendEmail(feedbackData = ScanFailsEmail(), scanResponse = null))
store.state.globalState.feedbackManager?.sendEmail(type = FeedbackEmailType.ScanningProblem)
}
customView.findViewById<TextView>(R.id.cancel_button)?.setOnClickListener {
store.dispatchDialogHide()