From 3a57ac60c2204d3566463faf7dab94dda283533c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 25 Jan 2023 01:33:28 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../converters/BasicEventConverter.kt | 171 +++++++++++++----- .../converters/ParamCardCurrencyConverter.kt | 1 + .../filters/ShopPurchasedEventFilter.kt | 0 .../com/tangem/tap/domain/TapWalletManager.kt | 7 +- .../walletStores/WalletStoresManager.kt | 1 + .../DefaultWalletStoresManager.kt | 4 + .../DummyWalletStoresManager.kt | 4 + .../tap/features/wallet/redux/WalletAction.kt | 1 + .../tap/features/wallet/redux/WalletState.kt | 4 + .../middlewares/MultiWalletMiddleware.kt | 4 + .../redux/middlewares/WalletMiddleware.kt | 21 +++ .../redux/reducers/MultiWalletReducer.kt | 8 +- .../tap/features/wallet/ui/WalletFragment.kt | 12 +- 13 files changed, 177 insertions(+), 61 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 4936e61523..1eeda46c6f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -2,77 +2,156 @@ package com.tangem.tap.common.analytics.converters import com.tangem.common.Converter import com.tangem.common.extensions.isZero +import com.tangem.core.analytics.Analytics import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.filters.BasicTopUpFilter import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.reducers.calculateTotalCryptoAmount +import timber.log.Timber +import java.math.BigDecimal /** [REDACTED_AUTHOR] */ -class BasicSignInEventConverter( - private val scanResponse: ScanResponse, -) : Converter { +class BasicEventsPreChecker { - override fun convert(value: WalletState): Basic.SignedIn? { - if (!statesIsReadyToCreateEvent(scanResponse, value)) return null - val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null + fun tryToSend(converterData: BasicEventsSourceData) { + if (!isReadyToSend(converterData)) return + + BasicSignInEventConverter().convert(converterData)?.let { Analytics.send(it) } + BasicTopUpEventConverter().convert(converterData)?.let { Analytics.send(it) } + } + + private fun isReadyToSend(data: BasicEventsSourceData): Boolean { + val (scanResponse, walletState, biometricsWalletDataModels) = data + if (walletState.derivationsCheckIsScheduled) { + Timber.d("FAILED: derivationsCheckIsScheduled") + return false + } + if (scanResponse.card.isMultiwalletAllowed && walletState.missingDerivations.isNotEmpty()) { + Timber.d("FAILED: isMultiwalletAllowed || missingDerivations.isNotEmpty") + return false + } + + if (biometricsWalletDataModels == null) { + Timber.d("SWITCH: OLD") + val walletsDataFromStores = data.walletState.walletsDataFromStores + if (walletsDataFromStores.isEmpty()) { + Timber.d("FAILED: walletsDataFromStores.isEmpty") + return false + } + + val totalBalanceState = data.walletState.totalBalance?.state + if (totalBalanceState == null || totalBalanceState == ProgressState.Loading || + totalBalanceState == ProgressState.Refreshing + ) { + Timber.d("FAILED: totalBalanceState: ${totalBalanceState?.name}") + return false + } + + val balancesCount = walletsDataFromStores + .map { if (it.currencyData.amount == null) 0 else 1 } + .reduce { acc, i -> acc + i } + + if (balancesCount != walletsDataFromStores.size) { + Timber.d("FAILED: balancesCount != walletsDataFromStores.size") + return false + } + } else { + Timber.d("SWITCH: BIOMETRICS") + if (biometricsWalletDataModels.isEmpty()) { + Timber.d("FAILED: biometricsWalletDataModels.isEmpty") + return false + } + + if (biometricsWalletDataModels.any { + it.status is WalletDataModel.Loading || + it.status is WalletDataModel.NoAccount || + it.status is WalletDataModel.Unreachable || + it.status is WalletDataModel.MissedDerivation || + it.status.isErrorStatus + }) { + Timber.d("FAILED: by status") + return false + } + } + + Timber.d("SUCCESS") + return true + } +} + +/** + * With biometrics enabled, we should check its storage instead of "WalletState.walletsDataFromStores" + * because the latter is updated after some time. + * @property biometricsWalletDataModels - wallet data models from the 'WalletStoresManager'. If null, then + * the "WalletState.walletsDataFromStores" will be used to determine appropriate state + */ +data class BasicEventsSourceData( + val scanResponse: ScanResponse, + val walletState: WalletState, + val biometricsWalletDataModels: List?, +) { + val batchId: String by lazy { scanResponse.card.batchId } + + val userWalletIdStringValue: String? by lazy { + UserWalletIdBuilder.scanResponse(scanResponse) + .build() + ?.stringValue + } + + val paramCardCurrency: AnalyticsParam.CardCurrency? by lazy { ParamCardCurrencyConverter().convert(scanResponse) } + + val paramCardBalanceState: AnalyticsParam.CardBalanceState by lazy { calculateAmount().toCardBalanceState() } + + private fun calculateAmount(): BigDecimal { + return biometricsWalletDataModels?.calculateTotalCryptoAmount() + ?: walletState.walletsDataFromStores.calculateTotalCryptoAmount() + } + + private fun BigDecimal.toCardBalanceState(): AnalyticsParam.CardBalanceState = when { + isZero() -> AnalyticsParam.CardBalanceState.Empty + else -> AnalyticsParam.CardBalanceState.Full + } + + private fun List.calculateTotalCryptoAmount(): BigDecimal { + return this + .map { it.status.amount } + .reduce(BigDecimal::plus) + } +} + +class BasicSignInEventConverter : Converter { + + override fun convert(value: BasicEventsSourceData): Basic.SignedIn? { + if (value.paramCardCurrency == null || value.userWalletIdStringValue == null) return null return Basic.SignedIn( - state = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), - currency = cardCurrency, - batch = scanResponse.card.batchId, + state = value.paramCardBalanceState, + currency = value.paramCardCurrency!!, + batch = value.batchId, ).apply { - filterData = UserWalletIdBuilder.scanResponse(scanResponse) - .build() - ?.stringValue + filterData = value.userWalletIdStringValue } } } -class BasicTopUpEventConverter( - private val scanResponse: ScanResponse, -) : Converter { +class BasicTopUpEventConverter : Converter { - override fun convert(value: WalletState): Basic.ToppedUp? { - if (!statesIsReadyToCreateEvent(scanResponse, value)) return null - val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null + override fun convert(value: BasicEventsSourceData): Basic.ToppedUp? { + if (value.paramCardCurrency == null || value.userWalletIdStringValue == null) return null val data = BasicTopUpFilter.Data( - walletId = UserWalletIdBuilder.scanResponse(scanResponse).build()?.stringValue ?: "", - cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), + walletId = value.userWalletIdStringValue!!, + cardBalanceState = value.paramCardBalanceState, ) - return Basic.ToppedUp(cardCurrency).apply { filterData = data } + return Basic.ToppedUp(value.paramCardCurrency!!).apply { filterData = data } } -} - -private fun AnalyticsParam.CardBalanceState.Companion.from(walletsData: List): AnalyticsParam.CardBalanceState { - val totalCryptoAmount = walletsData.calculateTotalCryptoAmount() - return when { - totalCryptoAmount.isZero() -> AnalyticsParam.CardBalanceState.Empty - else -> AnalyticsParam.CardBalanceState.Full - } -} - -private fun statesIsReadyToCreateEvent(scanResponse: ScanResponse, state: WalletState): Boolean { - if (scanResponse.card.isMultiwalletAllowed && state.missingDerivations.isNotEmpty()) return false - if (state.walletsDataFromStores.isEmpty()) return false - - val totalBalanceState = state.totalBalance?.state ?: return false - if (totalBalanceState == ProgressState.Loading || totalBalanceState == ProgressState.Refreshing) return false - - val balancesCount = state.walletsDataFromStores.map { - if (it.currencyData.amount == null) 0 else 1 - }.reduce { acc, i -> acc + i } - - if (balancesCount != state.walletsDataFromStores.size) return false - - return true } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt index e64b751f4f..e359de8e5a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt @@ -22,6 +22,7 @@ class ParamCardCurrencyConverter : Converter AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin) value.isSaltPay() -> AnalyticsParam.CurrencyType.Token(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay)) + value.getBlockchain() != Blockchain.Unknown -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain()) value.getPrimaryToken() != null -> AnalyticsParam.CurrencyType.Token(value.getPrimaryToken()!!) else -> null } ?: return null diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index ea26d15848..4eb41fd759 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -31,6 +31,7 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.wallet.models.toBlockchainNetworks import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.middlewares.handleBasicAnalyticsEvent import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -117,6 +118,7 @@ class TapWalletManager { .doOnSuccess { Timber.d("Wallet stores fetched for ${userWallet.walletId}") store.dispatchOnMain(WalletAction.LoadData.Success) + handleBasicAnalyticsEvent() } .doOnFailure { error -> val errorAction = when (error) { @@ -190,6 +192,7 @@ class TapWalletManager { } if (data.card.isMultiwalletAllowed) { + dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation) loadMultiWalletData(data) } else { loadSingleWalletData(data) @@ -212,9 +215,7 @@ class TapWalletManager { .filter { it.derivationPath != null && !scanResponse.hasDerivation(it.blockchain, it.derivationPath) } - if (missingDerivations.isNotEmpty()) { - store.dispatch(WalletAction.MultiWallet.AddMissingDerivations(missingDerivations)) - } + store.dispatch(WalletAction.MultiWallet.AddMissingDerivations(missingDerivations)) } private suspend fun loadSingleWalletData(data: ScanResponse) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt index e834cf0541..f1fdaff7aa 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow interface WalletStoresManager { fun getAll(): Flow>> fun get(userWalletId: UserWalletId): Flow> + suspend fun getSync(userWalletId: UserWalletId): List suspend fun delete(userWalletsIds: List): CompletionResult suspend fun clear(): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt index 219d7f4af1..22e86d2fa1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -44,6 +44,10 @@ internal class DefaultWalletStoresManager( .distinctUntilChanged() } + override suspend fun getSync(userWalletId: UserWalletId): List { + return walletStoresRepository.getSync(userWalletId) + } + override suspend fun delete(userWalletsIds: List): CompletionResult { return walletStoresRepository.delete(userWalletsIds) .flatMap { walletManagersRepository.delete(userWalletsIds) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt index b1729a0b11..7657f2d47b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt @@ -17,6 +17,10 @@ internal class DummyWalletStoresManager : WalletStoresManager { return emptyFlow() } + override suspend fun getSync(userWalletId: UserWalletId): List { + return emptyList() + } + override suspend fun delete(userWalletsIds: List): CompletionResult { return CompletionResult.Success(Unit) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index c261f4c7ba..dc3e074fac 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -102,6 +102,7 @@ sealed class WalletAction : Action { data class SetPrimaryToken(val token: Token) : MultiWallet() data class ShowWalletBackupWarning(val show: Boolean) : MultiWallet() object BackupWallet : MultiWallet() + object ScheduleCheckForMissingDerivation : MultiWallet() data class AddMissingDerivations(val blockchains: List) : MultiWallet() object ScanToGetDerivations : MultiWallet() } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 49773a9854..0566b5d4a4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -29,6 +29,9 @@ import org.rekotlin.StateType import java.math.BigDecimal import kotlin.properties.ReadOnlyProperty +/** + * @property derivationsCheckIsScheduled - used only for analytics + */ data class WalletState( val cardId: String = "", val state: ProgressState = ProgressState.Done, @@ -46,6 +49,7 @@ data class WalletState( val totalBalance: TotalBalance? = null, val showBackupWarning: Boolean = false, val missingDerivations: List = emptyList(), + val derivationsCheckIsScheduled: Boolean = false, val loadingUserTokens: Boolean = false, val walletCardsCount: Int? = null, ) : StateType { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index e6632583c4..e6798ea6ca 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -167,6 +167,9 @@ class MultiWalletMiddleware { store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } } + is WalletAction.MultiWallet.AddMissingDerivations -> { + scope.launch { handleBasicAnalyticsEvent() } + } is WalletAction.MultiWallet.ScanToGetDerivations -> { val selectedWallet = userWalletsListManager.selectedUserWalletSync if (selectedWallet != null) { @@ -182,6 +185,7 @@ class MultiWalletMiddleware { selectedUserWallet: UserWallet, state: WalletState?, ) = scope.launch(Dispatchers.Default) { + dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation) ScanCardProcessor.scan( analyticsEvent = null, cardId = selectedUserWallet.cardId, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index a20c8b5b36..80b1ddfc2b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -12,6 +12,8 @@ import com.tangem.core.analytics.Analytics import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.attestation.Attestation import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.common.analytics.converters.BasicEventsPreChecker +import com.tangem.tap.common.analytics.converters.BasicEventsSourceData import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token @@ -34,6 +36,7 @@ import com.tangem.tap.domain.failedRates import com.tangem.tap.domain.loadedRates import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen @@ -55,6 +58,7 @@ import com.tangem.tap.tangemSdkManager import com.tangem.tap.totalFiatBalanceCalculator import com.tangem.tap.userWalletsListManager import com.tangem.tap.userWalletsListManagerSafe +import com.tangem.tap.walletStoresManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -108,6 +112,7 @@ class WalletMiddleware { walletState.walletManagers.map { walletManager -> async { globalState.tapWalletManager.loadWalletData(walletManager) } }.awaitAll() + handleBasicAnalyticsEvent() } else { val walletManager = walletState.getWalletManager(action.blockchain) ?: action.walletManager @@ -295,6 +300,7 @@ class WalletMiddleware { } is WalletAction.UserWalletChanged -> Unit is WalletAction.WalletStoresChanged -> { + store.dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation) updateWalletStores(action.walletStores, walletState) fetchTotalFiatBalance(action.walletStores) findMissedDerivations(action.walletStores) @@ -489,4 +495,19 @@ class WalletMiddleware { } } } +} + +suspend fun handleBasicAnalyticsEvent() { + val scanResponse = store.state.globalState.scanResponse ?: return + + val biometricsWalletDataModels = if (preferencesStorage.shouldSaveUserWallets) { + UserWalletIdBuilder.scanResponse(scanResponse).build()?.let { userWalletId -> + walletStoresManager.getSync(userWalletId).map { it.walletsData }.flatten() + } + } else { + null + } + + val converterData = BasicEventsSourceData(scanResponse, store.state.walletState, biometricsWalletDataModels) + BasicEventsPreChecker().tryToSend(converterData) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index afb523a5ca..711373d52f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -185,7 +185,13 @@ class MultiWalletReducer { is WalletAction.MultiWallet.SetPrimaryToken -> state.copy(primaryToken = action.token) is WalletAction.MultiWallet.SaveCurrencies -> state is WalletAction.MultiWallet.ShowWalletBackupWarning -> state.copy(showBackupWarning = action.show) - is WalletAction.MultiWallet.AddMissingDerivations -> state.copy(missingDerivations = action.blockchains) + is WalletAction.MultiWallet.ScheduleCheckForMissingDerivation -> state.copy( + derivationsCheckIsScheduled = true, + ) + is WalletAction.MultiWallet.AddMissingDerivations -> state.copy( + missingDerivations = action.blockchains, + derivationsCheckIsScheduled = false + ) is WalletAction.MultiWallet.BackupWallet -> state is WalletAction.MultiWallet.ScanToGetDerivations -> state.copy(state = ProgressState.Loading) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 7c622df177..15eacf8c99 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -17,13 +17,11 @@ import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import coil.load import coil.size.Scale +import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity -import com.tangem.core.analytics.Analytics -import com.tangem.tap.common.analytics.converters.BasicSignInEventConverter -import com.tangem.tap.common.analytics.converters.BasicTopUpEventConverter import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Portfolio import com.tangem.tap.common.extensions.show @@ -148,7 +146,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber