Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-15 17:18:55 +03:00
parent 957bc23951
commit a0ec01abfe
17 changed files with 400 additions and 177 deletions

View file

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

View file

@ -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<AppState> = { dispatch, state ->
@ -40,6 +47,9 @@ val notificationsMiddleware: Middleware<AppState> = { 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<AppState> = { dispatch, state ->
}
}
interface ToastNotificationAction : Action {
val messageResource: Int
}
interface NotificationAction : Action {
val messageResource: Int
}

View file

@ -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<StateModifier>
val toggleViewModifiers: MutableList<StateModifier>
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<StateModifier> = mutableListOf(),
loadingViewModifier: List<StateModifier> = 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<StateModifier> = mutableListOf(),
loadingViewModifier: List<StateModifier> = 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<StateModifier> = mutableListOf()
override val toggleViewModifiers: MutableList<StateModifier> = 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
}

View file

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

View file

@ -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<CommandResponse> {
return withContext(Dispatchers.IO) {
suspendCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(SendTask(walletManager, recipientAddress, amountToSend, feeAmount)) {
continuation.resume(it)
}
}
}
}
private suspend fun <T : CommandResponse> runTaskAsync(
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null
): CompletionResult<T> =

View file

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

View file

@ -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<CommandResponse> {
override val requiresPin2: Boolean = false
override fun run(session: CardSession, callback: (result: CompletionResult<CommandResponse>) -> 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<ByteArray>, cardId: String): CompletionResult<SignResponse> =
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]
}
}
}

View file

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

View file

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

View file

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

View file

@ -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<AppState> = { 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))
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -87,16 +87,32 @@
android:layout_width="100dp"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSend"
style="@style/TapButtonWithIcon"
android:layout_width="200dp"
android:layout_height="48dp"
android:layout_gravity="end"
android:enabled="false"
android:fontFamily="@font/saira_semi_condensed_regular"
android:text="@string/send_btn_send"
app:icon="@drawable/ic_arrow_right" />
<FrameLayout
android:id="@+id/flSendButtonContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSend"
style="@style/TapButtonWithIcon"
android:layout_width="200dp"
android:layout_height="48dp"
android:layout_gravity="end"
android:enabled="false"
android:fontFamily="@font/saira_semi_condensed_regular"
android:text="@string/send_btn_send"
app:icon="@drawable/ic_arrow_right" />
<ProgressBar
android:id="@+id/progress"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_gravity="center"
android:elevation="18dp"
android:indeterminate="true"
android:indeterminateTint="@color/backgroundLightGray" />
</FrameLayout>
</LinearLayout>

View file

@ -44,6 +44,7 @@
<string name="error_payid_already_created">This PayID already exists. Try a different one.</string>
<string name="error_creating_payid">Error response while creating PayID.</string>
<string name="error_unknown">Unknown error</string>
<string name="error_payid_verification_failed">PayID verification failed</string>
<string name="error_payid_unsupported_by_blockchain">PayID unsupported by blockchain</string>
<string name="error_payid_not_registere">PayID not registered</string>
@ -71,5 +72,6 @@
<string name="send_total_will_be_sent">will be sent</string>
<string name="send_balance">Balance: %1s %2s</string>
<string name="send_set_maximum_amount">Maximum amount</string>
<string name="send_transaction_complete">Transaction was signed and sent to the blockchain</string>
</resources>