Updated on 2026-08-14
This commit is contained in:
parent
fe1d2342f4
commit
24848624e9
49 changed files with 414 additions and 182 deletions
|
|
@ -18,6 +18,7 @@ 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
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
|
|
@ -112,6 +113,8 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getDetailsFeatureToggles(): DetailsFeatureToggles
|
||||
|
||||
fun getGetCardInfoUseCase(): GetCardInfoUseCase
|
||||
|
||||
fun getUrlOpener(): UrlOpener
|
||||
|
||||
fun getShareManager(): ShareManager
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ 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
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
|
|
@ -183,6 +184,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase
|
||||
get() = entryPoint.getSaveBlockchainErrorUseCase()
|
||||
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase
|
||||
get() = entryPoint.getGetCardInfoUseCase()
|
||||
|
||||
private val detailsFeatureToggles: DetailsFeatureToggles
|
||||
get() = entryPoint.getDetailsFeatureToggles()
|
||||
|
||||
|
|
@ -284,6 +288,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
settingsRepository = settingsRepository,
|
||||
blockchainSDKFactory = blockchainSDKFactory,
|
||||
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
|
||||
getFeedbackEmailUseCase = getFeedbackEmailUseCase,
|
||||
getCardInfoUseCase = getCardInfoUseCase,
|
||||
assetLoader = assetLoader,
|
||||
detailsFeatureToggles = detailsFeatureToggles,
|
||||
urlOpener = urlOpener,
|
||||
shareManager = shareManager,
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ 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.chat.ChatManager
|
||||
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.mainScope
|
||||
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
|
||||
|
|
@ -36,16 +37,24 @@ class LegacyFeedbackManager(
|
|||
private var sessionFeedbackFile: File? = null
|
||||
private var sessionLogsFile: File? = null
|
||||
|
||||
fun sendEmail(feedbackData: FeedbackData, onFail: ((Exception) -> Unit)? = null) {
|
||||
fun sendEmail(feedbackData: FeedbackData, scanResponse: ScanResponse?) {
|
||||
if (feedbackManagerFeatureToggles.isLocalLogsEnabled) {
|
||||
mainScope.launch {
|
||||
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(
|
||||
when (feedbackData) {
|
||||
is FeedbackEmail -> FeedbackEmailType.DirectUserRequest
|
||||
is RateCanBeBetterEmail -> FeedbackEmailType.RateCanBeBetter
|
||||
type = when (feedbackData) {
|
||||
is FeedbackEmail -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
|
||||
is RateCanBeBetterEmail -> FeedbackEmailType.RateCanBeBetter(cardInfo = getCardInfo())
|
||||
is ScanFailsEmail -> FeedbackEmailType.ScanningProblem
|
||||
is SendTransactionFailedEmail -> FeedbackEmailType.TransactionSendingProblem
|
||||
else -> FeedbackEmailType.DirectUserRequest
|
||||
is SendTransactionFailedEmail -> {
|
||||
FeedbackEmailType.TransactionSendingProblem(cardInfo = getCardInfo())
|
||||
}
|
||||
else -> FeedbackEmailType.DirectUserRequest(cardInfo = getCardInfo())
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -66,12 +75,28 @@ class LegacyFeedbackManager(
|
|||
subject = activity.getString(feedbackData.subjectResId),
|
||||
message = feedbackData.joinTogether(activity, infoHolder),
|
||||
file = getLogFile(activity),
|
||||
onFail = onFail,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendEmail(type: FeedbackEmailType) {
|
||||
if (!feedbackManagerFeatureToggles.isLocalLogsEnabled) error("LOCAL_LOGS feature toggle must be enabled")
|
||||
|
||||
scope.launch {
|
||||
val email = getFeedbackEmailUseCase(type = type)
|
||||
|
||||
store.inject(DaggerGraphState::emailSender).send(
|
||||
email = EmailSender.Email(
|
||||
address = email.address,
|
||||
subject = email.subject,
|
||||
message = email.message,
|
||||
attachment = email.file,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun openChat(config: ChatConfig, feedbackData: FeedbackData) {
|
||||
chatManager.open(
|
||||
config = config,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.tap.common.feedback
|
||||
|
||||
import com.tangem.core.navigation.feedback.FeedbackManager
|
||||
import com.tangem.core.navigation.feedback.FeedbackType
|
||||
import com.tangem.domain.feedback.FeedbackManager
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
|
||||
internal class ProxyFeedbackManager : FeedbackManager {
|
||||
|
||||
override fun sendEmail(type: FeedbackType) {
|
||||
override fun sendEmail(type: FeedbackEmailType) {
|
||||
val manager = store.state.globalState.feedbackManager
|
||||
|
||||
if (manager == null) {
|
||||
|
|
@ -15,14 +15,6 @@ internal class ProxyFeedbackManager : FeedbackManager {
|
|||
return
|
||||
}
|
||||
|
||||
val data = when (type) {
|
||||
is FeedbackType.Feedback -> FeedbackEmail()
|
||||
is FeedbackType.RateCanBeBetter -> RateCanBeBetterEmail()
|
||||
is FeedbackType.ScanFails -> ScanFailsEmail()
|
||||
is FeedbackType.SendTransactionFailed -> SendTransactionFailedEmail(type.error)
|
||||
is FeedbackType.Support -> FeedbackEmail()
|
||||
}
|
||||
|
||||
manager.sendEmail(data)
|
||||
manager.sendEmail(type)
|
||||
}
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ sealed class GlobalAction : Action {
|
|||
data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction()
|
||||
data class SetFeedbackManager(val feedbackManager: LegacyFeedbackManager) : GlobalAction()
|
||||
|
||||
data class SendEmail(val feedbackData: FeedbackData) : GlobalAction()
|
||||
data class SendEmail(val feedbackData: FeedbackData, val scanResponse: ScanResponse?) : GlobalAction()
|
||||
data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction()
|
||||
|
||||
object ExchangeManager : GlobalAction() {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,10 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
}
|
||||
is GlobalAction.SendEmail -> {
|
||||
store.state.globalState.feedbackManager?.sendEmail(action.feedbackData)
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
feedbackData = action.feedbackData,
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is GlobalAction.OpenChat -> {
|
||||
val globalState = store.state.globalState
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.tap.common.redux.legacy
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.redux.LegacyAction
|
||||
import com.tangem.domain.tokens.utils.convertToAmount
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.feedback.RateCanBeBetterEmail
|
||||
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -19,7 +22,10 @@ internal object LegacyMiddleware {
|
|||
{ action ->
|
||||
when (action) {
|
||||
is LegacyAction.SendEmailRateCanBeBetter -> {
|
||||
store.state.globalState.feedbackManager?.sendEmail(RateCanBeBetterEmail())
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
feedbackData = RateCanBeBetterEmail(),
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is LegacyAction.StartOnboardingProcess -> {
|
||||
store.dispatch(
|
||||
|
|
@ -28,8 +34,28 @@ internal object LegacyMiddleware {
|
|||
}
|
||||
is LegacyAction.SendEmailTransactionFailed -> {
|
||||
if (store.inject(DaggerGraphState::feedbackManagerFeatureToggles).isLocalLogsEnabled) {
|
||||
|
||||
val amount = action.amount?.convertToAmount(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?.convertToAmount(action.cryptoCurrency)
|
||||
?.value?.stripZeroPlainString() ?: "unknown",
|
||||
),
|
||||
)
|
||||
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
SendTransactionFailedEmail(action.errorMessage),
|
||||
feedbackData = SendTransactionFailedEmail(action.errorMessage),
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
} else {
|
||||
scope.launch {
|
||||
|
|
@ -46,7 +72,8 @@ internal object LegacyMiddleware {
|
|||
)
|
||||
}
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
SendTransactionFailedEmail(action.errorMessage),
|
||||
feedbackData = SendTransactionFailedEmail(action.errorMessage),
|
||||
scanResponse = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ internal object ScanFailsDialog {
|
|||
}
|
||||
customView.findViewById<TextView>(R.id.request_support_button)?.setOnClickListener {
|
||||
Analytics.send(Basic.ButtonSupport(sourceAnalytics))
|
||||
store.dispatch(GlobalAction.SendEmail(ScanFailsEmail()))
|
||||
store.dispatch(GlobalAction.SendEmail(feedbackData = ScanFailsEmail(), scanResponse = null))
|
||||
}
|
||||
customView.findViewById<TextView>(R.id.cancel_button)?.setOnClickListener {
|
||||
store.dispatchDialogHide()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.core.navigation.feedback.FeedbackManager
|
||||
import com.tangem.core.navigation.finisher.AppFinisher
|
||||
import com.tangem.core.navigation.settings.SettingsManager
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.feedback.FeedbackManager
|
||||
import com.tangem.tap.common.feedback.ProxyFeedbackManager
|
||||
import com.tangem.tap.common.finisher.AndroidAppFinisher
|
||||
import com.tangem.tap.common.settings.IntentSettingsManager
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
|
|
@ -15,6 +16,12 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object FeedbackDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCardInfoUseCase(feedbackRepository: FeedbackRepository): GetCardInfoUseCase {
|
||||
return GetCardInfoUseCase(feedbackRepository = feedbackRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetFeedbackEmailUseCase(
|
||||
|
|
|
|||
|
|
@ -142,7 +142,13 @@ internal class DetailsViewModel(
|
|||
|
||||
private fun sendFeedback() {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Settings))
|
||||
store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail()))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = FeedbackEmail(),
|
||||
scanResponse = userWalletsListManager.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateToAppSettings() {
|
||||
|
|
|
|||
|
|
@ -7,15 +7,20 @@ 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.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingMenuProvider : MenuProvider {
|
||||
class OnboardingMenuProvider(
|
||||
private val scanResponseProvider: Provider<ScanResponse>,
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_onboarding, menu)
|
||||
}
|
||||
|
|
@ -24,7 +29,12 @@ class OnboardingMenuProvider : MenuProvider {
|
|||
R.id.menu_item_chat_support -> {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
// changed on email support [REDACTED_TASK_KEY]
|
||||
store.dispatch(GlobalAction.SendEmail(SupportInfo()))
|
||||
store.dispatch(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = SupportInfo(),
|
||||
scanResponse = scanResponseProvider(),
|
||||
),
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import com.tangem.tap.common.extensions.show
|
|||
import com.tangem.tap.common.transitions.HomeToOnboardingTransition
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingMainBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
|
|
@ -32,7 +34,13 @@ abstract class BaseOnboardingFragment<T> : BaseStoreFragment(R.layout.fragment_o
|
|||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider()
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider {
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: store.state.detailsState.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
},
|
||||
)
|
||||
|
||||
protected fun showConfetti(show: Boolean) = with(binding.vConfetti) {
|
||||
lavConfetti.show(show)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.getTwinCardNumber
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -35,7 +35,10 @@ private fun internalReduce(action: Action, state: AppState): TwinCardsState {
|
|||
)
|
||||
}
|
||||
is TwinCardsAction.SetStepOfScreen -> {
|
||||
state = state.copy(currentStep = action.step)
|
||||
state = state.copy(
|
||||
currentStep = action.step,
|
||||
welcomeOnlyScanResponse = (action.step as? TwinCardsStep.WelcomeOnly)?.scanResponse,
|
||||
)
|
||||
}
|
||||
is TwinCardsAction.SetUserUnderstand -> {
|
||||
state = state.copy(userWasUnderstandIfWalletRecreate = action.isUnderstand)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
|
|
@ -29,6 +29,7 @@ data class TwinCardsState(
|
|||
val balanceNonCriticalError: TapError? = null,
|
||||
val balanceCriticalError: TapError? = null,
|
||||
val showConfetti: Boolean = false,
|
||||
val welcomeOnlyScanResponse: ScanResponse? = null,
|
||||
) : StateType {
|
||||
|
||||
val steps: List<TwinCardsStep>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.view.View
|
|||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
|
|
@ -25,12 +26,14 @@ import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
|||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.domain.twins.TwinsCardWidget
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -61,6 +64,15 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
|
|||
}
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider {
|
||||
store.state.twinCardsState.welcomeOnlyScanResponse
|
||||
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: store.state.detailsState.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
},
|
||||
)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ 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.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingSeedPhraseBinding
|
||||
|
|
@ -115,7 +116,12 @@ class OnboardingWalletFragment :
|
|||
)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider()
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider {
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
},
|
||||
)
|
||||
|
||||
private fun reInitCardsWidgetIfNeeded(backupCardsCounts: Int) = with(binding) {
|
||||
val viewBackupCount = flCardsContainer.childCount - 1
|
||||
|
|
@ -507,7 +513,13 @@ class OnboardingWalletFragment :
|
|||
onOpenChat = {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
// changed on email support [REDACTED_TASK_KEY]
|
||||
store.dispatch(GlobalAction.SendEmail(SupportInfo()))
|
||||
store.dispatch(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = SupportInfo(),
|
||||
scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
},
|
||||
onOpenUriClick = { uri ->
|
||||
store.dispatchOpenUrl(uri.toString())
|
||||
|
|
|
|||
|
|
@ -23,7 +23,13 @@ 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(SupportInfo()))
|
||||
store.dispatch(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = SupportInfo(),
|
||||
scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
setOnDismissListener { store.dispatchDialogHide() }
|
||||
setCancelable(false)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.blockchain.common.FeePaidCurrency
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered
|
||||
|
|
@ -185,12 +186,16 @@ sealed class SendAction : SendScreenAction {
|
|||
) : Dialog()
|
||||
|
||||
sealed class SendTransactionFails : Dialog() {
|
||||
data class CardSdkError(val error: TangemSdkError) : Dialog()
|
||||
data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError) : Dialog()
|
||||
data class CardSdkError(val error: TangemSdkError, val scanResponse: ScanResponse) : Dialog()
|
||||
data class BlockchainSdkError(
|
||||
val error: com.tangem.blockchain.common.BlockchainSdkError,
|
||||
val scanResponse: ScanResponse,
|
||||
) : Dialog()
|
||||
}
|
||||
|
||||
data class RequestFeeError(
|
||||
val error: com.tangem.blockchain.common.BlockchainSdkError,
|
||||
val scanResponse: ScanResponse,
|
||||
val onRetry: () -> Unit,
|
||||
) : Dialog()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.tap.features.send.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.extensions.isZero
|
||||
|
|
@ -92,6 +95,7 @@ class RequestFeeMiddleware {
|
|||
dispatch(
|
||||
SendAction.Dialog.RequestFeeError(
|
||||
error = blockchainSdkError,
|
||||
scanResponse = scanResponse,
|
||||
onRetry = { dispatch(FeeAction.RequestFee) },
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -288,11 +288,25 @@ private fun sendTransaction(
|
|||
val tangemSdkError = error.tangemError as? TangemSdkError ?: return@withMainContext
|
||||
if (tangemSdkError is TangemSdkError.UserCancelled) return@withMainContext
|
||||
|
||||
dispatch(SendAction.Dialog.SendTransactionFails.CardSdkError(tangemSdkError))
|
||||
dispatch(
|
||||
SendAction.Dialog.SendTransactionFails.CardSdkError(
|
||||
error = tangemSdkError,
|
||||
scanResponse = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
is BlockchainSdkError.CreateAccountUnderfunded -> {
|
||||
// from XLM, XRP, Polkadot
|
||||
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
|
||||
dispatch(
|
||||
SendAction.Dialog.SendTransactionFails.BlockchainSdkError(
|
||||
error = error,
|
||||
scanResponse = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
is BlockchainSdkError.Kaspa.UtxoAmountError -> {
|
||||
dispatch(
|
||||
|
|
@ -331,7 +345,14 @@ private fun sendTransaction(
|
|||
)
|
||||
}
|
||||
else -> {
|
||||
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
|
||||
dispatch(
|
||||
SendAction.Dialog.SendTransactionFails.BlockchainSdkError(
|
||||
error = error,
|
||||
scanResponse = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,12 @@ object RequestFeeErrorDialog {
|
|||
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
|
||||
setNegativeButton(R.string.details_row_title_contact_to_support) { _, _ ->
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Send))
|
||||
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
|
||||
store.dispatch(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = SendTransactionFailedEmail(errorMessage),
|
||||
scanResponse = dialog.scanResponse,
|
||||
),
|
||||
)
|
||||
}
|
||||
setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() }
|
||||
setNeutralButton(R.string.common_cancel) { _, _ -> }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.common.module.ModuleMessageConverter
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.sdk.extensions.localizedDescription
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
||||
|
|
@ -21,21 +22,21 @@ import com.tangem.wallet.R
|
|||
*/
|
||||
object SendTransactionFailsDialog {
|
||||
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.CardSdkError): AlertDialog {
|
||||
return create(context, dialog.error.localizedDescription(context))
|
||||
return create(context, dialog.error.localizedDescription(context), dialog.scanResponse)
|
||||
}
|
||||
|
||||
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.BlockchainSdkError): AlertDialog {
|
||||
val errorConverter = BlockchainSdkErrorConverter(context)
|
||||
return create(context, errorConverter.convert(dialog.error))
|
||||
return create(context, errorConverter.convert(dialog.error), dialog.scanResponse)
|
||||
}
|
||||
|
||||
private fun create(context: Context, errorMessage: String): AlertDialog {
|
||||
private fun create(context: Context, errorMessage: String, scanResponse: ScanResponse): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.alert_failed_to_send_transaction_title)
|
||||
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
|
||||
setNeutralButton(R.string.details_row_title_contact_to_support) { _, _ ->
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Send))
|
||||
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
|
||||
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage), scanResponse))
|
||||
}
|
||||
setPositiveButton(R.string.common_cancel) { _, _ -> }
|
||||
setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) }
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ 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
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
|
|
@ -82,6 +84,8 @@ data class DaggerGraphState(
|
|||
val blockchainSDKFactory: BlockchainSDKFactory? = null,
|
||||
val emailSender: EmailSender? = null,
|
||||
val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase? = null,
|
||||
val getFeedbackEmailUseCase: GetFeedbackEmailUseCase? = null,
|
||||
val getCardInfoUseCase: GetCardInfoUseCase? = null,
|
||||
val assetLoader: AssetLoader? = null,
|
||||
val detailsFeatureToggles: DetailsFeatureToggles? = null,
|
||||
val stakingRouter: StakingRouter? = null,
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.core.navigation.feedback
|
||||
|
||||
class DummyFeedbackManager : FeedbackManager {
|
||||
|
||||
override fun sendEmail(type: FeedbackType) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.core.navigation.feedback
|
||||
|
||||
interface FeedbackManager {
|
||||
|
||||
fun sendEmail(type: FeedbackType)
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.core.navigation.feedback
|
||||
|
||||
sealed class FeedbackType {
|
||||
data object RateCanBeBetter : FeedbackType()
|
||||
|
||||
data object ScanFails : FeedbackType()
|
||||
|
||||
data class SendTransactionFailed(val error: String) : FeedbackType()
|
||||
|
||||
data object Feedback : FeedbackType()
|
||||
|
||||
data object Support : FeedbackType()
|
||||
}
|
||||
|
|
@ -37,7 +37,9 @@ dependencies {
|
|||
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
}
|
||||
|
|
@ -9,12 +9,14 @@ import com.tangem.data.feedback.converters.CardInfoConverter
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectMap
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.feedback.models.*
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import timber.log.Timber
|
||||
|
|
@ -25,43 +27,47 @@ import java.io.StringWriter
|
|||
/**
|
||||
* Implementation of [FeedbackRepository]
|
||||
*
|
||||
* @property appPreferencesStore application preferences store
|
||||
* @property userWalletsStore user wallets store
|
||||
* @property walletManagersStore wallet managers store
|
||||
* @property context context for getting app version
|
||||
* @property appPreferencesStore application preferences store
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
* @property walletManagersStore wallet managers store
|
||||
* @property context context for getting app version
|
||||
* @property dispatchers coroutine dispatchers provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultFeedbackRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val walletManagersStore: WalletManagersStore,
|
||||
private val context: Context,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : FeedbackRepository {
|
||||
|
||||
private val blockchainsErrors = MutableStateFlow<Map<UserWalletId, BlockchainErrorInfo>>(emptyMap())
|
||||
|
||||
override suspend fun getUserWalletsInfo(): UserWalletsInfo {
|
||||
override suspend fun getCardInfo(scanResponse: ScanResponse) = CardInfoConverter.convert(value = scanResponse)
|
||||
|
||||
override suspend fun getUserWalletsInfo(userWalletId: UserWalletId?): UserWalletsInfo {
|
||||
return UserWalletsInfo(
|
||||
selectedUserWalletId = getSelectedUserWallet().walletId.stringValue,
|
||||
totalUserWallets = userWalletsStore.getAllSyncOrNull()?.size ?: error("No user wallets found"),
|
||||
selectedUserWalletId = userWalletId?.stringValue ?: "card isn't activated",
|
||||
totalUserWallets = userWalletsListManager.walletsCount,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getCardInfo(): CardInfo {
|
||||
return CardInfoConverter.convert(value = getSelectedUserWallet())
|
||||
}
|
||||
|
||||
override suspend fun getBlockchainInfoList(): List<BlockchainInfo> {
|
||||
override suspend fun getBlockchainInfoList(userWalletId: UserWalletId): List<BlockchainInfo> {
|
||||
return walletManagersStore
|
||||
.getAllSync(userWalletId = getSelectedUserWallet().walletId)
|
||||
.getAllSync(userWalletId = userWalletId)
|
||||
.map(BlockchainInfoConverter::convert)
|
||||
}
|
||||
|
||||
override suspend fun getBlockchainInfo(blockchainId: String, derivationPath: String?): BlockchainInfo? {
|
||||
override suspend fun getBlockchainInfo(
|
||||
userWalletId: UserWalletId,
|
||||
blockchainId: String,
|
||||
derivationPath: String?,
|
||||
): BlockchainInfo? {
|
||||
return walletManagersStore
|
||||
.getSyncOrNull(
|
||||
userWalletId = getSelectedUserWallet().walletId,
|
||||
userWalletId = userWalletId,
|
||||
blockchain = Blockchain.fromId(blockchainId),
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
|
@ -77,18 +83,18 @@ internal class DefaultFeedbackRepository(
|
|||
}
|
||||
|
||||
override fun saveBlockchainErrorInfo(error: BlockchainErrorInfo) {
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync ?: error("UserWallet is not selected")
|
||||
|
||||
blockchainsErrors.update {
|
||||
it.toMutableMap().apply {
|
||||
put(getSelectedUserWallet().walletId, error)
|
||||
put(userWallet.walletId, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getBlockchainErrorInfo(): BlockchainErrorInfo? {
|
||||
return blockchainsErrors.value[getSelectedUserWallet().walletId].also {
|
||||
if (it == null) {
|
||||
Timber.e("Blockchain error info is null for ${getSelectedUserWallet().walletId}")
|
||||
}
|
||||
override suspend fun getBlockchainErrorInfo(userWalletId: UserWalletId): BlockchainErrorInfo? {
|
||||
return blockchainsErrors.value[userWalletId].also {
|
||||
if (it == null) Timber.e("Blockchain error info is null for $userWalletId")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +105,7 @@ internal class DefaultFeedbackRepository(
|
|||
}
|
||||
|
||||
override suspend fun createLogFile(logs: String): File? {
|
||||
return try {
|
||||
return runCatching(dispatchers.io) {
|
||||
val file = File(context.filesDir, LOGS_FILE)
|
||||
file.delete()
|
||||
file.createNewFile()
|
||||
|
|
@ -113,8 +119,8 @@ internal class DefaultFeedbackRepository(
|
|||
fileWriter.close()
|
||||
|
||||
file
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Logs file isn't created")
|
||||
}.getOrElse {
|
||||
Timber.e(it, "Logs file isn't created")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
@ -130,11 +136,6 @@ internal class DefaultFeedbackRepository(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getSelectedUserWallet(): UserWallet {
|
||||
return userWalletsStore.selectedUserWalletOrNull
|
||||
?: error("UserWallet is not selected")
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val LOGS_FILE = "logs.txt"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,28 +2,36 @@ package com.tangem.data.feedback.converters
|
|||
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converter from [UserWallet] to [CardInfo]
|
||||
* Converter from [ScanResponse] to [CardInfo]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object CardInfoConverter : Converter<UserWallet, CardInfo> {
|
||||
internal object CardInfoConverter : Converter<ScanResponse, CardInfo> {
|
||||
|
||||
override fun convert(value: UserWallet): CardInfo {
|
||||
return with(value.scanResponse) {
|
||||
override fun convert(value: ScanResponse): CardInfo {
|
||||
return with(value) {
|
||||
CardInfo(
|
||||
userWalletId = createUserWalletId(scanResponse = value),
|
||||
cardId = card.cardId,
|
||||
firmwareVersion = card.firmwareVersion.stringValue,
|
||||
cardBlockchain = walletData?.blockchain,
|
||||
signedHashesList = card.wallets.map {
|
||||
CardInfo.SignedHashes(curve = it.curve.curve, total = it.totalSignedHashes?.toString())
|
||||
},
|
||||
isImported = value.isImported,
|
||||
isStart2Coin = value.scanResponse.card.isStart2Coin,
|
||||
isImported = value.card.wallets.any(CardDTO.Wallet::isImported),
|
||||
isStart2Coin = value.card.isStart2Coin,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createUserWalletId(scanResponse: ScanResponse): UserWalletId? {
|
||||
return UserWalletIdBuilder.scanResponse(scanResponse = scanResponse).build()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,10 @@ package com.tangem.data.feedback.di
|
|||
import android.content.Context
|
||||
import com.tangem.data.feedback.DefaultFeedbackRepository
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -21,10 +22,17 @@ internal object FeedbackRepositoryModule {
|
|||
@Singleton
|
||||
fun provideFeedbackRepository(
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
walletManagersStore: WalletManagersStore,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): FeedbackRepository {
|
||||
return DefaultFeedbackRepository(appPreferencesStore, userWalletsStore, walletManagersStore, context)
|
||||
return DefaultFeedbackRepository(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
walletManagersStore = walletManagersStore,
|
||||
context = context,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,5 +13,6 @@ dependencies {
|
|||
implementation(deps.jodatime)
|
||||
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.feedback
|
||||
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
|
||||
interface FeedbackManager {
|
||||
|
||||
fun sendEmail(type: FeedbackEmailType)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.feedback
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
||||
/**
|
||||
* UseCase for creating 'CardInfo'
|
||||
*
|
||||
* @property feedbackRepository feedback repository
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class GetCardInfoUseCase(
|
||||
private val feedbackRepository: FeedbackRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(scanResponse: ScanResponse): Either<Throwable, CardInfo> {
|
||||
return catch { feedbackRepository.getCardInfo(scanResponse) }
|
||||
}
|
||||
}
|
||||
|
|
@ -25,23 +25,21 @@ class GetFeedbackEmailUseCase(
|
|||
private val emailMessageBodyResolver = EmailMessageBodyResolver(feedbackRepository)
|
||||
|
||||
suspend operator fun invoke(type: FeedbackEmailType): FeedbackEmail {
|
||||
val cardInfo = feedbackRepository.getCardInfo()
|
||||
|
||||
val formattedLogs = AppLogsFormatter().format(appLogs = feedbackRepository.getAppLogs())
|
||||
|
||||
return FeedbackEmail(
|
||||
address = getAddress(cardInfo),
|
||||
subject = emailSubjectResolver.resolve(type, cardInfo),
|
||||
message = createMessage(type, cardInfo),
|
||||
address = getAddress(type.cardInfo),
|
||||
subject = emailSubjectResolver.resolve(type),
|
||||
message = createMessage(type),
|
||||
file = feedbackRepository.createLogFile(logs = formattedLogs),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAddress(cardInfo: CardInfo): String {
|
||||
return if (cardInfo.isStart2Coin) START2COIN_SUPPORT_EMAIL else TANGEM_SUPPORT_EMAIL
|
||||
private fun getAddress(cardInfo: CardInfo?): String {
|
||||
return if (cardInfo?.isStart2Coin == true) START2COIN_SUPPORT_EMAIL else TANGEM_SUPPORT_EMAIL
|
||||
}
|
||||
|
||||
private suspend fun createMessage(type: FeedbackEmailType, cardInfo: CardInfo): String {
|
||||
private suspend fun createMessage(type: FeedbackEmailType): String {
|
||||
return StringBuilder().apply {
|
||||
val title = emailMessageTitleResolver.resolve(type)
|
||||
append(title)
|
||||
|
|
@ -50,9 +48,7 @@ class GetFeedbackEmailUseCase(
|
|||
|
||||
appendDisclaimerIfNeeded(type)
|
||||
|
||||
skipLine()
|
||||
|
||||
val body = emailMessageBodyResolver.resolve(type, cardInfo)
|
||||
val body = emailMessageBodyResolver.resolve(type)
|
||||
append(body)
|
||||
}.toString()
|
||||
}
|
||||
|
|
@ -62,6 +58,7 @@ class GetFeedbackEmailUseCase(
|
|||
this
|
||||
} else {
|
||||
append(resources.getString(R.string.feedback_data_collection_message))
|
||||
skipLine()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.domain.feedback.models
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
data class CardInfo(
|
||||
val userWalletId: UserWalletId?,
|
||||
val cardId: String,
|
||||
val firmwareVersion: String,
|
||||
val cardBlockchain: String?,
|
||||
|
|
|
|||
|
|
@ -7,15 +7,19 @@ package com.tangem.domain.feedback.models
|
|||
*/
|
||||
sealed interface FeedbackEmailType {
|
||||
|
||||
val cardInfo: CardInfo?
|
||||
|
||||
/** User initiate request yourself. Example, button on DetailsScreen or OnboardingScreen */
|
||||
data object DirectUserRequest : FeedbackEmailType
|
||||
data class DirectUserRequest(override val cardInfo: CardInfo) : FeedbackEmailType
|
||||
|
||||
/** User rate the app as "can be better" */
|
||||
data object RateCanBeBetter : FeedbackEmailType
|
||||
data class RateCanBeBetter(override val cardInfo: CardInfo) : FeedbackEmailType
|
||||
|
||||
/** User has problem with scanning */
|
||||
data object ScanningProblem : FeedbackEmailType
|
||||
data object ScanningProblem : FeedbackEmailType {
|
||||
override val cardInfo: CardInfo? = null
|
||||
}
|
||||
|
||||
/** User has problem with sending transaction */
|
||||
data object TransactionSendingProblem : FeedbackEmailType
|
||||
data class TransactionSendingProblem(override val cardInfo: CardInfo) : FeedbackEmailType
|
||||
}
|
||||
|
|
@ -1,23 +1,29 @@
|
|||
package com.tangem.domain.feedback.repository
|
||||
|
||||
import com.tangem.domain.feedback.models.*
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import java.io.File
|
||||
|
||||
interface FeedbackRepository {
|
||||
|
||||
suspend fun getUserWalletsInfo(): UserWalletsInfo
|
||||
suspend fun getCardInfo(scanResponse: ScanResponse): CardInfo
|
||||
|
||||
suspend fun getCardInfo(): CardInfo
|
||||
suspend fun getUserWalletsInfo(userWalletId: UserWalletId?): UserWalletsInfo
|
||||
|
||||
suspend fun getBlockchainInfoList(): List<BlockchainInfo>
|
||||
|
||||
suspend fun getBlockchainInfo(blockchainId: String, derivationPath: String?): BlockchainInfo?
|
||||
suspend fun getBlockchainInfoList(userWalletId: UserWalletId): List<BlockchainInfo>
|
||||
|
||||
fun getPhoneInfo(): PhoneInfo
|
||||
|
||||
suspend fun getBlockchainInfo(
|
||||
userWalletId: UserWalletId,
|
||||
blockchainId: String,
|
||||
derivationPath: String?,
|
||||
): BlockchainInfo?
|
||||
|
||||
fun saveBlockchainErrorInfo(error: BlockchainErrorInfo)
|
||||
|
||||
suspend fun getBlockchainErrorInfo(): BlockchainErrorInfo?
|
||||
suspend fun getBlockchainErrorInfo(userWalletId: UserWalletId): BlockchainErrorInfo?
|
||||
|
||||
suspend fun getAppLogs(): List<AppLogModel>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,25 +16,33 @@ internal class EmailMessageBodyResolver(
|
|||
private val feedbackRepository: FeedbackRepository,
|
||||
) {
|
||||
|
||||
/** Resolve email message body by [type] using [cardInfo] */
|
||||
suspend fun resolve(type: FeedbackEmailType, cardInfo: CardInfo): String = with(FeedbackDataBuilder()) {
|
||||
/** Resolve email message body by [type] */
|
||||
suspend fun resolve(type: FeedbackEmailType): String = with(FeedbackDataBuilder()) {
|
||||
when (type) {
|
||||
FeedbackEmailType.DirectUserRequest -> addUserRequestBody(cardInfo)
|
||||
FeedbackEmailType.RateCanBeBetter -> addCardAndPhoneInfo(cardInfo)
|
||||
FeedbackEmailType.ScanningProblem -> addScanningProblemBody()
|
||||
FeedbackEmailType.TransactionSendingProblem -> addTransactionSendingProblemBody(cardInfo)
|
||||
is FeedbackEmailType.DirectUserRequest -> addUserRequestBody(type.cardInfo)
|
||||
is FeedbackEmailType.RateCanBeBetter -> addCardAndPhoneInfo(type.cardInfo)
|
||||
is FeedbackEmailType.ScanningProblem -> addScanningProblemBody()
|
||||
is FeedbackEmailType.TransactionSendingProblem -> addTransactionSendingProblemBody(type.cardInfo)
|
||||
}
|
||||
|
||||
return build()
|
||||
}
|
||||
|
||||
private suspend fun FeedbackDataBuilder.addUserRequestBody(cardInfo: CardInfo) {
|
||||
addUserWalletsInfo(userWalletsInfo = feedbackRepository.getUserWalletsInfo())
|
||||
addUserWalletsInfo(userWalletsInfo = feedbackRepository.getUserWalletsInfo(cardInfo.userWalletId))
|
||||
addDelimiter()
|
||||
addCardInfo(cardInfo)
|
||||
addDelimiter()
|
||||
addBlockchainInfoList(blockchainInfoList = feedbackRepository.getBlockchainInfoList())
|
||||
addDelimiter()
|
||||
|
||||
if (cardInfo.userWalletId != null) {
|
||||
val blockchainInfoList = feedbackRepository.getBlockchainInfoList(cardInfo.userWalletId)
|
||||
|
||||
if (blockchainInfoList.isNotEmpty()) {
|
||||
addBlockchainInfoList(blockchainInfoList = blockchainInfoList)
|
||||
addDelimiter()
|
||||
}
|
||||
}
|
||||
|
||||
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
|
||||
}
|
||||
|
||||
|
|
@ -46,9 +54,11 @@ internal class EmailMessageBodyResolver(
|
|||
addCardInfo(cardInfo)
|
||||
addDelimiter()
|
||||
|
||||
val blockchainError = feedbackRepository.getBlockchainErrorInfo()
|
||||
val userWalletId = requireNotNull(cardInfo.userWalletId) { "UserWalletId must be not null" }
|
||||
val blockchainError = feedbackRepository.getBlockchainErrorInfo(userWalletId = userWalletId)
|
||||
val blockchainInfo = blockchainError?.let {
|
||||
feedbackRepository.getBlockchainInfo(
|
||||
userWalletId = userWalletId,
|
||||
blockchainId = blockchainError.blockchainId,
|
||||
derivationPath = blockchainError.derivationPath,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ internal class EmailMessageTitleResolver(private val resources: Resources) {
|
|||
/** Resolve email message title by [type] */
|
||||
fun resolve(type: FeedbackEmailType): String {
|
||||
return when (type) {
|
||||
FeedbackEmailType.DirectUserRequest -> R.string.feedback_preface_support
|
||||
FeedbackEmailType.RateCanBeBetter -> R.string.feedback_preface_rate_negative
|
||||
FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed
|
||||
FeedbackEmailType.TransactionSendingProblem -> R.string.feedback_preface_tx_failed
|
||||
is FeedbackEmailType.DirectUserRequest -> R.string.feedback_preface_support
|
||||
is FeedbackEmailType.RateCanBeBetter -> R.string.feedback_preface_rate_negative
|
||||
is FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed
|
||||
is FeedbackEmailType.TransactionSendingProblem -> R.string.feedback_preface_tx_failed
|
||||
}
|
||||
.let(resources::getString)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.domain.feedback.utils
|
|||
|
||||
import android.content.res.Resources
|
||||
import com.tangem.domain.feedback.R
|
||||
import com.tangem.domain.feedback.models.CardInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
|
||||
/**
|
||||
|
|
@ -14,19 +13,19 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
|
|||
*/
|
||||
internal class EmailSubjectResolver(private val resources: Resources) {
|
||||
|
||||
/** Resolve email message body by [type] using [cardInfo] */
|
||||
fun resolve(type: FeedbackEmailType, cardInfo: CardInfo): String {
|
||||
/** Resolve email message body by [type] */
|
||||
fun resolve(type: FeedbackEmailType): String {
|
||||
return when (type) {
|
||||
FeedbackEmailType.DirectUserRequest -> {
|
||||
if (cardInfo.isStart2Coin) {
|
||||
is FeedbackEmailType.DirectUserRequest -> {
|
||||
if (type.cardInfo.isStart2Coin) {
|
||||
R.string.feedback_subject_support
|
||||
} else {
|
||||
R.string.feedback_subject_support_tangem
|
||||
}
|
||||
}
|
||||
FeedbackEmailType.RateCanBeBetter -> R.string.feedback_subject_rate_negative
|
||||
FeedbackEmailType.ScanningProblem -> R.string.feedback_subject_scan_failed
|
||||
FeedbackEmailType.TransactionSendingProblem -> R.string.feedback_subject_tx_failed
|
||||
is FeedbackEmailType.RateCanBeBetter -> R.string.feedback_subject_rate_negative
|
||||
is FeedbackEmailType.ScanningProblem -> R.string.feedback_subject_scan_failed
|
||||
is FeedbackEmailType.TransactionSendingProblem -> R.string.feedback_subject_tx_failed
|
||||
}
|
||||
.let(resources::getString)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import java.math.BigDecimal
|
|||
|
||||
sealed interface LegacyAction : Action {
|
||||
|
||||
object SendEmailRateCanBeBetter : LegacyAction
|
||||
data class SendEmailRateCanBeBetter(val scanResponse: ScanResponse) : LegacyAction
|
||||
|
||||
/**
|
||||
* Initiate an onboarding process.
|
||||
|
|
@ -34,5 +34,6 @@ sealed interface LegacyAction : Action {
|
|||
val fee: BigDecimal?,
|
||||
val destinationAddress: String?,
|
||||
val errorMessage: String,
|
||||
val scanResponse: ScanResponse,
|
||||
) : LegacyAction
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ dependencies {
|
|||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.card)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.features.details.component.preview
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.navigation.DummyRouter
|
||||
import com.tangem.core.navigation.feedback.DummyFeedbackManager
|
||||
import com.tangem.core.navigation.url.DummyUrlOpener
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
|
|
@ -19,8 +18,7 @@ internal class PreviewDetailsComponent : DetailsComponent {
|
|||
ItemsBuilder(
|
||||
router = DummyRouter(),
|
||||
urlOpener = DummyUrlOpener(),
|
||||
feedbackManager = DummyFeedbackManager(),
|
||||
).buldAll(isWalletConnectAvailable = true)
|
||||
).buildAll(isWalletConnectAvailable = true, onSupportClick = {})
|
||||
}
|
||||
|
||||
private val previewFooter = DetailsFooterUM(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ import com.tangem.core.decompose.di.ComponentScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.feedback.FeedbackManager
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.entity.DetailsItemUM
|
||||
|
|
@ -33,6 +37,9 @@ internal class DetailsModel @Inject constructor(
|
|||
private val checkIsWalletConnectAvailableUseCase: CheckIsWalletConnectAvailableUseCase,
|
||||
private val router: Router,
|
||||
private val paramsContainer: ParamsContainer,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val feedbackManager: FeedbackManager,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -66,7 +73,22 @@ internal class DetailsModel @Inject constructor(
|
|||
false
|
||||
}
|
||||
|
||||
items.value = itemsBuilder.buldAll(isWalletConnectAvailable)
|
||||
items.value = itemsBuilder.buildAll(
|
||||
isWalletConnectAvailable = isWalletConnectAvailable,
|
||||
onSupportClick = ::sendFeedback,
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendFeedback() {
|
||||
modelScope.launch {
|
||||
val scanResponse = getSelectedWalletSyncUseCase().getOrNull()?.scanResponse
|
||||
?: error("Selected wallet is null")
|
||||
|
||||
val cardInfo = getCardInfoUseCase(scanResponse = scanResponse).getOrNull()
|
||||
?: error("CardInfo must be not null")
|
||||
|
||||
feedbackManager.sendEmail(type = FeedbackEmailType.DirectUserRequest(cardInfo))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateState(items: ImmutableList<DetailsItemUM>) {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ package com.tangem.features.details.utils
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.feedback.FeedbackManager
|
||||
import com.tangem.core.navigation.feedback.FeedbackType
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.components.block.model.BlockUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
|
@ -21,18 +19,18 @@ import javax.inject.Inject
|
|||
internal class ItemsBuilder @Inject constructor(
|
||||
private val router: Router,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val feedbackManager: FeedbackManager,
|
||||
) {
|
||||
|
||||
suspend fun buldAll(isWalletConnectAvailable: Boolean): ImmutableList<DetailsItemUM> = buildList {
|
||||
buildWalletConnectBlock(isWalletConnectAvailable)?.let(::add)
|
||||
buildUserWalletListBlock().let(::add)
|
||||
buildShopBlock().let(::add)
|
||||
buildSettingsBlock().let(::add)
|
||||
buildSupportBlock().let(::add)
|
||||
}.toImmutableList()
|
||||
fun buildAll(isWalletConnectAvailable: Boolean, onSupportClick: () -> Unit): ImmutableList<DetailsItemUM> =
|
||||
buildList {
|
||||
buildWalletConnectBlock(isWalletConnectAvailable)?.let(::add)
|
||||
buildUserWalletListBlock().let(::add)
|
||||
buildShopBlock().let(::add)
|
||||
buildSettingsBlock().let(::add)
|
||||
buildSupportBlock(onSupportClick).let(::add)
|
||||
}.toImmutableList()
|
||||
|
||||
private suspend fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean): DetailsItemUM? {
|
||||
private fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean): DetailsItemUM? {
|
||||
return if (isWalletConnectAvailable) {
|
||||
DetailsItemUM.WalletConnect(
|
||||
onClick = { router.push(AppRoute.WalletConnectSessions) },
|
||||
|
|
@ -83,7 +81,7 @@ internal class ItemsBuilder @Inject constructor(
|
|||
}.toImmutableList(),
|
||||
)
|
||||
|
||||
private fun buildSupportBlock(): DetailsItemUM = DetailsItemUM.Basic(
|
||||
private fun buildSupportBlock(onClick: () -> Unit): DetailsItemUM = DetailsItemUM.Basic(
|
||||
id = "support",
|
||||
items = persistentListOf(
|
||||
DetailsItemUM.Basic.Item(
|
||||
|
|
@ -91,7 +89,7 @@ internal class ItemsBuilder @Inject constructor(
|
|||
block = BlockUM(
|
||||
text = resourceReference(R.string.details_send_feedback),
|
||||
iconRes = R.drawable.ic_comment_24,
|
||||
onClick = { feedbackManager.sendEmail(FeedbackType.Feedback) },
|
||||
onClick = onClick,
|
||||
),
|
||||
),
|
||||
DetailsItemUM.Basic.Item(
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ dependencies {
|
|||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.qrScanning)
|
||||
implementation(projects.domain.qrScanning.models)
|
||||
implementation(projects.domain.settings)
|
||||
|
|
|
|||
|
|
@ -530,6 +530,7 @@ internal class SendViewModel @Inject constructor(
|
|||
fee = feeValue,
|
||||
destinationAddress = recipient,
|
||||
errorMessage = errorMessage,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,7 +204,12 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
viewModelScope.launch(dispatchers.main) {
|
||||
neverToSuggestRateAppUseCase()
|
||||
|
||||
reduxStateHolder.dispatch(LegacyAction.SendEmailRateCanBeBetter)
|
||||
reduxStateHolder.dispatch(
|
||||
LegacyAction.SendEmailRateCanBeBetter(
|
||||
scanResponse = getSelectedUserWallet()?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +255,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getSelectedUserWallet(): UserWallet? {
|
||||
private fun getSelectedUserWallet(): UserWallet? {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
return getUserWalletUseCase(userWalletId).getOrElse {
|
||||
Timber.e(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue