diff --git a/app/build.gradle b/app/build.gradle index bbbdd40201..75d49cd47e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -79,7 +79,8 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-65' + implementation 'com.tangem:blockchain:develop-66' +// implementation 'com.tangem:blockchain:0.0.1' implementation 'com.tangem.tangem-sdk-kotlin:core:develop-140' implementation 'com.tangem.tangem-sdk-kotlin:android:develop-140' diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt b/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt index 63c62dfdc2..57e88e4be2 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Coroutine.kt @@ -7,10 +7,6 @@ import kotlinx.coroutines.withContext /** [REDACTED_AUTHOR] */ -suspend fun withMainContext(block: suspend CoroutineScope.() -> T) { - withContext(Dispatchers.Main, block) -} +suspend fun withMainContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatchers.Main, block) -suspend fun withIOContext(block: suspend CoroutineScope.() -> T) { - withContext(Dispatchers.IO, block) -} \ No newline at end of file +suspend fun withIOContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatchers.IO, block) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 678caeb9f0..f4b2863aac 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -18,6 +18,10 @@ fun Store<*>.dispatchOnMain(action: Action) { } } +suspend fun dispatchOnMain(vararg actions: Action) { + withMainContext { actions.forEach { store.dispatch(it) } } +} + suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) { store.state.globalState.tapWalletManager.onCardScanned(scanResponse) } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/compose/Color.kt b/app/src/main/java/com/tangem/tap/common/extensions/compose/Color.kt new file mode 100644 index 0000000000..d78a72ead5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/compose/Color.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.common.extensions.compose + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.core.graphics.alpha +import androidx.core.graphics.blue +import androidx.core.graphics.green +import androidx.core.graphics.red + +/** +[REDACTED_AUTHOR] + */ +fun Color.argb(): Int { + val argb = this.toArgb() + return android.graphics.Color.argb(argb.alpha, argb.red, argb.green, argb.blue) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index acbf6e3650..488ab3940e 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -4,10 +4,10 @@ import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.common.* import com.tangem.common.services.Result import com.tangem.tap.common.ThrottlerWithValues -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.extensions.withMainContext import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.currenciesRepository @@ -25,11 +25,8 @@ import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.network.coinmarketcap.CoinMarketCapService -import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigDecimal @@ -49,32 +46,30 @@ class TapWalletManager { suspend fun loadWalletData(walletManager: WalletManager) { val blockchain = walletManager.wallet.blockchain val result = if (walletManagersThrottler.isStillThrottled(blockchain)) { - delay(500) walletManagersThrottler.geValue(blockchain)!! } else { updateThrottlingForWalletManager(walletManager) } - handleUpdateWalletResult(result, walletManager) - } - - suspend fun updateWallet(walletManager: WalletManager, force: Boolean) { - val result = if (force) { - updateThrottlingForWalletManager(walletManager) - } else { - val blockchain = walletManager.wallet.blockchain - if (walletManagersThrottler.isStillThrottled(blockchain)) { - delay(500) - walletManagersThrottler.geValue(blockchain)!! - } else { - updateThrottlingForWalletManager(walletManager) + when (result) { + is Result.Success -> { + checkForRentWarning(walletManager) + dispatchOnMain(WalletAction.LoadWallet.Success(result.data)) } - } - withContext(Dispatchers.Main) { - when (result) { - is Result.Success -> - store.dispatch(WalletAction.UpdateWallet.Success(result.data)) - is Result.Failure -> - store.dispatch(WalletAction.UpdateWallet.Failure(result.error.localizedMessage)) + is Result.Failure -> { + when (result.error) { + is TapError.WalletManagerUpdate.NoAccountError -> { + dispatchOnMain(WalletAction.LoadWallet.NoAccount( + walletManager.wallet, + (result.error as TapError.WalletManagerUpdate.NoAccountError).customMessage + )) + } + else -> { + dispatchOnMain(WalletAction.LoadWallet.Failure( + walletManager.wallet, + result.error.localizedMessage + )) + } + } } } } @@ -115,10 +110,10 @@ class TapWalletManager { suspend fun onCardScanned(data: ScanResponse) { walletManagersThrottler.clear() // fiatRatesThrottler.clear() - store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card) + store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data) updateConfigManager(data) - withContext(Dispatchers.Main) { + withMainContext { store.dispatch(WalletAction.ResetState) store.dispatch(GlobalAction.SaveScanNoteResponse(data)) store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) @@ -144,42 +139,43 @@ class TapWalletManager { } suspend fun loadData(data: ScanResponse) { - withContext(Dispatchers.Main) { - store.dispatch(WalletAction.LoadCardInfo(data.card)) - getActionIfUnknownBlockchainOrEmptyWallet(data)?.let { - store.dispatch(it) - return@withContext - } - - val blockchain = data.getBlockchain() - val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) - - if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { - val primaryToken = data.getPrimaryToken() - - store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain)) - if (primaryToken != null) { - primaryWalletManager.addToken(primaryToken) - store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken)) - } - if (data.card.isMultiwalletAllowed) { - loadMultiWalletData(data, blockchain, primaryWalletManager) - } else { - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) - store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain))) - } - - } else { - if (data.card.isMultiwalletAllowed) { - loadMultiWalletData(data, blockchain, null) - } - } - store.dispatch(WalletAction.LoadWallet()) - store.dispatch(WalletAction.LoadFiatRate()) + dispatchOnMain(WalletAction.LoadCardInfo(data.card)) + getActionIfUnknownBlockchainOrEmptyWallet(data)?.let { + dispatchOnMain(it) + return } + + val blockchain = data.getBlockchain() + val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data) + + if (blockchain != Blockchain.Unknown && primaryWalletManager != null) { + val primaryToken = data.getPrimaryToken() + + dispatchOnMain(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain)) + if (primaryToken != null) { + primaryWalletManager.addToken(primaryToken) + dispatchOnMain(WalletAction.MultiWallet.SetPrimaryToken(primaryToken)) + } + if (data.card.isMultiwalletAllowed) { + loadMultiWalletData(data, blockchain, primaryWalletManager) + } else { + dispatchOnMain( + WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager), + WalletAction.MultiWallet.AddBlockchains(listOf(blockchain)) + ) + } + } else { + if (data.card.isMultiwalletAllowed) { + loadMultiWalletData(data, blockchain, null) + } + } + dispatchOnMain( + WalletAction.LoadWallet(), + WalletAction.LoadFiatRate() + ) } - private fun loadMultiWalletData( + private suspend fun loadMultiWalletData( scanResponse: ScanResponse, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager? ) { val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList() @@ -187,25 +183,31 @@ class TapWalletManager { if (savedCurrencies == null) { if (primaryBlockchain != null && primaryWalletManager != null) { - store.dispatch(WalletAction.MultiWallet.SaveCurrencies( - CardCurrencies( - blockchains = listOf(primaryBlockchain), tokens = primaryTokens - ))) - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager)) - store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain))) - store.dispatch(WalletAction.MultiWallet.AddTokens(primaryTokens.toList())) + dispatchOnMain( + WalletAction.MultiWallet.SaveCurrencies( + CardCurrencies(blockchains = listOf(primaryBlockchain), tokens = primaryTokens) + ), + WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager), + WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain)), + WalletAction.MultiWallet.AddTokens(primaryTokens.toList()) + ) + } else { val blockchains = listOf(Blockchain.Bitcoin, Blockchain.Ethereum) - store.dispatch(WalletAction.MultiWallet.SaveCurrencies( - CardCurrencies(blockchains = blockchains, tokens = emptyList()) - )) - val walletManagers = - walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList()) - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers)) - store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains.toList())) + val walletManagers = walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList()) + dispatchOnMain( + WalletAction.MultiWallet.SaveCurrencies(CardCurrencies( + blockchains = blockchains, + tokens = emptyList() + )), + WalletAction.MultiWallet.AddWalletManagers(walletManagers), + WalletAction.MultiWallet.AddBlockchains(blockchains.toList()), + ) } - store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse) - store.dispatch(WalletAction.MultiWallet.FindTokensInUse) + dispatchOnMain( + WalletAction.MultiWallet.FindBlockchainsInUse, + WalletAction.MultiWallet.FindTokensInUse, + ) } else { val blockchains = savedCurrencies.blockchains.toList() val walletManagers = if ( @@ -218,10 +220,11 @@ class TapWalletManager { } else { walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains) } - - store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers)) - store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains)) - store.dispatch(WalletAction.MultiWallet.AddTokens(savedCurrencies.tokens.toList())) + dispatchOnMain( + WalletAction.MultiWallet.AddWalletManagers(walletManagers), + WalletAction.MultiWallet.AddBlockchains(blockchains), + WalletAction.MultiWallet.AddTokens(savedCurrencies.tokens.toList()), + ) } } @@ -260,79 +263,45 @@ class TapWalletManager { } } - private suspend fun handleUpdateWalletResult(result: Result, walletManager: WalletManager) { - withContext(Dispatchers.Main) { - when (result) { - is Result.Success -> { - checkForRentWarning(walletManager) - store.dispatch(WalletAction.LoadWallet.Success(result.data)) - } - is Result.Failure -> { - val error = (result.error as? TapError) ?: TapError.UnknownError - when (error) { - TapError.NoInternetConnection -> { - store.dispatch(WalletAction.LoadData.Failure(error)) - } - is TapError.WalletManagerUpdate.NoAccountError -> { - store.dispatch(WalletAction.LoadWallet - .NoAccount(walletManager.wallet, error.customMessage)) - } - is TapError.WalletManagerUpdate.InternalError -> { - store.dispatch(WalletAction.LoadWallet - .Failure(walletManager.wallet, error.customMessage)) - } - else -> { - store.dispatchDebugErrorNotification(error) - } - } - } - } - } - } - - private fun checkForRentWarning(walletManager: WalletManager) { + private suspend fun checkForRentWarning(walletManager: WalletManager) { val rentProvider = walletManager as? RentProvider ?: return - scope.launch { - when (val result = rentProvider.minimalBalanceForRentExemption()) { - is com.tangem.blockchain.extensions.Result.Success -> { - fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean = balance < rentExempt + when (val result = rentProvider.minimalBalanceForRentExemption()) { + is com.tangem.blockchain.extensions.Result.Success -> { + fun isNeedToShowWarning(balance: BigDecimal, rentExempt: BigDecimal): Boolean = balance < rentExempt - val balance = walletManager.wallet.fundsAvailable(AmountType.Coin) - val outgoingTxs = walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing) - val rentExempt = result.data - val show = if (outgoingTxs.isEmpty()) { - isNeedToShowWarning(balance, rentExempt) - } else { - val outgoingAmount = outgoingTxs.sumOf { it.amount ?: BigDecimal.ZERO } - val rest = balance.minus(outgoingAmount) - isNeedToShowWarning(rest, rentExempt) - } - if (!show) return@launch - - val currency = walletManager.wallet.blockchain.currency - store.dispatchOnMain(WalletAction.SetWalletRent( - blockchain = walletManager.wallet.blockchain, - minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"), - rentExempt = ("${rentExempt.stripZeroPlainString()} $currency") - )) + val balance = walletManager.wallet.fundsAvailable(AmountType.Coin) + val outgoingTxs = walletManager.wallet.getPendingTransactions(PendingTransactionType.Outgoing) + val rentExempt = result.data + val show = if (outgoingTxs.isEmpty()) { + isNeedToShowWarning(balance, rentExempt) + } else { + val outgoingAmount = outgoingTxs.sumOf { it.amount ?: BigDecimal.ZERO } + val rest = balance.minus(outgoingAmount) + isNeedToShowWarning(rest, rentExempt) } - is com.tangem.blockchain.extensions.Result.Failure -> {} + if (!show) return + + val currency = walletManager.wallet.blockchain.currency + dispatchOnMain(WalletAction.SetWalletRent( + blockchain = walletManager.wallet.blockchain, + minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency"), + rentExempt = ("${rentExempt.stripZeroPlainString()} $currency") + )) } + is com.tangem.blockchain.extensions.Result.Failure -> {} } } private suspend fun handleFiatRatesResult(results: List?>>) { - withContext(Dispatchers.Main) { - results.map { - when (it.second) { - is Result.Success -> { - val rate = it.first to (it.second as Result.Success).data - store.dispatch(WalletAction.LoadFiatRate.Success(rate)) - } - is Result.Failure -> store.dispatch(WalletAction.LoadFiatRate.Failure) - null -> {} + results.map { + when (it.second) { + is Result.Success -> { + val rate = it.first to (it.second as Result.Success).data + dispatchOnMain(WalletAction.LoadFiatRate.Success(rate)) } + is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure) + null -> {} } } } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt index 89130c4a16..6443b95987 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateWalletsTask.kt @@ -4,6 +4,7 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.TangemSdkError import com.tangem.operations.CommandResponse import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask @@ -22,6 +23,11 @@ class CreateWalletsTask( private val createdWalletsResponses = mutableListOf() override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + if (curves.isEmpty()){ + callback(CompletionResult.Failure(TangemSdkError.WalletIsNotCreated())) + return + } + val curve = curves[createdWalletsResponses.size] createWallet(curve, session, callback) } diff --git a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt index 34f6cc4807..e3d88e1b99 100644 --- a/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/features/feedback/FeedbackManager.kt @@ -7,12 +7,10 @@ import android.os.Build import com.tangem.Log import com.tangem.TangemSdkLogger import com.tangem.blockchain.common.* -import com.tangem.common.card.Card import com.tangem.tap.common.extensions.sendEmail import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.domain.TapWorkarounds -import com.tangem.tap.features.feedback.EmailData.Companion.appendBlankLine -import com.tangem.tap.features.feedback.EmailData.Companion.appendDelimiter +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.wallet.R import timber.log.Timber import java.io.File @@ -86,16 +84,19 @@ class FeedbackManager( class TangemLogCollector : TangemSdkLogger { private val dateFormatter = SimpleDateFormat("HH:mm:ss.SSS") private val logs = mutableListOf() + private val mutex = Object() override fun log(message: () -> String, level: Log.Level) { val time = dateFormatter.format(Date()) - logs.add("$time: ${message()}\n") + synchronized(mutex) { + logs.add("$time: ${message()}\n") + } } - fun getLogs(): List = logs.toList() + fun getLogs(): List = synchronized(mutex) { logs.toList() } fun clearLogs() { - logs.clear() + synchronized(mutex) { logs.clear() } } } @@ -105,8 +106,6 @@ class AdditionalEmailInfo { var address: String = "", var explorerLink: String = "", var host: String = "", - // var outputsCount: String = "" - // var transactionHex: String = "" ) var appVersion: String = "" @@ -115,6 +114,7 @@ class AdditionalEmailInfo { var cardId: String = "" var cardFirmwareVersion: String = "" var cardIssuer: String = "" + var cardBlockchain: String = "" // wallets internal val walletsInfo = mutableListOf() @@ -141,12 +141,13 @@ class AdditionalEmailInfo { } } - fun setCardInfo(card: Card) { - cardId = card.cardId - cardFirmwareVersion = card.firmwareVersion.stringValue - cardIssuer = card.issuer.name - signedHashesCount = card.wallets - .joinToString(";") { "${it.curve?.curve} - ${it.totalSignedHashes}" } + fun setCardInfo(data: ScanResponse) { + cardId = data.card.cardId + cardBlockchain = data.walletData?.blockchain ?: "" + cardFirmwareVersion = data.card.firmwareVersion.stringValue + cardIssuer = data.card.issuer.name + signedHashesCount = data.card.wallets + .joinToString("; ") { "${it.curve.curve} - ${it.totalSignedHashes}" } } fun setWalletsInfo(walletManagers: List) { @@ -217,33 +218,23 @@ interface EmailData { fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String { return StringBuilder().apply { append(context.getString(mainMessageResId)) - append("\n\n\n\n") + appendLine(3) append(context.getString(getDataCollectionMessageResId())) + appendLine() append(createOptionalMessage(infoHolder)) }.toString() } - - companion object { - internal fun StringBuilder.appendDelimiter() = append("----------\n") - internal fun StringBuilder.appendBlankLine() = append("\n") - } } class RateCanBeBetterEmail : EmailData { override val subjectResId: Int = R.string.feedback_subject_rate_negative override val mainMessageResId: Int = R.string.feedback_preface_rate_negative - override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { - val walletInfo = infoHolder.walletsInfo[0] - return StringBuilder().apply { - appendKeyValue("Card ID", infoHolder.cardId) - appendKeyValue("Blockchain", walletInfo.blockchain.fullName) - appendBlankLine() - appendKeyValue("Phone model", infoHolder.phoneModel) - appendKeyValue("OS version", infoHolder.osVersion) - appendKeyValue("App version", infoHolder.appVersion) - }.toString() - } + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder) + .appendCardInfo() + .appendLine() + .appendPhoneInfo() + .build() } class ScanFailsEmail : EmailData { @@ -251,51 +242,31 @@ class ScanFailsEmail : EmailData { override val subjectResId: Int = R.string.feedback_subject_scan_failed override val mainMessageResId: Int = R.string.feedback_preface_scan_failed - override fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String { - return StringBuilder().apply { - append(context.getString(mainMessageResId)) - append("\n\n\n\n") - append(createOptionalMessage(infoHolder)) - }.toString() - } + override fun joinTogether(context: Context, infoHolder: AdditionalEmailInfo): String = StringBuilder().apply { + append(context.getString(mainMessageResId)) + appendLine(4) + append(createOptionalMessage(infoHolder)) + }.toString() - override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { - return StringBuilder().apply { - appendBlankLine() - appendKeyValue("Phone model", infoHolder.phoneModel) - appendKeyValue("OS version", infoHolder.osVersion) - appendKeyValue("App version", infoHolder.appVersion) - }.toString() - } + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder) + .appendPhoneInfo() + .build() } -class SendTransactionFailedEmail(private val error: String) : EmailData { +class SendTransactionFailedEmail( + val error: String +) : EmailData { + override val subjectResId: Int = R.string.feedback_subject_tx_failed override val mainMessageResId: Int = R.string.feedback_preface_tx_failed - override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { - val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo() - return StringBuilder().apply { - appendKeyValue("Card ID", infoHolder.cardId) - appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) - appendKeyValue("Signed hashes", infoHolder.signedHashesCount) - appendDelimiter() - appendKeyValue("Blockchain", walletInfo.blockchain.fullName) - appendKeyValue("Host", walletInfo.host) - appendKeyValue("Token", infoHolder.token) - appendKeyValue("Error", error) - appendDelimiter() - appendKeyValue("Source address", walletInfo.address) - appendKeyValue("Destination address", infoHolder.destinationAddress) - appendKeyValue("Amount", infoHolder.amount) - appendKeyValue("Fee", infoHolder.fee) - appendBlankLine() - appendKeyValue("Phone model", infoHolder.phoneModel) - appendKeyValue("OS version", infoHolder.osVersion) - appendKeyValue("App version", infoHolder.appVersion) -// appendKeyValue("Transaction HEX", infoHolder.transactionHex) - }.toString() - } + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder) + .appendCardInfo() + .appendDelimiter() + .appendTxFailedBlockchainInfo(error) + .appendLine() + .appendPhoneInfo() + .build() } class FeedbackEmail : EmailData { @@ -316,37 +287,88 @@ class FeedbackEmail : EmailData { isS2CCard = TapWorkarounds.isStart2CoinIssuer(infoHolder.cardIssuer) } - override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String { - val builder = StringBuilder() + override fun createOptionalMessage(infoHolder: AdditionalEmailInfo): String = EmailDataBuilder(infoHolder) + .appendCardInfo() + .appendWalletsInfo() + .appendLine() + .appendPhoneInfo() + .build() +} + + +class EmailDataBuilder( + private val infoHolder: AdditionalEmailInfo +) { + val builder = StringBuilder() + + fun appendDelimiter(): EmailDataBuilder { + builder.appendDelimiter() + return this + } + + fun appendLine(count: Int = 1): EmailDataBuilder { + builder.appendLine(count) + return this + } + + fun appendCardInfo(): EmailDataBuilder { builder.appendKeyValue("Card ID", infoHolder.cardId) builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion) + builder.appendKeyValue("Card Blockchain", infoHolder.cardBlockchain) builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount) + return this + } + fun appendWalletsInfo(): EmailDataBuilder { infoHolder.walletsInfo.forEach { builder.appendDelimiter() builder.appendKeyValue("Blockchain", it.blockchain.fullName) builder.appendKeyValue("Host", it.host) builder.appendKeyValue("Wallet address", it.address) builder.appendKeyValue("Explorer link", it.explorerLink) - } - builder.appendBlankLine() - infoHolder.tokens.forEach { tokens -> - builder.appendDelimiter() - builder.appendKeyValue("Blockchain", tokens.key.fullName) - builder.appendKeyValue("Tokens", tokens.value.map { "${it.name} - ${it.symbol}" }.toString()) + infoHolder.tokens[it.blockchain]?.let { tokens -> + builder.append("Tokens:") + appendLine() + tokens.forEach { token -> + builder.appendKeyValue("Name", token.name) + builder.appendKeyValue("Contract address", token.contractAddress) + } + } } + return this + } + + fun appendTxFailedBlockchainInfo(error: String): EmailDataBuilder { + val walletInfo = infoHolder.onSendErrorWalletInfo ?: AdditionalEmailInfo.EmailWalletInfo() + builder.appendKeyValue("Blockchain", walletInfo.blockchain.fullName) + builder.appendKeyValue("Host", walletInfo.host) + builder.appendKeyValue("Token", infoHolder.token) + builder.appendKeyValue("Error", error) builder.appendDelimiter() - builder.appendBlankLine() -// appendKeyValue("Outputs count", infoHolder.outputsCount) + builder.appendKeyValue("Source address", walletInfo.address) + builder.appendKeyValue("Destination address", infoHolder.destinationAddress) + builder.appendKeyValue("Amount", infoHolder.amount) + builder.appendKeyValue("Fee", infoHolder.fee) + return this + } + + fun appendPhoneInfo(): EmailDataBuilder { builder.appendKeyValue("Phone model", infoHolder.phoneModel) builder.appendKeyValue("OS version", infoHolder.osVersion) builder.appendKeyValue("App version", infoHolder.appVersion) - - return builder.toString() + return this } + + fun build(): String = builder.toString() } private fun StringBuilder.appendKeyValue(key: String, value: String): StringBuilder { return if (value.isNotBlank()) this.append("$key: $value\n") else this +} + +private fun StringBuilder.appendDelimiter(): StringBuilder = append("----------\n") + +private fun StringBuilder.appendLine(count: Int = 1): StringBuilder { + return append(List(count) { "\n" }.joinToString(separator = "")) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt index b12be36802..8d2577fc01 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesGeneralContent.kt @@ -1,5 +1,11 @@ package com.tangem.tap.features.home.compose +import android.content.Context +import android.graphics.Typeface +import android.util.TypedValue +import android.view.View +import android.view.ViewGroup +import android.widget.TextView import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.material.Text @@ -14,6 +20,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import com.tangem.tangem_sdk_new.extensions.dpToPx +import com.tangem.tap.common.extensions.compose.argb import com.tangem.wallet.R @Composable @@ -22,8 +31,9 @@ fun StoriesGeneralContent( subtitleText: String, imageSource: Int?, isDarkBackground: Boolean, + subtitleTextId: Int? = null, imageComposable: (() -> Unit)? = null, - ) { +) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, @@ -45,15 +55,7 @@ fun StoriesGeneralContent( Spacer(modifier = Modifier.size(16.dp)) - Text( - text = subtitleText, - fontSize = 20.sp, - fontWeight = FontWeight.Normal, - modifier = Modifier - .padding(start = 40.dp, end = 40.dp), - color = Color(0xFFA6AAAD), - textAlign = TextAlign.Center - ) + SubtitleText(subtitleText, subtitleTextId) Spacer(modifier = Modifier.size(25.dp)) @@ -61,7 +63,7 @@ fun StoriesGeneralContent( Image( painter = painterResource(id = imageSource), contentDescription = null, - contentScale = if(isDarkBackground) ContentScale.Inside else ContentScale.FillWidth, + contentScale = if (isDarkBackground) ContentScale.Inside else ContentScale.FillWidth, modifier = Modifier.fillMaxWidth() ) } @@ -69,6 +71,34 @@ fun StoriesGeneralContent( } } +@Composable +fun SubtitleText(subtitleText: String, subtitleTextId: Int?) { + val color = Color(0xFFA6AAAD) + + if (subtitleTextId == null) { + Text( + text = subtitleText, + fontSize = 20.sp, + fontWeight = FontWeight.Normal, + modifier = Modifier.padding(start = 40.dp, end = 40.dp), + color = color, + textAlign = TextAlign.Center + ) + } else { + HtmlText(subtitleTextId) { context -> + val padding = context.dpToPx(40f).toInt() + TextView(context).apply { + layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + setPadding(padding, 0, padding, 0) + textAlignment = View.TEXT_ALIGNMENT_CENTER + typeface = Typeface.DEFAULT + setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f) + setTextColor(color.argb()) + } + } + } +} + @Composable fun StoriesRevolutionaryWallet() { StoriesGeneralContent( @@ -85,6 +115,7 @@ fun StoriesUltraSecureBackup() { titleText = stringResource(id = R.string.story_backup_title), subtitleText = stringResource(id = R.string.story_backup_description), imageSource = R.drawable.floating_cards, + subtitleTextId = R.string.story_backup_description, isDarkBackground = false ) } @@ -117,4 +148,13 @@ fun StoriesWalletForEveryone() { imageSource = R.drawable.wallet_for_everyone, isDarkBackground = true ) +} + +@Composable +fun HtmlText( + stringResId: Int, + modifier: Modifier = Modifier, + factory: (Context) -> TextView +) { + AndroidView(factory, modifier) { it.text = it.context.getText(stringResId) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index d659510cd4..eaba6384c7 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -202,11 +202,11 @@ private fun sendTransaction( } scope.launch(Dispatchers.IO) { withContext(Dispatchers.Main) { - dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain)) + dispatch(WalletAction.LoadWallet(walletManager.wallet.blockchain)) } delay(11000) // more than 10000 to avoid throttling withContext(Dispatchers.Main) { - dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain)) + dispatch(WalletAction.LoadWallet(walletManager.wallet.blockchain)) } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 26fae5925a..d7c9c56390 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -74,12 +74,6 @@ sealed class WalletAction : Action { class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings() } - data class UpdateWallet(val blockchain: Blockchain? = null, val force: Boolean = true) : WalletAction() { - object ScheduleUpdatingWallet : WalletAction() - data class Success(val wallet: Wallet) : WalletAction() - data class Failure(val errorMessage: String? = null) : WalletAction() - } - data class LoadFiatRate( val wallet: Wallet? = null, val currencyList: List? = null, ) : WalletAction() { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index b4ce090f81..0f2ea1dae0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -4,6 +4,7 @@ import android.graphics.Bitmap import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.AddressType import com.tangem.blockchain.extensions.isAboveZero +import com.tangem.common.extensions.isZero import com.tangem.tap.common.entities.Button import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.redux.StateDialog @@ -32,11 +33,11 @@ data class WalletState( val hashesCountVerified: Boolean? = null, val walletDialog: StateDialog? = null, val mainWarningsList: List = mutableListOf(), - val wallets: List = emptyList(), + val walletsData: List = emptyList(), val walletManagers: List = emptyList(), val isMultiwalletAllowed: Boolean = false, val cardCurrency: CryptoCurrencyName? = null, - val selectedWallet: Currency? = null, + val selectedCurrency: Currency? = null, val primaryBlockchain: Blockchain? = null, val primaryToken: Token? = null, val isTestnet: Boolean = false, @@ -51,7 +52,7 @@ data class WalletState( val isTangemTwins: Boolean get() = store.state.globalState.scanResponse?.isTangemTwins() == true - val primaryWallet = if (wallets.isNotEmpty()) wallets[0] else null + val primaryWallet = if (walletsData.isNotEmpty()) walletsData[0] else null val shouldShowDetails: Boolean = primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard && @@ -61,7 +62,7 @@ data class WalletState( get() = walletManagers.map { it.wallet.blockchain } val currencies: List - get() = wallets.mapNotNull { it.currency } + get() = walletsData.mapNotNull { it.currency } fun getWalletManager(token: Token?): WalletManager? { if (token == null) return null @@ -79,21 +80,21 @@ data class WalletState( fun getWalletData(currency: Currency?): WalletData? { if (currency == null) return null - return wallets.find { it.currency == currency } + return walletsData.find { it.currency == currency } } fun getWalletData(blockchain: Blockchain?): WalletData? { if (blockchain == null) return null - return wallets.find { (it.currency as? Currency.Blockchain)?.blockchain == blockchain } + return walletsData.find { (it.currency as? Currency.Blockchain)?.blockchain == blockchain } } fun getWalletData(token: Token?): WalletData? { if (token == null) return null - return wallets.find { (it.currency as? Currency.Token)?.token == token } + return walletsData.find { (it.currency as? Currency.Token)?.token == token } } fun getSelectedWalletData(): WalletData? { - return wallets.find { it.currency == selectedWallet } + return walletsData.find { it.currency == selectedCurrency } } fun canBeRemoved(walletData: WalletData?): Boolean { @@ -132,9 +133,9 @@ data class WalletState( } fun replaceWalletInWallets(walletData: WalletData?): List { - if (walletData == null) return wallets + if (walletData == null) return walletsData var changed = false - val updatedWallets = wallets.map { + val updatedWallets = walletsData.map { if (it.currency == walletData.currency) { changed = true walletData @@ -142,12 +143,12 @@ data class WalletState( it } } - return if (changed) updatedWallets else wallets + walletData + return if (changed) updatedWallets else walletsData + walletData } fun replaceSomeWallets(newWallets: List): List { val remainingWallets: MutableList = newWallets.toMutableList() - val updatedWallets = wallets.map { wallet -> + val updatedWallets = walletsData.map { wallet -> val newWallet = newWallets .firstOrNull { wallet.currency == it.currency } if (newWallet == null) { @@ -248,17 +249,26 @@ data class WalletData( val currencyData: BalanceWidgetData = BalanceWidgetData(), val updatingWallet: Boolean = false, val tradeCryptoState: TradeCryptoState = TradeCryptoState(), - val allowToSend: Boolean = true, val fiatRateString: String? = null, val fiatRate: BigDecimal? = null, val mainButton: WalletMainButton = WalletMainButton.SendButton(false), val currency: Currency, - val walletRent: WalletRent? = null, + val warningRent: WalletRent? = null, ) { fun shouldShowMultipleAddress(): Boolean { val listOfAddresses = walletAddresses?.list ?: return false return listOfAddresses.size > 1 } + + fun shouldShowCoinAmountWarning(): Boolean = when (currency) { + is Currency.Blockchain -> false + is Currency.Token -> blockchainAmountIsEmpty() && !tokenAmountIsEmpty() + } + + fun shouldEnableTokenSendButton(): Boolean = !blockchainAmountIsEmpty() || !tokenAmountIsEmpty() + + private fun blockchainAmountIsEmpty(): Boolean = currencyData.blockchainAmount?.isZero() ?: false + private fun tokenAmountIsEmpty(): Boolean = currencyData.amount?.isZero() == true } data class WalletRent( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index f0b6c3506e..ade8ab52d7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -187,9 +187,7 @@ class MultiWalletMiddleware { store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain)) } store.dispatch(WalletAction.LoadFiatRate(currencyList = tokensList.map { Currency.Token(it) })) - walletManager?.apply { - scope.launch { async { addTokens(tokensList) }.await() } - } + walletManager?.apply { addTokens(tokensList) } } scope.launch { walletManagers.forEach { walletManager -> diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 8c9a06c2e2..094379cc79 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -24,7 +24,10 @@ import com.tangem.tap.domain.extensions.toSendableAmounts import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.send.redux.PrepareSendScreen -import com.tangem.tap.features.wallet.redux.* +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WalletData +import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.network.NetworkStateChanged import com.tangem.tap.scope import com.tangem.tap.store @@ -84,24 +87,27 @@ class WalletMiddleware { warningsMiddleware.tryToShowAppRatingWarning(action.wallet) } is WalletAction.LoadFiatRate -> { + val tapWalletManager = globalState.tapWalletManager + val fiatAppCurrency = globalState.appCurrency scope.launch { when { action.wallet != null -> { globalState.tapWalletManager.loadFiatRate( - fiatCurrency = globalState.appCurrency, + fiatCurrency = fiatAppCurrency, wallet = action.wallet, ) } action.currencyList != null -> { globalState.tapWalletManager.loadFiatRate( - fiatCurrency = globalState.appCurrency, + fiatCurrency = fiatAppCurrency, currencies = action.currencyList, ) } else -> { + val currencyList = walletState.walletsData.map { it.currency } globalState.tapWalletManager.loadFiatRate( - fiatCurrency = globalState.appCurrency, - currencies = walletState.wallets.map { it.currency }, + fiatCurrency = fiatAppCurrency, + currencies = currencyList, ) } } @@ -129,22 +135,6 @@ class WalletMiddleware { } } } - is WalletAction.UpdateWallet -> { - if (action.blockchain != null) { - scope.launch { - val walletManager = walletState.getWalletManager(action.blockchain) - walletManager?.let { globalState.tapWalletManager.updateWallet(it, action.force) } - } - } else { - scope.launch { - if (walletState.state == ProgressState.Done) { - walletState.walletManagers.map { walletManager -> - globalState.tapWalletManager.updateWallet(walletManager, action.force) - } - } - } - } - } is WalletAction.Scan -> { store.dispatch(HomeAction.ShouldScanCardOnResume(true)) store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) @@ -173,7 +163,7 @@ class WalletMiddleware { is WalletAction.LoadData -> { scope.launch { val scanNoteResponse = globalState.scanResponse ?: return@launch - if (!walletState.wallets.isEmpty()) { + if (walletState.walletsData.isNotEmpty()) { globalState.tapWalletManager.reloadData(scanNoteResponse) } else { globalState.tapWalletManager.loadData(scanNoteResponse) @@ -206,7 +196,7 @@ class WalletMiddleware { } } is WalletAction.ShowDialog.QrCode -> { - val selectedWalletData = walletState.getWalletData(walletState.selectedWallet) ?: return + val selectedWalletData = walletState.getWalletData(walletState.selectedCurrency) ?: return val selectedAddressData = selectedWalletData.walletAddresses?.selectedAddress ?: return val currency = selectedWalletData.currency diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index e0f1fec69b..e64d5a82cf 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -21,7 +21,7 @@ class MultiWalletReducer { state.addWalletManagers(action.walletManagers) } is WalletAction.MultiWallet.AddBlockchains -> { - val wallets = action.blockchains.map { blockchain -> + val walletsData = action.blockchains.map { blockchain -> val wallet = state.getWalletManager(blockchain)?.wallet val cardToken = if (!state.isMultiwalletAllowed) { wallet?.getFirstToken()?.symbol?.let { TokenData("", tokenSymbol = it) } @@ -29,41 +29,41 @@ class MultiWalletReducer { null } WalletData( - currencyData = BalanceWidgetData( - BalanceStatus.Loading, - blockchain.fullName, - currencySymbol = blockchain.currency, - token = cardToken - ), - walletAddresses = createAddressList(wallet), - mainButton = WalletMainButton.SendButton(false), - currency = Currency.Blockchain(blockchain), + currencyData = BalanceWidgetData( + BalanceStatus.Loading, + blockchain.fullName, + currencySymbol = blockchain.currency, + token = cardToken + ), + walletAddresses = createAddressList(wallet), + mainButton = WalletMainButton.SendButton(false), + currency = Currency.Blockchain(blockchain), ) } - val selectedWallet = if (!state.isMultiwalletAllowed) { - wallets[0].currency + val selectedCurrency = if (!state.isMultiwalletAllowed) { + walletsData[0].currency } else { - state.selectedWallet + state.selectedCurrency } state.copy( - wallets = wallets, - selectedWallet = selectedWallet + walletsData = walletsData, + selectedCurrency = selectedCurrency ) } is WalletAction.MultiWallet.AddBlockchain -> { val wallet = state.getWalletManager(action.blockchain)?.wallet val walletData = WalletData( - currencyData = BalanceWidgetData( - BalanceStatus.Loading, - action.blockchain.fullName, - currencySymbol = action.blockchain.currency, - ), - walletAddresses = createAddressList(wallet), - mainButton = WalletMainButton.SendButton(false), - currency = Currency.Blockchain(action.blockchain), + currencyData = BalanceWidgetData( + BalanceStatus.Loading, + action.blockchain.fullName, + currencySymbol = action.blockchain.currency, + ), + walletAddresses = createAddressList(wallet), + mainButton = WalletMainButton.SendButton(false), + currency = Currency.Blockchain(action.blockchain), ) - val newState = state.copy(wallets = state.replaceWalletInWallets(walletData)) + val newState = state.copy(walletsData = state.replaceWalletInWallets(walletData)) if (wallet != null && wallet.amounts[AmountType.Coin]?.value != null) { OnWalletLoadedReducer().reduce(wallet, newState) } else { @@ -72,21 +72,21 @@ class MultiWalletReducer { } is WalletAction.MultiWallet.AddTokens -> { val wallets = action.tokens.mapNotNull { token -> token.toWallet(state) } - state.copy(wallets = state.replaceSomeWallets(wallets)) + state.copy(walletsData = state.replaceSomeWallets(wallets)) } is WalletAction.MultiWallet.AddToken -> { val walletData = action.token.toWallet(state) ?: return state - state.copy(wallets = state.replaceWalletInWallets(walletData)) + state.copy(walletsData = state.replaceWalletInWallets(walletData)) } is WalletAction.MultiWallet.TokenLoaded -> { val pendingTransactions = state.getWalletManager(action.token) - ?.wallet?.let { wallet -> - wallet.recentTransactions.toPendingTransactions(wallet.address) - } ?: emptyList() + ?.wallet?.let { wallet -> + wallet.recentTransactions.toPendingTransactions(wallet.address) + } ?: emptyList() val sendButtonEnabled = action.amount.value?.isZero() == false && pendingTransactions.isEmpty() val tokenPendingTransactions = pendingTransactions - .filter { it.currency == action.amount.currencySymbol } + .filter { it.currency == action.amount.currencySymbol } val tokenBalanceStatus = when { tokenPendingTransactions.isNotEmpty() -> BalanceStatus.TransactionInProgress pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress @@ -94,42 +94,43 @@ class MultiWalletReducer { } val tokenWalletData = state.getWalletData(action.token) val newTokenWalletData = tokenWalletData?.copy( - currencyData = tokenWalletData.currencyData.copy( - status = tokenBalanceStatus, - amount = action.amount.value?.toFormattedCurrencyString( - action.amount.decimals, action.amount.currencySymbol - ), - fiatAmountFormatted = tokenWalletData.fiatRate?.let { - action.amount.value - ?.toFiatString(it, store.state.globalState.appCurrency) - } + currencyData = tokenWalletData.currencyData.copy( + status = tokenBalanceStatus, + amount = action.amount.value, + amountFormatted = action.amount.value?.toFormattedCurrencyString( + action.amount.decimals, action.amount.currencySymbol ), - pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled), - currency = Currency.Token(action.token) + fiatAmountFormatted = tokenWalletData.fiatRate?.let { + action.amount.value + ?.toFiatString(it, store.state.globalState.appCurrency) + } + ), + pendingTransactions = pendingTransactions.removeUnknownTransactions(), + mainButton = WalletMainButton.SendButton(sendButtonEnabled), + currency = Currency.Token(action.token) ) val wallets = state.replaceWalletInWallets(newTokenWalletData) - state.copy(wallets = wallets) + state.copy(walletsData = wallets) } is WalletAction.MultiWallet.SetIsMultiwalletAllowed -> state.copy(isMultiwalletAllowed = action.isMultiwalletAllowed) is WalletAction.MultiWallet.SelectWallet -> - state.copy(selectedWallet = action.walletData?.currency) + state.copy(selectedCurrency = action.walletData?.currency) is WalletAction.MultiWallet.RemoveWallet -> { - val wallets = state.wallets.filterNot { + val wallets = state.walletsData.filterNot { it.currency == action.walletData.currency } if (action.walletData.currency is Currency.Blockchain) { state.copy( - wallets = wallets, + walletsData = wallets, walletManagers = state.walletManagers.filterNot { it.wallet.blockchain == action.walletData.currency.blockchain } ) } else { - state.copy(wallets = wallets) + state.copy(walletsData = wallets) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index c28bea2b5b..caf41a9386 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -36,11 +36,11 @@ class OnWalletLoadedReducer { return walletState } val formattedAmount = coinAmountValue?.toFormattedCurrencyString( - wallet.blockchain.decimals(), - wallet.blockchain.currency) + wallet.blockchain.decimals(), + wallet.blockchain.currency) val pendingTransactions = wallet.recentTransactions - .toPendingTransactions(wallet.address) + .toPendingTransactions(wallet.address) val coinSendButton = coinAmountValue?.isZero() == false && pendingTransactions.isEmpty() val balanceStatus = if (pendingTransactions.isNotEmpty()) { @@ -52,17 +52,19 @@ class OnWalletLoadedReducer { val fiatAmount = walletData?.fiatRate?.let { coinAmountValue?.toFiatValue(it) } val newWalletData = walletData?.copy( - currencyData = walletData.currencyData.copy( - status = balanceStatus, currency = wallet.blockchain.fullName, - currencySymbol = wallet.blockchain.currency, - amount = formattedAmount, - fiatAmount = fiatAmount, - fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrencySymbol) - ), - pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(coinSendButton), - currency = Currency.Blockchain(wallet.blockchain), - tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData) + currencyData = walletData.currencyData.copy( + status = balanceStatus, currency = wallet.blockchain.fullName, + currencySymbol = wallet.blockchain.currency, + blockchainAmount = coinAmountValue, + amount = coinAmountValue, + amountFormatted = formattedAmount, + fiatAmount = fiatAmount, + fiatAmountFormatted = fiatAmount?.toFormattedFiatValue(fiatCurrencySymbol) + ), + pendingTransactions = pendingTransactions.removeUnknownTransactions(), + mainButton = WalletMainButton.SendButton(coinSendButton), + currency = Currency.Blockchain(wallet.blockchain), + tradeCryptoState = TradeCryptoState.from(exchangeManager, walletData), ) val tokens = wallet.getTokens().mapNotNull { token -> @@ -73,20 +75,23 @@ class OnWalletLoadedReducer { pendingTransactions.isNotEmpty() -> BalanceStatus.SameCurrencyTransactionInProgress else -> BalanceStatus.VerifiedOnline } - val tokenAmountValue = wallet.getTokenAmount(token)?.value - val tokenFiatAmount = tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate)} + val tokenAmountValue = wallet.getTokenAmount(token)?.value + val tokenFiatAmount = tokenWalletData?.fiatRate?.let { rate -> tokenAmountValue?.toFiatValue(rate) } - val tokenSendButton = tokenAmountValue?.isZero() == false && tokenPendingTransactions.isEmpty() + val tokenSendButton = newWalletData?.shouldEnableTokenSendButton() == true + && tokenPendingTransactions.isEmpty() tokenWalletData?.copy( - currencyData = tokenWalletData.currencyData.copy( - status = tokenBalanceStatus, - amount = tokenAmountValue?.toFormattedCurrencyString(token.decimals, token.symbol), - fiatAmount = tokenFiatAmount, - fiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrencySymbol) - ), - pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(tokenSendButton), - tradeCryptoState = TradeCryptoState.from(exchangeManager, tokenWalletData) + currencyData = tokenWalletData.currencyData.copy( + status = tokenBalanceStatus, + blockchainAmount = coinAmountValue, + amount = tokenAmountValue, + amountFormatted = tokenAmountValue?.toFormattedCurrencyString(token.decimals, token.symbol), + fiatAmount = tokenFiatAmount, + fiatAmountFormatted = tokenFiatAmount?.toFormattedFiatValue(fiatCurrencySymbol) + ), + pendingTransactions = tokenPendingTransactions.removeUnknownTransactions(), + mainButton = WalletMainButton.SendButton(tokenSendButton), + tradeCryptoState = TradeCryptoState.from(exchangeManager, tokenWalletData), ) } val newWallets = (tokens + newWalletData).mapNotNull { it } @@ -98,7 +103,7 @@ class OnWalletLoadedReducer { ProgressState.Done } return walletState.copy( - state = state, wallets = wallets, error = null + state = state, walletsData = wallets, error = null ) } @@ -128,15 +133,13 @@ class OnWalletLoadedReducer { } val amount = wallet.amounts[AmountType.Coin]?.value val formattedAmount = amount?.toFormattedCurrencyString( - wallet.blockchain.decimals(), - wallet.blockchain.currency) + wallet.blockchain.decimals(), + wallet.blockchain.currency) val fiatRate = walletState.primaryWallet?.fiatRate val fiatAmountRaw = fiatRate?.multiply(amount)?.setScale(2, RoundingMode.DOWN) val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) } - val pendingTransactions = wallet.recentTransactions - .toPendingTransactions(wallet.address) - + val pendingTransactions = wallet.recentTransactions.toPendingTransactions(wallet.address) val sendButtonEnabled = amount?.isZero() == false && pendingTransactions.isEmpty() val balanceStatus = if (pendingTransactions.isNotEmpty()) { BalanceStatus.TransactionInProgress @@ -144,21 +147,23 @@ class OnWalletLoadedReducer { BalanceStatus.VerifiedOnline } val walletData = walletState.primaryWallet?.copy( - currencyData = BalanceWidgetData( - balanceStatus, wallet.blockchain.fullName, - currencySymbol = wallet.blockchain.currency, - formattedAmount, - token = tokenData, - fiatAmountFormatted = fiatAmount, - fiatAmount = fiatAmountRaw - ), - pendingTransactions = pendingTransactions.removeUnknownTransactions(), - mainButton = WalletMainButton.SendButton(sendButtonEnabled), - tradeCryptoState = TradeCryptoState.from(exchangeManager, walletState.primaryWallet) + currencyData = BalanceWidgetData( + balanceStatus, wallet.blockchain.fullName, + currencySymbol = wallet.blockchain.currency, + token = tokenData, + blockchainAmount = amount, + amount = amount, + amountFormatted = formattedAmount, + fiatAmountFormatted = fiatAmount, + fiatAmount = fiatAmountRaw + ), + pendingTransactions = pendingTransactions.removeUnknownTransactions(), + mainButton = WalletMainButton.SendButton(sendButtonEnabled), + tradeCryptoState = TradeCryptoState.from(exchangeManager, walletState.primaryWallet), ) val wallets = walletData?.let { listOf(walletData) } ?: emptyList() return walletState.copy( - state = ProgressState.Done, wallets = wallets, error = null + state = ProgressState.Done, walletsData = wallets, error = null ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 960a4e76b7..20bbe52092 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -46,7 +46,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.EmptyWallet -> { newState = newState.copy( state = ProgressState.Done, - wallets = listOf( + walletsData = listOf( WalletData( currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), mainButton = WalletMainButton.CreateWalletButton(true), @@ -58,7 +58,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.LoadData.Failure -> { when (action.error) { is TapError.NoInternetConnection -> { - val wallets = newState.wallets + val wallets = newState.walletsData .map { it.copy( currencyData = it.currencyData.copy( @@ -69,13 +69,13 @@ private fun internalReduce(action: Action, state: AppState): WalletState { newState = newState.copy( state = ProgressState.Error, error = ErrorType.NoInternetConnection, - wallets = wallets + walletsData = wallets ) } is TapError.UnknownBlockchain -> { newState = newState.copy( state = ProgressState.Done, - wallets = listOf( + walletsData = listOf( WalletData( currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain), currency = Currency.Blockchain(Blockchain.Unknown) @@ -94,7 +94,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } is WalletAction.LoadWallet -> { if (action.blockchain == null) { - val wallets = newState.wallets.map { wallet -> + val wallets = newState.walletsData.map { wallet -> wallet.copy( currencyData = wallet.currencyData.copy( status = BalanceStatus.Loading, @@ -107,14 +107,14 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } newState = newState.copy( state = ProgressState.Loading, - wallets = wallets, + walletsData = wallets, ) } else { val walletManager = newState.getWalletManager(action.blockchain) ?: return newState val blockchain = walletManager.wallet.blockchain val currencies = listOf(Currency.Blockchain(blockchain)) + walletManager.cardTokens.map { Currency.Token(it) } - val newWallets = newState.wallets.filter { currencies.contains(it.currency) } + val newWallets = newState.walletsData.filter { currencies.contains(it.currency) } .map { wallet -> wallet.copy( currencyData = wallet.currencyData.copy( @@ -127,14 +127,10 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) } val wallets = newState.replaceSomeWallets(newWallets) - newState = newState.copy(wallets = newState.updateTradeCryptoState(exchangeManager, wallets)) + newState = newState.copy(walletsData = newState.updateTradeCryptoState(exchangeManager, wallets)) } } - is WalletAction.LoadWallet.Success -> newState = - onWalletLoadedReducer.reduce(action.wallet, newState) - is WalletAction.UpdateWallet.Success -> { - newState = onWalletLoadedReducer.reduce(action.wallet, newState) - } + is WalletAction.LoadWallet.Success -> newState = onWalletLoadedReducer.reduce(action.wallet, newState) is WalletAction.LoadWallet.NoAccount -> { val walletData = newState.getWalletData(action.wallet.blockchain)?.copy( currencyData = BalanceWidgetData( @@ -152,7 +148,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } newState = newState.copy( state = progressState, - wallets = newState.updateTradeCryptoState(exchangeManager, wallets) + walletsData = newState.updateTradeCryptoState(exchangeManager, wallets) ) } is WalletAction.LoadWallet.Failure -> { @@ -187,7 +183,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } newState = newState.copy( state = progressState, - wallets = newState.updateTradeCryptoState(exchangeManager, wallets) + walletsData = newState.updateTradeCryptoState(exchangeManager, wallets) ) } is WalletAction.SetArtworkId -> { @@ -227,7 +223,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null) is WalletAction.TradeCryptoAction -> return newState is WalletAction.ChangeSelectedAddress -> { - val selectedWalletData = newState.getWalletData(newState.selectedWallet) + val selectedWalletData = newState.getWalletData(newState.selectedCurrency) val walletAddresses = newState.getWalletData(selectedWalletData?.currency)?.walletAddresses @@ -242,16 +238,16 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) ) ) - newState = newState.copy(wallets = wallets) + newState = newState.copy(walletsData = wallets) } is WalletAction.SetWalletRent -> { var walletData = newState.getWalletData(action.blockchain) if (walletData == null) { newState } else { - walletData = walletData.copy(walletRent = WalletRent(action.minRent, action.rentExempt)) + walletData = walletData.copy(warningRent = WalletRent(action.minRent, action.rentExempt)) newState = newState.copy( - wallets = newState.replaceSomeWallets(listOf(walletData)) + walletsData = newState.replaceSomeWallets(listOf(walletData)) ) } } @@ -342,7 +338,7 @@ private fun setMultiWalletFiatRate( ), fiatRate = rate, fiatRateString = rateFormatted ) - return state.copy(wallets = state.replaceWalletInWallets(newWalletData)) + return state.copy(walletsData = state.replaceWalletInWallets(newWalletData)) } private fun setSingeWalletFiatRate( @@ -360,7 +356,7 @@ private fun setSingeWalletFiatRate( fiatRate = rate, fiatRateString = rateFormatted ) - return state.copy(wallets = listOf(walletData)) + return state.copy(walletsData = listOf(walletData)) } else if (currency is Currency.Token && currency.token == token) { val tokenFiatAmount = wallet.getTokenAmount(token)?.value?.toFiatString(rate, appCurrency) val tokenData = state.primaryWallet?.currencyData?.token?.copy( @@ -375,7 +371,7 @@ private fun setSingeWalletFiatRate( ) ) val wallets = walletData?.let { listOf(walletData) } ?: emptyList() - return state.copy(wallets = wallets) + return state.copy(walletsData = wallets) } return state } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 42527e8528..6f666e8601 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -22,7 +22,9 @@ data class BalanceWidgetData( val status: BalanceStatus? = null, val currency: String? = null, val currencySymbol: String? = null, - val amount: String? = null, + val blockchainAmount: BigDecimal? = BigDecimal.ZERO, + val amount: BigDecimal? = null, + val amountFormatted: String? = null, val fiatAmountFormatted: String? = null, val fiatAmount: BigDecimal? = null, val token: TokenData? = null, @@ -149,7 +151,7 @@ class BalanceWidget( tvCurrency.text = data.token?.tokenSymbol tvBaseCurrency.text = data.currency tvAmount.text = if (showAmount) data.token?.amount else "" - tvBaseAmount.text = if (showAmount) data.amount else "" + tvBaseAmount.text = if (showAmount) data.amountFormatted else "" if (showAmount) { tvFiatAmount.show() tvFiatAmount.text = data.token?.fiatAmount @@ -160,7 +162,7 @@ class BalanceWidget( with(binding.lBalance) { groupBaseCurrency.hide() tvCurrency.text = data.currency - tvAmount.text = if (showAmount) data.amount else "" + tvAmount.text = if (showAmount) data.amountFormatted else "" if (showAmount) { tvFiatAmount.show() tvFiatAmount.text = data.fiatAmountFormatted diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 57d4b733de..6342b8f813 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -94,7 +94,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun newState(state: WalletState) { if (activity == null || view == null) return - if (state.selectedWallet == null) return + if (state.selectedCurrency == null) return val selectedWallet = state.getSelectedWalletData() ?: return @@ -107,8 +107,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), handleDialogs(state.walletDialog) handleCurrencyIcon(selectedWallet) - handleWalletRent(selectedWallet.walletRent) - + handleWalletRent(selectedWallet.warningRent) + handleNotEnoughFundsOnMainCurrency(selectedWallet) binding.srlWalletDetails.setOnRefreshListener { if (selectedWallet.currencyData.status != BalanceStatus.Loading) { @@ -153,14 +153,27 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), private fun handleWalletRent(rent: WalletRent?) = with(binding) { val rent = rent.guard { - lRentWarning.root.hide() + lWarning.root.hide() return } val warningMessage = requireContext().getString( R.string.solana_rent_warning, rent.minRentValue, rent.rentExemptValue ) - lRentWarning.tvRentWarningMessage.text = warningMessage - lRentWarning.root.show() + lWarning.tvWarningMessage.text = warningMessage + lWarning.root.show() + } + + private fun handleNotEnoughFundsOnMainCurrency(selectedWalletData: WalletData) = with(binding) { + if (selectedWalletData.shouldShowCoinAmountWarning()) { + val blockchainName = selectedWalletData.currency.blockchain.fullName + val warningMessage = requireContext().getString( + R.string.token_details_send_blocked_fee_format, blockchainName, blockchainName + ) + lWarning.tvWarningMessage.text = warningMessage + lWarning.root.show() + } else { + lWarning.root.hide() + } } private fun handleCurrencyIcon(wallet: WalletData) = with(binding.lWalletDetails.lBalance) { @@ -234,7 +247,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalance.root.show() lBalance.groupBalance.show() lBalance.tvError.hide() - lBalance.tvAmount.text = data.amount + lBalance.tvAmount.text = data.amountFormatted lBalance.tvFiatAmount.text = data.fiatAmountFormatted lBalance.tvStatus.setLoadingStatus(R.string.wallet_balance_loading) } @@ -244,7 +257,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), lBalance.root.show() lBalance.groupBalance.show() lBalance.tvError.hide() - lBalance.tvAmount.text = data.amount + lBalance.tvAmount.text = data.amountFormatted lBalance.tvFiatAmount.text = data.fiatAmountFormatted when (data.status) { BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress -> { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index ca1b70231e..cdfeed0122 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -63,7 +63,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber hideWarning() BalanceStatus.Loading -> { hideWarning() - if (wallet.currencyData.amount == null) { + if (wallet.currencyData.amountFormatted == null) { tvExchangeRate.text = root.getString(R.string.wallet_balance_loading) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 88557b8c27..dc3beeae72 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -94,12 +94,12 @@ class MultiWalletView : WalletView { val fragment = fragment ?: return val binding = binding ?: return - walletsAdapter.submitList(state.wallets, state.primaryBlockchain, state.primaryToken) + walletsAdapter.submitList(state.walletsData, state.primaryBlockchain, state.primaryToken) binding.btnAddToken.setOnClickListener { store.dispatch(TokensAction.LoadCurrencies) store.dispatch(TokensAction.AllowToAddTokens(true)) - store.dispatch(TokensAction.SetAddedCurrencies(state.wallets)) + store.dispatch(TokensAction.SetAddedCurrencies(state.walletsData)) store.dispatch(TokensAction.SetNonRemovableCurrencies( state.wallets.filterNot { state.canBeRemoved(it) }) ) diff --git a/app/src/main/res/layout/fragment_wallet_details.xml b/app/src/main/res/layout/fragment_wallet_details.xml index 0f6ef2b42d..a2d946a3ca 100644 --- a/app/src/main/res/layout/fragment_wallet_details.xml +++ b/app/src/main/res/layout/fragment_wallet_details.xml @@ -99,8 +99,8 @@ app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" /> diff --git a/app/src/main/res/layout/layout_wallet_details_rent.xml b/app/src/main/res/layout/layout_wallet_details_warning.xml similarity index 91% rename from app/src/main/res/layout/layout_wallet_details_rent.xml rename to app/src/main/res/layout/layout_wallet_details_warning.xml index 1f44d10ea7..43eeed159a 100644 --- a/app/src/main/res/layout/layout_wallet_details_rent.xml +++ b/app/src/main/res/layout/layout_wallet_details_warning.xml @@ -13,7 +13,7 @@ android:layout_height="wrap_content"> diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index b4b79f6fe6..d1ad971590 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -5,7 +5,7 @@ 2 cards Shipping Free - I have a promo code… + I have a promo code... Total Other payment methods Buy now @@ -23,18 +23,19 @@ Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup - Up to 3 physical cards to one wallet + Up to 3 physical cards to one wallet Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order card + Order Search tokens Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. Available networks diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml index e78a97bdb0..97fd745f11 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -5,7 +5,7 @@ 2 cards Shipping Free - I have a promo code… + I have a promo code... Total Other payment methods Buy now @@ -23,18 +23,19 @@ Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup - Up to 3 physical cards to one wallet + Up to 3 physical cards to one wallet Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order card + Order Search tokens Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. Available networks \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index b4b79f6fe6..d1ad971590 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -5,7 +5,7 @@ 2 cards Shipping Free - I have a promo code… + I have a promo code... Total Other payment methods Buy now @@ -23,18 +23,19 @@ Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup - Up to 3 physical cards to one wallet + Up to 3 physical cards to one wallet Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order card + Order Search tokens Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. Available networks diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 15eb075b42..0fc78cb747 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -23,7 +23,7 @@ Революционный аппаратный кошелек Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. Все ключи в безопасности - До трех карт с одним кошельком + До трех карт с одним кошельком Тысячи криптовалют Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте Поддержка DeFi @@ -35,6 +35,7 @@ Поиск токенов Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие. Эта функция недоступна в демонстрационном режиме + Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек. Доступные сети \ No newline at end of file diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index e78a97bdb0..97fd745f11 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -5,7 +5,7 @@ 2 cards Shipping Free - I have a promo code… + I have a promo code... Total Other payment methods Buy now @@ -23,18 +23,19 @@ Revolutionary Hardware Wallet Store your crypto assets secure while keeping private keys contained in your card Ultra Secure Backup - Up to 3 physical cards to one wallet + Up to 3 physical cards to one wallet Thousands of Currencies A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously – all in one card DeFi Compatible Exchange, buy NFT’s, make loans and deposits in more than 100 different decentralized services The Wallet for Everyone Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto. - Order card + Order Search tokens Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode + Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. Available networks \ No newline at end of file