diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 4ffc45da6d..d2b413e412 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -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)) diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt index 4ac3547aa0..6783e3762e 100644 --- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt @@ -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()) } } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 52fa450434..1eeda46c6f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -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 { +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?, +) { + 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.calculateTotalCryptoAmount(): BigDecimal { + return this + .map { it.status.amount } + .reduce(BigDecimal::plus) + } +} + +class BasicSignInEventConverter : Converter { + + 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 { +class BasicTopUpEventConverter : Converter { - 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): 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 } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt index e64b751f4f..e359de8e5a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt @@ -22,6 +22,7 @@ class ParamCardCurrencyConverter : Converter 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 diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt index ddbd1683bd..49f6762db9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt @@ -8,19 +8,19 @@ import com.tangem.tap.common.shop.data.ProductType /** [REDACTED_AUTHOR] */ -class ShopOrderToEventConverter : Converter, Shop.Purchased> { +class ShopOrderToEventConverter : Converter, Shop.Purchased> { - override fun convert(value: Pair): Shop.Purchased { - val order = value.first + override fun convert(value: Pair): 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) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt index 2b660fa11d..4db0526ad2 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt @@ -7,14 +7,14 @@ sealed class MyWallets( params: Map = 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") } } \ No newline at end of file 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 91a3ae0741..8eb0f6490d 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 @@ -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", diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index c9b0419a49..03cbb88ac6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -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", diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt index 788f557537..0ab4dd9361 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt @@ -11,7 +11,7 @@ sealed class Shop( params: Map = 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(), ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt new file mode 100644 index 0000000000..cad34430d9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt @@ -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 = 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") +} \ No newline at end of file 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 99badb9998..0ad3ca514e 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 @@ -46,14 +46,14 @@ sealed class Token( params = mapOf("Token" to type.value), ) - sealed class Recieve( + sealed class Receive( event: String, params: Map = 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( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt index b3e4b835be..9ce10213ac 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt @@ -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") diff --git a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt b/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt deleted file mode 100644 index 738d3fed8f..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/filters/ShopPurchasedEventFilter.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt index a0b6cb378f..d1b3189ab1 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt @@ -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" } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt index 2172fe9aee..7430fcc17f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt @@ -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) + } } } diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt index 12fe1e42e9..b44db4004b 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt index 47951bfb84..95b6a0e7db 100644 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt @@ -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) } } 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 ea26d15848..4eb41fd759 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -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) { diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 7b33c2f9fc..b9e8f526f9 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -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? = 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()) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index dd19a02575..2b5a565f7e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -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, ): CompletionResult = 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, @@ -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, + ): CompletionResult { + 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(), + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt index e834cf0541..f1fdaff7aa 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/WalletStoresManager.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow interface WalletStoresManager { fun getAll(): Flow>> fun get(userWalletId: UserWalletId): Flow> + suspend fun getSync(userWalletId: UserWalletId): List suspend fun delete(userWalletsIds: List): CompletionResult suspend fun clear(): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt index 219d7f4af1..22e86d2fa1 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -44,6 +44,10 @@ internal class DefaultWalletStoresManager( .distinctUntilChanged() } + override suspend fun getSync(userWalletId: UserWalletId): List { + return walletStoresRepository.getSync(userWalletId) + } + override suspend fun delete(userWalletsIds: List): CompletionResult { return walletStoresRepository.delete(userWalletsIds) .flatMap { walletManagersRepository.delete(userWalletsIds) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt index b1729a0b11..7657f2d47b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DummyWalletStoresManager.kt @@ -17,6 +17,10 @@ internal class DummyWalletStoresManager : WalletStoresManager { return emptyFlow() } + override suspend fun getSync(userWalletId: UserWalletId): List { + return emptyList() + } + override suspend fun delete(userWalletsIds: List): CompletionResult { return CompletionResult.Success(Unit) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt index 7ad0e38363..64e2241737 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt @@ -158,12 +158,12 @@ internal fun List.updateWithUnreachable(): List.updateWithSelf( - walletsData: List, + newWalletsData: List, ): List { val oldWalletsData = this val updatedWalletsData = arrayListOf() - walletsData.forEach { newWalletData -> + newWalletsData.forEach { newWalletData -> val walletDataToUpdate = oldWalletsData.find(newWalletData::isSameWalletData) if (walletDataToUpdate != null) { updatedWalletsData.add(walletDataToUpdate.updateWithSelf(newWalletData)) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 2aa7ffcf2d..0e9e9d8715 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -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.replaceWalletStores( walletStoresToUpdate: List, update: (walletStore: WalletStoreModel) -> WalletStoreModel, ): List { val mutableStores = ArrayList(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 } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index f6e2c4b7c4..8e9b0ccbb7 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index bf24996168..fcdc6689cf 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -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 = 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"), ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 6165dc5c39..4c75feefe3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -61,6 +61,7 @@ class DetailsViewModel(private val store: Store) { private fun handleClickingSettingsItem(item: SettingsElement) { when (item) { SettingsElement.WalletConnect -> { + Analytics.send(Settings.ButtonWalletConnect()) store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions)) } SettingsElement.Chat -> { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt index ec42337992..8b29784828 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectFragment.kt @@ -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 { 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) diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index 3c2eb278d7..60dc234e4f 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -82,7 +82,7 @@ class HomeFragment : Fragment(), StoreSubscriber { homeState, onScanButtonClick = { Analytics.send(IntroductionProcess.ButtonScanCard()) - store.dispatch(HomeAction.ReadCard) + store.dispatch(HomeAction.ReadCard()) }, onShopButtonClick = { Analytics.send(IntroductionProcess.ButtonBuyCards()) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index 6b71aee0f7..59e169ee04 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -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() diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 167b7ec607..b85a6601c5 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index 511aea3ddc..8e1583baa5 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -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) } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt index 9ef6ea2b57..618e432dec 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt index 0372ae6ee4..66e30fe595 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt index 2fff1d0fab..4af057eb58 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt @@ -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 } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt index e63a4a9b7c..c4f112770f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index 4492881a57..6c913f0b1e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -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() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 6c35be3ead..84e02b7b66 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt index f4663eda6e..63803d4095 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt @@ -28,9 +28,9 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() - 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 { + Analytics.send(Shop.Redirected(null)) store.dispatchOpenUrl(shopService.getCheckoutUrl(shopState.selectedProduct)) store.dispatch(ShopAction.FinishSuccessfulOrder) } diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt index 1f32a5305e..622f440a18 100644 --- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt @@ -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() { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 499ed8e2f5..6550d26e1e 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -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) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 0cc8039e96..dc3e074fac 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -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) : 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() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 49773a9854..0566b5d4a4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -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 = emptyList(), + val derivationsCheckIsScheduled: Boolean = false, val loadingUserTokens: Boolean = false, val walletCardsCount: Int? = null, ) : StateType { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 87341ef9c0..e6798ea6ca 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -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 -> diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 0bb74bbf6b..33b7f54788 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 96922afa51..80b1ddfc2b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt index fc182e2c4f..fb16e8420c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index afb523a5ca..711373d52f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -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) } 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 7c622df177..15eacf8c99 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,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 { 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 -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt index ca665749bb..e504ff3172 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt @@ -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)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index a20ebd503d..06e2772ba4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -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) 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 38832e455a..4405407dcd 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 @@ -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 { + 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, 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 -> 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 96bba1df0e..f8bc829bde 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 @@ -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() override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - Analytics.send(MyWallets.MyWalletsScreenOpened) + Analytics.send(MyWallets.MyWalletsScreenOpened()) return super.onCreateDialog(savedInstanceState) } 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 52a99013e3..edb923f5ba 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 @@ -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 diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index e7cb2e01e7..c1cdf0f74e 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -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) } }, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt index 9ff1ad727f..70477f5ad1 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeFragment.kt @@ -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() { private val viewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Analytics.send(SignIn.ScreenOpened()) + } + @Composable override fun provideState(): State { return viewModel.state.collectAsState() diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt index a047d8518e..b810503287 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt @@ -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 { } fun unlockWallets() { + Analytics.send(SignIn.ButtonBiometricSignIn()) store.dispatch(WelcomeAction.ProceedWithBiometrics) } fun scanCard() { + Analytics.send(SignIn.ButtonCardSignIn()) store.dispatch(WelcomeAction.ProceedWithCard) } diff --git a/network/src/main/java/com/tangem/network/common/MoshiConverter.kt b/network/src/main/java/com/tangem/network/common/MoshiConverter.kt new file mode 100644 index 0000000000..e69de29bb2