Updated on 2026-08-14

This commit is contained in:
Tangem 2023-03-29 13:24:05 +03:00
parent bf1989f43f
commit 8a90697079
15 changed files with 116 additions and 19 deletions

@ -1 +1 @@
Subproject commit a5f541fa4380cf430144fab04e42b32b033ff548
Subproject commit c714678d9c46dee1b15ef9e8d27aa23afa1dfd8d

View file

@ -24,6 +24,8 @@ sealed class AnalyticsParam {
sealed class CardBalanceState(val value: String) {
object Empty : CardBalanceState("Empty")
object Full : CardBalanceState("Full")
object CustomToken : CardBalanceState("Custom token")
object BlockchainError : CardBalanceState("Blockchain error")
companion object
}
@ -79,6 +81,7 @@ sealed class AnalyticsParam {
companion object Key {
const val Source = "Source"
const val Balance = "Balance"
const val Batch = "Batch"
const val ProductType = "Product Type"
const val Firmware = "Firmware"

View file

@ -11,6 +11,13 @@ sealed class Basic(
error: Throwable? = null,
) : AnalyticsEvent("Basic", event, params, error) {
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState) : Basic(
event = "Balance Loaded",
params = mapOf(
AnalyticsParam.Balance to balance.value,
),
)
class CardWasScanned(
source: AnalyticsParam.ScannedFrom,
) : Basic(
@ -47,4 +54,6 @@ sealed class Basic(
event = "Scan",
error = error,
)
class WalletOpened : Basic(event = "Wallet Opened")
}

View file

@ -11,7 +11,6 @@ sealed class MainScreen(
) : AnalyticsEvent("Main Screen", event, params) {
class ScreenOpened : MainScreen("Screen opened")
class ButtonScanCard : MainScreen("Button - Scan Card")
class ButtonMyWallets : MainScreen("Button - My Wallets")
class ButtonBuy : MainScreen("Button - Buy")

View file

@ -71,16 +71,16 @@ sealed class Onboarding(
class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code")
class CardConnectionScreenOpened : Onboarding("Onboarding", "Card Connection Screen Opened")
class ButtonConnect : Onboarding("Onboarding", "Button - Connect")
class PinCodeSet : Onboarding("Onboarding", "PIN code set")
class PinCodeSet : Onboarding("Onboarding", "PIN Code Set")
class KYCScreenOpened : Onboarding("Onboarding", "KYC screen opened")
class KYCStarted : Onboarding("Onboarding", "KYC started")
class KYCInProgress : Onboarding("Onboarding", "KYC in progress")
class KYCRejected : Onboarding("Onboarding", "KYC rejected")
class KYCStarted : Onboarding("Onboarding", "KYC Started")
class KYCInProgress : Onboarding("Onboarding", "KYC In Progress")
class KYCRejected : Onboarding("Onboarding", "KYC Rejected")
class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened")
class ClaimScreenOpened : Onboarding("Onboarding", "Claim Screen Opened")
class ButtonClaim : Onboarding("Onboarding", "Button - Claim")
class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully")
class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim Was Successfully")
class ButtonChat : Onboarding("Onboarding", "Button - Chat")

View file

@ -48,7 +48,7 @@ sealed class Token(
class Bought(type: CurrencyType) : Token(
category = "Token",
event = "Token bought",
event = "Token Bought",
params = mapOf("Token" to type.value),
)

View file

@ -42,8 +42,12 @@ fun Store<*>.dispatchNotification(resId: Int) {
dispatchOnMain(GlobalAction.ShowNotification(resId))
}
suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) {
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh)
suspend fun Store<AppState>.onUserWalletSelected(
userWallet: UserWallet,
refresh: Boolean = false,
sendAnalyticsEvent: Boolean = false,
) {
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh, sendAnalyticsEvent)
}
fun Store<*>.dispatchToastNotification(resId: Int) {

View file

@ -12,6 +12,7 @@ import com.tangem.datasource.config.ConfigManager
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.attestation.Attestation
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction
@ -37,8 +38,11 @@ class TapWalletManager {
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
}
suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean) {
suspend fun onWalletSelected(userWallet: UserWallet, refresh: Boolean, sendAnalyticsEvent: Boolean) {
Analytics.setContext(userWallet.scanResponse)
if (sendAnalyticsEvent) {
Analytics.send(Basic.WalletOpened())
}
val scanResponse = userWallet.scanResponse
val card = scanResponse.card
val attestationFailed = card.attestation.status == Attestation.Status.Failed

View file

@ -17,6 +17,7 @@ import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import coil.load
import coil.size.Scale
import com.badoo.mvicore.modelWatcher
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.fragments.setStatusBarColor
import com.tangem.core.ui.utils.OneTouchClickListener
@ -68,9 +69,17 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
private val viewModel by viewModels<WalletViewModel>()
private val totalBalanceWatcher = modelWatcher {
(WalletState::totalBalance) { totalBalance ->
totalBalance?.state?.let {
viewModel.onBalanceLoaded(totalBalance)
}
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
activity?.lifecycleScope?.launchWhenCreated {
lifecycleScope.launchWhenCreated {
viewModel.launch()
}
}
@ -172,6 +181,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
}
else -> {} // we keep the same view unless we scan a card that requires a different view
}
totalBalanceWatcher.invoke(state)
walletView.swapInteractor = swapInteractor
walletView.swapFeatureToggleManager = swapFeatureToggleManager

View file

@ -2,11 +2,16 @@ package com.tangem.tap.features.wallet.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.ui.analytics.WalletAnalyticsEventsMapper
import com.tangem.tap.store
import com.tangem.tap.walletStoresManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@ -16,16 +21,22 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.rekotlin.StoreSubscriber
import javax.inject.Inject
// TODO: Kill me, please
@OptIn(ExperimentalCoroutinesApi::class)
internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListManager?> {
@HiltViewModel
internal class WalletViewModel @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), StoreSubscriber<UserWalletsListManager?> {
private var observeWalletStoresUpdatesJob: Job? = null
set(value) {
field?.cancel()
field = value
}
private val walletAnalyticsEventsMapper = WalletAnalyticsEventsMapper()
init {
subscribeToUserWalletsListManagerUpdates()
}
@ -49,6 +60,18 @@ internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListMan
bootstrapShowSaveWalletIfNeeded()
}
fun onBalanceLoaded(totalBalance: TotalBalance?) {
if (totalBalance != null) {
walletAnalyticsEventsMapper.convert(totalBalance)?.let { balanceParam ->
analyticsEventHandler.send(
Basic.BalanceLoaded(
balance = balanceParam,
),
)
}
}
}
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
observeWalletStoresUpdatesJob = manager.selectedUserWallet
.map { it.walletId }

View file

@ -0,0 +1,31 @@
package com.tangem.tap.features.wallet.ui.analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.extensions.isGreaterThan
import com.tangem.tap.features.wallet.models.TotalBalance
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
class WalletAnalyticsEventsMapper : Converter<TotalBalance, AnalyticsParam.CardBalanceState?> {
override fun convert(value: TotalBalance): AnalyticsParam.CardBalanceState? {
return when (value.state) {
ProgressState.Done -> if (value.fiatAmount?.isGreaterThan(BigDecimal.ZERO) == true) {
AnalyticsParam.CardBalanceState.Full
} else {
AnalyticsParam.CardBalanceState.Empty
}
ProgressState.Error -> {
if (value.fiatAmount == null) {
// if fiatAmount is null while ProgressState.Error it means error occurs when loading blockchain
AnalyticsParam.CardBalanceState.BlockchainError
} else {
// if fiatAmount is not null while ProgressState.Error it means couldn't load custom token amount
AnalyticsParam.CardBalanceState.CustomToken
}
}
else -> null
}
}
}

View file

@ -35,6 +35,7 @@ internal sealed interface WalletSelectorAction : Action {
data class SelectWallet(
val userWalletId: UserWalletId,
val sendAnalyticsEvent: Boolean = false,
) : WalletSelectorAction
data class RenameWallet(

View file

@ -70,7 +70,7 @@ internal class WalletSelectorMiddleware {
addWallet()
}
is WalletSelectorAction.SelectWallet -> {
selectWallet(action.userWalletId)
selectWallet(action.userWalletId, action.sendAnalyticsEvent)
}
is WalletSelectorAction.RemoveWallets -> {
deleteWallets(action.userWalletsIds, state)
@ -189,7 +189,7 @@ internal class WalletSelectorMiddleware {
}
}
private fun selectWallet(userWalletId: UserWalletId) {
private fun selectWallet(userWalletId: UserWalletId, sendAnalyticsEvent: Boolean) {
scope.launch {
userWalletsListManager.get(userWalletId)
.flatMap { userWallet ->
@ -212,7 +212,10 @@ internal class WalletSelectorMiddleware {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
if (selectedUserWallet != null) {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
store.onUserWalletSelected(selectedUserWallet)
store.onUserWalletSelected(
userWallet = selectedUserWallet,
sendAnalyticsEvent = true,
)
}
}
}
@ -336,7 +339,10 @@ internal class WalletSelectorMiddleware {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
}
currentSelectedWalletId != selectedUserWallet.walletId -> {
store.onUserWalletSelected(selectedUserWallet)
store.onUserWalletSelected(
userWallet = selectedUserWallet,
sendAnalyticsEvent = true,
)
}
}
}

View file

@ -32,7 +32,9 @@ import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogCo
import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<WalletSelectorScreenState>() {
private val viewModel by viewModels<WalletSelectorViewModel>()

View file

@ -54,7 +54,12 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
cancelWalletEditing(userWalletId)
}
selectedUserWalletId != userWalletId -> {
store.dispatch(WalletSelectorAction.SelectWallet(userWalletId))
store.dispatch(
WalletSelectorAction.SelectWallet(
userWalletId = userWalletId,
sendAnalyticsEvent = true,
),
)
}
}
}