diff --git a/app/src/debug/res/values-ru/strings.xml b/app/src/debug/res/values-ru/strings.xml index 1abc31ee12..682b552975 100644 --- a/app/src/debug/res/values-ru/strings.xml +++ b/app/src/debug/res/values-ru/strings.xml @@ -1,6 +1,6 @@ - DebugTangem + DebugTangem \ No newline at end of file diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml index bf9ef4d842..764dd201cc 100644 --- a/app/src/debug/res/values/strings.xml +++ b/app/src/debug/res/values/strings.xml @@ -1,6 +1,6 @@ - DebugTangem + DebugTangem \ No newline at end of file diff --git a/app/src/debug_beta/res/values-ru/strings.xml b/app/src/debug_beta/res/values-ru/strings.xml index 3e2d147b31..3ae149049f 100644 --- a/app/src/debug_beta/res/values-ru/strings.xml +++ b/app/src/debug_beta/res/values-ru/strings.xml @@ -1,6 +1,6 @@ - BetaTangem + BetaTangem \ No newline at end of file diff --git a/app/src/debug_beta/res/values/strings.xml b/app/src/debug_beta/res/values/strings.xml index 3e2d147b31..3ae149049f 100644 --- a/app/src/debug_beta/res/values/strings.xml +++ b/app/src/debug_beta/res/values/strings.xml @@ -1,6 +1,6 @@ - BetaTangem + BetaTangem \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e7d11764b4..f09883d42e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,7 @@ { is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) is BackupDialog.ConfirmDiscardingBackup -> ConfirmDiscardingBackupDialog.create(context) is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context) + is SaltPayDialog.Activation.PutVisaCard -> PutVisaCardDialog.create(context) is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog) is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context) is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context, state.dialog) 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 f60b23937b..ec30d82d45 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 264c3a5567..49a4b4854f 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,79 +2,157 @@ package com.tangem.tap.common.analytics.converters import com.tangem.common.Converter import com.tangem.common.extensions.isZero -import com.tangem.domain.common.CardTypesResolver +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.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.cardTypesResolver, value)) return null - val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver) ?: 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.cardTypesResolver.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.cardTypesResolver) + } + + 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.cardTypesResolver, value)) return null - val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver) ?: 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(cardTypesResolver: CardTypesResolver, state: WalletState): Boolean { - if (cardTypesResolver.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 95ec22a8aa..61d71c5a14 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 @@ -18,6 +18,7 @@ class ParamCardCurrencyConverter : Converter AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain()) 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 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/extensions/Specific.kt b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt index a2f9dc5f7b..ff4f898d8d 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Specific.kt @@ -11,7 +11,7 @@ import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.* -// todo move extensions to utils +// TODO: move extensions to utils fun BigDecimal.toFormattedString( decimals: Int, roundingMode: RoundingMode = RoundingMode.DOWN, @@ -75,7 +75,7 @@ fun BigDecimal.toFormattedFiatValue( ): String { val fiatValue = this.setScale(2, RoundingMode.HALF_UP) .let { if (formatWithSpaces) it.formatWithSpaces() else it } - return " $fiatValue  $fiatCurrencyName" + return " $fiatValue $fiatCurrencyName" } fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString() 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 4408155f8e..8366078322 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 @@ -202,8 +202,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 82f2159279..dd13b37e8d 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -29,6 +29,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 @@ -123,6 +124,7 @@ class TapWalletManager { .doOnSuccess { Timber.d("Wallet stores fetched for ${userWallet.walletId}") store.dispatchOnMain(WalletAction.LoadData.Success) + handleBasicAnalyticsEvent() } .doOnFailure { error -> val errorAction = when (error) { @@ -200,6 +202,7 @@ class TapWalletManager { } if (data.cardTypesResolver.isMultiwalletAllowed()) { + dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation) loadMultiWalletData(data) } else { loadSingleWalletData(data) @@ -222,9 +225,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) { @@ -276,7 +277,9 @@ class TapWalletManager { } suspend fun reloadData(data: ScanResponse) { - loadUserCurrencies(data, walletManagerFactory) + if (data.cardTypesResolver.isMultiwalletAllowed()) { + loadUserCurrencies(data, walletManagerFactory) + } withContext(Dispatchers.Main) { getActionIfUnknownBlockchainOrEmptyWallet(data)?.let { store.dispatch(it) diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt index 4462f4d8ec..3429afa07c 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt @@ -14,6 +14,7 @@ import java.math.BigDecimal * @param existentialDeposit Amount that must be held on currency's balance, if balance is below that amount all * founds will be destroyed. Null if currency don't have existential deposit * @param fiatRate Wallet's fiat rate, used to calculate fiat balance. Null if not provided + * @param isCardSingleToken shows that [Currency] is a card token * */ data class WalletDataModel( val currency: Currency, @@ -21,6 +22,7 @@ data class WalletDataModel( val walletAddresses: List, val existentialDeposit: BigDecimal?, val fiatRate: BigDecimal?, + val isCardSingleToken: Boolean, ) { /** diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt index a0511784e2..7bca1028c0 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt @@ -6,10 +6,12 @@ import com.tangem.common.hdWallet.DerivationPath import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.WalletStoreModel.WalletRent import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.features.wallet.models.Currency import java.math.BigDecimal /** * Contains info about the blockchain and its currencies + * * @param userWalletId ID of the associated [UserWallet] * @param blockchain [Blockchain] of this WalletStore * @param derivationPath [DerivationPath] of this store, null if the card does not support the @@ -21,6 +23,9 @@ import java.math.BigDecimal * TODO: Remove after WalletMiddleware refactoring * @param walletManager [WalletManager], may be null if it fails to create this manager. * TODO: Remove after WalletMiddleware refactoring + * + * @property blockchainWalletData Returns the [WalletDataModel] of the blockchain of this wallet store + * or throw [NoSuchElementException] if this wallet store not contains [WalletDataModel] of the blockchain * */ data class WalletStoreModel( val userWalletId: UserWalletId, @@ -34,6 +39,9 @@ data class WalletStoreModel( val walletManager: WalletManager?, ) { + val blockchainWalletData: WalletDataModel + get() = walletsData.first { it.currency is Currency.Blockchain } + /** * Represents wallet blockchain rent * @param rent Amount that will be charged in overtime if the blockchain does not have an amount greater than diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt index ada8c6896e..b48ae715a2 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -97,6 +97,7 @@ private fun BlockchainNetwork.getBlockchainWalletData(walletManager: WalletManag walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(), existentialDeposit = getExistentialDeposit(walletManager), fiatRate = null, + isCardSingleToken = false, ) } @@ -113,6 +114,7 @@ private fun BlockchainNetwork.getTokensWalletsData(walletManager: WalletManager? walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(), existentialDeposit = getExistentialDeposit(walletManager), fiatRate = null, + isCardSingleToken = walletManager?.cardTokens?.contains(token) ?: false, ) } } @@ -128,6 +130,7 @@ private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): Wal walletAddresses = wallet.createAddressesData(), existentialDeposit = getExistentialDeposit(walletManager), fiatRate = null, + isCardSingleToken = false, ) } @@ -143,6 +146,7 @@ private fun Token.toTokenWalletData(walletManager: WalletManager): WalletDataMod walletAddresses = wallet.createAddressesData(), existentialDeposit = getExistentialDeposit(walletManager), fiatRate = null, + isCardSingleToken = walletManager.cardTokens.contains(this), ) } 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 f8404f04ab..07e0eee2d4 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 @@ -7,17 +7,15 @@ 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.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 import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa -import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -30,6 +28,7 @@ import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState import com.tangem.tap.preferencesStorage @@ -37,7 +36,6 @@ import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.userTokensRepository -import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -45,6 +43,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 = {}, @@ -69,7 +68,6 @@ object ScanCardProcessor { result .doOnFailure { error -> - onProgressStateChange(false) onScanStateChange(false) onFailure(error) } @@ -77,14 +75,15 @@ object ScanCardProcessor { tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) onScanStateChange(false) + sendAnalytics(analyticsEvent, scanResponse.card.batchId) + checkForUnfinishedBackupForSaltPay( backupService = backupService, scanResponse = scanResponse, - onProgressStateChange = { onProgressStateChange(it) }, + onFailure = onFailure, nextHandler = { scanResponse1 -> showDisclaimerIfNeed( scanResponse = scanResponse1, - onProgressStateChange = onProgressStateChange, disclaimerWillShow = disclaimerWillShow, onFailure = onFailure, nextHandler = { scanResponse2 -> @@ -102,16 +101,24 @@ 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 * If user touches card other than Visa SaltPay - show dialog and block next processing */ - private inline fun checkForUnfinishedBackupForSaltPay( + private suspend inline fun checkForUnfinishedBackupForSaltPay( backupService: BackupService, scanResponse: ScanResponse, - onProgressStateChange: (showProgress: Boolean) -> Unit, nextHandler: (ScanResponse) -> Unit, + onFailure: suspend (error: TangemError) -> Unit, ) { if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { nextHandler(scanResponse) @@ -123,8 +130,9 @@ object ScanCardProcessor { ?: false if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { - onProgressStateChange(false) - showSaltPayTapVisaLogoCardDialog() + val error = SaltPayActivationError.PutVisaCard + SaltPayExceptionHandler.handle(error) + onFailure(TangemSdkError.ExceptionError(error)) } else { nextHandler(scanResponse) } @@ -133,7 +141,6 @@ object ScanCardProcessor { private suspend inline fun showDisclaimerIfNeed( scanResponse: ScanResponse, crossinline disclaimerWillShow: () -> Unit = {}, - crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, crossinline nextHandler: suspend (ScanResponse) -> Unit, crossinline onFailure: suspend (error: TangemError) -> Unit, ) { @@ -141,30 +148,27 @@ object ScanCardProcessor { store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) if (disclaimer.isAccepted()) { - nextHandler(scanResponse) - } else { - scope.launch { - delay(DELAY_SDK_DIALOG_CLOSE) - disclaimerWillShow() - dispatchOnMain( - DisclaimerAction.Show( - fromScreen = AppScreen.Home, - callback = DisclaimerCallback( - onAccept = { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, - onDismiss = { - scope.launch(Dispatchers.Main) { - onProgressStateChange(false) - onFailure(TangemSdkError.UserCancelled()) - } - }, - ), + nextHandler((scanResponse)) + } else scope.launch { + delay(DELAY_SDK_DIALOG_CLOSE) + disclaimerWillShow() + dispatchOnMain( + DisclaimerAction.Show( + fromScreen = AppScreen.Home, + callback = DisclaimerCallback( + onAccept = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismiss = { + scope.launch(Dispatchers.Main) { + onFailure(TangemSdkError.UserCancelled()) + } + }, ), - ) - } + ), + ) } } @@ -176,14 +180,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.cardTypesResolver.isSaltPay()) { @@ -204,25 +204,24 @@ object ScanCardProcessor { } else { delay(DELAY_SDK_DIALOG_CLOSE) onSuccess(scanResponse) - onProgressStateChange(false) } } is Result.Failure -> { - SaltPayExceptionHandler.handle(result.error) delay(DELAY_SDK_DIALOG_CLOSE) + SaltPayExceptionHandler.handle(result.error) onFailure(TangemSdkError.ExceptionError(result.error)) - onProgressStateChange(false) } } } } else { delay(DELAY_SDK_DIALOG_CLOSE) if (scanResponse.card.backupStatus?.isActive == false) { - showSaltPayTapVisaLogoCardDialog() + val error = SaltPayActivationError.PutVisaCard + SaltPayExceptionHandler.handle(error) + onFailure(TangemSdkError.ExceptionError(error)) } else { onSuccess(scanResponse) } - onProgressStateChange(false) } } else { if (OnboardingHelper.isOnboardingCase(scanResponse)) { @@ -238,21 +237,11 @@ object ScanCardProcessor { } else { delay(DELAY_SDK_DIALOG_CLOSE) onSuccess(scanResponse) - onProgressStateChange(false) } } } } - private fun showSaltPayTapVisaLogoCardDialog() { - store.dispatchDialogShow( - AppDialog.SimpleOkDialogRes( - headerId = R.string.saltpay_error_empty_backup_title, - messageId = R.string.saltpay_error_empty_backup_message, - ), - ) - } - private suspend inline fun navigateTo( screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit, diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt index 12fd8abb1e..faa4628b9b 100644 --- a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt @@ -48,7 +48,10 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { is WalletDataModel.SameCurrencyTransactionInProgress, is WalletDataModel.TransactionInProgress, is WalletDataModel.NoAccount, - -> TotalFiatBalanceStatus.Loaded + -> { + if (walletData.fiatRate == null) TotalFiatBalanceStatus.Error + else TotalFiatBalanceStatus.Loaded + } is WalletDataModel.Unreachable, is WalletDataModel.MissedDerivation, -> TotalFiatBalanceStatus.Error 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 ed773b522d..e4bebc2c07 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 @@ -22,6 +22,7 @@ interface WalletStoresManager { * @return [Flow] with [WalletStoreModel] list * */ fun get(userWalletId: UserWalletId): Flow> + suspend fun getSync(userWalletId: UserWalletId): List /** * Delete [WalletStoreModel]s associated with provided [UserWalletId]s 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/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 2739a8c2b1..a7dc0a853d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -26,6 +26,7 @@ import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStores import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithAmounts +import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithDemoAmounts import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithError import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithMissedDerivation @@ -33,6 +34,7 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithUnreachable import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage +import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransactionType import com.tangem.tap.features.wallet.models.filterByCoin @@ -208,6 +210,8 @@ internal class DefaultWalletAmountsRepository( updateWalletStoreWithAmounts( walletStore = walletStore, updatedWallet = walletManager.wallet, + // FIXME: move DemoHelper to Demo core module maybe + isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId), ) } .flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) } @@ -297,6 +301,7 @@ internal class DefaultWalletAmountsRepository( private suspend fun updateWalletStoreWithAmounts( walletStore: WalletStoreModel, updatedWallet: Wallet, + isDemo: Boolean, ) = withContext(Dispatchers.Default) { Timber.d( """ @@ -310,7 +315,11 @@ internal class DefaultWalletAmountsRepository( prevState.replaceWalletStore( walletStoreToUpdate = walletStore, update = { - it.updateWithAmounts(wallet = updatedWallet) + if (isDemo) { + it.updateWithDemoAmounts(wallet = updatedWallet) + } else { + it.updateWithAmounts(wallet = updatedWallet) + } }, ) } 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 64e2241737..cb49fe33c1 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 @@ -7,6 +7,7 @@ import com.tangem.common.core.TangemError import com.tangem.tap.domain.extensions.amountToCreateAccount import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.model.WalletDataModel +import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.getPendingTransactions import java.math.BigDecimal @@ -74,12 +75,25 @@ internal fun WalletDataModel.updateWithAmount(wallet: Wallet): WalletDataModel { ) } +internal fun WalletDataModel.updateWithDemoAmount(wallet: Wallet): WalletDataModel { + val amount = DemoHelper.config.getBalance(wallet.blockchain) + return this.copy( + status = WalletDataModel.VerifiedOnline(amount = amount.value ?: BigDecimal.ZERO), + ) +} + internal fun List.updateWithAmounts(wallet: Wallet): List { return this.map { walletData -> walletData.updateWithAmount(wallet) } } +internal fun List.updateWithDemoAmounts(wallet: Wallet): List { + return this.map { walletData -> + walletData.updateWithDemoAmount(wallet) + } +} + internal fun WalletDataModel.updateWithError( wallet: Wallet, error: TangemError, @@ -164,7 +178,7 @@ internal fun List.updateWithSelf( val updatedWalletsData = arrayListOf() newWalletsData.forEach { newWalletData -> - val walletDataToUpdate = oldWalletsData.find(newWalletData::isSameWalletData) + val walletDataToUpdate = oldWalletsData.firstOrNull(newWalletData::isSameWalletData) if (walletDataToUpdate != null) { updatedWalletsData.add(walletDataToUpdate.updateWithSelf(newWalletData)) } else { @@ -176,5 +190,5 @@ internal fun List.updateWithSelf( } internal fun WalletDataModel.isSameWalletData(other: WalletDataModel): Boolean { - return currency == other.currency + return this.currency == other.currency } \ No newline at end of file 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 0e9e9d8715..36d9eac8b2 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 @@ -49,6 +49,14 @@ internal fun WalletStoreModel.updateWithAmounts( ) } +internal fun WalletStoreModel.updateWithDemoAmounts( + wallet: Wallet, +): WalletStoreModel { + return this.copy( + walletsData = walletsData.updateWithDemoAmounts(wallet = wallet), + ) +} + internal fun WalletStoreModel.updateWithFiatRates( rates: Map, ): WalletStoreModel { @@ -96,26 +104,30 @@ private inline fun List.replaceWalletStores( walletStoresToUpdate.forEach { walletStoreToUpdate -> val index = mutableStores.indexOfFirst(walletStoreToUpdate::isSameWalletStore) + // Can be possible if user hides wallet store when it's tokens is loading + if (index == -1) return@forEach + 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 && + return this.userWalletId == other.userWalletId && + 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 757011d676..203af20870 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 @@ -140,8 +140,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)) } } } @@ -179,7 +179,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 ac69fe0ad8..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 @@ -12,7 +12,7 @@ data class DetailsScreenState( val onItemsClick: (SettingsElement) -> Unit, val onSocialNetworkClick: (SocialNetworkLink) -> Unit, ) { - val appNameRes: Int = R.string.app_name + val appNameRes: Int = R.string.tangem_app_name } @Immutable @@ -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 cd4f28fe78..2f46d7e7f8 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 @@ -62,6 +62,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 c6adeb7177..3ed76fdee2 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 0e0dc87965..f01fdaeb7c 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 @@ -83,7 +83,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/compose/views/HomeButtons.kt b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt index eca85a7f61..95049be464 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/views/HomeButtons.kt @@ -97,6 +97,7 @@ fun RowScope.ProgressButton( .weight(1f) .height(48.dp), onClick = onClick, + enabled = !inProgress, colors = ButtonDefaults.textButtonColors( backgroundColor = backgroundColor, contentColor = contentColor, 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 0e98babe31..44dbb45fda 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 @@ -52,14 +54,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(ms = 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)) @@ -68,19 +67,19 @@ 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) + } else { + changeButtonState(ButtonState.ENABLED) } - // else { //todo hide this because - // changeButtonState(ButtonState.ENABLED) - // } }, onScanStateChange = { scanInProgress -> store.dispatch(HomeAction.ScanInProgress(scanInProgress)) @@ -92,25 +91,32 @@ private fun readCard() = scope.launch { scope.launch { if (preferencesStorage.shouldSaveUserWallets) { val userWallet = UserWalletBuilder(scanResponse).build() ?: return@launch - userWalletsListManager.save(userWallet).doOnFailure { error -> - Timber.e(error, "Unable to save user wallet") - store.onCardScanned(scanResponse) - }.doOnSuccess { - scope.launch { store.onUserWalletSelected(userWallet) } - }.doOnResult { - changeButtonState(ButtonState.ENABLED) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) - } + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + store.onCardScanned(scanResponse) + } + .doOnSuccess { + scope.launch { store.onUserWalletSelected(userWallet) } + } + .doOnResult { + navigateTo(AppScreen.Wallet) + } } else { store.onCardScanned(scanResponse) - changeButtonState(ButtonState.ENABLED) - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) + navigateTo(AppScreen.Wallet) } } }, ) } +private suspend fun navigateTo(appScreen: AppScreen) { + store.dispatchOnMain(NavigationAction.NavigateTo(appScreen)) + delay(200) + changeButtonState(ButtonState.ENABLED) +} + private fun changeButtonState(state: ButtonState) { store.dispatchOnMain(HomeAction.ChangeScanCardButtonState(IndeterminateProgressButton(state))) } \ No newline at end of file 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 a998d9a359..f9f2911c32 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 @@ -15,9 +15,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 4538cb5145..152bbcc49e 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) + } } is 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 01290fcdad..e9cb977dc5 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.domain.common.extensions.withMainContext import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE 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 @@ -103,10 +106,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 } @@ -147,18 +147,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, @@ -167,7 +172,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, @@ -288,12 +292,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 a8e20e6640..8b7db136ec 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 @@ -154,7 +154,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/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt index 3a17f07fb7..8921be37f3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt @@ -38,7 +38,7 @@ class GnosisRegistrator( } private val otpProcessorContractAddress: String = when (walletManager.wallet.blockchain) { - Blockchain.SaltPay -> "0x3B4397C817A26521Df8bD01a949AFDE2251d91C2" + Blockchain.SaltPay -> "0xc659f4FEd7A84a188F54cBA4A7a49D77c1a20522" else -> throw IllegalArgumentException("GnosisRegistrator supports only the SaltPay blockchain") } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt index cf34a759d4..0f0b207431 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt @@ -11,33 +11,34 @@ import com.tangem.tap.store /** [REDACTED_AUTHOR] */ -object SaltPayExceptionHandler { - fun handle(throwable: Throwable) { - when (throwable) { - is SaltPayActivationError -> { - val dialog = when (throwable) { - is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas - else -> SaltPayDialog.Activation.OnError(throwable) - } - store.dispatchDialogShow(dialog) - } - is TangemSdkError -> { - when (throwable) { - is TangemSdkError.NetworkError -> { - val message = throwable.customMessage - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) +class SaltPayExceptionHandler { + companion object { + fun handle(throwable: Throwable) { + when (throwable) { + is SaltPayActivationError -> { + val dialog = when (throwable) { + is SaltPayActivationError.NoGas -> SaltPayDialog.Activation.NoGas + is SaltPayActivationError.PutVisaCard -> SaltPayDialog.Activation.PutVisaCard + else -> SaltPayDialog.Activation.OnError(throwable) } - else -> { - // do nothing + store.dispatchDialogShow(dialog) + } + is TangemSdkError -> { + when (throwable) { + is TangemSdkError.NetworkError -> { + val message = (throwable).customMessage + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + } + else -> Unit } } - } - is BlockchainSdkError -> { - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(throwable.customMessage)) - } - else -> { - val message = throwable.localizedMessage ?: "SaltPay unknown error" - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + is BlockchainSdkError -> { + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(throwable.customMessage)) + } + else -> { + val message = throwable.localizedMessage ?: "SaltPay unknown error" + store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt index 35a99173c3..1636675ae1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt @@ -11,15 +11,17 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -object InterruptOnboardingDialog { - fun create(context: Context, dialog: OnboardingDialog.InterruptOnboarding): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(context.getString(R.string.onboarding_exit_alert_title)) - setMessage(context.getString(R.string.onboarding_exit_alert_message)) - setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() } - setNegativeButton(R.string.common_cancel) { _, _ -> } - setOnDismissListener { store.dispatchDialogHide() } - setCancelable(false) - }.create() +class InterruptOnboardingDialog { + companion object { + fun create(context: Context, dialog: OnboardingDialog.InterruptOnboarding): Dialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.onboarding_exit_alert_title)) + setMessage(context.getString(R.string.onboarding_exit_alert_message)) + setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() } + setNegativeButton(R.string.common_cancel) { _, _ -> } + setOnDismissListener { store.dispatchDialogHide() } + setCancelable(false) + }.create() + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt new file mode 100644 index 0000000000..ba689a1449 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog + +import android.app.Dialog +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.store +import com.tangem.wallet.R + +/** +[REDACTED_AUTHOR] + */ +class PutVisaCardDialog { + companion object { + fun create(context: Context): Dialog { + return AlertDialog.Builder(context).apply { + setTitle(R.string.saltpay_error_empty_backup_title) + setMessage(R.string.saltpay_error_empty_backup_message) + setPositiveButton(R.string.common_ok) { _, _ -> } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + setCancelable(false) + }.create() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt index b8b512aef1..26228ba27f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt @@ -9,6 +9,7 @@ import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPa sealed class SaltPayDialog : StateDialog { sealed class Activation : SaltPayDialog() { object NoGas : SaltPayDialog() + object PutVisaCard : SaltPayDialog() data class OnError(val error: SaltPayActivationError) : SaltPayDialog() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt index a8e4a95885..592b888850 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt @@ -49,6 +49,8 @@ sealed class SaltPayActivationError( object NoFundsToClaim : SaltPayActivationError(subCode = 15) object ClaimTransactionFailed : SaltPayActivationError(subCode = 16) + object PutVisaCard : SaltPayActivationError(18) + companion object { const val EMPTY_MESSAGE = "" } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt index a97cb49580..e25117369f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt @@ -285,7 +285,7 @@ suspend fun SaltPayActivationManager.update( return Result.Failure(ex) } - // checkGasIfNeeded(this, newStep).successOr { return it } + checkGasIfNeeded(this, newStep).successOr { return it } Timber.d("update: success: %s", newStep) return Result.Success(newStep) 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 80ed02da90..3f0f4f77c4 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 94d681c64e..47a769f3c6 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 @@ -44,7 +44,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, 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 e8c8f62fc4..955d0e73fd 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 c7cffcb201..ab67ba0579 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 @@ -102,6 +103,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() } @@ -144,7 +146,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 7ace673faa..681b095f81 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/Mapper.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt index 78fbe2eae1..34ad6fc335 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/Mapper.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.wallet.redux.middlewares -import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.isZero import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.extensions.toFiatRateString @@ -10,7 +10,6 @@ import com.tangem.tap.common.extensions.toFormattedFiatValue import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel -import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent import com.tangem.tap.features.wallet.redux.ProgressState @@ -24,11 +23,9 @@ import com.tangem.tap.features.wallet.ui.TokenData import com.tangem.tap.store import java.math.BigDecimal -internal fun List.mapToReduxModels( - isMultiWalletAllowed: Boolean, -): List { +internal fun List.mapToReduxModels(): List { return this.map { walletStoreModel -> - walletStoreModel.mapToReduxModel(isMultiWalletAllowed) + walletStoreModel.mapToReduxModel() } } @@ -44,103 +41,151 @@ internal fun TotalFiatBalance.mapToReduxModel(): TotalBalance { ) } -internal fun WalletStoreModel.mapToReduxModel( - isMultiWalletAllowed: Boolean, -): WalletStore { +internal fun WalletStoreModel.mapToReduxModel(): WalletStore { + val appCurrencySymbol = store.state.globalState.appCurrency.symbol return WalletStore( walletManager = walletManager, blockchainNetwork = blockchainNetwork, - walletsData = walletsData.mapToReduxModel(isMultiWalletAllowed, walletRent), + walletsData = walletsData.mapToReduxModels(walletRent, appCurrencySymbol), ) + .updateTokenModels(blockchainWalletData.status.amount) + .setupIfHadCardSingleToken( + blockchain = blockchain, + walletsDataModel = walletsData, + appCurrencySymbol = appCurrencySymbol, + blockchainWalletData = blockchainWalletData.mapToReduxModel( + walletRent = walletRent, + appCurrencySymbol = appCurrencySymbol, + ), + ) } @Suppress("LongMethod", "ComplexMethod") -private fun List.mapToReduxModel( - isMultiWalletAllowed: Boolean, +private fun List.mapToReduxModels( walletRent: WalletStoreModel.WalletRent?, + appCurrencySymbol: String, ): List { return this.map { walletDataModel -> - with(walletDataModel) { - val amount = status.amount - val amountFormatted = amount.toFormattedCurrencyString( - decimals = currency.decimals, - currency = currency.currencySymbol, - ) - val appCurrency = store.state.globalState.appCurrency - val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } - val fiatAmountFormatted = fiatAmount - ?.takeIf { !status.isErrorStatus } - ?.toFormattedFiatValue(appCurrency.symbol) - val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrency.symbol) - val blockchainAmountValue = getBlockchainAmount() - - WalletData( - currency = currency, - walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress -> - WalletAddresses( - selectedAddress = selectedAddress, - list = walletAddresses, - ) - }, - existentialDepositString = existentialDeposit?.toPlainString(), - fiatRate = fiatRate, - fiatRateString = fiatRateFormatted, - pendingTransactions = status.pendingTransactions, - mainButton = WalletMainButton.SendButton( - enabled = !blockchainAmountValue.isZero() && - !status.amount.isZero() && - status.pendingTransactions.isEmpty(), - ), - walletRent = walletRent?.let { - WalletRent( - minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", - rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} " + - currency.blockchain.currency, - ) - }, - currencyData = BalanceWidgetData( - status = when (status) { - is WalletDataModel.Loading -> BalanceStatus.Loading - is WalletDataModel.NoAccount -> BalanceStatus.NoAccount - is WalletDataModel.SameCurrencyTransactionInProgress -> - BalanceStatus.SameCurrencyTransactionInProgress - is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress - is WalletDataModel.Unreachable -> BalanceStatus.Unreachable - is WalletDataModel.MissedDerivation -> BalanceStatus.MissedDerivation - is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline - }, - currency = currency.currencyName, - currencySymbol = currency.currencySymbol, - blockchainAmount = blockchainAmountValue, - amount = amount, - amountFormatted = amountFormatted, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - token = when { - !isMultiWalletAllowed && currency is Currency.Token -> { - TokenData( - amount = amount, - amountFormatted = amountFormatted, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmountFormatted, - tokenSymbol = currency.currencySymbol, - fiatRate = fiatRate, - fiatRateString = fiatRateFormatted, - ) - } - else -> null - }, - amountToCreateAccount = (status as? WalletDataModel.NoAccount) - ?.amountToCreateAccount - ?.toString(), - errorMessage = status.errorMessage, - ), - ) - } + walletDataModel.mapToReduxModel(walletRent, appCurrencySymbol) } } -private fun WalletDataModel.getBlockchainAmount(): BigDecimal { - val walletStore = store.state.walletState.getWalletStore(currency) ?: return BigDecimal.ZERO - return walletStore.walletManager?.wallet?.amounts?.get(AmountType.Coin)?.value ?: BigDecimal.ZERO +private fun WalletDataModel.mapToReduxModel( + walletRent: WalletStoreModel.WalletRent?, + appCurrencySymbol: String, +): WalletData { + val amount = status.amount + val amountFormatted = amount.toFormattedCurrencyString( + decimals = currency.decimals, + currency = currency.currencySymbol, + ) + val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } + val fiatAmountFormatted = fiatAmount + ?.takeIf { !status.isErrorStatus } + ?.toFormattedFiatValue(appCurrencySymbol) + val fiatRateFormatted = fiatRate?.toFiatRateString(appCurrencySymbol) + + return WalletData( + currency = currency, + walletAddresses = walletAddresses.getOrNull(0)?.let { selectedAddress -> + WalletAddresses( + selectedAddress = selectedAddress, + list = walletAddresses, + ) + }, + existentialDepositString = existentialDeposit?.toPlainString(), + fiatRate = fiatRate, + fiatRateString = fiatRateFormatted, + pendingTransactions = status.pendingTransactions, + mainButton = WalletMainButton.SendButton( + enabled = !status.amount.isZero() && status.pendingTransactions.isEmpty(), + ), + walletRent = walletRent?.let { + WalletRent( + minRentValue = "${it.rent.stripZeroPlainString()} ${currency.blockchain.currency}", + rentExemptValue = "${it.exemptionAmount.stripZeroPlainString()} ${currency.blockchain.currency}", + ) + }, + currencyData = BalanceWidgetData( + status = when (status) { + is WalletDataModel.Loading -> BalanceStatus.Loading + is WalletDataModel.NoAccount -> BalanceStatus.NoAccount + is WalletDataModel.SameCurrencyTransactionInProgress -> BalanceStatus.SameCurrencyTransactionInProgress + is WalletDataModel.TransactionInProgress -> BalanceStatus.TransactionInProgress + is WalletDataModel.Unreachable -> BalanceStatus.Unreachable + is WalletDataModel.MissedDerivation -> BalanceStatus.MissedDerivation + is WalletDataModel.VerifiedOnline -> BalanceStatus.VerifiedOnline + }, + currency = currency.currencyName, + currencySymbol = currency.currencySymbol, + blockchainAmount = BigDecimal.ZERO, + amount = amount, + amountFormatted = amountFormatted, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmountFormatted, + token = null, + amountToCreateAccount = (status as? WalletDataModel.NoAccount) + ?.amountToCreateAccount + ?.toString(), + errorMessage = status.errorMessage, + ), + ) +} + +private fun WalletStore.updateTokenModels(blockchainAmount: BigDecimal): WalletStore { + val updatedTokensWalletData = walletsData.filter { it.currency.isToken() }.map { + it.copy( + mainButton = when (it.mainButton) { + is WalletMainButton.SendButton -> { + WalletMainButton.SendButton(it.mainButton.enabled && !blockchainAmount.isZero()) + } + is WalletMainButton.CreateWalletButton -> it.mainButton + }, + currencyData = it.currencyData.copy( + blockchainAmount = blockchainAmount, + ), + ) + } + return updateWallets(updatedTokensWalletData) +} + +private fun WalletStore.setupIfHadCardSingleToken( + blockchain: Blockchain, + walletsDataModel: List, + appCurrencySymbol: String, + blockchainWalletData: WalletData, +): WalletStore { + // Card with single token contains only 2 model - blockchain and token + if (walletsData.size != 2) return this + + val cardSingleTokenWalletData = walletsDataModel.firstOrNull { + it.currency.isToken() && it.currency.blockchain == blockchain && it.isCardSingleToken + } ?: return this + + val blockchainWalletDataWithSingleToken = blockchainWalletData.copy( + currencyData = blockchainWalletData.currencyData.copy( + token = cardSingleTokenWalletData.toTokenData(appCurrencySymbol), + ), + ) + return updateWallets(listOf(blockchainWalletDataWithSingleToken)) +} + +private fun WalletDataModel.toTokenData(appCurrencySymbol: String): TokenData { + val amount = status.amount + val fiatAmount = fiatRate?.let { status.amount.toFiatValue(it) } + + return TokenData( + amount = amount, + amountFormatted = amount.toFormattedCurrencyString( + decimals = currency.decimals, + currency = currency.currencySymbol, + ), + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmount + ?.takeIf { !status.isErrorStatus } + ?.toFormattedFiatValue(appCurrencySymbol), + tokenSymbol = currency.currencySymbol, + fiatRate = fiatRate, + fiatRateString = fiatRate?.toFiatRateString(appCurrencySymbol), + ) } \ No newline at end of file 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 b3957806f4..1f322a6152 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 @@ -8,7 +8,6 @@ import com.tangem.common.extensions.guard 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 @@ -174,12 +173,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)) } } else -> {} @@ -190,8 +192,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 ee4f964106..1afc0422ec 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 @@ -48,21 +48,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 c47055fc46..437c1fd905 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 @@ -109,6 +115,7 @@ class WalletMiddleware { walletState.walletManagers.map { walletManager -> async { globalState.tapWalletManager.loadWalletData(walletManager) } }.awaitAll() + handleBasicAnalyticsEvent() } else { val walletManager = walletState.getWalletManager(action.blockchain) ?: action.walletManager @@ -194,8 +201,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 @@ -249,10 +259,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 -> { @@ -274,6 +286,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)) } } @@ -286,6 +301,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) @@ -303,7 +319,7 @@ class WalletMiddleware { } } - val reduxWalletStores = wallStores.mapToReduxModels(state.isMultiwalletAllowed) + val reduxWalletStores = wallStores.mapToReduxModels() store.dispatchOnMain( WalletAction.WalletStoresChanged.UpdateWalletStores( reduxWalletStores = reduxWalletStores, @@ -361,7 +377,7 @@ class WalletMiddleware { } else -> { Analytics.send(MainScreen.ButtonScanCard()) - store.dispatch(WalletAction.Scan) + store.dispatch(WalletAction.Scan(MainScreen.CardWasScanned())) } } } @@ -481,4 +497,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 d8ffb0df63..c772684c48 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 @@ -38,6 +37,6 @@ sealed interface WalletDialog : StateDialog { } 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 86a92f276a..3553809ffb 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 @@ -187,7 +187,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/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index fdc0f2fd60..6d9c01d27c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -16,6 +16,8 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding +import com.badoo.mvicore.DiffStrategy +import com.badoo.mvicore.ModelWatcher import com.badoo.mvicore.modelWatcher import com.tangem.common.doOnResult import com.tangem.core.analytics.Analytics @@ -39,6 +41,8 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction +import com.tangem.tap.features.wallet.models.WalletWarning +import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.features.wallet.redux.ErrorType import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletAction @@ -68,7 +72,13 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), private val binding: FragmentWalletDetailsBinding by viewBinding(FragmentWalletDetailsBinding::bind) - private val walletDataWatcher = modelWatcher { + private val walletDataWatcher: ModelWatcher = modelWatcher { + val addressCardStrategy: DiffStrategy = { old, new -> + old.currency != new.currency || + old.walletAddresses?.selectedAddress != new.walletAddresses?.selectedAddress || + old.shouldShowMultipleAddress() != new.shouldShowMultipleAddress() + } + WalletData::pendingTransactions { showPendingTransactionsIfPresent(it) } @@ -78,25 +88,35 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), WalletData::currencyData { setupBalanceData(it) } + WalletData::walletAddresses { walletAddresses -> + setupCopyAndShareButtons(walletAddresses?.selectedAddress?.address) + } + WalletData::assembleWarnings { warnings -> + handleWarnings(warnings) + } (WalletData::currencyData or WalletData::currency) { walletData -> setupCurrency(walletData.currencyData, walletData.currency) setupSwipeRefresh(walletData.currencyData, walletData.currency) } + watch({ it }, addressCardStrategy) { walletData -> + setupAddressCard( + shouldShowMultipleAddress = walletData.shouldShowMultipleAddress(), + selectedAddress = walletData.walletAddresses?.selectedAddress, + currency = walletData.currency, + ) + } } - private val walletStateWatcher = modelWatcher { - (WalletState::selectedCurrency or WalletState::selectedWalletData) { state -> - val selectedWalletData = state.selectedWalletData - if (selectedWalletData != null) { - walletDataWatcher.invoke(selectedWalletData) - setupButtons(selectedWalletData, state.isExchangeServiceFeatureOn) - setupAddressCard(selectedWalletData) - handleWarnings(selectedWalletData) + private val walletStateWatcher: ModelWatcher = modelWatcher { + WalletState::selectedWalletData { selectedWallet -> + if (selectedWallet != null) { + walletDataWatcher.invoke(selectedWallet) } } - (WalletState::selectedCurrency or WalletState::isExchangeServiceFeatureOn) { state -> - if (state.selectedWalletData != null) { - setupButtons(state.selectedWalletData!!, state.isExchangeServiceFeatureOn) + (WalletState::selectedWalletData or WalletState::isExchangeServiceFeatureOn) { state -> + val selectedWallet = state.selectedWalletData + if (selectedWallet != null) { + setupButtonsRow(selectedWallet, state.isExchangeServiceFeatureOn) } } (WalletState::state or WalletState::error) { state -> @@ -133,8 +153,6 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun onStop() { super.onStop() store.unsubscribe(this) - walletDataWatcher.clear() - walletStateWatcher.clear() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -148,6 +166,11 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), setupTestActionButton() } + override fun onDestroyView() { + super.onDestroyView() + clearWatchers() + } + private fun setupTransactionsRecyclerView() = with(binding) { pendingTransactionAdapter = PendingTransactionsAdapter() rvPendingTransaction.layoutManager = LinearLayoutManager(requireContext()) @@ -243,23 +266,25 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), currencyData.status == BalanceStatus.Refreshing } - private fun setupButtons(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) = with(binding) { - lWalletDetails.btnCopy.setOnClickListener { - selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString -> - store.dispatch(WalletAction.CopyAddress(addressString, requireContext())) + private fun setupCopyAndShareButtons(walletAddress: String?) { + binding.lWalletDetails.btnCopy.setOnClickListener { + if (walletAddress != null) { + store.dispatch(WalletAction.CopyAddress(walletAddress, requireContext())) } } - lWalletDetails.btnShare.setOnClickListener { - selectedWallet.walletAddresses?.selectedAddress?.address?.let { addressString -> - store.dispatch(WalletAction.ShareAddress(addressString, requireContext())) + binding.lWalletDetails.btnShare.setOnClickListener { + if (walletAddress != null) { + store.dispatch(WalletAction.ShareAddress(walletAddress, requireContext())) } } + } - rowButtons.updateButtonsVisibility( + private fun setupButtonsRow(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) { + binding.rowButtons.updateButtonsVisibility( exchangeServiceFeatureOn = isExchangeServiceFeatureOn, sendAllowed = selectedWallet.mainButton.enabled, ) - rowButtons.onTradeClick = { + binding.rowButtons.onTradeClick = { store.dispatch( WalletAction.DialogAction.ChooseTradeActionDialog( buyAllowed = selectedWallet.isAvailableToBuy, @@ -270,9 +295,9 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } } - private fun handleWarnings(selectedWallet: WalletData) = with(binding) { + private fun handleWarnings(warnings: List) = with(binding) { val converter = WalletWarningConverter(requireContext()) - val warningDetails = selectedWallet.assembleWarnings().map { converter.convert(it) } + val warningDetails = warnings.map { converter.convert(it) } warningMessagesAdapter.submitList(warningDetails) rvWarningMessages.show(warningDetails.isNotEmpty()) @@ -293,51 +318,59 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), binding.rvPendingTransaction.show(pendingTransactions.isNotEmpty()) } - private fun setupAddressCard(state: WalletData) = with(binding.lWalletDetails) { - if (state.walletAddresses != null) { - if (state.shouldShowMultipleAddress() && state.currency is Currency.Blockchain) { - (cardBalance as? ViewGroup)?.beginDelayedTransition() - chipGroupAddressType.show() - chipGroupAddressType.fitChipsByGroupWidth() + private fun setupAddressCard( + shouldShowMultipleAddress: Boolean, + selectedAddress: AddressData?, + currency: Currency, + ) = with(binding.lWalletDetails) { + if (selectedAddress == null) return@with - val checkedId = - MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) - if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) + setupAddressTypeChips(shouldShowMultipleAddress, selectedAddress, currency) - chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> - if (checkedId == -1) return@setOnCheckedChangeListener - val type = - MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) - type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } - } - } else { - chipGroupAddressType.hide() - } - tvAddress.text = state.walletAddresses.selectedAddress.address - tvExplore.setOnClickListener { - store.dispatch( - WalletAction.ExploreAddress( - state.walletAddresses.selectedAddress.exploreUrl, - requireContext(), - ), - ) - } - ivQrCode.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode()) + tvAddress.text = selectedAddress.address + tvExplore.setOnClickListener { + store.dispatch(WalletAction.ExploreAddress(selectedAddress.exploreUrl, requireContext())) + } + ivQrCode.setImageBitmap(selectedAddress.shareUrl.toQrCode()) - tvReceiveMessage.text = when (val currency = state.currency) { - is Currency.Blockchain -> tvReceiveMessage.getString( - id = R.string.address_qr_code_message_format, - currency.blockchain.fullName, - currency.currencySymbol, - currency.blockchain.fullName, - ) - is Currency.Token -> tvReceiveMessage.getString( - id = R.string.address_qr_code_message_format, - currency.token.name, - currency.currencySymbol, - currency.blockchain.fullName, - ) + tvReceiveMessage.text = when (currency) { + is Currency.Blockchain -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.blockchain.fullName, + currency.currencySymbol, + currency.blockchain.fullName, + ) + is Currency.Token -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.token.name, + currency.currencySymbol, + currency.blockchain.fullName, + ) + } + } + + private fun setupAddressTypeChips( + shouldShowMultipleAddress: Boolean, + selectedAddress: AddressData, + currency: Currency, + ) = with(binding.lWalletDetails) { + if (shouldShowMultipleAddress && currency is Currency.Blockchain) { + (cardBalance as? ViewGroup)?.beginDelayedTransition() + chipGroupAddressType.show() + chipGroupAddressType.fitChipsByGroupWidth() + + val checkedId = + MultipleAddressUiHelper.typeToId(selectedAddress.type) + if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) + + chipGroupAddressType.setOnCheckedChangeListener { _, checkedId -> + if (checkedId == -1) return@setOnCheckedChangeListener + val type = + MultipleAddressUiHelper.idToType(checkedId, currency.blockchain) + type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } + } else { + chipGroupAddressType.hide() } } @@ -426,6 +459,11 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), inflater.inflate(R.menu.menu_wallet_details, menu) } + private fun clearWatchers() { + walletDataWatcher.clear() + walletStateWatcher.clear() + } + private fun TextView.setWarningStatus(mainMessage: Int, error: String? = null) { val text = getString(mainMessage).appendIfNotNull(error, "\nError: ") setStatus(text, R.color.warning, R.drawable.ic_warning_small) 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 964cafee2b..df88f6b063 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 @@ -22,8 +22,6 @@ 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.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 @@ -150,7 +148,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { 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 29bef197e1..667d056d40 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 3a66140412..00bd560d80 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/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 6b53c12857..9390d7601a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.extensions.show import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction +import com.tangem.tap.features.wallet.models.PendingTransactionType import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletMainButton @@ -72,8 +73,11 @@ class SingleWalletView : WalletView() { } private fun showPendingTransactionsIfPresent(pendingTransactions: List) { - pendingTransactionAdapter.submitList(pendingTransactions) - binding?.rvPendingTransaction?.show(pendingTransactions.isNotEmpty()) + val knownTransactions = pendingTransactions.filterNot { + it.type == PendingTransactionType.Unknown + } + pendingTransactionAdapter.submitList(knownTransactions) + binding?.rvPendingTransaction?.show(knownTransactions.isNotEmpty()) } private fun setupBalance(state: WalletState, primaryWallet: WalletData) { diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index 17824db23f..4e8b5ec2d6 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -24,8 +24,8 @@ internal sealed interface WalletSelectorAction : Action { val walletsStores: Map>, ) : WalletSelectorAction - data class BalanceLoaded( - val userWalletModel: UserWalletModel, + data class BalancesLoaded( + val userWalletModels: List, ) : WalletSelectorAction object UnlockWithBiometry : WalletSelectorAction { 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 96896eb57a..4debaff531 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 @@ -21,6 +22,7 @@ import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store @@ -35,7 +37,6 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber -import java.math.BigDecimal // Refactoring is coming @Suppress("LargeClass") @@ -83,7 +84,7 @@ internal class WalletSelectorMiddleware { is WalletSelectorAction.SelectedWalletChanged, is WalletSelectorAction.UnlockWithBiometry.Error, is WalletSelectorAction.UnlockWithBiometry.Success, - is WalletSelectorAction.BalanceLoaded, + is WalletSelectorAction.BalancesLoaded, is WalletSelectorAction.IsLockedChanged, is WalletSelectorAction.CloseError, -> Unit @@ -103,28 +104,16 @@ internal class WalletSelectorMiddleware { updatedWalletStores: Map>, state: WalletSelectorState, ) { - if (updatedWalletStores.isNotEmpty()) { - scope.launch(Dispatchers.Default) { - state.wallets - .associateWith { updatedWalletStores[it.id] } - .forEach { (wallet, walletStores) -> - val isWalletTokensEmpty = (wallet.type as? UserWalletModel.Type.MultiCurrency)?.tokensCount == 0 - val updatedWallet = if (walletStores == null && isWalletTokensEmpty) { - wallet.copy(fiatBalance = TotalFiatBalance.Loaded(BigDecimal.ZERO)) - } else { - wallet.updateWalletStoresAndCalculateFiatBalance(walletStores.orEmpty()) - } - - if (wallet != updatedWallet) { - store.dispatchOnMain(WalletSelectorAction.BalanceLoaded(updatedWallet)) - } - } + if (updatedWalletStores.isNotEmpty()) scope.launch(Dispatchers.Default) { + val updatedWallets = state.wallets.updateWalletStoresAndCalculateFiatBalance(updatedWalletStores) + if (updatedWallets != state.wallets) { + store.dispatchOnMain(WalletSelectorAction.BalancesLoaded(updatedWallets)) } } } private fun unlockWalletsWithBiometry() { - Analytics.send(MyWallets.Button.UnlockWithBiometrics) + Analytics.send(MyWallets.Button.UnlockWithBiometrics()) scope.launch { userWalletsListManager.unlockWithBiometry() @@ -139,7 +128,7 @@ internal class WalletSelectorMiddleware { } private fun addWallet() = scope.launch { - Analytics.send(MyWallets.Button.ScanNewCard) + Analytics.send(MyWallets.Button.ScanNewCard()) val prevUseBiometricsForAccessCode = tangemSdkManager.useBiometricsForAccessCode() @@ -149,6 +138,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) @@ -169,8 +159,16 @@ internal class WalletSelectorMiddleware { onFailure = { error -> // Rollback policy if card scanning was failed tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) - Timber.e(error, "Unable to scan card") - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + when { + error is TangemSdkError.ExceptionError && error.cause is SaltPayActivationError -> { + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo()) + } + else -> { + Timber.e(error, "Unable to scan card") + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + } + } }, ) } @@ -181,8 +179,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) @@ -211,6 +207,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) } @@ -219,6 +218,7 @@ internal class WalletSelectorMiddleware { } private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { + Analytics.send(MyWallets.Button.WalletUnlockTapped()) tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) return tangemSdkManager.scanProduct(userTokensRepository) .map { scanResponse -> @@ -258,7 +258,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) { @@ -282,8 +282,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 -> @@ -357,18 +355,28 @@ internal class WalletSelectorMiddleware { } } + private suspend fun List.updateWalletStoresAndCalculateFiatBalance( + walletStores: Map>, + ): List { + return this + .associateWith { walletStores[it.id] } + .map { (wallet, walletStores) -> + wallet.updateWalletStoresAndCalculateFiatBalance(walletStores) + } + } + private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( - walletStores: List, + walletStores: List?, ): UserWalletModel { return this.copy( type = when (type) { is UserWalletModel.Type.MultiCurrency -> type.copy( - tokensCount = walletStores.flatMap { it.walletsData }.size, + tokensCount = walletStores?.flatMap { it.walletsData }?.size ?: 0, ) is UserWalletModel.Type.SingleCurrency -> type }, fiatBalance = totalFiatBalanceCalculator.calculate( - walletStores = walletStores, + walletStores = walletStores.orEmpty(), initial = TotalFiatBalance.Loading, ), ) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index 5c6823a485..e965a29794 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -25,8 +25,8 @@ internal object WalletSelectorReducer { is WalletSelectorAction.IsLockedChanged -> state.copy( isLocked = action.isLocked, ) - is WalletSelectorAction.BalanceLoaded -> state.copy( - wallets = state.wallets.updateWithBalance(action.userWalletModel), + is WalletSelectorAction.BalancesLoaded -> state.copy( + wallets = action.userWalletModels, ) is WalletSelectorAction.CloseError -> state.copy(error = null) is WalletSelectorAction.UnlockWithBiometry -> state.copy( @@ -85,19 +85,6 @@ internal object WalletSelectorReducer { } } - private fun List.updateWithBalance( - userWalletModel: UserWalletModel, - ): List { - return ArrayList(this).apply { - val index = indexOfFirst { it.id == userWalletModel.id } - if (index == -1) { - add(userWalletModel) - } else { - this[index] = userWalletModel - } - } - } - private fun UserWallet.getType(prevType: UserWalletModel.Type? = null): UserWalletModel.Type { return if (isMultiCurrency) { UserWalletModel.Type.MultiCurrency( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt index e5dc088a55..5d18c5baa9 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt @@ -9,6 +9,7 @@ import com.tangem.tap.features.walletSelector.redux.WalletSelectorState import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem +import java.math.BigDecimal internal fun WalletSelectorScreenState.updateWithNewState( newState: WalletSelectorState, @@ -50,10 +51,14 @@ private fun List.toUiModels( ): Sequence { return this.asSequence().map { userWalletModel -> with(userWalletModel) { - val balance = UserWalletItem.Balance( - amount = fiatBalance.amount.toFormattedFiatValue(appCurrency.symbol), - isLoading = fiatBalance is TotalFiatBalance.Loading, - ) + val formatAmount = { amount: BigDecimal -> + amount.toFormattedFiatValue(appCurrency.symbol) + } + val balance = when (fiatBalance) { + is TotalFiatBalance.Error -> UserWalletItem.Balance.Error(formatAmount(fiatBalance.amount)) + is TotalFiatBalance.Loaded -> UserWalletItem.Balance.Loaded(formatAmount(fiatBalance.amount)) + is TotalFiatBalance.Loading -> UserWalletItem.Balance.Loading + } when (type) { is UserWalletModel.Type.MultiCurrency -> MultiCurrencyUserWalletItem( id = id, 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 9ccbecd997..4e0edeb703 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 @@ -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/walletSelector/ui/components/MockData.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt index 65349386dc..db09868d03 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt @@ -9,9 +9,8 @@ import com.tangem.tap.features.walletSelector.ui.model.UserWalletItem internal object MockData { private val multiCurrencyUserWallet = MultiCurrencyUserWalletItem( id = UserWalletId("wallet_1"), - balance = UserWalletItem.Balance( + balance = UserWalletItem.Balance.Loaded( amount = "6781.05 $", - isLoading = false, ), name = "Wallet", imageUrl = "https://app.tangem.com/cards/card_default.png", @@ -22,9 +21,8 @@ internal object MockData { private val singleCurrencyUserWallet = SingleCurrencyUserWalletItem( id = UserWalletId("wallet_4"), - balance = UserWalletItem.Balance( + balance = UserWalletItem.Balance.Loaded( amount = "6781.05 $", - isLoading = false, ), name = "Wallet", imageUrl = "https://app.tangem.com/cards/card_default.png", diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index 9bc603525d..b67acbf338 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -171,13 +171,24 @@ private fun RowScope.TokensInfo( if (isLocked) { LockedPlaceholder() } else { - if (balance.isLoading) { - LoadingTokensInfo(isMultiCurrencyWallet = tokensCount != null) - } else { - LoadedTokensInfo( - balanceAmount = balance.amount, - tokensCount = tokensCount, - ) + when (balance) { + is UserWalletItem.Balance.Error -> { + LoadedTokensInfo( + balanceAmount = balance.amount, + tokensCount = tokensCount, + showWarning = true, + ) + } + is UserWalletItem.Balance.Loaded -> { + LoadedTokensInfo( + balanceAmount = balance.amount, + tokensCount = tokensCount, + showWarning = false, + ) + } + is UserWalletItem.Balance.Loading -> { + LoadingTokensInfo(isMultiCurrencyWallet = tokensCount != null) + } } } } @@ -216,14 +227,35 @@ private fun LoadingTokensInfo(isMultiCurrencyWallet: Boolean) { @OptIn(ExperimentalComposeUiApi::class) @Composable -private fun LoadedTokensInfo(balanceAmount: String, tokensCount: Int?) { - Column(horizontalAlignment = Alignment.End) { - Text( - text = balanceAmount, - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.End, - ) +private fun LoadedTokensInfo( + modifier: Modifier = Modifier, + balanceAmount: String, + tokensCount: Int?, + showWarning: Boolean, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.End, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), + ) { + Text( + text = balanceAmount, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.End, + ) + if (showWarning) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size16), + painter = painterResource(id = R.drawable.ic_alert_24), + tint = TangemTheme.colors.icon.attention, + contentDescription = null, + ) + } + } if (tokensCount != null) { SpacerH2() Text( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt index ddd7518ca6..eeca9d7611 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt @@ -9,10 +9,13 @@ internal sealed interface UserWalletItem { val balance: Balance val isLocked: Boolean - data class Balance( - val amount: String, - val isLoading: Boolean, - ) + sealed interface Balance { + object Loading : Balance + + data class Error(val amount: String) : Balance + + data class Loaded(val amount: String) : Balance + } } internal data class MultiCurrencyUserWalletItem( 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 726b57a823..b9e142d8ba 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 @@ -1,9 +1,11 @@ package com.tangem.tap.features.welcome.redux import android.content.Intent +import com.tangem.common.core.TangemSdkError 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 @@ -11,6 +13,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.intentHandler import com.tangem.tap.preferencesStorage import com.tangem.tap.scope @@ -98,11 +101,19 @@ internal class WelcomeMiddleware { useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) ScanCardProcessor.scan( + analyticsEvent = SignIn.CardWasScanned(), onSuccess = { scanResponse -> scope.launch { onCardScanned(scanResponse) } }, onFailure = { - store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(it)) + when { + it is TangemSdkError.ExceptionError && it.cause is SaltPayActivationError -> { + store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) + } + else -> { + store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(it)) + } + } }, onWalletNotCreated = { store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) 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 0afd4fe8f6..7cebf15891 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/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index 9e8f55e880..6d8b8d8b5a 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -151,13 +151,12 @@ android:id="@+id/rv_pending_transaction" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="12dp" android:nestedScrollingEnabled="false" android:overScrollMode="never" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/l_card_total_balance" - app:layout_goneMarginTop="12dp" /> + app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" /> @@ -194,7 +194,7 @@ android:nestedScrollingEnabled="false" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" /> + app:layout_constraintTop_toBottomOf="@id/l_card_total_balance" /> + app:layout_constraintTop_toBottomOf="@id/rv_warning_messages" /> - Tangem + Tangem \ No newline at end of file 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