Updated on 2026-08-14
This commit is contained in:
commit
b080f0b4e6
28 changed files with 605 additions and 214 deletions
|
|
@ -74,6 +74,9 @@ dependencies {
|
||||||
implementation 'com.tangem:blockchain:1.123.0'
|
implementation 'com.tangem:blockchain:1.123.0'
|
||||||
implementation 'com.tangem:core:1.90.0'
|
implementation 'com.tangem:core:1.90.0'
|
||||||
implementation 'com.tangem:sdk:1.90.0'
|
implementation 'com.tangem:sdk:1.90.0'
|
||||||
|
implementation 'com.tangem:blockchain:1.123.0'
|
||||||
|
implementation 'com.tangem:core:1.92.0'
|
||||||
|
implementation 'com.tangem:sdk:1.92.0'
|
||||||
|
|
||||||
// WebView
|
// WebView
|
||||||
implementation "androidx.browser:browser:1.2.0"
|
implementation "androidx.browser:browser:1.2.0"
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
[
|
{
|
||||||
{
|
"isWalletPayIdEnabled": true,
|
||||||
"name": "isWalletPayIdEnabled",
|
"isTopUpEnabled": true,
|
||||||
"value": true
|
"isSendingToPayIdEnabled": true,
|
||||||
},
|
"isCreatingTwinCardsAllowed": false
|
||||||
{
|
}
|
||||||
"name": "isSendingToPayIdEnabled",
|
|
||||||
"value": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "isTopUpEnabled",
|
|
||||||
"value": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "isCreatingTwinCardsAllowed",
|
|
||||||
"value": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
[
|
{
|
||||||
{
|
"isWalletPayIdEnabled": true,
|
||||||
"name": "isWalletPayIdEnabled",
|
"isTopUpEnabled": true,
|
||||||
"value": true
|
"isSendingToPayIdEnabled": true,
|
||||||
},
|
"isCreatingTwinCardsAllowed": true
|
||||||
{
|
}
|
||||||
"name": "isSendingToPayIdEnabled",
|
|
||||||
"value": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "isTopUpEnabled",
|
|
||||||
"value": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "isCreatingTwinCardsAllowed",
|
|
||||||
"value": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.tangem.tap.common
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
class GlobalLayoutStateHandler<T: View>(
|
||||||
|
private val view: T,
|
||||||
|
attachImmediately: Boolean = true
|
||||||
|
) : ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
|
||||||
|
var onStateChanged: ((T) -> Unit)? = null
|
||||||
|
|
||||||
|
private var isAttached: Boolean = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (attachImmediately) attach()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attach() {
|
||||||
|
if (isAttached) {
|
||||||
|
Timber.d("Already attached")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isAttached = true
|
||||||
|
view.viewTreeObserver.addOnGlobalLayoutListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun detach() {
|
||||||
|
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||||
|
isAttached = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGlobalLayout() {
|
||||||
|
onStateChanged?.invoke(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,12 +6,20 @@ import com.google.firebase.analytics.ktx.analytics
|
||||||
import com.google.firebase.ktx.Firebase
|
import com.google.firebase.ktx.Firebase
|
||||||
import com.tangem.commands.common.card.Card
|
import com.tangem.commands.common.card.Card
|
||||||
|
|
||||||
object FirebaseAnalyticsHandler: AnalyticsHandler {
|
object FirebaseAnalyticsHandler : AnalyticsHandler {
|
||||||
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
|
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
|
||||||
Firebase.analytics
|
Firebase.analytics
|
||||||
.logEvent(event.event, setCardData(card))
|
.logEvent(event.event, setCardData(card))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun logException(name: String, throwable: Throwable) {
|
||||||
|
Firebase.analytics.logEvent(name, bundleOf(
|
||||||
|
"message" to (throwable.message ?: "none"),
|
||||||
|
"cause_message" to (throwable.cause?.message ?: "none"),
|
||||||
|
"stack_trace" to throwable.stackTraceToString()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
private fun setCardData(card: Card?): Bundle {
|
private fun setCardData(card: Card?): Bundle {
|
||||||
if (card == null) return bundleOf()
|
if (card == null) return bundleOf()
|
||||||
return bundleOf(
|
return bundleOf(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Resources
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
|
|
@ -39,6 +40,14 @@ fun View.getString(@StringRes id: Int): String {
|
||||||
return context.getString(id)
|
return context.getString(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.getResourceName(): String {
|
||||||
|
return try {
|
||||||
|
resources.getResourceEntryName(id)
|
||||||
|
} catch (ex: Resources.NotFoundException) {
|
||||||
|
"Not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) {
|
fun View.show(show: Boolean, invokeBeforeStateChanged: (() -> Unit)? = null) {
|
||||||
return if (show) this.show(invokeBeforeStateChanged)
|
return if (show) this.show(invokeBeforeStateChanged)
|
||||||
else this.hide(invokeBeforeStateChanged)
|
else this.hide(invokeBeforeStateChanged)
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,13 @@ package com.tangem.tap.common.extensions
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewParent
|
import android.view.ViewParent
|
||||||
|
import androidx.core.view.forEach
|
||||||
import androidx.transition.AutoTransition
|
import androidx.transition.AutoTransition
|
||||||
import androidx.transition.Transition
|
import androidx.transition.Transition
|
||||||
import androidx.transition.TransitionManager
|
import androidx.transition.TransitionManager
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
import com.tangem.tap.common.GlobalLayoutStateHandler
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,4 +29,19 @@ fun ViewParent?.beginDelayedTransition(transition: Transition = AutoTransition()
|
||||||
|
|
||||||
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||||
TransitionManager.beginDelayedTransition(this, transition)
|
TransitionManager.beginDelayedTransition(this, transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ChipGroup.fitChipsByGroupWidth() {
|
||||||
|
val layoutStateHandler = GlobalLayoutStateHandler(this)
|
||||||
|
layoutStateHandler.onStateChanged = stateHandler@{
|
||||||
|
if (it.childCount < 2) {
|
||||||
|
layoutStateHandler.detach()
|
||||||
|
return@stateHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
val spacingBetweenViews = it.chipSpacingHorizontal * (it.childCount - 1)
|
||||||
|
val width = (it.width - spacingBetweenViews) / it.childCount
|
||||||
|
it.forEach { chip -> (chip as? Chip)?.width = width }
|
||||||
|
layoutStateHandler.detach()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
package com.tangem.tap.domain.config
|
package com.tangem.tap.domain.config
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import com.google.firebase.ktx.Firebase
|
import com.google.firebase.ktx.Firebase
|
||||||
import com.google.firebase.remoteconfig.ktx.remoteConfig
|
import com.google.firebase.remoteconfig.ktx.remoteConfig
|
||||||
import com.squareup.moshi.JsonAdapter
|
import com.squareup.moshi.JsonAdapter
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.Types
|
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||||
import com.tangem.wallet.BuildConfig
|
import com.tangem.wallet.BuildConfig
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
@ -30,16 +29,13 @@ class LocalLoader(
|
||||||
|
|
||||||
override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
|
override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
|
||||||
val config = try {
|
val config = try {
|
||||||
val featureType = Types.newParameterizedType(List::class.java, FeatureModel::class.java)
|
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
|
||||||
val featureAdapter: JsonAdapter<List<FeatureModel>> = moshi.adapter(featureType)
|
val valuesAdapter: JsonAdapter<ConfigValueModel> = moshi.adapter(ConfigValueModel::class.java)
|
||||||
val valuesType = Types.newParameterizedType(List::class.java, ConfigValueModel::class.java)
|
|
||||||
val valuesAdapter: JsonAdapter<List<ConfigValueModel>> = moshi.adapter(valuesType)
|
|
||||||
|
|
||||||
val jsonFeatures = readAssetAsString(ConfigLoader.featuresName)
|
val jsonFeatures = readAssetAsString(ConfigLoader.featuresName)
|
||||||
val jsonConfigValues = readAssetAsString(ConfigLoader.configValuesName)
|
val jsonConfigValues = readAssetAsString(ConfigLoader.configValuesName)
|
||||||
|
|
||||||
ConfigModel(featureAdapter.fromJson(jsonFeatures) ?: listOf(),
|
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
|
||||||
valuesAdapter.fromJson(jsonConfigValues) ?: listOf())
|
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Timber.e(ex)
|
Timber.e(ex)
|
||||||
ConfigModel.empty()
|
ConfigModel.empty()
|
||||||
|
|
@ -67,14 +63,13 @@ class RemoteLoader(
|
||||||
onComplete(emptyConfig)
|
onComplete(emptyConfig)
|
||||||
return@addOnCompleteListener
|
return@addOnCompleteListener
|
||||||
}
|
}
|
||||||
val featureType = Types.newParameterizedType(List::class.java, FeatureModel::class.java)
|
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
|
||||||
val featureAdapter: JsonAdapter<List<FeatureModel>> = moshi.adapter(featureType)
|
onComplete(ConfigModel(featureAdapter.fromJson(jsonConfig), null))
|
||||||
onComplete(ConfigModel(featureAdapter.fromJson(jsonConfig) ?: listOf(), listOf()))
|
|
||||||
} else {
|
} else {
|
||||||
onComplete(emptyConfig)
|
onComplete(emptyConfig)
|
||||||
}
|
}
|
||||||
}.addOnFailureListener {
|
}.addOnFailureListener {
|
||||||
FirebaseCrashlytics.getInstance().recordException(it)
|
FirebaseAnalyticsHandler.logException("remote_config_error", it)
|
||||||
onComplete(emptyConfig)
|
onComplete(emptyConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,13 @@ class ConfigManager(
|
||||||
|
|
||||||
fun load(onComplete: VoidCallback? = null) {
|
fun load(onComplete: VoidCallback? = null) {
|
||||||
localLoader.loadConfig { config ->
|
localLoader.loadConfig { config ->
|
||||||
config.features?.forEach { setupFeature(it.name, it.value) }
|
setupFeature(config.features)
|
||||||
config.configValues?.forEach { setupKey(it.name, it.value) }
|
setupKey(config.configValues)
|
||||||
|
}
|
||||||
|
remoteLoader.loadConfig { config ->
|
||||||
|
setupFeature(config.features)
|
||||||
onComplete?.invoke()
|
onComplete?.invoke()
|
||||||
}
|
}
|
||||||
// remoteLoader.loadConfig { config ->
|
|
||||||
// config.features?.forEach { setupFeature(it.name, it.value) }
|
|
||||||
// onComplete?.invoke()
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun turnOff(name: String) {
|
fun turnOff(name: String) {
|
||||||
|
|
@ -56,44 +55,35 @@ class ConfigManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupFeature(name: String, value: Boolean) {
|
private fun setupFeature(featureModel: FeatureModel?) {
|
||||||
val newValue = value ?: return
|
val model = featureModel ?: return
|
||||||
|
|
||||||
when (name) {
|
config = config.copy(
|
||||||
isWalletPayIdEnabled -> {
|
isWalletPayIdEnabled = model.isWalletPayIdEnabled,
|
||||||
config = config.copy(isWalletPayIdEnabled = newValue)
|
isTopUpEnabled = model.isTopUpEnabled,
|
||||||
defaultConfig = defaultConfig.copy(isWalletPayIdEnabled = newValue)
|
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||||
}
|
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
|
||||||
isSendingToPayIdEnabled -> {
|
)
|
||||||
config = config.copy(isSendingToPayIdEnabled = newValue)
|
defaultConfig = defaultConfig.copy(
|
||||||
defaultConfig = defaultConfig.copy(isSendingToPayIdEnabled = newValue)
|
isWalletPayIdEnabled = model.isWalletPayIdEnabled,
|
||||||
}
|
isTopUpEnabled = model.isTopUpEnabled,
|
||||||
isTopUpEnabled -> {
|
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||||
config = config.copy(isTopUpEnabled = newValue)
|
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
|
||||||
defaultConfig = defaultConfig.copy(isTopUpEnabled = newValue)
|
)
|
||||||
}
|
|
||||||
isCreatingTwinCardsAllowed -> {
|
|
||||||
config = config.copy(isCreatingTwinCardsAllowed = newValue)
|
|
||||||
defaultConfig = defaultConfig.copy(isCreatingTwinCardsAllowed = newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupKey(name: String, value: String) {
|
private fun setupKey(configValues: ConfigValueModel?) {
|
||||||
when (name) {
|
val values = configValues ?: return
|
||||||
coinMarketCapKey -> {
|
config = config.copy(
|
||||||
config = config.copy(coinMarketCapKey = value)
|
coinMarketCapKey = values.coinMarketCapKey,
|
||||||
defaultConfig = defaultConfig.copy(coinMarketCapKey = value)
|
moonPayApiKey = values.moonPayApiKey,
|
||||||
}
|
moonPayApiSecretKey = values.moonPayApiSecretKey
|
||||||
moonPayApiKey -> {
|
)
|
||||||
config = config.copy(moonPayApiKey = value)
|
defaultConfig = defaultConfig.copy(
|
||||||
defaultConfig = defaultConfig.copy(moonPayApiKey = value)
|
coinMarketCapKey = values.coinMarketCapKey,
|
||||||
}
|
moonPayApiKey = values.moonPayApiKey,
|
||||||
moonPayApiSecretKey -> {
|
moonPayApiSecretKey = values.moonPayApiSecretKey
|
||||||
config = config.copy(moonPayApiSecretKey = value)
|
)
|
||||||
defaultConfig = defaultConfig.copy(moonPayApiSecretKey = value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,21 @@ package com.tangem.tap.domain.config
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface BaseConfigModel<V> {
|
|
||||||
val name: String
|
|
||||||
val value: V?
|
|
||||||
}
|
|
||||||
|
|
||||||
class FeatureModel(
|
class FeatureModel(
|
||||||
override val name: String,
|
val isWalletPayIdEnabled: Boolean,
|
||||||
override val value: Boolean
|
val isTopUpEnabled: Boolean,
|
||||||
) : BaseConfigModel<Boolean>
|
val isSendingToPayIdEnabled: Boolean,
|
||||||
|
val isCreatingTwinCardsAllowed: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
class ConfigValueModel(
|
class ConfigValueModel(
|
||||||
override val name: String,
|
val coinMarketCapKey: String,
|
||||||
override val value: String
|
val moonPayApiKey: String,
|
||||||
) : BaseConfigModel<String>
|
val moonPayApiSecretKey: String,
|
||||||
|
)
|
||||||
|
|
||||||
class ConfigModel(val features: List<FeatureModel>?, val configValues: List<ConfigValueModel>?) {
|
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
|
||||||
companion object {
|
companion object {
|
||||||
fun empty(): ConfigModel = ConfigModel(listOf(), listOf())
|
fun empty(): ConfigModel = ConfigModel(null, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.send.redux
|
||||||
|
|
||||||
import com.tangem.Message
|
import com.tangem.Message
|
||||||
import com.tangem.blockchain.common.Amount
|
import com.tangem.blockchain.common.Amount
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.tap.common.redux.ErrorAction
|
import com.tangem.tap.common.redux.ErrorAction
|
||||||
import com.tangem.tap.common.redux.ToastNotificationAction
|
import com.tangem.tap.common.redux.ToastNotificationAction
|
||||||
import com.tangem.tap.domain.TapError
|
import com.tangem.tap.domain.TapError
|
||||||
|
|
@ -22,7 +23,7 @@ object ReleaseSendState : Action
|
||||||
|
|
||||||
data class PrepareSendScreen(
|
data class PrepareSendScreen(
|
||||||
val coinAmount: Amount?,
|
val coinAmount: Amount?,
|
||||||
val tokenAmount: Amount? = null
|
val tokenAmount: Amount? = null,
|
||||||
) : SendScreenAction
|
) : SendScreenAction
|
||||||
|
|
||||||
// Address or PayId
|
// Address or PayId
|
||||||
|
|
@ -33,7 +34,27 @@ sealed class AddressPayIdActionUi : SendScreenActionUi {
|
||||||
object CheckAddressPayId : AddressPayIdActionUi()
|
object CheckAddressPayId : AddressPayIdActionUi()
|
||||||
data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi()
|
data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi()
|
||||||
data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi()
|
data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi()
|
||||||
data class ChangePayIdState(val sendingToPayIdEnabled: Boolean): AddressPayIdActionUi()
|
data class ChangePayIdState(val sendingToPayIdEnabled: Boolean) : AddressPayIdActionUi()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sealed class TransactionExtrasAction : SendScreenActionUi {
|
||||||
|
data class Prepare(
|
||||||
|
val blockchain: Blockchain,
|
||||||
|
val walletAddress: String,
|
||||||
|
val xrpTag: String?,
|
||||||
|
) : TransactionExtrasAction()
|
||||||
|
|
||||||
|
object Release : TransactionExtrasAction()
|
||||||
|
|
||||||
|
sealed class XlmMemo : TransactionExtrasAction() {
|
||||||
|
// data class ChangeSelectedMemo(val memoType: XlmMemoType) : XlmMemo()
|
||||||
|
data class HandleUserInput(val data: String) : XlmMemo()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class XrpDestinationTag : TransactionExtrasAction() {
|
||||||
|
data class HandleUserInput(val data: String) : XrpDestinationTag()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class AddressPayIdVerifyAction : SendScreenAction {
|
sealed class AddressPayIdVerifyAction : SendScreenAction {
|
||||||
|
|
@ -122,6 +143,7 @@ sealed class SendAction : SendScreenAction {
|
||||||
val sendAllCallback: () -> Unit,
|
val sendAllCallback: () -> Unit,
|
||||||
val reduceAmount: BigDecimal,
|
val reduceAmount: BigDecimal,
|
||||||
) : Dialog()
|
) : Dialog()
|
||||||
|
|
||||||
object Hide : Dialog()
|
object Hide : Dialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
|
||||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError
|
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError
|
||||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress
|
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress
|
||||||
import com.tangem.tap.features.send.redux.FeeAction
|
import com.tangem.tap.features.send.redux.FeeAction
|
||||||
|
import com.tangem.tap.features.send.redux.TransactionExtrasAction
|
||||||
import com.tangem.tap.scope
|
import com.tangem.tap.scope
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -82,22 +83,26 @@ internal class AddressPayIdMiddleware {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
when (result) {
|
when (result) {
|
||||||
is Result.Success -> {
|
is Result.Success -> {
|
||||||
val address = result.data.getAddress()
|
val addressDetails = result.data.getAddressDetails()
|
||||||
if (address == null) {
|
if (addressDetails == null) {
|
||||||
dispatch(SetPayIdError(Error.PAY_ID_NOT_REGISTERED))
|
dispatch(SetPayIdError(Error.PAY_ID_NOT_REGISTERED))
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val address = addressDetails.address
|
||||||
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, address)
|
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, address)
|
||||||
if (failReason == null) {
|
if (failReason == null) {
|
||||||
dispatch(SetPayIdWalletAddress(payId, address, isUserInput))
|
dispatch(SetPayIdWalletAddress(payId, address, isUserInput))
|
||||||
|
dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, addressDetails.tag))
|
||||||
dispatch(FeeAction.RequestFee)
|
dispatch(FeeAction.RequestFee)
|
||||||
} else {
|
} else {
|
||||||
dispatch(SetAddressError(failReason))
|
dispatch(SetAddressError(failReason))
|
||||||
|
dispatch(TransactionExtrasAction.Release)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Result.Failure -> {
|
is Result.Failure -> {
|
||||||
dispatch(SetPayIdError(Error.PAY_ID_REQUEST_FAILED))
|
dispatch(SetPayIdError(Error.PAY_ID_REQUEST_FAILED))
|
||||||
|
dispatch(TransactionExtrasAction.Release)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,8 +115,10 @@ internal class AddressPayIdMiddleware {
|
||||||
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress)
|
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress)
|
||||||
if (failReason == null) {
|
if (failReason == null) {
|
||||||
dispatch(SetWalletAddress(supposedAddress, isUserInput))
|
dispatch(SetWalletAddress(supposedAddress, isUserInput))
|
||||||
|
dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, null))
|
||||||
} else {
|
} else {
|
||||||
dispatch(SetAddressError(failReason))
|
dispatch(SetAddressError(failReason))
|
||||||
|
dispatch(TransactionExtrasAction.Release)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,15 +33,13 @@ class RequestFeeMiddleware {
|
||||||
dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState()))
|
dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val typedAmount = sendState.amountState.amountToExtract ?: return
|
val typedAmount = sendState.amountState.amountToExtract ?: return
|
||||||
val recipientAddress = sendState.addressPayIdState.recipientWalletAddress!!
|
|
||||||
val cryptoSendToRecipient = sendState.amountState.amountToSendCrypto
|
|
||||||
|
|
||||||
val recipientAmount = Amount(typedAmount, cryptoSendToRecipient)
|
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress!!
|
||||||
|
val destinationAmount = Amount(typedAmount, sendState.amountState.amountToSendCrypto)
|
||||||
val txSender = walletManager as TransactionSender
|
val txSender = walletManager as TransactionSender
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val feeResult = txSender.getFee(recipientAmount, recipientAddress)
|
val feeResult = txSender.getFee(destinationAmount, destinationAddress)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
when (feeResult) {
|
when (feeResult) {
|
||||||
is Result.Success -> {
|
is Result.Success -> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.tangem.tap.features.send.redux.middlewares
|
package com.tangem.tap.features.send.redux.middlewares
|
||||||
|
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
|
||||||
|
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
|
||||||
import com.tangem.blockchain.common.*
|
import com.tangem.blockchain.common.*
|
||||||
import com.tangem.blockchain.extensions.Result
|
import com.tangem.blockchain.extensions.Result
|
||||||
import com.tangem.blockchain.extensions.Signer
|
import com.tangem.blockchain.extensions.Signer
|
||||||
|
|
@ -17,6 +19,7 @@ import com.tangem.tap.domain.extensions.minimalAmount
|
||||||
import com.tangem.tap.features.send.redux.*
|
import com.tangem.tap.features.send.redux.*
|
||||||
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
|
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
|
||||||
import com.tangem.tap.features.send.redux.states.SendButtonState
|
import com.tangem.tap.features.send.redux.states.SendButtonState
|
||||||
|
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
|
||||||
import com.tangem.tap.scope
|
import com.tangem.tap.scope
|
||||||
import com.tangem.tap.tangemSdk
|
import com.tangem.tap.tangemSdk
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -52,7 +55,7 @@ private fun verifyAndSendTransaction(
|
||||||
val sendState = appState?.sendState ?: return
|
val sendState = appState?.sendState ?: return
|
||||||
val walletManager = appState.globalState.scanNoteResponse?.walletManager ?: return
|
val walletManager = appState.globalState.scanNoteResponse?.walletManager ?: return
|
||||||
val card = appState.globalState.scanNoteResponse.card
|
val card = appState.globalState.scanNoteResponse.card
|
||||||
val recipientAddress = sendState.addressPayIdState.recipientWalletAddress ?: return
|
val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return
|
||||||
val typedAmount = sendState.amountState.amountToExtract ?: return
|
val typedAmount = sendState.amountState.amountToExtract ?: return
|
||||||
val feeAmount = sendState.feeState.currentFee ?: return
|
val feeAmount = sendState.feeState.currentFee ?: return
|
||||||
|
|
||||||
|
|
@ -67,14 +70,16 @@ private fun verifyAndSendTransaction(
|
||||||
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
|
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
|
||||||
dispatch(AmountActionUi.CheckAmountToSend)
|
dispatch(AmountActionUi.CheckAmountToSend)
|
||||||
}, sendAllCallback = {
|
}, sendAllCallback = {
|
||||||
sendTransaction(action, walletManager, amountToSend, feeAmount, recipientAddress, card, dispatch)
|
sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
|
||||||
|
sendState.transactionExtrasState, card, dispatch)
|
||||||
}, reduceAmount))
|
}, reduceAmount))
|
||||||
}
|
}
|
||||||
transactionErrors.isNotEmpty() -> {
|
transactionErrors.isNotEmpty() -> {
|
||||||
dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager)))
|
dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager)))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
sendTransaction(action, walletManager, amountToSend, feeAmount, recipientAddress, card, dispatch)
|
sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
|
||||||
|
sendState.transactionExtrasState, card, dispatch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -84,12 +89,17 @@ private fun sendTransaction(
|
||||||
walletManager: WalletManager,
|
walletManager: WalletManager,
|
||||||
amountToSend: Amount,
|
amountToSend: Amount,
|
||||||
feeAmount: Amount,
|
feeAmount: Amount,
|
||||||
recipientAddress: String,
|
destinationAddress: String,
|
||||||
|
transactionExtras: TransactionExtrasState,
|
||||||
card: Card,
|
card: Card,
|
||||||
dispatch: (Action) -> Unit
|
dispatch: (Action) -> Unit
|
||||||
) {
|
) {
|
||||||
dispatch(SendAction.ChangeSendButtonState(SendButtonState.PROGRESS))
|
dispatch(SendAction.ChangeSendButtonState(SendButtonState.PROGRESS))
|
||||||
val txData = walletManager.createTransaction(amountToSend, feeAmount, recipientAddress)
|
var txData = walletManager.createTransaction(amountToSend, feeAmount, destinationAddress)
|
||||||
|
|
||||||
|
transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) }
|
||||||
|
transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it)) }
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
walletManager.update()
|
walletManager.update()
|
||||||
val isLinkedTerminal = tangemSdk.config.linkedTerminal
|
val isLinkedTerminal = tangemSdk.config.linkedTerminal
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class AddressPayIdReducer : SendInternalReducer {
|
||||||
viewFieldValue = InputViewValue(action.payId, action.isUserInput),
|
viewFieldValue = InputViewValue(action.payId, action.isUserInput),
|
||||||
normalFieldValue = action.payId,
|
normalFieldValue = action.payId,
|
||||||
truncatedFieldValue = state.truncate(action.payId),
|
truncatedFieldValue = state.truncate(action.payId),
|
||||||
recipientWalletAddress = action.payIdWalletAddress,
|
destinationWalletAddress = action.payIdWalletAddress,
|
||||||
error = null
|
error = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -51,13 +51,13 @@ class AddressPayIdReducer : SendInternalReducer {
|
||||||
viewFieldValue = InputViewValue(action.address, action.isUserInput),
|
viewFieldValue = InputViewValue(action.address, action.isUserInput),
|
||||||
normalFieldValue = action.address,
|
normalFieldValue = action.address,
|
||||||
truncatedFieldValue = state.truncate(action.address),
|
truncatedFieldValue = state.truncate(action.address),
|
||||||
recipientWalletAddress = action.address,
|
destinationWalletAddress = action.address,
|
||||||
error = null
|
error = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled)
|
is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled)
|
||||||
is AddressVerification.SetAddressError -> state.copy(error = action.error, recipientWalletAddress = null)
|
is AddressVerification.SetAddressError -> state.copy(error = action.error, destinationWalletAddress = null)
|
||||||
is PayIdVerification.SetPayIdError -> state.copy(error = action.error, recipientWalletAddress = null)
|
is PayIdVerification.SetPayIdError -> state.copy(error = action.error, destinationWalletAddress = null)
|
||||||
}
|
}
|
||||||
return updateLastState(sendState.copy(addressPayIdState = result), result)
|
return updateLastState(sendState.copy(addressPayIdState = result), result)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class SendScreenReducer {
|
||||||
val reducer: SendInternalReducer = when (action) {
|
val reducer: SendInternalReducer = when (action) {
|
||||||
is PrepareSendScreen -> PrepareSendScreenStatesReducer()
|
is PrepareSendScreen -> PrepareSendScreenStatesReducer()
|
||||||
is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer()
|
is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer()
|
||||||
|
is TransactionExtrasAction -> TransactionExtrasReducer()
|
||||||
is AmountActionUi, is AmountAction -> AmountReducer()
|
is AmountActionUi, is AmountAction -> AmountReducer()
|
||||||
is FeeActionUi, is FeeAction -> FeeReducer()
|
is FeeActionUi, is FeeAction -> FeeReducer()
|
||||||
is ReceiptAction -> ReceiptReducer()
|
is ReceiptAction -> ReceiptReducer()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
package com.tangem.tap.features.send.redux.reducers
|
||||||
|
|
||||||
|
import com.tangem.blockchain.blockchains.stellar.StellarMemo
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||||
|
import com.tangem.tap.features.send.redux.TransactionExtrasAction.*
|
||||||
|
import com.tangem.tap.features.send.redux.states.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
class TransactionExtrasReducer : SendInternalReducer {
|
||||||
|
override fun handle(action: SendScreenAction, sendState: SendState): SendState {
|
||||||
|
return when (action) {
|
||||||
|
is Prepare -> handleInitialization(action, sendState)
|
||||||
|
Release -> handleRelease(action, sendState)
|
||||||
|
is XlmMemo -> handleMemo(action, sendState, sendState.transactionExtrasState)
|
||||||
|
is XrpDestinationTag -> handleXrpTag(action, sendState, sendState.transactionExtrasState)
|
||||||
|
else -> sendState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleInitialization(action: Prepare, sendState: SendState): SendState {
|
||||||
|
val emptyResult = TransactionExtrasState()
|
||||||
|
val result = when (action.blockchain) {
|
||||||
|
Blockchain.XRP -> {
|
||||||
|
// 'r' - without tag, 'x' - with tag
|
||||||
|
if (action.walletAddress.startsWith("r", true)) {
|
||||||
|
val tag = action.xrpTag?.toLongOrNull()
|
||||||
|
if (tag == null) {
|
||||||
|
TransactionExtrasState(xrpDestinationTag = XrpDestinationTagState())
|
||||||
|
} else {
|
||||||
|
TransactionExtrasState(xrpDestinationTag = XrpDestinationTagState(
|
||||||
|
InputViewValue("$tag", false), tag)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Blockchain.Stellar -> TransactionExtrasState(xlmMemo = XlmMemoState())
|
||||||
|
else -> emptyResult
|
||||||
|
}
|
||||||
|
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRelease(action: SendScreenAction, sendState: SendState): SendState {
|
||||||
|
val result = TransactionExtrasState()
|
||||||
|
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleMemo(
|
||||||
|
action: XlmMemo,
|
||||||
|
sendState: SendState,
|
||||||
|
infoState: TransactionExtrasState,
|
||||||
|
): SendState {
|
||||||
|
fun clearMemo(memo: XlmMemoState): XlmMemoState = memo.copy(text = null, id = null)
|
||||||
|
|
||||||
|
val result = when (action) {
|
||||||
|
// is XlmMemo.ChangeSelectedMemo -> {
|
||||||
|
// val inputViewValue = InputViewValue("", false)
|
||||||
|
// val memo = infoState.xlmMemo?.copy(
|
||||||
|
// viewFieldValue = inputViewValue,
|
||||||
|
// selectedMemoType = action.memoType,
|
||||||
|
// ) ?: XlmMemoState(inputViewValue, action.memoType)
|
||||||
|
//
|
||||||
|
// infoState.copy(xlmMemo = clearMemo(memo))
|
||||||
|
// }
|
||||||
|
is XlmMemo.HandleUserInput -> {
|
||||||
|
val inputViewValue = InputViewValue(action.data, true)
|
||||||
|
var memo = infoState.xlmMemo?.copy(viewFieldValue = inputViewValue) ?: XlmMemoState(inputViewValue)
|
||||||
|
memo = clearMemo(memo)
|
||||||
|
memo = when (infoState.xlmMemo?.selectedMemoType) {
|
||||||
|
XlmMemoType.TEXT -> memo.copy(text = StellarMemo.Text(action.data))
|
||||||
|
XlmMemoType.ID -> {
|
||||||
|
val id = action.data.toIntOrNull()?.toBigInteger()
|
||||||
|
if (id != null) memo.copy(id = StellarMemo.Id(id)) else memo
|
||||||
|
}
|
||||||
|
null -> memo
|
||||||
|
}
|
||||||
|
infoState.copy(xlmMemo = memo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleXrpTag(
|
||||||
|
action: XrpDestinationTag,
|
||||||
|
sendState: SendState,
|
||||||
|
infoState: TransactionExtrasState,
|
||||||
|
): SendState {
|
||||||
|
val result = when (action) {
|
||||||
|
is XrpDestinationTag.HandleUserInput -> {
|
||||||
|
val tag = action.data.toLongOrNull()
|
||||||
|
if (tag != null) {
|
||||||
|
val tagState = XrpDestinationTagState(InputViewValue(action.data, true), tag)
|
||||||
|
infoState.copy(xrpDestinationTag = tagState)
|
||||||
|
} else {
|
||||||
|
infoState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updateLastState(sendState.copy(transactionExtrasState = result), result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package com.tangem.tap.features.send.redux.states
|
package com.tangem.tap.features.send.redux.states
|
||||||
|
|
||||||
|
import com.tangem.blockchain.blockchains.stellar.StellarMemo
|
||||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
|
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
|
||||||
|
|
||||||
data class AddressPayIdState(
|
data class AddressPayIdState(
|
||||||
val viewFieldValue: InputViewValue = InputViewValue(""),
|
val viewFieldValue: InputViewValue = InputViewValue(""),
|
||||||
val normalFieldValue: String? = null,
|
val normalFieldValue: String? = null,
|
||||||
val truncatedFieldValue: String? = null,
|
val truncatedFieldValue: String? = null,
|
||||||
val recipientWalletAddress: String? = null,
|
val destinationWalletAddress: String? = null,
|
||||||
val error: AddressPayIdVerifyAction.Error? = null,
|
val error: AddressPayIdVerifyAction.Error? = null,
|
||||||
val truncateHandler: ((String) -> String)? = null,
|
val truncateHandler: ((String) -> String)? = null,
|
||||||
val sendingToPayIdEnabled: Boolean = false,
|
val sendingToPayIdEnabled: Boolean = false,
|
||||||
|
|
@ -17,7 +18,37 @@ data class AddressPayIdState(
|
||||||
|
|
||||||
fun truncate(value: String): String = truncateHandler?.invoke(value) ?: value
|
fun truncate(value: String): String = truncateHandler?.invoke(value) ?: value
|
||||||
|
|
||||||
fun isReady(): Boolean = error == null && recipientWalletAddress?.isNotEmpty() ?: false
|
fun isReady(): Boolean = error == null && destinationWalletAddress?.isNotEmpty() ?: false
|
||||||
|
|
||||||
fun isPayIdState(): Boolean = recipientWalletAddress != null && recipientWalletAddress != normalFieldValue
|
fun isPayIdState(): Boolean = destinationWalletAddress != null && destinationWalletAddress != normalFieldValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class TransactionExtrasState(
|
||||||
|
val xlmMemo: XlmMemoState? = null,
|
||||||
|
val xrpDestinationTag: XrpDestinationTagState? = null
|
||||||
|
) : IdStateHolder {
|
||||||
|
override val stateId: StateId = StateId.TRANSACTION_EXTRAS
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class XlmMemoType {
|
||||||
|
TEXT, ID
|
||||||
|
}
|
||||||
|
|
||||||
|
data class XlmMemoState(
|
||||||
|
val viewFieldValue: InputViewValue = InputViewValue(""),
|
||||||
|
val selectedMemoType: XlmMemoType = XlmMemoType.ID,
|
||||||
|
val text: StellarMemo.Text? = null,
|
||||||
|
val id: StellarMemo.Id? = null,
|
||||||
|
) {
|
||||||
|
val memo: StellarMemo?
|
||||||
|
get() = when (selectedMemoType) {
|
||||||
|
XlmMemoType.TEXT -> text
|
||||||
|
XlmMemoType.ID -> id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag must contains only digits
|
||||||
|
data class XrpDestinationTagState(
|
||||||
|
val viewFieldValue: InputViewValue = InputViewValue(""),
|
||||||
|
val tag: Long? = null
|
||||||
|
)
|
||||||
|
|
@ -22,7 +22,7 @@ interface IdStateHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class StateId {
|
enum class StateId {
|
||||||
SEND_SCREEN, ADDRESS_PAY_ID, AMOUNT, FEE, RECEIPT
|
SEND_SCREEN, ADDRESS_PAY_ID, TRANSACTION_EXTRAS, AMOUNT, FEE, RECEIPT
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SendScreenState : StateType, IdStateHolder
|
interface SendScreenState : StateType, IdStateHolder
|
||||||
|
|
@ -33,6 +33,7 @@ data class SendState(
|
||||||
val tokenConverter: CurrencyConverter? = null,
|
val tokenConverter: CurrencyConverter? = null,
|
||||||
val lastChangedStates: LinkedHashSet<StateId> = linkedSetOf(),
|
val lastChangedStates: LinkedHashSet<StateId> = linkedSetOf(),
|
||||||
val addressPayIdState: AddressPayIdState = AddressPayIdState(),
|
val addressPayIdState: AddressPayIdState = AddressPayIdState(),
|
||||||
|
val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(),
|
||||||
val amountState: AmountState = AmountState(),
|
val amountState: AmountState = AmountState(),
|
||||||
val feeState: FeeState = FeeState(),
|
val feeState: FeeState = FeeState(),
|
||||||
val receiptState: ReceiptState = ReceiptState(),
|
val receiptState: ReceiptState = ReceiptState(),
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
||||||
|
|
||||||
initSendButtonStates()
|
initSendButtonStates()
|
||||||
setupAddressOrPayIdLayout()
|
setupAddressOrPayIdLayout()
|
||||||
|
setupTransactionExtrasLayout()
|
||||||
setupAmountLayout()
|
setupAmountLayout()
|
||||||
setupFeeLayout()
|
setupFeeLayout()
|
||||||
|
|
||||||
|
|
@ -106,6 +107,32 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupTransactionExtrasLayout() {
|
||||||
|
etMemo.inputtedTextAsFlow()
|
||||||
|
.debounce(400)
|
||||||
|
.filter {
|
||||||
|
val info = store.state.sendState.transactionExtrasState
|
||||||
|
info.xlmMemo?.viewFieldValue?.value != it
|
||||||
|
}
|
||||||
|
.onEach { store.dispatch(TransactionExtrasAction.XlmMemo.HandleUserInput(it)) }
|
||||||
|
.launchIn(mainScope)
|
||||||
|
|
||||||
|
// groupMemo.setOnCheckedChangeListener { group, checkedId ->
|
||||||
|
// if (checkedId == -1) return@setOnCheckedChangeListener
|
||||||
|
//
|
||||||
|
// store.dispatch(TransactionExtrasAction.XlmMemo.ChangeSelectedMemo(MemoUiHelper.toType(checkedId)))
|
||||||
|
// }
|
||||||
|
|
||||||
|
etDestinationTag.inputtedTextAsFlow()
|
||||||
|
.debounce(400)
|
||||||
|
.filter {
|
||||||
|
val info = store.state.sendState.transactionExtrasState
|
||||||
|
info.xrpDestinationTag?.viewFieldValue?.value != it
|
||||||
|
}
|
||||||
|
.onEach { store.dispatch(TransactionExtrasAction.XrpDestinationTag.HandleUserInput(it)) }
|
||||||
|
.launchIn(mainScope)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode != ScanQrCodeActivity.SCAN_QR_REQUEST_CODE) return
|
if (requestCode != ScanQrCodeActivity.SCAN_QR_REQUEST_CODE) return
|
||||||
|
|
||||||
|
|
@ -191,7 +218,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
||||||
chipGroup.setOnCheckedChangeListener { group, checkedId ->
|
chipGroup.setOnCheckedChangeListener { group, checkedId ->
|
||||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||||
|
|
||||||
store.dispatch(ChangeSelectedFee(FeeUiHelper.idToFee(checkedId)))
|
store.dispatch(ChangeSelectedFee(FeeUiHelper.toType(checkedId)))
|
||||||
store.dispatch(CheckAmountToSend)
|
store.dispatch(CheckAmountToSend)
|
||||||
}
|
}
|
||||||
swIncludeFee.setOnCheckedChangeListener { btn, isChecked ->
|
swIncludeFee.setOnCheckedChangeListener { btn, isChecked ->
|
||||||
|
|
@ -238,7 +265,7 @@ fun EditText.inputtedTextAsFlow(): Flow<String> = callbackFlow {
|
||||||
|
|
||||||
class FeeUiHelper {
|
class FeeUiHelper {
|
||||||
companion object {
|
companion object {
|
||||||
fun feeToId(fee: FeeType): Int {
|
fun toId(fee: FeeType): Int {
|
||||||
return when (fee) {
|
return when (fee) {
|
||||||
FeeType.SINGLE -> View.NO_ID
|
FeeType.SINGLE -> View.NO_ID
|
||||||
FeeType.LOW -> R.id.chipLow
|
FeeType.LOW -> R.id.chipLow
|
||||||
|
|
@ -247,7 +274,7 @@ class FeeUiHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun idToFee(id: Int): FeeType {
|
fun toType(id: Int): FeeType {
|
||||||
return when (id) {
|
return when (id) {
|
||||||
R.id.chipLow -> FeeType.LOW
|
R.id.chipLow -> FeeType.LOW
|
||||||
R.id.chipNormal -> FeeType.NORMAL
|
R.id.chipNormal -> FeeType.NORMAL
|
||||||
|
|
@ -258,6 +285,26 @@ class FeeUiHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//class MemoUiHelper {
|
||||||
|
// companion object {
|
||||||
|
// fun toId(memo: MemoType): Int {
|
||||||
|
// return when (memo) {
|
||||||
|
// MemoType.TEXT -> R.id.chipMemoText
|
||||||
|
// MemoType.ID -> R.id.chipMemoId
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun toType(id: Int): MemoType {
|
||||||
|
// return when (id) {
|
||||||
|
// R.id.chipMemoText -> MemoType.TEXT
|
||||||
|
// R.id.chipMemoId -> MemoType.ID
|
||||||
|
// else -> MemoType.TEXT
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
private fun ToggleWidget.setupSendButtonStateModifiers(context: Context) {
|
private fun ToggleWidget.setupSendButtonStateModifiers(context: Context) {
|
||||||
mainViewModifiers.clear()
|
mainViewModifiers.clear()
|
||||||
mainViewModifiers.add(ReplaceTextStateModifier(context.getString(R.string.send_title), ""))
|
mainViewModifiers.add(ReplaceTextStateModifier(context.getString(R.string.send_title), ""))
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.send.ui.stateSubscribers
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.text.InputType
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
|
@ -43,10 +44,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
||||||
override fun updateWithNewState(fg: BaseStoreFragment, state: SendState) {
|
override fun updateWithNewState(fg: BaseStoreFragment, state: SendState) {
|
||||||
val lastChangedStates = state.lastChangedStates.toList()
|
val lastChangedStates = state.lastChangedStates.toList()
|
||||||
state.lastChangedStates.clear()
|
state.lastChangedStates.clear()
|
||||||
|
fg.main_send_container.beginDelayedTransition()
|
||||||
lastChangedStates.forEach {
|
lastChangedStates.forEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
StateId.SEND_SCREEN -> handleSendScreen(fg, state)
|
StateId.SEND_SCREEN -> handleSendScreen(fg, state)
|
||||||
StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState)
|
StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState)
|
||||||
|
StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(fg, state.transactionExtrasState)
|
||||||
StateId.AMOUNT -> handleAmountState(fg, state.amountState)
|
StateId.AMOUNT -> handleAmountState(fg, state.amountState)
|
||||||
StateId.FEE -> handleFeeState(fg, state.feeState)
|
StateId.FEE -> handleFeeState(fg, state.feeState)
|
||||||
StateId.RECEIPT -> handleReceiptState(fg, state.receiptState)
|
StateId.RECEIPT -> handleReceiptState(fg, state.receiptState)
|
||||||
|
|
@ -54,6 +57,25 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleTransactionExtrasState(fg: BaseStoreFragment, infoState: TransactionExtrasState) {
|
||||||
|
fun showView(view: View, info: Any?) {
|
||||||
|
view.show(info != null)
|
||||||
|
}
|
||||||
|
showView(fg.xlmMemoContainer, infoState.xlmMemo)
|
||||||
|
showView(fg.xrpDestinationTagContainer, infoState.xrpDestinationTag)
|
||||||
|
|
||||||
|
infoState.xlmMemo?.let {
|
||||||
|
fg.etMemo.inputType = when (it.selectedMemoType) {
|
||||||
|
XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||||
|
XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
|
||||||
|
}
|
||||||
|
if (!it.viewFieldValue.isFromUserInput) fg.etMemo.setText(it.viewFieldValue.value)
|
||||||
|
}
|
||||||
|
infoState.xrpDestinationTag?.let {
|
||||||
|
if (!it.viewFieldValue.isFromUserInput) fg.etDestinationTag.setText(it.viewFieldValue.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSendScreen(fg: BaseStoreFragment, state: SendState) {
|
private fun handleSendScreen(fg: BaseStoreFragment, state: SendState) {
|
||||||
val sendFragment = (fg as? SendFragment) ?: return
|
val sendFragment = (fg as? SendFragment) ?: return
|
||||||
|
|
||||||
|
|
@ -106,14 +128,13 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
||||||
|
|
||||||
val hintResId = if (state.sendingToPayIdEnabled) {
|
val hintResId = if (state.sendingToPayIdEnabled) {
|
||||||
R.string.send_destination_hint_address_payid
|
R.string.send_destination_hint_address_payid
|
||||||
}else {
|
} else {
|
||||||
R.string.send_destination_hint_address
|
R.string.send_destination_hint_address
|
||||||
}
|
}
|
||||||
til.hint = til.getString(hintResId)
|
til.hint = til.getString(hintResId)
|
||||||
til.parent?.parent?.beginDelayedTransition()
|
|
||||||
til.error = parsedError
|
til.error = parsedError
|
||||||
til.isErrorEnabled = parsedError != null
|
til.isErrorEnabled = parsedError != null
|
||||||
til.helperText = state.recipientWalletAddress
|
til.helperText = state.destinationWalletAddress
|
||||||
til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
|
til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
|
||||||
|
|
||||||
if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
|
if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
|
||||||
|
|
@ -130,10 +151,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
||||||
}
|
}
|
||||||
else -> context.getString(state.error.localizedMessage)
|
else -> context.getString(state.error.localizedMessage)
|
||||||
}
|
}
|
||||||
fg.amountContainer.parent?.beginDelayedTransition()
|
|
||||||
fg.tilAmountToSend.enableError(true, message)
|
fg.tilAmountToSend.enableError(true, message)
|
||||||
} else {
|
} else {
|
||||||
if (fg.tilAmountToSend.isErrorEnabled) fg.amountContainer.parent?.beginDelayedTransition()
|
|
||||||
fg.tilAmountToSend.enableError(false)
|
fg.tilAmountToSend.enableError(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,26 +187,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleFeeState(fg: BaseStoreFragment, state: FeeState) {
|
private fun handleFeeState(fg: BaseStoreFragment, state: FeeState) {
|
||||||
var delayedTransitionScheduled = false
|
fg.chipGroup.fitChipsByGroupWidth()
|
||||||
fg.view?.findViewById<ViewGroup>(R.id.clNetworkFee)?.let {
|
fg.view?.findViewById<ViewGroup>(R.id.clNetworkFee)?.show(state.mainLayoutIsVisible)
|
||||||
it.show(state.mainLayoutIsVisible) {
|
|
||||||
(it.parent as? ViewGroup)?.beginDelayedTransition()
|
|
||||||
delayedTransitionScheduled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fg.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f
|
fg.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f
|
||||||
fg.llFeeControlsContainer.show(state.controlsLayoutIsVisible) {
|
fg.llFeeControlsContainer.show(state.controlsLayoutIsVisible)
|
||||||
if (!delayedTransitionScheduled) {
|
fg.chipGroup.show(state.feeChipGroupIsVisible)
|
||||||
fg.llFeeControlsContainer.parent?.parent?.beginDelayedTransition()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fg.chipGroup.show(state.feeChipGroupIsVisible) {
|
|
||||||
if (!delayedTransitionScheduled) {
|
|
||||||
fg.llFeeControlsContainer.parent?.parent?.beginDelayedTransition()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fg.swIncludeFee.isEnabled = state.includeFeeSwitcherIsEnabled
|
fg.swIncludeFee.isEnabled = state.includeFeeSwitcherIsEnabled
|
||||||
if (fg.swIncludeFee.isChecked != state.feeIsIncluded) {
|
if (fg.swIncludeFee.isChecked != state.feeIsIncluded) {
|
||||||
|
|
@ -200,7 +205,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val chipId = FeeUiHelper.feeToId(state.selectedFeeType)
|
val chipId = FeeUiHelper.toId(state.selectedFeeType)
|
||||||
if (fg.chipGroup.checkedChipId != chipId && chipId != View.NO_ID) fg.chipGroup.check(chipId)
|
if (fg.chipGroup.checkedChipId != chipId && chipId != View.NO_ID) fg.chipGroup.check(chipId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@ data class WalletState(
|
||||||
val showSegwitAddress: Boolean
|
val showSegwitAddress: Boolean
|
||||||
get() {
|
get() {
|
||||||
val listOfAddresses = walletAddresses?.list ?: return false
|
val listOfAddresses = walletAddresses?.list ?: return false
|
||||||
return wallet?.blockchain == Blockchain.Bitcoin && listOfAddresses.size > 1
|
return (wallet?.blockchain == Blockchain.Bitcoin || wallet?.blockchain == Blockchain.BitcoinTestnet)
|
||||||
|
&& listOfAddresses.size > 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ import com.google.android.material.snackbar.Snackbar
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
|
import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
|
||||||
import com.tangem.blockchain.common.address.AddressType
|
import com.tangem.blockchain.common.address.AddressType
|
||||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
import com.tangem.tap.common.extensions.*
|
||||||
import com.tangem.tap.common.extensions.hide
|
|
||||||
import com.tangem.tap.common.extensions.show
|
|
||||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||||
|
|
@ -236,6 +234,8 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber<Walle
|
||||||
tv_address.setPadding(tv_address.paddingStart, tvAddressPaddingTop / 2,
|
tv_address.setPadding(tv_address.paddingStart, tvAddressPaddingTop / 2,
|
||||||
tv_address.paddingEnd, tv_address.paddingBottom)
|
tv_address.paddingEnd, tv_address.paddingBottom)
|
||||||
chip_group_segwit.show()
|
chip_group_segwit.show()
|
||||||
|
chip_group_segwit.fitChipsByGroupWidth()
|
||||||
|
|
||||||
val checkedId = SegwitUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
|
val checkedId = SegwitUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
|
||||||
if (checkedId != View.NO_ID) chip_group_segwit.check(checkedId)
|
if (checkedId != View.NO_ID) chip_group_segwit.check(checkedId)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ data class VerifyPayIdResponse(
|
||||||
val addresses: List<PayIdAddress> = mutableListOf(),
|
val addresses: List<PayIdAddress> = mutableListOf(),
|
||||||
val payId: String? = null,
|
val payId: String? = null,
|
||||||
) {
|
) {
|
||||||
fun getAddress(): String? {
|
fun getAddressDetails(): PayIdAddressDetails? {
|
||||||
return if (addresses.isEmpty()) null
|
return if (addresses.isEmpty()) null
|
||||||
else addresses[0].addressDetails.address
|
else addresses[0].addressDetails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/main_send_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,9 @@
|
||||||
android:id="@+id/chip_group_segwit"
|
android:id="@+id/chip_group_segwit"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="10dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="10dp"
|
||||||
android:visibility="visible"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,197 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/csRecipientAddress"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="16dp">
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/tilAddressOrPayId"
|
android:id="@+id/addressContainer"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/send_destination_hint_address_payid"
|
|
||||||
app:boxBackgroundColor="@color/backgroundLightGray"
|
|
||||||
app:errorIconDrawable="@null"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/etAddressOrPayId"
|
android:id="@+id/tilAddressOrPayId"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/send_destination_hint_address_payid"
|
||||||
|
app:boxBackgroundColor="@color/backgroundLightGray"
|
||||||
|
app:errorIconDrawable="@null"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/etAddressOrPayId"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="98dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="139mrsJgyWnJ **** y9BV" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/flPaste"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<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"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/extrasFieldsContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/addressContainer">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/xlmMemoContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="middle"
|
android:orientation="vertical"
|
||||||
android:inputType="textNoSuggestions"
|
android:visibility="gone"
|
||||||
android:paddingStart="0dp"
|
tools:visibility="visible">
|
||||||
android:paddingEnd="98dp"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textSize="16sp"
|
|
||||||
tools:text="139mrsJgyWnJ **** y9BV" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
<!-- <com.google.android.material.chip.ChipGroup-->
|
||||||
|
<!-- android:id="@+id/groupMemo"-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- android:layout_marginTop="8dp"-->
|
||||||
|
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||||
|
<!-- app:layout_constraintStart_toStartOf="parent"-->
|
||||||
|
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||||
|
<!-- app:selectionRequired="true"-->
|
||||||
|
<!-- app:singleLine="true"-->
|
||||||
|
<!-- app:singleSelection="true">-->
|
||||||
|
|
||||||
<FrameLayout
|
<!-- <com.google.android.material.chip.Chip-->
|
||||||
android:id="@+id/flPaste"
|
<!-- android:id="@+id/chipMemoText"-->
|
||||||
android:layout_width="@dimen/btn_rounded_size"
|
<!-- style="@style/TapChip"-->
|
||||||
android:layout_height="@dimen/btn_rounded_size"
|
<!-- android:layout_width="wrap_content"-->
|
||||||
android:layout_marginEnd="8dp"
|
<!-- android:layout_height="wrap_content"-->
|
||||||
android:background="@drawable/shape_ellipse"
|
<!-- android:text="Text" />-->
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/flQrCode"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/flQrCode"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/flQrCode">
|
|
||||||
|
|
||||||
<ImageView
|
<!-- <com.google.android.material.chip.Chip-->
|
||||||
android:id="@+id/imvPaste"
|
<!-- android:id="@+id/chipMemoId"-->
|
||||||
android:layout_width="wrap_content"
|
<!-- style="@style/TapChip"-->
|
||||||
|
<!-- android:layout_width="wrap_content"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- android:text="ID" />-->
|
||||||
|
|
||||||
|
<!-- <com.google.android.material.chip.Chip-->
|
||||||
|
<!-- android:id="@+id/chipMemoHash"-->
|
||||||
|
<!-- style="@style/TapChip"-->
|
||||||
|
<!-- android:layout_width="wrap_content"-->
|
||||||
|
<!-- android:layout_height="wrap_content"-->
|
||||||
|
<!-- android:text="Hash" />-->
|
||||||
|
|
||||||
|
<!-- </com.google.android.material.chip.ChipGroup>-->
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tilMemo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/send_extras_hint_memo"
|
||||||
|
app:boxBackgroundColor="@color/backgroundLightGray"
|
||||||
|
app:errorIconDrawable="@null">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/etMemo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Some memo" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/xrpDestinationTagContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:visibility="gone"
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
tools:visibility="visible">
|
||||||
android:padding="6dp"
|
|
||||||
app:srcCompat="@drawable/paste_selector" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tilDestinationTag"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/send_extras_hint_destination_tag"
|
||||||
|
app:boxBackgroundColor="@color/backgroundLightGray"
|
||||||
|
app:errorIconDrawable="@null">
|
||||||
|
|
||||||
<FrameLayout
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/flQrCode"
|
android:id="@+id/etDestinationTag"
|
||||||
android:layout_width="@dimen/btn_rounded_size"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/btn_rounded_size"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:ellipsize="end"
|
||||||
android:background="@drawable/shape_ellipse"
|
android:inputType="number"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
android:paddingStart="0dp"
|
||||||
app:layout_constraintTop_toTopOf="@+id/tilAddressOrPayId">
|
android:paddingEnd="0dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Some destination tag" />
|
||||||
|
|
||||||
<ImageView
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
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>
|
</FrameLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -33,4 +33,7 @@ this wallet.
|
||||||
<string name="common_start" translatable="false">Start</string>
|
<string name="common_start" translatable="false">Start</string>
|
||||||
<string name="common_back" translatable="false">Back</string>
|
<string name="common_back" translatable="false">Back</string>
|
||||||
|
|
||||||
|
<string name="send_extras_hint_memo" translatable="false">Memo</string>
|
||||||
|
<string name="send_extras_hint_destination_tag" translatable="false">Destination tag</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue