From a0ec01abfe5ed1572268715855cb35f0fa1fde86 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 15 Sep 2020 17:18:55 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/tap/common/redux/AppReducer.kt | 4 +- .../common/redux/NotificationsMiddleware.kt | 14 ++ .../tap/common/toggleWidget/ToggleWidget.kt | 85 ++++++++++ .../common/toggleWidget/progressModifiers.kt | 145 ++++++++++++++++++ .../com/tangem/tap/domain/TangemSdkManager.kt | 18 --- .../java/com/tangem/tap/domain/TapErrors.kt | 3 +- .../com/tangem/tap/domain/tasks/SendTask.kt | 114 -------------- .../features/send/redux/SendScreenAction.kt | 20 ++- .../redux/middlewares/AmountMiddleware.kt | 2 + .../redux/middlewares/RequestFeeMiddleware.kt | 3 + .../send/redux/middlewares/SendMiddleware.kt | 56 +++++-- .../{SendReducer.kt => SendScreenReducer.kt} | 21 ++- .../features/send/redux/states/SendState.kt | 8 +- .../tap/features/send/ui/SendFragment.kt | 26 +++- .../stateSubscribers/SendStateSubscriber.kt | 20 ++- app/src/main/res/layout/fragment_send.xml | 36 +++-- app/src/main/res/values/strings.xml | 2 + 17 files changed, 400 insertions(+), 177 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt create mode 100644 app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt delete mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/SendTask.kt rename app/src/main/java/com/tangem/tap/features/send/redux/reducers/{SendReducer.kt => SendScreenReducer.kt} (83%) diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index b98107376d..68a809b4ec 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -2,7 +2,7 @@ package com.tangem.tap.common.redux import com.tangem.tap.common.redux.global.globalReducer import com.tangem.tap.common.redux.navigation.NavigationReducer -import com.tangem.tap.features.send.redux.reducers.SendReducer +import com.tangem.tap.features.send.redux.reducers.SendScreenReducer import com.tangem.tap.features.wallet.redux.WalletReducer import org.rekotlin.Action @@ -14,7 +14,7 @@ fun appReducer(action: Action, state: AppState?): AppState { navigationState = NavigationReducer.reduce(action, state), globalState = globalReducer(action, state), walletState = WalletReducer.reduce(action, state), - sendState = SendReducer.reduce(action, state.sendState) + sendState = SendScreenReducer.reduce(action, state.sendState) ) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt index 9511364f8a..177089ffba 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.redux +import android.widget.Toast import androidx.coordinatorlayout.widget.CoordinatorLayout import com.google.android.material.snackbar.Snackbar import com.tangem.tap.domain.TapError @@ -32,6 +33,12 @@ class NotificationsHandler(coordinatorLayout: CoordinatorLayout) { showNotification(it.context.getString(message)) } } + + fun showToastNotification(message: Int) { + baseLayout.get()?.let { + Toast.makeText(it.context, it.context.getString(message), Toast.LENGTH_LONG).show() + } + } } val notificationsMiddleware: Middleware = { dispatch, state -> @@ -40,6 +47,9 @@ val notificationsMiddleware: Middleware = { dispatch, state -> if (action is NotificationAction) { notificationsHandler?.showNotification(action.messageResource) } + if (action is ToastNotificationAction) { + notificationsHandler?.showToastNotification(action.messageResource) + } if (action is ErrorAction) { notificationsHandler?.showNotification(action.error.localizedMessage) } @@ -48,6 +58,10 @@ val notificationsMiddleware: Middleware = { dispatch, state -> } } +interface ToastNotificationAction : Action { + val messageResource: Int +} + interface NotificationAction : Action { val messageResource: Int } diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt new file mode 100644 index 0000000000..153a5eddc8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/ToggleWidget.kt @@ -0,0 +1,85 @@ +package com.tangem.merchant.common.toggleWidget + +import android.view.View +import android.view.ViewGroup + + +/** +[REDACTED_AUTHOR] + */ +interface ToggleState + +interface StateModifier { + fun stateChanged(container: ViewGroup, view: View, state: ToggleState) +} + +interface ToggleView { + val mainViewModifiers: MutableList + val toggleViewModifiers: MutableList + + fun setState(state: ToggleState, andApply: Boolean = true) + fun applyState() + fun getView(): View + fun getMainView(): View + fun getToggleView(): View +} + +class ToggleWidget : ToggleView { + private val container: ViewGroup + private val mainView: View + private val toggleView: View + + private var state: ToggleState + + constructor( + container: ViewGroup, + mainView: View, + toggleView: View, + initialState: ToggleState, + mainViewModifier: List = mutableListOf(), + loadingViewModifier: List = mutableListOf() + ) { + this.container = container + this.mainView = mainView + this.toggleView = toggleView + this.state = initialState + this.mainViewModifiers.addAll(mainViewModifier) + this.toggleViewModifiers.addAll(loadingViewModifier) + } + + constructor( + container: ViewGroup, + mainViewId: Int, + toggleViewId: Int, + initialState: ToggleState, + mainViewModifier: List = mutableListOf(), + loadingViewModifier: List = mutableListOf() + ) { + this.container = container + this.mainView = container.findViewById(mainViewId) + this.toggleView = container.findViewById(toggleViewId) + this.state = initialState + this.mainViewModifiers.addAll(mainViewModifier) + this.toggleViewModifiers.addAll(loadingViewModifier) + } + + override val mainViewModifiers: MutableList = mutableListOf() + + override val toggleViewModifiers: MutableList = mutableListOf() + + override fun setState(state: ToggleState, andApply: Boolean) { + this.state = state + if (andApply) applyState() + } + + override fun applyState() { + mainViewModifiers.forEach { it.stateChanged(container, mainView, state) } + toggleViewModifiers.forEach { it.stateChanged(container, toggleView, state) } + } + + override fun getView(): View = container + + override fun getMainView(): View = mainView + + override fun getToggleView(): View = toggleView +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt new file mode 100644 index 0000000000..bc66fa830b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/toggleWidget/progressModifiers.kt @@ -0,0 +1,145 @@ +package com.tangem.tap.common.toggleWidget + +import android.graphics.drawable.Drawable +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.core.widget.TextViewCompat +import com.google.android.material.button.MaterialButton +import com.tangem.merchant.common.toggleWidget.StateModifier +import com.tangem.merchant.common.toggleWidget.ToggleState +import com.tangem.tap.common.extensions.beginDelayedTransition + +/** +[REDACTED_AUTHOR] + */ + +sealed class ProgressState : ToggleState { + class Progress : ProgressState() + class None : ProgressState() +} + +class ReplaceTextStateModifier( + private val initialText: String, + private val replaceText: String = "" +) : StateModifier { + + override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { + val tv = view as? TextView ?: return + + when (state) { + is ProgressState.Progress -> { + tv.text = replaceText + } + is ProgressState.None -> { + tv.text = initialText + } + } + } +} + +class TextViewDrawableStateModifier( + private val initialDrawable: Drawable?, + private val replaceDrawable: Drawable?, + private val position: Int +) : StateModifier { + companion object { + val LEFT = 1 + val RIGHT = 2 + } + + override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { + val drawable = when (state) { + is ProgressState.Progress -> replaceDrawable + is ProgressState.None -> initialDrawable + else -> null + } + val drawableChanger = getChanger(view) + drawableChanger?.change(drawable, position) + } + + private fun getChanger(view: View): DrawableChanger? = when (view) { + is MaterialButton -> MaterialButtonChanger(view) + is Button -> TextViewChanger(view) + is TextView -> TextViewChanger(view) + else -> null + } + + internal interface DrawableChanger { + fun change(drawable: Drawable?, position: Int) + } + + internal class TextViewChanger(private val view: TextView) : DrawableChanger { + override fun change(drawable: Drawable?, position: Int) { + when (position) { + LEFT -> setLeft(drawable) + RIGHT -> setRight(drawable) + } + } + + private fun setLeft(drawable: Drawable?) { + if (drawable == null) { + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, 0, 0, 0, 0) + } else { + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, drawable, null, null, null) + } + } + + private fun setRight(drawable: Drawable?) { + if (drawable == null) { + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, 0, 0, 0, 0) + } else { + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(view, null, null, drawable, null) + } + } + } + + internal class MaterialButtonChanger(private val view: MaterialButton) : DrawableChanger { + override fun change(drawable: Drawable?, position: Int) { + when (position) { + LEFT -> setLeft(drawable) + RIGHT -> setRight(drawable) + } + } + + private fun setLeft(drawable: Drawable?) { + view.iconGravity = MaterialButton.ICON_GRAVITY_START + view.icon = drawable + } + + private fun setRight(drawable: Drawable?) { + view.iconGravity = MaterialButton.ICON_GRAVITY_END + view.icon = drawable + } + } +} + +class ShowHideStateModifier( + private val isShowOnLoading: Boolean = true, + private val typeOfHiding: Int = View.INVISIBLE +) : StateModifier { + + override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { + container.beginDelayedTransition() + view.visibility = when (state) { + is ProgressState.Progress -> if (isShowOnLoading) View.VISIBLE else typeOfHiding + is ProgressState.None -> if (isShowOnLoading) typeOfHiding else View.VISIBLE + else -> return + } + } +} + +class ClickableStateModifier( + private val isClickableOnLoading: Boolean = false +) : StateModifier { + + override fun stateChanged(container: ViewGroup, view: View, state: ToggleState) { + view.isClickable = when (state) { + is ProgressState.Progress -> isClickableOnLoading + is ProgressState.None -> !isClickableOnLoading + else -> return + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 0b76964de9..9fdfca3cd7 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -2,8 +2,6 @@ package com.tangem.tap.domain import androidx.activity.ComponentActivity import com.tangem.* -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.WalletManager import com.tangem.commands.CommandResponse import com.tangem.common.CompletionResult import com.tangem.common.extensions.CardType @@ -11,7 +9,6 @@ import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tasks.ScanNoteTask -import com.tangem.tap.domain.tasks.SendTask import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.util.* @@ -31,21 +28,6 @@ class TangemSdkManager(val activity: ComponentActivity) { return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask()) } - suspend fun send( - walletManager: WalletManager, - recipientAddress: String, - amountToSend: Amount, - feeAmount: Amount - ): CompletionResult { - return withContext(Dispatchers.IO) { - suspendCoroutine { continuation -> - tangemSdk.startSessionWithRunnable(SendTask(walletManager, recipientAddress, amountToSend, feeAmount)) { - continuation.resume(it) - } - } - } - } - private suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null ): CompletionResult = 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 29147d93c6..4f98257223 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -10,5 +10,6 @@ sealed class TapError(@StringRes val localizedMessage: Int): Throwable() { object UnknownBlockchain: TapError(R.string.wallet_unknown_blockchain) object NoInternetConnection: TapError(R.string.notification_no_internet) object InsufficientBalance: TapError(R.string.error_insufficient_balance) - object BlockchainInternalError: TapError(R.string.error_insufficient_balance) + object BlockchainInternalError: TapError(R.string.error_blockchain_internal) + object UnknownError: TapError(R.string.error_unknown) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/SendTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/SendTask.kt deleted file mode 100644 index e5cdbf959a..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/tasks/SendTask.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.tangem.tap.domain.tasks - -import com.tangem.CardSession -import com.tangem.CardSessionRunnable -import com.tangem.TangemError -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.TransactionSender -import com.tangem.blockchain.common.TransactionSigner -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.commands.CommandResponse -import com.tangem.commands.SignCommand -import com.tangem.commands.SignResponse -import com.tangem.common.CompletionResult -import com.tangem.tap.scope -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import timber.log.Timber -import kotlin.coroutines.resume - -class SendTask( - private val walletManager: WalletManager, - private val recipientAddress: String, - private val amountToSend: Amount, - private val feeAmount: Amount, -) : CardSessionRunnable { - - override val requiresPin2: Boolean = false - - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val txSender = walletManager as TransactionSender - - val verifyResult = walletManager.validateTransaction(amountToSend, feeAmount) - if (verifyResult.isNotEmpty()) { - callback(CompletionResult.Failure(InsufficientBalance())) - return - } - val txData = walletManager.createTransaction(amountToSend, feeAmount, recipientAddress) - - scope.launch { - when (val result = txSender.send(txData, SessionTransactionSigner(session))) { - is SimpleResult.Success -> callback(CompletionResult.Success(SendResponse())) - is SimpleResult.Failure -> { - callback(CompletionResult.Failure(BlockchainInternalErrorConverter.convert(result.error))) - } - } - } - } -} - -class SendResponse : CommandResponse - -class SessionTransactionSigner( - private val session: CardSession -) : TransactionSigner { - override suspend fun sign(hashes: Array, cardId: String): CompletionResult = - suspendCancellableCoroutine { continuation -> - Timber.d("sign transaction...") - SignCommand(hashes).run(session) { - if (continuation.isActive) { - continuation.resume(it) - } - } - } -} - -abstract class SendError : TangemError { -} - -class UnknownError : SendError() { - override val code: Int = 1000 - override var customMessage: String = "Unknown error" -} - -open class ThrowableError(throwable: Throwable?) : SendError() { - override val code: Int = 1001 - override var customMessage: String = throwable?.localizedMessage ?: "Unknown exception" -} - -class InsufficientBalance( - override var customMessage: String = "Insufficient balance" -) : SendError() { - override val code: Int = 1021 -} - -class BlockchainInternalError( - override var customMessage: String -) : SendError() { - override val code: Int = 2000 -} - -class BlockchainInternalErrorConverter { - companion object { - - private val stellarInternalErrors = mapOf( - "tx_bad_seq" to "Sequence number does not match source account", - "tx_too_late" to "The ledger closeTime was after the maxTime", - "tx_failedop_no_destination" to "The destination account does not exist", - "tx_no_source_account" to "Source account not found" - ) - - fun convert(throwable: Throwable?): TangemError { - val message = throwable?.message ?: return ThrowableError(throwable) - - val customMessage = getInternalBlockchainErrorMessage(message) - return if (customMessage == null) ThrowableError(throwable) - else BlockchainInternalError(customMessage) - } - - private fun getInternalBlockchainErrorMessage(message: String): String? { - return stellarInternalErrors[message] - } - } -} \ No newline at end of file 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 1676c1ee66..ba4308edc8 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 @@ -2,9 +2,12 @@ package com.tangem.tap.features.send.redux import com.tangem.blockchain.common.Amount import com.tangem.tap.common.redux.ErrorAction +import com.tangem.tap.common.redux.ToastNotificationAction import com.tangem.tap.domain.TapError import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType +import com.tangem.tap.features.send.redux.states.SendButtonState +import com.tangem.wallet.R import org.rekotlin.Action import java.math.BigDecimal @@ -38,7 +41,7 @@ sealed class AddressPayIdVerifyAction : SendScreenAction { } data class VerifyClipboard(val data: String?) : AddressPayIdVerifyAction() - data class ChangePasteBtnEnableState(val isEnabled: Boolean): AddressPayIdVerifyAction() + data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressPayIdVerifyAction() sealed class PayIdVerification : AddressPayIdVerifyAction() { data class SetError(val payId: String, val error: Error) : PayIdVerification() @@ -106,16 +109,11 @@ sealed class SendActionUi : SendScreenActionUi { } sealed class SendAction : SendScreenAction { - enum class Error { - INSUFFICIENT_BALANCE, BLOCKCHAIN_INTERNAL + + data class ChangeSendButtonState(val state: SendButtonState) : SendAction() + object SendSuccess : SendAction(), ToastNotificationAction { + override val messageResource: Int = R.string.send_transaction_complete } - object SendSuccess : SendAction() - - data class SendError(val sendError: Error) : SendAction(), ErrorAction { - override val error: TapError = when (sendError) { - Error.INSUFFICIENT_BALANCE -> TapError.InsufficientBalance - Error.BLOCKCHAIN_INTERNAL -> TapError.BlockchainInternalError - } - } + data class SendError(override val error: TapError) : SendAction(), ErrorAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt index eb86509ec1..259051b8a4 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt @@ -6,6 +6,7 @@ import com.tangem.tap.common.extensions.isGreaterThanOrEqual import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.send.redux.AmountAction import com.tangem.tap.features.send.redux.ReceiptAction +import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.store import org.rekotlin.Action @@ -43,6 +44,7 @@ class AmountMiddleware { dispatch(AmountAction.AmountVerification.SetError(checkResult.amount, checkResult.error)) } dispatch(ReceiptAction.RefreshReceipt) + dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState())) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt index 331a83fe37..f1d202cf46 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt @@ -9,6 +9,7 @@ import com.tangem.common.extensions.isZero import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.send.redux.FeeAction import com.tangem.tap.features.send.redux.ReceiptAction +import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.scope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -29,6 +30,7 @@ class RequestFeeMiddleware { dispatch(FeeAction.FeeCalculation.SetFeeError(FeeAction.Error.ADDRESS_OR_AMOUNT_IS_EMPTY)) dispatch(FeeAction.ChangeLayoutVisibility(main = false, controls = true, chipGroup = true)) dispatch(ReceiptAction.RefreshReceipt) + dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState())) return } @@ -67,6 +69,7 @@ class RequestFeeMiddleware { } } dispatch(ReceiptAction.RefreshReceipt) + dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState())) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 6944fd09aa..3f55dfddd5 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -1,17 +1,24 @@ package com.tangem.tap.features.send.redux.middlewares import com.tangem.blockchain.common.Amount -import com.tangem.common.CompletionResult +import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.extensions.Signer +import com.tangem.blockchain.extensions.SimpleResult import com.tangem.tap.common.redux.AppState +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.TapError import com.tangem.tap.features.send.redux.AddressPayIdActionUi.ChangeAddressOrPayId import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.* import com.tangem.tap.features.send.redux.AmountActionUi.CheckAmountToSend import com.tangem.tap.features.send.redux.FeeAction.RequestFee import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.send.redux.SendActionUi +import com.tangem.tap.features.send.redux.states.SendButtonState import com.tangem.tap.scope -import com.tangem.tap.tangemSdkManager +import com.tangem.tap.tangemSdk +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.rekotlin.Action import org.rekotlin.Middleware @@ -24,8 +31,8 @@ val sendMiddleware: Middleware = { dispatch, appState -> when (action) { is ChangeAddressOrPayId -> AddressPayIdMiddleware().handle(action.data, appState(), dispatch) is VerifyClipboard -> { - AddressPayIdMiddleware().handle(action.data, appState()){ - when(it) { + AddressPayIdMiddleware().handle(action.data, appState()) { + when (it) { is AddressVerification.SetWalletAddress, is PayIdVerification.SetPayIdWalletAddress -> { dispatch(ChangePasteBtnEnableState(true)) } @@ -54,15 +61,44 @@ private fun verifyAndSendTransaction(appState: AppState?, dispatch: (Action) -> val feeAmount = Amount(sendState.feeState.getCurrentFee(), blockchain) val amountToSend = Amount(sendState.amountState.amountToSendCrypto, blockchain, recipientAddress) + val txSender = walletManager as TransactionSender + + val verifyResult = walletManager.validateTransaction(amountToSend, feeAmount) + if (verifyResult.isNotEmpty()) { + dispatch(SendAction.SendError(TapError.InsufficientBalance)) + return + } + + dispatch(SendAction.ChangeSendButtonState(SendButtonState.PROGRESS)) + val txData = walletManager.createTransaction(amountToSend, feeAmount, recipientAddress) scope.launch { - when (val sendResult = tangemSdkManager.send(walletManager, recipientAddress, amountToSend, feeAmount)) { - is CompletionResult.Success -> dispatch(SendAction.SendSuccess) - is CompletionResult.Failure -> { - when (sendResult.error.code) { - 1021 -> dispatch(SendAction.SendError(SendAction.Error.INSUFFICIENT_BALANCE)) - 1001, 2000 -> dispatch(SendAction.SendError(SendAction.Error.BLOCKCHAIN_INTERNAL)) + val result = txSender.send(txData, Signer(tangemSdk)) + withContext(Dispatchers.Main) { + when (result) { + is SimpleResult.Success -> { + dispatch(SendAction.SendSuccess) + dispatch(NavigationAction.PopBackTo()) + } + is SimpleResult.Failure -> { + when (result.error) { + is Throwable -> { + val message = (result.error as Throwable).message + when { + message == null -> { + dispatch(SendAction.SendError(TapError.UnknownError)) + } + message.contains("50002") -> { + // user was cancelled the operation by closing the Sdk bottom sheet + } + else -> { + dispatch(SendAction.SendError(TapError.BlockchainInternalError)) + } + } + } + } } } + dispatch(SendAction.ChangeSendButtonState(SendButtonState.ENABLED)) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt similarity index 83% rename from app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendReducer.kt rename to app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 925e8aad05..06b4e51957 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -7,7 +7,6 @@ import com.tangem.tap.features.send.redux.states.IdStateHolder import com.tangem.tap.features.send.redux.states.SendState import com.tangem.tap.store import org.rekotlin.Action -import timber.log.Timber import java.math.BigDecimal /** @@ -17,7 +16,7 @@ interface SendInternalReducer { fun handle(action: SendScreenAction, sendState: SendState): SendState } -class SendReducer { +class SendScreenReducer { companion object { fun reduce(incomingAction: Action, sendState: SendState): SendState { if (incomingAction is ReleaseSendState) return SendState() @@ -29,18 +28,26 @@ class SendReducer { is AmountActionUi, is AmountAction -> AmountReducer() is FeeActionUi, is FeeAction -> FeeReducer() is ReceiptAction -> ReceiptReducer() + is SendAction -> SendReducer() else -> EmptyReducer() } - - val newState = reducer.handle(action, sendState).copy(sendButtonIsEnabled = sendState.isReadyToSend()) - Timber.i("${newState.lastChangedStates}.") - - return newState + return reducer.handle(action, sendState) } } } +private class SendReducer : SendInternalReducer { + override fun handle(action: SendScreenAction, sendState: SendState): SendState { + val result = when (action) { + is SendAction.ChangeSendButtonState -> sendState.copy(sendButtonState = action.state) + else -> return sendState + } + + return updateLastState(result, result) + } +} + private class EmptyReducer : SendInternalReducer { override fun handle(action: SendScreenAction, sendState: SendState): SendState = sendState } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt index 6d31cd31cc..4d3cc3cd98 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt @@ -34,7 +34,7 @@ data class SendState( val amountState: AmountState = AmountState(), val feeState: FeeState = FeeState(), val receiptState: ReceiptState = ReceiptState(), - val sendButtonIsEnabled: Boolean = false, + val sendButtonState: SendButtonState = SendButtonState.DISABLED, override val stateId: StateId = StateId.SEND_SCREEN ) : SendScreenState { @@ -49,6 +49,12 @@ data class SendState( MainCurrencyType.FIAT -> 2 MainCurrencyType.CRYPTO -> amount?.decimals ?: 0 } + + fun getButtonState(): SendButtonState = if (isReadyToSend()) SendButtonState.ENABLED else SendButtonState.DISABLED +} + +enum class SendButtonState { + ENABLED, DISABLED, PROGRESS } data class AmountState( diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 3e0cc714be..688554c986 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -8,14 +8,17 @@ import android.view.inputmethod.EditorInfo import android.widget.EditText import androidx.core.view.postDelayed import androidx.core.widget.addTextChangedListener +import com.tangem.merchant.common.toggleWidget.ToggleWidget import com.tangem.tangem_sdk_new.extensions.hideSoftKeyboard import com.tangem.tap.common.KeyboardObserver import com.tangem.tap.common.entities.TapCurrency +import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.tap.common.extensions.setOnImeActionListener import com.tangem.tap.common.qrCodeScan.ScanQrCodeActivity import com.tangem.tap.common.snackBar.MaxAmountSnackbar import com.tangem.tap.common.text.truncateMiddleWith +import com.tangem.tap.common.toggleWidget.* import com.tangem.tap.features.send.BaseStoreFragment import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* @@ -40,12 +43,21 @@ import kotlinx.coroutines.flow.* */ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { + lateinit var sendBtn: ToggleWidget + + private fun initSendButtonStates() { + sendBtn = ToggleWidget(flSendButtonContainer, btnSend, progress, ProgressState.None()) + sendBtn.setupSendButtonStateModifiers(requireContext()) + sendBtn.setState(ProgressState.None()) + } + private val sendSubscriber = SendStateSubscriber(this) private lateinit var keyboardObserver: KeyboardObserver override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + initSendButtonStates() setupAddressOrPayIdLayout() setupAmountLayout() setupFeeLayout() @@ -98,10 +110,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { private fun setupAmountLayout() { store.dispatch(SetMainCurrency(restoreMainCurrency())) store.dispatch(ReceiptAction.RefreshReceipt) + store.dispatch(SendAction.ChangeSendButtonState(store.state.sendState.getButtonState())) tvAmountCurrency.setOnClickListener { store.dispatch(ToggleMainCurrency) store.dispatch(ReceiptAction.RefreshReceipt) + store.dispatch(SendAction.ChangeSendButtonState(store.state.sendState.getButtonState())) } val maxAmountSnackbar = MaxAmountSnackbar.make(etAmountToSend) { @@ -229,5 +243,15 @@ class FeeUiHelper { } } - +private fun ToggleWidget.setupSendButtonStateModifiers(context: Context) { + mainViewModifiers.clear() + mainViewModifiers.add(ReplaceTextStateModifier(context.getString(R.string.send_btn_send), "")) + mainViewModifiers.add( + TextViewDrawableStateModifier( + context.getDrawableCompat(R.drawable.ic_arrow_right), null, TextViewDrawableStateModifier.RIGHT + )) + mainViewModifiers.add(ClickableStateModifier()) + toggleViewModifiers.clear() + toggleViewModifiers.add(ShowHideStateModifier()) +} diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 09f085f036..c4ee8d767a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -9,6 +9,7 @@ import com.tangem.tap.common.extensions.enableError import com.tangem.tap.common.extensions.show import com.tangem.tap.common.extensions.update import com.tangem.tap.common.text.DecimalDigitsInputFilter +import com.tangem.tap.common.toggleWidget.ProgressState import com.tangem.tap.features.send.BaseStoreFragment import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error import com.tangem.tap.features.send.redux.AmountAction @@ -44,7 +45,22 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber StateId.RECEIPT -> handleReceiptState(fg, state.receiptState) } } - fg.btnSend.isEnabled = state.sendButtonIsEnabled + + val sendFragment = (fg as? SendFragment) ?: return + when (state.sendButtonState) { + SendButtonState.ENABLED -> { + fg.btnSend.isEnabled = true + sendFragment.sendBtn.setState(ProgressState.None(), true) + } + SendButtonState.DISABLED -> { + fg.btnSend.isEnabled = false + sendFragment.sendBtn.setState(ProgressState.None(), true) + } + SendButtonState.PROGRESS -> { + fg.btnSend.isEnabled = true + sendFragment.sendBtn.setState(ProgressState.Progress(), true) + } + } } private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) { @@ -101,7 +117,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber // fg.tvAmountToSendShadow.text = amountToSend // if (amountToSend.length > 10) { - // post is needed to wait for text size changes +// post is needed to wait for text size changes // fg.tvAmountToSendShadow.post { // fg.etAmountToSend.setTextSize(TypedValue.COMPLEX_UNIT_PX, fg.tvAmountToSendShadow.textSize - 2) // fg.etAmountToSend.update(amountToSend) diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml index e08bf8139a..20e5f8ea74 100644 --- a/app/src/main/res/layout/fragment_send.xml +++ b/app/src/main/res/layout/fragment_send.xml @@ -87,16 +87,32 @@ android:layout_width="100dp" android:layout_height="wrap_content" /> - + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a871a0485b..d0eaa3b368 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,6 +44,7 @@ This PayID already exists. Try a different one. Error response while creating PayID. + Unknown error PayID verification failed PayID unsupported by blockchain PayID not registered @@ -71,5 +72,6 @@ will be sent Balance: %1s %2s Maximum amount + Transaction was signed and sent to the blockchain \ No newline at end of file