Updated on 2026-08-14
This commit is contained in:
parent
bf1989f43f
commit
8a90697079
15 changed files with 116 additions and 19 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit a5f541fa4380cf430144fab04e42b32b033ff548
|
Subproject commit c714678d9c46dee1b15ef9e8d27aa23afa1dfd8d
|
||||||
|
|
@ -24,6 +24,8 @@ sealed class AnalyticsParam {
|
||||||
sealed class CardBalanceState(val value: String) {
|
sealed class CardBalanceState(val value: String) {
|
||||||
object Empty : CardBalanceState("Empty")
|
object Empty : CardBalanceState("Empty")
|
||||||
object Full : CardBalanceState("Full")
|
object Full : CardBalanceState("Full")
|
||||||
|
object CustomToken : CardBalanceState("Custom token")
|
||||||
|
object BlockchainError : CardBalanceState("Blockchain error")
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,6 +81,7 @@ sealed class AnalyticsParam {
|
||||||
|
|
||||||
companion object Key {
|
companion object Key {
|
||||||
const val Source = "Source"
|
const val Source = "Source"
|
||||||
|
const val Balance = "Balance"
|
||||||
const val Batch = "Batch"
|
const val Batch = "Batch"
|
||||||
const val ProductType = "Product Type"
|
const val ProductType = "Product Type"
|
||||||
const val Firmware = "Firmware"
|
const val Firmware = "Firmware"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,13 @@ sealed class Basic(
|
||||||
error: Throwable? = null,
|
error: Throwable? = null,
|
||||||
) : AnalyticsEvent("Basic", event, params, error) {
|
) : AnalyticsEvent("Basic", event, params, error) {
|
||||||
|
|
||||||
|
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState) : Basic(
|
||||||
|
event = "Balance Loaded",
|
||||||
|
params = mapOf(
|
||||||
|
AnalyticsParam.Balance to balance.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
class CardWasScanned(
|
class CardWasScanned(
|
||||||
source: AnalyticsParam.ScannedFrom,
|
source: AnalyticsParam.ScannedFrom,
|
||||||
) : Basic(
|
) : Basic(
|
||||||
|
|
@ -47,4 +54,6 @@ sealed class Basic(
|
||||||
event = "Scan",
|
event = "Scan",
|
||||||
error = error,
|
error = error,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class WalletOpened : Basic(event = "Wallet Opened")
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ sealed class MainScreen(
|
||||||
) : AnalyticsEvent("Main Screen", event, params) {
|
) : AnalyticsEvent("Main Screen", event, params) {
|
||||||
|
|
||||||
class ScreenOpened : MainScreen("Screen opened")
|
class ScreenOpened : MainScreen("Screen opened")
|
||||||
|
|
||||||
class ButtonScanCard : MainScreen("Button - Scan Card")
|
class ButtonScanCard : MainScreen("Button - Scan Card")
|
||||||
class ButtonMyWallets : MainScreen("Button - My Wallets")
|
class ButtonMyWallets : MainScreen("Button - My Wallets")
|
||||||
class ButtonBuy : MainScreen("Button - Buy")
|
class ButtonBuy : MainScreen("Button - Buy")
|
||||||
|
|
|
||||||
|
|
@ -71,16 +71,16 @@ sealed class Onboarding(
|
||||||
class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code")
|
class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code")
|
||||||
class CardConnectionScreenOpened : Onboarding("Onboarding", "Card Connection Screen Opened")
|
class CardConnectionScreenOpened : Onboarding("Onboarding", "Card Connection Screen Opened")
|
||||||
class ButtonConnect : Onboarding("Onboarding", "Button - Connect")
|
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 KYCScreenOpened : Onboarding("Onboarding", "KYC screen opened")
|
||||||
class KYCStarted : Onboarding("Onboarding", "KYC started")
|
class KYCStarted : Onboarding("Onboarding", "KYC Started")
|
||||||
class KYCInProgress : Onboarding("Onboarding", "KYC in progress")
|
class KYCInProgress : Onboarding("Onboarding", "KYC In Progress")
|
||||||
class KYCRejected : Onboarding("Onboarding", "KYC rejected")
|
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 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")
|
class ButtonChat : Onboarding("Onboarding", "Button - Chat")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ sealed class Token(
|
||||||
|
|
||||||
class Bought(type: CurrencyType) : Token(
|
class Bought(type: CurrencyType) : Token(
|
||||||
category = "Token",
|
category = "Token",
|
||||||
event = "Token bought",
|
event = "Token Bought",
|
||||||
params = mapOf("Token" to type.value),
|
params = mapOf("Token" to type.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,12 @@ fun Store<*>.dispatchNotification(resId: Int) {
|
||||||
dispatchOnMain(GlobalAction.ShowNotification(resId))
|
dispatchOnMain(GlobalAction.ShowNotification(resId))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) {
|
suspend fun Store<AppState>.onUserWalletSelected(
|
||||||
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh)
|
userWallet: UserWallet,
|
||||||
|
refresh: Boolean = false,
|
||||||
|
sendAnalyticsEvent: Boolean = false,
|
||||||
|
) {
|
||||||
|
state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh, sendAnalyticsEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Store<*>.dispatchToastNotification(resId: Int) {
|
fun Store<*>.dispatchToastNotification(resId: Int) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.datasource.config.ConfigManager
|
||||||
import com.tangem.domain.common.ScanResponse
|
import com.tangem.domain.common.ScanResponse
|
||||||
import com.tangem.domain.common.extensions.withMainContext
|
import com.tangem.domain.common.extensions.withMainContext
|
||||||
import com.tangem.operations.attestation.Attestation
|
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.dispatchOnMain
|
||||||
import com.tangem.tap.common.extensions.setContext
|
import com.tangem.tap.common.extensions.setContext
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
|
|
@ -37,8 +38,11 @@ class TapWalletManager {
|
||||||
store.state.globalState.configManager?.config?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
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)
|
Analytics.setContext(userWallet.scanResponse)
|
||||||
|
if (sendAnalyticsEvent) {
|
||||||
|
Analytics.send(Basic.WalletOpened())
|
||||||
|
}
|
||||||
val scanResponse = userWallet.scanResponse
|
val scanResponse = userWallet.scanResponse
|
||||||
val card = scanResponse.card
|
val card = scanResponse.card
|
||||||
val attestationFailed = card.attestation.status == Attestation.Status.Failed
|
val attestationFailed = card.attestation.status == Attestation.Status.Failed
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.transition.TransitionInflater
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
import coil.load
|
import coil.load
|
||||||
import coil.size.Scale
|
import coil.size.Scale
|
||||||
|
import com.badoo.mvicore.modelWatcher
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||||
import com.tangem.core.ui.utils.OneTouchClickListener
|
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 viewModel by viewModels<WalletViewModel>()
|
||||||
|
|
||||||
|
private val totalBalanceWatcher = modelWatcher {
|
||||||
|
(WalletState::totalBalance) { totalBalance ->
|
||||||
|
totalBalance?.state?.let {
|
||||||
|
viewModel.onBalanceLoaded(totalBalance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
activity?.lifecycleScope?.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
viewModel.launch()
|
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
|
else -> {} // we keep the same view unless we scan a card that requires a different view
|
||||||
}
|
}
|
||||||
|
totalBalanceWatcher.invoke(state)
|
||||||
|
|
||||||
walletView.swapInteractor = swapInteractor
|
walletView.swapInteractor = swapInteractor
|
||||||
walletView.swapFeatureToggleManager = swapFeatureToggleManager
|
walletView.swapFeatureToggleManager = swapFeatureToggleManager
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,16 @@ package com.tangem.tap.features.wallet.ui
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
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.common.extensions.dispatchOnMain
|
||||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
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.redux.WalletAction
|
||||||
|
import com.tangem.tap.features.wallet.ui.analytics.WalletAnalyticsEventsMapper
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.tap.walletStoresManager
|
import com.tangem.tap.walletStoresManager
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
@ -16,16 +21,22 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.rekotlin.StoreSubscriber
|
import org.rekotlin.StoreSubscriber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
// TODO: Kill me, please
|
// TODO: Kill me, please
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@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
|
private var observeWalletStoresUpdatesJob: Job? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field?.cancel()
|
field?.cancel()
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val walletAnalyticsEventsMapper = WalletAnalyticsEventsMapper()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
subscribeToUserWalletsListManagerUpdates()
|
subscribeToUserWalletsListManagerUpdates()
|
||||||
}
|
}
|
||||||
|
|
@ -49,6 +60,18 @@ internal class WalletViewModel : ViewModel(), StoreSubscriber<UserWalletsListMan
|
||||||
bootstrapShowSaveWalletIfNeeded()
|
bootstrapShowSaveWalletIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onBalanceLoaded(totalBalance: TotalBalance?) {
|
||||||
|
if (totalBalance != null) {
|
||||||
|
walletAnalyticsEventsMapper.convert(totalBalance)?.let { balanceParam ->
|
||||||
|
analyticsEventHandler.send(
|
||||||
|
Basic.BalanceLoaded(
|
||||||
|
balance = balanceParam,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
|
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
|
||||||
observeWalletStoresUpdatesJob = manager.selectedUserWallet
|
observeWalletStoresUpdatesJob = manager.selectedUserWallet
|
||||||
.map { it.walletId }
|
.map { it.walletId }
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ internal sealed interface WalletSelectorAction : Action {
|
||||||
|
|
||||||
data class SelectWallet(
|
data class SelectWallet(
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
|
val sendAnalyticsEvent: Boolean = false,
|
||||||
) : WalletSelectorAction
|
) : WalletSelectorAction
|
||||||
|
|
||||||
data class RenameWallet(
|
data class RenameWallet(
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ internal class WalletSelectorMiddleware {
|
||||||
addWallet()
|
addWallet()
|
||||||
}
|
}
|
||||||
is WalletSelectorAction.SelectWallet -> {
|
is WalletSelectorAction.SelectWallet -> {
|
||||||
selectWallet(action.userWalletId)
|
selectWallet(action.userWalletId, action.sendAnalyticsEvent)
|
||||||
}
|
}
|
||||||
is WalletSelectorAction.RemoveWallets -> {
|
is WalletSelectorAction.RemoveWallets -> {
|
||||||
deleteWallets(action.userWalletsIds, state)
|
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 {
|
scope.launch {
|
||||||
userWalletsListManager.get(userWalletId)
|
userWalletsListManager.get(userWalletId)
|
||||||
.flatMap { userWallet ->
|
.flatMap { userWallet ->
|
||||||
|
|
@ -212,7 +212,10 @@ internal class WalletSelectorMiddleware {
|
||||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
||||||
if (selectedUserWallet != null) {
|
if (selectedUserWallet != null) {
|
||||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
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))
|
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
|
||||||
}
|
}
|
||||||
currentSelectedWalletId != selectedUserWallet.walletId -> {
|
currentSelectedWalletId != selectedUserWallet.walletId -> {
|
||||||
store.onUserWalletSelected(selectedUserWallet)
|
store.onUserWalletSelected(
|
||||||
|
userWallet = selectedUserWallet,
|
||||||
|
sendAnalyticsEvent = true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.components.WalletSelectorScreenContent
|
||||||
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
|
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
|
||||||
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<WalletSelectorScreenState>() {
|
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<WalletSelectorScreenState>() {
|
||||||
private val viewModel by viewModels<WalletSelectorViewModel>()
|
private val viewModel by viewModels<WalletSelectorViewModel>()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,12 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
||||||
cancelWalletEditing(userWalletId)
|
cancelWalletEditing(userWalletId)
|
||||||
}
|
}
|
||||||
selectedUserWalletId != userWalletId -> {
|
selectedUserWalletId != userWalletId -> {
|
||||||
store.dispatch(WalletSelectorAction.SelectWallet(userWalletId))
|
store.dispatch(
|
||||||
|
WalletSelectorAction.SelectWallet(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
sendAnalyticsEvent = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue