Updated on 2026-08-14
This commit is contained in:
commit
ed77fecdee
26 changed files with 676 additions and 99 deletions
23
app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt
Normal file
23
app/src/main/java/com/tangem/tap/common/CurrencyConverter.kt
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import com.tangem.common.extensions.isZero
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CurrencyConverter(var rateValue: BigDecimal) {
|
||||
|
||||
fun toFiat(crypto: BigDecimal, decimals: Int = 2): BigDecimal {
|
||||
return rateValue.multiply(crypto).setScale(decimals, RoundingMode.DOWN)
|
||||
}
|
||||
|
||||
fun toCrypto(fiat: BigDecimal, decimals: Int): BigDecimal {
|
||||
if (fiat.isZero() || rateValue.isZero()) return fiat
|
||||
|
||||
val scaledRateValue = rateValue.setScale(decimals, RoundingMode.DOWN)
|
||||
val scaledFiat = fiat.setScale(decimals, RoundingMode.DOWN)
|
||||
return scaledFiat.divide(scaledRateValue, RoundingMode.UP)
|
||||
}
|
||||
}
|
||||
61
app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt
Normal file
61
app/src/main/java/com/tangem/tap/common/KeyboardObserver.kt
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Rect
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
class KeyboardObserver(activity: Activity) {
|
||||
|
||||
private val decorView = activity.window.decorView
|
||||
private val windowManager = activity.windowManager
|
||||
private val originalWindowHeight: Int = getWindowHeight()
|
||||
private val onGlobalLayoutListener: OnGlobalLayoutListener = OnGlobalLayoutListener { onGlobalLayout() }
|
||||
|
||||
private var onKeyboardListener: ((Boolean) -> Unit)? = null
|
||||
private var lastIsShow = false
|
||||
private var lastWindowHeight = getWindowHeight()
|
||||
|
||||
fun registerListener(listener: (Boolean) -> Unit) {
|
||||
decorView.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
|
||||
onKeyboardListener = listener
|
||||
}
|
||||
|
||||
fun unregisterListener() {
|
||||
decorView.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalLayoutListener)
|
||||
onKeyboardListener = null
|
||||
}
|
||||
|
||||
private fun getWindowHeight() = Rect().apply { decorView.getWindowVisibleDisplayFrame(this) }.bottom
|
||||
|
||||
private fun onGlobalLayout() {
|
||||
val currentWindowHeight = getWindowHeight()
|
||||
if (isSoftKeyChanged()) {
|
||||
lastWindowHeight = currentWindowHeight
|
||||
return
|
||||
}
|
||||
|
||||
lastWindowHeight = currentWindowHeight
|
||||
val isShow = originalWindowHeight != currentWindowHeight
|
||||
if (lastIsShow == isShow) return
|
||||
|
||||
lastIsShow = isShow
|
||||
onKeyboardListener?.invoke(isShow)
|
||||
}
|
||||
|
||||
private fun isSoftKeyChanged() = ((lastWindowHeight - getWindowHeight()).absoluteValue) == getSoftKeyButtonHeight()
|
||||
|
||||
private fun getSoftKeyButtonHeight(): Int {
|
||||
val applicationDisplayHeight = DisplayMetrics().apply {
|
||||
windowManager.defaultDisplay.getMetrics(this)
|
||||
}.heightPixels
|
||||
|
||||
val realDisplayHeight = DisplayMetrics().apply {
|
||||
windowManager.defaultDisplay.getRealMetrics(this)
|
||||
}.heightPixels
|
||||
|
||||
return realDisplayHeight - applicationDisplayHeight
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.tap.common.entities
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class TapCurrency {
|
||||
companion object{
|
||||
val main = "USD"
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ fun BigDecimal.toFormattedString(decimals: Int): String {
|
|||
df.minimumFractionDigits = 0
|
||||
df.isGroupingUsed = false
|
||||
val bd = BigDecimal(unscaledValue(), scale())
|
||||
bd.setScale(decimals, BigDecimal.ROUND_DOWN)
|
||||
bd.setScale(decimals, RoundingMode.DOWN)
|
||||
return df.format(bd)
|
||||
}
|
||||
|
||||
|
|
@ -50,4 +50,6 @@ fun BigDecimal.toFiatString(rateValue: BigDecimal): String? {
|
|||
var fiatValue = rateValue.multiply(this)
|
||||
fiatValue = fiatValue.setScale(2, RoundingMode.DOWN)
|
||||
return "≈ USD $fiatValue"
|
||||
}
|
||||
}
|
||||
|
||||
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextSwitcher
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun TextView.update(text: String?) {
|
||||
if (this.text?.toString() != text) this.text = text
|
||||
}
|
||||
|
||||
fun EditText.update(text: String?) {
|
||||
if (this.text?.toString() == text) return
|
||||
|
||||
val textLength = text?.length ?: 0
|
||||
|
||||
//prevent cursor jumping while editing a text
|
||||
val cursorPosition = if (selectionEnd > textLength) textLength else selectionEnd
|
||||
this.setText(text)
|
||||
if (!isFocused || textLength == 0) return
|
||||
|
||||
if (cursorPosition == 0) setSelection(textLength)
|
||||
else setSelection(cursorPosition)
|
||||
}
|
||||
|
||||
fun TextSwitcher.update(text: String?) {
|
||||
val textView = this.currentView as? TextView ?: return
|
||||
|
||||
if (textView.text?.toString() != text) this.setText(text)
|
||||
}
|
||||
|
||||
// By default the TextInputLayout didn't activates the error state if the message is empty or null
|
||||
fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) {
|
||||
if (enable) {
|
||||
if (errorMessage == null || errorMessage.isEmpty()) {
|
||||
error = "Any message"
|
||||
if (childCount == 2) getChildAt(1).visibility = View.GONE
|
||||
} else {
|
||||
error = errorMessage
|
||||
}
|
||||
} else {
|
||||
error = null
|
||||
isErrorEnabled = false
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ import android.os.Build
|
|||
import android.text.Spannable
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
|
|
@ -132,11 +131,4 @@ fun Context.shareText(text: String) {
|
|||
|
||||
fun Fragment.shareText(text: String) {
|
||||
requireContext().shareText(text)
|
||||
}
|
||||
|
||||
fun ViewGroup.inflate(viewToInflate: Int, rootView: ViewGroup?, parent: ViewGroup) {
|
||||
if (rootView == null) {
|
||||
val inflatedView = LayoutInflater.from(context).inflate(viewToInflate, rootView)
|
||||
parent.addView(inflatedView)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.Transition
|
||||
import androidx.transition.TransitionManager
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
fun ViewGroup.inflate(viewToInflate: Int, rootView: ViewGroup?, parent: ViewGroup) {
|
||||
if (rootView == null) {
|
||||
val inflatedView = LayoutInflater.from(context).inflate(viewToInflate, rootView)
|
||||
parent.addView(inflatedView)
|
||||
}
|
||||
}
|
||||
|
||||
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.tap.common.snackBar
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.ContentViewCallback
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class MaxAmountSnackbar(
|
||||
parent: ViewGroup,
|
||||
content: MaxAmountSnackbarView
|
||||
) : BaseTransientBottomBar<MaxAmountSnackbar>(parent, content, content) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun make(view: View, onClick: () -> Unit): MaxAmountSnackbar {
|
||||
val parent = view.findSuitableParent() ?: throw IllegalArgumentException(
|
||||
"No suitable parent found from the given view. Please provide a valid view."
|
||||
)
|
||||
val inflater = LayoutInflater.from(view.context)
|
||||
val customView = inflater.inflate(R.layout.view_snackbar_max_amount, parent, false) as MaxAmountSnackbarView
|
||||
customView.setOnClickListener { onClick() }
|
||||
|
||||
return MaxAmountSnackbar(parent, customView).apply {
|
||||
duration = Snackbar.LENGTH_INDEFINITE
|
||||
}
|
||||
}
|
||||
|
||||
private fun View?.findSuitableParent(): ViewGroup? {
|
||||
var view = this
|
||||
var fallback: ViewGroup? = null
|
||||
do {
|
||||
if (view is CoordinatorLayout) {
|
||||
return view
|
||||
} else if (view is FrameLayout) {
|
||||
if (view.id == android.R.id.content) {
|
||||
return view
|
||||
} else {
|
||||
fallback = view
|
||||
}
|
||||
}
|
||||
|
||||
if (view != null) {
|
||||
val parent = view.parent
|
||||
view = if (parent is View) parent else null
|
||||
}
|
||||
} while (view != null)
|
||||
return fallback
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class MaxAmountSnackbarView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr), ContentViewCallback {
|
||||
|
||||
init {
|
||||
View.inflate(context, R.layout.view_snackbar_max_amount_content, this)
|
||||
clipToPadding = false
|
||||
}
|
||||
|
||||
|
||||
override fun animateContentIn(delay: Int, duration: Int) {
|
||||
}
|
||||
|
||||
override fun animateContentOut(delay: Int, duration: Int) {
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,10 @@ import com.tangem.tap.common.redux.AppState
|
|||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.PayIdManager.Companion.isPayId
|
||||
import com.tangem.tap.domain.isPayIdSupported
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.SetAddressOrPayId
|
||||
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.SetMainCurrency
|
||||
import com.tangem.tap.features.send.redux.AmountActionUi.ToggleMainCurrency
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -30,12 +32,23 @@ val sendMiddleware: Middleware<AppState> = { dispatch, appState ->
|
|||
}
|
||||
|
||||
private fun handleSendAction(action: Action) {
|
||||
val sendAction = action as? SendScreenActionUI ?: return
|
||||
val sendAction = action as? SendScreenActionUi ?: return
|
||||
|
||||
when (sendAction) {
|
||||
is AddressPayIdActionUi -> {
|
||||
when (sendAction) {
|
||||
is SetAddressOrPayId -> AddressPayIdHandler().handle(sendAction.data)
|
||||
is ChangeAddressOrPayId -> AddressPayIdHandler().handle(sendAction.data)
|
||||
}
|
||||
}
|
||||
is AmountActionUi -> {
|
||||
when (sendAction) {
|
||||
is ToggleMainCurrency -> {
|
||||
if (store.state.sendState.amountState.mainCurrency.value == MainCurrencyType.FIAT) {
|
||||
store.dispatch(SetMainCurrency(MainCurrencyType.CRYPTO))
|
||||
} else {
|
||||
store.dispatch(SetMainCurrency(MainCurrencyType.FIAT))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
package com.tangem.tap.features.send.redux
|
||||
|
||||
import android.view.View
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.common.extensions.isZero
|
||||
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.AddressPayIdActionUi.*
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification
|
||||
import com.tangem.tap.features.send.redux.AmountActionUi.ChangeAmountToSend
|
||||
import com.tangem.tap.features.send.redux.AmountActionUi.SetMainCurrency
|
||||
import com.tangem.tap.features.send.redux.FeeActionUi.*
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.StateType
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -27,12 +36,19 @@ private fun internalReduce(incomingAction: Action, sendState: SendState): SendSt
|
|||
if (incomingAction is ReleaseSendState) return SendState()
|
||||
val action = incomingAction as? SendScreenAction ?: return sendState
|
||||
|
||||
return when (action) {
|
||||
var state = when (action) {
|
||||
is AddressPayIdActionUi -> handleAddressPayIdActionUi(action, sendState, sendState.addressPayIdState)
|
||||
is AddressPayIdVerifyAction -> handleAddressPayIdAction(action, sendState, sendState.addressPayIdState)
|
||||
is AmountActionUi -> handleAmountActionUi(action, sendState, sendState.amountState)
|
||||
is FeeActionUi -> handleFeeActionUi(action, sendState, sendState.feeLayoutState)
|
||||
else -> sendState
|
||||
}
|
||||
state = state.copy(sendButtonIsEnabled = state.addressPayIdState.error == null
|
||||
&& state.addressPayIdState.walletAddress?.isNotEmpty() ?: false
|
||||
&& !state.amountState.amountIsOverBalance
|
||||
&& !state.amountState.amountToSendCrypto.isZero()
|
||||
)
|
||||
return state
|
||||
}
|
||||
|
||||
fun handleAddressPayIdActionUi(
|
||||
|
|
@ -41,10 +57,10 @@ fun handleAddressPayIdActionUi(
|
|||
state: AddressPayIdState
|
||||
): SendState {
|
||||
val result = when (action) {
|
||||
is SetAddressOrPayId -> state
|
||||
is ChangeAddressOrPayId -> state
|
||||
is SetTruncateHandler -> state.copy(truncateHandler = action.handler)
|
||||
is TruncateOrRestore -> {
|
||||
if(action.truncate) state.copy(etFieldValue = state.truncatedFieldValue)
|
||||
if (action.truncate) state.copy(etFieldValue = state.truncatedFieldValue)
|
||||
else state.copy(etFieldValue = state.normalFieldValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +81,96 @@ private fun handleAddressPayIdAction(
|
|||
return updateLastState(sendState.copy(addressPayIdState = result), result)
|
||||
}
|
||||
|
||||
private fun handleAmountActionUi(action: AmountActionUi, sendState: SendState, state: AmountState): SendState {
|
||||
val rates = store.state.globalState.fiatRates
|
||||
val wallet = store.state.walletState.wallet ?: return sendState
|
||||
val fiatRate = rates.getRateForCryptoCurrency(wallet.blockchain.currency) ?: return sendState
|
||||
|
||||
val walletAmount = wallet.amounts[AmountType.Token] ?: wallet.amounts[AmountType.Coin]
|
||||
val converter = CurrencyConverter(fiatRate)
|
||||
|
||||
val result = when (action) {
|
||||
is SetMainCurrency -> {
|
||||
when (action.mainCurrency) {
|
||||
MainCurrencyType.FIAT -> {
|
||||
val fiatToSend = if (state.amountToSendCrypto.isZero()) BigDecimal.ZERO
|
||||
else converter.toFiat(state.amountToSendCrypto)
|
||||
|
||||
val fiatBalance = converter.toFiat(walletAmount?.value ?: BigDecimal.ZERO)
|
||||
|
||||
state.copy(
|
||||
etAmountFieldValue = fiatToSend.stripZeroPlainString(),
|
||||
balance = fiatBalance,
|
||||
mainCurrency = Value(MainCurrencyType.FIAT, TapCurrency.main),
|
||||
cursorAtTheSamePosition = false
|
||||
)
|
||||
}
|
||||
MainCurrencyType.CRYPTO -> {
|
||||
val cryptoBalance = walletAmount?.value ?: BigDecimal.ZERO
|
||||
state.copy(
|
||||
etAmountFieldValue = state.amountToSendCrypto.stripZeroPlainString(),
|
||||
amountToSendCrypto = state.amountToSendCrypto,
|
||||
balance = cryptoBalance.stripTrailingZeros(),
|
||||
mainCurrency = Value(MainCurrencyType.CRYPTO, walletAmount?.currencySymbol ?: "null"),
|
||||
cursorAtTheSamePosition = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is AmountActionUi.SetMaxAmount -> {
|
||||
when (state.mainCurrency.value) {
|
||||
MainCurrencyType.FIAT -> {
|
||||
val cryptoBalance = walletAmount?.value ?: BigDecimal.ZERO
|
||||
val fiatToSend = converter.toFiat(cryptoBalance)
|
||||
|
||||
state.copy(
|
||||
etAmountFieldValue = fiatToSend.stripZeroPlainString(),
|
||||
amountToSendCrypto = cryptoBalance,
|
||||
cursorAtTheSamePosition = false
|
||||
)
|
||||
}
|
||||
MainCurrencyType.CRYPTO -> {
|
||||
val cryptoBalance = walletAmount?.value ?: BigDecimal.ZERO
|
||||
state.copy(
|
||||
etAmountFieldValue = cryptoBalance.stripZeroPlainString(),
|
||||
amountToSendCrypto = cryptoBalance,
|
||||
cursorAtTheSamePosition = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
is ChangeAmountToSend -> {
|
||||
val bdData = when {
|
||||
action.data.isEmpty() || action.data == "0" -> BigDecimal.ZERO
|
||||
else -> BigDecimal(action.data)
|
||||
}
|
||||
|
||||
when (state.mainCurrency.value) {
|
||||
MainCurrencyType.FIAT -> {
|
||||
val sendCrypto = converter.toCrypto(bdData, wallet.blockchain.decimals()).stripTrailingZeros()
|
||||
state.copy(
|
||||
etAmountFieldValue = action.data,
|
||||
amountToSendCrypto = sendCrypto,
|
||||
amountIsOverBalance = state.balance < bdData,
|
||||
cursorAtTheSamePosition = true
|
||||
)
|
||||
}
|
||||
MainCurrencyType.CRYPTO -> {
|
||||
state.copy(
|
||||
etAmountFieldValue = action.data,
|
||||
amountToSendCrypto = bdData,
|
||||
amountIsOverBalance = state.balance < bdData,
|
||||
cursorAtTheSamePosition = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
return updateLastState(sendState.copy(amountState = result), result)
|
||||
}
|
||||
|
||||
private fun handleFeeActionUi(action: FeeActionUi, sendState: SendState, state: FeeLayoutState): SendState {
|
||||
val result = when (action) {
|
||||
is ToggleFeeLayoutVisibility -> {
|
||||
|
|
@ -77,4 +183,4 @@ private fun handleFeeActionUi(action: FeeActionUi, sendState: SendState, state:
|
|||
}
|
||||
|
||||
private fun updateLastState(sendState: SendState, lastChangedState: StateType): SendState =
|
||||
sendState.copy(lastChangedStateType = lastChangedState)
|
||||
sendState.copy(lastChangedStateType = lastChangedState)
|
||||
|
|
@ -6,19 +6,13 @@ import org.rekotlin.Action
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SendScreenAction : Action
|
||||
interface SendScreenActionUI : SendScreenAction
|
||||
interface SendScreenActionUi : SendScreenAction
|
||||
|
||||
object ReleaseSendState : Action
|
||||
|
||||
sealed class FeeActionUi : SendScreenActionUI {
|
||||
object ToggleFeeLayoutVisibility : FeeActionUi()
|
||||
data class ChangeSelectedFee(val id: Int) : FeeActionUi()
|
||||
class ChangeIncludeFee(val isChecked: Boolean) : FeeActionUi()
|
||||
}
|
||||
|
||||
// shortness AddressOrPayId = APid
|
||||
sealed class AddressPayIdActionUi : SendScreenActionUI {
|
||||
data class SetAddressOrPayId(val data: String) : AddressPayIdActionUi()
|
||||
// Address or PayId
|
||||
sealed class AddressPayIdActionUi : SendScreenActionUi {
|
||||
data class ChangeAddressOrPayId(val data: String) : AddressPayIdActionUi()
|
||||
data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi()
|
||||
data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi()
|
||||
}
|
||||
|
|
@ -43,4 +37,19 @@ sealed class AddressPayIdVerifyAction : SendScreenAction {
|
|||
data class SetError(val address: String, val reason: FailReason) : AddressVerification()
|
||||
data class SetWalletAddress(val address: String) : AddressVerification()
|
||||
}
|
||||
}
|
||||
|
||||
// Amount to send
|
||||
sealed class AmountActionUi : SendScreenActionUi {
|
||||
object SetMaxAmount : AmountActionUi()
|
||||
data class ChangeAmountToSend(val data: String) : AmountActionUi()
|
||||
data class SetMainCurrency(val mainCurrency: MainCurrencyType) : AmountActionUi()
|
||||
object ToggleMainCurrency : AmountActionUi()
|
||||
}
|
||||
|
||||
// Fee
|
||||
sealed class FeeActionUi : SendScreenActionUi {
|
||||
object ToggleFeeLayoutVisibility : FeeActionUi()
|
||||
data class ChangeSelectedFee(val id: Int) : FeeActionUi()
|
||||
class ChangeIncludeFee(val isChecked: Boolean) : FeeActionUi()
|
||||
}
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
package com.tangem.tap.features.send.redux
|
||||
|
||||
import android.view.View
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.common.entities.TapCurrency
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StateType
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class SendState(
|
||||
val walletManager: WalletManager? = null,
|
||||
val lastChangedStateType: StateType = NoneState(),
|
||||
val addressPayIdState: AddressPayIdState = AddressPayIdState(),
|
||||
val feeLayoutState: FeeLayoutState = FeeLayoutState()
|
||||
val amountState: AmountState = AmountState(),
|
||||
val feeLayoutState: FeeLayoutState = FeeLayoutState(),
|
||||
val sendButtonIsEnabled: Boolean = false
|
||||
) : StateType
|
||||
|
||||
class NoneState : StateType
|
||||
|
|
@ -73,6 +75,24 @@ data class AddressPayIdState(
|
|||
}
|
||||
}
|
||||
|
||||
enum class MainCurrencyType {
|
||||
FIAT, CRYPTO
|
||||
}
|
||||
|
||||
data class Value<T>(
|
||||
val value: T,
|
||||
val displayedValue: String
|
||||
)
|
||||
|
||||
data class AmountState(
|
||||
val etAmountFieldValue: String = BigDecimal.ZERO.toPlainString(),
|
||||
val cursorAtTheSamePosition: Boolean = true,
|
||||
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
|
||||
val balance: BigDecimal = BigDecimal.ZERO,
|
||||
val mainCurrency: Value<MainCurrencyType> = Value(MainCurrencyType.FIAT, TapCurrency.main),
|
||||
val amountIsOverBalance: Boolean = false
|
||||
) : StateType
|
||||
|
||||
data class FeeLayoutState(
|
||||
val visibility: Int = View.GONE,
|
||||
val selectedFeeId: Int = R.id.chipNormal,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
package com.tangem.tap.features.send.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import com.tangem.tap.common.KeyboardObserver
|
||||
import com.tangem.tap.common.entities.TapCurrency
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
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.features.send.BaseStoreFragment
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.*
|
||||
import com.tangem.tap.features.send.redux.AmountActionUi.*
|
||||
import com.tangem.tap.features.send.redux.FeeActionUi.*
|
||||
import com.tangem.tap.features.send.redux.MainCurrencyType
|
||||
import com.tangem.tap.features.send.redux.ReleaseSendState
|
||||
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
|
||||
import com.tangem.tap.mainScope
|
||||
|
|
@ -19,6 +26,7 @@ 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.layout_send_address_payid.*
|
||||
import kotlinx.android.synthetic.main.layout_send_amount.*
|
||||
import kotlinx.android.synthetic.main.layout_send_network_fee.*
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
|
|
@ -30,11 +38,13 @@ import kotlinx.coroutines.flow.*
|
|||
class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
||||
|
||||
private val sendSubscriber = SendStateSubscriber(this)
|
||||
private lateinit var keyboardObserver: KeyboardObserver
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupAddressOrPayIdLayout()
|
||||
setupAmountLayout()
|
||||
setupFeeLayout()
|
||||
}
|
||||
|
||||
|
|
@ -47,11 +57,11 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
etAddressOrPayId.inputedTextAsFlow()
|
||||
.debounce(400)
|
||||
.filter { store.state.sendState.addressPayIdState.etFieldValue != it }
|
||||
.onEach { store.dispatch(SetAddressOrPayId(it)) }
|
||||
.onEach { store.dispatch(ChangeAddressOrPayId(it)) }
|
||||
.launchIn(mainScope)
|
||||
|
||||
imvPaste.setOnClickListener {
|
||||
store.dispatch(SetAddressOrPayId(requireContext().getFromClipboard()?.toString() ?: ""))
|
||||
store.dispatch(ChangeAddressOrPayId(requireContext().getFromClipboard()?.toString() ?: ""))
|
||||
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
|
||||
}
|
||||
imvQrCode.setOnClickListener {
|
||||
|
|
@ -66,10 +76,57 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
if (requestCode != ScanQrCodeActivity.SCAN_QR_REQUEST_CODE) return
|
||||
|
||||
val scannedCode = data?.getStringExtra(ScanQrCodeActivity.SCAN_RESULT) ?: ""
|
||||
store.dispatch(SetAddressOrPayId(scannedCode))
|
||||
store.dispatch(ChangeAddressOrPayId(scannedCode))
|
||||
store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
|
||||
}
|
||||
|
||||
private fun setupAmountLayout() {
|
||||
store.dispatch(SetMainCurrency(restoreMainCurrency()))
|
||||
tvAmountCurrency.setOnClickListener { store.dispatch(ToggleMainCurrency) }
|
||||
|
||||
val maxAmountSnackbar = MaxAmountSnackbar.make(etAmountToSend) { store.dispatch(SetMaxAmount) }
|
||||
var snackbarControlledByChangingFocus = false
|
||||
keyboardObserver = KeyboardObserver(requireActivity())
|
||||
keyboardObserver.registerListener { isShow ->
|
||||
if (snackbarControlledByChangingFocus) return@registerListener
|
||||
|
||||
if (isShow) {
|
||||
if (etAmountToSend.isFocused && !maxAmountSnackbar.isShown) maxAmountSnackbar.show()
|
||||
} else {
|
||||
if (maxAmountSnackbar.isShown) maxAmountSnackbar.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
etAmountToSend.setOnFocusChangeListener { v, hasFocus ->
|
||||
snackbarControlledByChangingFocus = true
|
||||
if (hasFocus) {
|
||||
etAmountToSend.postDelayed(200) {
|
||||
maxAmountSnackbar.show()
|
||||
snackbarControlledByChangingFocus = false
|
||||
|
||||
}
|
||||
} else {
|
||||
etAmountToSend.postDelayed(350) {
|
||||
maxAmountSnackbar.dismiss()
|
||||
snackbarControlledByChangingFocus = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() == "") etAmountToSend.setText("0")
|
||||
}
|
||||
|
||||
etAmountToSend.inputedTextAsFlow()
|
||||
.debounce(400)
|
||||
.filter { store.state.sendState.amountState.etAmountFieldValue != it }
|
||||
.onEach { store.dispatch(ChangeAmountToSend(it)) }
|
||||
.launchIn(mainScope)
|
||||
}
|
||||
|
||||
private fun setupFeeLayout() {
|
||||
flExpandCollapse.setOnClickListener {
|
||||
store.dispatch(ToggleFeeLayoutVisibility)
|
||||
|
|
@ -86,11 +143,24 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
store.subscribe(sendSubscriber) { appState ->
|
||||
appState.skipRepeats { oldState, newState -> oldState == newState }.select { it.sendState }
|
||||
}
|
||||
|
||||
storeSubscribersList.add(sendSubscriber)
|
||||
}
|
||||
|
||||
fun restoreMainCurrency(): MainCurrencyType {
|
||||
val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE)
|
||||
val mainCurrency = sp.getString("mainCurrency", TapCurrency.main)
|
||||
val foundType = MainCurrencyType.values()
|
||||
.firstOrNull { it.name.toLowerCase() == mainCurrency!!.toLowerCase() } ?: MainCurrencyType.FIAT
|
||||
return foundType
|
||||
}
|
||||
|
||||
fun saveMainCurrency(type: MainCurrencyType) {
|
||||
val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE)
|
||||
sp.edit().putString("mainCurrency", type.name).apply()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
keyboardObserver.unregisterListener()
|
||||
store.dispatch(ReleaseSendState)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
package com.tangem.tap.features.send.ui.stateSubscribers
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionManager
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.enableError
|
||||
import com.tangem.tap.common.extensions.update
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdState
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.FailReason
|
||||
import com.tangem.tap.features.send.redux.AmountState
|
||||
import com.tangem.tap.features.send.redux.FeeLayoutState
|
||||
import com.tangem.tap.features.send.redux.SendState
|
||||
import com.tangem.tap.features.send.ui.SendFragment
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.btn_expand_collapse.*
|
||||
import kotlinx.android.synthetic.main.fragment_send.*
|
||||
import kotlinx.android.synthetic.main.layout_send_address_payid.*
|
||||
import kotlinx.android.synthetic.main.layout_send_amount.*
|
||||
import kotlinx.android.synthetic.main.layout_send_network_fee.*
|
||||
|
||||
/**
|
||||
|
|
@ -22,7 +29,10 @@ class SendStateSubscriber(fragment: Fragment) : FragmentStateSubscriber<SendStat
|
|||
when (state.lastChangedStateType) {
|
||||
is FeeLayoutState -> handleFeeLayoutState(fg, state.feeLayoutState)
|
||||
is AddressPayIdState -> handleAddressPayIdState(fg, state.addressPayIdState)
|
||||
is AmountState -> handleAmountState(fg, state.amountState)
|
||||
}
|
||||
|
||||
fg.btnSend.isEnabled = state.sendButtonIsEnabled
|
||||
}
|
||||
|
||||
private fun handleAddressPayIdState(fg: Fragment, state: AddressPayIdState) {
|
||||
|
|
@ -50,16 +60,9 @@ class SendStateSubscriber(fragment: Fragment) : FragmentStateSubscriber<SendStat
|
|||
til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
|
||||
|
||||
// prevent cycling
|
||||
if (state.etFieldValue == null || et.text?.toString() == state.etFieldValue) return
|
||||
if (state.etFieldValue == null) return
|
||||
|
||||
// prevent cursor jumping while editing
|
||||
if (et.isFocused) {
|
||||
val prevSelection = et.selectionStart
|
||||
et.setText(state.etFieldValue)
|
||||
et.setSelection(prevSelection)
|
||||
} else {
|
||||
et.setText(state.etFieldValue)
|
||||
}
|
||||
et.update(state.etFieldValue)
|
||||
}
|
||||
|
||||
private fun handleFeeLayoutState(fg: Fragment, layoutState: FeeLayoutState) {
|
||||
|
|
@ -67,7 +70,7 @@ class SendStateSubscriber(fragment: Fragment) : FragmentStateSubscriber<SendStat
|
|||
val rotationAngle = if (fg.imvExpandCollapse.rotation == 0f) 180f else 0f
|
||||
fg.imvExpandCollapse.rotation = rotationAngle
|
||||
|
||||
(fg.llFeeContainer.parent?.parent as? ViewGroup)?.let { TransitionManager.beginDelayedTransition(it) }
|
||||
(fg.llFeeContainer.parent?.parent as? ViewGroup)?.beginDelayedTransition()
|
||||
fg.llFeeContainer.visibility = layoutState.visibility
|
||||
}
|
||||
|
||||
|
|
@ -79,4 +82,33 @@ class SendStateSubscriber(fragment: Fragment) : FragmentStateSubscriber<SendStat
|
|||
fg.chipGroup.check(layoutState.selectedFeeId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAmountState(fg: Fragment, state: AmountState) {
|
||||
fg.tilAmountToSend.enableError(state.amountIsOverBalance)
|
||||
|
||||
val amountToSend = state.etAmountFieldValue
|
||||
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)?.let { it.saveMainCurrency(state.mainCurrency.value) }
|
||||
|
||||
val balanceText = fg.getString(R.string.send_balance,
|
||||
state.mainCurrency.displayedValue,
|
||||
state.balance.toPlainString())
|
||||
fg.tvBalance.update(balanceText)
|
||||
}
|
||||
}
|
||||
11
app/src/main/res/anim/slide_in_right.xml
Normal file
11
app/src/main/res/anim/slide_in_right.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="50%p"
|
||||
android:toXDelta="0" />
|
||||
<alpha
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0" />
|
||||
</set>
|
||||
11
app/src/main/res/anim/slide_out_right.xml
Normal file
11
app/src/main/res/anim/slide_out_right.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="0"
|
||||
android:toXDelta="50%p" />
|
||||
<alpha
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0" />
|
||||
</set>
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<!-- padding was added nice ripple effect -->
|
||||
<ImageView
|
||||
android:id="@+id/imvExpandCollapse"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -19,6 +18,7 @@
|
|||
android:layout_gravity="center"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="5dp"
|
||||
android:rotation="180"
|
||||
app:srcCompat="@drawable/ic_angle_bracket_up" />
|
||||
|
||||
</FrameLayout>
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<!-- padding was added nice ripple effect -->
|
||||
<ImageView
|
||||
android:id="@+id/imvPaste"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -28,7 +29,7 @@
|
|||
layout="@layout/layout_send_address_payid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp" />
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
<include
|
||||
layout="@layout/layout_send_amount"
|
||||
|
|
@ -56,17 +57,35 @@
|
|||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/TapButtonWithIcon"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="16dp"
|
||||
<LinearLayout
|
||||
android:id="@+id/llBottomButtonContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:layout_marginBottom="33dp"
|
||||
android:fontFamily="@font/saira_semi_condensed_regular"
|
||||
android:text="@string/send_btn_send"
|
||||
app:icon="@drawable/ic_arrow_right" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="1dp" />
|
||||
|
||||
<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" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
layout="@layout/btn_qr_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginTop="14dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/tilAddressOrPayId" />
|
||||
|
||||
|
|
|
|||
|
|
@ -7,40 +7,70 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tilAddressOrPayId">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilAmount"
|
||||
<FrameLayout
|
||||
android:id="@+id/flAmountToSend"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:boxBackgroundColor="@android:color/transparent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etAmount"
|
||||
<FrameLayout
|
||||
android:id="@+id/flShadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:inputType="numberDecimal"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="96dp"
|
||||
android:text="0"
|
||||
android:textSize="32sp"
|
||||
tools:text="139" />
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:focusable="false"
|
||||
android:visibility="invisible">
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<TextView
|
||||
android:id="@+id/tvAmountToSendShadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="96dp"
|
||||
android:textSize="@dimen/text_size_amount_to_send"
|
||||
app:autoSizeTextType="uniform" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilAmountToSend"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="82dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:boxBackgroundColor="@android:color/transparent"
|
||||
app:errorIconDrawable="@null">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etAmountToSend"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="82dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:inputType="numberDecimal"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="96dp"
|
||||
android:textSize="@dimen/text_size_amount_to_send"
|
||||
tools:text="139" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextSwitcher
|
||||
android:id="@+id/tvCurrency"
|
||||
android:id="@+id/tvAmountCurrency"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:inAnimation="@anim/slide_in_up"
|
||||
android:outAnimation="@anim/slide_out_down"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/tilAmount"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/flAmountToSend"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
|
|
@ -49,7 +79,6 @@
|
|||
android:layout_gravity="end"
|
||||
android:drawablePadding="10dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:text="usd"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/blue"
|
||||
android:textSize="32sp"
|
||||
|
|
@ -61,7 +90,6 @@
|
|||
android:layout_gravity="end"
|
||||
android:drawablePadding="10dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:text="btc"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/blue"
|
||||
android:textSize="32sp"
|
||||
|
|
@ -69,39 +97,29 @@
|
|||
|
||||
</TextSwitcher>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/balanceContainer"
|
||||
<TextSwitcher
|
||||
android:id="@+id/tvBalance"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:orientation="horizontal"
|
||||
android:inAnimation="@anim/slide_in_right"
|
||||
android:outAnimation="@android:anim/slide_out_right"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tilAmount">
|
||||
app:layout_constraintTop_toBottomOf="@+id/flAmountToSend">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBalance"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingEnd="5dp"
|
||||
android:text="@string/send_balance"
|
||||
android:layout_gravity="end"
|
||||
android:textColor="@color/darkGray1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBalanceCurrency"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingEnd="5dp"
|
||||
android:textColor="@color/darkGray1"
|
||||
tools:text="USD" />
|
||||
android:layout_gravity="end"
|
||||
android:textColor="@color/darkGray1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvBalanceAmount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/darkGray1"
|
||||
tools:text="23.45" />
|
||||
|
||||
</LinearLayout>
|
||||
</TextSwitcher>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="" />
|
||||
android:text="@string/send_fee_include" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
|||
5
app/src/main/res/layout/view_snackbar_max_amount.xml
Normal file
5
app/src/main/res/layout/view_snackbar_max_amount.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.tangem.tap.common.snackBar.MaxAmountSnackbarView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:padding="8dp" />
|
||||
22
app/src/main/res/layout/view_snackbar_max_amount_content.xml
Normal file
22
app/src/main/res/layout/view_snackbar_max_amount_content.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge 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:background="@color/tapButtonColorBlack"
|
||||
tools:layout_height="44dp"
|
||||
tools:layout_width="match_parent"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/tv_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send_set_maximum_amount"
|
||||
android:textColor="#F4F5F6"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</merge>
|
||||
|
|
@ -31,4 +31,6 @@
|
|||
|
||||
<dimen name="btn_corner_radius">4dp</dimen>
|
||||
|
||||
<dimen name="text_size_amount_to_send">32sp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
<string name="send_total_amount">Amount</string>
|
||||
<string name="send_total_fee">Fee</string>
|
||||
<string name="send_total_will_be_sent">will be sent</string>
|
||||
<string name="send_balance">Balance:</string>
|
||||
<string name="send_balance">Balance: %1s %2s</string>
|
||||
<string name="send_set_maximum_amount">Maximum amount</string>
|
||||
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue