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

@ -17,7 +17,6 @@ import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
@ -94,8 +93,6 @@ interface ApplicationEntryPoint {
fun getCardRepository(): CardRepository
fun getFeedbackManagerFeatureToggles(): FeedbackManagerFeatureToggles
fun getTangemSdkLogger(): TangemSdkLogger
fun getSettingsRepository(): SettingsRepository

View file

@ -1,13 +1,10 @@
package com.tangem.tap
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.tangem.Log
import com.tangem.LogFormat
import com.tangem.TangemSdkLogger
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.blockchainsdk.BlockchainSDKFactory
@ -30,7 +27,6 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.common.LogConfig
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
@ -48,11 +44,9 @@ import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
import com.tangem.tap.common.feedback.LegacyFeedbackManager
import com.tangem.tap.common.images.createCoilImageLoader
import com.tangem.tap.common.log.TangemAppLoggerInitializer
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
@ -153,9 +147,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val cardRepository: CardRepository
get() = entryPoint.getCardRepository()
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles
get() = entryPoint.getFeedbackManagerFeatureToggles()
private val tangemSdkLogger: TangemSdkLogger
get() = entryPoint.getTangemSdkLogger()
@ -260,7 +251,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
generateWalletNameUseCase = generateWalletNameUseCase,
cardRepository = cardRepository,
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
tangemSdkLogger = tangemSdkLogger,
settingsRepository = settingsRepository,
blockchainSDKFactory = blockchainSDKFactory,
@ -298,7 +288,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private fun initWithConfigDependency(config: Config) {
initAnalytics(this, config)
initFeedbackManager(this, store)
initFeedbackManager(store)
}
private fun initAnalytics(application: Application, config: Config) {
@ -319,50 +309,10 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
// ExceptionHandler.append(blockchainExceptionHandler) TODO: [REDACTED_JIRA]
}
private fun initFeedbackManager(context: Context, store: Store<AppState>) {
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo {
return AdditionalFeedbackInfo().apply {
appVersion = try {
// TODO don't use deprecated method
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
pInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
"x.y.z"
}
}
}
private fun initFeedbackManager(store: Store<AppState>) {
Log.addLogger(logger = tangemSdkLogger)
fun initTangemLogCollector(): TangemLogCollector {
val logLevels = listOf(
Log.Level.ApduCommand,
Log.Level.Apdu,
Log.Level.Tlv,
Log.Level.Nfc,
Log.Level.Command,
Log.Level.Session,
Log.Level.View,
Log.Level.Network,
Log.Level.Error,
Log.Level.Biometric,
Log.Level.Info,
)
return TangemLogCollector(logLevels, LogFormat.StairsFormatter())
}
val additionalFeedbackInfo = initAdditionalFeedbackInfo(context)
val tangemLogCollector = initTangemLogCollector()
Log.addLogger(
logger = if (feedbackManagerFeatureToggles.isLocalLogsEnabled) tangemSdkLogger else tangemLogCollector,
)
val feedbackManager = LegacyFeedbackManager(
infoHolder = additionalFeedbackInfo,
logCollector = tangemLogCollector,
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
getFeedbackEmailUseCase = getFeedbackEmailUseCase,
)
val feedbackManager = LegacyFeedbackManager(getFeedbackEmailUseCase = getFeedbackEmailUseCase)
store.dispatch(GlobalAction.SetFeedbackManager(feedbackManager))
}
}

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()

View file

@ -1,23 +0,0 @@
package com.tangem.tap.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.tap.common.feedback.DefaultFeedbackManagerFeatureToggles
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object FeedbackManagerFeatureTogglesModule {
@Provides
@Singleton
fun provideFeedbackManagerFeatureToggles(
featureTogglesManager: FeatureTogglesManager,
): FeedbackManagerFeatureToggles {
return DefaultFeedbackManagerFeatureToggles(featureTogglesManager)
}
}

View file

@ -10,11 +10,9 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.operations.attestation.Attestation
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
@ -52,11 +50,6 @@ class TapWalletManager(
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
val featureToggles = store.inject(DaggerGraphState::feedbackManagerFeatureToggles)
if (!featureToggles.isLocalLogsEnabled) {
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse)
}
updateConfigManager(scanResponse)
withMainContext {
// Order is important

View file

@ -13,18 +13,15 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.staking.FetchStakingTokensUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.features.main.model.MainScreenState
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
@ -43,13 +40,11 @@ internal class MainViewModel @Inject constructor(
private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase,
private val blockchainSDKFactory: BlockchainSDKFactory,
private val userWalletsListManager: UserWalletsListManager,
private val walletManagersFacade: WalletManagersFacade,
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
private val dispatchers: CoroutineDispatcherProvider,
stakingFeatureToggles: StakingFeatureToggles,
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
private val apiConfigsManager: ApiConfigsManager,
private val homeFeatureToggles: HomeFeatureToggles,
homeFeatureToggles: HomeFeatureToggles,
private val fetchUserCountryUseCase: FetchUserCountryUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel(), MainIntents {
@ -109,19 +104,6 @@ internal class MainViewModel @Inject constructor(
.onEach { userWallet ->
Analytics.setContext(userWallet.scanResponse)
Analytics.send(Basic.WalletOpened())
if (!feedbackManagerFeatureToggles.isLocalLogsEnabled) {
store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder ->
infoHolder.setCardInfo(userWallet.scanResponse)
walletManagersFacade
.getAll(userWallet.walletId)
.distinctUntilChanged()
.onEach(infoHolder::setWalletsInfo)
.catch { Timber.e(it) }
.launchIn(viewModelScope)
}
}
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)

View file

@ -7,9 +7,10 @@ import androidx.core.view.MenuProvider
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.models.scan.ScanResponse
import com.tangem.tap.common.feedback.SupportInfo
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.Provider
import com.tangem.wallet.R
@ -28,13 +29,12 @@ class OnboardingMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.menu_item_chat_support -> {
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
// changed on email support [REDACTED_TASK_KEY]
store.dispatch(
GlobalAction.SendEmail(
feedbackData = SupportInfo(),
scanResponse = scanResponseProvider(),
),
)
val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponseProvider()).getOrNull()
?: error("CardInfo must be not null")
store.state.globalState.feedbackManager?.sendEmail(type = FeedbackEmailType.DirectUserRequest(cardInfo))
true
}
else -> false

View file

@ -28,6 +28,7 @@ import com.tangem.core.analytics.models.Basic
import com.tangem.core.ui.extensions.setStatusBarColor
import com.tangem.datasource.utils.isNullOrEmpty
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator
@ -37,8 +38,6 @@ import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.feedback.SupportInfo
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.BaseFragment
import com.tangem.tap.features.FragmentOnBackPressedHandler
import com.tangem.tap.features.addBackPressHandler
@ -46,6 +45,7 @@ import com.tangem.tap.features.onboarding.OnboardingMenuProvider
import com.tangem.tap.features.onboarding.products.wallet.redux.*
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
import com.tangem.tap.mainScope
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.Provider
import com.tangem.wallet.R
@ -510,14 +510,13 @@ class OnboardingWalletFragment :
onBack = ::legacyOnBackHandler,
onOpenChat = {
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
// changed on email support [REDACTED_TASK_KEY]
store.dispatch(
GlobalAction.SendEmail(
feedbackData = SupportInfo(),
scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse
?: error("ScanResponse must be not null"),
),
)
val scanResponse = requireNotNull(store.state.globalState.onboardingState.onboardingManager?.scanResponse)
val getCardInfoUseCase = store.inject(DaggerGraphState::getCardInfoUseCase)
val cardInfo = requireNotNull(getCardInfoUseCase.invoke(scanResponse).getOrNull())
store.state.globalState.feedbackManager?.sendEmail(type = FeedbackEmailType.DirectUserRequest(cardInfo))
},
onOpenUriClick = { uri ->
store.dispatchOpenUrl(uri.toString())

View file

@ -6,10 +6,11 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
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.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.feedback.SupportInfo
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.wallet.R
@ -23,13 +24,14 @@ object WalletActivationErrorDialog {
setNegativeButton(R.string.common_support) { _, _ ->
// changed on email support [REDACTED_TASK_KEY]
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
store.dispatch(
GlobalAction.SendEmail(
feedbackData = SupportInfo(),
scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse
?: error("ScanResponse must be not null"),
),
)
val scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse
?: error("ScanResponse must be not null")
val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull()
?: error("CardInfo must be not null")
store.state.globalState.feedbackManager?.sendEmail(type = FeedbackEmailType.DirectUserRequest(cardInfo))
}
setOnDismissListener { store.dispatchDialogHide() }
setCancelable(false)

View file

@ -15,7 +15,6 @@ import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
@ -71,7 +70,6 @@ data class DaggerGraphState(
val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null,
val generateWalletNameUseCase: GenerateWalletNameUseCase? = null,
val cardRepository: CardRepository? = null,
val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles? = null,
val tangemSdkLogger: TangemSdkLogger? = null,
val settingsRepository: SettingsRepository? = null,
val blockchainSDKFactory: BlockchainSDKFactory? = null,