diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt index 256bd62a5f..e30b53c765 100644 --- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt +++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt @@ -7,6 +7,7 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.wallets.legacy.asLockable import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.dispatchWithMain import kotlinx.coroutines.* import timber.log.Timber import kotlin.time.Duration @@ -103,12 +104,14 @@ internal class LockUserWalletsTimer( |- Millis passed: ${currentTime - startTime} """.trimIndent(), ) - userWalletsListManager.lock() + if (preferencesStorage.wasApplicationStopped) { preferencesStorage.shouldOpenWelcomeScreenOnResume = true } else { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } + + userWalletsListManager.lock() } } } diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index d5d7000089..2cc0b685f7 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -19,12 +19,14 @@ import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat +import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -35,10 +37,13 @@ import com.tangem.data.card.sdk.CardSdkLifecycleObserver import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase +import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles import com.tangem.domain.wallets.legacy.asLockable import com.tangem.feature.qrscanning.QrScanningRouter +import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.features.managetokens.navigation.ManageTokensUi import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.tester.api.TesterRouter @@ -95,6 +100,7 @@ val userWalletsListManagerSafe: UserWalletsListManager? val userWalletsListManager: UserWalletsListManager get() = userWalletsListManagerSafe!! +@Suppress("LargeClass") @AndroidEntryPoint class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { @@ -141,6 +147,15 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles + @Inject + lateinit var getPolkadotCheckHasResetUseCase: GetPolkadotCheckHasResetUseCase + + @Inject + lateinit var getPolkadotCheckHasImmortalUseCase: GetPolkadotCheckHasImmortalUseCase + + @Inject + lateinit var analyticsEventsHandler: AnalyticsEventHandler + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -168,6 +183,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac checkForNotificationPermission() observeStateUpdates() + observePolkadotAccountHealthCheck() if (intent != null) { deepLinksRegistry.launch(intent) @@ -457,4 +473,25 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0) } } + + private fun observePolkadotAccountHealthCheck() { + lifecycleScope.launch { + getPolkadotCheckHasResetUseCase() + .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .distinctUntilChanged() + .collect { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotAccountReset(it.second)) + } + } + lifecycleScope.launch { + getPolkadotCheckHasImmortalUseCase() + .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .distinctUntilChanged() + .collect { + analyticsEventsHandler.send( + WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it.second), + ) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index cbbcee8526..5887deeadc 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -46,7 +46,7 @@ class DialogManager : StoreSubscriber { dialog = when (state.dialog) { is AppDialog.SimpleOkDialogRes -> SimpleOkDialog.create(state.dialog, context) - is StateDialog.ScanFailsDialog -> ScanFailsDialog.create(context) + is StateDialog.ScanFailsDialog -> ScanFailsDialog.create(context, state.dialog.source) is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context) is AppDialog.RussianCardholdersWarningDialog -> RussianCardholdersWarningBottomSheetDialog( diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index e3b376b612..a9c23d448f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.redux.global import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.navigation.StateDialog import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.models.ChatConfig @@ -51,7 +52,11 @@ sealed class GlobalAction : Action { } object ScanFailsCounter { - data class ChooseBehavior(val result: CompletionResult) : GlobalAction() + data class ChooseBehavior( + val result: CompletionResult, + val analyticsSource: AnalyticsParam.ScreensSources, + ) : GlobalAction() + object Reset : GlobalAction() object Increment : GlobalAction() } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 79d6ed3a69..921898e383 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.StateDialog import com.tangem.datasource.config.models.Config @@ -54,14 +55,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) { when (action.result) { is CompletionResult.Success -> store.dispatch(GlobalAction.ScanFailsCounter.Reset) is CompletionResult.Failure -> { - if (action.result.error is TangemSdkError.UserCancelled) { - store.dispatch(GlobalAction.ScanFailsCounter.Increment) - if (store.state.globalState.scanCardFailsCounter >= 2) { - store.dispatchDialogShow(StateDialog.ScanFailsDialog) - } - } else { - store.dispatch(GlobalAction.ScanFailsCounter.Reset) - } + handleFailureChooseBehaviour(action.result, action.analyticsSource) } } } @@ -184,6 +178,26 @@ private fun handleAction(action: Action, appState: () -> AppState?) { } } +private fun handleFailureChooseBehaviour( + result: CompletionResult.Failure, + analyticsSource: AnalyticsParam.ScreensSources, +) { + if (result.error is TangemSdkError.UserCancelled) { + store.dispatch(GlobalAction.ScanFailsCounter.Increment) + if (store.state.globalState.scanCardFailsCounter >= 2) { + val scanFailsSource = when (analyticsSource) { + is AnalyticsParam.ScreensSources.SignIn -> StateDialog.ScanFailsSource.SIGN_IN + is AnalyticsParam.ScreensSources.Settings -> StateDialog.ScanFailsSource.SETTINGS + is AnalyticsParam.ScreensSources.Intro -> StateDialog.ScanFailsSource.INTRO + else -> StateDialog.ScanFailsSource.MAIN + } + store.dispatchDialogShow(StateDialog.ScanFailsDialog(scanFailsSource)) + } + } else { + store.dispatch(GlobalAction.ScanFailsCounter.Reset) + } +} + private fun restoreAppCurrency() { scope.launch { val currency = store.inject(DaggerGraphState::appCurrencyRepository) diff --git a/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt index 70ad852f71..d59f668687 100644 --- a/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/ui/ScanFailsDialog.kt @@ -4,7 +4,9 @@ import android.content.Context import androidx.appcompat.app.AlertDialog 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.core.navigation.StateDialog import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.feedback.ScanFailsEmail import com.tangem.tap.common.redux.global.GlobalAction @@ -15,12 +17,18 @@ import com.tangem.wallet.R [REDACTED_AUTHOR] */ object ScanFailsDialog { - fun create(context: Context): AlertDialog { + fun create(context: Context, source: StateDialog.ScanFailsSource): AlertDialog { return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply { setTitle(context.getString(R.string.common_warning)) setMessage(R.string.alert_troubleshooting_scan_card_title) setPositiveButton(R.string.alert_button_request_support) { _, _ -> - Analytics.send(Basic.ButtonSupport()) + val sourceAnalytics = when (source) { + StateDialog.ScanFailsSource.MAIN -> AnalyticsParam.ScreensSources.Main + StateDialog.ScanFailsSource.SIGN_IN -> AnalyticsParam.ScreensSources.SignIn + StateDialog.ScanFailsSource.SETTINGS -> AnalyticsParam.ScreensSources.Settings + StateDialog.ScanFailsSource.INTRO -> AnalyticsParam.ScreensSources.Intro + } + Analytics.send(Basic.ButtonSupport(sourceAnalytics)) store.dispatch(GlobalAction.SendEmail(ScanFailsEmail())) } setNeutralButton(R.string.common_cancel) { _, _ -> } diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 2ff65f28a2..9a0f838f4a 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -4,6 +4,9 @@ import android.content.Context import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase +import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager @@ -57,4 +60,20 @@ internal object ActivityModule { fun provideActivityDelayedWorkCoroutineScope(): CoroutineScope { return CoroutineScope(SupervisorJob() + Dispatchers.IO) } + + @Provides + @Singleton + fun provideGetPolkadotCheckHasResetUseCase( + polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, + ): GetPolkadotCheckHasResetUseCase { + return GetPolkadotCheckHasResetUseCase(polkadotAccountHealthCheckRepository) + } + + @Provides + @Singleton + fun provideGetPolkadotCheckHasImmortalUseCase( + polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, + ): GetPolkadotCheckHasImmortalUseCase { + return GetPolkadotCheckHasImmortalUseCase(polkadotAccountHealthCheckRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 54089b880e..e4cf06239e 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -343,4 +343,12 @@ internal object TokensDomainModule { ): IsAmountSubtractAvailableUseCase { return IsAmountSubtractAvailableUseCase(currenciesRepository, dispatchers) } + + @Provides + @ViewModelScoped + fun provideRunPolkadotAccountHealthCheckUseCase( + repository: PolkadotAccountHealthCheckRepository, + ): RunPolkadotAccountHealthCheckUseCase { + return RunPolkadotAccountHealthCheckUseCase(repository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt index 06df7c2274..d4b5ac160c 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt @@ -2,7 +2,7 @@ package com.tangem.tap.domain.scanCard import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.extensions.inject @@ -27,7 +27,7 @@ internal class DefaultScanCardProcessor : ScanCardProcessor { @Suppress("LongParameterList") override suspend fun scan( - analyticsEvent: AnalyticsEvent?, + analyticsSource: AnalyticsParam.ScreensSources, cardId: String?, onProgressStateChange: suspend (showProgress: Boolean) -> Unit, onWalletNotCreated: suspend () -> Unit, @@ -37,7 +37,7 @@ internal class DefaultScanCardProcessor : ScanCardProcessor { ) { if (isNewCardScanningEnabled) { UseCaseScanProcessor.scan( - analyticsEvent, + analyticsSource, cardId, onProgressStateChange, onWalletNotCreated, @@ -47,7 +47,7 @@ internal class DefaultScanCardProcessor : ScanCardProcessor { ) } else { LegacyScanProcessor.scan( - analyticsEvent, + analyticsSource, cardId, onProgressStateChange, onWalletNotCreated, diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index cf1c9f95be..ce649ece46 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -7,6 +7,8 @@ import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.TapWorkarounds.canSkipBackup @@ -45,7 +47,7 @@ internal object LegacyScanProcessor { @Suppress("LongParameterList") suspend fun scan( - analyticsEvent: AnalyticsEvent?, + analyticsSource: AnalyticsParam.ScreensSources, cardId: String?, onProgressStateChange: suspend (showProgress: Boolean) -> Unit, onWalletNotCreated: suspend () -> Unit, @@ -59,7 +61,8 @@ internal object LegacyScanProcessor { val result = tangemSdkManager.scanProduct(cardId) - store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + val analyticsEvent = Basic.CardWasScanned(analyticsSource) + store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result, analyticsSource)) result .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt index 3a0902520a..9578e1ffe1 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -4,7 +4,7 @@ import arrow.fx.coroutines.resourceScope import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -44,7 +44,7 @@ internal object UseCaseScanProcessor { @Suppress("LongParameterList") suspend fun scan( - analyticsEvent: AnalyticsEvent?, + analyticsSource: AnalyticsParam.ScreensSources, cardId: String?, onProgressStateChange: suspend (showProgress: Boolean) -> Unit, onWalletNotCreated: suspend () -> Unit, @@ -55,9 +55,7 @@ internal object UseCaseScanProcessor { val scanCardUseCase = store.inject(DaggerGraphState::scanCardUseCase) val chains = buildList { add(FailedScansCounterChain(UseCaseScanProcessor::showMaxUnsuccessfulScansReachedDialog)) - if (analyticsEvent != null) { - add(AnalyticsChain(analyticsEvent)) - } + add(AnalyticsChain(Basic.CardWasScanned(analyticsSource))) add(DisclaimerChain(store, disclaimerWillShow)) add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager)) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 160d433012..99418a7aa0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -81,6 +81,10 @@ internal class BiometricUserWalletsListManager( state.update { State() } } + override fun isLockable(): Boolean { + return true + } + override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { if (state.value.selectedUserWalletId == userWalletId) { return@catching findSelectedUserWallet()!! diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 1e01c26cd2..762ba82f54 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -118,6 +118,10 @@ internal class GeneralUserWalletsListManager( } } + override fun isLockable(): Boolean { + return implementation.value.isLockable() + } + private fun subscribeOnCurrentManager() { appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false) .distinctUntilChanged() diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index eedc68771b..31ef3366dd 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -85,6 +85,10 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { state.value.userWallet ?: walletNotFound() } + override fun isLockable(): Boolean { + return false + } + private fun saveInternal(userWallet: UserWallet): CompletionResult = catching { state.update { prevState -> prevState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 90aa10f2cd..654edd4102 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -7,7 +7,6 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.core.UserCodeRequestPolicy import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.core.ui.extensions.TextReference @@ -580,7 +579,7 @@ class DetailsMiddleware { ) store.inject(DaggerGraphState::scanCardProcessor).scan( - analyticsEvent = Basic.CardWasScanned(CoreAnalyticsParam.ScannedFrom.MyWallets), + analyticsSource = CoreAnalyticsParam.ScreensSources.Settings, onWalletNotCreated = { // No need to rollback policy, continue with the policy set before the card scan store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 8453733f19..1819ad46c5 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -146,7 +147,7 @@ internal class DetailsViewModel( } private fun sendFeedback() { - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Settings)) if (feedbackManagerFeatureToggles.isLocalLogsEnabled) { mainScope.launch { val email = getSupportFeedbackEmailUseCase() diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index cb2d157825..2266971dad 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -1,8 +1,5 @@ package com.tangem.tap.features.home.redux -import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.Basic import kotlinx.coroutines.CoroutineScope import org.rekotlin.Action @@ -16,11 +13,9 @@ sealed class HomeAction : Action { /** * Action for scanning card * - * @property analyticsEvent analytics event * @property scope lifecycle scope. It will be canceled when lifecycle-aware component is destroyed */ data class ReadCard( - val analyticsEvent: AnalyticsEvent? = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Introduction), val scope: CoroutineScope, ) : HomeAction() diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 18ec4fb565..40110a5a22 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -5,7 +5,7 @@ import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -60,7 +60,7 @@ private fun handleHomeAction(action: Action) { } is HomeAction.ReadCard -> { action.scope.launch { - readCard(action.analyticsEvent) + readCard() } } is HomeAction.GoToShop -> { @@ -76,13 +76,13 @@ private fun handleHomeAction(action: Action) { } } -private suspend fun readCard(analyticsEvent: AnalyticsEvent?) { +private suspend fun readCard() { store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, ) store.inject(DaggerGraphState::scanCardProcessor).scan( - analyticsEvent = analyticsEvent, + analyticsSource = AnalyticsParam.ScreensSources.Intro, onProgressStateChange = { showProgress -> if (showProgress) { store.dispatch(HomeAction.ScanInProgress(scanInProgress = true)) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 4030b48f6b..050f08d79e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -17,6 +17,7 @@ import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.tap.* import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter import com.tangem.tap.common.extensions.* +import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.proxy.redux.DaggerGraphState import kotlinx.coroutines.delay @@ -42,7 +43,8 @@ object OnboardingHelper { response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> { val emptyWallets = response.card.wallets.isEmpty() val activationInProgress = onboardingManager?.isActivationInProgress(cardId) - val isNoBackup = response.card.backupStatus == CardDTO.BackupStatus.NoBackup + val isNoBackup = response.card.backupStatus == CardDTO.BackupStatus.NoBackup && + !DemoHelper.isDemoCard(response) emptyWallets || activationInProgress == true || isNoBackup } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt index 936a202358..5e0028b00d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt @@ -5,6 +5,7 @@ import android.view.MenuInflater import android.view.MenuItem 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.tap.common.feedback.SupportInfo import com.tangem.tap.common.redux.global.GlobalAction @@ -21,7 +22,7 @@ class OnboardingMenuProvider : MenuProvider { override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) { R.id.menu_item_chat_support -> { - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) // changed on email support [REDACTED_TASK_KEY] store.dispatch(GlobalAction.SendEmail(SupportInfo())) true diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index deb5bb4873..5c7b1e8589 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -22,6 +22,7 @@ import com.tangem.common.CardIdFormatter import com.tangem.common.CompletionResult import com.tangem.common.core.CardIdDisplayFormat import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.ui.extensions.setStatusBarColor import com.tangem.domain.common.util.cardTypesResolver @@ -502,7 +503,7 @@ class OnboardingWalletFragment : private fun makeSeedPhraseRouter(): SeedPhraseRouter = SeedPhraseRouter( onBack = ::legacyOnBackHandler, onOpenChat = { - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) // changed on email support [REDACTED_TASK_KEY] store.dispatch(GlobalAction.SendEmail(SupportInfo())) }, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt index ddec9cdfb3..ace9edcf19 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletActivationErrorDialog.kt @@ -4,6 +4,7 @@ import android.app.Dialog import android.content.Context 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.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.feedback.SupportInfo @@ -21,7 +22,7 @@ object WalletActivationErrorDialog { setPositiveButton(R.string.common_ok) { _, _ -> dialog.onConfirm() } setNegativeButton(R.string.common_support) { _, _ -> // changed on email support [REDACTED_TASK_KEY] - Analytics.send(Basic.ButtonSupport()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro)) store.dispatch(GlobalAction.SendEmail(SupportInfo())) } setOnDismissListener { store.dispatchDialogHide() } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 67272db72d..5f110e31a9 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -8,7 +8,6 @@ import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.isLockable import com.tangem.domain.wallets.models.UserWallet import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -209,7 +208,7 @@ internal class SaveWalletMiddleware { } private suspend fun provideLockableUserWalletsListManagerIfNot() { - if (store.state.globalState.userWalletsListManager?.isLockable == true) return + if (store.state.globalState.userWalletsListManager?.isLockable() == true) return val context = foregroundActivityObserver.foregroundActivity?.applicationContext.guard { val error = IllegalStateException("No activities in foreground") diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt index 9cf4359f72..cf27394348 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/RequestFeeErrorDialog.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.send.ui.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog import com.tangem.core.analytics.Analytics +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.tap.common.feedback.SendTransactionFailedEmail import com.tangem.tap.common.redux.global.GlobalAction @@ -21,7 +22,7 @@ object RequestFeeErrorDialog { setTitle(R.string.common_fee_error) 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()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Send)) store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) } setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt index f862ff5e94..5687caa590 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/SendTransactionFailsDialog.kt @@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError 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.sdk.extensions.localizedDescription import com.tangem.tap.common.extensions.stripZeroPlainString @@ -33,7 +34,7 @@ object SendTransactionFailsDialog { 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()) + Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Send)) store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage))) } setPositiveButton(R.string.common_cancel) { _, _ -> } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index b4009829f8..dd239f1135 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -169,7 +169,7 @@ internal class WelcomeMiddleware { isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, ) store.inject(DaggerGraphState::scanCardProcessor).scan( - analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.SignIn), + analyticsSource = AnalyticsParam.ScreensSources.SignIn, onSuccess = { scanResponse -> scope.launch { onCardScanned(scanResponse) } }, diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 5f53ab40d7..a20223d2cb 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -57,11 +57,13 @@ sealed class AnalyticsParam { object BlockchainSdk : Error("Blockchain Sdk Error") } - sealed class ScannedFrom(val value: String) { - object Introduction : ScannedFrom("Introduction") - object Main : ScannedFrom("Main") - object SignIn : ScannedFrom("Sign In") - object MyWallets : ScannedFrom("My Wallets") + sealed class ScreensSources(val value: String) { + data object Settings : ScreensSources("Settings") + data object Main : ScreensSources("Main") + data object SignIn : ScreensSources("Sign In") + data object Send : ScreensSources("Send") + data object Intro : ScreensSources("Introduction") + data object MyWallets : ScreensSources("My Wallets") } sealed class TxSentFrom(val value: String) { diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt index 8820f8ff12..9f39ae5bdd 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt @@ -7,7 +7,7 @@ sealed class Basic( ) : AnalyticsEvent("Basic", event, params, error) { class CardWasScanned( - source: AnalyticsParam.ScannedFrom, + source: AnalyticsParam.ScreensSources, ) : Basic( event = "Card Was Scanned", params = mapOf( @@ -76,5 +76,10 @@ sealed class Basic( class WalletOpened : Basic(event = "Wallet Opened") - class ButtonSupport : Basic("Request Support") + class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic( + event = "Request Support", + params = mapOf( + AnalyticsParam.SOURCE to source.value, + ), + ) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt index 2b1de6b7b0..905fe4f767 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt @@ -72,6 +72,12 @@ class AppPreferencesStore( return this[key]?.let(adapter::fromJson).orEmpty() } + /** Get set of data [T] by string [key] */ + inline fun MutablePreferences.getObjectSet(key: Preferences.Key): Set? { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) + return this[key]?.let(adapter::fromJson) + } + /** * Set data [T] by string [key] to [MutablePreferences] * @@ -97,4 +103,10 @@ class AppPreferencesStore( this[key] = adapter.toJson(value) } + + /** Sets set of data [T] by string [key] to [MutablePreferences] */ + inline fun MutablePreferences.setObjectSet(key: Preferences.Key, value: Set) { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) + this[key] = adapter.toJson(value) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index ab0ab7c350..bec274ae30 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -63,6 +63,16 @@ object PreferencesKeys { val APP_LOGS_KEY by lazy { stringPreferencesKey(name = "app_logs") } + val POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY by lazy { + stringPreferencesKey(name = "POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX") + } + val POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY by lazy { + stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS") + } + val POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY by lazy { + stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS") + } + val SEND_TAP_HELP_PREVIEW_KEY by lazy { booleanPreferencesKey(name = "sendTapHelpPreview") } fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region") diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt index cd1facee55..aa3171803c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt @@ -128,6 +128,15 @@ suspend inline fun AppPreferencesStore.getObjectMap(key: Preferences val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java) val adapter = moshi.adapter>(type) + return data.firstOrNull() + ?.get(key) + ?.let(adapter::fromJson) + .orEmpty() +} + +/** Get set of data [T] by string [key], or empty if data is not found */ +suspend inline fun AppPreferencesStore.getObjectSetSync(key: Preferences.Key): Set { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) return data.firstOrNull() ?.get(key) ?.let(adapter::fromJson) diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt b/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt index 416307b644..75aa1f0579 100644 --- a/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt +++ b/core/navigation/src/main/java/com/tangem/core/navigation/StateDialog.kt @@ -2,5 +2,9 @@ package com.tangem.core.navigation interface StateDialog { - object ScanFailsDialog : StateDialog + data class ScanFailsDialog(val source: ScanFailsSource) : StateDialog + + enum class ScanFailsSource { + MAIN, SIGN_IN, SETTINGS, INTRO; + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 05c56cfaf9..062692cd06 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -119,4 +119,18 @@ internal object TokensDataModule { fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository { return DefaultCurrencyChecksRepository(walletManagersFacade = walletManagersFacade) } + + @Provides + @Singleton + fun providePolkadotAccountHealthCheckRepository( + walletManagersFacade: WalletManagersFacade, + appPreferencesStore: AppPreferencesStore, + dispatchers: CoroutineDispatcherProvider, + ): PolkadotAccountHealthCheckRepository { + return DefaultPolkadotAccountHealthCheckRepository( + walletManagersFacade = walletManagersFacade, + appPreferencesStore = appPreferencesStore, + dispatchers = dispatchers, + ) + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index 44096bdcd7..f25a65291c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest import timber.log.Timber +@Suppress("LongParameterList") internal class DefaultNetworksRepository( private val networksStatusesStore: NetworksStatusesStore, private val walletManagersFacade: WalletManagersFacade, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt new file mode 100644 index 0000000000..049991280b --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt @@ -0,0 +1,179 @@ +package com.tangem.data.tokens.repository + +import androidx.datastore.preferences.core.Preferences +import com.tangem.blockchain.blockchains.polkadot.AccountCheckProvider +import com.tangem.blockchain.blockchains.polkadot.network.accounthealthcheck.ExtrinsicListItemResponse +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY +import com.tangem.datasource.local.preferences.utils.getObjectMap +import com.tangem.datasource.local.preferences.utils.getObjectSetSync +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap +import kotlin.collections.set + +internal class DefaultPolkadotAccountHealthCheckRepository( + private val walletManagersFacade: WalletManagersFacade, + private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, +) : PolkadotAccountHealthCheckRepository { + + private val hasImmortalTransaction = MutableSharedFlow>() + private val hasResetTransaction = MutableSharedFlow>() + + private val mutex = Mutex() + private val mutexes = ConcurrentHashMap() + + override suspend fun runCheck(userWalletId: UserWalletId, network: Network) { + // Run Polkadot account health check + if (Blockchain.fromId(network.id.value) != Blockchain.Polkadot) return + + val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) + val address = requireNotNull(walletManager?.wallet?.address) { + Timber.e("Address is null") + return + } + val accountCheckProvider = requireNotNull(walletManager as? AccountCheckProvider) { + Timber.e("Unable to cast wallet manager to AccountCheckProvider") + return + } + + // use a separate mutexForKey for each key to avoid multiple calls block() to the same key + // also used mutex to safe create mutexForKey, otherwise it can lead to multiple calls for the same key + val mutexForKey = mutex.withLock { mutexes.getOrPut(address) { Mutex() } } + mutexForKey.withLock { + withContext(dispatchers.io) { + checkHasReset(accountCheckProvider, address) + checkHasImmortal(accountCheckProvider, address) + } + } + } + + override fun subscribeToHasImmortalResults() = hasImmortalTransaction.asSharedFlow() + + override fun subscribeToHasResetResults() = hasResetTransaction.asSharedFlow() + + private suspend fun checkHasReset(polkadotManager: AccountCheckProvider, address: String) { + val checkedAddresses = appPreferencesStore.getObjectSetSync(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY) + if (checkedAddresses.contains(address)) return + runCatching { + val accountInfo = requireNotNull(polkadotManager.getAccountInfo().account) { + Timber.e("Account info is null") + } + val nonce = accountInfo.nonce + val extrinsicCount = accountInfo.countExtrinsic + + // Account was reset + if (nonce != null && extrinsicCount != null) { + val hasReset = nonce < extrinsicCount + updateChecked(address, POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY) + hasResetTransaction.emit(address to hasReset) + } + }.onFailure { + if ((it as? BlockchainSdkError.CustomError)?.customMessage == ACCOUNT_NOT_FOUND) { + updateChecked(address, POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY) + } + } + } + + private suspend fun checkHasImmortal(accountCheckerProvider: AccountCheckProvider, address: String) { + val checkedAddresses = + appPreferencesStore.getObjectSetSync(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY) + if (checkedAddresses.contains(address)) return + + runCatching { + do { + // Getting batch of extrinsics to check + val lastChecked = appPreferencesStore.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY) + val lastExtrinsic = lastChecked[address] + val extrinsicListResult = accountCheckerProvider.getExtrinsicList(afterExtrinsicId = lastExtrinsic) + extrinsicListResult.extrinsic + ?.forEach { tx -> + // Checking extrinsic one by one + if (checkTx(tx, address, accountCheckerProvider)) return + } + } while (!extrinsicListResult.extrinsic.isNullOrEmpty()) + + // We checked all transactions up to current moment and did not found an `immortal` transaction + hasImmortalTransaction.emit(address to false) + updateChecked(address, POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY) + clearLastCheckedTransaction(address) + } + } + + private suspend fun checkTx( + tx: ExtrinsicListItemResponse, + address: String, + accountCheckerProvider: AccountCheckProvider, + ): Boolean { + val hash = tx.hash + val id = tx.id + if (hash != null && id != null) { + val details = accountCheckerProvider.getExtrinsicDetail(hash) + + // We found an `immortal` transaction + if (details.lifetime == null) { + hasImmortalTransaction.emit(address to true) + updateChecked(address, POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY) + clearLastCheckedTransaction(address) + return true + } + + // Saving last checked transaction + updateLastCheckedTransaction(address, id) + } + return false + } + + private suspend fun updateLastCheckedTransaction(address: String, txId: Long) { + appPreferencesStore.editData { + val savedList = it.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY) + val updatedList = savedList.toMutableMap() + updatedList[address] = txId + it.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY, updatedList) + } + } + + private suspend fun clearLastCheckedTransaction(address: String) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY) + val updatedList = savedList.toMutableMap() + updatedList.remove(address) + mutablePreferences.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY, updatedList) + } + } + + private suspend fun updateChecked(address: String, key: Preferences.Key) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectSet(key) + val updatedList = savedList?.toMutableSet() ?: mutableSetOf() + updatedList.add(address) + + if (updatedList.isNotEmpty()) { + mutablePreferences.setObjectSet( + key, + updatedList.toSet(), + ) + } else { + mutablePreferences.remove(key) + } + } + } + + private companion object { + const val ACCOUNT_NOT_FOUND = "Record Not Found" + } +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt index 4dfc1c9668..ff96eda7f5 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt @@ -2,7 +2,7 @@ package com.tangem.domain.card import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.models.scan.ScanResponse interface ScanCardProcessor { @@ -13,7 +13,7 @@ interface ScanCardProcessor { ): CompletionResult suspend fun scan( - analyticsEvent: AnalyticsEvent? = null, + analyticsSource: AnalyticsParam.ScreensSources, cardId: String? = null, onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {}, onWalletNotCreated: suspend () -> Unit = {}, diff --git a/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt b/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt index 304c70af48..7b9bb1a996 100644 --- a/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt +++ b/domain/demo/src/main/java/com/tangem/domain/demo/DemoConfig.kt @@ -399,12 +399,27 @@ class DemoConfig { "AB02000000048063", "AB02000000023736", "AB02000000058187", + "AB02000000000007", + "AC03000000076229", + "AF04000000000118", // Wallet 2 "AF04000000012006", "AF04000000012014", "AF04000000012022", "AF04000000012030", + "AF15000000257889", + "AF15000000257897", + "AF15000000637809", + "AF15000000640282", + "AF15000001187424", + "AF15000001187408", + "AF15000001187416", + "AF15000001195781", + "AF15000001195773", + "AF15000001195799", + "AF19000000000038", + "AC19000000000064", ) @Suppress("ClassOrdering") diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt new file mode 100644 index 0000000000..527f6e1703 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import kotlinx.coroutines.flow.Flow + +class GetPolkadotCheckHasImmortalUseCase( + private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, +) { + operator fun invoke(): Flow> = + polkadotAccountHealthCheckRepository.subscribeToHasImmortalResults() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt new file mode 100644 index 0000000000..33ce7e960e --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt @@ -0,0 +1,12 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import kotlinx.coroutines.flow.Flow + +class GetPolkadotCheckHasResetUseCase( + private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, +) { + + operator fun invoke(): Flow> = + polkadotAccountHealthCheckRepository.subscribeToHasResetResults() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt new file mode 100644 index 0000000000..f7b3131361 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import com.tangem.domain.wallets.models.UserWalletId + +class RunPolkadotAccountHealthCheckUseCase( + private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, +) { + + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = Either.catch { + polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt new file mode 100644 index 0000000000..a0cd15dd47 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.tokens.repository + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface PolkadotAccountHealthCheckRepository { + + suspend fun runCheck(userWalletId: UserWalletId, network: Network) + + fun subscribeToHasImmortalResults(): Flow> + + fun subscribeToHasResetResults(): Flow> +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index 8a63233bed..213d780d07 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -84,6 +84,11 @@ interface UserWalletsListManager { */ suspend fun get(userWalletId: UserWalletId): CompletionResult + /** + * Indicates that the [UserWalletsListManager] supports [UserWalletsListManager.Lockable] + * */ + fun isLockable(): Boolean + interface Lockable : UserWalletsListManager { /** diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt index 0003a1300d..a6bdbde07f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt @@ -6,12 +6,6 @@ import com.tangem.domain.wallets.models.UserWallet import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -/** - * Indicates that the [UserWalletsListManager] implements [UserWalletsListManager.Lockable] - * */ -val UserWalletsListManager.isLockable: Boolean - get() = this is UserWalletsListManager.Lockable - /** * Indicates that the [UserWalletsListManager] is locked * @@ -48,16 +42,6 @@ suspend fun UserWalletsListManager.unlockIfLockable(type: UnlockType = UnlockTyp return asLockable()?.unlock(type) ?: CompletionResult.Failure(UserWalletsListError.UnableToUnlockUserWallets()) } -/** - * Call [UserWalletsListManager.Lockable.lock] if [UserWalletsListManager] implements [UserWalletsListManager.Lockable] - * or do nothing otherwise - * - * @see UserWalletsListManager.Lockable.lock - * */ -fun UserWalletsListManager.lockIfLockable() { - asLockable()?.lock() -} - /** * Safe cast [UserWalletsListManager] to [UserWalletsListManager.Lockable] * @@ -65,5 +49,8 @@ fun UserWalletsListManager.lockIfLockable() { * [UserWalletsListManager.Lockable] otherwise * */ fun UserWalletsListManager.asLockable(): UserWalletsListManager.Lockable? { - return this as? UserWalletsListManager.Lockable + if (this.isLockable()) { + return this as? UserWalletsListManager.Lockable + } + return null } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 5ae3d34992..c0f4910bfe 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -163,7 +163,11 @@ internal class DefaultWalletRouter( } override fun openScanFailedDialog() { - reduxNavController.navigate(action = NavigationAction.OpenDialog(StateDialog.ScanFailsDialog)) + reduxNavController.navigate( + action = NavigationAction.OpenDialog( + StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN), + ), + ) } private companion object { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index 495f7edd82..de6ddf8b1b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -25,7 +25,7 @@ sealed class WalletScreenAnalyticsEvent { data object WalletOpened : Basic(event = "Wallet Opened") - class CardWasScanned(source: AnalyticsParam.ScannedFrom) : Basic( + class CardWasScanned(source: AnalyticsParam.ScreensSources) : Basic( event = "Card Was Scanned", params = mapOf( AnalyticsParam.SOURCE to source.value, @@ -48,6 +48,26 @@ sealed class WalletScreenAnalyticsEvent { ) } + sealed class Token( + event: String, + params: Map = mapOf(), + ) : AnalyticsEvent(category = "Token", event = event, params = params) { + + class PolkadotAccountReset(hasReset: Boolean) : Token( + event = "Polkadot Account Reset", + params = mapOf( + AnalyticsParam.STATE to if (hasReset) "Yes" else "No", + ), + ) + + class PolkadotImmortalTransactions(hasImmortalTransaction: Boolean) : Token( + event = "Polkadot Immortal Transactions", + params = mapOf( + AnalyticsParam.STATE to if (hasImmortalTransaction) "Yes" else "No", + ), + ) + } + sealed class MainScreen( event: String, params: Map = mapOf(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index ba746cd830..bc6d40794b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -29,6 +30,7 @@ internal class MultiWalletContentLoader( private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val reduxStateHolder: ReduxStateHolder, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -42,6 +44,7 @@ internal class MultiWalletContentLoader( getTokenListUseCase = getTokenListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, applyTokenListSortingUseCase = applyTokenListSortingUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ), MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 794574309a..540fdea589 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -26,6 +27,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val reduxStateHolder: ReduxStateHolder, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader { @@ -41,6 +43,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( reduxStateHolder = reduxStateHolder, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, applyTokenListSortingUseCase = applyTokenListSortingUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index 525f4952ae..655c6255a3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -24,6 +25,7 @@ internal class SingleWalletWithTokenContentLoader( private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val getCardTokensListUseCase: GetCardTokensListUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -36,6 +38,7 @@ internal class SingleWalletWithTokenContentLoader( walletWithFundsChecker = walletWithFundsChecker, getCardTokensListUseCase = getCardTokensListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ), MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index e92241ad9a..e00b6fe74c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -21,6 +22,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val getCardTokensListUseCase: GetCardTokensListUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader { @@ -34,6 +36,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( getCardTokensListUseCase = getCardTokensListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 0dcd95aa6a..7dcd29e739 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -4,7 +4,9 @@ import arrow.core.Either import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender @@ -33,6 +35,7 @@ internal abstract class BasicTokenListSubscriber( private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletSubscriber() { private val sendAnalyticsJobHolder = JobHolder() @@ -53,6 +56,8 @@ internal abstract class BasicTokenListSubscriber( coroutineScope.launch { onTokenListReceived(maybeTokenList) }.saveIn(onTokenListReceivedJobHolder) + + coroutineScope.launch { startCheck(maybeTokenList) } }, flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(), transform = { maybeTokenList, maybeAppCurrency -> @@ -72,6 +77,21 @@ internal abstract class BasicTokenListSubscriber( ) } + private suspend fun startCheck(maybeTokenList: Either) { + // Run Polkadot account health check + maybeTokenList.getOrNull()?.let { tokenList -> + val cryptoCurrencies = when (tokenList) { + is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) + is TokenList.Ungrouped -> tokenList.currencies + is TokenList.Empty -> emptyList() + } + + cryptoCurrencies.forEach { + runPolkadotAccountHealthCheckUseCase(userWallet.walletId, it.currency.network) + } + } + } + protected open suspend fun onTokenListReceived(maybeTokenList: Either) { /* no-op */ } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 9185370945..e3de1238a9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -5,6 +5,7 @@ import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.TokenList @@ -24,6 +25,7 @@ internal class MultiWalletTokenListSubscriber( tokenListAnalyticsSender: TokenListAnalyticsSender, walletWithFundsChecker: WalletWithFundsChecker, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -31,6 +33,7 @@ internal class MultiWalletTokenListSubscriber( tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase(userWallet.walletId) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index 54c8ca07de..952fcb8c29 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker @@ -17,6 +18,7 @@ internal class SingleWalletWithTokenListSubscriber( tokenListAnalyticsSender: TokenListAnalyticsSender, walletWithFundsChecker: WalletWithFundsChecker, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -24,6 +26,7 @@ internal class SingleWalletWithTokenListSubscriber( tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { override fun tokenListFlow(): MaybeTokenListFlow = getCardTokensListUseCase(userWallet.walletId) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index 1ff5dc6191..553dafc78b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -107,7 +107,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List) { - analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScannedFrom.Main)) + analyticsEventHandler.send(Basic.CardWasScanned(AnalyticsParam.ScreensSources.Main)) analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped) viewModelScope.launch(dispatchers.main) {