Updated on 2026-08-14
This commit is contained in:
commit
1e8f8723bb
29 changed files with 613 additions and 216 deletions
|
|
@ -72,8 +72,8 @@ dependencies {
|
|||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
|
||||
implementation 'com.tangem:blockchain:1.130.0'
|
||||
implementation 'com.tangem:core:1.90.0'
|
||||
implementation 'com.tangem:sdk:1.90.0'
|
||||
implementation 'com.tangem:core:1.92.0'
|
||||
implementation 'com.tangem:sdk:1.92.0'
|
||||
|
||||
// WebView
|
||||
implementation "androidx.browser:browser:1.2.0"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
[
|
||||
{
|
||||
"name": "isWalletPayIdEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isSendingToPayIdEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isTopUpEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isCreatingTwinCardsAllowed",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
{
|
||||
"isWalletPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isSendingToPayIdEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": false
|
||||
}
|
||||
|
|
@ -1,18 +1,6 @@
|
|||
[
|
||||
{
|
||||
"name": "isWalletPayIdEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isSendingToPayIdEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isTopUpEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "isCreatingTwinCardsAllowed",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
{
|
||||
"isWalletPayIdEnabled": true,
|
||||
"isTopUpEnabled": true,
|
||||
"isSendingToPayIdEnabled": true,
|
||||
"isCreatingTwinCardsAllowed": 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.tangem.commands.common.card.Card
|
||||
|
||||
object FirebaseAnalyticsHandler: AnalyticsHandler {
|
||||
object FirebaseAnalyticsHandler : AnalyticsHandler {
|
||||
override fun triggerEvent(event: AnalyticsEvent, card: Card?) {
|
||||
Firebase.analytics
|
||||
.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 {
|
||||
if (card == null) return bundleOf()
|
||||
return bundleOf(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.ClipData
|
|||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.text.Spannable
|
||||
|
|
@ -39,6 +40,14 @@ fun View.getString(@StringRes id: Int): String {
|
|||
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) {
|
||||
return if (show) this.show(invokeBeforeStateChanged)
|
||||
else this.hide(invokeBeforeStateChanged)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,13 @@ package com.tangem.tap.common.extensions
|
|||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewParent
|
||||
import androidx.core.view.forEach
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.Transition
|
||||
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
|
||||
|
||||
/**
|
||||
|
|
@ -25,4 +29,19 @@ fun ViewParent?.beginDelayedTransition(transition: Transition = AutoTransition()
|
|||
|
||||
fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) {
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.google.firebase.remoteconfig.ktx.remoteConfig
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -30,16 +29,13 @@ class LocalLoader(
|
|||
|
||||
override fun loadConfig(onComplete: (ConfigModel) -> Unit) {
|
||||
val config = try {
|
||||
val featureType = Types.newParameterizedType(List::class.java, FeatureModel::class.java)
|
||||
val featureAdapter: JsonAdapter<List<FeatureModel>> = moshi.adapter(featureType)
|
||||
val valuesType = Types.newParameterizedType(List::class.java, ConfigValueModel::class.java)
|
||||
val valuesAdapter: JsonAdapter<List<ConfigValueModel>> = moshi.adapter(valuesType)
|
||||
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
|
||||
val valuesAdapter: JsonAdapter<ConfigValueModel> = moshi.adapter(ConfigValueModel::class.java)
|
||||
|
||||
val jsonFeatures = readAssetAsString(ConfigLoader.featuresName)
|
||||
val jsonConfigValues = readAssetAsString(ConfigLoader.configValuesName)
|
||||
|
||||
ConfigModel(featureAdapter.fromJson(jsonFeatures) ?: listOf(),
|
||||
valuesAdapter.fromJson(jsonConfigValues) ?: listOf())
|
||||
ConfigModel(featureAdapter.fromJson(jsonFeatures), valuesAdapter.fromJson(jsonConfigValues))
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
ConfigModel.empty()
|
||||
|
|
@ -67,14 +63,13 @@ class RemoteLoader(
|
|||
onComplete(emptyConfig)
|
||||
return@addOnCompleteListener
|
||||
}
|
||||
val featureType = Types.newParameterizedType(List::class.java, FeatureModel::class.java)
|
||||
val featureAdapter: JsonAdapter<List<FeatureModel>> = moshi.adapter(featureType)
|
||||
onComplete(ConfigModel(featureAdapter.fromJson(jsonConfig) ?: listOf(), listOf()))
|
||||
val featureAdapter: JsonAdapter<FeatureModel> = moshi.adapter(FeatureModel::class.java)
|
||||
onComplete(ConfigModel(featureAdapter.fromJson(jsonConfig), null))
|
||||
} else {
|
||||
onComplete(emptyConfig)
|
||||
}
|
||||
}.addOnFailureListener {
|
||||
FirebaseCrashlytics.getInstance().recordException(it)
|
||||
FirebaseAnalyticsHandler.logException("remote_config_error", it)
|
||||
onComplete(emptyConfig)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,14 +27,13 @@ class ConfigManager(
|
|||
|
||||
fun load(onComplete: VoidCallback? = null) {
|
||||
localLoader.loadConfig { config ->
|
||||
config.features?.forEach { setupFeature(it.name, it.value) }
|
||||
config.configValues?.forEach { setupKey(it.name, it.value) }
|
||||
setupFeature(config.features)
|
||||
setupKey(config.configValues)
|
||||
}
|
||||
remoteLoader.loadConfig { config ->
|
||||
setupFeature(config.features)
|
||||
onComplete?.invoke()
|
||||
}
|
||||
// remoteLoader.loadConfig { config ->
|
||||
// config.features?.forEach { setupFeature(it.name, it.value) }
|
||||
// onComplete?.invoke()
|
||||
// }
|
||||
}
|
||||
|
||||
fun turnOff(name: String) {
|
||||
|
|
@ -56,44 +55,35 @@ class ConfigManager(
|
|||
}
|
||||
}
|
||||
|
||||
private fun setupFeature(name: String, value: Boolean) {
|
||||
val newValue = value ?: return
|
||||
private fun setupFeature(featureModel: FeatureModel?) {
|
||||
val model = featureModel ?: return
|
||||
|
||||
when (name) {
|
||||
isWalletPayIdEnabled -> {
|
||||
config = config.copy(isWalletPayIdEnabled = newValue)
|
||||
defaultConfig = defaultConfig.copy(isWalletPayIdEnabled = newValue)
|
||||
}
|
||||
isSendingToPayIdEnabled -> {
|
||||
config = config.copy(isSendingToPayIdEnabled = newValue)
|
||||
defaultConfig = defaultConfig.copy(isSendingToPayIdEnabled = newValue)
|
||||
}
|
||||
isTopUpEnabled -> {
|
||||
config = config.copy(isTopUpEnabled = newValue)
|
||||
defaultConfig = defaultConfig.copy(isTopUpEnabled = newValue)
|
||||
}
|
||||
isCreatingTwinCardsAllowed -> {
|
||||
config = config.copy(isCreatingTwinCardsAllowed = newValue)
|
||||
defaultConfig = defaultConfig.copy(isCreatingTwinCardsAllowed = newValue)
|
||||
}
|
||||
}
|
||||
config = config.copy(
|
||||
isWalletPayIdEnabled = model.isWalletPayIdEnabled,
|
||||
isTopUpEnabled = model.isTopUpEnabled,
|
||||
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
|
||||
)
|
||||
defaultConfig = defaultConfig.copy(
|
||||
isWalletPayIdEnabled = model.isWalletPayIdEnabled,
|
||||
isTopUpEnabled = model.isTopUpEnabled,
|
||||
isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
|
||||
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupKey(name: String, value: String) {
|
||||
when (name) {
|
||||
coinMarketCapKey -> {
|
||||
config = config.copy(coinMarketCapKey = value)
|
||||
defaultConfig = defaultConfig.copy(coinMarketCapKey = value)
|
||||
}
|
||||
moonPayApiKey -> {
|
||||
config = config.copy(moonPayApiKey = value)
|
||||
defaultConfig = defaultConfig.copy(moonPayApiKey = value)
|
||||
}
|
||||
moonPayApiSecretKey -> {
|
||||
config = config.copy(moonPayApiSecretKey = value)
|
||||
defaultConfig = defaultConfig.copy(moonPayApiSecretKey = value)
|
||||
}
|
||||
}
|
||||
private fun setupKey(configValues: ConfigValueModel?) {
|
||||
val values = configValues ?: return
|
||||
config = config.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey
|
||||
)
|
||||
defaultConfig = defaultConfig.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
moonPayApiKey = values.moonPayApiKey,
|
||||
moonPayApiSecretKey = values.moonPayApiSecretKey
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -4,23 +4,21 @@ package com.tangem.tap.domain.config
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
interface BaseConfigModel<V> {
|
||||
val name: String
|
||||
val value: V?
|
||||
}
|
||||
|
||||
class FeatureModel(
|
||||
override val name: String,
|
||||
override val value: Boolean
|
||||
) : BaseConfigModel<Boolean>
|
||||
val isWalletPayIdEnabled: Boolean,
|
||||
val isTopUpEnabled: Boolean,
|
||||
val isSendingToPayIdEnabled: Boolean,
|
||||
val isCreatingTwinCardsAllowed: Boolean,
|
||||
)
|
||||
|
||||
class ConfigValueModel(
|
||||
override val name: String,
|
||||
override val value: String
|
||||
) : BaseConfigModel<String>
|
||||
val coinMarketCapKey: String,
|
||||
val moonPayApiKey: String,
|
||||
val moonPayApiSecretKey: String,
|
||||
)
|
||||
|
||||
class ConfigModel(val features: List<FeatureModel>?, val configValues: List<ConfigValueModel>?) {
|
||||
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
|
||||
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.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.common.redux.ToastNotificationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
|
|
@ -22,7 +23,7 @@ object ReleaseSendState : Action
|
|||
|
||||
data class PrepareSendScreen(
|
||||
val coinAmount: Amount?,
|
||||
val tokenAmount: Amount? = null
|
||||
val tokenAmount: Amount? = null,
|
||||
) : SendScreenAction
|
||||
|
||||
// Address or PayId
|
||||
|
|
@ -33,7 +34,27 @@ sealed class AddressPayIdActionUi : SendScreenActionUi {
|
|||
object CheckAddressPayId : AddressPayIdActionUi()
|
||||
data class SetTruncateHandler(val handler: (String) -> String) : 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 {
|
||||
|
|
@ -122,6 +143,7 @@ sealed class SendAction : SendScreenAction {
|
|||
val sendAllCallback: () -> Unit,
|
||||
val reduceAmount: BigDecimal,
|
||||
) : 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.SetPayIdWalletAddress
|
||||
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.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -82,22 +83,26 @@ internal class AddressPayIdMiddleware {
|
|||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val address = result.data.getAddress()
|
||||
if (address == null) {
|
||||
val addressDetails = result.data.getAddressDetails()
|
||||
if (addressDetails == null) {
|
||||
dispatch(SetPayIdError(Error.PAY_ID_NOT_REGISTERED))
|
||||
return@withContext
|
||||
}
|
||||
|
||||
val address = addressDetails.address
|
||||
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, address)
|
||||
if (failReason == null) {
|
||||
dispatch(SetPayIdWalletAddress(payId, address, isUserInput))
|
||||
dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, addressDetails.tag))
|
||||
dispatch(FeeAction.RequestFee)
|
||||
} else {
|
||||
dispatch(SetAddressError(failReason))
|
||||
dispatch(TransactionExtrasAction.Release)
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
dispatch(SetPayIdError(Error.PAY_ID_REQUEST_FAILED))
|
||||
dispatch(TransactionExtrasAction.Release)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -127,8 +132,10 @@ internal class AddressPayIdMiddleware {
|
|||
val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, supposedAddress)
|
||||
if (failReason == null) {
|
||||
dispatch(SetWalletAddress(supposedAddress, isUserInput))
|
||||
dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, null))
|
||||
} else {
|
||||
dispatch(SetAddressError(failReason))
|
||||
dispatch(TransactionExtrasAction.Release)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,15 +33,13 @@ class RequestFeeMiddleware {
|
|||
dispatch(SendAction.ChangeSendButtonState(sendState.getButtonState()))
|
||||
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
|
||||
scope.launch {
|
||||
val feeResult = txSender.getFee(recipientAmount, recipientAddress)
|
||||
val feeResult = txSender.getFee(destinationAmount, destinationAddress)
|
||||
withContext(Dispatchers.Main) {
|
||||
when (feeResult) {
|
||||
is Result.Success -> {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.features.send.redux.middlewares
|
||||
|
||||
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.extensions.Result
|
||||
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.FeeAction.RequestFee
|
||||
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.tangemSdk
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -52,7 +55,7 @@ private fun verifyAndSendTransaction(
|
|||
val sendState = appState?.sendState ?: return
|
||||
val walletManager = appState.globalState.scanNoteResponse?.walletManager ?: return
|
||||
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 feeAmount = sendState.feeState.currentFee ?: return
|
||||
|
||||
|
|
@ -67,14 +70,16 @@ private fun verifyAndSendTransaction(
|
|||
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
|
||||
dispatch(AmountActionUi.CheckAmountToSend)
|
||||
}, sendAllCallback = {
|
||||
sendTransaction(action, walletManager, amountToSend, feeAmount, recipientAddress, card, dispatch)
|
||||
sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
|
||||
sendState.transactionExtrasState, card, dispatch)
|
||||
}, reduceAmount))
|
||||
}
|
||||
transactionErrors.isNotEmpty() -> {
|
||||
dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager)))
|
||||
}
|
||||
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,
|
||||
amountToSend: Amount,
|
||||
feeAmount: Amount,
|
||||
recipientAddress: String,
|
||||
destinationAddress: String,
|
||||
transactionExtras: TransactionExtrasState,
|
||||
card: Card,
|
||||
dispatch: (Action) -> Unit
|
||||
) {
|
||||
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 {
|
||||
walletManager.update()
|
||||
val isLinkedTerminal = tangemSdk.config.linkedTerminal
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class AddressPayIdReducer : SendInternalReducer {
|
|||
viewFieldValue = InputViewValue(action.payId, action.isUserInput),
|
||||
normalFieldValue = action.payId,
|
||||
truncatedFieldValue = state.truncate(action.payId),
|
||||
recipientWalletAddress = action.payIdWalletAddress,
|
||||
destinationWalletAddress = action.payIdWalletAddress,
|
||||
error = null
|
||||
)
|
||||
}
|
||||
|
|
@ -51,13 +51,13 @@ class AddressPayIdReducer : SendInternalReducer {
|
|||
viewFieldValue = InputViewValue(action.address, action.isUserInput),
|
||||
normalFieldValue = action.address,
|
||||
truncatedFieldValue = state.truncate(action.address),
|
||||
recipientWalletAddress = action.address,
|
||||
destinationWalletAddress = action.address,
|
||||
error = null
|
||||
)
|
||||
}
|
||||
is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled)
|
||||
is AddressVerification.SetAddressError -> state.copy(error = action.error, recipientWalletAddress = null)
|
||||
is PayIdVerification.SetPayIdError -> 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, destinationWalletAddress = null)
|
||||
}
|
||||
return updateLastState(sendState.copy(addressPayIdState = result), result)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class SendScreenReducer {
|
|||
val reducer: SendInternalReducer = when (action) {
|
||||
is PrepareSendScreen -> PrepareSendScreenStatesReducer()
|
||||
is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer()
|
||||
is TransactionExtrasAction -> TransactionExtrasReducer()
|
||||
is AmountActionUi, is AmountAction -> AmountReducer()
|
||||
is FeeActionUi, is FeeAction -> FeeReducer()
|
||||
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
|
||||
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarMemo
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
|
||||
|
||||
data class AddressPayIdState(
|
||||
val viewFieldValue: InputViewValue = InputViewValue(""),
|
||||
val normalFieldValue: String? = null,
|
||||
val truncatedFieldValue: String? = null,
|
||||
val recipientWalletAddress: String? = null,
|
||||
val destinationWalletAddress: String? = null,
|
||||
val error: AddressPayIdVerifyAction.Error? = null,
|
||||
val truncateHandler: ((String) -> String)? = null,
|
||||
val sendingToPayIdEnabled: Boolean = false,
|
||||
|
|
@ -17,7 +18,37 @@ data class AddressPayIdState(
|
|||
|
||||
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 {
|
||||
SEND_SCREEN, ADDRESS_PAY_ID, AMOUNT, FEE, RECEIPT
|
||||
SEND_SCREEN, ADDRESS_PAY_ID, TRANSACTION_EXTRAS, AMOUNT, FEE, RECEIPT
|
||||
}
|
||||
|
||||
interface SendScreenState : StateType, IdStateHolder
|
||||
|
|
@ -33,6 +33,7 @@ data class SendState(
|
|||
val tokenConverter: CurrencyConverter? = null,
|
||||
val lastChangedStates: LinkedHashSet<StateId> = linkedSetOf(),
|
||||
val addressPayIdState: AddressPayIdState = AddressPayIdState(),
|
||||
val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(),
|
||||
val amountState: AmountState = AmountState(),
|
||||
val feeState: FeeState = FeeState(),
|
||||
val receiptState: ReceiptState = ReceiptState(),
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
|
||||
initSendButtonStates()
|
||||
setupAddressOrPayIdLayout()
|
||||
setupTransactionExtrasLayout()
|
||||
setupAmountLayout()
|
||||
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?) {
|
||||
if (requestCode != ScanQrCodeActivity.SCAN_QR_REQUEST_CODE) return
|
||||
|
||||
|
|
@ -191,7 +218,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
chipGroup.setOnCheckedChangeListener { group, checkedId ->
|
||||
if (checkedId == -1) return@setOnCheckedChangeListener
|
||||
|
||||
store.dispatch(ChangeSelectedFee(FeeUiHelper.idToFee(checkedId)))
|
||||
store.dispatch(ChangeSelectedFee(FeeUiHelper.toType(checkedId)))
|
||||
store.dispatch(CheckAmountToSend)
|
||||
}
|
||||
swIncludeFee.setOnCheckedChangeListener { btn, isChecked ->
|
||||
|
|
@ -238,7 +265,7 @@ fun EditText.inputtedTextAsFlow(): Flow<String> = callbackFlow {
|
|||
|
||||
class FeeUiHelper {
|
||||
companion object {
|
||||
fun feeToId(fee: FeeType): Int {
|
||||
fun toId(fee: FeeType): Int {
|
||||
return when (fee) {
|
||||
FeeType.SINGLE -> View.NO_ID
|
||||
FeeType.LOW -> R.id.chipLow
|
||||
|
|
@ -247,7 +274,7 @@ class FeeUiHelper {
|
|||
}
|
||||
}
|
||||
|
||||
fun idToFee(id: Int): FeeType {
|
||||
fun toType(id: Int): FeeType {
|
||||
return when (id) {
|
||||
R.id.chipLow -> FeeType.LOW
|
||||
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) {
|
||||
mainViewModifiers.clear()
|
||||
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.content.Context
|
||||
import android.text.InputType
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -43,10 +44,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
override fun updateWithNewState(fg: BaseStoreFragment, state: SendState) {
|
||||
val lastChangedStates = state.lastChangedStates.toList()
|
||||
state.lastChangedStates.clear()
|
||||
fg.main_send_container.beginDelayedTransition()
|
||||
lastChangedStates.forEach {
|
||||
when (it) {
|
||||
StateId.SEND_SCREEN -> handleSendScreen(fg, state)
|
||||
StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState)
|
||||
StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(fg, state.transactionExtrasState)
|
||||
StateId.AMOUNT -> handleAmountState(fg, state.amountState)
|
||||
StateId.FEE -> handleFeeState(fg, state.feeState)
|
||||
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) {
|
||||
val sendFragment = (fg as? SendFragment) ?: return
|
||||
|
||||
|
|
@ -106,14 +128,13 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
|
||||
val hintResId = if (state.sendingToPayIdEnabled) {
|
||||
R.string.send_destination_hint_address_payid
|
||||
}else {
|
||||
} else {
|
||||
R.string.send_destination_hint_address
|
||||
}
|
||||
til.hint = til.getString(hintResId)
|
||||
til.parent?.parent?.beginDelayedTransition()
|
||||
til.error = parsedError
|
||||
til.isErrorEnabled = parsedError != null
|
||||
til.helperText = state.recipientWalletAddress
|
||||
til.helperText = state.destinationWalletAddress
|
||||
til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
|
||||
|
||||
if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
|
||||
|
|
@ -130,10 +151,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
}
|
||||
else -> context.getString(state.error.localizedMessage)
|
||||
}
|
||||
fg.amountContainer.parent?.beginDelayedTransition()
|
||||
fg.tilAmountToSend.enableError(true, message)
|
||||
} else {
|
||||
if (fg.tilAmountToSend.isErrorEnabled) fg.amountContainer.parent?.beginDelayedTransition()
|
||||
fg.tilAmountToSend.enableError(false)
|
||||
}
|
||||
|
||||
|
|
@ -168,26 +187,12 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
|
|||
}
|
||||
|
||||
private fun handleFeeState(fg: BaseStoreFragment, state: FeeState) {
|
||||
var delayedTransitionScheduled = false
|
||||
fg.view?.findViewById<ViewGroup>(R.id.clNetworkFee)?.let {
|
||||
it.show(state.mainLayoutIsVisible) {
|
||||
(it.parent as? ViewGroup)?.beginDelayedTransition()
|
||||
delayedTransitionScheduled = true
|
||||
}
|
||||
}
|
||||
fg.chipGroup.fitChipsByGroupWidth()
|
||||
fg.view?.findViewById<ViewGroup>(R.id.clNetworkFee)?.show(state.mainLayoutIsVisible)
|
||||
|
||||
fg.imvExpandCollapse.rotation = if (state.controlsLayoutIsVisible) 0f else 180f
|
||||
fg.llFeeControlsContainer.show(state.controlsLayoutIsVisible) {
|
||||
if (!delayedTransitionScheduled) {
|
||||
fg.llFeeControlsContainer.parent?.parent?.beginDelayedTransition()
|
||||
}
|
||||
}
|
||||
|
||||
fg.chipGroup.show(state.feeChipGroupIsVisible) {
|
||||
if (!delayedTransitionScheduled) {
|
||||
fg.llFeeControlsContainer.parent?.parent?.beginDelayedTransition()
|
||||
}
|
||||
}
|
||||
fg.llFeeControlsContainer.show(state.controlsLayoutIsVisible)
|
||||
fg.chipGroup.show(state.feeChipGroupIsVisible)
|
||||
|
||||
fg.swIncludeFee.isEnabled = state.includeFeeSwitcherIsEnabled
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ data class WalletState(
|
|||
val showSegwitAddress: Boolean
|
||||
get() {
|
||||
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.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
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.paddingEnd, tv_address.paddingBottom)
|
||||
chip_group_segwit.show()
|
||||
chip_group_segwit.fitChipsByGroupWidth()
|
||||
|
||||
val checkedId = SegwitUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
|
||||
if (checkedId != View.NO_ID) chip_group_segwit.check(checkedId)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import retrofit2.Converter
|
|||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import java.math.BigDecimal
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
fun createRetrofitInstance(
|
||||
baseUrl: String,
|
||||
|
|
@ -19,6 +20,7 @@ fun createRetrofitInstance(
|
|||
): Retrofit {
|
||||
val okHttpBuilder = OkHttpClient.Builder()
|
||||
interceptors.forEach { okHttpBuilder.addInterceptor(it) }
|
||||
addTimeOuts(okHttpBuilder)
|
||||
|
||||
if (BuildConfig.DEBUG) okHttpBuilder.addInterceptor(createHttpLoggingInterceptor())
|
||||
return Retrofit.Builder()
|
||||
|
|
@ -28,6 +30,13 @@ fun createRetrofitInstance(
|
|||
.build()
|
||||
}
|
||||
|
||||
private fun addTimeOuts(okHttpBuilder: OkHttpClient.Builder) {
|
||||
okHttpBuilder.callTimeout(1, TimeUnit.SECONDS)
|
||||
okHttpBuilder.connectTimeout(20, TimeUnit.SECONDS)
|
||||
okHttpBuilder.readTimeout(20, TimeUnit.SECONDS)
|
||||
okHttpBuilder.writeTimeout(20, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
fun createMoshiConverterFactory(): Converter.Factory = MoshiConverterFactory.create(createMoshi())
|
||||
|
||||
fun createMoshi(): Moshi = Moshi.Builder()
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ data class VerifyPayIdResponse(
|
|||
val addresses: List<PayIdAddress> = mutableListOf(),
|
||||
val payId: String? = null,
|
||||
) {
|
||||
fun getAddress(): String? {
|
||||
fun getAddressDetails(): PayIdAddressDetails? {
|
||||
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">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/main_send_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
|
|
|||
|
|
@ -27,10 +27,9 @@
|
|||
android:id="@+id/chip_group_segwit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:visibility="visible"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
|
|||
|
|
@ -1,76 +1,197 @@
|
|||
<?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:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/csRecipientAddress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilAddressOrPayId"
|
||||
android:layout_width="0dp"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/addressContainer"
|
||||
android:layout_width="match_parent"
|
||||
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"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
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_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" />
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
</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
|
||||
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">
|
||||
<!-- <com.google.android.material.chip.Chip-->
|
||||
<!-- android:id="@+id/chipMemoText"-->
|
||||
<!-- style="@style/TapChip"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="Text" />-->
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imvPaste"
|
||||
android:layout_width="wrap_content"
|
||||
<!-- <com.google.android.material.chip.Chip-->
|
||||
<!-- android:id="@+id/chipMemoId"-->
|
||||
<!-- 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_gravity="center"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="6dp"
|
||||
app:srcCompat="@drawable/paste_selector" />
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
</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
|
||||
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">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etDestinationTag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:inputType="number"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:singleLine="true"
|
||||
android:textSize="16sp"
|
||||
tools:text="Some destination tag" />
|
||||
|
||||
<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" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</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_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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue