Updated on 2026-08-14

This commit is contained in:
Tangem 2023-02-02 13:25:32 +04:00
parent 702041224f
commit 2a2ea1667a
18 changed files with 261 additions and 138 deletions

View file

@ -70,12 +70,12 @@ class BasicEventsPreChecker {
}
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
}) {
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
}

View file

@ -75,7 +75,7 @@ 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 ButtonChat : Onboarding("Onboarding", "Button - Chat")
class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding(
category = "Onboarding / Biometric",

View file

@ -110,18 +110,18 @@ private inline fun List<WalletStoreModel>.replaceWalletStores(
val currentWalletStore = mutableStores[index]
val updatedWalletStore = update(currentWalletStore)
if (currentWalletStore != updatedWalletStore) {
Timber.d(
"""
if (currentWalletStore != updatedWalletStore) {
Timber.d(
"""
Update wallet store in storage
|- User wallet ID: ${updatedWalletStore.userWalletId}
|- Blockchain: ${updatedWalletStore.blockchain}
""".trimIndent(),
)
""".trimIndent(),
)
mutableStores[index] = updatedWalletStore
}
mutableStores[index] = updatedWalletStore
}
}
return mutableStores
}

View file

@ -28,7 +28,6 @@ import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
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

View file

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

View file

@ -10,10 +10,8 @@ import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.activity.OnBackPressedCallback
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.GlobalLayoutStateHandler
import com.tangem.tap.common.KeyboardObserver
import com.tangem.tap.common.analytics.events.Shop
import com.tangem.tap.common.extensions.getQuantityString
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction

View file

@ -0,0 +1,116 @@
package com.tangem.tap.features.wallet.redux
import com.tangem.common.extensions.isZero
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.PendingTransaction
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.models.WalletWarning
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import java.math.BigDecimal
data class WalletData(
val pendingTransactions: List<PendingTransaction> = emptyList(),
val hashesCountVerified: Boolean? = null,
val walletAddresses: WalletAddresses? = null,
val currencyData: BalanceWidgetData = BalanceWidgetData(),
val updatingWallet: Boolean = false,
val fiatRateString: String? = null,
val fiatRate: BigDecimal? = null,
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
val currency: Currency,
val walletRent: WalletRent? = null,
val existentialDepositString: String? = null,
) {
fun isAvailableToBuy(exchangeManager: CurrencyExchangeManager): Boolean {
return exchangeManager.availableForBuy(currency)
}
fun isAvailableToSell(exchangeManager: CurrencyExchangeManager): Boolean {
return exchangeManager.availableForSell(currency)
}
fun isAvailableToSwap(swapInteractor: SwapInteractor, isExchangeFeatureOn: Boolean): Boolean {
return swapInteractor.isAvailableToSwap(currency.blockchain.toNetworkId()) && isExchangeFeatureOn
}
fun getAvailableActions(
swapInteractor: SwapInteractor,
exchangeManager: CurrencyExchangeManager,
isExchangeFeatureOn: Boolean,
): Set<CurrencyAction> {
return setOfNotNull(
if (isAvailableToBuy(exchangeManager)) CurrencyAction.Buy else null,
if (isAvailableToSell(exchangeManager)) CurrencyAction.Sell else null,
if (isAvailableToSwap(swapInteractor, isExchangeFeatureOn)) CurrencyAction.Swap else null,
)
}
fun shouldShowMultipleAddress(): Boolean {
val listOfAddresses = walletAddresses?.list ?: return false
return listOfAddresses.size > 1
}
fun shouldEnableTokenSendButton(): Boolean = if (blockchainAmountIsEmpty()) {
false
} else {
!tokenAmountIsEmpty()
}
fun assembleWarnings(): List<WalletWarning> {
val walletWarnings = mutableListOf<WalletWarning>()
assembleNonTypedWarnings(walletWarnings)
assembleBlockchainWarnings(walletWarnings)
assembleTokenWarnings(walletWarnings)
return walletWarnings.sortedBy { it.showingPosition }
}
private fun assembleNonTypedWarnings(walletWarnings: MutableList<WalletWarning>) {
if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) {
walletWarnings.add(WalletWarning.TransactionInProgress(currency.currencyName))
}
}
private fun assembleBlockchainWarnings(walletWarnings: MutableList<WalletWarning>) {
if (!currency.isBlockchain()) return
if (existentialDepositString != null) {
val warning = WalletWarning.ExistentialDeposit(
currencyName = currency.currencyName,
edStringValueWithSymbol = "$existentialDepositString ${currency.currencySymbol}",
)
walletWarnings.add(warning)
}
if (walletRent != null) {
walletWarnings.add(WalletWarning.Rent(walletRent))
}
}
private fun assembleTokenWarnings(walletWarnings: MutableList<WalletWarning>) {
with(currency) {
if (!isToken()) return
if (blockchainAmountIsEmpty() && !tokenAmountIsEmpty()) {
walletWarnings.add(
WalletWarning.BalanceNotEnoughForFee(
currencyName = currencyName,
blockchainFullName = blockchain.fullName,
blockchainSymbol = blockchain.currency,
),
)
}
}
}
private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() == true
private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true
}
enum class CurrencyAction {
Buy, Sell, Swap
}

View file

@ -6,7 +6,6 @@ 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.common.extensions.isZero
import com.tangem.tap.common.entities.Button
import com.tangem.tap.common.extensions.toQrCode
import com.tangem.tap.common.redux.global.CryptoCurrencyName
@ -15,18 +14,13 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
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.TotalBalance
import com.tangem.tap.features.wallet.models.WalletRent
import com.tangem.tap.features.wallet.models.WalletWarning
import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount
import com.tangem.tap.features.wallet.redux.reducers.findProgressState
import com.tangem.tap.features.wallet.ui.BalanceStatus
import com.tangem.tap.features.wallet.ui.BalanceWidgetData
import com.tangem.tap.store
import com.tangem.tap.userWalletsListManager
import org.rekotlin.StateType
import java.math.BigDecimal
import kotlin.properties.ReadOnlyProperty
/**
@ -307,90 +301,6 @@ data class Artwork(
}
}
data class WalletData(
val pendingTransactions: List<PendingTransaction> = emptyList(),
val hashesCountVerified: Boolean? = null,
val walletAddresses: WalletAddresses? = null,
val currencyData: BalanceWidgetData = BalanceWidgetData(),
val updatingWallet: Boolean = false,
val fiatRateString: String? = null,
val fiatRate: BigDecimal? = null,
val mainButton: WalletMainButton = WalletMainButton.SendButton(false),
val currency: Currency,
val walletRent: WalletRent? = null,
val existentialDepositString: String? = null,
) {
val isAvailableToBuy: Boolean
get() = store.state.globalState.exchangeManager.availableForBuy(currency)
val isAvailableToSell: Boolean
get() = store.state.globalState.exchangeManager.availableForSell(currency)
val isAvailableToSwap: Boolean
get() = currency.blockchain.isEvm() && currency.coinId != null
fun shouldShowMultipleAddress(): Boolean {
val listOfAddresses = walletAddresses?.list ?: return false
return listOfAddresses.size > 1
}
fun shouldEnableTokenSendButton(): Boolean = if (blockchainAmountIsEmpty()) {
false
} else {
!tokenAmountIsEmpty()
}
fun assembleWarnings(): List<WalletWarning> {
val walletWarnings = mutableListOf<WalletWarning>()
assembleNonTypedWarnings(walletWarnings)
assembleBlockchainWarnings(walletWarnings)
assembleTokenWarnings(walletWarnings)
return walletWarnings.sortedBy { it.showingPosition }
}
private fun assembleNonTypedWarnings(walletWarnings: MutableList<WalletWarning>) {
if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) {
walletWarnings.add(WalletWarning.TransactionInProgress(currency.currencyName))
}
}
private fun assembleBlockchainWarnings(walletWarnings: MutableList<WalletWarning>) {
if (!currency.isBlockchain()) return
if (existentialDepositString != null) {
val warning = WalletWarning.ExistentialDeposit(
currencyName = currency.currencyName,
edStringValueWithSymbol = "$existentialDepositString ${currency.currencySymbol}",
)
walletWarnings.add(warning)
}
if (walletRent != null) {
walletWarnings.add(WalletWarning.Rent(walletRent))
}
}
private fun assembleTokenWarnings(walletWarnings: MutableList<WalletWarning>) {
with(currency) {
if (!isToken()) return
if (blockchainAmountIsEmpty() && !tokenAmountIsEmpty()) {
walletWarnings.add(
WalletWarning.BalanceNotEnoughForFee(
currencyName = currencyName,
blockchainFullName = blockchain.fullName,
blockchainSymbol = blockchain.currency
)
)
}
}
}
private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() == true
private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true
}
data class WalletStore(
val walletManager: WalletManager?,
val blockchainNetwork: BlockchainNetwork,

View file

@ -22,6 +22,7 @@ import com.badoo.mvicore.modelWatcher
import com.tangem.common.doOnResult
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.tangem_sdk_new.extensions.dpToPx
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.TestActions
@ -58,15 +59,21 @@ import com.tangem.tap.userWalletsListManagerSafe
import com.tangem.tap.walletCurrenciesManager
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentWalletDetailsBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.StoreSubscriber
import javax.inject.Inject
@Suppress("LargeClass", "MagicNumber")
@AndroidEntryPoint
class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
StoreSubscriber<WalletState> {
@Inject
lateinit var swapInteractor: SwapInteractor
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
private lateinit var warningMessagesAdapter: WalletDetailWarningMessagesAdapter
@ -280,19 +287,31 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
}
private fun setupButtonsRow(selectedWallet: WalletData, isExchangeServiceFeatureOn: Boolean) {
val exchangeManager = store.state.globalState.exchangeManager
binding.rowButtons.apply {
onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) }
onSellClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
onSwapClick = { store.dispatch(WalletAction.TradeCryptoAction.Swap) }
onTradeClick = {
store.dispatch(
WalletAction.DialogAction.ChooseTradeActionDialog(
buyAllowed = selectedWallet.isAvailableToBuy(exchangeManager),
sellAllowed = selectedWallet.isAvailableToSell(exchangeManager),
swapAllowed = selectedWallet.isAvailableToSwap(swapInteractor, isExchangeServiceFeatureOn),
),
)
}
}
val actions = selectedWallet.getAvailableActions(
swapInteractor = swapInteractor,
exchangeManager = exchangeManager,
isExchangeFeatureOn = isExchangeServiceFeatureOn,
)
binding.rowButtons.updateButtonsVisibility(
actions = actions,
exchangeServiceFeatureOn = isExchangeServiceFeatureOn,
sendAllowed = selectedWallet.mainButton.enabled,
)
binding.rowButtons.onTradeClick = {
store.dispatch(
WalletAction.DialogAction.ChooseTradeActionDialog(
buyAllowed = selectedWallet.isAvailableToBuy,
sellAllowed = selectedWallet.isAvailableToSell,
swapAllowed = selectedWallet.isAvailableToSwap,
),
)
}
}
private fun handleWarnings(warnings: List<WalletWarning>) = with(binding) {

View file

@ -21,6 +21,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.fragments.setStatusBarColor
import com.tangem.core.ui.utils.OneTouchClickListener
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.tap.MainActivity
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Portfolio
@ -47,15 +48,21 @@ import com.tangem.tap.userWalletsListManager
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentWalletBinding
import dagger.hilt.android.AndroidEntryPoint
import org.rekotlin.StoreSubscriber
import javax.inject.Inject
@AndroidEntryPoint
class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<WalletState> {
@Inject
lateinit var swapInteractor: SwapInteractor
private lateinit var warningsAdapter: WarningMessagesAdapter
private val binding: FragmentWalletBinding by viewBinding(FragmentWalletBinding::bind)
private var walletView: WalletView = SingleWalletView()
private var walletView: WalletView = MultiWalletView()
private val viewModel by viewModels<WalletViewModel>()
@ -167,6 +174,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
else -> {} // we keep the same view unless we scan a card that requires a different view
}
walletView.swapInteractor = swapInteractor
walletView.onNewState(state)
if (!state.shouldShowDetails) {

View file

@ -5,8 +5,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.dispatchDialogHide
import com.tangem.tap.common.extensions.dispatchOpenUrl

View file

@ -2,10 +2,14 @@ package com.tangem.tap.features.wallet.ui.view
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.core.view.isVisible
import com.google.android.material.button.MaterialButton
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.wallet.redux.CurrencyAction
import com.tangem.wallet.databinding.ViewWalletDetailsButtonsRowBinding
internal class WalletDetailsButtonsRow @JvmOverloads constructor(
@ -20,6 +24,7 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor(
var onBuyClick: (() -> Unit)? = null
var onSellClick: (() -> Unit)? = null
var onTradeClick: (() -> Unit)? = null
var onSwapClick: (() -> Unit)? = null
var onSendClick: (() -> Unit)? = null
init {
@ -28,16 +33,45 @@ internal class WalletDetailsButtonsRow @JvmOverloads constructor(
with(binding) {
btnBuy.setOnClickListener { onBuyClick?.invoke() }
btnSell.setOnClickListener { onSellClick?.invoke() }
btnSwap.setOnClickListener { onSwapClick?.invoke() }
btnTrade.setOnClickListener { onTradeClick?.invoke() }
btnSend.setOnClickListener { onSendClick?.invoke() }
}
}
fun updateButtonsVisibility(exchangeServiceFeatureOn: Boolean, sendAllowed: Boolean) = with(binding) {
containerExchangeButtons.isVisible = exchangeServiceFeatureOn
btnBuy.hide()
btnSell.hide()
btnTrade.isVisible = exchangeServiceFeatureOn
fun updateButtonsVisibility(
actions: Set<CurrencyAction>,
exchangeServiceFeatureOn: Boolean,
sendAllowed: Boolean
) = with(binding) {
containerActionButtons.isVisible = exchangeServiceFeatureOn
when {
actions.isEmpty() -> {
containerActionButtons.hide()
}
actions.size == 1 -> {
val action = actions.first()
btnTrade.hide()
btnBuy.show(action == CurrencyAction.Buy)
btnSell.show(action == CurrencyAction.Sell)
btnSwap.show(action == CurrencyAction.Swap)
}
else -> {
btnBuy.hide()
btnSell.hide()
btnSwap.hide()
btnTrade.show()
}
}
if (containerActionButtons.isVisible) {
btnSend.gravity = Gravity.START or Gravity.CENTER_VERTICAL
btnSend.iconGravity = MaterialButton.ICON_GRAVITY_END
} else {
btnSend.gravity = Gravity.CENTER
btnSend.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_END
}
btnSend.isEnabled = sendAllowed
}
}

View file

@ -132,21 +132,34 @@ class SingleWalletView : WalletView() {
rowButtons: WalletDetailsButtonsRow,
isExchangeServiceFeatureEnabled: Boolean,
) {
rowButtons.updateButtonsVisibility(
val swapInteractor = this.swapInteractor ?: return
val exchangeManager = store.state.globalState.exchangeManager
binding?.rowButtons?.apply {
onBuyClick = { store.dispatch(WalletAction.TradeCryptoAction.Buy()) }
onSellClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) }
onSwapClick = { store.dispatch(WalletAction.TradeCryptoAction.Swap) }
onTradeClick = {
store.dispatch(
WalletAction.DialogAction.ChooseTradeActionDialog(
buyAllowed = walletData.isAvailableToBuy(exchangeManager),
sellAllowed = walletData.isAvailableToSell(exchangeManager),
swapAllowed = walletData.isAvailableToSwap(swapInteractor, isExchangeServiceFeatureEnabled),
),
)
}
}
val actions = walletData.getAvailableActions(
swapInteractor = swapInteractor,
exchangeManager = exchangeManager,
isExchangeFeatureOn = isExchangeServiceFeatureEnabled,
)
binding?.rowButtons?.updateButtonsVisibility(
actions = actions,
exchangeServiceFeatureOn = isExchangeServiceFeatureEnabled,
sendAllowed = walletData.mainButton.enabled,
)
rowButtons.onTradeClick = {
store.dispatch(
WalletAction.DialogAction.ChooseTradeActionDialog(
buyAllowed = walletData.isAvailableToBuy,
sellAllowed = walletData.isAvailableToSell,
swapAllowed = walletData.isAvailableToSwap,
),
)
}
rowButtons.onSendClick = {
when (walletData.mainButton) {
is WalletMainButton.SendButton -> store.dispatch(WalletAction.Send())

View file

@ -1,10 +1,13 @@
package com.tangem.tap.features.wallet.ui.wallet
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.databinding.FragmentWalletBinding
abstract class WalletView {
var swapInteractor: SwapInteractor? = null
protected var fragment: WalletFragment? = null
protected var binding: FragmentWalletBinding? = null
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {