Updated on 2026-08-14

This commit is contained in:
Tangem 2024-01-19 16:20:25 +03:00
parent c2e1609281
commit 9b853355fc
11 changed files with 127 additions and 115 deletions

View file

@ -4,7 +4,7 @@ import com.tangem.domain.features.BuildConfig
object LogConfig {
const val imageLoader: Boolean = false
val storeAction: Boolean = BuildConfig.DEBUG
val storeAction: Boolean = BuildConfig.LOG_ENABLED
val network: NetworkLogConfig = NetworkLogConfig
val analyticsHandlers: AnalyticsHandlersLogConfig = AnalyticsHandlersLogConfig
}
@ -13,13 +13,13 @@ object NetworkLogConfig {
const val mercuryoService: Boolean = false
const val moonPayService: Boolean = false
const val utorgService: Boolean = false
val tangemTechService: Boolean = BuildConfig.DEBUG
val paymentologyApiService: Boolean = BuildConfig.DEBUG
val blockchainSdkNetwork: Boolean = BuildConfig.DEBUG
val tangemTechService: Boolean = BuildConfig.LOG_ENABLED
val paymentologyApiService: Boolean = BuildConfig.LOG_ENABLED
val blockchainSdkNetwork: Boolean = BuildConfig.LOG_ENABLED
}
object AnalyticsHandlersLogConfig {
const val firebase: Boolean = false
const val appsFlyer: Boolean = false
const val amplitude: Boolean = false
val amplitude: Boolean = BuildConfig.LOG_ENABLED
}

View file

@ -1,18 +1,17 @@
package com.tangem.feature.wallet.presentation.wallet.analytics.utils
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase
import com.tangem.domain.analytics.model.WalletBalanceState
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
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.WalletScreenAnalyticsEvent.Basic
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState
import dagger.hilt.android.scopes.ViewModelScoped
import java.math.BigDecimal
import javax.inject.Inject
@ -23,9 +22,8 @@ internal class TokenListAnalyticsSender @Inject constructor(
private val checkIsWalletToppedUpUseCase: CheckIsWalletToppedUpUseCase,
) {
suspend fun send(userWallet: UserWallet, maybeTokenList: Either<TokenListError, TokenList>) {
val tokenList = maybeTokenList.getOrElse { return }
suspend fun send(displayedUiState: WalletState?, userWallet: UserWallet, tokenList: TokenList) {
if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return
if (tokenList.totalFiatBalance is TokenList.FiatBalance.Loading) return
val currenciesStatuses = getCurrenciesStatuses(tokenList)
@ -33,7 +31,6 @@ internal class TokenListAnalyticsSender @Inject constructor(
sendBalanceLoadedEventIfNeeded(tokenList.totalFiatBalance, currenciesStatuses)
sendToppedUpEventIfNeeded(userWallet, tokenList.totalFiatBalance, currenciesStatuses)
sendUnreachableNetworksEventIfNeeded(currenciesStatuses)
sendMissedAddressesEventIfNeeded(currenciesStatuses)
}
private fun getCurrenciesStatuses(tokenList: TokenList): List<CryptoCurrencyStatus> = when (tokenList) {
@ -119,20 +116,13 @@ internal class TokenListAnalyticsSender @Inject constructor(
}
private fun sendUnreachableNetworksEventIfNeeded(currenciesStatuses: List<CryptoCurrencyStatus>) {
val hasUnreachableCurrencies = currenciesStatuses.any { it.value is CryptoCurrencyStatus.Unreachable }
val hasUnreachableCurrencies = currenciesStatuses.any {
it.value is CryptoCurrencyStatus.Unreachable ||
it.value is CryptoCurrencyStatus.UnreachableWithoutAddresses
}
if (hasUnreachableCurrencies) {
analyticsEventHandler.send(MainScreen.NetworksUnreachable)
}
}
private fun sendMissedAddressesEventIfNeeded(currenciesStatuses: List<CryptoCurrencyStatus>) {
val hasCurrenciesWithMissedDerivation = currenciesStatuses.any {
it.value is CryptoCurrencyStatus.MissedDerivation
}
if (hasCurrenciesWithMissedDerivation) {
analyticsEventHandler.send(MainScreen.MissingAddresses)
}
}
}

View file

@ -1,10 +1,11 @@
package com.tangem.feature.wallet.presentation.wallet.analytics.utils
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.collections.immutable.ImmutableList
import javax.inject.Inject
@ViewModelScoped
@ -12,29 +13,40 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
) {
fun send(warnings: ImmutableList<WalletNotification>) {
val analyticsEvents = warnings.mapNotNull { warning ->
when (warning) {
is WalletNotification.Critical.DevCard -> MainScreen.DevelopmentCard
is WalletNotification.Informational.DemoCard -> MainScreen.DemoCard
is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem
is WalletNotification.Warning.NumberOfSignedHashesIncorrect -> MainScreen.CardSignedTransactions
is WalletNotification.Warning.TestNetCard -> MainScreen.TestnetCard
is WalletNotification.UnlockWallets -> MainScreen.WalletUnlock
is WalletNotification.Critical.FailedCardValidation -> MainScreen.ProductSampleCard
is WalletNotification.Warning.MissingBackup -> MainScreen.BackupYourWallet
is WalletNotification.Informational.MissingAddresses,
is WalletNotification.Informational.NoAccount,
is WalletNotification.Warning.LowSignatures,
is WalletNotification.Warning.NetworksUnreachable,
is WalletNotification.Warning.SomeNetworksUnreachable,
is WalletNotification.SwapPromo,
-> null
}
}
fun send(displayedUiState: WalletState?, newWarnings: List<WalletNotification>) {
if (newWarnings.isEmpty()) return
if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return
analyticsEvents.forEach { event ->
val eventsFromNewWarnings = getEvents(newWarnings)
val eventsFromDisplayedWarnings = getEvents(displayedUiState.warnings)
val eventsToSend = eventsFromNewWarnings.filter { it !in eventsFromDisplayedWarnings }
eventsToSend.forEach { event ->
analyticsEventHandler.send(event)
}
}
private fun getEvents(warnings: List<WalletNotification>): Set<AnalyticsEvent> {
return warnings.mapNotNullTo(mutableSetOf(), ::getEvent)
}
private fun getEvent(warning: WalletNotification): AnalyticsEvent? {
return when (warning) {
is WalletNotification.Critical.DevCard -> MainScreen.DevelopmentCard
is WalletNotification.Critical.FailedCardValidation -> MainScreen.ProductSampleCard
is WalletNotification.Warning.MissingBackup -> MainScreen.BackupYourWallet
is WalletNotification.Warning.NumberOfSignedHashesIncorrect -> MainScreen.CardSignedTransactions
is WalletNotification.Warning.TestNetCard -> MainScreen.TestnetCard
is WalletNotification.Informational.DemoCard -> MainScreen.DemoCard
is WalletNotification.Informational.MissingAddresses -> MainScreen.MissingAddresses
is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem
is WalletNotification.UnlockWallets -> MainScreen.WalletUnlock
is WalletNotification.Informational.NoAccount,
is WalletNotification.Warning.LowSignatures,
is WalletNotification.Warning.SomeNetworksUnreachable,
is WalletNotification.Warning.NetworksUnreachable,
is WalletNotification.SwapPromo,
-> null
}
}
}

View file

@ -47,7 +47,7 @@ internal class WalletScreenContentLoader @Inject constructor(
} else {
if (isRefresh) {
storage.remove(id)
loadInternal(userWallet, clickIntents, coroutineScope, true)
loadInternal(userWallet, clickIntents, coroutineScope, isRefresh = true)
} else {
Timber.d("$id content loading has already started")
}

View file

@ -9,8 +9,8 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarni
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.TokenListSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletConnectNetworksSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
@ -31,7 +31,7 @@ internal class MultiWalletContentLoader(
override fun create(): List<WalletSubscriber> {
return listOf(
TokenListSubscriber(
MultiWalletTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,

View file

@ -42,6 +42,14 @@ internal class WalletStateController @Inject constructor() {
mutableUiState.update { getInitialState() }
}
fun getWalletIfSelected(walletId: UserWalletId): WalletState? {
val selectedWalletId = getSelectedWalletId()
return value.wallets.firstOrNull {
it.walletCardState.id == walletId && it.walletCardState.id == selectedWalletId
}
}
fun getSelectedWallet(): WalletState {
return with(value) { wallets[selectedWalletIndex] }
}

View file

@ -4,7 +4,6 @@ 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.GetTokenListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWallet
@ -15,37 +14,40 @@ import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetToke
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListTransformer
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
typealias MaybeTokenListFlow = Flow<Either<TokenListError, TokenList>>
internal typealias MaybeTokenListFlow = Flow<Either<TokenListError, TokenList>>
@Suppress("LongParameterList")
internal class TokenListSubscriber(
internal abstract class BasicTokenListSubscriber(
private val userWallet: UserWallet,
private val stateHolder: WalletStateController,
private val clickIntents: WalletClickIntentsV2,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getTokenListUseCase: GetTokenListUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<Pair<Either<TokenListError, TokenList>, AppCurrency>> {
protected abstract fun tokenListFlow(): MaybeTokenListFlow
override fun create(coroutineScope: CoroutineScope): Flow<*> {
return combine(
flow = getTokenListUseCase(userWalletId = userWallet.walletId)
.conflate()
flow = tokenListFlow()
.onEach {
val displayedState = stateHolder.getWalletIfSelected(userWallet.walletId)
tokenListAnalyticsSender.send(displayedState, userWallet, it.getOrElse { return@onEach })
}
.distinctUntilChanged(),
flow2 = getSelectedAppCurrencyUseCase()
.conflate()
.distinctUntilChanged()
.map { maybeAppCurrency -> maybeAppCurrency.getOrElse { AppCurrency.Default } },
transform = { tokenList, appCurrency -> tokenList to appCurrency },
)
.onEach { (maybeTokenList, appCurrency) ->
updateContent(maybeTokenList, appCurrency)
tokenListAnalyticsSender.send(userWallet, maybeTokenList)
flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(),
transform = { maybeTokenList, maybeAppCurrency ->
updateContent(maybeTokenList, maybeAppCurrency.getOrElse { AppCurrency.Default })
walletWithFundsChecker.check(maybeTokenList)
}
},
)
}
private fun updateContent(maybeTokenList: Either<TokenListError, TokenList>, appCurrency: AppCurrency) {

View file

@ -0,0 +1,30 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
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
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
@Suppress("LongParameterList")
internal class MultiWalletTokenListSubscriber(
private val userWallet: UserWallet,
private val getTokenListUseCase: GetTokenListUseCase,
stateHolder: WalletStateController,
clickIntents: WalletClickIntentsV2,
tokenListAnalyticsSender: TokenListAnalyticsSender,
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
) {
override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase(userWallet.walletId)
}

View file

@ -27,8 +27,10 @@ internal class MultiWalletWarningsSubscriber(
.conflate()
.distinctUntilChanged()
.onEach { warnings ->
val displayedState = stateHolder.getWalletIfSelected(userWalletId)
stateHolder.update(SetWarningsTransformer(userWalletId, warnings))
walletWarningsAnalyticsSender.send(warnings)
walletWarningsAnalyticsSender.send(displayedState, warnings)
}
}
}

View file

@ -30,8 +30,10 @@ internal class SingleWalletNotificationsSubscriber(
.conflate()
.distinctUntilChanged()
.onEach { warnings ->
val displayedState = stateHolder.getWalletIfSelected(userWalletId)
stateHolder.update(SetWarningsTransformer(userWalletId, warnings))
walletWarningsAnalyticsSender.send(warnings)
walletWarningsAnalyticsSender.send(displayedState, warnings)
}
}
}

View file

@ -1,64 +1,30 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
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.GetCardTokensListUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.TokenList
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
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTokenListTransformer
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
@Suppress("LongParameterList")
internal class SingleWalletWithTokenListSubscriber(
private val userWallet: UserWallet,
private val stateHolder: WalletStateController,
private val clickIntents: WalletClickIntentsV2,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getCardTokensListUseCase: GetCardTokensListUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
) : WalletSubscriber() {
stateHolder: WalletStateController,
clickIntents: WalletClickIntentsV2,
tokenListAnalyticsSender: TokenListAnalyticsSender,
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
) {
override fun create(coroutineScope: CoroutineScope): Flow<Pair<Either<TokenListError, TokenList>, AppCurrency>> {
return combine(
flow = getCardTokensListUseCase(userWalletId = userWallet.walletId)
.conflate()
.distinctUntilChanged(),
flow2 = getSelectedAppCurrencyUseCase()
.conflate()
.distinctUntilChanged()
.map { maybeAppCurrency -> maybeAppCurrency.getOrElse { AppCurrency.Default } },
transform = { tokenList, appCurrency -> tokenList to appCurrency },
)
.onEach { (maybeTokenList, appCurrency) ->
updateContent(maybeTokenList, appCurrency)
tokenListAnalyticsSender.send(userWallet, maybeTokenList)
walletWithFundsChecker.check(maybeTokenList)
}
}
private fun updateContent(maybeTokenList: Either<TokenListError, TokenList>, appCurrency: AppCurrency) {
stateHolder.update(
maybeTokenList.fold(
ifLeft = { SetTokenListErrorTransformer(userWalletId = userWallet.walletId, error = it) },
ifRight = {
SetTokenListTransformer(
tokenList = it,
userWallet = userWallet,
appCurrency = appCurrency,
clickIntents = clickIntents,
)
},
),
)
}
override fun tokenListFlow(): MaybeTokenListFlow = getCardTokensListUseCase(userWallet.walletId)
}