Updated on 2026-08-14

This commit is contained in:
Tangem 2022-03-29 21:04:17 +04:00
commit b9c8d4fed1
28 changed files with 511 additions and 442 deletions

View file

@ -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'

View file

@ -7,10 +7,6 @@ import kotlinx.coroutines.withContext
/**
[REDACTED_AUTHOR]
*/
suspend fun <T> withMainContext(block: suspend CoroutineScope.() -> T) {
withContext(Dispatchers.Main, block)
}
suspend fun <T> withMainContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatchers.Main, block)
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T) {
withContext(Dispatchers.IO, block)
}
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatchers.IO, block)

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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<Wallet>, 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<Pair<Currency, Result<BigDecimal>?>>) {
withContext(Dispatchers.Main) {
results.map {
when (it.second) {
is Result.Success -> {
val rate = it.first to (it.second as Result.Success<BigDecimal>).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<BigDecimal>).data
dispatchOnMain(WalletAction.LoadFiatRate.Success(rate))
}
is Result.Failure -> dispatchOnMain(WalletAction.LoadFiatRate.Failure)
null -> {}
}
}
}

View file

@ -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<CreateWalletResponse>()
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit) {
if (curves.isEmpty()){
callback(CompletionResult.Failure(TangemSdkError.WalletIsNotCreated()))
return
}
val curve = curves[createdWalletsResponses.size]
createWallet(curve, session, callback)
}

View file

@ -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<String>()
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<String> = logs.toList()
fun getLogs(): List<String> = 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<EmailWalletInfo>()
@ -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<WalletManager>) {
@ -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 = ""))
}

View file

@ -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) }
}

View file

@ -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))
}
}
}

View file

@ -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<Currency>? = null,
) : WalletAction() {

View file

@ -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<WarningMessage> = mutableListOf(),
val wallets: List<WalletData> = emptyList(),
val walletsData: List<WalletData> = emptyList(),
val walletManagers: List<WalletManager> = 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<Currency>
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<WalletData> {
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<WalletData>): List<WalletData> {
val remainingWallets: MutableList<WalletData> = 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(

View file

@ -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 ->

View file

@ -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

View file

@ -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)
}

View file

@ -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
)
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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 -> {

View file

@ -63,7 +63,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
}.select { it.walletState }
}
walletView.setFragment(this, binding)
store.dispatch(WalletAction.UpdateWallet(force = false))
// store.dispatch(WalletAction.UpdateWallet(force = false))
store.dispatch(WalletAction.LoadWallet())
}
override fun onStop() {

View file

@ -57,8 +57,8 @@ class WalletAdapter
fun bind(wallet: WalletData) = with(binding) {
tvCurrency.text = wallet.currencyData.currency
tvAmount.text = wallet.currencyData.amount?.takeWhile { !it.isWhitespace() }
tvCurrencySymbol.text = wallet.currencyData.amount?.takeLastWhile { !it.isWhitespace() }
tvAmount.text = wallet.currencyData.amountFormatted?.takeWhile { !it.isWhitespace() }
tvCurrencySymbol.text = wallet.currencyData.amountFormatted?.takeLastWhile { !it.isWhitespace() }
tvAmountFiat.text = wallet.currencyData.fiatAmountFormatted
tvExchangeRate.text = wallet.fiatRateString
cardWallet.setOnClickListener {
@ -77,7 +77,7 @@ class WalletAdapter
BalanceStatus.VerifiedOnline, BalanceStatus.SameCurrencyTransactionInProgress -> hideWarning()
BalanceStatus.Loading -> {
hideWarning()
if (wallet.currencyData.amount == null) {
if (wallet.currencyData.amountFormatted == null) {
tvExchangeRate.text = root.getString(R.string.wallet_balance_loading)
}
}

View file

@ -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) })
)

View file

@ -99,8 +99,8 @@
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
<include
android:id="@+id/l_rent_warning"
layout="@layout/layout_wallet_details_rent"
android:id="@+id/l_warning"
layout="@layout/layout_wallet_details_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@ -168,7 +168,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_sell"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/l_rent_warning"
app:layout_constraintTop_toBottomOf="@+id/l_warning"
app:layout_constraintVertical_bias="1" />
<com.google.android.material.button.MaterialButton
@ -185,7 +185,7 @@
app:layout_constraintEnd_toStartOf="@id/btn_confirm"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/btn_trade"
app:layout_constraintTop_toBottomOf="@+id/l_rent_warning"
app:layout_constraintTop_toBottomOf="@+id/l_warning"
app:layout_constraintVertical_bias="1" />
<com.google.android.material.button.MaterialButton
@ -201,7 +201,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btn_sell"
app:layout_constraintTop_toBottomOf="@+id/l_rent_warning"
app:layout_constraintTop_toBottomOf="@+id/l_warning"
app:layout_constraintVertical_bias="1" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -13,7 +13,7 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_rent_warning_title"
android:id="@+id/tv_warning_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@ -27,7 +27,7 @@
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_rent_warning_message"
android:id="@+id/tv_warning_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@ -39,7 +39,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_rent_warning_title"
app:layout_constraintTop_toBottomOf="@+id/tv_warning_title"
tools:text="@string/solana_rent_warning" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -5,7 +5,7 @@
<string name="shop_2_cards">2 cards</string>
<string name="shop_shipping">Shipping</string>
<string name="shop_free">Free</string>
<string name="shop_i_have_a_promo_code">I have a promo code</string>
<string name="shop_i_have_a_promo_code">I have a promo code...</string>
<string name="shop_total">Total</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_buy_now">Buy now</string>
@ -23,18 +23,19 @@
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_backup_title">Ultra Secure Backup</string>
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
<string name="story_backup_description">Up to <b>3 physical cards</b> to one wallet</string>
<string name="story_currencies_title">Thousands of Currencies</string>
<string name="story_currencies_description">A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously all in one card</string>
<string name="story_web3_title">DeFi Compatible</string>
<string name="story_web3_description">Exchange, buy NFTs, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_finish_title">The Wallet for Everyone</string>
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
<string name="home_button_order">Order card</string>
<string name="home_button_order">Order</string>
<string name="home_button_search_tokens">Search tokens</string>
<string name="search_tokens_title">Search tokens</string>
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>

View file

@ -5,7 +5,7 @@
<string name="shop_2_cards">2 cards</string>
<string name="shop_shipping">Shipping</string>
<string name="shop_free">Free</string>
<string name="shop_i_have_a_promo_code">I have a promo code</string>
<string name="shop_i_have_a_promo_code">I have a promo code...</string>
<string name="shop_total">Total</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_buy_now">Buy now</string>
@ -23,18 +23,19 @@
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_backup_title">Ultra Secure Backup</string>
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
<string name="story_backup_description">Up to <b>3 physical cards</b> to one wallet</string>
<string name="story_currencies_title">Thousands of Currencies</string>
<string name="story_currencies_description">A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously all in one card</string>
<string name="story_web3_title">DeFi Compatible</string>
<string name="story_web3_description">Exchange, buy NFTs, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_finish_title">The Wallet for Everyone</string>
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
<string name="home_button_order">Order card</string>
<string name="home_button_order">Order</string>
<string name="home_button_search_tokens">Search tokens</string>
<string name="search_tokens_title">Search tokens</string>
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
</resources>

View file

@ -5,7 +5,7 @@
<string name="shop_2_cards">2 cards</string>
<string name="shop_shipping">Shipping</string>
<string name="shop_free">Free</string>
<string name="shop_i_have_a_promo_code">I have a promo code</string>
<string name="shop_i_have_a_promo_code">I have a promo code...</string>
<string name="shop_total">Total</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_buy_now">Buy now</string>
@ -23,18 +23,19 @@
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_backup_title">Ultra Secure Backup</string>
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
<string name="story_backup_description">Up to <b>3 physical cards</b> to one wallet</string>
<string name="story_currencies_title">Thousands of Currencies</string>
<string name="story_currencies_description">A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously all in one card</string>
<string name="story_web3_title">DeFi Compatible</string>
<string name="story_web3_description">Exchange, buy NFTs, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_finish_title">The Wallet for Everyone</string>
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
<string name="home_button_order">Order card</string>
<string name="home_button_order">Order</string>
<string name="home_button_search_tokens">Search tokens</string>
<string name="search_tokens_title">Search tokens</string>
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>

View file

@ -23,7 +23,7 @@
<string name="story_awe_title">Революционный аппаратный кошелек</string>
<string name="story_awe_description">Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.</string>
<string name="story_backup_title">Все ключи в безопасности</string>
<string name="story_backup_description">До трех карт с одним кошельком</string>
<string name="story_backup_description">До <b>трех карт</b> с одним кошельком</string>
<string name="story_currencies_title">Тысячи криптовалют</string>
<string name="story_currencies_description">Аппаратный кошелек для ваших биткоинов, эфира и многих других валют одновременно — все в одной карте</string>
<string name="story_web3_title">Поддержка DeFi</string>
@ -35,6 +35,7 @@
<string name="search_tokens_title">Поиск токенов</string>
<string name="alert_demo_message">Приложение работает в демонстрационном режиме. Средства на всех счетах ненастоящие.</string>
<string name="alert_demo_feature_disabled">Эта функция недоступна в демонстрационном режиме</string>
<string name="token_details_send_blocked_fee_format">Недостаточно средств для комиссии на вашем %s кошельке для отправки транзакции. Сначала пополните свой %s кошелек.</string>
<string name="currency_subtitle_expanded">Доступные сети</string>
</resources>

View file

@ -5,7 +5,7 @@
<string name="shop_2_cards">2 cards</string>
<string name="shop_shipping">Shipping</string>
<string name="shop_free">Free</string>
<string name="shop_i_have_a_promo_code">I have a promo code</string>
<string name="shop_i_have_a_promo_code">I have a promo code...</string>
<string name="shop_total">Total</string>
<string name="shop_other_payment_methods">Other payment methods</string>
<string name="shop_buy_now">Buy now</string>
@ -23,18 +23,19 @@
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
<string name="story_backup_title">Ultra Secure Backup</string>
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
<string name="story_backup_description">Up to <b>3 physical cards</b> to one wallet</string>
<string name="story_currencies_title">Thousands of Currencies</string>
<string name="story_currencies_description">A hardware wallet for your Bitcoin, Ethereum and many more currencies simultaneously all in one card</string>
<string name="story_web3_title">DeFi Compatible</string>
<string name="story_web3_description">Exchange, buy NFTs, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_finish_title">The Wallet for Everyone</string>
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
<string name="home_button_order">Order card</string>
<string name="home_button_order">Order</string>
<string name="home_button_search_tokens">Search tokens</string>
<string name="search_tokens_title">Search tokens</string>
<string name="alert_demo_message">You are currently running in Demo mode. All funds are not real.</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="token_details_send_blocked_fee_format">Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first.</string>
<string name="currency_subtitle_expanded">Available networks</string>
</resources>