diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index b0ee231286..fc7bdedd39 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -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) : 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) diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt index fe751acee5..e6782972eb 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt @@ -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 } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt index e342ff4e70..396329ad4f 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt @@ -170,7 +170,10 @@ internal fun List.updateWithMissedDerivation(): List.updateWithUnreachable(): List { return this.map { walletData -> walletData.copy( - status = WalletDataModel.Unreachable(errorMessage = null), + status = WalletDataModel.Unreachable( + errorMessage = null, + amount = walletData.status.amount, + ), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 719cd930c3..5dee274734 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 48eafac40f..4134e8c148 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index db13376244..8db98a4ee0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -98,7 +98,7 @@ sealed class WalletAction : Action { val swapAllowed: Boolean, ) : DialogAction() - data class ChooseCurrency(val amounts: List?) : DialogAction() + data class ChooseCurrency(val amounts: List) : DialogAction() data class RussianCardholdersWarningDialog( val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null, ) : DialogAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 6020f4c151..0c56d30f51 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -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( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index c21a15ce98..b381ec0365 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -37,6 +37,8 @@ class WalletDialogsMiddleware { ) } is WalletAction.DialogAction.ChooseCurrency -> { + if (action.amounts.isEmpty()) return + store.dispatchDialogShow( WalletDialog.SelectAmountToSendDialog( amounts = action.amounts, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index b71a2eccc9..c7bde94141 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -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, - ) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt index c772684c48..88fbb86932 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt @@ -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?) : WalletDialog + data class SelectAmountToSendDialog(val amounts: List) : WalletDialog object SignedHashesMultiWalletDialog : WalletDialog data class ChooseTradeActionDialog( val buyAllowed: Boolean, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt index 160d3fc40e..75cbe3010a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/AmountToSendBottomSheetDialog.kt @@ -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(DiffUtilCallback) { +private class ChooseAmountAdapter : ListAdapter(DiffUtilCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder { val binding = ItemWalletAmountToSendBinding.inflate(