Updated on 2026-08-14

This commit is contained in:
Tangem 2023-01-25 17:38:01 +03:00
commit c2b451c71b
62 changed files with 451 additions and 255 deletions

View file

@ -11,18 +11,17 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.core.analytics.Analytics
import com.tangem.datasource.api.common.MoshiConverter
import com.tangem.domain.DomainLayer
import com.tangem.domain.common.LogConfig
import com.tangem.tap.common.AndroidAssetReader
import com.tangem.tap.common.AssetReader
import com.tangem.tap.common.IntentHandler
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.filters.BasicSignInFilter
import com.tangem.tap.common.analytics.filters.BasicTopUpFilter
import com.tangem.tap.common.analytics.filters.ShopPurchasedEventFilter
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
@ -198,7 +197,6 @@ class TapApplication : Application(), ImageLoaderFactory {
factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder())
factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder())
factory.addFilter(ShopPurchasedEventFilter())
factory.addFilter(BasicSignInFilter())
factory.addFilter(BasicTopUpFilter(preferencesStorage.toppedUpWalletStorage))

View file

@ -42,7 +42,7 @@ class IntentHandler {
intent.action = null
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
store.dispatch(HomeAction.ReadCard)
store.dispatch(HomeAction.ReadCard())
}
}
}

View file

@ -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<WalletState, Basic.SignedIn?> {
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<WalletDataModel>?,
) {
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<WalletDataModel>.calculateTotalCryptoAmount(): BigDecimal {
return this
.map { it.status.amount }
.reduce(BigDecimal::plus)
}
}
class BasicSignInEventConverter : Converter<BasicEventsSourceData, Basic.SignedIn?> {
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<WalletState, Basic.ToppedUp?> {
class BasicTopUpEventConverter : Converter<BasicEventsSourceData, Basic.ToppedUp?> {
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<WalletData>): 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.walletsStores.size) return false
return true
}

View file

@ -22,6 +22,7 @@ class ParamCardCurrencyConverter : Converter<ScanResponse, AnalyticsParam.CardCu
}
value.isTangemTwins() -> 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

View file

@ -8,19 +8,19 @@ import com.tangem.tap.common.shop.data.ProductType
/**
[REDACTED_AUTHOR]
*/
class ShopOrderToEventConverter : Converter<Pair<Storefront.Order, ProductType>, Shop.Purchased> {
class ShopOrderToEventConverter : Converter<Pair<Storefront.Checkout, ProductType>, Shop.Purchased> {
override fun convert(value: Pair<Storefront.Order, ProductType>): Shop.Purchased {
val order = value.first
override fun convert(value: Pair<Storefront.Checkout, ProductType>): Shop.Purchased {
val checkout = value.first
val productType = value.second
val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: productType.sku
val sku = checkout.lineItems?.edges?.firstOrNull()?.node?.variant?.sku ?: productType.sku
val count = when (productType) {
ProductType.WALLET_2_CARDS -> "2"
ProductType.WALLET_3_CARDS -> "3"
}
val amount = "${order.totalPriceV2.amount} ${order.totalPriceV2.currencyCode.name}"
val code = (order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
val amount = "${checkout.totalPriceV2.amount} ${checkout.totalPriceV2.currencyCode.name}"
val code = (checkout.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
return Shop.Purchased(sku, count, amount, code)
}

View file

@ -7,14 +7,14 @@ sealed class MyWallets(
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("My Wallets", event, params) {
object MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened")
object CardWasScanned : MyWallets(event = "Card Was Scanned")
object WalletUnlockTapped : MyWallets(event = "Wallet Unlock Tapped")
class MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened")
class CardWasScanned : MyWallets(event = "Card Was Scanned")
object Button {
object ScanNewCard : MyWallets(event = "Button - Scan New Card")
object UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID")
object EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped")
object DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped")
sealed class Button {
class ScanNewCard : MyWallets(event = "Button - Scan New Card")
class UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID")
class EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped")
class DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped")
class WalletUnlockTapped : MyWallets(event = "Button - Wallet Unlock Tapped")
}
}

View file

@ -75,6 +75,8 @@ sealed class Onboarding(
class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened")
class ButtonClaim : Onboarding("Onboarding", "Button - Claim")
class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully")
class ButtonChat: Onboarding("Onboarding", "Button - Chat")
class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding(
category = "Onboarding / Biometric",
event = "Enable Biometric",

View file

@ -21,6 +21,7 @@ sealed class Settings(
class ButtonCardSettings : Settings(event = "Button - Card Settings")
class ButtonAppSettings : Settings(event = "Button - App Settings")
class ButtonCreateBackup : Settings(event = "Button - Create Backup")
class ButtonWalletConnect : Settings(event = "Button - Wallet Connect")
class ButtonSocialNetwork(network: SocialNetwork) : Settings(
event = "Button - Social Network",

View file

@ -11,7 +11,7 @@ sealed class Shop(
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Shop", event, params) {
class ScreenOpened : IntroductionProcess("Shop Screen Opened")
class ScreenOpened : Shop("Shop Screen Opened")
class Purchased(sku: String, count: String, amount: String, couponCode: String?) : Shop(
event = "Purchased",
@ -23,8 +23,8 @@ sealed class Shop(
).filterNotNull(),
)
class Redirected(partnerName: String) : Shop(
class Redirected(partnerName: String?) : Shop(
event = "Redirected",
params = mapOf("Partner" to partnerName),
params = partnerName?.let { mapOf("Partner" to partnerName) } ?: mapOf(),
)
}

View file

@ -0,0 +1,19 @@
package com.tangem.tap.common.analytics.events
import com.tangem.core.analytics.AnalyticsEvent
/**
[REDACTED_AUTHOR]
*/
sealed class SignIn(
event: String,
params: Map<String, String> = mapOf(),
error: Throwable? = null,
) : AnalyticsEvent("Sign In", event, params, error) {
class ScreenOpened : SignIn(event = "Sing In Screen Opened")
class CardWasScanned : SignIn(event = "Card Was Scanned")
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
class ButtonCardSignIn : SignIn(event = "Button - Card Sign In")
}

View file

@ -46,14 +46,14 @@ sealed class Token(
params = mapOf("Token" to type.value),
)
sealed class Recieve(
sealed class Receive(
event: String,
params: Map<String, String> = mapOf(),
) : Token("Token / Recieve", event, params) {
) : Token("Token / Receive", event, params) {
class ScreenOpened : Recieve("Recieve Screen Opened")
class ButtonCopyAddress : Recieve("Button - Copy Address")
class ButtonShareAddress : Recieve("Button - Share Address")
class ScreenOpened : Receive("Receive Screen Opened")
class ButtonCopyAddress : Receive("Button - Copy Address")
class ButtonShareAddress : Receive("Button - Share Address")
}
sealed class Send(

View file

@ -11,6 +11,7 @@ sealed class WalletConnect(
error: Throwable? = null,
) : AnalyticsEvent("Wallet Connect", event, params, error) {
class ScreenOpened : WalletConnect(event = "WC Screen Opened")
class NewSessionEstablished : WalletConnect("New Session Established")
class SessionDisconnected : WalletConnect("Session Disconnected")
class RequestSigned : WalletConnect("Request Signed")

View file

@ -1,25 +0,0 @@
package com.tangem.tap.common.analytics.filters
import com.tangem.core.analytics.api.AnalyticsEventFilter
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
/**
[REDACTED_AUTHOR]
*/
class ShopPurchasedEventFilter : AnalyticsEventFilter {
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = event is Shop.Purchased
override fun canBeSent(event: AnalyticsEvent): Boolean = true
override fun canBeConsumedByHandler(handler: AnalyticsHandler, event: AnalyticsEvent): Boolean {
return when (handler) {
is AppsFlyerAnalyticsHandler, is FirebaseAnalyticsHandler -> true
else -> false
}
}
}

View file

@ -1,7 +1,10 @@
package com.tangem.tap.common.analytics.handlers.appsFlyer
import com.appsflyer.AFInAppEventType
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.events.Shop
class AppsFlyerAnalyticsHandler(
private val client: AppsFlyerAnalyticsClient,
@ -13,6 +16,14 @@ class AppsFlyerAnalyticsHandler(
client.logEvent(event, params)
}
override fun send(event: AnalyticsEvent) {
if (event is Shop.Purchased) {
send(AFInAppEventType.PURCHASE, event.params)
} else {
super.send(event)
}
}
companion object {
const val ID = "AppsFlyer"
}

View file

@ -1,10 +1,12 @@
package com.tangem.tap.common.analytics.handlers.firebase
import com.google.firebase.analytics.FirebaseAnalytics
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.core.analytics.api.AnalyticsHandler
import com.tangem.core.analytics.api.ErrorEventHandler
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter
import com.tangem.tap.common.analytics.events.Shop
class FirebaseAnalyticsHandler(
private val client: FirebaseAnalyticsClient,
@ -18,17 +20,23 @@ class FirebaseAnalyticsHandler(
override fun send(event: AnalyticsEvent) {
val error = event.error
if (error == null) {
super.send(event)
} else {
val errorConverter = AnalyticsErrorConverter()
if (!errorConverter.canBeHandled(error)) return
when {
error != null -> {
val errorConverter = AnalyticsErrorConverter()
if (!errorConverter.canBeHandled(error)) return
val errorParams = errorConverter.convert(error).toMutableMap()
errorParams["Category"] = event.category
errorParams["Event"] = event.event
errorParams.putAll(event.params)
send(error, errorParams)
val errorParams = errorConverter.convert(error).toMutableMap()
errorParams["Category"] = event.category
errorParams["Event"] = event.event
errorParams.putAll(event.params)
send(error, errorParams)
}
event is Shop.Purchased -> {
send(FirebaseAnalytics.Event.PURCHASE, event.params)
}
else -> {
super.send(event)
}
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.common.feedback
import android.content.Context
import android.os.Build
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.TapWorkarounds
import com.tangem.tap.common.extensions.sendEmail
import com.tangem.tap.common.log.TangemLogCollector
@ -108,6 +109,7 @@ class FeedbackManager(
}
private fun showMessagingActivity(context: Context) {
Analytics.send(com.tangem.tap.common.analytics.events.Chat.ScreenOpened())
MessagingActivity.builder()
.withMultilineResponseOptionsEnabled(false)
.withBotLabelStringRes(R.string.chat_bot_name)

View file

@ -200,8 +200,8 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
suspend fun waitForCheckout(productType: ProductType) {
val result = shopifyService.checkout(true, checkouts[productType]!!.id)
result.onSuccess { checkout ->
checkout.order?.let {
val event = ShopOrderToEventConverter().convert(it to productType)
if (checkout.order != null && checkout.lineItems != null) {
val event = ShopOrderToEventConverter().convert(checkout to productType)
Analytics.send(event)
}
}

View file

@ -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) {

View file

@ -6,13 +6,13 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.common.services.Result
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.operations.backup.BackupService
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.backupService
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
@ -45,6 +45,7 @@ import kotlinx.coroutines.launch
// TODO: Create repository for that
object ScanCardProcessor {
suspend fun scan(
analyticsEvent: AnalyticsEvent? = null,
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
cardId: String? = null,
onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {},
@ -77,6 +78,8 @@ object ScanCardProcessor {
tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
onScanStateChange(false)
sendAnalytics(analyticsEvent, scanResponse.card.batchId)
checkForUnfinishedBackupForSaltPay(
backupService = backupService,
scanResponse = scanResponse,
@ -102,6 +105,14 @@ object ScanCardProcessor {
}
}
private fun sendAnalytics(
analyticsEvent: AnalyticsEvent?,
batchId: String,
) {
Analytics.addParamsInterceptor(BatchIdParamsInterceptor(batchId))
analyticsEvent?.let { Analytics.send(it) }
}
/**
* It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards
* see BackupAction.CheckForUnfinishedBackup
@ -173,14 +184,10 @@ object ScanCardProcessor {
crossinline onSuccess: suspend (ScanResponse) -> Unit,
crossinline onFailure: suspend (error: TangemError) -> Unit,
) {
Analytics.send(IntroductionProcess.CardWasScanned())
val globalState = store.state.globalState
val tapWalletManager = globalState.tapWalletManager
tapWalletManager.updateConfigManager(scanResponse)
Analytics.addParamsInterceptor(BatchIdParamsInterceptor(scanResponse.card.batchId))
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse))
if (scanResponse.isSaltPay()) {

View file

@ -1,5 +1,6 @@
package com.tangem.tap.domain.walletCurrencies.implementation
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.CompletionResult
import com.tangem.common.flatMap
@ -54,28 +55,17 @@ internal class DefaultWalletCurrenciesManager(
currenciesToAdd: List<Currency>,
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
val card = userWallet.scanResponse.card
val updatedBlockchainNetworks = currenciesToAdd
.addMissingBlockchains(card)
.toBlockchainNetworks()
val newCurrencies = (getSavedCurrencies(userWallet.walletId) + currenciesToAdd)
.addMissingBlockchains(card)
updateWalletStores(userWallet, updatedBlockchainNetworks)
updateWalletStores(userWallet, newCurrencies.toBlockchainNetworks())
.map {
saveUserCurrencies(card, newCurrencies)
}
.flatMap {
val updatedBlockchains = updatedBlockchainNetworks
.map { it.blockchain }
val updatedWalletStores = walletStoresRepository.get(userWallet.walletId)
.firstOrNull()
?.filter { it.blockchain in updatedBlockchains }
?: return@flatMap CompletionResult.Success(Unit)
walletAmountsRepository.updateAmountsForWalletStores(
walletStores = updatedWalletStores,
updateWalletStoresAmounts(
userWallet = userWallet,
fiatCurrency = appCurrencyProvider(),
updatedBlockchains = currenciesToAdd.map { it.blockchain }.distinct(),
)
}
}
@ -161,6 +151,10 @@ internal class DefaultWalletCurrenciesManager(
return newCurrencies
}
private fun findDerivationPath(currency: Currency, cardDerivationStyle: DerivationStyle?): String? {
return currency.derivationPath ?: currency.blockchain.derivationPath(cardDerivationStyle)?.rawPath
}
private suspend fun updateWalletStores(
userWallet: UserWallet,
blockchainNetworks: List<BlockchainNetwork>,
@ -184,7 +178,19 @@ internal class DefaultWalletCurrenciesManager(
.fold()
}
private fun findDerivationPath(currency: Currency, cardDerivationStyle: DerivationStyle?): String? {
return currency.derivationPath ?: currency.blockchain.derivationPath(cardDerivationStyle)?.rawPath
private suspend fun updateWalletStoresAmounts(
userWallet: UserWallet,
updatedBlockchains: List<Blockchain>,
): CompletionResult<Unit> {
val updatedWalletStores = walletStoresRepository.get(userWallet.walletId)
.firstOrNull()
?.filter { it.blockchain in updatedBlockchains }
?: return CompletionResult.Success(Unit)
return walletAmountsRepository.updateAmountsForWalletStores(
walletStores = updatedWalletStores,
userWallet = userWallet,
fiatCurrency = appCurrencyProvider(),
)
}
}

View file

@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow
interface WalletStoresManager {
fun getAll(): Flow<Map<UserWalletId, List<WalletStoreModel>>>
fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>>
suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel>
suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit>
suspend fun clear(): CompletionResult<Unit>

View file

@ -44,6 +44,10 @@ internal class DefaultWalletStoresManager(
.distinctUntilChanged()
}
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
return walletStoresRepository.getSync(userWalletId)
}
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
return walletStoresRepository.delete(userWalletsIds)
.flatMap { walletManagersRepository.delete(userWalletsIds) }

View file

@ -17,6 +17,10 @@ internal class DummyWalletStoresManager : WalletStoresManager {
return emptyFlow()
}
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
return emptyList()
}
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
return CompletionResult.Success(Unit)
}

View file

@ -158,12 +158,12 @@ internal fun List<WalletDataModel>.updateWithUnreachable(): List<WalletDataModel
}
internal fun List<WalletDataModel>.updateWithSelf(
walletsData: List<WalletDataModel>,
newWalletsData: List<WalletDataModel>,
): List<WalletDataModel> {
val oldWalletsData = this
val updatedWalletsData = arrayListOf<WalletDataModel>()
walletsData.forEach { newWalletData ->
newWalletsData.forEach { newWalletData ->
val walletDataToUpdate = oldWalletsData.find(newWalletData::isSameWalletData)
if (walletDataToUpdate != null) {
updatedWalletsData.add(walletDataToUpdate.updateWithSelf(newWalletData))

View file

@ -88,39 +88,34 @@ internal fun WalletStoreModel.updateWithRent(rent: WalletStoreModel.WalletRent?)
)
}
internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean {
return this.blockchain == other.blockchain &&
this.derivationPath == other.derivationPath
}
private inline fun List<WalletStoreModel>.replaceWalletStores(
walletStoresToUpdate: List<WalletStoreModel>,
update: (walletStore: WalletStoreModel) -> WalletStoreModel,
): List<WalletStoreModel> {
val mutableStores = ArrayList<WalletStoreModel>(this)
walletStoresToUpdate
.asSequence()
.map { it.blockchain to it.derivationPath }
.forEach { (blockchain, derivationPath) ->
val index = mutableStores.indexOfFirst {
it.blockchain == blockchain && it.derivationPath == derivationPath
}
val currentWalletStore = mutableStores[index]
val updatedWalletStore = update(currentWalletStore)
walletStoresToUpdate.forEach { walletStoreToUpdate ->
val index = mutableStores.indexOfFirst(walletStoreToUpdate::isSameWalletStore)
val currentWalletStore = mutableStores[index]
val updatedWalletStore = update(currentWalletStore)
if (currentWalletStore != updatedWalletStore) {
Timber.d(
"""
Update wallet store in storage
|- User wallet ID: ${updatedWalletStore.userWalletId}
|- Blockchain: ${updatedWalletStore.blockchain}
""".trimIndent(),
)
if (currentWalletStore != updatedWalletStore) {
Timber.d(
"""
Update wallet store in storage
|- User wallet ID: ${updatedWalletStore.userWalletId}
|- Blockchain: ${updatedWalletStore.blockchain}
""".trimIndent(),
)
mutableStores[index] = updatedWalletStore
}
mutableStores[index] = updatedWalletStore
}
}
return mutableStores
}
internal fun WalletStoreModel.isSameWalletStore(other: WalletStoreModel): Boolean {
return this.blockchain == other.blockchain &&
this.derivationPath == other.derivationPath
}

View file

@ -134,8 +134,8 @@ class DetailsMiddleware {
}
}
.doOnFailure { error ->
(error as? TangemSdkError)?.let { sdkError ->
Analytics.send(Settings.CardSettings.FactoryResetFinished(sdkError))
if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) {
Analytics.send(Settings.CardSettings.FactoryResetFinished(error))
}
}
}
@ -172,7 +172,8 @@ class DetailsMiddleware {
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success)
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
val error = result.error
if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) {
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue, error))
}
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)

View file

@ -44,7 +44,7 @@ sealed class SocialNetwork(val id: String, val iconRes: Int) {
object Facebook : SocialNetwork("Facebook", R.drawable.ic_facebook)
object Instagram : SocialNetwork("Instagram", R.drawable.ic_instagram)
object GitHub : SocialNetwork("GitHub", R.drawable.ic_github)
object Youtube : SocialNetwork("Youtube", R.drawable.ic_youtube)
object YouTube : SocialNetwork("YouTube", R.drawable.ic_youtube)
object LinkedIn : SocialNetwork("LinkedIn", R.drawable.ic_linkedin)
}
@ -55,7 +55,7 @@ object TangemSocialAccounts {
SocialNetworkLink(SocialNetwork.Facebook, "https://m.facebook.com/TangemCards/"),
SocialNetworkLink(SocialNetwork.Instagram, "https://instagram.com/tangemcards"),
SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"),
SocialNetworkLink(SocialNetwork.Youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
SocialNetworkLink(SocialNetwork.YouTube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"),
)
val accountsRu: List<SocialNetworkLink> = listOf(
@ -64,7 +64,7 @@ object TangemSocialAccounts {
SocialNetworkLink(SocialNetwork.Facebook, "https://m.facebook.com/TangemCards/"),
SocialNetworkLink(SocialNetwork.Instagram, "https://instagram.com/tangemcards"),
SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"),
SocialNetworkLink(SocialNetwork.Youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
SocialNetworkLink(SocialNetwork.YouTube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"),
)
}

View file

@ -61,6 +61,7 @@ class DetailsViewModel(private val store: Store<AppState>) {
private fun handleClickingSettingsItem(item: SettingsElement) {
when (item) {
SettingsElement.WalletConnect -> {
Analytics.send(Settings.ButtonWalletConnect())
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
}
SettingsElement.Chat -> {

View file

@ -9,7 +9,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
@ -23,6 +25,7 @@ class WalletConnectFragment : Fragment(), StoreSubscriber<WalletConnectState> {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(WalletConnect.ScreenOpened())
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(android.R.transition.fade)
exitTransition = inflater.inflateTransition(android.R.transition.fade)

View file

@ -82,7 +82,7 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
homeState,
onScanButtonClick = {
Analytics.send(IntroductionProcess.ButtonScanCard())
store.dispatch(HomeAction.ReadCard)
store.dispatch(HomeAction.ReadCard())
},
onShopButtonClick = {
Analytics.send(IntroductionProcess.ButtonBuyCards())

View file

@ -1,15 +1,18 @@
package com.tangem.tap.features.home.redux
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.entities.IndeterminateProgressButton
import org.rekotlin.Action
sealed class HomeAction : Action {
object Init : HomeAction()
data class ShouldScanCardOnResume(val shouldScanCard: Boolean) : HomeAction()
// from ui
object ReadCard : HomeAction()
data class ReadCard(
val analyticsEvent: AnalyticsEvent? = IntroductionProcess.CardWasScanned(),
) : HomeAction()
data class ScanInProgress(val scanInProgress: Boolean) : HomeAction()
data class GoToShop(val userCountryCode: String?) : HomeAction()

View file

@ -3,12 +3,14 @@ package com.tangem.tap.features.home.redux
import com.tangem.common.doOnFailure
import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.postUiDelayBg
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
@ -54,14 +56,11 @@ private fun handleHomeAction(action: Action) {
store.dispatch(GlobalAction.ExchangeManager.Init)
store.dispatch(GlobalAction.FetchUserCountry)
}
is HomeAction.ShouldScanCardOnResume -> {
if (action.shouldScanCard) {
store.dispatch(HomeAction.ShouldScanCardOnResume(false))
postUiDelayBg(700) { store.dispatch(HomeAction.ReadCard) }
}
is HomeAction.ReadCard -> {
readCard(action.analyticsEvent)
}
is HomeAction.ReadCard -> readCard()
is HomeAction.GoToShop -> {
Analytics.send(Shop.ScreenOpened())
when (action.userCountryCode) {
RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL)
else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
@ -70,12 +69,13 @@ private fun handleHomeAction(action: Action) {
}
}
private fun readCard() = scope.launch {
private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch {
delay(timeMillis = 200)
tangemSdkManager.setAccessCodeRequestPolicy(
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
)
ScanCardProcessor.scan(
analyticsEvent = analyticsEvent,
onProgressStateChange = { showProgress ->
if (showProgress) {
changeButtonState(ButtonState.PROGRESS)

View file

@ -18,9 +18,6 @@ private fun internalReduce(action: Action, state: AppState): HomeState {
is HomeAction.ScanInProgress -> {
state = state.copy(scanInProgress = action.scanInProgress)
}
is HomeAction.ShouldScanCardOnResume -> {
state = state.copy(shouldScanCardOnResume = action.shouldScanCard)
}
is HomeAction.ChangeScanCardButtonState -> {
state = state.copy(btnScanState = action.state)
}

View file

@ -6,7 +6,6 @@ import com.tangem.tap.features.wallet.redux.ProgressState
import org.rekotlin.StateType
data class HomeState(
val shouldScanCardOnResume: Boolean = false,
val scanInProgress: Boolean = false,
val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED),
) : StateType {

View file

@ -42,7 +42,7 @@ class AddressInfoBottomSheetDialog(
override fun show() {
super.show()
Analytics.send(Token.Recieve.ScreenOpened())
Analytics.send(Token.Receive.ScreenOpened())
showData(data = stateDialog.addressData)
}
@ -54,12 +54,12 @@ class AddressInfoBottomSheetDialog(
imvQrCode.setImageBitmap(data.qrCode)
tvAddress.text = data.address
btnFlCopyAddress.setOnClickListener {
Analytics.send(Token.Recieve.ButtonCopyAddress())
Analytics.send(Token.Receive.ButtonCopyAddress())
context.copyToClipboard(data.address)
store.dispatchToastNotification(R.string.copy_toast_msg)
}
btnFlShare.setOnClickListener {
Analytics.send(Token.Recieve.ButtonShareAddress())
Analytics.send(Token.Receive.ButtonShareAddress())
store.dispatchShare(data.shareUrl)
}
val blockchain = stateDialog.currency.blockchain

View file

@ -4,6 +4,8 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuProvider
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.feedback.SupportInfo
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.store
@ -19,6 +21,7 @@ class OnboardingMenuProvider : MenuProvider {
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.menu_item_chat_support -> {
Analytics.send(Onboarding.ButtonChat())
store.dispatch(GlobalAction.OpenChat(SupportInfo()))
true
}

View file

@ -175,16 +175,16 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
val topUpUrl = walletManager.getTopUpUrl() ?: return
val blockchain = walletManager.wallet.blockchain
if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) {
val dialogData = WalletDialog.RussianCardholdersWarningDialog.Data(topUpUrl, blockchain)
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog(dialogData))
return
}
val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain)
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
store.dispatchOpenUrl(topUpUrl)
if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) {
val dialogData = WalletDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog(dialogData))
} else {
store.dispatchOpenUrl(topUpUrl)
}
}
OnboardingNoteAction.Done -> {
store.dispatch(GlobalAction.Onboarding.Stop)

View file

@ -1,6 +1,5 @@
package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.extensions.Result
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard
@ -10,6 +9,7 @@ import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
@ -27,8 +27,11 @@ import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
@ -104,10 +107,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
CreateTwinWalletMode.CreateWallet -> {
if (preferencesStorage.wasTwinsOnboardingShown()) {
val step = when {
!scanResponse.twinsIsTwinned() -> {
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
TwinCardsStep.CreateFirstWallet
}
!scanResponse.twinsIsTwinned() -> TwinCardsStep.CreateFirstWallet
twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done
else -> TwinCardsStep.TopUpWallet
}
@ -148,18 +148,23 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
Analytics.send(Onboarding.Twins.ScreenOpened())
preferencesStorage.saveTwinsOnboardingShown()
}
TwinCardsStep.TopUpWallet -> store.dispatch(TwinCardsAction.Balance.Update)
TwinCardsStep.Done -> {
finishCardActivation()
postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) }
}
TwinCardsStep.CreateFirstWallet -> {
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
Analytics.send(Onboarding.Twins.SetupStarted())
scope.launch {
userWalletsListManager.delete(
listOfNotNull(UserWalletIdBuilder.scanResponse(getScanResponse()).build()),
)
}
}
TwinCardsStep.TopUpWallet -> {
Analytics.send(Onboarding.Topup.ScreenOpened())
store.dispatch(TwinCardsAction.Balance.Update)
}
TwinCardsStep.Done -> {
finishCardActivation()
postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) }
}
TwinCardsStep.None,
TwinCardsStep.Warning,
TwinCardsStep.CreateSecondWallet,
@ -168,7 +173,6 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
}
}
is TwinCardsAction.Wallet.LaunchFirstStep -> {
Analytics.send(Onboarding.Twins.SetupStarted())
val manager = TwinCardsManager(
card = getScanResponse().card,
assetReader = action.reader,
@ -289,12 +293,23 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
store.dispatchDialogShow(appDialog)
}
is TwinCardsAction.TopUp -> {
val topUpUrl = twinCardsState.walletManager?.getTopUpUrl() ?: return
val walletManager = twinCardsState.walletManager.guard {
store.dispatchDebugErrorNotification("NPE: WalletManager")
return
}
val currencyType = AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin)
val topUpUrl = walletManager.getTopUpUrl() ?: return
val blockchain = walletManager.wallet.blockchain
val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain)
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
store.dispatchOpenUrl(topUpUrl)
if (globalState.userCountryCode == RUSSIA_COUNTRY_CODE) {
val dialogData = WalletDialog.RussianCardholdersWarningDialog.Data(topUpUrl)
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog(dialogData))
} else {
store.dispatchOpenUrl(topUpUrl)
}
}
TwinCardsAction.Done -> {
val scanResponse = getScanResponse()

View file

@ -156,7 +156,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch:
if (scanResponse == null) {
store.dispatch(NavigationAction.PopBackTo())
store.dispatch(HomeAction.ReadCard)
store.dispatch(HomeAction.ReadCard())
} else {
val backupState = store.state.onboardingWalletState.backupState
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)

View file

@ -28,9 +28,9 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWa
private val viewModel by viewModels<SaveWalletViewModel>()
override fun onDismiss(dialog: DialogInterface) {
viewModel.dismiss()
super.onDismiss(dialog)
override fun onCancel(dialog: DialogInterface) {
viewModel.cancelOrClose()
super.onCancel(dialog)
}
@Composable
@ -51,7 +51,10 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWa
SaveWalletScreenContent(
showProgress = state.showProgress,
onSaveWalletClick = viewModel::saveWallet,
onCloseClick = this@SaveWalletBottomSheetFragment::dismissAllowingStateLoss,
onCloseClick = {
viewModel.cancelOrClose()
dismissAllowingStateLoss()
},
)
SnackbarHost(

View file

@ -25,7 +25,7 @@ internal class SaveWalletViewModel : ViewModel(), StoreSubscriber<SaveWalletStat
store.dispatch(SaveWalletAction.Save)
}
fun dismiss() {
fun cancelOrClose(){
store.dispatch(SaveWalletAction.Dismiss)
}

View file

@ -1,5 +1,7 @@
package com.tangem.tap.features.shop.redux
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.redux.AppState
@ -113,6 +115,7 @@ private fun handle(action: Action) {
}
}
ShopAction.StartWebCheckout -> {
Analytics.send(Shop.Redirected(null))
store.dispatchOpenUrl(shopService.getCheckoutUrl(shopState.selectedProduct))
store.dispatch(ShopAction.FinishSuccessfulOrder)
}

View file

@ -12,8 +12,6 @@ import androidx.activity.OnBackPressedCallback
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.GlobalLayoutStateHandler
import com.tangem.tap.common.KeyboardObserver
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.shop.data.ProductType
@ -43,7 +41,6 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(Shop.ScreenOpened())
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {

View file

@ -49,7 +49,6 @@ class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubsc
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
Analytics.send(ManageTokens.ScreenOpened())
addBackPressHandler(this)
}

View file

@ -7,6 +7,7 @@ import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.core.analytics.AnalyticsEvent
import com.tangem.domain.common.CardDTO
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.redux.NotificationAction
@ -101,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<BlockchainNetwork>) : MultiWallet()
object ScanToGetDerivations : MultiWallet()
}
@ -142,7 +144,7 @@ sealed class WalletAction : Action {
object Failure : WalletAction()
}
object Scan : WalletAction()
data class Scan(val onScanSuccessEvent: AnalyticsEvent?) : WalletAction()
data class Send(val amount: Amount? = null) : WalletAction()

View file

@ -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<BlockchainNetwork> = emptyList(),
val derivationsCheckIsScheduled: Boolean = false,
val loadingUserTokens: Boolean = false,
val walletCardsCount: Int? = null,
) : StateType {

View file

@ -5,10 +5,9 @@ import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.guard
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
@ -168,12 +167,15 @@ 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) {
scanAndUpdateCard(selectedWallet, walletState)
} else {
store.dispatch(WalletAction.Scan)
store.dispatch(WalletAction.Scan(onScanSuccessEvent = null))
}
}
}
@ -183,8 +185,9 @@ class MultiWalletMiddleware {
selectedUserWallet: UserWallet,
state: WalletState?,
) = scope.launch(Dispatchers.Default) {
Analytics.send(MainScreen.CardWasScanned())
dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation)
ScanCardProcessor.scan(
analyticsEvent = null,
cardId = selectedUserWallet.cardId,
additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain },
) { scanResponse ->

View file

@ -39,21 +39,22 @@ class TradeCryptoMiddleware {
state: () -> AppState?,
action: WalletAction.TradeCryptoAction.Buy,
) {
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
val currency = selectedWalletData.currency
Analytics.send(Token.ButtonBuy(AnalyticsParam.CurrencyType.Currency(currency)))
if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) {
store.dispatchOnMain(WalletAction.DialogAction.RussianCardholdersWarningDialog())
return
}
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
val card = store.state.globalState.scanResponse?.card ?: return
val addresses = selectedWalletData.walletAddresses?.list.orEmpty()
if (addresses.isEmpty()) return
val exchangeManager = store.state.globalState.exchangeManager
val appCurrency = store.state.globalState.appCurrency
val currency = selectedWalletData.currency
Analytics.send(Token.ButtonBuy(AnalyticsParam.CurrencyType.Currency(currency)))
if (currency is Currency.Token && currency.blockchain.isTestnet()) {
val walletManager = store.state.walletState.getWalletManager(currency)

View file

@ -12,6 +12,9 @@ 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
import com.tangem.tap.common.extensions.copyToClipboard
@ -33,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
@ -54,10 +58,12 @@ 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
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
@ -106,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
@ -193,8 +200,11 @@ class WalletMiddleware {
}
}
is WalletAction.Scan -> {
store.dispatch(HomeAction.ShouldScanCardOnResume(true))
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
scope.launch {
delay(700)
store.dispatchOnMain(HomeAction.ReadCard(action.onScanSuccessEvent))
}
}
is WalletAction.LoadCardInfo -> {
val attestationFailed = action.card.attestation.status == Attestation.Status.Failed
@ -248,10 +258,12 @@ class WalletMiddleware {
}
}
is WalletAction.CopyAddress -> {
Analytics.send(Token.Receive.ButtonCopyAddress())
action.context.copyToClipboard(action.address)
store.dispatch(WalletAction.CopyAddress.Success)
}
is WalletAction.ShareAddress -> {
Analytics.send(Token.Receive.ButtonShareAddress())
action.context.shareText(action.address)
}
is WalletAction.ExploreAddress -> {
@ -273,6 +285,9 @@ class WalletMiddleware {
} else {
store.dispatch(newAction)
if (newAction is PrepareSendScreen) {
store.state.walletState.selectedWalletData?.currency?.let { currency ->
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency)))
}
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
}
}
@ -285,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)
@ -360,7 +376,7 @@ class WalletMiddleware {
}
else -> {
Analytics.send(MainScreen.ButtonScanCard())
store.dispatch(WalletAction.Scan)
store.dispatch(WalletAction.Scan(MainScreen.CardWasScanned()))
}
}
}
@ -479,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)
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.wallet.redux.models
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.redux.StateDialog
import com.tangem.wallet.R
@ -17,7 +16,7 @@ sealed interface WalletDialog : StateDialog {
data class RemoveWalletDialog(
val currencyTitle: String,
val onOk: () -> Unit
val onOk: () -> Unit,
) : WalletDialog {
val messageRes: Int = R.string.token_details_hide_alert_message
val titleRes: Int = R.string.token_details_hide_alert_title
@ -26,13 +25,13 @@ sealed interface WalletDialog : StateDialog {
data class TokensAreLinkedDialog(
val currencyTitle: String,
val currencySymbol: String
val currencySymbol: String,
) : WalletDialog {
val messageRes: Int = R.string.token_details_unable_hide_alert_message
val titleRes: Int = R.string.token_details_unable_hide_alert_title
}
data class RussianCardholdersWarningDialog(val data: Data?) : WalletDialog {
data class Data(val topUpUrl: String, val blockchain: Blockchain)
data class Data(val topUpUrl: String)
}
}

View file

@ -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)
}

View file

@ -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<Walle
override fun newState(state: WalletState) {
if (activity == null || view == null) return
handleBasicAnalyticsEvent(state)
val isSaltPay = store.state.globalState.scanResponse?.card?.isSaltPay == true
when {
@ -258,11 +255,4 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (store.state.walletState.shouldShowDetails) inflater.inflate(R.menu.menu_wallet, menu)
}
private fun handleBasicAnalyticsEvent(state: WalletState) {
val scanResponse = store.state.globalState.scanResponse ?: return
BasicSignInEventConverter(scanResponse).convert(state)?.let { Analytics.send(it) }
BasicTopUpEventConverter(scanResponse).convert(state)?.let { Analytics.send(it) }
}
}

View file

@ -24,13 +24,14 @@ class WalletWarningConverter(
is WalletWarning.BalanceNotEnoughForFee -> {
context.getString(
R.string.token_details_send_blocked_fee_format,
message.blockchainFullName, message.blockchainFullName,
message.blockchainFullName,
message.blockchainFullName,
)
}
is WalletWarning.TransactionInProgress -> {
context.getString(
R.string.token_details_send_blocked_tx_format,
message.currencyName
message.currencyName,
)
}
is WalletWarning.Rent -> {

View file

@ -41,8 +41,6 @@ class RussianCardholdersWarningBottomSheetDialog(
binding?.btnYes?.setOnClickListener {
if (dialogData != null) {
val currencyType = AnalyticsParam.CurrencyType.Blockchain(dialogData.blockchain)
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
store.dispatchOpenUrl(dialogData.topUpUrl)
} else {
store.dispatch(WalletAction.TradeCryptoAction.Buy(checkUserLocation = false))

View file

@ -9,6 +9,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.ManageTokens
import com.tangem.tap.common.analytics.events.Portfolio
import com.tangem.tap.common.extensions.animateVisibility
import com.tangem.tap.common.extensions.formatAmountAsSpannedString
@ -136,6 +137,8 @@ class MultiWalletView : WalletView() {
derivationStyle = card.derivationStyle,
),
)
Analytics.send(ManageTokens.ScreenOpened())
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
}
handleErrorStates(state = state, binding = binding, fragment = fragment)

View file

@ -10,6 +10,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.common.analytics.events.MyWallets
import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
@ -121,7 +122,7 @@ internal class WalletSelectorMiddleware {
}
private fun unlockWalletsWithBiometry() {
Analytics.send(MyWallets.Button.UnlockWithBiometrics)
Analytics.send(MyWallets.Button.UnlockWithBiometrics())
scope.launch {
userWalletsListManager.unlockWithBiometry()
@ -135,7 +136,7 @@ internal class WalletSelectorMiddleware {
}
private fun addWallet() = scope.launch {
Analytics.send(MyWallets.Button.ScanNewCard)
Analytics.send(MyWallets.Button.ScanNewCard())
val prevUseBiometricsForAccessCode = tangemSdkManager.useBiometricsForAccessCode()
@ -145,6 +146,7 @@ internal class WalletSelectorMiddleware {
)
ScanCardProcessor.scan(
analyticsEvent = MyWallets.CardWasScanned(),
onWalletNotCreated = {
// No need to rollback policy, continue with the policy set before the card scan
store.dispatchOnMain(WalletSelectorAction.AddWallet.Success)
@ -175,8 +177,6 @@ internal class WalletSelectorMiddleware {
return userWalletsListManager.save(userWallet)
.doOnSuccess {
Analytics.send(MyWallets.CardWasScanned)
store.dispatchOnMain(WalletSelectorAction.AddWallet.Success)
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
store.onUserWalletSelected(userWallet)
@ -199,6 +199,9 @@ internal class WalletSelectorMiddleware {
.doOnSuccess {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
if (selectedUserWallet != null) {
val batchId = selectedUserWallet.scanResponse.card.batchId
Analytics.addParamsInterceptor(BatchIdParamsInterceptor(batchId))
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
store.onUserWalletSelected(selectedUserWallet)
}
@ -207,6 +210,7 @@ internal class WalletSelectorMiddleware {
}
private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult<Unit> {
Analytics.send(MyWallets.Button.WalletUnlockTapped())
tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse)
return tangemSdkManager.scanCard(userWallet.cardId)
.map { scannedCard ->
@ -227,7 +231,7 @@ internal class WalletSelectorMiddleware {
}
private fun deleteWallets(userWalletsIds: List<UserWalletId>, state: WalletSelectorState) {
Analytics.send(MyWallets.Button.DeleteWalletTapped)
Analytics.send(MyWallets.Button.DeleteWalletTapped())
scope.launch {
when (userWalletsIds.size) {
@ -244,8 +248,6 @@ internal class WalletSelectorMiddleware {
}
private fun renameWallet(userWalletId: UserWalletId, newName: String) {
Analytics.send(MyWallets.Button.EditWalletTapped)
scope.launch {
userWalletsListManager.update(userWalletId) { it.copy(name = newName) }
.doOnFailure { error ->

View file

@ -20,9 +20,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.fragment.app.viewModels
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.MyWallets
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent
@ -33,7 +33,7 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
private val viewModel by viewModels<WalletSelectorViewModel>()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Analytics.send(MyWallets.MyWalletsScreenOpened)
Analytics.send(MyWallets.MyWalletsScreenOpened())
return super.onCreateDialog(savedInstanceState)
}

View file

@ -2,7 +2,9 @@ package com.tangem.tap.features.walletSelector.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.common.analytics.events.MyWallets
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
@ -71,6 +73,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
.find { it.id == editedUserWalletId }
if (editedUserWallet != null) {
Analytics.send(MyWallets.Button.EditWalletTapped())
val dialog = RenameWalletDialog(
currentName = editedUserWallet.name,
onApply = { newName ->

View file

@ -4,6 +4,7 @@ import android.content.Intent
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.analytics.events.SignIn
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
@ -98,6 +99,7 @@ internal class WelcomeMiddleware {
useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes,
)
ScanCardProcessor.scan(
analyticsEvent = SignIn.CardWasScanned(),
onSuccess = { scanResponse ->
scope.launch { onCardScanned(scanResponse) }
},

View file

@ -1,5 +1,6 @@
package com.tangem.tap.features.welcome.ui
import android.os.Bundle
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
@ -18,9 +19,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.fragment.app.viewModels
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.fragments.ComposeFragment
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.analytics.events.SignIn
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent
import com.tangem.wallet.R
@ -28,6 +31,11 @@ import com.tangem.wallet.R
internal class WelcomeFragment : ComposeFragment<WelcomeScreenState>() {
private val viewModel by viewModels<WelcomeViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(SignIn.ScreenOpened())
}
@Composable
override fun provideState(): State<WelcomeScreenState> {
return viewModel.state.collectAsState()

View file

@ -1,6 +1,8 @@
package com.tangem.tap.features.welcome.ui
import androidx.lifecycle.ViewModel
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.SignIn
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.tap.features.welcome.redux.WelcomeAction
@ -21,10 +23,12 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
}
fun unlockWallets() {
Analytics.send(SignIn.ButtonBiometricSignIn())
store.dispatch(WelcomeAction.ProceedWithBiometrics)
}
fun scanCard() {
Analytics.send(SignIn.ButtonCardSignIn())
store.dispatch(WelcomeAction.ProceedWithCard)
}