diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a5f541fa43..c714678d9c 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a5f541fa4380cf430144fab04e42b32b033ff548 +Subproject commit c714678d9c46dee1b15ef9e8d27aa23afa1dfd8d diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index ce8e64b0f6..8bbf95d217 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -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" diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index 76b00ef40f..42720ec44b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -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") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt index 6c8580df05..7a36f35d1e 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt @@ -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") diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt index c0d61c52d1..d684ed3cce 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt @@ -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") diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index 63dd109942..83ad4d4bfc 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -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), ) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index bfd3fa99c2..8b5eafb76f 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -42,8 +42,12 @@ fun Store<*>.dispatchNotification(resId: Int) { dispatchOnMain(GlobalAction.ShowNotification(resId)) } -suspend fun Store.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) { - state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh) +suspend fun Store.onUserWalletSelected( + userWallet: UserWallet, + refresh: Boolean = false, + sendAnalyticsEvent: Boolean = false, +) { + state.globalState.tapWalletManager.onWalletSelected(userWallet, refresh, sendAnalyticsEvent) } fun Store<*>.dispatchToastNotification(resId: Int) { 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 15a3835869..fa25c5fbb6 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -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 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 de756d8466..40f6f7f627 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,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() + 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 {} // we keep the same view unless we scan a card that requires a different view } + totalBalanceWatcher.invoke(state) walletView.swapInteractor = swapInteractor walletView.swapFeatureToggleManager = swapFeatureToggleManager diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt index bb5f847af9..f9cb7642eb 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt @@ -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 { +@HiltViewModel +internal class WalletViewModel @Inject constructor( + private val analyticsEventHandler: AnalyticsEventHandler, +) : ViewModel(), StoreSubscriber { 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 + analyticsEventHandler.send( + Basic.BalanceLoaded( + balance = balanceParam, + ), + ) + } + } + } + private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) { observeWalletStoresUpdatesJob = manager.selectedUserWallet .map { it.walletId } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/analytics/WalletAnalyticsEventsMapper.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/analytics/WalletAnalyticsEventsMapper.kt new file mode 100644 index 0000000000..6c750a5368 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/analytics/WalletAnalyticsEventsMapper.kt @@ -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 { + + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index 4e8b5ec2d6..b41635abb0 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -35,6 +35,7 @@ internal sealed interface WalletSelectorAction : Action { data class SelectWallet( val userWalletId: UserWalletId, + val sendAnalyticsEvent: Boolean = false, ) : WalletSelectorAction data class RenameWallet( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index bfd8936b17..c93b07f234 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -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, + ) } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index dbb367554c..b611289180 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -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() { private val viewModel by viewModels() diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index 9384a5f22b..dee873d6ce 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -54,7 +54,12 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber { - store.dispatch(WalletSelectorAction.SelectWallet(userWalletId)) + store.dispatch( + WalletSelectorAction.SelectWallet( + userWalletId = userWalletId, + sendAnalyticsEvent = true, + ), + ) } } }