Updated on 2026-08-14
This commit is contained in:
commit
e07cd9b157
11 changed files with 121 additions and 56 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue