Updated on 2026-08-14

This commit is contained in:
Tangem 2022-03-25 13:51:50 +00:00
commit 3752563b2f
26 changed files with 449 additions and 420 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-139'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-139'

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

@ -3,7 +3,6 @@ package com.tangem.tap.domain
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.*
import com.tangem.common.services.Result
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
@ -42,20 +41,31 @@ class TapWalletManager {
by lazy { WalletManagerFactory(blockchainSdkConfig) }
suspend fun loadWalletData(walletManager: WalletManager) {
handleUpdateWalletResult(walletManager.safeUpdate(), walletManager)
}
suspend fun updateWallet(walletManager: WalletManager) {
val result = walletManager.safeUpdate()
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.Success -> {
checkForRentWarning(walletManager)
store.dispatch(WalletAction.LoadWallet.Success(result.data))
}
is Result.Failure -> {
when (result.error) {
is TapError.WalletManagerUpdate.NoAccountError -> {
store.dispatch(WalletAction.LoadWallet.NoAccount(
walletManager.wallet,
(result.error as TapError.WalletManagerUpdate.NoAccountError).customMessage
))
}
else -> {
store.dispatch(WalletAction.LoadWallet.Failure(
walletManager.wallet,
result.error.localizedMessage
))
}
}
}
}
}
}
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, wallet: Wallet) {
@ -65,11 +75,6 @@ class TapWalletManager {
loadFiatRate(fiatCurrency, currencies)
}
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, currency: Currency) {
val currencies = listOf(currency)
loadFiatRate(fiatCurrency, currencies)
}
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName, currencies: List<Currency>) {
val results = mutableListOf<Pair<Currency, Result<BigDecimal>?>>()
currencies.forEach {
@ -79,7 +84,7 @@ class TapWalletManager {
}
suspend fun onCardScanned(data: ScanResponse) {
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card)
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data)
updateConfigManager(data)
withContext(Dispatchers.Main) {
@ -224,36 +229,6 @@ 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) {
val rentProvider = walletManager as? RentProvider ?: return

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(10000)
withContext(Dispatchers.Main) {
dispatch(WalletAction.UpdateWallet(walletManager.wallet.blockchain))
dispatch(WalletAction.LoadWallet(walletManager.wallet.blockchain))
}
}
}

View file

@ -74,14 +74,8 @@ sealed class WalletAction : Action {
class CheckRemainingSignatures(val remainingSignatures: Int?) : Warnings()
}
data class UpdateWallet(val blockchain: Blockchain? = null) : 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 currency: Currency? = null,
val wallet: Wallet? = null, val currencyList: List<Currency>? = null,
) : WalletAction() {
data class Success(val fiatRate: Pair<Currency, BigDecimal?>) : WalletAction()
object Failure : 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

@ -3,7 +3,6 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.navigation.AppScreen
@ -47,7 +46,7 @@ class MultiWalletMiddleware {
globalState.scanResponse?.card?.cardId?.let {
currenciesRepository.saveAddedToken(it, action.token)
}
addToken(action.token, walletState, globalState)
addTokens(listOf(action.token), walletState, globalState)
}
is WalletAction.MultiWallet.AddTokens -> {
addTokens(action.tokens, walletState, globalState)
@ -62,7 +61,9 @@ class MultiWalletMiddleware {
}
}
}
store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain)))
store.dispatch(WalletAction.LoadFiatRate(
currencyList = listOf(Currency.Blockchain(action.blockchain)))
)
store.dispatch(WalletAction.LoadWallet(action.blockchain)
)
}
@ -172,80 +173,39 @@ class MultiWalletMiddleware {
}
}
private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) {
private fun addTokens(tokens: List<Token>, walletState: WalletState?, globalState: GlobalState?) {
val scanResponse = globalState?.scanResponse ?: return
val wmFactory = globalState.tapWalletManager.walletManagerFactory
val walletManager = walletState?.getWalletManager(token)
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
scanResponse,
token.blockchain
)?.also { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
}
store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Token(token)))
scope.launch {
when (val result = walletManager?.addToken(token)) {
is Result.Success -> {
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(result.data, token))
val groupedTokens = tokens.groupBy { it.blockchain }
val walletManagers = groupedTokens.mapNotNull { entry ->
val blockchain = entry.key
val tokensList = entry.value
val walletManager = walletState?.getWalletManager(blockchain)
?: wmFactory.makeWalletManagerForApp(scanResponse, blockchain)?.also {
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain))
}
is Result.Failure -> {
when (val result = walletManager.safeUpdate()) {
is com.tangem.common.services.Result.Success -> {
val tokenAmount = result.data.getTokenAmount(token) ?: return@launch
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(tokenAmount, token))
}
is com.tangem.common.services.Result.Failure -> {
}
store.dispatch(WalletAction.LoadFiatRate(currencyList = tokensList.map { Currency.Token(it) }))
walletManager?.apply { addTokens(tokensList) }
}
scope.launch {
walletManagers.forEach { walletManager ->
when (val result = walletManager.safeUpdate()) {
is com.tangem.common.services.Result.Success -> {
val wallet = result.data
wallet.getTokens()
.filter { tokens.contains(it) }
.mapNotNull { token -> wallet.getTokenAmount(token)?.let { Pair(token, it) } }
.forEach {
withContext(Dispatchers.Main) {
store.dispatch(WalletAction.MultiWallet.TokenLoaded(it.second, it.first))
}
}
}
is com.tangem.common.services.Result.Failure -> {}
}
}
}
}
private fun addTokens(
tokens: List<Token>,
walletState: WalletState?,
globalState: GlobalState?,
) {
val scanResponse = globalState?.scanResponse ?: return
val tokensWithManagers = tokens.map { token ->
val walletManager = walletState?.getWalletManager(token)
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
scanResponse,
token.blockchain
)?.also { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
}
store.dispatch(
WalletAction.LoadFiatRate(currency = Currency.Token(token))
)
TokenWithManager(token, walletManager)
}
scope.launch {
tokensWithManagers.forEach {
when (val result = it.walletManager?.addToken(it.token)) {
is Result.Success -> {
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(result.data, it.token))
}
is Result.Failure -> {
when (val result = it.walletManager.safeUpdate()) {
is com.tangem.common.services.Result.Success -> {
val tokenAmount = result.data.getTokenAmount(it.token) ?: return@launch
store.dispatchOnMain(WalletAction.MultiWallet.TokenLoaded(tokenAmount, it.token))
}
is com.tangem.common.services.Result.Failure -> {
}
}
}
}
}
}
}
private data class TokenWithManager(val token: Token, val walletManager: 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
@ -63,9 +66,9 @@ class WalletMiddleware {
is WalletAction.LoadWallet -> {
scope.launch {
if (action.blockchain == null) {
walletState.walletManagers.map { walletManager ->
async { globalState.tapWalletManager.loadWalletData(walletManager) }
}.awaitAll()
walletState.walletManagers.map { walletManager ->
async { globalState.tapWalletManager.loadWalletData(walletManager) }
}.awaitAll()
} else {
val walletManager = walletState.getWalletManager(action.blockchain)
walletManager?.let { globalState.tapWalletManager.loadWalletData(it) }
@ -84,23 +87,19 @@ 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(
globalState.appCurrency, action.wallet
)
tapWalletManager.loadFiatRate(fiatAppCurrency, action.wallet)
}
action.currency != null -> {
globalState.tapWalletManager.loadFiatRate(
globalState.appCurrency, action.currency
)
action.currencyList != null -> {
tapWalletManager.loadFiatRate(fiatAppCurrency, action.currencyList)
}
else -> {
globalState.tapWalletManager.loadFiatRate(
fiatCurrency = globalState.appCurrency,
currencies = walletState.wallets.mapNotNull { it.currency }
)
val currencyList = walletState.walletsData.map { it.currency }
tapWalletManager.loadFiatRate(fiatAppCurrency, currencyList)
}
}
}
@ -127,22 +126,6 @@ class WalletMiddleware {
}
}
}
is WalletAction.UpdateWallet -> {
if (action.blockchain != null) {
scope.launch {
val walletManager = walletState.getWalletManager(action.blockchain)
walletManager?.let { globalState.tapWalletManager.updateWallet(it) }
}
} else {
scope.launch {
if (walletState.state == ProgressState.Done) {
walletState.walletManagers.map { walletManager ->
globalState.tapWalletManager.updateWallet(walletManager)
}
}
}
}
}
is WalletAction.Scan -> {
store.dispatch(HomeAction.ShouldScanCardOnResume(true))
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
@ -170,7 +153,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)
@ -203,7 +186,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
@ -230,12 +213,12 @@ class WalletMiddleware {
when (currency) {
is Currency.Blockchain -> {
val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency }
?: return WalletAction.Send.ChooseCurrency(amounts)
?: return WalletAction.Send.ChooseCurrency(amounts)
PrepareSendScreen(amountToSend, selectedWalletData.fiatRate, walletManager)
}
is Currency.Token -> {
val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol }
?: return WalletAction.Send.ChooseCurrency(amounts)
?: return WalletAction.Send.ChooseCurrency(amounts)
prepareSendActionForToken(amountToSend, state, selectedWalletData, wallet, walletManager)
}
}

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(
@ -108,14 +108,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(
@ -128,14 +128,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(
@ -153,7 +149,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 -> {
@ -188,7 +184,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 -> {
@ -228,7 +224,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
@ -243,16 +239,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))
)
}
}
@ -343,7 +339,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(
@ -361,7 +357,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(
@ -376,7 +372,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,7 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
}.select { it.walletState }
}
walletView.setFragment(this, binding)
store.dispatch(WalletAction.UpdateWallet())
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,11 +94,11 @@ 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.SetAddedCurrencies(state.wallets))
store.dispatch(TokensAction.SetAddedCurrencies(state.walletsData))
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddTokens))
}
handleErrorStates(state = state, binding = binding, fragment = fragment)

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,16 +23,17 @@
<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>
</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,16 +23,17 @@
<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>
</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,16 +23,17 @@
<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>
</resources>

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,4 +35,5 @@
<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>
</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,16 +23,17 @@
<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>
</resources>