Updated on 2026-08-14

This commit is contained in:
Tangem 2022-08-07 21:35:34 +04:00
parent efaf04920f
commit cb38a5783a
10 changed files with 280 additions and 81 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.details.redux
import com.tangem.common.card.Card
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTangemNote
import com.tangem.domain.common.isTangemTwin
@ -19,7 +20,6 @@ class DetailsReducer {
private fun internalReduce(action: Action, state: AppState): DetailsState {
if (action !is DetailsAction) return state.detailsState
val detailsState = state.detailsState
return when (action) {
is DetailsAction.PrepareScreen -> {
@ -40,7 +40,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
}
is DetailsAction.ChangeAppCurrency ->
detailsState.copy(appCurrency = action.fiatCurrency)
else -> detailsState
}
}
@ -48,7 +47,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
private fun handlePrepareScreen(
action: DetailsAction.PrepareScreen,
): DetailsState {
return DetailsState(
scanResponse = action.scanResponse,
wallets = action.wallets,
@ -99,7 +97,8 @@ private fun prepareSecurityOptions(card: Card): ManageSecurityState {
private fun isResetToFactoryAllowedByCard(card: Card): Boolean {
val notAllowedByAnyWallet = card.wallets.any { it.settings.isPermanent }
val notAllowedByCard = notAllowedByAnyWallet ||
(card.isWalletDataSupported && (!card.isTangemNote() && !card.settings.isBackupAllowed))
(card.isWalletDataSupported && (!card.isTangemNote() && !card.settings.isBackupAllowed)) ||
card.isSaltPay
return !notAllowedByCard
}
@ -140,7 +139,6 @@ private fun handleSecurityAction(
),
)
}
else -> state
}
}

View file

@ -14,6 +14,7 @@ import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import coil.load
import coil.size.Scale
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.tap.MainActivity
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
@ -32,6 +33,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter
import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView
import com.tangem.tap.features.wallet.ui.wallet.SaltPaySingleWalletView
import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView
import com.tangem.tap.features.wallet.ui.wallet.WalletView
import com.tangem.tap.store
@ -112,17 +114,22 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
override fun newState(state: WalletState) {
if (activity == null || view == null) return
if (state.isMultiwalletAllowed &&
state.primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
walletView is SingleWalletView
) {
walletView = MultiWalletView()
walletView.changeWalletView(this, binding)
} else if (!state.isMultiwalletAllowed && walletView is MultiWalletView) {
walletView = SingleWalletView()
walletView.changeWalletView(this, binding)
val isSaltPay = store.state.globalState.scanResponse?.card?.isSaltPay == true
when {
isSaltPay -> {
walletView = SaltPaySingleWalletView()
}
state.isMultiwalletAllowed &&
state.primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard &&
walletView is SingleWalletView -> {
walletView = MultiWalletView()
}
!state.isMultiwalletAllowed && walletView is MultiWalletView -> {
walletView = SingleWalletView()
}
}
walletView.changeWalletView(this, binding)
walletView.onNewState(state)
if (!state.shouldShowDetails) {
@ -134,7 +141,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
setupNoInternetHandling(state)
setupCardImage(state.cardImage)
showWarningsIfPresent(state.mainWarningsList)
if (!isSaltPay) showWarningsIfPresent(state.mainWarningsList)
binding.srlWallet.isRefreshing = state.state == ProgressState.Refreshing
binding.srlWallet.setOnRefreshListener {

View file

@ -25,14 +25,8 @@ import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentWalletBinding
class MultiWalletView : WalletView {
private var fragment: WalletFragment? = null
private var binding: FragmentWalletBinding? = null
class MultiWalletView : WalletView() {
private lateinit var walletsAdapter: WalletAdapter
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
@ -47,6 +41,7 @@ class MultiWalletView : WalletView {
lAddress.root.hide()
lButtonsShort.root.hide()
lButtonsLong.root.hide()
lSingleWalletBalance.root.hide()
rvMultiwallet.show()
btnAddToken.show()
setupWalletCardNumber(binding)
@ -64,15 +59,6 @@ class MultiWalletView : WalletView {
}
}
override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
}
override fun removeFragment() {
this.fragment = null
this.binding = null
}
override fun onViewCreated() {
setupWalletsRecyclerView()

View file

@ -0,0 +1,51 @@
package com.tangem.tap.features.wallet.ui.wallet
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.animateVisibility
import com.tangem.tap.common.extensions.formatAmountAsSpannedString
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.databinding.LayoutSingleWalletBalanceBinding
import java.math.BigDecimal
data class SaltPayBalanceWidgetData(
val state: ProgressState? = null,
val currencySymbol: String? = null,
val currency: String? = null,
val fiatAmount: BigDecimal? = null,
val fiatCurrency: FiatCurrency? = null,
)
class SaltPayBalanceWidget(
private val binding: LayoutSingleWalletBalanceBinding,
private val data: SaltPayBalanceWidgetData,
) {
fun setup(): Unit = with(binding) {
if (data.state == ProgressState.Loading) {
veilBalance.veil()
veilBalanceCrypto.veil()
} else {
veilBalance.unVeil()
veilBalanceCrypto.unVeil()
}
tvProcessing.animateVisibility(
show = data.state == ProgressState.Error,
)
veilBalanceCrypto.animateVisibility(
show = data.state != ProgressState.Error,
)
tvBalance.text = data.fiatAmount?.formatAmountAsSpannedString(
currencySymbol = data.fiatCurrency?.symbol ?: "",
)
tvBalanceCrypto.text = data.currency
tvCurrencyName.text = data.fiatCurrency?.code
tvCurrencyName.setOnClickListener {
store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
}
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.tap.features.wallet.ui.wallet
import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.store
import com.tangem.wallet.databinding.FragmentWalletBinding
class SaltPaySingleWalletView : WalletView() {
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
showSingleWalletView(binding)
}
private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
rvMultiwallet.hide()
btnAddToken.hide()
rvPendingTransaction.hide()
tvTwinCardNumber.hide()
lCardBalance.root.hide()
lAddress.root.hide()
lSingleWalletBalance.root.show()
}
override fun onViewCreated() {
}
override fun onNewState(state: WalletState) {
val binding = binding ?: return
state.primaryWallet ?: return
setupBalance(state, state.primaryWallet, binding)
}
private fun setupBalance(state: WalletState, primaryWallet: WalletData, binding: FragmentWalletBinding) {
binding.lSingleWalletBalance.root.show()
SaltPayBalanceWidget(
binding = binding.lSingleWalletBalance,
data = SaltPayBalanceWidgetData(
state = state.state,
currencySymbol = primaryWallet.currencyData.currencySymbol,
currency = primaryWallet.currencyData.amountFormatted,
fiatAmount = primaryWallet.currencyData.fiatAmount,
fiatCurrency = store.state.globalState.appCurrency,
),
).setup()
}
}

View file

@ -21,22 +21,8 @@ import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentWalletBinding
class SingleWalletView : WalletView {
class SingleWalletView : WalletView() {
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
private var fragment: WalletFragment? = null
private var binding: FragmentWalletBinding? = null
override fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
}
override fun removeFragment() {
fragment = null
binding = null
}
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
@ -49,13 +35,13 @@ class SingleWalletView : WalletView {
rvPendingTransaction.hide()
lCardBalance.root.show()
lAddress.root.show()
lSingleWalletBalance.root.hide()
}
override fun onViewCreated() {
setupTransactionsRecyclerView()
}
private fun setupTransactionsRecyclerView() {
val fragment = fragment ?: return
pendingTransactionAdapter = PendingTransactionsAdapter()
@ -65,7 +51,6 @@ class SingleWalletView : WalletView {
}
override fun onNewState(state: WalletState) {
val fragment = fragment ?: return
val binding = binding ?: return
state.primaryWallet ?: return
@ -81,7 +66,6 @@ class SingleWalletView : WalletView {
binding?.rvPendingTransaction?.show(pendingTransactions.isNotEmpty())
}
private fun setupBalance(state: WalletState, primaryWallet: WalletData) {
val fragment = fragment ?: return
binding?.apply {
@ -90,13 +74,13 @@ class SingleWalletView : WalletView {
binding = this.lCardBalance,
fragment = fragment,
data = primaryWallet.currencyData,
isTwinCard = state.isTangemTwins
isTwinCard = state.isTangemTwins,
).setup()
}
}
private fun setupTwinCards(
twinCardsState: TwinCardsState?, binding: FragmentWalletBinding
twinCardsState: TwinCardsState?, binding: FragmentWalletBinding,
) = with(binding) {
twinCardsState?.cardNumber?.let { cardNumber ->
tvTwinCardNumber.show()
@ -113,11 +97,9 @@ class SingleWalletView : WalletView {
}
private fun setupButtons(
state: WalletData, isTwinsWallet: Boolean, binding: FragmentWalletBinding
state: WalletData, isTwinsWallet: Boolean, binding: FragmentWalletBinding,
) = with(binding) {
setupButtonsType(state, binding)
val tradeState = state.tradeCryptoState
val btnConfirm = if (tradeState.isAvailableToSell() || tradeState.isAvailableToBuy()) {
lButtonsShort.btnConfirm
@ -137,8 +119,8 @@ class SingleWalletView : WalletView {
store.dispatch(
WalletAction.DialogAction.QrCode(
currency = state.currency,
selectedAddress = selectedAddress
)
selectedAddress = selectedAddress,
),
)
}
}
@ -207,7 +189,6 @@ class SingleWalletView : WalletView {
}
}
private fun setupAddressCard(state: WalletData, binding: FragmentWalletBinding) = with(binding.lAddress) {
if (state.walletAddresses != null && state.currency is Currency.Blockchain) {
binding.lAddress.root.show()
@ -215,7 +196,6 @@ class SingleWalletView : WalletView {
(binding.lAddress.root as? ViewGroup)?.beginDelayedTransition()
chipGroupAddressType.show()
chipGroupAddressType.fitChipsByGroupWidth()
val checkedId =
MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId)
@ -234,8 +214,8 @@ class SingleWalletView : WalletView {
store.dispatch(
WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,
fragment!!.requireContext()
)
fragment!!.requireContext(),
),
)
}
} else {

View file

@ -4,16 +4,20 @@ import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.databinding.FragmentWalletBinding
interface WalletView {
abstract class WalletView {
protected var fragment: WalletFragment? = null
protected var binding: FragmentWalletBinding? = null
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding) {
this.fragment = fragment
this.binding = binding
}
fun setFragment(fragment: WalletFragment, binding: FragmentWalletBinding)
fun removeFragment()
fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
fun onViewCreated()
fun onNewState(state: WalletState)
fun removeFragment() {
fragment = null
binding = null
}
abstract fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding)
abstract fun onViewCreated()
abstract fun onNewState(state: WalletState)
}

View file

@ -148,6 +148,16 @@
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
<include
android:id="@+id/l_single_wallet_balance"
layout="@layout/layout_single_wallet_balance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
<include
android:id="@+id/l_address"
layout="@layout/layout_address"

View file

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_balance"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="18dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="18dp"
android:animateLayoutChanges="true">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/main_page_balance"
android:textColor="@color/iconGray"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@id/tv_currency_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.skydoves.androidveil.VeilLayout
android:id="@+id/veil_balance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layout_constraintBottom_toTopOf="@id/tv_processing"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title"
app:veilLayout_baseColor="@color/lightGray0"
app:veilLayout_highlightColor="@color/lightGray1"
app:veilLayout_layout="@layout/card_total_balance_shimmer"
app:veilLayout_radius="4dp"
app:veilLayout_shimmerEnable="true"
app:veilLayout_veiled="true"
tools:veilLayout_veiled="false">
<TextView
android:id="@+id/tv_balance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:minWidth="152dp"
android:textColor="@color/darkGray6"
android:textSize="26sp"
android:textStyle="bold"
tools:text="22 325.40 $" />
</com.skydoves.androidveil.VeilLayout>
<com.skydoves.androidveil.VeilLayout
android:id="@+id/veil_balance_crypto"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_marginTop="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/veil_balance"
app:veilLayout_baseColor="@color/lightGray0"
app:veilLayout_highlightColor="@color/lightGray1"
app:veilLayout_layout="@layout/card_total_balance_shimmer"
app:veilLayout_radius="4dp"
app:veilLayout_shimmerEnable="true"
app:veilLayout_veiled="true"
tools:veilLayout_veiled="false"
tools:visibility="visible">
<TextView
android:id="@+id/tv_balance_crypto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:maxLines="1"
android:minWidth="152dp"
android:textColor="@color/text_tertiary"
android:textSize="12sp"
tools:text="5.13123123123 ETH"
tools:visibility="visible" />
</com.skydoves.androidveil.VeilLayout>
<TextView
android:id="@+id/tv_processing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/wallet_balance_blockchain_unreachable"
android:textColor="@color/warning"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/veil_balance"
tools:visibility="visible" />
<TextView
android:id="@+id/tv_currency_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/darkGray1"
android:textSize="16sp"
app:drawableEndCompat="@drawable/ic_arrow_angle_down"
app:drawableTint="@color/darkGray1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="USD" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -9,20 +9,18 @@ import java.util.*
[REDACTED_AUTHOR]
*/
object TapWorkarounds {
fun isStart2CoinIssuer(cardIssuer: String?): Boolean {
return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER
}
val Card.isStart2Coin: Boolean
get() = isStart2CoinIssuer(issuer.name)
val Card.isSaltPay: Boolean
get() = false //TODO fix when we know which cards are SaltPay cards
val Card.isTestCard: Boolean
get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
val Card.useOldStyleDerivation: Boolean
get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95"
val Card.derivationStyle: DerivationStyle?
get() = if (!settings.isHDWalletAllowed) {
null
@ -42,21 +40,19 @@ object TapWorkarounds {
return false
}
fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId)
fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId) || isSaltPay
fun isTangemWalletBatch(card: Card): Boolean = tangemWalletBatches.contains(card.batchId)
fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId]
fun Card.getTangemNoteBlockchain(): Blockchain? =
tangemNoteBatches[batchId] ?: if (isSaltPay) Blockchain.Gnosis else null
private const val START_2_COIN_ISSUER = "start2coin"
private const val TEST_CARD_BATCH = "99FF"
private const val TEST_CARD_ID_STARTS_WITH = "FF99"
private val excludedBatches = listOf(
"0027",
"0030",
"0031",
"0035"
"0035",
)
private val excludedIssuers = listOf(