Updated on 2026-08-14
This commit is contained in:
commit
ae43a82d23
92 changed files with 964 additions and 564 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">DebugTangem</string>
|
||||
<string name="tangem_app_name">DebugTangem</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name" translatable="false">DebugTangem</string>
|
||||
<string name="tangem_app_name" translatable="false">DebugTangem</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">BetaTangem</string>
|
||||
<string name="tangem_app_name">BetaTangem</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">BetaTangem</string>
|
||||
<string name="tangem_app_name">BetaTangem</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<application
|
||||
android:theme="@style/AppTheme"
|
||||
android:label="@string/app_name"
|
||||
android:label="@string/tangem_app_name"
|
||||
android:name="com.tangem.tap.TapApplication"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.tap.common.analytics.AnalyticsFactory
|
|||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.filters.BasicSignInFilter
|
||||
import com.tangem.tap.common.analytics.filters.BasicTopUpFilter
|
||||
import com.tangem.tap.common.analytics.filters.ShopPurchasedEventFilter
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
|
|
@ -182,7 +181,6 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder())
|
||||
factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder())
|
||||
|
||||
factory.addFilter(ShopPurchasedEventFilter())
|
||||
factory.addFilter(BasicSignInFilter())
|
||||
factory.addFilter(BasicTopUpFilter(preferencesStorage.toppedUpWalletStorage))
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
|||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.InterruptOnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.NoFundsForActivationDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.PutVisaCardDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.RegistrationErrorDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AddMoreBackupCardsDialog
|
||||
|
|
@ -132,6 +133,7 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WalletState, Basic.SignedIn?> {
|
||||
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<WalletDataModel>?,
|
||||
) {
|
||||
val batchId: String by lazy { scanResponse.card.batchId }
|
||||
|
||||
val userWalletIdStringValue: String? by lazy {
|
||||
UserWalletIdBuilder.scanResponse(scanResponse)
|
||||
.build()
|
||||
?.stringValue
|
||||
}
|
||||
|
||||
val paramCardCurrency: AnalyticsParam.CardCurrency? by lazy {
|
||||
ParamCardCurrencyConverter().convert(scanResponse.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<WalletDataModel>.calculateTotalCryptoAmount(): BigDecimal {
|
||||
return this
|
||||
.map { it.status.amount }
|
||||
.reduce(BigDecimal::plus)
|
||||
}
|
||||
}
|
||||
|
||||
class BasicSignInEventConverter : Converter<BasicEventsSourceData, Basic.SignedIn?> {
|
||||
|
||||
override fun convert(value: BasicEventsSourceData): Basic.SignedIn? {
|
||||
if (value.paramCardCurrency == null || value.userWalletIdStringValue == null) return null
|
||||
|
||||
return Basic.SignedIn(
|
||||
state = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores),
|
||||
currency = cardCurrency,
|
||||
batch = scanResponse.card.batchId,
|
||||
state = value.paramCardBalanceState,
|
||||
currency = value.paramCardCurrency!!,
|
||||
batch = value.batchId,
|
||||
).apply {
|
||||
filterData = UserWalletIdBuilder.scanResponse(scanResponse)
|
||||
.build()
|
||||
?.stringValue
|
||||
filterData = value.userWalletIdStringValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BasicTopUpEventConverter(
|
||||
private val scanResponse: ScanResponse,
|
||||
) : Converter<WalletState, Basic.ToppedUp?> {
|
||||
class BasicTopUpEventConverter : Converter<BasicEventsSourceData, Basic.ToppedUp?> {
|
||||
|
||||
override fun convert(value: WalletState): Basic.ToppedUp? {
|
||||
if (!statesIsReadyToCreateEvent(scanResponse.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<WalletData>,
|
||||
): 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
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ class ParamCardCurrencyConverter : Converter<CardTypesResolver, AnalyticsParam.C
|
|||
value.isTangemNote() -> 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
|
||||
|
|
|
|||
|
|
@ -8,19 +8,19 @@ import com.tangem.tap.common.shop.data.ProductType
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ShopOrderToEventConverter : Converter<Pair<Storefront.Order, ProductType>, Shop.Purchased> {
|
||||
class ShopOrderToEventConverter : Converter<Pair<Storefront.Checkout, ProductType>, Shop.Purchased> {
|
||||
|
||||
override fun convert(value: Pair<Storefront.Order, ProductType>): Shop.Purchased {
|
||||
val order = value.first
|
||||
override fun convert(value: Pair<Storefront.Checkout, ProductType>): Shop.Purchased {
|
||||
val checkout = value.first
|
||||
val productType = value.second
|
||||
|
||||
val sku = order.lineItems.edges.firstOrNull()?.node?.variant?.sku ?: productType.sku
|
||||
val sku = checkout.lineItems?.edges?.firstOrNull()?.node?.variant?.sku ?: productType.sku
|
||||
val count = when (productType) {
|
||||
ProductType.WALLET_2_CARDS -> "2"
|
||||
ProductType.WALLET_3_CARDS -> "3"
|
||||
}
|
||||
val amount = "${order.totalPriceV2.amount} ${order.totalPriceV2.currencyCode.name}"
|
||||
val code = (order.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
|
||||
val amount = "${checkout.totalPriceV2.amount} ${checkout.totalPriceV2.currencyCode.name}"
|
||||
val code = (checkout.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication)?.code
|
||||
|
||||
return Shop.Purchased(sku, count, amount, code)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ sealed class MyWallets(
|
|||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("My Wallets", event, params) {
|
||||
|
||||
object MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened")
|
||||
object CardWasScanned : MyWallets(event = "Card Was Scanned")
|
||||
object WalletUnlockTapped : MyWallets(event = "Wallet Unlock Tapped")
|
||||
class MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened")
|
||||
class CardWasScanned : MyWallets(event = "Card Was Scanned")
|
||||
|
||||
object Button {
|
||||
object ScanNewCard : MyWallets(event = "Button - Scan New Card")
|
||||
object UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID")
|
||||
object EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped")
|
||||
object DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped")
|
||||
sealed class Button {
|
||||
class ScanNewCard : MyWallets(event = "Button - Scan New Card")
|
||||
class UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID")
|
||||
class EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped")
|
||||
class DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped")
|
||||
class WalletUnlockTapped : MyWallets(event = "Button - Wallet Unlock Tapped")
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ sealed class Shop(
|
|||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Shop", event, params) {
|
||||
|
||||
class ScreenOpened : IntroductionProcess("Shop Screen Opened")
|
||||
class ScreenOpened : Shop("Shop Screen Opened")
|
||||
|
||||
class Purchased(sku: String, count: String, amount: String, couponCode: String?) : Shop(
|
||||
event = "Purchased",
|
||||
|
|
@ -23,8 +23,8 @@ sealed class Shop(
|
|||
).filterNotNull(),
|
||||
)
|
||||
|
||||
class Redirected(partnerName: String) : Shop(
|
||||
class Redirected(partnerName: String?) : Shop(
|
||||
event = "Redirected",
|
||||
params = mapOf("Partner" to partnerName),
|
||||
params = partnerName?.let { mapOf("Partner" to partnerName) } ?: mapOf(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class SignIn(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
error: Throwable? = null,
|
||||
) : AnalyticsEvent("Sign In", event, params, error) {
|
||||
|
||||
class ScreenOpened : SignIn(event = "Sing In Screen Opened")
|
||||
class CardWasScanned : SignIn(event = "Card Was Scanned")
|
||||
|
||||
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
|
||||
class ButtonCardSignIn : SignIn(event = "Button - Card Sign In")
|
||||
}
|
||||
|
|
@ -46,14 +46,14 @@ sealed class Token(
|
|||
params = mapOf("Token" to type.value),
|
||||
)
|
||||
|
||||
sealed class Recieve(
|
||||
sealed class Receive(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : Token("Token / Recieve", event, params) {
|
||||
) : Token("Token / Receive", event, params) {
|
||||
|
||||
class ScreenOpened : Recieve("Recieve Screen Opened")
|
||||
class ButtonCopyAddress : Recieve("Button - Copy Address")
|
||||
class ButtonShareAddress : Recieve("Button - Share Address")
|
||||
class ScreenOpened : Receive("Receive Screen Opened")
|
||||
class ButtonCopyAddress : Receive("Button - Copy Address")
|
||||
class ButtonShareAddress : Receive("Button - Share Address")
|
||||
}
|
||||
|
||||
sealed class Send(
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<AddressData>,
|
||||
val existentialDeposit: BigDecimal?,
|
||||
val fiatRate: BigDecimal?,
|
||||
val isCardSingleToken: Boolean,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Blockchain>? = 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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ interface WalletStoresManager {
|
|||
* @return [Flow] with [WalletStoreModel] list
|
||||
* */
|
||||
fun get(userWalletId: UserWalletId): Flow<List<WalletStoreModel>>
|
||||
suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel>
|
||||
|
||||
/**
|
||||
* Delete [WalletStoreModel]s associated with provided [UserWalletId]s
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ internal class DefaultWalletStoresManager(
|
|||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
|
||||
return walletStoresRepository.getSync(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
return walletStoresRepository.delete(userWalletsIds)
|
||||
.flatMap { walletManagersRepository.delete(userWalletsIds) }
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ internal class DummyWalletStoresManager : WalletStoresManager {
|
|||
return emptyFlow()
|
||||
}
|
||||
|
||||
override suspend fun getSync(userWalletId: UserWalletId): List<WalletStoreModel> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WalletDataModel>.updateWithAmounts(wallet: Wallet): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
walletData.updateWithAmount(wallet)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithDemoAmounts(wallet: Wallet): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
walletData.updateWithDemoAmount(wallet)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.updateWithError(
|
||||
wallet: Wallet,
|
||||
error: TangemError,
|
||||
|
|
@ -164,7 +178,7 @@ internal fun List<WalletDataModel>.updateWithSelf(
|
|||
val updatedWalletsData = arrayListOf<WalletDataModel>()
|
||||
|
||||
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<WalletDataModel>.updateWithSelf(
|
|||
}
|
||||
|
||||
internal fun WalletDataModel.isSameWalletData(other: WalletDataModel): Boolean {
|
||||
return currency == other.currency
|
||||
return this.currency == other.currency
|
||||
}
|
||||
|
|
@ -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<String, Double>,
|
||||
): WalletStoreModel {
|
||||
|
|
@ -96,26 +104,30 @@ private inline fun List<WalletStoreModel>.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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<SocialNetworkLink> = listOf(
|
||||
|
|
@ -64,7 +64,7 @@ object TangemSocialAccounts {
|
|||
SocialNetworkLink(SocialNetwork.Facebook, "https://m.facebook.com/TangemCards/"),
|
||||
SocialNetworkLink(SocialNetwork.Instagram, "https://instagram.com/tangemcards"),
|
||||
SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"),
|
||||
SocialNetworkLink(SocialNetwork.Youtube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
|
||||
SocialNetworkLink(SocialNetwork.YouTube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"),
|
||||
SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"),
|
||||
)
|
||||
}
|
||||
|
|
@ -62,6 +62,7 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
|||
private fun handleClickingSettingsItem(item: SettingsElement) {
|
||||
when (item) {
|
||||
SettingsElement.WalletConnect -> {
|
||||
Analytics.send(Settings.ButtonWalletConnect())
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
|
||||
}
|
||||
SettingsElement.Chat -> {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
|
|
@ -23,6 +25,7 @@ class WalletConnectFragment : Fragment(), StoreSubscriber<WalletConnectState> {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Analytics.send(WalletConnect.ScreenOpened())
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(android.R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(android.R.transition.fade)
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
|
|||
homeState,
|
||||
onScanButtonClick = {
|
||||
Analytics.send(IntroductionProcess.ButtonScanCard())
|
||||
store.dispatch(HomeAction.ReadCard)
|
||||
store.dispatch(HomeAction.ReadCard())
|
||||
},
|
||||
onShopButtonClick = {
|
||||
Analytics.send(IntroductionProcess.ButtonBuyCards())
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ fun RowScope.ProgressButton(
|
|||
.weight(1f)
|
||||
.height(48.dp),
|
||||
onClick = onClick,
|
||||
enabled = !inProgress,
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
backgroundColor = backgroundColor,
|
||||
contentColor = contentColor,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWa
|
|||
|
||||
private val viewModel by viewModels<SaveWalletViewModel>()
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
viewModel.dismiss()
|
||||
super.onDismiss(dialog)
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
viewModel.cancelOrClose()
|
||||
super.onCancel(dialog)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -51,7 +51,10 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWa
|
|||
SaveWalletScreenContent(
|
||||
showProgress = state.showProgress,
|
||||
onSaveWalletClick = viewModel::saveWallet,
|
||||
onCloseClick = this@SaveWalletBottomSheetFragment::dismissAllowingStateLoss,
|
||||
onCloseClick = {
|
||||
viewModel.cancelOrClose()
|
||||
dismissAllowingStateLoss()
|
||||
},
|
||||
)
|
||||
|
||||
SnackbarHost(
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ internal class SaveWalletViewModel : ViewModel(), StoreSubscriber<SaveWalletStat
|
|||
store.dispatch(SaveWalletAction.Save)
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
fun cancelOrClose(){
|
||||
store.dispatch(SaveWalletAction.Dismiss)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.features.shop.redux
|
||||
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Shop
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchOpenUrl
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -111,6 +113,7 @@ private fun handle(action: Action) {
|
|||
}
|
||||
}
|
||||
ShopAction.StartWebCheckout -> {
|
||||
Analytics.send(Shop.Redirected(null))
|
||||
store.dispatchOpenUrl(shopService.getCheckoutUrl(shopState.selectedProduct))
|
||||
store.dispatch(ShopAction.FinishSuccessfulOrder)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<BlockchainNetwork>) : 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()
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ import org.rekotlin.StateType
|
|||
import java.math.BigDecimal
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
/**
|
||||
* @property derivationsCheckIsScheduled - used only for analytics
|
||||
*/
|
||||
data class WalletState(
|
||||
val cardId: String = "",
|
||||
val state: ProgressState = ProgressState.Done,
|
||||
|
|
@ -46,6 +49,7 @@ data class WalletState(
|
|||
val totalBalance: TotalBalance? = null,
|
||||
val showBackupWarning: Boolean = false,
|
||||
val missingDerivations: List<BlockchainNetwork> = emptyList(),
|
||||
val derivationsCheckIsScheduled: Boolean = false,
|
||||
val loadingUserTokens: Boolean = false,
|
||||
val walletCardsCount: Int? = null,
|
||||
) : StateType {
|
||||
|
|
|
|||
|
|
@ -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<WalletStoreModel>.mapToReduxModels(
|
||||
isMultiWalletAllowed: Boolean,
|
||||
): List<WalletStore> {
|
||||
internal fun List<WalletStoreModel>.mapToReduxModels(): List<WalletStore> {
|
||||
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<WalletDataModel>.mapToReduxModel(
|
||||
isMultiWalletAllowed: Boolean,
|
||||
private fun List<WalletDataModel>.mapToReduxModels(
|
||||
walletRent: WalletStoreModel.WalletRent?,
|
||||
appCurrencySymbol: String,
|
||||
): List<WalletData> {
|
||||
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<WalletDataModel>,
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WalletData> {
|
||||
private val walletDataWatcher: ModelWatcher<WalletData> = modelWatcher {
|
||||
val addressCardStrategy: DiffStrategy<WalletData> = { 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> {
|
||||
(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<WalletState> = 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<WalletWarning>) = 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)
|
||||
|
|
|
|||
|
|
@ -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<Walle
|
|||
override fun newState(state: WalletState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
handleBasicAnalyticsEvent(state)
|
||||
val isSaltPay = store.state.globalState.scanResponse?.card?.isSaltPay == true
|
||||
|
||||
when {
|
||||
|
|
@ -260,11 +257,4 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
|||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
if (store.state.walletState.shouldShowDetails) inflater.inflate(R.menu.menu_wallet, menu)
|
||||
}
|
||||
|
||||
private fun handleBasicAnalyticsEvent(state: WalletState) {
|
||||
val scanResponse = store.state.globalState.scanResponse ?: return
|
||||
|
||||
BasicSignInEventConverter(scanResponse).convert(state)?.let { Analytics.send(it) }
|
||||
BasicTopUpEventConverter(scanResponse).convert(state)?.let { Analytics.send(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ class WalletWarningConverter(
|
|||
is WalletWarning.TransactionInProgress -> {
|
||||
context.getString(
|
||||
R.string.token_details_send_blocked_tx_format,
|
||||
message.currencyName
|
||||
message.currencyName,
|
||||
)
|
||||
}
|
||||
is WalletWarning.Rent -> {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<PendingTransaction>) {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ internal sealed interface WalletSelectorAction : Action {
|
|||
val walletsStores: Map<UserWalletId, List<WalletStoreModel>>,
|
||||
) : WalletSelectorAction
|
||||
|
||||
data class BalanceLoaded(
|
||||
val userWalletModel: UserWalletModel,
|
||||
data class BalancesLoaded(
|
||||
val userWalletModels: List<UserWalletModel>,
|
||||
) : WalletSelectorAction
|
||||
|
||||
object UnlockWithBiometry : WalletSelectorAction {
|
||||
|
|
|
|||
|
|
@ -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<UserWalletId, List<WalletStoreModel>>,
|
||||
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<Unit> {
|
||||
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<UserWalletId>, 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<UserWalletModel>.updateWalletStoresAndCalculateFiatBalance(
|
||||
walletStores: Map<UserWalletId, List<WalletStoreModel>>,
|
||||
): List<UserWalletModel> {
|
||||
return this
|
||||
.associateWith { walletStores[it.id] }
|
||||
.map { (wallet, walletStores) ->
|
||||
wallet.updateWalletStoresAndCalculateFiatBalance(walletStores)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance(
|
||||
walletStores: List<WalletStoreModel>,
|
||||
walletStores: List<WalletStoreModel>?,
|
||||
): 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,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<UserWalletModel>.updateWithBalance(
|
||||
userWalletModel: UserWalletModel,
|
||||
): List<UserWalletModel> {
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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<UserWalletModel>.toUiModels(
|
|||
): Sequence<UserWalletItem> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
|
|||
private val viewModel by viewModels<WalletSelectorViewModel>()
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
Analytics.send(MyWallets.MyWalletsScreenOpened)
|
||||
Analytics.send(MyWallets.MyWalletsScreenOpened())
|
||||
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package com.tangem.tap.features.walletSelector.ui
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.common.analytics.events.MyWallets
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
|
|
@ -71,6 +73,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
.find { it.id == editedUserWalletId }
|
||||
|
||||
if (editedUserWallet != null) {
|
||||
Analytics.send(MyWallets.Button.EditWalletTapped())
|
||||
val dialog = RenameWalletDialog(
|
||||
currentName = editedUserWallet.name,
|
||||
onApply = { newName ->
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.welcome.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -18,9 +19,11 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.fragments.ComposeFragment
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.analytics.events.SignIn
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -28,6 +31,11 @@ import com.tangem.wallet.R
|
|||
internal class WelcomeFragment : ComposeFragment<WelcomeScreenState>() {
|
||||
private val viewModel by viewModels<WelcomeViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Analytics.send(SignIn.ScreenOpened())
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun provideState(): State<WelcomeScreenState> {
|
||||
return viewModel.state.collectAsState()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.features.welcome.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.SignIn
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
|
|
@ -21,10 +23,12 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
|
|||
}
|
||||
|
||||
fun unlockWallets() {
|
||||
Analytics.send(SignIn.ButtonBiometricSignIn())
|
||||
store.dispatch(WelcomeAction.ProceedWithBiometrics)
|
||||
}
|
||||
|
||||
fun scanCard() {
|
||||
Analytics.send(SignIn.ButtonCardSignIn())
|
||||
store.dispatch(WelcomeAction.ProceedWithCard)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_card_balance"
|
||||
|
|
@ -173,6 +172,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
|
||||
|
|
@ -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" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pb_loading_user_tokens"
|
||||
|
|
@ -206,7 +206,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/rv_warning_messages" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier_wallets"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name" translatable="false">Tangem</string>
|
||||
<string name="tangem_app_name" translatable="false">Tangem</string>
|
||||
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue