Updated on 2026-08-14

This commit is contained in:
Tangem 2023-05-02 15:32:40 +03:00
commit e07cd9b157
11 changed files with 121 additions and 56 deletions

View file

@ -55,7 +55,7 @@ fun BigDecimal.toFiatRateString(fiatCurrencyName: String, fiatCode: String): Str
formatter.currency = currency
formatter.maximumFractionDigits = 2
formatter.roundingMode = RoundingMode.HALF_UP
return formatter.format(this)
return formatter.format(this).replace(currency.symbol, "$fiatCurrencyName")
}
} catch (e: IllegalArgumentException) {
Timber.e(e, "can't parse currency")
@ -96,7 +96,7 @@ fun BigDecimal.toFormattedFiatValue(
formatter.currency = currency
formatter.maximumFractionDigits = 2
formatter.roundingMode = RoundingMode.HALF_UP
return formatter.format(this)
return formatter.format(this).replace(currency.symbol, "$fiatCurrencyName")
}
} catch (e: IllegalArgumentException) {
Timber.e(e, "can't parse currency")

View file

@ -157,6 +157,10 @@ internal fun WalletDataModel.updateWithSelf(newWalletData: WalletDataModel): Wal
-> newStatus
},
existentialDeposit = newWalletData.existentialDeposit,
walletAddresses = newWalletData.walletAddresses?.copy(
selectedAddress = oldWalletData.walletAddresses?.selectedAddress
?: newWalletData.walletAddresses.selectedAddress,
),
fiatRate = newWalletData.fiatRate ?: oldWalletData.fiatRate,
)
}
@ -201,16 +205,7 @@ internal fun List<WalletDataModel>.updateSelectedAddress(
if (index == -1) return this
val oldWalletData = this[index]
val addresses = oldWalletData.walletAddresses
?: return this
val selectedAddress = addresses.list
.firstOrNull { it.type == addressType }
?: addresses.selectedAddress
val updatedWalletData = oldWalletData.copy(
walletAddresses = addresses.copy(
selectedAddress = selectedAddress,
),
)
val updatedWalletData = oldWalletData.updateSelectedAddress(addressType)
if (oldWalletData == updatedWalletData) return this
return this.toMutableList().apply {
@ -218,6 +213,19 @@ internal fun List<WalletDataModel>.updateSelectedAddress(
}
}
internal fun WalletDataModel.updateSelectedAddress(addressType: AddressType): WalletDataModel {
val addresses = walletAddresses ?: return this
val selectedAddress = addresses.list
.firstOrNull { it.type == addressType }
?: addresses.selectedAddress
return this.copy(
walletAddresses = addresses.copy(
selectedAddress = selectedAddress,
),
)
}
internal fun WalletDataModel.isSameWalletData(other: WalletDataModel): Boolean {
return this.currency == other.currency
}

View file

@ -176,18 +176,29 @@ private fun sendTransaction(
scope.launch {
val updateWalletResult = walletManager.safeUpdate()
if (updateWalletResult is Result.Failure) {
when (val error = updateWalletResult.error) {
is TapError -> store.dispatchErrorNotification(error)
else -> {
val tapError = if (error.message == null) {
TapError.UnknownError
} else {
TapError.CustomError(error.message!!)
withMainContext {
when (val error = updateWalletResult.error) {
is TapError -> store.dispatchErrorNotification(error)
is BlockchainSdkError -> {
updateFeedbackManagerInfo(
walletManager = walletManager,
amountToSend = amountToSend,
feeAmount = feeAmount,
destinationAddress = destinationAddress,
)
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error = error))
}
else -> {
val tapError = if (error.message == null) {
TapError.UnknownError
} else {
TapError.CustomError(error.message!!)
}
store.dispatchErrorNotification(tapError)
}
store.dispatchErrorNotification(tapError)
}
dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED))
}
withMainContext { dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) }
return@launch
}
@ -250,7 +261,7 @@ private fun sendTransaction(
}
}
is SimpleResult.Failure -> {
store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
updateFeedbackManagerInfo(
walletManager = walletManager,
amountToSend = amountToSend,
feeAmount = feeAmount,
@ -306,6 +317,20 @@ private fun sendTransaction(
}
}
private fun updateFeedbackManagerInfo(
walletManager: WalletManager,
amountToSend: Amount,
feeAmount: Amount,
destinationAddress: String,
) {
store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
walletManager = walletManager,
amountToSend = amountToSend,
feeAmount = feeAmount,
destinationAddress = destinationAddress,
)
}
fun createValidateTransactionError(
errorList: EnumSet<TransactionError>,
walletManager: WalletManager,

View file

@ -9,8 +9,8 @@ import kotlinx.collections.immutable.ImmutableList
*/
sealed interface TokenItemState {
/** Token name */
val name: String
/** Token full name (name with symbol) */
val fullName: String
/** Token icon url */
val iconUrl: String
@ -21,12 +21,12 @@ sealed interface TokenItemState {
/**
* Token item state that is available for read
*
* @property name token name
* @property fullName token name
* @property iconUrl token icon url
* @property networks list of networks that is available for read
*/
data class ReadContent(
override val name: String,
override val fullName: String,
override val iconUrl: String,
override val networks: ImmutableList<NetworkItemState.ReadContent>,
) : TokenItemState
@ -34,17 +34,19 @@ sealed interface TokenItemState {
/**
* Token item state that is available for read and manage
*
* @property name token name
* @property fullName token name
* @property iconUrl token icon url
* @property networks list of networks is available for read and edit
* @property id token id
* @property name token name
* @property symbol token brief name
*/
data class ManageContent(
override val name: String,
override val fullName: String,
override val iconUrl: String,
override val networks: ImmutableList<NetworkItemState.ManageContent>,
val id: String,
val name: String,
val symbol: String,
) : TokenItemState
}

View file

@ -60,7 +60,7 @@ internal fun TokenItem(model: TokenItemState) {
val spacing6 = TangemTheme.dimens.spacing6
Icon(
name = model.name,
name = model.fullName,
iconUrl = model.iconUrl,
modifier = Modifier.constrainAs(icon) {
top.linkTo(parent.top)
@ -69,7 +69,7 @@ internal fun TokenItem(model: TokenItemState) {
)
Title(
title = model.name,
title = model.fullName,
modifier = Modifier.constrainAs(title) {
top.linkTo(parent.top)
start.linkTo(icon.end, margin = spacing16)

View file

@ -15,17 +15,18 @@ object TokenListPreviewData {
fun createManageToken(): TokenItemState.ManageContent {
return TokenItemState.ManageContent(
name = "Tether (USDT)",
fullName = "Tether (USDT)",
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
networks = createManageNetworksList(),
id = "",
name = "Tether",
symbol = "",
)
}
fun createReadToken(): TokenItemState.ReadContent {
return TokenItemState.ReadContent(
name = "Tether (USDT)",
fullName = "Tether (USDT)",
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
networks = createReadNetworksList(),
)

View file

@ -141,7 +141,7 @@ private fun TokensListContent(
item { DifferentAddressesWarning() }
}
items(items = tokens, key = TokenItemState::name) {
items(items = tokens, key = TokenItemState::fullName) {
it?.let { TokenItem(model = it) }
}
}

View file

@ -126,10 +126,11 @@ internal class TokensListViewModel @Inject constructor(
private fun createManageTokenContent(token: Token): TokenItemState.ManageContent {
return TokenItemState.ManageContent(
name = getTokenName(token),
fullName = getTokenFullName(token),
iconUrl = token.iconUrl,
networks = token.networks.map(::createManageNetworkContent).toImmutableList(),
id = token.id,
name = token.name,
symbol = token.symbol,
)
}
@ -156,7 +157,7 @@ internal class TokensListViewModel @Inject constructor(
private fun createReadTokenContent(token: Token): TokenItemState.ReadContent {
return TokenItemState.ReadContent(
name = getTokenName(token),
fullName = getTokenFullName(token),
iconUrl = token.iconUrl,
networks = token.networks.map(::createReadNetworkContent).toImmutableList(),
)
@ -171,7 +172,7 @@ internal class TokensListViewModel @Inject constructor(
)
}
private fun getTokenName(token: Token) = "${token.name} (${token.symbol})"
private fun getTokenFullName(token: Token) = "${token.name} (${token.symbol})"
private fun getNetworkProtocolName(network: Network): String {
return if (network.address == null) {

View file

@ -14,10 +14,10 @@ import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.ui.images.load
import com.tangem.tap.features.wallet.ui.utils.getFormattedAmount
import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount
import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatRate
import com.tangem.tap.features.wallet.ui.images.load
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.ItemCurrencyWalletBinding
@ -96,9 +96,13 @@ class WalletAdapter : ListAdapter<WalletDataModel, WalletAdapter.WalletsViewHold
noRateValue = root.getString(id = R.string.token_item_no_rate),
)
cardWallet.setOnClickListener {
Analytics.send(Portfolio.TokenTapped())
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet.currency))
if (wallet.walletAddresses != null) {
cardWallet.setOnClickListener {
Analytics.send(Portfolio.TokenTapped())
store.dispatch(WalletAction.MultiWallet.SelectWallet(wallet.currency))
}
} else {
cardWallet.setOnClickListener(null)
}
}
}

View file

@ -34,7 +34,6 @@ import com.tangem.core.ui.components.SpacerW16
import com.tangem.core.ui.components.SpacerW4
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.formatWithSpaces
import com.tangem.tap.domain.model.TotalFiatBalance
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
import com.tangem.wallet.R
@ -42,6 +41,7 @@ import com.valentinilk.shimmer.shimmer
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.*
internal class TotalBalanceCard @JvmOverloads constructor(
@ -231,16 +231,21 @@ private fun LoadedAmount(amount: AnnotatedString, modifier: Modifier = Modifier)
private fun buildAmountString(amount: BigDecimal?, fiatCurrencySymbol: String): AnnotatedString {
if (amount == null) return AnnotatedString(text = UNKNOWN_AMOUNT_SIGN)
val format = DecimalFormat.getInstance(Locale.getDefault()) as DecimalFormat
val scaledAmount = amount
.setScale(2, RoundingMode.HALF_UP)
.formatWithSpaces()
val integer = scaledAmount.substringBefore('.')
val reminder = scaledAmount.substringAfter('.')
val formatter = NumberFormat.getInstance(Locale.getDefault()) as? DecimalFormat
?: return AnnotatedString("${amount.toPlainString()} $fiatCurrencySymbol")
val decimalFormat = formatter.apply {
maximumFractionDigits = 2
minimumFractionDigits = 2
isGroupingUsed = true
this.roundingMode = RoundingMode.HALF_UP
}
val scaledAmount = decimalFormat.format(amount)
val integer = scaledAmount.substringBefore(decimalFormat.decimalFormatSymbols.decimalSeparator)
val reminder = scaledAmount.substringAfter(decimalFormat.decimalFormatSymbols.decimalSeparator)
return buildAnnotatedString {
append(integer)
append(format.decimalFormatSymbols.decimalSeparator)
append(decimalFormat.decimalFormatSymbols.decimalSeparator)
append(
AnnotatedString(
text = "$reminder $fiatCurrencySymbol",

View file

@ -19,15 +19,15 @@ import com.tangem.tap.features.wallet.models.PendingTransactionType
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletMainButton
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.ui.BalanceWidget
import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
import com.tangem.tap.features.wallet.ui.utils.getAvailableActions
import com.tangem.tap.features.wallet.ui.utils.isAvailableToBuy
import com.tangem.tap.features.wallet.ui.utils.isAvailableToSell
import com.tangem.tap.features.wallet.ui.utils.mainButton
import com.tangem.tap.features.wallet.ui.utils.shouldShowMultipleAddress
import com.tangem.tap.features.wallet.ui.BalanceWidget
import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter
import com.tangem.tap.features.wallet.ui.view.WalletDetailsButtonsRow
import com.tangem.tap.store
import com.tangem.wallet.R
@ -35,6 +35,11 @@ import com.tangem.wallet.databinding.FragmentWalletBinding
class SingleWalletView : WalletView() {
private lateinit var pendingTransactionAdapter: PendingTransactionsAdapter
// FIXME: Move to model watcher
private var watchedPrimaryWalletForAddressCard: WalletDataModel? = null
private var watchedPrimaryWalletForBalance: WalletDataModel? = null
override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) {
setFragment(fragment, binding)
onViewCreated()
@ -42,6 +47,8 @@ class SingleWalletView : WalletView() {
}
private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) {
watchedPrimaryWalletForAddressCard = null
watchedPrimaryWalletForBalance = null
lSaltPayWallet.root.hide()
tvTwinCardNumber.hide()
rvMultiwallet.hide()
@ -61,6 +68,12 @@ class SingleWalletView : WalletView() {
setupTransactionsRecyclerView()
}
override fun onDestroyFragment() {
super.onDestroyFragment()
watchedPrimaryWalletForAddressCard = null
watchedPrimaryWalletForBalance = null
}
private fun setupTransactionsRecyclerView() {
val fragment = fragment ?: return
pendingTransactionAdapter = PendingTransactionsAdapter()
@ -89,6 +102,9 @@ class SingleWalletView : WalletView() {
}
private fun setupBalance(state: WalletState, primaryWallet: WalletDataModel) {
if (watchedPrimaryWalletForBalance == primaryWallet) return
watchedPrimaryWalletForBalance = primaryWallet
val fragment = fragment ?: return
binding?.apply {
lCardBalance.lBalance.root.show()
@ -180,6 +196,9 @@ class SingleWalletView : WalletView() {
private fun setupAddressCard(state: WalletState, binding: FragmentWalletBinding) = with(binding.lAddress) {
val primaryWallet = state.primaryWalletData
if (primaryWallet == watchedPrimaryWalletForAddressCard) return@with
watchedPrimaryWalletForAddressCard = primaryWallet
if (primaryWallet?.walletAddresses != null && primaryWallet.currency is Currency.Blockchain) {
binding.lAddress.root.show()
if (primaryWallet.shouldShowMultipleAddress()) {
@ -206,16 +225,16 @@ class SingleWalletView : WalletView() {
),
)
}
setupCardInfo(state)
setupCardInfo(primaryWallet)
} else {
binding.lAddress.root.hide()
}
}
private fun setupCardInfo(state: WalletState) {
private fun setupCardInfo(walletData: WalletDataModel) {
val textView = binding?.lAddress?.tvInfo
val blockchain = state.primaryWalletData?.currency?.blockchain
if (textView != null && blockchain != null) {
val blockchain = walletData.currency.blockchain
if (textView != null) {
textView.text = textView.getString(
id = R.string.address_qr_code_message_format,
blockchain.fullName,