Updated on 2026-08-14
This commit is contained in:
commit
9f46037b4c
53 changed files with 860 additions and 377 deletions
|
|
@ -40,10 +40,9 @@ import com.tangem.tap.domain.tasks.product.ScanProductTask
|
|||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
|
||||
|
||||
|
|
@ -68,6 +67,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
cardId: String? = null,
|
||||
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
messageRes: Int? = null,
|
||||
allowsRequestAccessCodeFromRepository: Boolean = false,
|
||||
): CompletionResult<ScanResponse> {
|
||||
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
return runTaskAsyncReturnOnMain(
|
||||
|
|
@ -75,6 +75,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
card = null,
|
||||
userTokensRepository = userTokensRepository,
|
||||
additionalBlockchainsToDerive = additionalBlockchainsToDerive,
|
||||
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
|
||||
),
|
||||
cardId = cardId,
|
||||
initialMessage = message,
|
||||
|
|
@ -111,7 +112,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
}
|
||||
|
||||
suspend fun derivePublicKeys(
|
||||
cardId: String,
|
||||
cardId: String?,
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>,
|
||||
): CompletionResult<DerivationTaskResponse> {
|
||||
return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId)
|
||||
|
|
@ -195,9 +196,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
accessCode: String? = null,
|
||||
): CompletionResult<T> =
|
||||
withContext(Dispatchers.Main) {
|
||||
suspendCoroutine { continuation ->
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage, accessCode) { result ->
|
||||
if (continuation.context.isActive) continuation.resume(result)
|
||||
if (continuation.isActive) continuation.resume(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import com.tangem.blockchain.common.Wallet
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.tap.domain.tasks.SignHashesTask
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class TangemSigner(
|
||||
private val card: CardDTO,
|
||||
|
|
@ -20,9 +20,9 @@ class TangemSigner(
|
|||
|
||||
override suspend fun sign(
|
||||
hashes: List<ByteArray>,
|
||||
publicKey: Wallet.PublicKey
|
||||
publicKey: Wallet.PublicKey,
|
||||
): CompletionResult<List<ByteArray>> {
|
||||
return suspendCoroutine { continuation ->
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
val cardId = if (card.backupStatus?.isActive == true) null else card.cardId
|
||||
|
||||
val task = SignHashesTask(hashes, publicKey)
|
||||
|
|
@ -37,13 +37,17 @@ class TangemSigner(
|
|||
signerCallback(
|
||||
TangemSignerResponse(
|
||||
result.data.totalSignedHashes,
|
||||
result.data.remainingSignatures
|
||||
)
|
||||
result.data.remainingSignatures,
|
||||
),
|
||||
)
|
||||
continuation.resume(CompletionResult.Success(result.data.signatures))
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(CompletionResult.Success(result.data.signatures))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure ->
|
||||
continuation.resume(CompletionResult.Failure(result.error))
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,11 +55,11 @@ class TangemSigner(
|
|||
|
||||
override suspend fun sign(
|
||||
hash: ByteArray,
|
||||
publicKey: Wallet.PublicKey
|
||||
publicKey: Wallet.PublicKey,
|
||||
): CompletionResult<ByteArray> {
|
||||
val result = sign(
|
||||
hashes = listOf(hash),
|
||||
publicKey = publicKey
|
||||
publicKey = publicKey,
|
||||
)
|
||||
|
||||
return when (result) {
|
||||
|
|
|
|||
|
|
@ -6,16 +6,18 @@ import java.math.BigDecimal
|
|||
* Represents fiat balance of [WalletStoreModel] list
|
||||
* @property amount Amount of the total balance
|
||||
* */
|
||||
sealed class TotalFiatBalance {
|
||||
open val amount: BigDecimal = BigDecimal.ZERO
|
||||
sealed interface TotalFiatBalance {
|
||||
val amount: BigDecimal?
|
||||
|
||||
object Loading : TotalFiatBalance()
|
||||
object Loading : TotalFiatBalance {
|
||||
override val amount: BigDecimal? = null
|
||||
}
|
||||
|
||||
data class Error(
|
||||
override val amount: BigDecimal,
|
||||
) : TotalFiatBalance()
|
||||
override val amount: BigDecimal?,
|
||||
) : TotalFiatBalance
|
||||
|
||||
data class Loaded(
|
||||
override val amount: BigDecimal,
|
||||
) : TotalFiatBalance()
|
||||
) : TotalFiatBalance
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import java.math.BigDecimal
|
|||
* 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
|
||||
* @param isCustom shows that currency is a custom
|
||||
* */
|
||||
data class WalletDataModel(
|
||||
val currency: Currency,
|
||||
|
|
@ -24,6 +25,7 @@ data class WalletDataModel(
|
|||
val existentialDeposit: BigDecimal?,
|
||||
val fiatRate: BigDecimal?,
|
||||
val isCardSingleToken: Boolean,
|
||||
val isCustom: Boolean,
|
||||
val historyTransactions: List<TransactionData>?,
|
||||
) {
|
||||
|
||||
|
|
@ -59,9 +61,7 @@ data class WalletDataModel(
|
|||
|
||||
data class NoAccount(
|
||||
val amountToCreateAccount: BigDecimal?,
|
||||
) : Status() {
|
||||
override val isErrorStatus: Boolean = true
|
||||
}
|
||||
) : Status()
|
||||
|
||||
data class Unreachable(
|
||||
override val errorMessage: String?,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ package com.tangem.tap.domain.model.builders
|
|||
|
||||
import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.domain.common.TapWorkarounds.derivationStyle
|
||||
import com.tangem.tap.common.extensions.getBlockchainTxHistory
|
||||
import com.tangem.tap.common.extensions.getTokenTxHistory
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.model.WalletDataModel
|
||||
import com.tangem.tap.domain.model.WalletStoreModel
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
|
|
@ -26,23 +28,23 @@ interface WalletStoreBuilder {
|
|||
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
userWallet: UserWallet,
|
||||
blockchainNetwork: BlockchainNetwork,
|
||||
): BlockchainNetworkWalletStoreBuilder {
|
||||
return BlockchainNetworkWalletStoreBuilderImpl(userWalletId, blockchainNetwork)
|
||||
return BlockchainNetworkWalletStoreBuilderImpl(userWallet, blockchainNetwork)
|
||||
}
|
||||
|
||||
operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
userWallet: UserWallet,
|
||||
walletManager: WalletManager,
|
||||
): WalletMangerWalletStoreBuilder {
|
||||
return WalletMangerWalletStoreBuilderImpl(userWalletId, walletManager)
|
||||
return WalletMangerWalletStoreBuilderImpl(userWallet, walletManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockchainNetworkWalletStoreBuilderImpl(
|
||||
private val userWalletId: UserWalletId,
|
||||
private val userWallet: UserWallet,
|
||||
private val blockchainNetwork: BlockchainNetwork,
|
||||
) : WalletStoreBuilder.BlockchainNetworkWalletStoreBuilder {
|
||||
private var walletManager: WalletManager? = null
|
||||
|
|
@ -52,11 +54,12 @@ private class BlockchainNetworkWalletStoreBuilderImpl(
|
|||
}
|
||||
|
||||
override fun build(): WalletStoreModel {
|
||||
val blockchainWalletData = blockchainNetwork.getBlockchainWalletData(walletManager)
|
||||
val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager)
|
||||
val cardDerivationStyle = userWallet.scanResponse.card.derivationStyle
|
||||
val blockchainWalletData = blockchainNetwork.getBlockchainWalletData(walletManager, cardDerivationStyle)
|
||||
val tokensWalletsData = blockchainNetwork.getTokensWalletsData(walletManager, cardDerivationStyle)
|
||||
|
||||
return WalletStoreModel(
|
||||
userWalletId = userWalletId,
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationPath = blockchainNetwork.derivationPath?.let { DerivationPath(it) },
|
||||
walletsData = listOf(blockchainWalletData) + tokensWalletsData,
|
||||
|
|
@ -68,7 +71,7 @@ private class BlockchainNetworkWalletStoreBuilderImpl(
|
|||
}
|
||||
|
||||
private class WalletMangerWalletStoreBuilderImpl(
|
||||
private val userWalletId: UserWalletId,
|
||||
private val userWallet: UserWallet,
|
||||
private val walletManager: WalletManager,
|
||||
) : WalletStoreBuilder.WalletMangerWalletStoreBuilder {
|
||||
|
||||
|
|
@ -78,7 +81,7 @@ private class WalletMangerWalletStoreBuilderImpl(
|
|||
val tokenWalletsData = wallet.getTokens().firstOrNull()?.toTokenWalletData(walletManager)
|
||||
|
||||
return WalletStoreModel(
|
||||
userWalletId = userWalletId,
|
||||
userWalletId = userWallet.walletId,
|
||||
blockchain = wallet.blockchain,
|
||||
derivationPath = wallet.publicKey.derivationPath,
|
||||
walletsData = listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData),
|
||||
|
|
@ -89,35 +92,45 @@ private class WalletMangerWalletStoreBuilderImpl(
|
|||
}
|
||||
}
|
||||
|
||||
private fun BlockchainNetwork.getBlockchainWalletData(walletManager: WalletManager?): WalletDataModel {
|
||||
private fun BlockchainNetwork.getBlockchainWalletData(
|
||||
walletManager: WalletManager?,
|
||||
cardDerivationStyle: DerivationStyle?,
|
||||
): WalletDataModel {
|
||||
val currency = Currency.Blockchain(
|
||||
blockchain = blockchain,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
return WalletDataModel(
|
||||
currency = Currency.Blockchain(
|
||||
blockchain = blockchain,
|
||||
derivationPath = derivationPath,
|
||||
),
|
||||
currency = currency,
|
||||
status = WalletDataModel.Loading,
|
||||
walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(),
|
||||
existentialDeposit = getExistentialDeposit(walletManager),
|
||||
fiatRate = null,
|
||||
isCardSingleToken = false,
|
||||
isCustom = currency.isCustomCurrency(cardDerivationStyle),
|
||||
historyTransactions = walletManager?.getBlockchainTxHistory(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun BlockchainNetwork.getTokensWalletsData(walletManager: WalletManager?): List<WalletDataModel> {
|
||||
private fun BlockchainNetwork.getTokensWalletsData(
|
||||
walletManager: WalletManager?,
|
||||
cardDerivationStyle: DerivationStyle?,
|
||||
): List<WalletDataModel> {
|
||||
return this.tokens
|
||||
.map { token ->
|
||||
val currency = Currency.Token(
|
||||
token = token,
|
||||
blockchain = blockchain,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
WalletDataModel(
|
||||
currency = Currency.Token(
|
||||
token = token,
|
||||
blockchain = blockchain,
|
||||
derivationPath = derivationPath,
|
||||
),
|
||||
currency = currency,
|
||||
status = WalletDataModel.Loading,
|
||||
walletAddresses = walletManager?.wallet?.createAddressesData().orEmpty(),
|
||||
existentialDeposit = getExistentialDeposit(walletManager),
|
||||
fiatRate = null,
|
||||
isCardSingleToken = walletManager?.cardTokens?.contains(token) ?: false,
|
||||
isCustom = currency.isCustomCurrency(cardDerivationStyle),
|
||||
historyTransactions = walletManager?.getTokenTxHistory(token),
|
||||
)
|
||||
}
|
||||
|
|
@ -135,6 +148,7 @@ private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): Wal
|
|||
existentialDeposit = getExistentialDeposit(walletManager),
|
||||
fiatRate = null,
|
||||
isCardSingleToken = false,
|
||||
isCustom = false,
|
||||
historyTransactions = walletManager.getBlockchainTxHistory(),
|
||||
)
|
||||
}
|
||||
|
|
@ -152,6 +166,7 @@ private fun Token.toTokenWalletData(walletManager: WalletManager): WalletDataMod
|
|||
existentialDeposit = getExistentialDeposit(walletManager),
|
||||
fiatRate = null,
|
||||
isCardSingleToken = walletManager.cardTokens.contains(this),
|
||||
isCustom = false,
|
||||
historyTransactions = walletManager.getTokenTxHistory(this),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.domain.scanCard
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
|
|
@ -46,7 +45,6 @@ import kotlinx.coroutines.launch
|
|||
object ScanCardProcessor {
|
||||
suspend fun scan(
|
||||
analyticsEvent: AnalyticsEvent? = null,
|
||||
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
cardId: String? = null,
|
||||
onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {},
|
||||
onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {},
|
||||
|
|
@ -63,7 +61,6 @@ object ScanCardProcessor {
|
|||
val result = tangemSdkManager.scanProduct(
|
||||
userTokensRepository = userTokensRepository,
|
||||
cardId = cardId,
|
||||
additionalBlockchainsToDerive = additionalBlockchainsToDerive,
|
||||
)
|
||||
|
||||
store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
|
||||
|
|
|
|||
|
|
@ -49,11 +49,9 @@ class ScanProductTask(
|
|||
val card: Card? = null,
|
||||
private val userTokensRepository: UserTokensRepository?,
|
||||
private val additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
override val allowsRequestAccessCodeFromRepository: Boolean = false,
|
||||
) : CardSessionRunnable<ScanResponse> {
|
||||
|
||||
override val allowsRequestAccessCodeFromRepository: Boolean
|
||||
get() = !additionalBlockchainsToDerive.isNullOrEmpty()
|
||||
|
||||
override fun run(
|
||||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||
|
|
@ -186,6 +184,11 @@ private class ScanWalletProcessor(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||
) {
|
||||
if (card.wallets.isNotEmpty() && card.backupStatus?.isActive == true) {
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
return
|
||||
}
|
||||
|
||||
if (card.wallets.isEmpty() || !card.isFirmwareMultiwalletAllowed) {
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
|
|||
when (walletsData.findStatus()) {
|
||||
TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading
|
||||
TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount())
|
||||
TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(calculateAmount())
|
||||
TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(
|
||||
amount = calculateAmount() ?: BigDecimal.ZERO,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +49,8 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
|
|||
is WalletDataModel.VerifiedOnline,
|
||||
is WalletDataModel.SameCurrencyTransactionInProgress,
|
||||
is WalletDataModel.TransactionInProgress,
|
||||
is WalletDataModel.NoAccount -> if (walletData.fiatRate == null) {
|
||||
is WalletDataModel.NoAccount,
|
||||
-> if (walletData.isCustom || walletData.fiatRate == null) {
|
||||
TotalFiatBalanceStatus.Error
|
||||
} else {
|
||||
TotalFiatBalanceStatus.Loaded
|
||||
|
|
@ -60,14 +63,17 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Sequence<WalletDataModel>.calculateTotalFiatAmount(): BigDecimal {
|
||||
private fun Sequence<WalletDataModel>.calculateTotalFiatAmount(): BigDecimal? {
|
||||
return this
|
||||
.filterNot { it.isCustom }
|
||||
.map { walletData ->
|
||||
walletData.fiatRate
|
||||
?.takeUnless { walletData.status.isErrorStatus }
|
||||
?.let { walletData.status.amount.toFiatValue(it) }
|
||||
?: BigDecimal.ZERO
|
||||
}
|
||||
.reduce(BigDecimal::plus)
|
||||
.reduce { acc, value ->
|
||||
value?.let { acc?.plus(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCurrentStatus(
|
||||
|
|
@ -88,8 +94,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
|
|||
}
|
||||
|
||||
private enum class TotalFiatBalanceStatus {
|
||||
Loading,
|
||||
Error,
|
||||
Loaded,
|
||||
Loading, Error, Loaded,
|
||||
}
|
||||
}
|
||||
|
|
@ -110,21 +110,26 @@ internal class BiometricUserWalletsListManager(
|
|||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
if (userWalletIds.isEmpty()) {
|
||||
val idsToRemove = state.value.userWallets
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.filter { it.walletId in userWalletIds }
|
||||
?.map { it.walletId }
|
||||
|
||||
if (idsToRemove.isNullOrEmpty()) {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
changeSelectedUserWalletIdIfNeeded(userWalletIds)
|
||||
changeSelectedUserWalletIdIfNeeded(idsToRemove)
|
||||
|
||||
return sensitiveInformationRepository.delete(userWalletIds)
|
||||
.flatMap { publicInformationRepository.delete(userWalletIds) }
|
||||
.flatMap { keysRepository.delete(userWalletIds) }
|
||||
return sensitiveInformationRepository.delete(idsToRemove)
|
||||
.flatMap { publicInformationRepository.delete(idsToRemove) }
|
||||
.flatMap { keysRepository.delete(idsToRemove) }
|
||||
.map {
|
||||
state.update { prevState ->
|
||||
val newUserWallets = prevState.userWallets.filter { it.walletId !in userWalletIds }
|
||||
val newUserWallets = prevState.userWallets.filter { it.walletId !in idsToRemove }
|
||||
|
||||
prevState.copy(
|
||||
encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds },
|
||||
encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in idsToRemove },
|
||||
userWallets = newUserWallets,
|
||||
isLocked = newUserWallets.any { it.isLocked },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,12 +55,16 @@ internal class DefaultWalletCurrenciesManager(
|
|||
currenciesToAdd: List<Currency>,
|
||||
): CompletionResult<Unit> = withContext(Dispatchers.Default) {
|
||||
val card = userWallet.scanResponse.card
|
||||
val newCurrencies = (getSavedCurrencies(userWallet.walletId) + currenciesToAdd)
|
||||
.addMissingBlockchains(card)
|
||||
val currenciesToAddWithMissingBlockchains = currenciesToAdd.addMissingBlockchainsIfNeeded(card)
|
||||
|
||||
updateWalletStores(userWallet, newCurrencies.toBlockchainNetworks())
|
||||
updateWalletStores(
|
||||
userWallet = userWallet,
|
||||
blockchainNetworks = currenciesToAddWithMissingBlockchains
|
||||
.toBlockchainNetworks()
|
||||
.addSameBlockchainTokens(userWallet.walletId),
|
||||
)
|
||||
.map {
|
||||
saveUserCurrencies(card, newCurrencies)
|
||||
saveUserCurrencies(card, getSavedCurrencies(userWallet.walletId))
|
||||
}
|
||||
.flatMap {
|
||||
updateWalletStoresAmounts(
|
||||
|
|
@ -114,7 +118,38 @@ internal class DefaultWalletCurrenciesManager(
|
|||
}
|
||||
}
|
||||
|
||||
private fun List<Currency>.addMissingBlockchains(card: CardDTO): List<Currency> {
|
||||
// TODO: Need refactoring
|
||||
private suspend fun List<BlockchainNetwork>.addSameBlockchainTokens(
|
||||
userWalletId: UserWalletId,
|
||||
): List<BlockchainNetwork> {
|
||||
val networks = arrayListOf<BlockchainNetwork>()
|
||||
val savedWalletStores = withContext(Dispatchers.Default) {
|
||||
walletStoresRepository.getSync(userWalletId)
|
||||
}
|
||||
|
||||
this.forEach { network ->
|
||||
val walletStore = savedWalletStores.firstOrNull {
|
||||
it.blockchain == network.blockchain && it.derivationPath?.rawPath == network.derivationPath
|
||||
}
|
||||
|
||||
if (walletStore != null) {
|
||||
val tokens = walletStore.walletsData
|
||||
.asSequence()
|
||||
.map { it.currency }
|
||||
.filterIsInstance<Currency.Token>()
|
||||
.map { it.token }
|
||||
.toList()
|
||||
|
||||
networks.add(network.copy(tokens = tokens + network.tokens.toSet()))
|
||||
} else {
|
||||
networks.add(network)
|
||||
}
|
||||
}
|
||||
|
||||
return networks
|
||||
}
|
||||
|
||||
private fun List<Currency>.addMissingBlockchainsIfNeeded(card: CardDTO): List<Currency> {
|
||||
if (this.isEmpty()) return this
|
||||
val currencies = this.asSequence()
|
||||
|
||||
|
|
@ -165,7 +200,6 @@ internal class DefaultWalletCurrenciesManager(
|
|||
userWallet: UserWallet,
|
||||
blockchainNetworks: List<BlockchainNetwork>,
|
||||
): CompletionResult<Unit> {
|
||||
val userWalletId = userWallet.walletId
|
||||
return blockchainNetworks
|
||||
.map { blockchainNetwork ->
|
||||
walletManagersRepository.findOrMakeMultiCurrencyWalletManager(
|
||||
|
|
@ -174,8 +208,8 @@ internal class DefaultWalletCurrenciesManager(
|
|||
)
|
||||
.flatMap { walletManager ->
|
||||
walletStoresRepository.storeOrUpdate(
|
||||
userWalletId = userWalletId,
|
||||
walletStore = WalletStoreBuilder(userWalletId, blockchainNetwork)
|
||||
userWalletId = userWallet.walletId,
|
||||
walletStore = WalletStoreBuilder(userWallet, blockchainNetwork)
|
||||
.walletManager(walletManager)
|
||||
.build(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ internal class DefaultWalletStoresManager(
|
|||
{ walletManager ->
|
||||
walletStoresRepository.storeOrUpdate(
|
||||
userWalletId = userWalletId,
|
||||
walletStore = WalletStoreBuilder(userWalletId, blockchainNetwork)
|
||||
walletStore = WalletStoreBuilder(userWallet, blockchainNetwork)
|
||||
.walletManager(walletManager)
|
||||
.build(),
|
||||
)
|
||||
|
|
@ -171,7 +171,7 @@ internal class DefaultWalletStoresManager(
|
|||
val userWalletId = userWallet.walletId
|
||||
walletStoresRepository.storeOrUpdate(
|
||||
userWalletId = userWalletId,
|
||||
walletStore = WalletStoreBuilder(userWalletId, walletManager)
|
||||
walletStore = WalletStoreBuilder(userWallet, walletManager)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ internal class DefaultWalletAmountsRepository(
|
|||
.toMutableList()
|
||||
.apply {
|
||||
replaceByOrAdd(walletManager) {
|
||||
it.wallet.blockchain == it.wallet.blockchain
|
||||
it.wallet.blockchain == walletManager.wallet.blockchain
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import com.tangem.blockchain.common.WalletManager
|
|||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.catching
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.mapFailure
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
|
|
@ -22,7 +22,7 @@ import com.tangem.tap.domain.walletStores.WalletStoresError
|
|||
import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository
|
||||
import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -93,10 +93,7 @@ internal class DefaultWalletManagersRepository(
|
|||
scanResponse = scanResponse,
|
||||
blockchainNetwork = blockchainNetwork,
|
||||
)
|
||||
.map { updatedWalletManager ->
|
||||
store(userWallet.walletId, updatedWalletManager)
|
||||
updatedWalletManager
|
||||
}
|
||||
.doOnSuccess { store(userWallet.walletId, it) }
|
||||
}
|
||||
else -> {
|
||||
val error = WalletStoresError.WalletManagerNotCreated(blockchain)
|
||||
|
|
@ -158,8 +155,9 @@ internal class DefaultWalletManagersRepository(
|
|||
return catching {
|
||||
val tokens = blockchainNetwork?.tokens ?: listOfNotNull(scanResponse.cardTypesResolver.getPrimaryToken())
|
||||
|
||||
if (tokens != cardTokens) {
|
||||
cardTokens.clear()
|
||||
if (tokens != walletManager.cardTokens) {
|
||||
walletManager.cardTokens.clear()
|
||||
walletManager.wallet.removeAllTokens()
|
||||
if (tokens.isNotEmpty()) {
|
||||
walletManager.cardTokens.addAll(tokens)
|
||||
}
|
||||
|
|
@ -181,13 +179,16 @@ internal class DefaultWalletManagersRepository(
|
|||
userWalletId: UserWalletId,
|
||||
blockchain: Blockchain?,
|
||||
): WalletManager? {
|
||||
return walletManagersStorage.getAll().first()[userWalletId]?.let { userWalletManagers ->
|
||||
if (blockchain == null) {
|
||||
userWalletManagers.firstOrNull()
|
||||
} else {
|
||||
userWalletManagers.find { it.wallet.blockchain == blockchain }
|
||||
return walletManagersStorage.getAll()
|
||||
.firstOrNull()
|
||||
?.get(userWalletId)
|
||||
?.let { userWalletManagers ->
|
||||
if (blockchain == null) {
|
||||
userWalletManagers.firstOrNull()
|
||||
} else {
|
||||
userWalletManagers.firstOrNull { it.wallet.blockchain == blockchain }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDerivationParams(derivationPath: String?, card: CardDTO): DerivationParams? {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,10 @@ class DetailsMiddleware {
|
|||
}
|
||||
DetailsAction.ScanCard -> {
|
||||
scope.launch {
|
||||
tangemSdkManager.scanProduct(userTokensRepository)
|
||||
tangemSdkManager.scanProduct(
|
||||
userTokensRepository = userTokensRepository,
|
||||
allowsRequestAccessCodeFromRepository = true,
|
||||
)
|
||||
.doOnSuccess { scanResponse ->
|
||||
val currentUserWalletId = state.scanResponse
|
||||
?.let { UserWalletIdBuilder.scanResponse(it).build() }
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package com.tangem.tap.features.details.redux
|
|||
|
||||
import com.tangem.domain.common.CardDTO
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.extensions.isWalletDataSupported
|
||||
import com.tangem.tap.domain.extensions.signedHashesCount
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -109,11 +107,9 @@ private fun prepareSecurityOptions(card: CardDTO, cardTypesResolver: CardTypesRe
|
|||
}
|
||||
|
||||
private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean {
|
||||
val notAllowedByAnyWallet = card.wallets.any { it.settings.isPermanent }
|
||||
val notAllowedByCard = notAllowedByAnyWallet ||
|
||||
card.isWalletDataSupported && !cardTypesResolver.isTangemNote() && !card.settings.isBackupAllowed ||
|
||||
card.isSaltPay
|
||||
return !notAllowedByCard
|
||||
val hasPermanentWallet = card.wallets.any { it.settings.isPermanent }
|
||||
val isNotAllowed = hasPermanentWallet || cardTypesResolver.isSaltPay() || cardTypesResolver.isStart2Coin()
|
||||
return !isNotAllowed
|
||||
}
|
||||
|
||||
private fun handleEraseWallet(
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
fun updateScanResponse(response: ScanResponse) {
|
||||
when (twinCardsState.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response
|
||||
CreateTwinWalletMode.RecreateWallet -> store.dispatch(GlobalAction.SaveScanResponse(response))
|
||||
CreateTwinWalletMode.RecreateWallet -> store.dispatchOnMain(GlobalAction.SaveScanResponse(response))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -322,8 +322,9 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(TwinCardsAction.CardsManager.Release)
|
||||
|
||||
action.shouldResetTwinCardsWidget(shouldReturnCardBack) {
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(getPopBackScreen()))
|
||||
}
|
||||
}
|
||||
store.dispatchDialogShow(OnboardingDialog.InterruptOnboarding(onOkCallback))
|
||||
|
|
@ -331,4 +332,16 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPopBackScreen(): AppScreen {
|
||||
return if (userWalletsListManager.hasSavedUserWallets) {
|
||||
if (userWalletsListManager.isLockedSync) {
|
||||
AppScreen.Welcome
|
||||
} else {
|
||||
AppScreen.Wallet
|
||||
}
|
||||
} else {
|
||||
AppScreen.Home
|
||||
}
|
||||
}
|
||||
|
|
@ -15,10 +15,10 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.fragments.setStatusBarColor
|
||||
import com.tangem.datasource.utils.AndroidAssetReader
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.datasource.utils.AndroidAssetReader
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
|
|
|
|||
|
|
@ -207,9 +207,8 @@ class TokensMiddleware {
|
|||
}
|
||||
|
||||
scope.launch {
|
||||
val card = scanResponse.card
|
||||
val result = tangemSdkManager.derivePublicKeys(
|
||||
cardId = card.cardId,
|
||||
cardId = null,
|
||||
derivations = derivations,
|
||||
)
|
||||
when (result) {
|
||||
|
|
@ -232,7 +231,6 @@ class TokensMiddleware {
|
|||
|
||||
onSuccess(updatedScanResponse)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ import java.math.BigDecimal
|
|||
|
||||
data class TotalBalance(
|
||||
val state: ProgressState,
|
||||
val fiatAmount: BigDecimal,
|
||||
val fiatAmount: BigDecimal?,
|
||||
val fiatCurrency: FiatCurrency,
|
||||
)
|
||||
|
|
@ -5,8 +5,8 @@ import com.tangem.blockchain.common.Token
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.flatMap
|
||||
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.Token.ButtonRemoveToken
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
|
|
@ -21,7 +21,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.scanCard.ScanCardProcessor
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
|
|
@ -33,12 +32,12 @@ import com.tangem.tap.features.wallet.redux.models.WalletDialog
|
|||
import com.tangem.tap.features.wallet.redux.reducers.toWallet
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
class MultiWalletMiddleware {
|
||||
|
|
@ -195,24 +194,26 @@ class MultiWalletMiddleware {
|
|||
state: WalletState?,
|
||||
) = scope.launch(Dispatchers.Default) {
|
||||
dispatchOnMain(WalletAction.MultiWallet.ScheduleCheckForMissingDerivation)
|
||||
ScanCardProcessor.scan(
|
||||
analyticsEvent = null,
|
||||
tangemSdkManager.scanProduct(
|
||||
cardId = selectedUserWallet.cardId,
|
||||
userTokensRepository = userTokensRepository,
|
||||
additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain },
|
||||
) { scanResponse ->
|
||||
userWalletsListManager.update(
|
||||
userWalletId = selectedUserWallet.walletId,
|
||||
update = { userWallet ->
|
||||
userWallet.copy(
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
},
|
||||
)
|
||||
.doOnSuccess { updatedUserWallet ->
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList()))
|
||||
store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true)
|
||||
}
|
||||
}
|
||||
allowsRequestAccessCodeFromRepository = true,
|
||||
)
|
||||
.flatMap { scanResponse ->
|
||||
userWalletsListManager.update(
|
||||
userWalletId = selectedUserWallet.walletId,
|
||||
update = { userWallet ->
|
||||
userWallet.copy(
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
.doOnSuccess { updatedUserWallet ->
|
||||
store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList()))
|
||||
store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addDummyBalances(walletManagers: List<WalletManager>) {
|
||||
|
|
@ -251,10 +252,16 @@ class MultiWalletMiddleware {
|
|||
scope.launch { userTokensRepository.saveUserTokens(scanResponse.card, currencies) }
|
||||
}
|
||||
} ?: wmFactory.makeWalletManagerForApp(scanResponse, blockchainNetwork)?.also {
|
||||
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchainNetwork.updateTokens(tokens), it, save))
|
||||
store.dispatchOnMain(
|
||||
WalletAction.MultiWallet.AddBlockchain(
|
||||
blockchain = blockchainNetwork.updateTokens(tokens),
|
||||
walletManager = it,
|
||||
save = save,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
store.dispatchOnMain(
|
||||
WalletAction.LoadFiatRate(
|
||||
coinsList = tokens.map { token ->
|
||||
Currency.Token(
|
||||
|
|
@ -268,30 +275,25 @@ class MultiWalletMiddleware {
|
|||
if (tokens.isNotEmpty()) walletManager?.addTokens(tokens)
|
||||
|
||||
scope.launch {
|
||||
val result = walletManager?.safeUpdate()
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val wallet = result.data
|
||||
wallet.getTokens()
|
||||
.filter { tokens.contains(it) }
|
||||
.mapNotNull { token ->
|
||||
wallet.getTokenAmount(token)?.let { Pair(token, it) }
|
||||
}
|
||||
.forEach {
|
||||
withContext(Dispatchers.Main) {
|
||||
store.dispatch(
|
||||
WalletAction.MultiWallet.TokenLoaded(
|
||||
it.second,
|
||||
it.first,
|
||||
blockchainNetwork,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
when (val result = walletManager?.safeUpdate()) {
|
||||
is com.tangem.common.services.Result.Success -> {
|
||||
val wallet = result.data
|
||||
wallet.getTokens()
|
||||
.filter { tokens.contains(it) }
|
||||
.mapNotNull { token ->
|
||||
wallet.getTokenAmount(token)?.let { amount -> Pair(token, amount) }
|
||||
}
|
||||
.forEach { (token, tokenAmount) ->
|
||||
store.dispatchOnMain(
|
||||
WalletAction.MultiWallet.TokenLoaded(
|
||||
amount = tokenAmount,
|
||||
token = token,
|
||||
blockchain = blockchainNetwork,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
|
|||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchToastNotification
|
||||
import com.tangem.tap.common.extensions.getBlockchainTxHistory
|
||||
import com.tangem.tap.common.extensions.getTokenTxHistory
|
||||
|
|
@ -113,14 +114,17 @@ class MultiWalletReducer {
|
|||
val currency = Currency.fromBlockchainNetwork(action.blockchain, action.token)
|
||||
val walletManager = state.getWalletManager(currency)
|
||||
if (walletManager == null) {
|
||||
if (userWalletsListManager.hasSavedUserWallets) {
|
||||
store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Welcome))
|
||||
val screen = if (userWalletsListManager.hasSavedUserWallets) {
|
||||
AppScreen.Welcome
|
||||
} else {
|
||||
store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home))
|
||||
AppScreen.Home
|
||||
}
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(screen))
|
||||
|
||||
FirebaseCrashlytics.getInstance().recordException(
|
||||
IllegalStateException("MultiWallet.TokenLoaded: walletManager is null"),
|
||||
)
|
||||
|
||||
store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found)
|
||||
return state
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ 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.PendingTransactionType
|
||||
import com.tangem.tap.features.wallet.models.WalletWarning
|
||||
import com.tangem.tap.features.wallet.redux.AddressData
|
||||
import com.tangem.tap.features.wallet.redux.ErrorType
|
||||
|
|
@ -333,8 +334,11 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details),
|
|||
}
|
||||
|
||||
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 setupAddressCard(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,340 @@
|
|||
package com.tangem.tap.features.wallet.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.AbstractComposeView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SelectorButton
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
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.features.wallet.models.TotalBalance
|
||||
import com.tangem.tap.features.wallet.redux.ProgressState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.wallet.R
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.util.*
|
||||
|
||||
internal class TotalBalanceCard @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : AbstractComposeView(context, attrs, defStyleAttr) {
|
||||
private var state by mutableStateOf<TotalBalanceCardState>(TotalBalanceCardState.Empty)
|
||||
|
||||
var status: TotalBalance? = null
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
updateState(value, onChangeFiatCurrencyClick)
|
||||
}
|
||||
|
||||
var onChangeFiatCurrencyClick: () -> Unit = { /* no-op */ }
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
updateState(status, value)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
TangemTheme {
|
||||
TotalBalanceCardContent(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAccessibilityClassName(): CharSequence {
|
||||
return javaClass.name
|
||||
}
|
||||
|
||||
private fun updateState(status: TotalBalance?, onChangeCurrencyClick: () -> Unit) {
|
||||
state = when (status?.state) {
|
||||
null -> TotalBalanceCardState.Empty
|
||||
ProgressState.Loading -> TotalBalanceCardState.Loading(
|
||||
fiatCurrency = status.fiatCurrency,
|
||||
onChangeFiatCurrencyClick = onChangeCurrencyClick,
|
||||
)
|
||||
ProgressState.Error -> TotalBalanceCardState.Failure(
|
||||
amount = status.fiatAmount,
|
||||
fiatCurrency = status.fiatCurrency,
|
||||
onChangeFiatCurrencyClick = onChangeCurrencyClick,
|
||||
)
|
||||
ProgressState.Refreshing,
|
||||
ProgressState.Done,
|
||||
-> TotalBalanceCardState.Success(
|
||||
amount = status.fiatAmount ?: BigDecimal.ZERO,
|
||||
fiatCurrency = status.fiatCurrency,
|
||||
onChangeFiatCurrencyClick = onChangeCurrencyClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TotalBalanceCardContent(
|
||||
state: TotalBalanceCardState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TotalBalanceCardScaffold(
|
||||
modifier = modifier,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.main_page_balance),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
},
|
||||
amount = {
|
||||
when (state) {
|
||||
is TotalBalanceCardState.Empty,
|
||||
is TotalBalanceCardState.Loading,
|
||||
-> LoadingAmount()
|
||||
is TotalBalanceCardState.Failure,
|
||||
is TotalBalanceCardState.Success,
|
||||
-> LoadedAmount(
|
||||
amount = buildAmountString(
|
||||
amount = state.amount,
|
||||
fiatCurrencySymbol = state.fiatCurrency.symbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
currencySelector = {
|
||||
if (state !is TotalBalanceCardState.Empty) {
|
||||
SelectorButton(
|
||||
text = state.fiatCurrency.code,
|
||||
onClick = state.onChangeFiatCurrencyClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
failureText = {
|
||||
AnimatedVisibility(visible = state is TotalBalanceCardState.Failure) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.main_processing_full_amount),
|
||||
style = TangemTheme.typography.caption,
|
||||
color = TangemTheme.colors.text.attention,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TotalBalanceCardScaffold(
|
||||
title: @Composable () -> Unit,
|
||||
amount: @Composable () -> Unit,
|
||||
currencySelector: @Composable () -> Unit,
|
||||
failureText: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
amountWeight: Float = 0.8f,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = TangemTheme.shapes.roundedCornersMedium,
|
||||
color = TangemTheme.colors.background.plain,
|
||||
elevation = TangemTheme.dimens.elevation1,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
SpacerW16()
|
||||
Column(
|
||||
modifier = Modifier.weight(amountWeight),
|
||||
) {
|
||||
SpacerH12()
|
||||
title()
|
||||
SpacerH4()
|
||||
amount()
|
||||
}
|
||||
currencySelector()
|
||||
SpacerW4()
|
||||
}
|
||||
SpacerH4()
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
) {
|
||||
failureText()
|
||||
}
|
||||
SpacerH12()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingAmount(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier.shimmer()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(TangemTheme.dimens.size116)
|
||||
.height(TangemTheme.dimens.size32)
|
||||
.background(
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersSmall2,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadedAmount(
|
||||
amount: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
Text(
|
||||
text = amount,
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
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('.')
|
||||
|
||||
return buildAnnotatedString {
|
||||
append(integer)
|
||||
append(format.decimalFormatSymbols.decimalSeparator)
|
||||
append(
|
||||
AnnotatedString(
|
||||
text = "$reminder $fiatCurrencySymbol",
|
||||
spanStyle = TangemTheme.typography.h3.toSpanStyle(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private sealed interface TotalBalanceCardState {
|
||||
val amount: BigDecimal?
|
||||
val onChangeFiatCurrencyClick: () -> Unit
|
||||
val fiatCurrency: FiatCurrency
|
||||
|
||||
object Empty : TotalBalanceCardState {
|
||||
override val amount: BigDecimal? = null
|
||||
override val fiatCurrency: FiatCurrency = FiatCurrency.Default
|
||||
override val onChangeFiatCurrencyClick: () -> Unit = { /* no-op */ }
|
||||
}
|
||||
|
||||
data class Loading(
|
||||
override val fiatCurrency: FiatCurrency,
|
||||
override val onChangeFiatCurrencyClick: () -> Unit,
|
||||
) : TotalBalanceCardState {
|
||||
override val amount: BigDecimal = BigDecimal.ZERO
|
||||
}
|
||||
|
||||
data class Failure(
|
||||
override val amount: BigDecimal?,
|
||||
override val fiatCurrency: FiatCurrency,
|
||||
override val onChangeFiatCurrencyClick: () -> Unit,
|
||||
) : TotalBalanceCardState
|
||||
|
||||
data class Success(
|
||||
override val amount: BigDecimal,
|
||||
override val fiatCurrency: FiatCurrency,
|
||||
override val onChangeFiatCurrencyClick: () -> Unit,
|
||||
) : TotalBalanceCardState
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun TotalBalanceCardContentSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
TotalBalanceCardContent(state = TotalBalanceCardState.Empty)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
TotalBalanceCardContent(
|
||||
state = TotalBalanceCardState.Loading(FiatCurrency("USD", "USD", "$")) {},
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
TotalBalanceCardContent(
|
||||
state = TotalBalanceCardState.Failure(
|
||||
amount = BigDecimal("9917.72"),
|
||||
onChangeFiatCurrencyClick = {},
|
||||
fiatCurrency = FiatCurrency("USD", "USD", "$"),
|
||||
),
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
TotalBalanceCardContent(
|
||||
state = TotalBalanceCardState.Success(
|
||||
amount = BigDecimal("9917.72"),
|
||||
onChangeFiatCurrencyClick = {},
|
||||
fiatCurrency = FiatCurrency("USD", "USD", "$"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun TotalBalanceCardContentPreview_Light() {
|
||||
TangemTheme {
|
||||
TotalBalanceCardContentSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun TotalBalanceCardContentPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
TotalBalanceCardContentSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.wallet.ui.wallet
|
||||
|
||||
import android.widget.Button
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.badoo.mvicore.DiffStrategy
|
||||
|
|
@ -11,8 +12,6 @@ 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
|
||||
import com.tangem.tap.common.extensions.getQuantityString
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
|
|
@ -95,7 +94,7 @@ class MultiWalletView : WalletView() {
|
|||
lAddress.root.hide()
|
||||
rowButtons.hide()
|
||||
lSingleWalletBalance.root.hide()
|
||||
lCardTotalBalance.root.show()
|
||||
lCardTotalBalance.show()
|
||||
rvMultiwallet.show()
|
||||
btnAddToken.show()
|
||||
}
|
||||
|
|
@ -183,37 +182,12 @@ class MultiWalletView : WalletView() {
|
|||
progressState: ProgressState,
|
||||
walletsCount: Int,
|
||||
) = with(binding.lCardTotalBalance) {
|
||||
if (walletsCount == 0) {
|
||||
root.isVisible = false
|
||||
} else {
|
||||
if (totalBalance == null) {
|
||||
if (progressState != ProgressState.Loading) {
|
||||
root.isVisible = false
|
||||
}
|
||||
} else {
|
||||
root.isVisible = true
|
||||
// Skip changes when on refreshing state
|
||||
if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) {
|
||||
return@with
|
||||
}
|
||||
isGone = progressState == ProgressState.Done && walletsCount == 0
|
||||
|
||||
if (totalBalance.state == ProgressState.Loading) {
|
||||
veilBalance.veil()
|
||||
} else {
|
||||
veilBalance.unVeil()
|
||||
}
|
||||
tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error)
|
||||
|
||||
tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString(
|
||||
currencySymbol = totalBalance.fiatCurrency.symbol,
|
||||
)
|
||||
tvCurrencyName.text = totalBalance.fiatCurrency.code
|
||||
|
||||
tvCurrencyName.setOnClickListener {
|
||||
store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
|
||||
}
|
||||
}
|
||||
onChangeFiatCurrencyClick = {
|
||||
store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency)
|
||||
}
|
||||
status = totalBalance
|
||||
}
|
||||
|
||||
private fun handleErrorStates(
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class SingleWalletView : WalletView() {
|
|||
btnAddToken.hide()
|
||||
rvPendingTransaction.hide()
|
||||
pbLoadingUserTokens.hide()
|
||||
lCardTotalBalance.root.hide()
|
||||
lCardTotalBalance.hide()
|
||||
lSingleWalletBalance.root.hide()
|
||||
lWalletRescanWarning.root.hide()
|
||||
lWalletBackupWarning.root.hide()
|
||||
|
|
|
|||
|
|
@ -3,23 +3,24 @@ package com.tangem.tap.features.walletSelector.ui
|
|||
import com.tangem.tap.common.entities.FiatCurrency
|
||||
import com.tangem.tap.common.extensions.toFormattedFiatValue
|
||||
import com.tangem.tap.domain.model.TotalFiatBalance
|
||||
import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN
|
||||
import com.tangem.tap.features.walletSelector.redux.UserWalletModel
|
||||
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 List<UserWalletModel>.toUiModels(
|
||||
appCurrency: FiatCurrency,
|
||||
): Sequence<UserWalletItem> {
|
||||
return this.asSequence().map { userWalletModel ->
|
||||
with(userWalletModel) {
|
||||
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.Error -> UserWalletItem.Balance.Error(
|
||||
amount = fiatBalance.amount?.toFormattedFiatValue(appCurrency.symbol) ?: UNKNOWN_AMOUNT_SIGN,
|
||||
)
|
||||
is TotalFiatBalance.Loaded -> UserWalletItem.Balance.Loaded(
|
||||
amount = fiatBalance.amount.toFormattedFiatValue(appCurrency.symbol),
|
||||
)
|
||||
is TotalFiatBalance.Loading -> UserWalletItem.Balance.Loading
|
||||
}
|
||||
when (type) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -31,6 +30,8 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
|
|
@ -38,6 +39,7 @@ import com.tangem.core.ui.components.SpacerH2
|
|||
import com.tangem.core.ui.components.SpacerW6
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.common.compose.TangemTypography
|
||||
import com.tangem.tap.common.extensions.cardImageData
|
||||
import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem
|
||||
|
|
@ -137,23 +139,21 @@ private fun WalletCardImage(
|
|||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun RowScope.WalletInfo(
|
||||
wallet: UserWalletItem,
|
||||
isSelected: Boolean,
|
||||
) {
|
||||
private fun RowScope.WalletInfo(wallet: UserWalletItem, isSelected: Boolean) {
|
||||
Column(
|
||||
modifier = Modifier.weight(weight = .6f),
|
||||
modifier = Modifier.weight(weight = 2f),
|
||||
verticalArrangement = Arrangement.SpaceAround,
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = wallet.name,
|
||||
color = if (isSelected) TangemTheme.colors.text.accent else TangemTheme.colors.text.primary1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTypography.subtitle1,
|
||||
)
|
||||
SpacerH2()
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = when (wallet) {
|
||||
is MultiCurrencyUserWalletItem -> pluralStringResource(
|
||||
id = R.plurals.card_label_card_count,
|
||||
|
|
@ -162,20 +162,17 @@ private fun RowScope.WalletInfo(
|
|||
)
|
||||
is SingleCurrencyUserWalletItem -> wallet.tokenName
|
||||
},
|
||||
style = TangemTheme.typography.caption,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.caption,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.TokensInfo(
|
||||
isLocked: Boolean,
|
||||
balance: UserWalletItem.Balance,
|
||||
tokensCount: Int?,
|
||||
) {
|
||||
private fun RowScope.TokensInfo(isLocked: Boolean, balance: UserWalletItem.Balance, tokensCount: Int?) {
|
||||
Column(
|
||||
modifier = Modifier.weight(weight = .4f),
|
||||
modifier = Modifier.weight(weight = 3f),
|
||||
verticalArrangement = Arrangement.SpaceAround,
|
||||
horizontalAlignment = Alignment.End,
|
||||
) {
|
||||
|
|
@ -254,9 +251,11 @@ private fun LoadedTokensInfo(
|
|||
) {
|
||||
Text(
|
||||
text = balanceAmount,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.End,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
if (showWarning) {
|
||||
Icon(
|
||||
|
|
@ -371,4 +370,26 @@ private fun CheckedWalletMark(
|
|||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, heightDp = 72, showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewWalletItem() {
|
||||
TangemTheme {
|
||||
WalletItem(
|
||||
wallet = MultiCurrencyUserWalletItem(
|
||||
id = UserWalletId(value = null),
|
||||
name = "Tangem Card",
|
||||
imageUrl = "",
|
||||
balance = UserWalletItem.Balance.Loaded("141212121888 BTC"),
|
||||
isLocked = false,
|
||||
tokensCount = 2,
|
||||
cardsInWallet = 1,
|
||||
),
|
||||
isSelected = false,
|
||||
isChecked = false,
|
||||
onWalletClick = {},
|
||||
onWalletLongClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ import com.tangem.lib.crypto.models.transactions.SendTxResult
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TangemSigner
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdk
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -172,6 +171,10 @@ class TransactionManagerImpl(
|
|||
val error = result.error as? BlockchainSdkError ?: return SendTxResult.UnknownError()
|
||||
when (error) {
|
||||
is BlockchainSdkError.WrappedTangemError -> {
|
||||
val errorByCode = mapErrorByCode(error)
|
||||
if (errorByCode != null) {
|
||||
return errorByCode
|
||||
}
|
||||
val tangemSdkError = error.tangemError as? TangemSdkError ?: return SendTxResult.UnknownError()
|
||||
if (tangemSdkError is TangemSdkError.UserCancelled) return SendTxResult.UserCancelledError
|
||||
return SendTxResult.TangemSdkError(tangemSdkError.code, tangemSdkError.cause)
|
||||
|
|
@ -184,6 +187,17 @@ class TransactionManagerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
private fun mapErrorByCode(error: BlockchainSdkError.WrappedTangemError): SendTxResult? {
|
||||
return when (error.code) {
|
||||
USER_CANCELLED_ERROR_CODE -> {
|
||||
return SendTxResult.UserCancelledError
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun transactionSigner(walletManager: WalletManager): TransactionSigner {
|
||||
val actualCard = requireNotNull(appStateHolder.getActualCard()) { "no card found" }
|
||||
return TangemSigner(
|
||||
|
|
@ -191,7 +205,7 @@ class TransactionManagerImpl(
|
|||
tangemSdk = tangemSdk,
|
||||
initialMessage = Message(),
|
||||
) { signResponse ->
|
||||
store.dispatch(
|
||||
appStateHolder.mainStore?.dispatch(
|
||||
GlobalAction.UpdateWalletSignedHashes(
|
||||
walletSignedHashes = signResponse.totalSignedHashes,
|
||||
walletPublicKey = walletManager.wallet.publicKey.seedKey,
|
||||
|
|
@ -292,5 +306,6 @@ class TransactionManagerImpl(
|
|||
|
||||
companion object {
|
||||
private const val HEX_PREFIX = "0x"
|
||||
private const val USER_CANCELLED_ERROR_CODE = 50002
|
||||
}
|
||||
}
|
||||
|
|
@ -23,9 +23,12 @@ import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
|||
import com.tangem.tap.domain.model.builders.UserWalletIdBuilder
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletCurrenciesManager
|
||||
import kotlinx.coroutines.delay
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.tap.features.wallet.models.Currency as WalletCurrency
|
||||
|
||||
class UserWalletManagerImpl(
|
||||
private val appStateHolder: AppStateHolder,
|
||||
|
|
@ -106,13 +109,21 @@ class UserWalletManagerImpl(
|
|||
val card = requireNotNull(appStateHolder.getActualCard()) { "card not found" }
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(currency.networkId)) { "blockchain not found" }
|
||||
val blockchainNetwork = BlockchainNetwork(blockchain, card)
|
||||
val walletManager = getOrCreateBlockchain(blockchainNetwork, blockchain)
|
||||
if (currency is NonNativeToken &&
|
||||
!walletManager.cardTokens.contains(currency.toSdkToken())
|
||||
) {
|
||||
val action = addNonNativeTokenToWalletAction(currency, card, blockchain)
|
||||
val mainStore = requireNotNull(appStateHolder.mainStore) { "mainStore is null" }
|
||||
mainStore.dispatchOnMain(action)
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
||||
if (selectedUserWallet != null) {
|
||||
walletCurrenciesManager.addCurrencies(
|
||||
userWallet = selectedUserWallet,
|
||||
currenciesToAdd = listOf(currency.toWalletCurrency(blockchainNetwork)),
|
||||
)
|
||||
} else {
|
||||
val walletManager = getOrCreateBlockchain(blockchainNetwork, blockchain)
|
||||
if (currency is NonNativeToken && !walletManager.cardTokens.contains(currency.toSdkToken())) {
|
||||
val action = addNonNativeTokenToWalletAction(currency, card, blockchain)
|
||||
val mainStore = requireNotNull(appStateHolder.mainStore) { "mainStore is null" }
|
||||
mainStore.dispatchOnMain(action)
|
||||
delay(DELAY_UPDATE_WALLET)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -238,6 +249,11 @@ class UserWalletManagerImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override fun refreshWallet() {
|
||||
// workaround, should update wallet after transaction
|
||||
appStateHolder.mainStore?.dispatchOnMain(WalletAction.LoadData.Refresh)
|
||||
}
|
||||
|
||||
private fun createDerivationParams(derivationStyle: DerivationStyle?): DerivationParams? {
|
||||
// todo clarify if its need to add Custom
|
||||
return derivationStyle?.let { DerivationParams.Default(derivationStyle) }
|
||||
|
|
@ -265,4 +281,18 @@ private fun NonNativeToken.toSdkToken(): Token {
|
|||
contractAddress = this.contractAddress,
|
||||
decimals = this.decimalCount,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Currency.toWalletCurrency(network: BlockchainNetwork): WalletCurrency {
|
||||
return when (this) {
|
||||
is NativeToken -> WalletCurrency.Blockchain(
|
||||
blockchain = network.blockchain,
|
||||
derivationPath = network.derivationPath,
|
||||
)
|
||||
is NonNativeToken -> WalletCurrency.Token(
|
||||
token = this.toSdkToken(),
|
||||
blockchain = network.blockchain,
|
||||
derivationPath = network.derivationPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:text="@string/swapping_swap"
|
||||
android:text="@string/swapping_swap_action"
|
||||
android:textColor="@color/darkGray3"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false"
|
||||
android:paddingBottom="92dp">
|
||||
|
||||
<ImageView
|
||||
|
|
@ -136,9 +137,8 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
<com.tangem.tap.features.wallet.ui.view.TotalBalanceCard
|
||||
android:id="@+id/l_card_total_balance"
|
||||
layout="@layout/layout_card_total_balance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
<?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">
|
||||
|
||||
<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/text_tertiary"
|
||||
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="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_processing"
|
||||
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">
|
||||
|
||||
<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/text_primary_1"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="visible"
|
||||
tools:text="22 325.40 $"
|
||||
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/main_processing_full_amount"
|
||||
android:textColor="@color/text_secondary"
|
||||
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/text_tertiary"
|
||||
android:textSize="16sp"
|
||||
app:drawableEndCompat="@drawable/ic_arrow_angle_down"
|
||||
app:drawableTint="@color/icon_informative"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="USD" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_swap"
|
||||
style="@style/TapPrimaryIconButton"
|
||||
android:text="@string/swapping_swap"
|
||||
android:text="@string/swapping_swap_action"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_arrows_up_down"
|
||||
tools:visibility="gone" />
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ import com.tangem.datasource.config.models.ConfigModel
|
|||
import com.tangem.datasource.config.models.ConfigValueModel
|
||||
import com.tangem.datasource.config.models.FeatureModel
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
||||
|
||||
override var config: Config = Config()
|
||||
|
|
|
|||
|
|
@ -351,8 +351,9 @@
|
|||
<string name="swapping_success_view_explorer_button_title">View in Explorer</string>
|
||||
<string name="swapping_success_view_title">In progress</string>
|
||||
<string name="swapping_swap">Swap</string>
|
||||
<string name="swapping_swap_action">Swap</string>
|
||||
<string name="swapping_swap_of_to">Swap of %s to</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">Other tokens</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_list_your_tokens">Your tokens</string>
|
||||
|
|
|
|||
|
|
@ -351,8 +351,9 @@
|
|||
<string name="swapping_success_view_explorer_button_title">View in Explorer</string>
|
||||
<string name="swapping_success_view_title">In progress</string>
|
||||
<string name="swapping_swap">Swap</string>
|
||||
<string name="swapping_swap_action">Swap</string>
|
||||
<string name="swapping_swap_of_to">Swap of %s to</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">Other tokens</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_list_your_tokens">Your tokens</string>
|
||||
|
|
|
|||
|
|
@ -351,8 +351,9 @@
|
|||
<string name="swapping_success_view_explorer_button_title">View in Explorer</string>
|
||||
<string name="swapping_success_view_title">In progress</string>
|
||||
<string name="swapping_swap">Swap</string>
|
||||
<string name="swapping_swap_action">Swap</string>
|
||||
<string name="swapping_swap_of_to">Swap of %s to</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">Other tokens</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_list_your_tokens">Your tokens</string>
|
||||
|
|
|
|||
|
|
@ -351,8 +351,9 @@
|
|||
<string name="swapping_success_view_explorer_button_title">Открыть в обозревателе</string>
|
||||
<string name="swapping_success_view_title">В процессе</string>
|
||||
<string name="swapping_swap">Обмен</string>
|
||||
<string name="swapping_swap_action">Обменять</string>
|
||||
<string name="swapping_swap_of_to">Обмен %s на</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Кроме того, котировки включают комиссию Tangem в размере %s. Это помогает нам предоставлять первоклассный продукт.</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Котировки включают дополнительную комиссию Tangem в размере %s. Это помогает нам предоставлять первоклассный продукт.</string>
|
||||
<string name="swapping_token_list_other_tokens">Другие токены</string>
|
||||
<string name="swapping_token_list_title">Выберите токен</string>
|
||||
<string name="swapping_token_list_your_tokens">Ваши токены</string>
|
||||
|
|
|
|||
|
|
@ -351,8 +351,9 @@
|
|||
<string name="swapping_success_view_explorer_button_title">在瀏覽器中查看</string>
|
||||
<string name="swapping_success_view_title">進行中</string>
|
||||
<string name="swapping_swap">交換</string>
|
||||
<string name="swapping_swap_action">Swap</string>
|
||||
<string name="swapping_swap_of_to">交易 %s 至</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">其他代幣</string>
|
||||
<string name="swapping_token_list_title">選擇代幣</string>
|
||||
<string name="swapping_token_list_your_tokens">您的代幣</string>
|
||||
|
|
|
|||
|
|
@ -351,8 +351,9 @@
|
|||
<string name="swapping_success_view_explorer_button_title">View in Explorer</string>
|
||||
<string name="swapping_success_view_title">In progress</string>
|
||||
<string name="swapping_swap">Swap</string>
|
||||
<string name="swapping_swap_action">Swap</string>
|
||||
<string name="swapping_swap_of_to">Swap of %s to</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">Other tokens</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_list_your_tokens">Your tokens</string>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -31,11 +30,14 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
// region TextButton
|
||||
/**
|
||||
* [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=97%3A103&t=TmfD6UBHPg9uYfev-4)
|
||||
* */
|
||||
|
|
@ -99,7 +101,9 @@ fun WarningTextButton(
|
|||
size = TangemButtonSize.Text,
|
||||
)
|
||||
}
|
||||
// endregion TextButton
|
||||
|
||||
// region PrimaryButton
|
||||
@Composable
|
||||
fun PrimaryButton(
|
||||
text: String,
|
||||
|
|
@ -164,7 +168,9 @@ fun PrimaryButtonIconLeft(
|
|||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
// endregion PrimaryButton
|
||||
|
||||
// region SecondaryButton
|
||||
@Composable
|
||||
fun SecondaryButton(
|
||||
text: String,
|
||||
|
|
@ -229,6 +235,29 @@ fun SecondaryButtonIconLeft(
|
|||
showProgress = showProgress,
|
||||
)
|
||||
}
|
||||
// endregion SecondaryButton
|
||||
|
||||
// region Other
|
||||
@Composable
|
||||
fun SelectorButton(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
textStyle = TangemTheme.typography.subtitle2,
|
||||
icon = TangemButtonIcon.Right(painterResource(id = R.drawable.ic_chevron_24)),
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.selectorButtonColors,
|
||||
showProgress = false,
|
||||
enabled = enabled,
|
||||
size = TangemButtonSize.Selector,
|
||||
)
|
||||
}
|
||||
// endregion Other
|
||||
|
||||
// region Defaults
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -243,12 +272,12 @@ private fun TangemButton(
|
|||
modifier: Modifier = Modifier,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
elevation: ButtonElevation = TangemButtonsDefaults.elevation,
|
||||
textStyle: TextStyle = TangemTheme.typography.button,
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier
|
||||
.width(IntrinsicSize.Min)
|
||||
.height(IntrinsicSize.Min)
|
||||
.heightIn(size.toHeightDp()),
|
||||
.heightIn(min = size.toHeightDp()),
|
||||
onClick = {
|
||||
if (!showProgress) {
|
||||
onClick()
|
||||
|
|
@ -261,19 +290,24 @@ private fun TangemButton(
|
|||
) {
|
||||
ButtonContent(
|
||||
text = text,
|
||||
textStyle = textStyle,
|
||||
buttonIcon = icon,
|
||||
colors = colors,
|
||||
showProgress = showProgress,
|
||||
enabled = enabled,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun ButtonContent(
|
||||
text: String,
|
||||
textStyle: TextStyle,
|
||||
buttonIcon: TangemButtonIcon,
|
||||
colors: ButtonColors,
|
||||
size: TangemButtonSize,
|
||||
enabled: Boolean,
|
||||
showProgress: Boolean,
|
||||
) {
|
||||
|
|
@ -297,18 +331,19 @@ private fun ButtonContent(
|
|||
)
|
||||
}
|
||||
} else {
|
||||
Row {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(size.toIconPadding()),
|
||||
) {
|
||||
if (buttonIcon is TangemButtonIcon.Left) {
|
||||
icon(buttonIcon.painter)
|
||||
Spacer(modifier = Modifier.width(TangemTheme.dimens.size8))
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
style = TangemTheme.typography.button,
|
||||
style = textStyle,
|
||||
color = colors.contentColor(enabled = enabled).value,
|
||||
)
|
||||
if (buttonIcon is TangemButtonIcon.Right) {
|
||||
Spacer(modifier = Modifier.width(TangemTheme.dimens.size8))
|
||||
icon(buttonIcon.painter)
|
||||
}
|
||||
}
|
||||
|
|
@ -332,18 +367,28 @@ sealed interface TangemButtonIcon {
|
|||
enum class TangemButtonSize {
|
||||
Default,
|
||||
Text,
|
||||
Selector,
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemButtonSize.toHeightDp(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.size48
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.size40
|
||||
TangemButtonSize.Selector -> TangemTheme.dimens.size24
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemButtonSize.toShape(): Shape = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium
|
||||
TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall
|
||||
TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemButtonSize.toIconPadding(): Dp = when (this) {
|
||||
TangemButtonSize.Default -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Text -> TangemTheme.dimens.spacing8
|
||||
TangemButtonSize.Selector -> 0.dp
|
||||
}
|
||||
|
||||
object TangemButtonsDefaults {
|
||||
|
|
@ -385,6 +430,14 @@ object TangemButtonsDefaults {
|
|||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
||||
val selectorButtonColors: ButtonColors
|
||||
@Composable get() = TangemButtonColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
contentColor = TangemTheme.colors.text.tertiary,
|
||||
disabledBackgroundColor = Color.Transparent,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
@ -595,6 +648,11 @@ private fun TextButtonSample(
|
|||
text = "Delete",
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
|
||||
SelectorButton(
|
||||
text = "USD",
|
||||
onClick = { /* no-op */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ fun BigDecimal.toFiatString(
|
|||
val formatter = NumberFormat.getInstance(Locale.getDefault()) as? DecimalFormat
|
||||
val df = formatter?.apply {
|
||||
maximumFractionDigits = 2
|
||||
minimumFractionDigits = 0
|
||||
minimumFractionDigits = 2
|
||||
isGroupingUsed = true
|
||||
this.roundingMode = roundingMode
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
|
||||
// replace tokens in wallet tokens list with loaded same
|
||||
val loadedOnWalletsMap = mutableSetOf<String>()
|
||||
val tokensInWallet = userWalletManager.getUserTokens(networkId, false)
|
||||
val tokensInWallet = userWalletManager.getUserTokens(networkId = networkId, isExcludeCustom = true)
|
||||
.filter { it.symbol != initialCurrency.symbol }
|
||||
.map { token ->
|
||||
allLoadedTokens.firstOrNull { it.symbol == token.symbol }?.let {
|
||||
|
|
@ -68,17 +68,17 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
.mapValues { SwapAmount(it.value.value, it.value.decimals) }
|
||||
val appCurrency = userWalletManager.getUserAppCurrency()
|
||||
val rates = repository.getRates(appCurrency.code, tokensInWallet.map { it.id })
|
||||
cache.cacheLoadedTokens(loadedTokens)
|
||||
cache.cacheBalances(tokensBalance)
|
||||
cache.cacheInWalletTokens(tokensInWallet)
|
||||
cache.cacheLoadedTokens(loadedTokens.map { TokenWithBalance(it) })
|
||||
cache.cacheInWalletTokens(getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency))
|
||||
return TokensDataState(
|
||||
preselectTokens = PreselectTokens(
|
||||
fromToken = initialCurrency,
|
||||
toToken = selectToToken(initialCurrency, tokensInWallet, loadedTokens),
|
||||
),
|
||||
foundTokensState = FoundTokensState(
|
||||
tokensInWallet = getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency),
|
||||
loadedTokens = loadedTokens.map { TokenWithBalance(it) },
|
||||
tokensInWallet = cache.getInWalletTokens(),
|
||||
loadedTokens = cache.getLoadedTokens(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -87,28 +87,25 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
val searchQueryLowerCase = searchQuery.lowercase()
|
||||
val tokensInWallet = cache.getInWalletTokens()
|
||||
.filter {
|
||||
it.name.lowercase().contains(searchQueryLowerCase) ||
|
||||
it.symbol.lowercase().contains(searchQueryLowerCase)
|
||||
it.token.name.lowercase().contains(searchQueryLowerCase) ||
|
||||
it.token.symbol.lowercase().contains(searchQueryLowerCase)
|
||||
}
|
||||
val loadedTokens = cache.getLoadedTokens()
|
||||
.filter {
|
||||
it.name.lowercase().contains(searchQueryLowerCase) ||
|
||||
it.symbol.lowercase().contains(searchQueryLowerCase)
|
||||
it.token.name.lowercase().contains(searchQueryLowerCase) ||
|
||||
it.token.symbol.lowercase().contains(searchQueryLowerCase)
|
||||
}
|
||||
val tokensBalance = userWalletManager.getCurrentWalletTokensBalance(networkId, emptyList())
|
||||
.mapValues { SwapAmount(it.value.value, it.value.decimals) }
|
||||
val appCurrency = userWalletManager.getUserAppCurrency()
|
||||
val rates = repository.getRates(appCurrency.code, tokensInWallet.map { it.id })
|
||||
return FoundTokensState(
|
||||
tokensInWallet = getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency),
|
||||
loadedTokens = loadedTokens.map { TokenWithBalance(it) },
|
||||
tokensInWallet = tokensInWallet,
|
||||
loadedTokens = loadedTokens,
|
||||
)
|
||||
}
|
||||
|
||||
override fun findTokenById(id: String): Currency? {
|
||||
val tokensInWallet = cache.getInWalletTokens()
|
||||
val loadedTokens = cache.getLoadedTokens()
|
||||
return tokensInWallet.firstOrNull { it.id == id } ?: loadedTokens.firstOrNull { it.id == id }
|
||||
return tokensInWallet.firstOrNull { it.token.id == id }?.token
|
||||
?: loadedTokens.firstOrNull { it.token.id == id }?.token
|
||||
}
|
||||
|
||||
override suspend fun givePermissionToSwap(
|
||||
|
|
@ -218,6 +215,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
return when (result) {
|
||||
is SendTxResult.Success -> {
|
||||
userWalletManager.addToken(cryptoCurrencyConverter.convert(currencyToGet))
|
||||
userWalletManager.refreshWallet()
|
||||
TxState.TxSent(
|
||||
fromAmount = amountFormatter.formatSwapAmountToUI(swapData.fromTokenAmount, currencyToSend.symbol),
|
||||
toAmount = amountFormatter.formatSwapAmountToUI(swapData.toTokenAmount, currencyToGet.symbol),
|
||||
|
|
@ -283,10 +281,13 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
TokenWithBalance(
|
||||
token = it,
|
||||
tokenBalanceData = TokenBalanceData(
|
||||
amount = balance?.let { amount -> amountFormatter.formatSwapAmountToUI(amount, "") },
|
||||
amount = balance?.let { amount ->
|
||||
amountFormatter.formatSwapAmountToUI(amount, it.symbol)
|
||||
},
|
||||
amountEquivalent = balance?.value?.toFiatString(
|
||||
rates[it.id]?.toBigDecimal() ?: BigDecimal.ZERO,
|
||||
appCurrency.symbol,
|
||||
rateValue = rates[it.id]?.toBigDecimal() ?: BigDecimal.ZERO,
|
||||
fiatCurrencyName = appCurrency.symbol,
|
||||
formatWithSpaces = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -331,7 +332,11 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
return SwapState.EmptyAmountState(
|
||||
fromTokenWalletBalance = fromTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }.orEmpty(),
|
||||
toTokenWalletBalance = toTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }.orEmpty(),
|
||||
zeroAmountEquivalent = BigDecimal.ZERO.toFiatString(BigDecimal.ONE, appCurrency.symbol),
|
||||
zeroAmountEquivalent = BigDecimal.ZERO.toFiatString(
|
||||
rateValue = BigDecimal.ONE,
|
||||
fiatCurrencyName = appCurrency.symbol,
|
||||
formatWithSpaces = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -402,7 +407,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
val nativeToken = userWalletManager.getNativeTokenForNetwork(networkId)
|
||||
val rates = repository.getRates(appCurrency.code, listOf(fromTokenId, toTokenId, nativeToken.id))
|
||||
return rates[nativeToken.id]?.toBigDecimal()?.let { rate ->
|
||||
" (${fee.toFiatString(rate, appCurrency.symbol)})"
|
||||
" (${fee.toFiatString(rate, appCurrency.symbol, true)})"
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
|
|
@ -482,8 +487,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
tokenWalletBalance = fromTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }
|
||||
?: ZERO_BALANCE,
|
||||
tokenFiatBalance = fromTokenAmount.value.toFiatString(
|
||||
rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO,
|
||||
appCurrency.symbol,
|
||||
rateValue = rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO,
|
||||
fiatCurrencyName = appCurrency.symbol,
|
||||
formatWithSpaces = true,
|
||||
),
|
||||
),
|
||||
toTokenInfo = TokenSwapInfo(
|
||||
|
|
@ -492,8 +498,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
tokenWalletBalance = toTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }
|
||||
?: ZERO_BALANCE,
|
||||
tokenFiatBalance = toTokenAmount.value.toFiatString(
|
||||
rates[toToken.id]?.toBigDecimal() ?: BigDecimal.ZERO,
|
||||
appCurrency.symbol,
|
||||
rateValue = rates[toToken.id]?.toBigDecimal() ?: BigDecimal.ZERO,
|
||||
fiatCurrencyName = appCurrency.symbol,
|
||||
formatWithSpaces = true,
|
||||
),
|
||||
),
|
||||
fee = formattedFee,
|
||||
|
|
@ -546,12 +553,15 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun syncWalletBalanceForTokens(networkId: String, tokens: List<Currency>) {
|
||||
val tokensBalance =
|
||||
userWalletManager.getCurrentWalletTokensBalance(
|
||||
networkId = networkId,
|
||||
extraTokens = tokens.map { cryptoCurrencyConverter.convert(it) },
|
||||
)
|
||||
cache.cacheBalances(tokensBalance.mapValues { SwapAmount(it.value.value, it.value.decimals) })
|
||||
val tokensToSync = tokens.filter { cache.getBalanceForToken(it.symbol) == null }
|
||||
if (tokensToSync.isNotEmpty()) {
|
||||
val tokensBalance =
|
||||
userWalletManager.getCurrentWalletTokensBalance(
|
||||
networkId = networkId,
|
||||
extraTokens = tokensToSync.map { cryptoCurrencyConverter.convert(it) },
|
||||
)
|
||||
cache.cacheBalances(tokensBalance.mapValues { SwapAmount(it.value.value, it.value.decimals) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBalanceEnough(fromToken: Currency, amount: SwapAmount, fee: BigDecimal?): Boolean {
|
||||
|
|
@ -618,9 +628,9 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
toTokenAmount: BigDecimal,
|
||||
toRate: Double,
|
||||
): Float {
|
||||
val toTokenFiatValue = toTokenAmount.multiply(toRate.toBigDecimal()).setScale(2, RoundingMode.HALF_UP)
|
||||
val fromTokenFiatValue = fromTokenAmount.multiply(fromRate.toBigDecimal()).setScale(2, RoundingMode.HALF_UP)
|
||||
return (BigDecimal.ONE - toTokenFiatValue / fromTokenFiatValue).toFloat()
|
||||
val toTokenFiatValue = toTokenAmount.multiply(toRate.toBigDecimal())
|
||||
val fromTokenFiatValue = fromTokenAmount.multiply(fromRate.toBigDecimal())
|
||||
return (BigDecimal.ONE - toTokenFiatValue.divide(fromTokenFiatValue, 2, RoundingMode.HALF_UP)).toFloat()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -2,18 +2,19 @@ package com.tangem.feature.swap.domain.cache
|
|||
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.Currency
|
||||
import com.tangem.feature.swap.domain.models.ui.TokenWithBalance
|
||||
import java.math.BigDecimal
|
||||
|
||||
interface SwapDataCache {
|
||||
|
||||
fun cacheAvailableToSwapTokens(networkId: String, tokens: List<Currency>)
|
||||
fun cacheInWalletTokens(tokens: List<Currency>)
|
||||
fun cacheLoadedTokens(tokens: List<Currency>)
|
||||
fun cacheInWalletTokens(tokens: List<TokenWithBalance>)
|
||||
fun cacheLoadedTokens(tokens: List<TokenWithBalance>)
|
||||
fun cacheBalances(balances: Map<String, SwapAmount>)
|
||||
fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String)
|
||||
fun getAvailableTokens(networkId: String): List<Currency>
|
||||
fun getInWalletTokens(): List<Currency>
|
||||
fun getLoadedTokens(): List<Currency>
|
||||
fun getInWalletTokens(): List<TokenWithBalance>
|
||||
fun getLoadedTokens(): List<TokenWithBalance>
|
||||
fun getBalanceForToken(symbol: String): SwapAmount?
|
||||
fun getLastFeeForNetwork(networkId: String): BigDecimal?
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.swap.domain.cache
|
|||
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.Currency
|
||||
import com.tangem.feature.swap.domain.models.ui.TokenWithBalance
|
||||
import java.math.BigDecimal
|
||||
|
||||
class SwapDataCacheImpl : SwapDataCache {
|
||||
|
|
@ -9,28 +10,28 @@ class SwapDataCacheImpl : SwapDataCache {
|
|||
private val availableTokensForNetwork: MutableMap<String, List<Currency>> = mutableMapOf()
|
||||
private val feesForNetworks: MutableMap<String, BigDecimal> = mutableMapOf()
|
||||
private val tokensBalances: MutableMap<String, SwapAmount> = mutableMapOf()
|
||||
private val lastInWalletTokens = mutableListOf<Currency>()
|
||||
private val lastLoadedTokens = mutableListOf<Currency>()
|
||||
private val lastInWalletTokens = mutableListOf<TokenWithBalance>()
|
||||
private val lastLoadedTokens = mutableListOf<TokenWithBalance>()
|
||||
|
||||
override fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String) {
|
||||
feesForNetworks[networkId] = fee
|
||||
}
|
||||
|
||||
override fun cacheInWalletTokens(tokens: List<Currency>) {
|
||||
override fun cacheInWalletTokens(tokens: List<TokenWithBalance>) {
|
||||
lastInWalletTokens.clear()
|
||||
lastInWalletTokens.addAll(tokens)
|
||||
}
|
||||
|
||||
override fun cacheLoadedTokens(tokens: List<Currency>) {
|
||||
override fun cacheLoadedTokens(tokens: List<TokenWithBalance>) {
|
||||
lastLoadedTokens.clear()
|
||||
lastLoadedTokens.addAll(tokens)
|
||||
}
|
||||
|
||||
override fun getInWalletTokens(): List<Currency> {
|
||||
override fun getInWalletTokens(): List<TokenWithBalance> {
|
||||
return lastInWalletTokens
|
||||
}
|
||||
|
||||
override fun getLoadedTokens(): List<Currency> {
|
||||
override fun getLoadedTokens(): List<TokenWithBalance> {
|
||||
return lastLoadedTokens
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import android.view.ViewGroup
|
|||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.feature.swap.router.CustomTabsManager
|
||||
|
|
@ -26,7 +25,6 @@ class SwapFragment : Fragment() {
|
|||
private val viewModel by viewModels<SwapViewModel>()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
|
||||
viewModel.setRouter(
|
||||
SwapRouter(
|
||||
fragmentManager = WeakReference(parentFragmentManager),
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ internal class StateBuilder(val actions: UiActions) {
|
|||
toToken: Currency,
|
||||
mainTokenId: String,
|
||||
): SwapStateHolder {
|
||||
val canSelectSendToken = mainTokenId != fromToken.id
|
||||
val canSelectReceiveToken = mainTokenId != toToken.id
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
|
||||
|
|
@ -88,8 +90,8 @@ internal class StateBuilder(val actions: UiActions) {
|
|||
tokenCurrency = fromToken.symbol,
|
||||
coinId = fromToken.id,
|
||||
isNotNativeToken = fromToken.isNonNative(),
|
||||
canSelectAnotherToken = mainTokenId != fromToken.id,
|
||||
balance = "",
|
||||
canSelectAnotherToken = canSelectSendToken,
|
||||
balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "",
|
||||
),
|
||||
receiveCardData = SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
|
|
@ -99,8 +101,8 @@ internal class StateBuilder(val actions: UiActions) {
|
|||
tokenCurrency = toToken.symbol,
|
||||
coinId = toToken.id,
|
||||
isNotNativeToken = toToken.isNonNative(),
|
||||
canSelectAnotherToken = mainTokenId != toToken.id,
|
||||
balance = "",
|
||||
canSelectAnotherToken = canSelectReceiveToken,
|
||||
balance = if (!canSelectReceiveToken) uiStateHolder.receiveCardData.balance else "",
|
||||
),
|
||||
fee = FeeState.Loading,
|
||||
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
|
||||
|
|
@ -198,6 +200,7 @@ internal class StateBuilder(val actions: UiActions) {
|
|||
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
||||
balance = emptyAmountState.toTokenWalletBalance,
|
||||
),
|
||||
warnings = emptyList(),
|
||||
fee = FeeState.Empty,
|
||||
swapButton = SwapButton(
|
||||
enabled = false,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.swap.ui
|
|||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ModalBottomSheetLayout
|
||||
|
|
@ -39,6 +40,7 @@ internal fun SwapScreen(stateHolder: SwapStateHolder) {
|
|||
|
||||
TangemTheme {
|
||||
ModalBottomSheetLayout(
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
sheetContent = {
|
||||
if (stateHolder.permissionState is SwapPermissionState.ReadyForRequest) {
|
||||
SwapPermissionBottomSheetContent(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -37,6 +38,7 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.PrimaryButtonIconRight
|
||||
import com.tangem.core.ui.components.RefreshableWaringCard
|
||||
import com.tangem.core.ui.components.SimpleOkDialog
|
||||
import com.tangem.core.ui.components.SmallInfoCard
|
||||
import com.tangem.core.ui.components.SmallInfoCardWithDisclaimer
|
||||
import com.tangem.core.ui.components.SmallInfoCardWithWarning
|
||||
import com.tangem.core.ui.components.WarningCard
|
||||
|
|
@ -120,7 +122,9 @@ internal fun SwapScreenContent(state: SwapStateHolder, onPermissionWarningClick:
|
|||
|
||||
AnimatedVisibility(
|
||||
visible = keyboard is Keyboard.Opened,
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.align(Alignment.BottomCenter),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.send_max_amount_label),
|
||||
|
|
@ -277,7 +281,9 @@ private fun FeeItem(feeState: FeeState, currency: String) {
|
|||
),
|
||||
)
|
||||
}
|
||||
is FeeState.Empty -> {}
|
||||
is FeeState.Empty -> {
|
||||
SmallInfoCard(startText = titleString, endText = "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
|
|
@ -51,6 +52,9 @@ fun SwapSelectTokenScreen(
|
|||
) {
|
||||
TangemTheme {
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.systemBarsPadding()
|
||||
.background(color = TangemTheme.colors.background.secondary),
|
||||
content = { padding ->
|
||||
ListOfTokens(state = state, Modifier.padding(padding))
|
||||
},
|
||||
|
|
@ -66,7 +70,6 @@ fun SwapSelectTokenScreen(
|
|||
icon = painterResource(id = getActiveIconRes(state.network.blockchainId)),
|
||||
)
|
||||
},
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -178,13 +181,13 @@ private fun TokenItem(token: TokenToSelect, network: Network, onTokenClick: () -
|
|||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Text(
|
||||
text = token.addedTokenBalanceData.amount ?: "",
|
||||
text = token.addedTokenBalanceData.amountEquivalent.orEmpty(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
SpacerW2()
|
||||
Text(
|
||||
text = token.addedTokenBalanceData.amountEquivalent ?: "",
|
||||
text = token.addedTokenBalanceData.amount.orEmpty(),
|
||||
style = TangemTheme.typography.caption,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -19,6 +20,7 @@ import com.tangem.feature.swap.presentation.R
|
|||
fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) {
|
||||
TangemTheme {
|
||||
Scaffold(
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
content = { padding ->
|
||||
ResultScreenContent(
|
||||
resultMessage = makeSuccessMessage(
|
||||
|
|
|
|||
|
|
@ -251,6 +251,9 @@ internal class SwapViewModel @Inject constructor(
|
|||
analyticsEventHandler.send(SwapEvents.SwapInProgressScreen)
|
||||
swapRouter.openScreen(SwapNavScreen.Success)
|
||||
}
|
||||
is TxState.UserCancelled -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
}
|
||||
else -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
uiState = stateBuilder.createErrorTransaction(uiState, it) {
|
||||
|
|
@ -282,6 +285,9 @@ internal class SwapViewModel @Inject constructor(
|
|||
is TxState.TxSent -> {
|
||||
uiState = stateBuilder.loadingPermissionState(uiState)
|
||||
}
|
||||
is TxState.UserCancelled -> {
|
||||
/* no-op */
|
||||
}
|
||||
else -> {
|
||||
uiState = stateBuilder.createErrorTransaction(uiState, it) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ interface UserWalletManager {
|
|||
@Throws(IllegalStateException::class)
|
||||
suspend fun addToken(currency: Currency)
|
||||
|
||||
fun refreshWallet()
|
||||
|
||||
/**
|
||||
* Returns wallet public address for token
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue