Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-16 13:12:12 +03:00
commit a797ab1dc3
38 changed files with 762 additions and 573 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.details.redux.DetailsReducer
import com.tangem.tap.features.wallet.redux.WalletReducer
import org.rekotlin.Action
@ -15,7 +15,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),
detailsState = DetailsReducer.reduce(action, state)
)
}

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,26 @@
package com.tangem.tap.common.text
import android.text.InputFilter
import android.text.Spanned
import java.util.regex.Pattern
/**
[REDACTED_AUTHOR]
*/
class DecimalDigitsInputFilter(digitsBeforeDecimal: Int, digitsAfterDecimal: Int) : InputFilter {
private val pattern: Pattern = Pattern.compile("(([1-9]{1}[0-9]{0," + (digitsBeforeDecimal - 1) + "})?||[0]{1})((\\.[0-9]{0," + digitsAfterDecimal + "})?)||(\\.)?")
override fun filter(source: CharSequence, sourceStart: Int, sourceEnd: Int, destination: Spanned, destinationStart: Int, destinationEnd: Int): CharSequence? {
val destString = destination.toString()
val prefix = destString.substring(0, destinationStart)
val suffix = destString.substring(destinationEnd, destString.length)
val newDestination = prefix + suffix
val resultPrefix = newDestination.substring(0, destinationStart)
val resultSuffix = newDestination.substring(destinationStart, newDestination.length)
val result = resultPrefix + source.toString() + resultSuffix
val hasMatches = pattern.matcher(result).matches()
return if (hasMatches) null else ""
}
}

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

@ -20,7 +20,7 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) {
abstract fun subscribeToStore()
private lateinit var mainView: View
protected lateinit var mainView: View
protected val storeSubscribersList = mutableListOf<StoreSubscriber<*>>()
override fun onCreate(savedInstanceState: Bundle?) {

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
@ -37,6 +40,9 @@ sealed class AddressPayIdVerifyAction : SendScreenAction {
ADDRESS_SAME_AS_WALLET
}
data class VerifyClipboard(val data: String?) : AddressPayIdVerifyAction()
data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressPayIdVerifyAction()
sealed class PayIdVerification : AddressPayIdVerifyAction() {
data class SetError(val payId: String, val error: Error) : PayIdVerification()
data class SetPayIdWalletAddress(val payId: String, val payIdWalletAddress: String) : PayIdVerification()
@ -103,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

@ -17,7 +17,12 @@ import org.rekotlin.DispatchFunction
[REDACTED_AUTHOR]
*/
internal class AddressPayIdMiddleware {
fun handle(data: String, appState: AppState?, dispatch: DispatchFunction) {
fun handle(data: String?, appState: AppState?, dispatch: DispatchFunction) {
if (data == null) {
dispatch(AddressVerification.SetError("", Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN))
return
}
val sendState = appState?.sendState ?: return
val walletManager = sendState.walletManager ?: return
if (data == sendState.addressPayIdState.etFieldValue) return

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,16 +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
@ -22,6 +30,18 @@ val sendMiddleware: Middleware<AppState> = { dispatch, appState ->
{ action ->
when (action) {
is ChangeAddressOrPayId -> AddressPayIdMiddleware().handle(action.data, appState(), dispatch)
is VerifyClipboard -> {
AddressPayIdMiddleware().handle(action.data, appState()) {
when (it) {
is AddressVerification.SetWalletAddress, is PayIdVerification.SetPayIdWalletAddress -> {
dispatch(ChangePasteBtnEnableState(true))
}
is AddressVerification.SetError, is PayIdVerification.SetError -> {
dispatch(ChangePasteBtnEnableState(false))
}
}
}
}
is CheckAmountToSend -> AmountMiddleware().handle(action.data, appState(), dispatch)
is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch)
is SendActionUi.SendAmountToRecipient -> verifyAndSendTransaction(appState(), dispatch)
@ -41,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

@ -36,6 +36,8 @@ class AddressPayIdReducer : SendInternalReducer {
is PayIdVerification.SetError -> state.copyPayIdError(action.payId, action.error)
is AddressVerification.SetWalletAddress -> state.copyWalletAddress(action.address)
is AddressVerification.SetError -> state.copyError(action.address, action.error)
is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled)
is AddressPayIdVerifyAction.VerifyClipboard -> state
}
return updateLastState(sendState.copy(addressPayIdState = result), result)
}

View file

@ -43,6 +43,7 @@ class AmountReducer : SendInternalReducer {
viewAmountValue = fiatToSend.stripZeroPlainString(),
viewBalanceValue = converter.toFiat(state.balanceCrypto).stripZeroPlainString(),
mainCurrency = Value(MainCurrencyType.FIAT, TapCurrency.main),
maxLengthOfAmount = sendState.getDecimals(action.mainCurrency),
cursorAtTheSamePosition = false
)
}
@ -52,6 +53,7 @@ class AmountReducer : SendInternalReducer {
viewAmountValue = state.amountToSendCrypto.stripZeroPlainString(),
viewBalanceValue = state.balanceCrypto.stripZeroPlainString(),
mainCurrency = mainCurrency,
maxLengthOfAmount = sendState.getDecimals(action.mainCurrency),
cursorAtTheSamePosition = false
)
}

View file

@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Wallet
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.TapCurrency
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.states.*
@ -29,14 +30,14 @@ class ReceiptReducer : SendInternalReducer {
val layoutType = determineLayoutType(amountState.mainCurrency.value, amountState.typeOfAmount)
val symbols = determineSymbols(wallet)
val showBlank = !sendState.isReadyToSend()
val result = state.copy(
visibleTypeOfReceipt = layoutType,
mainCurrencyType = sendState.amountState.mainCurrency,
mainLayoutIsVisible = sendState.isReadyToSend(),
fiat = createFiatType(converter, amountState, feeState, symbols),
crypto = createCryptoType(converter, amountState, feeState, symbols),
tokenFiat = createTokenFiatType(converter, amountState, feeState, symbols),
tokenCrypto = createTokenCryptoType(converter, amountState, feeState, symbols)
fiat = createFiatType(converter, amountState, feeState, symbols, showBlank),
crypto = createCryptoType(converter, amountState, feeState, symbols, showBlank),
tokenFiat = createTokenFiatType(converter, amountState, feeState, symbols, showBlank),
tokenCrypto = createTokenCryptoType(converter, amountState, feeState, symbols, showBlank)
)
return updateLastState(sendState.copy(receiptState = result), result)
}
@ -45,19 +46,24 @@ class ReceiptReducer : SendInternalReducer {
converter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptFiat {
val feeCrypto = feeState.getCurrentFee()
val feeFiat = converter.toFiat(feeCrypto)
if (showBlank) {
return ReceiptFiat("0", feeFiat.stripZeroPlainString(), feeFiat.stripZeroPlainString(), "0", symbols)
}
return if (feeState.feeIsIncluded) {
val amountFiat = converter.toFiat(amountState.amountToSendCrypto.minus(feeCrypto))
val totalFiat = converter.toFiat(amountState.amountToSendCrypto)
ReceiptFiat(
amountFiat = amountFiat,
feeFiat = feeFiat,
totalFiat = totalFiat,
willSentCrypto = amountState.amountToSendCrypto.stripTrailingZeros(),
amountFiat = amountFiat.stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
totalFiat = totalFiat.stripZeroPlainString(),
willSentCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
symbols = symbols
)
} else {
@ -65,10 +71,10 @@ class ReceiptReducer : SendInternalReducer {
val amountFiat = converter.toFiat(amountState.amountToSendCrypto)
val totalFiat = converter.toFiat(totalAmountCrypto)
ReceiptFiat(
amountFiat = amountFiat,
feeFiat = feeFiat,
totalFiat = totalFiat,
willSentCrypto = totalAmountCrypto.stripTrailingZeros(),
amountFiat = amountFiat.stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
totalFiat = totalFiat.stripZeroPlainString(),
willSentCrypto = totalAmountCrypto.stripZeroPlainString(),
symbols = symbols
)
}
@ -78,25 +84,32 @@ class ReceiptReducer : SendInternalReducer {
converter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptCrypto {
val feeCrypto = feeState.getCurrentFee()
if (showBlank) {
return ReceiptCrypto("0", feeCrypto.stripZeroPlainString(), "0", converter.toFiat(feeCrypto).stripZeroPlainString(), "0", symbols)
}
if (feeState.feeIsIncluded) {
return ReceiptCrypto(
amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripTrailingZeros(),
feeCrypto = feeCrypto.stripTrailingZeros(),
totalCrypto = amountState.amountToSendCrypto.stripTrailingZeros(),
willSentFiat = converter.toFiat(amountState.amountToSendCrypto),
amountCrypto = amountState.amountToSendCrypto.minus(feeCrypto).stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString(),
totalCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
feeFiat = converter.toFiat(feeCrypto).stripZeroPlainString(),
willSentFiat = converter.toFiat(amountState.amountToSendCrypto).stripZeroPlainString(),
symbols = symbols
)
} else {
val totalCrypto = amountState.amountToSendCrypto.plus(feeCrypto)
return ReceiptCrypto(
amountCrypto = amountState.amountToSendCrypto.stripTrailingZeros(),
feeCrypto = feeCrypto.stripTrailingZeros(),
totalCrypto = totalCrypto.stripTrailingZeros(),
willSentFiat = converter.toFiat(totalCrypto),
amountCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString(),
totalCrypto = totalCrypto.stripZeroPlainString(),
feeFiat = converter.toFiat(feeCrypto).stripZeroPlainString(),
willSentFiat = converter.toFiat(totalCrypto).stripZeroPlainString(),
symbols = symbols
)
}
@ -106,17 +119,23 @@ class ReceiptReducer : SendInternalReducer {
converter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptTokenFiat {
val feeCrypto = feeState.getCurrentFee()
val amountFiat = converter.toFiat(amountState.amountToSendCrypto)
val feeFiat = converter.toFiat(feeCrypto)
if (showBlank) {
return ReceiptTokenFiat("0", feeFiat.stripZeroPlainString(), "0", "0", "0", symbols)
}
return ReceiptTokenFiat(
amountFiat = amountFiat,
feeFiat = feeFiat,
totalFiat = amountFiat.plus(feeFiat),
willSentTokenCrypto = amountState.amountToSendCrypto.stripTrailingZeros(),
willSentFeeCrypto = feeCrypto.stripTrailingZeros(),
amountFiat = amountFiat.stripZeroPlainString(),
feeFiat = feeFiat.stripZeroPlainString(),
totalFiat = amountFiat.plus(feeFiat).stripZeroPlainString(),
willSentTokenCrypto = amountState.amountToSendCrypto.stripZeroPlainString(),
willSentFeeCrypto = feeCrypto.stripZeroPlainString(),
symbols = symbols
)
}
@ -125,14 +144,19 @@ class ReceiptReducer : SendInternalReducer {
converter: CurrencyConverter,
amountState: AmountState,
feeState: FeeState,
symbols: ReceiptSymbols
symbols: ReceiptSymbols,
showBlank: Boolean
): ReceiptTokenCrypto {
val feeCrypto = feeState.getCurrentFee()
val totalFiat = converter.toFiat(amountState.amountToSendCrypto).plus(converter.toFiat(feeCrypto))
if (showBlank) {
return ReceiptTokenCrypto("0", feeCrypto.stripZeroPlainString(), "0", symbols)
}
return ReceiptTokenCrypto(
amountToken = amountState.amountToSendCrypto.stripTrailingZeros(),
feeCrypto = feeCrypto.stripTrailingZeros(),
totalFiat = totalFiat.stripTrailingZeros(),
amountToken = amountState.amountToSendCrypto.stripZeroPlainString(),
feeCrypto = feeCrypto.stripZeroPlainString(),
totalFiat = totalFiat.stripZeroPlainString(),
symbols = symbols
)
}

View file

@ -3,11 +3,10 @@ package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.common.WalletManager
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.features.send.redux.*
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 org.rekotlin.StateType
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.lastChangedStateType}.")
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
}
@ -63,5 +70,7 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer {
}
}
internal fun updateLastState(sendState: SendState, lastChangedState: StateType): SendState =
sendState.copy(lastChangedStateType = lastChangedState)
internal fun updateLastState(sendState: SendState, lastChangedState: IdStateHolder): SendState {
sendState.lastChangedStates.add(lastChangedState.stateId)
return sendState
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.send.redux.states
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
import org.rekotlin.StateType
data class AddressPayIdState(
val etFieldValue: String? = null,
@ -9,8 +8,10 @@ data class AddressPayIdState(
val truncatedFieldValue: String? = null,
val recipientWalletAddress: String? = null,
val error: AddressPayIdVerifyAction.Error? = null,
val truncateHandler: ((String) -> String)? = null
) : StateType {
val truncateHandler: ((String) -> String)? = null,
val pasteIsEnabled: Boolean = false,
override val stateId: StateId = StateId.ADDRESS_PAY_ID
) : SendScreenState {
fun isReady(): Boolean = error == null && recipientWalletAddress?.isNotEmpty() ?: false

View file

@ -2,7 +2,6 @@ package com.tangem.tap.features.send.redux.states
import com.tangem.blockchain.common.Amount
import com.tangem.tap.features.send.redux.FeeAction
import org.rekotlin.StateType
import java.math.BigDecimal
/**
@ -20,8 +19,9 @@ data class FeeState(
val mainLayoutIsVisible: Boolean = false,
val controlsLayoutIsVisible: Boolean = true,
val feeChipGroupIsVisible: Boolean = true,
val error: FeeAction.Error? = null
) : StateType {
val error: FeeAction.Error? = null,
override val stateId: StateId = StateId.FEE
) : SendScreenState {
fun isReady(): Boolean = error == null && currentFee != null
fun getCurrentFee(): BigDecimal = currentFee?.value?.value ?: BigDecimal.ZERO

View file

@ -1,8 +1,5 @@
package com.tangem.tap.features.send.redux.states
import org.rekotlin.StateType
import java.math.BigDecimal
// Shows only one type of the layout
enum class ReceiptLayoutType {
UNKNOWN, FIAT, CRYPTO, TOKEN_FIAT, TOKEN_CRYPTO
@ -15,8 +12,8 @@ data class ReceiptState(
val tokenFiat: ReceiptTokenFiat? = null,
val tokenCrypto: ReceiptTokenCrypto? = null,
val mainCurrencyType: Value<MainCurrencyType>? = null,
val mainLayoutIsVisible: Boolean = false,
) : StateType
override val stateId: StateId = StateId.RECEIPT
) : SendScreenState
data class ReceiptSymbols(
val fiat: String,
@ -25,33 +22,34 @@ data class ReceiptSymbols(
)
data class ReceiptFiat(
val amountFiat: BigDecimal,
val feeFiat: BigDecimal,
val totalFiat: BigDecimal,
val willSentCrypto: BigDecimal,
val amountFiat: String,
val feeFiat: String,
val totalFiat: String,
val willSentCrypto: String,
val symbols: ReceiptSymbols
)
data class ReceiptCrypto(
val amountCrypto: BigDecimal,
val feeCrypto: BigDecimal,
val totalCrypto: BigDecimal,
val willSentFiat: BigDecimal,
val amountCrypto: String,
val feeCrypto: String,
val totalCrypto: String,
val feeFiat: String,
val willSentFiat: String,
val symbols: ReceiptSymbols
)
data class ReceiptTokenCrypto(
val amountToken: BigDecimal,
val feeCrypto: BigDecimal,
val totalFiat: BigDecimal,
val amountToken: String,
val feeCrypto: String,
val totalFiat: String,
val symbols: ReceiptSymbols
)
data class ReceiptTokenFiat(
val amountFiat: BigDecimal,
val feeFiat: BigDecimal,
val totalFiat: BigDecimal,
val willSentTokenCrypto: BigDecimal,
val willSentFeeCrypto: BigDecimal,
val amountFiat: String,
val feeFiat: String,
val totalFiat: String,
val willSentTokenCrypto: String,
val willSentFeeCrypto: String,
val symbols: ReceiptSymbols
)

View file

@ -14,17 +14,29 @@ import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
interface IdStateHolder {
val stateId: StateId
}
enum class StateId {
SEND_SCREEN, ADDRESS_PAY_ID, AMOUNT, FEE, RECEIPT
}
interface SendScreenState : StateType, IdStateHolder
data class SendState(
val amount: Amount? = null,
val walletManager: WalletManager? = null,
val currencyConverter: CurrencyConverter = CurrencyConverter(BigDecimal.ONE),
val lastChangedStateType: StateType = NoneState(),
val lastChangedStates: LinkedHashSet<StateId> = linkedSetOf(),
val addressPayIdState: AddressPayIdState = AddressPayIdState(),
val amountState: AmountState = AmountState(),
val feeState: FeeState = FeeState(),
val receiptState: ReceiptState = ReceiptState(),
val sendButtonIsEnabled: Boolean = false,
) : StateType {
val sendButtonState: SendButtonState = SendButtonState.DISABLED,
override val stateId: StateId = StateId.SEND_SCREEN
) : SendScreenState {
fun isReadyToSend(): Boolean {
val sendState = store.state.sendState
@ -32,9 +44,18 @@ data class SendState(
}
fun addressPayIdIsReady(): Boolean = store.state.sendState.addressPayIdState.isReady()
fun getDecimals(type: MainCurrencyType): Int = when (type) {
MainCurrencyType.FIAT -> 2
MainCurrencyType.CRYPTO -> amount?.decimals ?: 0
}
fun getButtonState(): SendButtonState = if (isReadyToSend()) SendButtonState.ENABLED else SendButtonState.DISABLED
}
class NoneState : StateType
enum class SendButtonState {
ENABLED, DISABLED, PROGRESS
}
data class AmountState(
val viewAmountValue: String = BigDecimal.ZERO.toPlainString(),
@ -44,8 +65,10 @@ data class AmountState(
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
val balanceCrypto: BigDecimal = BigDecimal.ZERO,
val cursorAtTheSamePosition: Boolean = true,
val error: AmountAction.Error? = null
) : StateType {
val maxLengthOfAmount: Int = 2,
val error: AmountAction.Error? = null,
override val stateId: StateId = StateId.AMOUNT
) : SendScreenState {
fun isReady(): Boolean = error == null && !amountToSendCrypto.isZero()
}

View file

@ -8,30 +8,28 @@ 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.*
import com.tangem.tap.features.send.redux.AmountActionUi.*
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.FeeActionUi.*
import com.tangem.tap.features.send.redux.ReceiptAction
import com.tangem.tap.features.send.redux.ReleaseSendState
import com.tangem.tap.features.send.redux.SendActionUi
import com.tangem.tap.features.send.redux.states.FeeType
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
import com.tangem.tap.mainScope
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.btn_paste.*
import kotlinx.android.synthetic.main.btn_qr_code.*
import kotlinx.android.synthetic.main.fragment_send.*
import kotlinx.android.synthetic.main.layout_send_address_payid.*
import kotlinx.android.synthetic.main.layout_send_amount.*
@ -45,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()
@ -61,7 +68,8 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
private fun setupAddressOrPayIdLayout() {
store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, " *** ") })
store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") })
store.dispatch(AddressPayIdVerifyAction.VerifyClipboard(requireContext().getFromClipboard()?.toString()))
etAddressOrPayId.setOnFocusChangeListener { v, hasFocus ->
store.dispatch(TruncateOrRestore(!hasFocus))
@ -92,16 +100,22 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
if (requestCode != ScanQrCodeActivity.SCAN_QR_REQUEST_CODE) return
val scannedCode = data?.getStringExtra(ScanQrCodeActivity.SCAN_RESULT) ?: ""
if (scannedCode.isEmpty()) return
store.dispatch(ChangeAddressOrPayId(scannedCode))
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
view?.postDelayed(200) { store.dispatch(FeeAction.RequestFee) }
store.dispatch(FeeAction.RequestFee)
}
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) {
@ -140,7 +154,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
val prevFocusChangeListener = etAmountToSend.onFocusChangeListener
etAmountToSend.setOnFocusChangeListener { v, hasFocus ->
prevFocusChangeListener.onFocusChange(v, hasFocus)
if (hasFocus && etAmountToSend.text?.toString() == "0") etAmountToSend.setText("")
// if (hasFocus && etAmountToSend.text?.toString() == "0") etAmountToSend.setText("")
if (!hasFocus && etAmountToSend.text?.toString() == "") etAmountToSend.setText("0")
}
@ -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

@ -2,13 +2,14 @@ package com.tangem.tap.features.send.ui.stateSubscribers
import android.content.Context
import android.text.SpannableStringBuilder
import android.util.TypedValue
import android.view.ViewGroup
import androidx.core.text.bold
import com.tangem.tap.common.extensions.beginDelayedTransition
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
@ -34,14 +35,32 @@ import kotlinx.android.synthetic.main.layout_send_receipt.*
class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber<SendState>(fragment) {
override fun updateWithNewState(fg: BaseStoreFragment, state: SendState) {
when (state.lastChangedStateType) {
is FeeState -> handleFeeState(fg, state.feeState)
is AddressPayIdState -> handleAddressPayIdState(fg, state.addressPayIdState)
is AmountState -> handleAmountState(fg, state.amountState)
is ReceiptState -> handleReceiptState(fg, state.receiptState)
val lastChangedStates = state.lastChangedStates.toList()
state.lastChangedStates.clear()
lastChangedStates.forEach {
when (it) {
StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState)
StateId.AMOUNT -> handleAmountState(fg, state.amountState)
StateId.FEE -> handleFeeState(fg, state.feeState)
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) {
@ -58,6 +77,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
return if (resId == null) null
else context.getString(resId)
}
fg.imvPaste.isEnabled = state.pasteIsEnabled
val et = fg.etAddressOrPayId
val til = fg.tilAddressOrPayId
@ -91,22 +111,25 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
}
}
fg.etAmountToSend.filters = arrayOf(DecimalDigitsInputFilter(12, state.maxLengthOfAmount))
val amountToSend = state.viewAmountValue
fg.tvAmountToSendShadow.text = amountToSend
if (amountToSend.length > 10) {
// 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)
if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length)
}
} else {
val textSize = fg.resources.getDimension(R.dimen.text_size_amount_to_send)
fg.tvAmountToSendShadow.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
fg.etAmountToSend.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
fg.etAmountToSend.update(amountToSend)
if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length)
}
fg.etAmountToSend.update(amountToSend)
// fg.tvAmountToSendShadow.text = amountToSend
// if (amountToSend.length > 10) {
// 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)
// if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length)
// }
// } else {
// val textSize = fg.resources.getDimension(R.dimen.text_size_amount_to_send)
// fg.tvAmountToSendShadow.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
// fg.etAmountToSend.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
// fg.etAmountToSend.update(amountToSend)
// if (!state.cursorAtTheSamePosition) fg.etAmountToSend.setSelection(amountToSend.length)
// }
fg.tvAmountCurrency.update(state.mainCurrency.displayedValue)
(fg as? SendFragment)?.saveMainCurrency(state.mainCurrency.value)
@ -159,22 +182,21 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
val totalTokenLayout = fg.flTotalTokenCrypto as ViewGroup
fun getString(id: Int): String = mainLayout.context.getString(id)
mainLayout.show(state.mainLayoutIsVisible)
when (state.visibleTypeOfReceipt) {
ReceiptLayoutType.FIAT -> {
val receipt = state.fiat ?: return
totalLayout.show(true)
totalTokenLayout.show(false)
fg.tvReceiptAmountValue.update("${receipt.amountFiat.toPlainString()} ${receipt.symbols.fiat}")
fg.tvReceiptFeeValue.update("${receipt.feeFiat.toPlainString()} ${receipt.symbols.fiat}")
totalLayout.tvTotalValue.update("${receipt.totalFiat.toPlainString()} ${receipt.symbols.fiat}")
fg.tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
fg.tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
totalLayout.tvTotalValue.update("${receipt.totalFiat} ${receipt.symbols.fiat}")
val willSent = SpannableStringBuilder()
.bold { append(receipt.willSentCrypto.toPlainString()) }.append(" ")
.bold { append(receipt.willSentCrypto) }.append(" ")
.append(receipt.symbols.crypto).append(" ")
.append(getString(R.string.send_total_will_be_sent))
totalLayout.tvWillBeSentValue.update(willSent)
totalLayout.tvWillBeSentValue.update(willSent.toString())
}
ReceiptLayoutType.CRYPTO -> {
@ -182,41 +204,41 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalLayout.show(true)
totalTokenLayout.show(false)
fg.tvReceiptAmountValue.update("${receipt.amountCrypto.toPlainString()} ${receipt.symbols.crypto}")
fg.tvReceiptFeeValue.update("${receipt.feeCrypto.toPlainString()} ${receipt.symbols.crypto}")
totalLayout.tvTotalValue.update("${receipt.totalCrypto.toPlainString()} ${receipt.symbols.crypto}")
fg.tvReceiptAmountValue.update("${receipt.amountCrypto} ${receipt.symbols.crypto}")
fg.tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}")
totalLayout.tvTotalValue.update("${receipt.totalCrypto} ${receipt.symbols.crypto}")
val willSent = SpannableStringBuilder()
.bold {
append(getString(R.string.sign_rough))
append(" ")
append(receipt.willSentFiat.toPlainString())
append(" ")
append(getString(R.string.sign_rough)).append(" ")
append(receipt.willSentFiat).append(" ")
append(receipt.symbols.fiat)
append(" (fee: ${receipt.feeFiat} ")
append(receipt.symbols.fiat).append(")")
}
totalLayout.tvWillBeSentValue.update(willSent)
totalLayout.tvWillBeSentValue.update(willSent.toString())
}
ReceiptLayoutType.TOKEN_FIAT -> {
val receipt = state.tokenFiat ?: return
totalLayout.show(true)
totalTokenLayout.show(false)
fg.tvReceiptAmountValue.update("${receipt.amountFiat.toPlainString()} ${receipt.symbols.fiat}")
fg.tvReceiptFeeValue.update("${receipt.feeFiat.toPlainString()} ${receipt.symbols.fiat}")
totalLayout.tvTotalValue.update("${receipt.totalFiat.toPlainString()} ${receipt.symbols.fiat}")
fg.tvReceiptAmountValue.update("${receipt.amountFiat} ${receipt.symbols.fiat}")
fg.tvReceiptFeeValue.update("${receipt.feeFiat} ${receipt.symbols.fiat}")
totalLayout.tvTotalValue.update("${receipt.totalFiat} ${receipt.symbols.fiat}")
val willSent = SpannableStringBuilder()
.bold {
append(receipt.symbols.token)
append(" ")
append(receipt.willSentFeeCrypto.toPlainString())
append(receipt.willSentFeeCrypto)
}.append(" ").append(getString(R.string.generic_and)).append(" ")
.bold {
append(receipt.symbols.crypto).append(" ")
append(receipt.willSentFeeCrypto.toPlainString()).append(" ")
append(receipt.willSentFeeCrypto).append(" ")
}
.append(mainLayout.context.getString(R.string.send_total_will_be_sent))
totalLayout.tvWillBeSentValue.update(willSent)
totalLayout.tvWillBeSentValue.update(willSent.toString())
}
ReceiptLayoutType.TOKEN_CRYPTO -> {
val receipt = state.tokenCrypto ?: return
@ -224,18 +246,16 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
totalLayout.show(false)
totalTokenLayout.show(true)
fg.tvReceiptAmountValue.update("${receipt.amountToken.toPlainString()} ${receipt.symbols.token}")
fg.tvReceiptFeeValue.update("${receipt.feeCrypto.toPlainString()} ${receipt.symbols.crypto}")
fg.tvReceiptAmountValue.update("${receipt.amountToken} ${receipt.symbols.token}")
fg.tvReceiptFeeValue.update("${receipt.feeCrypto} ${receipt.symbols.crypto}")
val willSent = SpannableStringBuilder()
.bold {
append(getString(R.string.sign_rough))
append(" ")
append(receipt.totalFiat.toPlainString())
append(" ")
append(getString(R.string.sign_rough)).append(" ")
append(receipt.totalFiat).append(" ")
append(receipt.symbols.fiat)
}
totalTokenLayout.tvTotalTokenCryptoValue.update(willSent)
totalTokenLayout.tvTotalTokenCryptoValue.update(willSent.toString())
}
}
}

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="15dp"
android:height="18dp"
android:viewportWidth="15"
android:viewportHeight="18">
android:width="16dp"
android:height="19dp"
android:viewportWidth="16"
android:viewportHeight="19">
<path
android:fillColor="#1C1C1E"
android:pathData="M12.4212,14.1668H12.8176C14.2324,14.1668 14.9364,13.4492 14.9364,12.0207V2.3564C14.9364,0.928 14.2255,0.2103 12.8176,0.2103H6.2221C4.8141,0.2103 4.1033,0.928 4.1033,2.3564V2.6913H4.9918C5.9555,2.6913 6.721,2.9647 7.4865,3.7302L11.3686,7.6192C12.1341,8.3915 12.4212,9.1843 12.4212,10.1138V14.1668ZM7.8556,2.623C7.5549,2.623 7.4182,2.4248 7.4182,2.2197V2.0762C7.4182,1.8643 7.5549,1.6729 7.8556,1.6729H11.1841C11.4848,1.6729 11.6215,1.8643 11.6215,2.0762V2.2197C11.6215,2.4248 11.4848,2.623 11.1841,2.623H7.8556ZM2.7706,17.5773H9.3729C10.7808,17.5773 11.4848,16.8597 11.4848,15.4312V9.9293H6.5433C5.6616,9.9293 5.2516,9.5124 5.2516,8.6375V3.6208H2.7706C1.3626,3.6208 0.6518,4.3385 0.6518,5.7669V15.4312C0.6518,16.8665 1.3558,17.5773 2.7706,17.5773ZM6.5365,8.9929H11.1294C11.0542,8.7264 10.8697,8.4803 10.569,8.1659L7.0081,4.5504C6.7142,4.2428 6.4476,4.0583 6.1811,3.9831V8.6444C6.1811,8.8767 6.3041,8.9929 6.5365,8.9929Z" />
android:pathData="M13.8333,1.6667H10.35C10,0.7 9.0833,0 8,0C6.9167,0 6,0.7 5.65,1.6667H2.1667C1.25,1.6667 0.5,2.4167 0.5,3.3333V16.6667C0.5,17.5833 1.25,18.3333 2.1667,18.3333H13.8333C14.75,18.3333 15.5,17.5833 15.5,16.6667V3.3333C15.5,2.4167 14.75,1.6667 13.8333,1.6667ZM8,1.6667C8.4583,1.6667 8.8333,2.0417 8.8333,2.5C8.8333,2.9583 8.4583,3.3333 8,3.3333C7.5417,3.3333 7.1667,2.9583 7.1667,2.5C7.1667,2.0417 7.5417,1.6667 8,1.6667ZM13.8333,16.6667H2.1667V3.3333H3.8333V5.8333H12.1667V3.3333H13.8333V16.6667Z"
android:fillColor="#1C1C1E"/>
</vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="19dp"
android:viewportWidth="16"
android:viewportHeight="19">
<path
android:fillColor="#99838383"
android:pathData="M13.8333 1.66667H10.35C10 0.7 9.08333 0 8 0C6.91667 0 6 0.7 5.65 1.66667H2.16667C1.25 1.66667 0.5 2.41667 0.5 3.33333V16.6667C0.5 17.5833 1.25 18.3333 2.16667 18.3333H13.8333C14.75 18.3333 15.5 17.5833 15.5 16.6667V3.33333C15.5 2.41667 14.75 1.66667 13.8333 1.66667ZM8 1.66667C8.45833 1.66667 8.83333 2.04167 8.83333 2.5C8.83333 2.95833 8.45833 3.33333 8 3.33333C7.54167 3.33333 7.16667 2.95833 7.16667 2.5C7.16667 2.04167 7.54167 1.66667 8 1.66667ZM13.8333 16.6667H2.16667V3.33333H3.83333V5.83333H12.1667V3.33333H13.8333V16.6667Z" />
</vector>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true"
android:drawable="@drawable/ic_paste" />
<item android:state_enabled="false"
android:drawable="@drawable/ic_paste_disabled" />
</selector>

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/flArrowUpDown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_ellipse"
android:backgroundTint="@android:color/transparent"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/imvArrowUpDown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:padding="4dp"
app:srcCompat="@drawable/ic_arrows_up_down" />
</FrameLayout>

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/flPaste"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:background="@drawable/shape_ellipse"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/imvPaste"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:padding="5dp"
app:srcCompat="@drawable/ic_paste" />
</FrameLayout>

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/flQrCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="@drawable/shape_ellipse"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/imvQrCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:padding="4dp"
app:srcCompat="@drawable/ic_qr_code_scan" />
</FrameLayout>

View file

@ -1,25 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator_wallet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="24dp"
android:background="@color/backgroundLightGray"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
style="@style/Widget.MaterialComponents.Toolbar.Surface"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
app:title="@string/send_title" />
android:layout_height="wrap_content"
android:background="@color/backgroundLightGray"
android:fitsSystemWindows="true"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
app:title="@string/send_title" />
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
@ -60,33 +71,49 @@
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginBottom="24dp"
android:visibility="gone"
tools:visibility="visible" />
tools:visibility="gone" />
<LinearLayout
android:id="@+id/llBottomButtonContainer"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:layout_marginBottom="33dp"
android:layout_gravity="center"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:orientation="horizontal">
<View
android:layout_width="100dp"
android:layout_height="1dp" />
<Space
android:layout_width="100.5dp"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSend"
style="@style/TapButtonWithIcon"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_gravity="end"
android:layout_weight="1"
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:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingBottom="33dp"
android:clipToPadding="false"
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: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>
@ -94,4 +121,4 @@
</ScrollView>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -60,8 +60,8 @@
<FrameLayout
android:id="@+id/btn_copy"
android:layout_width="43dp"
android:layout_height="43dp"
android:layout_width="@dimen/btn_rounded_size"
android:layout_height="@dimen/btn_rounded_size"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/shape_ellipse"
@ -84,8 +84,8 @@
<FrameLayout
android:id="@+id/btn_show_qr"
android:layout_width="43dp"
android:layout_height="43dp"
android:layout_width="@dimen/btn_rounded_size"
android:layout_height="@dimen/btn_rounded_size"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/shape_ellipse"

View file

@ -28,60 +28,26 @@
android:textSize="20sp"
android:textStyle="bold" />
<TextSwitcher
<TextView
android:id="@+id/tvTotalValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:inAnimation="@anim/slide_in_right"
android:outAnimation="@android:anim/slide_out_right">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textAllCaps="true"
android:textSize="20sp"
android:textStyle="bold"
tools:text="usd" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textAllCaps="true"
android:textSize="20sp"
android:textStyle="bold"
tools:text="usd" />
</TextSwitcher>
android:textAllCaps="true"
android:textSize="20sp"
android:textStyle="bold"
tools:text="usd" />
</FrameLayout>
<TextSwitcher
<TextView
android:id="@+id/tvWillBeSentValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:inAnimation="@anim/slide_in_right"
android:outAnimation="@android:anim/slide_out_right">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="4dp"
android:text="@string/send_total_will_be_sent"
android:textColor="@color/darkGray1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="4dp"
android:text="@string/send_total_will_be_sent"
android:textColor="@color/darkGray1" />
</TextSwitcher>
android:layout_marginTop="4dp"
android:text="@string/send_total_will_be_sent"
android:textColor="@color/darkGray1" />
</LinearLayout>
@ -101,36 +67,16 @@
android:textSize="14sp"
android:textStyle="bold" />
<TextSwitcher
<TextView
android:id="@+id/tvTotalTokenCryptoValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:inAnimation="@anim/slide_in_right"
android:outAnimation="@android:anim/slide_out_right">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textAllCaps="true"
android:textColor="@color/darkGray1"
android:textSize="14sp"
android:textStyle="bold"
tools:text="usd" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textAllCaps="true"
android:textColor="@color/darkGray1"
android:textSize="14sp"
android:textStyle="bold"
tools:text="usd" />
</TextSwitcher>
android:textAllCaps="true"
android:textColor="@color/darkGray1"
android:textSize="14sp"
android:textStyle="bold"
tools:text="usd" />
</FrameLayout>

View file

@ -12,7 +12,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/send_address_or_payid"
app:boxBackgroundColor="@android:color/transparent"
app:boxBackgroundColor="@color/backgroundLightGray"
app:errorIconDrawable="@null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -31,23 +31,45 @@
</com.google.android.material.textfield.TextInputLayout>
<include
<FrameLayout
android:id="@+id/flPaste"
layout="@layout/btn_paste"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_width="@dimen/btn_rounded_size"
android:layout_height="@dimen/btn_rounded_size"
android:layout_marginEnd="8dp"
android:background="@drawable/shape_ellipse"
app:layout_constraintBottom_toBottomOf="@+id/flQrCode"
app:layout_constraintEnd_toStartOf="@+id/flQrCode"
app:layout_constraintTop_toTopOf="@+id/flQrCode" />
app:layout_constraintTop_toTopOf="@+id/flQrCode">
<include
<ImageView
android:id="@+id/imvPaste"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:padding="6dp"
app:srcCompat="@drawable/paste_selector" />
</FrameLayout>
<FrameLayout
android:id="@+id/flQrCode"
layout="@layout/btn_qr_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:layout_width="@dimen/btn_rounded_size"
android:layout_height="@dimen/btn_rounded_size"
android:layout_marginTop="10dp"
android:background="@drawable/shape_ellipse"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tilAddressOrPayId" />
app:layout_constraintTop_toTopOf="@+id/tilAddressOrPayId">
<ImageView
android:id="@+id/imvQrCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:padding="5dp"
app:srcCompat="@drawable/ic_qr_code_scan" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -44,7 +44,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:boxBackgroundColor="@android:color/transparent"
app:boxBackgroundColor="@color/backgroundLightGray"
app:errorIconDrawable="@null">
<com.google.android.material.textfield.TextInputEditText
@ -62,67 +62,33 @@
</FrameLayout>
<TextSwitcher
<TextView
android:id="@+id/tvAmountCurrency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="26dp"
android:layout_gravity="end"
android:layout_marginTop="28dp"
android:layout_marginEnd="16dp"
android:inAnimation="@anim/slide_in_up"
android:outAnimation="@anim/slide_out_down"
android:drawablePadding="10dp"
android:fontFamily="sans-serif-light"
android:textAllCaps="true"
android:textColor="@color/blue"
android:textSize="32sp"
app:drawableEndCompat="@drawable/ic_arrows_up_down"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
app:layout_constraintTop_toTopOf="parent"
tools:text="USD" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:drawablePadding="10dp"
android:fontFamily="sans-serif-light"
android:textAllCaps="true"
android:textColor="@color/blue"
android:textSize="32sp"
app:drawableEndCompat="@drawable/ic_arrows_up_down"
tools:text="USD" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:drawablePadding="10dp"
android:fontFamily="sans-serif-light"
android:textAllCaps="true"
android:textColor="@color/blue"
android:textSize="32sp"
app:drawableEndCompat="@drawable/ic_arrows_up_down" />
</TextSwitcher>
<TextSwitcher
<TextView
android:id="@+id/tvBalance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:inAnimation="@anim/slide_in_right"
android:outAnimation="@android:anim/slide_out_right"
android:textColor="@color/darkGray1"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/flAmountToSend">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="@color/darkGray1"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textColor="@color/darkGray1"
android:textSize="16sp" />
</TextSwitcher>
app:layout_constraintTop_toBottomOf="@+id/flAmountToSend" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -17,32 +17,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextSwitcher
<TextView
android:id="@+id/tvReceiptAmountValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inAnimation="@anim/slide_in_right"
android:outAnimation="@android:anim/slide_out_right"
android:layout_gravity="end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textAllCaps="true"
android:textStyle="bold"
tools:text="75.00 usd" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textAllCaps="true"
android:textStyle="bold"
tools:text="75.00 usd" />
</TextSwitcher>
app:layout_constraintTop_toTopOf="parent"
android:textAllCaps="true"
android:textStyle="bold"
tools:text="75.00 usd" />
<TextView
android:id="@+id/tvReceiptFee"
@ -55,34 +39,17 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvReceiptAmount" />
<TextSwitcher
<TextView
android:id="@+id/tvReceiptFeeValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inAnimation="@anim/slide_in_right"
android:outAnimation="@android:anim/slide_out_right"
android:layout_gravity="end"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tvReceiptFee">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textAllCaps="true"
android:textColor="@color/darkGray1"
android:textStyle="bold"
tools:text="0.03 usd" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:textAllCaps="true"
android:textColor="@color/darkGray1"
android:textStyle="bold"
tools:text="0.03 usd" />
</TextSwitcher>
app:layout_constraintTop_toTopOf="@+id/tvReceiptFee"
android:textAllCaps="true"
android:textColor="@color/darkGray1"
android:textStyle="bold"
tools:text="0.03 usd" />
<View
android:id="@+id/delimiter"

View file

@ -30,6 +30,7 @@
<dimen name="size_progress_bar_big">135dp</dimen>
<dimen name="btn_corner_radius">4dp</dimen>
<dimen name="btn_rounded_size">44dp</dimen>
<dimen name="text_size_amount_to_send">32sp</dimen>

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,6 +72,7 @@
<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>
<string name="details_toolbar_title">Details</string>