Updated on 2026-08-14

This commit is contained in:
Tangem 2023-06-07 08:53:24 +03:00
parent 99b4c180cc
commit 1a4e8d8f20
11 changed files with 142 additions and 102 deletions

View file

@ -22,6 +22,10 @@ sealed class TapError(
object UnknownError : TapError(R.string.send_error_unknown)
open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
data class UnsupportedState(
val stateError: String,
) : TapError(R.string.common_custom_string, listOf("Unsupported state: $stateError"))
object ScanCardError : TapError(R.string.scan_card_error)
object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle)
object NoInternetConnection : TapError(R.string.wallet_notification_no_internet)
@ -38,11 +42,6 @@ sealed class TapError(
data class DustAmount(override val args: List<Any>) : TapError(R.string.send_error_dust_amount_format)
object DustChange : TapError(R.string.send_error_dust_change)
data class UnsupportedState(
val stateError: String,
val customMessage: String = "Unsupported state:",
) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError"))
sealed class WalletManager {
object CreationError : CustomError("Can't create wallet manager")
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)

View file

@ -79,6 +79,7 @@ data class WalletDataModel(
data class Unreachable(
override val errorMessage: String?,
override val amount: BigDecimal = BigDecimal.ZERO,
) : Status() {
override val isErrorStatus: Boolean = true
}

View file

@ -170,7 +170,10 @@ internal fun List<WalletDataModel>.updateWithMissedDerivation(): List<WalletData
internal fun List<WalletDataModel>.updateWithUnreachable(): List<WalletDataModel> {
return this.map { walletData ->
walletData.copy(
status = WalletDataModel.Unreachable(errorMessage = null),
status = WalletDataModel.Unreachable(
errorMessage = null,
amount = walletData.status.amount,
),
)
}
}

View file

@ -26,9 +26,9 @@ interface SendScreenActionUi : SendScreenAction
object ReleaseSendState : Action
data class PrepareSendScreen(
val walletManager: WalletManager,
val coinAmount: Amount?,
val coinRate: BigDecimal?,
val walletManager: WalletManager?,
val tokenAmount: Amount? = null,
val tokenRate: BigDecimal? = null,
) : SendScreenAction

View file

@ -85,7 +85,7 @@ private class EmptyReducer : SendInternalReducer {
private class PrepareSendScreenStatesReducer : SendInternalReducer {
override fun handle(action: SendScreenAction, sendState: SendState): SendState {
val prepareAction = action as PrepareSendScreen
val walletManager = action.walletManager!!
val walletManager = action.walletManager
val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!!
val decimals = amountToExtract.decimals
val feePaidInNetworkCurrency = isFeePaidInNetworkCurrency(walletManager.wallet.blockchain)

View file

@ -98,7 +98,7 @@ sealed class WalletAction : Action {
val swapAllowed: Boolean,
) : DialogAction()
data class ChooseCurrency(val amounts: List<Amount>?) : DialogAction()
data class ChooseCurrency(val amounts: List<Amount>) : DialogAction()
data class RussianCardholdersWarningDialog(
val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null,
) : DialogAction()

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.wallet.redux.middlewares
import androidx.core.os.bundleOf
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.AmountType
import com.tangem.common.extensions.guard
@ -129,13 +130,16 @@ class TradeCryptoMiddleware {
val selectedWalletData = store.state.walletState.selectedWalletData ?: return
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency)))
val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency).guard {
FirebaseCrashlytics.getInstance().recordException(IllegalStateException("WalletManager is null"))
return
}
val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency)
store.dispatchOnMain(
PrepareSendScreen(
coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin),
coinRate = selectedWalletData.fiatRate,
walletManager = walletManager,
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
coinRate = selectedWalletData.fiatRate,
),
)
store.dispatchOnMain(

View file

@ -37,6 +37,8 @@ class WalletDialogsMiddleware {
)
}
is WalletAction.DialogAction.ChooseCurrency -> {
if (action.amounts.isEmpty()) return
store.dispatchDialogShow(
WalletDialog.SelectAmountToSendDialog(
amounts = action.amounts,

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.CompletionResult
import com.tangem.common.doOnSuccess
@ -32,7 +33,6 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.ifActive
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@ -147,26 +147,76 @@ class WalletMiddleware {
store.dispatchOpenUrl(action.exploreUrl)
}
is WalletAction.Send -> {
val walletStore = walletState.getWalletStore(walletState.selectedCurrency)
val selectedWalletData = walletState.selectedWalletData
val walletManager = walletStore?.walletManager
if (walletStore == null || walletManager == null || selectedWalletData == null) {
val error = TapError.UnsupportedState(
"WalletAction.Send: walletStore or selectedWalletData or walletManager is null",
)
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
store.dispatchErrorNotification(error)
return
}
if (!networkConnectionManager.isOnline) {
store.dispatchErrorNotification(TapError.NoInternetConnection)
return
}
val newAction = prepareSendAction(action.amount, store.state.walletState)
if (newAction is PrepareSendScreen && newAction.walletManager == null) {
store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home))
FirebaseCrashlytics.getInstance().recordException(
IllegalStateException("PrepareSendScreen: walletManager is null"),
)
store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found)
} else {
store.dispatch(newAction)
if (newAction is PrepareSendScreen) {
store.state.walletState.selectedWalletData?.currency?.let { currency ->
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency)))
}
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
val currency = selectedWalletData.currency
val initSendStateAction = if (action.amount == null) {
val sendableAmounts = walletManager.wallet.getSendableAmounts()
if (sendableAmounts.isEmpty()) {
val error = TapError.UnsupportedState("WalletAction.Send: Nothing to send")
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
store.dispatchErrorNotification(error)
return
}
if (walletState.isMultiwalletAllowed) {
val amountToSend = sendableAmounts.find { it.currencySymbol == currency.currencySymbol }
if (amountToSend == null) {
val error = TapError.UnsupportedState("WalletAction.Send: Amount to send is null")
FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError))
store.dispatchErrorNotification(error)
return
}
makeInitSendStateActionByCurrency(
currency = currency,
amount = amountToSend,
walletStore = walletStore,
walletManager = walletManager,
selectedWalletData = selectedWalletData,
)
} else {
val isSingleAmount = sendableAmounts.size == 1
if (isSingleAmount) {
makeInitSendStateActionByAmount(
amount = sendableAmounts.first(),
walletStore = walletStore,
walletManager = walletManager,
selectedWalletData = selectedWalletData,
)
} else {
store.dispatch(WalletAction.DialogAction.ChooseCurrency(sendableAmounts))
return
}
}
} else {
// action.amount received from the ChooseCurrency dialog
makeInitSendStateActionByAmount(
amount = action.amount,
walletManager = walletManager,
walletStore = walletStore,
selectedWalletData = selectedWalletData,
)
}
Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency)))
store.dispatch(initSendStateAction)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Send))
}
is WalletAction.ShowSaveWalletIfNeeded -> {
showSaveWalletIfNeeded()
@ -222,6 +272,59 @@ class WalletMiddleware {
}
}
private fun makeInitSendStateActionByAmount(
amount: Amount,
walletStore: WalletStoreModel,
walletManager: WalletManager,
selectedWalletData: WalletDataModel,
): PrepareSendScreen = when (amount.type) {
AmountType.Coin ->
PrepareSendScreen(
walletManager = walletManager,
coinAmount = amount,
coinRate = selectedWalletData.fiatRate,
)
is AmountType.Token -> {
PrepareSendScreen(
walletManager = walletManager,
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
coinRate = walletStore.blockchainWalletData.fiatRate,
tokenAmount = amount,
tokenRate = selectedWalletData.fiatRate,
)
}
AmountType.Reserve -> {
val exception = IllegalStateException("WalletAction.Send: Reserve can't be sent")
FirebaseCrashlytics.getInstance().recordException(exception)
throw exception
}
}
private fun makeInitSendStateActionByCurrency(
currency: Currency,
amount: Amount,
walletStore: WalletStoreModel,
walletManager: WalletManager,
selectedWalletData: WalletDataModel,
): PrepareSendScreen = when (currency) {
is Currency.Blockchain -> {
PrepareSendScreen(
walletManager = walletManager,
coinAmount = amount,
coinRate = selectedWalletData.fiatRate,
)
}
is Currency.Token -> {
PrepareSendScreen(
walletManager = walletManager,
coinAmount = walletManager.wallet.amounts[AmountType.Coin],
coinRate = walletStore.blockchainWalletData.fiatRate,
tokenAmount = amount,
tokenRate = selectedWalletData.fiatRate,
)
}
}
private fun changeSelectedWalletAddress(type: AddressType, state: WalletState) {
val selectedUserWalletId = userWalletsListManager.selectedUserWalletSync?.walletId.guard {
Timber.e("Unable to change selected wallet address, no user wallet selected")
@ -284,72 +387,4 @@ class WalletMiddleware {
}
}
}
private fun prepareSendAction(amount: Amount?, state: WalletState?): Action {
val selectedWalletData = state?.selectedWalletData
val currency = selectedWalletData?.currency
val walletStore = state?.getWalletStore(currency)
return if (amount != null) {
if (amount.type is AmountType.Token) {
prepareSendActionForToken(amount, selectedWalletData, walletStore)
} else {
PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletStore?.walletManager)
}
} else {
val amounts = walletStore?.walletManager?.wallet?.getSendableAmounts()
if (currency != null && state.isMultiwalletAllowed) {
when (currency) {
is Currency.Blockchain -> {
val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency }
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
PrepareSendScreen(
coinAmount = amountToSend,
coinRate = selectedWalletData.fiatRate,
walletManager = walletStore.walletManager,
)
}
is Currency.Token -> {
val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol }
?: return WalletAction.DialogAction.ChooseCurrency(amounts)
prepareSendActionForToken(
amount = amountToSend,
selectedWalletData = selectedWalletData,
walletStore = walletStore,
)
}
}
} else {
val amountsSize = amounts?.size ?: 0
if (amountsSize > 1) {
WalletAction.DialogAction.ChooseCurrency(amounts)
} else {
val amountToSend = amounts?.first()
PrepareSendScreen(
coinAmount = amountToSend,
coinRate = selectedWalletData?.fiatRate,
walletManager = walletStore?.walletManager,
)
}
}
}
}
private fun prepareSendActionForToken(
amount: Amount,
selectedWalletData: WalletDataModel?,
walletStore: WalletStoreModel?,
): PrepareSendScreen {
val coinRate = walletStore?.blockchainWalletData?.fiatRate
val tokenRate = selectedWalletData?.fiatRate
val coinAmount = walletStore?.walletManager?.wallet?.amounts?.get(AmountType.Coin)
return PrepareSendScreen(
coinAmount = coinAmount,
coinRate = coinRate,
walletManager = walletStore?.walletManager,
tokenAmount = amount,
tokenRate = tokenRate,
)
}
}

View file

@ -6,7 +6,7 @@ import com.tangem.tap.common.redux.StateDialog
import com.tangem.wallet.R
sealed interface WalletDialog : StateDialog {
data class SelectAmountToSendDialog(val amounts: List<Amount>?) : WalletDialog
data class SelectAmountToSendDialog(val amounts: List<Amount>) : WalletDialog
object SignedHashesMultiWalletDialog : WalletDialog
data class ChooseTradeActionDialog(
val buyAllowed: Boolean,

View file

@ -5,11 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.*
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.blockchain.common.Amount
import com.tangem.tap.common.extensions.toFormattedString
@ -55,7 +51,7 @@ class AmountToSendBottomSheetDialog(
}
}
class ChooseAmountAdapter : ListAdapter<Amount, ChooseAmountAdapter.AmountViewHolder>(DiffUtilCallback) {
private class ChooseAmountAdapter : ListAdapter<Amount, ChooseAmountAdapter.AmountViewHolder>(DiffUtilCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder {
val binding = ItemWalletAmountToSendBinding.inflate(