mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 18:25:50 +00:00
Merge branch 'feature/btc-native-send' into fix/multisig-first-signer-deep-link
# Conflicts: # feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeFragment.kt # feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeViewModel.kt # feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/di/BridgeModule.kt
This commit is contained in:
+16
@@ -39,12 +39,24 @@ class BalancesUpdateSystem(
|
||||
override fun start(): Flow<Updater.SideEffect> {
|
||||
return accountUpdateScope.invalidationFlow().flatMapLatest { metaAccount ->
|
||||
chainRegistry.currentChains.transformLatestDiffed { chain ->
|
||||
if (chain.isTronBased) {
|
||||
Log.d(LOG_TAG, "TronDebug: currentChains delivered chain=${chain.id} name=${chain.name}")
|
||||
}
|
||||
emitAll(balancesSync(chain, metaAccount))
|
||||
}
|
||||
}.flowOn(Dispatchers.Default)
|
||||
}
|
||||
|
||||
private suspend fun balancesSync(chain: Chain, metaAccount: MetaAccount): Flow<Updater.SideEffect> {
|
||||
if (chain.isTronBased) {
|
||||
Log.d(
|
||||
LOG_TAG,
|
||||
"TronDebug: gate check chain=${chain.id} name=${chain.name} hasAccountIn=${metaAccount.hasAccountIn(chain)} " +
|
||||
"connectionState=${chain.connectionState} isDisabled=${chain.connectionState.isDisabled} " +
|
||||
"hasSubstrateRuntime=${chain.hasSubstrateRuntime} canPerformFullSync=${chain.canPerformFullSync()}"
|
||||
)
|
||||
}
|
||||
|
||||
return when {
|
||||
!metaAccount.hasAccountIn(chain) -> emptyFlow()
|
||||
chain.connectionState.isDisabled -> emptyFlow()
|
||||
@@ -89,6 +101,10 @@ class BalancesUpdateSystem(
|
||||
try {
|
||||
updater.listenForUpdates(subscriptionBuilder, metaAccount).catch { logError(chain, it) }
|
||||
} catch (e: Exception) {
|
||||
// Was silently swallowed here with zero logging - listenForUpdates() itself is a suspend
|
||||
// call that can throw synchronously (e.g. FullSyncPaymentUpdater.listenForUpdates() calling
|
||||
// requireAccountIdIn(chain)), before ever returning a flow for the .catch{} above to guard.
|
||||
Log.e(LOG_TAG, "listenForUpdates() threw synchronously for ${updater.javaClass.simpleName} in ${chain.name}", e)
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -6,7 +6,8 @@ import io.novafoundation.nova.common.presentation.AssetIconProvider
|
||||
import io.novafoundation.nova.common.presentation.getAssetIconOrFallback
|
||||
import io.novafoundation.nova.common.utils.formatting.formatAsChange
|
||||
import io.novafoundation.nova.common.utils.orZero
|
||||
import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.ChainUi
|
||||
import io.novafoundation.nova.runtime.ext.displayNameWithAssetStandard
|
||||
import io.novafoundation.nova.feature_assets.R
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.getAssetIconOrFallback
|
||||
import io.novafoundation.nova.feature_assets.domain.common.AssetWithNetwork
|
||||
@@ -90,7 +91,14 @@ class TokenAssetFormatter(
|
||||
group.getId(),
|
||||
mapAssetToAssetModel(it.asset, balance(it.balanceWithOffChain)),
|
||||
assetIconProvider.getAssetIconOrFallback(it.asset.token.configuration),
|
||||
mapChainToUi(it.chain)
|
||||
// Not mapChainToUi() here - this row's subtitle needs to disambiguate which issuance of the
|
||||
// token this is (e.g. "Ethereum (ERC-20)" vs "Tron (TRC-20)"), which a bare chain name alone
|
||||
// doesn't when multiple ecosystems share the same symbol (USDT, USDC, etc.).
|
||||
ChainUi(
|
||||
id = it.chain.id,
|
||||
name = it.chain.displayNameWithAssetStandard(),
|
||||
icon = it.chain.icon
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+43
-83
@@ -5,11 +5,14 @@ import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.utils.setVisible
|
||||
import io.novafoundation.nova.common.view.AlertView
|
||||
import io.novafoundation.nova.common.view.setState
|
||||
import io.novafoundation.nova.feature_assets.R
|
||||
import io.novafoundation.nova.feature_assets.databinding.FragmentBridgeBinding
|
||||
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
|
||||
import io.novafoundation.nova.feature_assets.di.AssetsFeatureComponent
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.MaxActionAvailability
|
||||
|
||||
class BridgeFragment : BaseFragment<BridgeViewModel, FragmentBridgeBinding>() {
|
||||
|
||||
@@ -18,26 +21,29 @@ class BridgeFragment : BaseFragment<BridgeViewModel, FragmentBridgeBinding>() {
|
||||
override fun initViews() {
|
||||
binder.bridgeToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
|
||||
// Pair selector
|
||||
binder.bridgePairDotHez.setOnClickListener {
|
||||
viewModel.setPair(BridgePair.DOT_HEZ)
|
||||
binder.bridgeToCard.setEditable(false)
|
||||
|
||||
// Tapping the "from" card opens the pair picker - replaces the old segmented pair buttons
|
||||
binder.bridgeFromCard.setCardClickListener {
|
||||
val options = viewModel.pairOptions.value.orEmpty()
|
||||
if (options.isNotEmpty()) {
|
||||
BridgePairListBottomSheet(requireContext(), options) { selected ->
|
||||
viewModel.setPair(selected.pair)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
binder.bridgePairUsdt.setOnClickListener {
|
||||
viewModel.setPair(BridgePair.USDT)
|
||||
}
|
||||
|
||||
// Direction toggle
|
||||
binder.bridgeDirectionLeft.setOnClickListener {
|
||||
viewModel.setDirectionLeft()
|
||||
}
|
||||
|
||||
binder.bridgeDirectionRight.setOnClickListener {
|
||||
viewModel.setDirectionRight()
|
||||
// One-tap direction flip - replaces the old segmented direction buttons
|
||||
binder.bridgeFlipButton.setOnClickListener {
|
||||
when (viewModel.direction.value) {
|
||||
BridgeDirection.DOT_TO_HEZ, BridgeDirection.USDT_TO_WUSDT -> viewModel.setDirectionRight()
|
||||
BridgeDirection.HEZ_TO_DOT, BridgeDirection.WUSDT_TO_USDT -> viewModel.setDirectionLeft()
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
// Amount input
|
||||
binder.bridgeFromAmount.addTextChangedListener(object : TextWatcher {
|
||||
binder.bridgeFromCard.amountInput.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
@@ -46,6 +52,10 @@ class BridgeFragment : BaseFragment<BridgeViewModel, FragmentBridgeBinding>() {
|
||||
}
|
||||
})
|
||||
|
||||
binder.bridgeFromMaxAmount.setMaxActionAvailability(
|
||||
MaxActionAvailability.Available { viewModel.maxClicked() }
|
||||
)
|
||||
|
||||
// Swap button
|
||||
binder.bridgeSwapButton.setOnClickListener {
|
||||
viewModel.swapClicked()
|
||||
@@ -74,16 +84,16 @@ class BridgeFragment : BaseFragment<BridgeViewModel, FragmentBridgeBinding>() {
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: BridgeViewModel) {
|
||||
viewModel.pair.observe { pair ->
|
||||
updatePairUI(pair)
|
||||
viewModel.fromCard.observe { model ->
|
||||
binder.bridgeFromCard.setModel(model)
|
||||
}
|
||||
|
||||
viewModel.direction.observe { direction ->
|
||||
updateDirectionUI(direction)
|
||||
viewModel.toCard.observe { model ->
|
||||
binder.bridgeToCard.setModel(model)
|
||||
}
|
||||
|
||||
viewModel.outputAmount.observe { output ->
|
||||
binder.bridgeToAmount.text = output
|
||||
binder.bridgeToCard.setAmountText(output)
|
||||
}
|
||||
|
||||
viewModel.exchangeRateText.observe { rate ->
|
||||
@@ -99,20 +109,18 @@ class BridgeFragment : BaseFragment<BridgeViewModel, FragmentBridgeBinding>() {
|
||||
}
|
||||
|
||||
viewModel.showWarning.observe { show ->
|
||||
binder.bridgeHezToDotWarning.visibility = if (show) View.VISIBLE else View.GONE
|
||||
binder.bridgeWarningAlert.setVisible(show)
|
||||
}
|
||||
|
||||
viewModel.warningBlocked.observe { blocked ->
|
||||
if (blocked) {
|
||||
binder.bridgeHezToDotWarning.setBackgroundColor(resources.getColor(R.color.error_block_background, null))
|
||||
} else {
|
||||
binder.bridgeHezToDotWarning.setBackgroundColor(resources.getColor(R.color.warning_block_background, null))
|
||||
}
|
||||
binder.bridgeWarningAlert.setStylePreset(
|
||||
if (blocked) AlertView.StylePreset.ERROR else AlertView.StylePreset.WARNING
|
||||
)
|
||||
}
|
||||
|
||||
viewModel.warningText.observe { text ->
|
||||
if (text.isNotEmpty()) {
|
||||
binder.bridgeHezToDotWarning.text = text
|
||||
binder.bridgeWarningAlert.setMessage(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,65 +144,17 @@ class BridgeFragment : BaseFragment<BridgeViewModel, FragmentBridgeBinding>() {
|
||||
viewModel.signButtonLabel.observe { label ->
|
||||
binder.bridgeSignButton.text = label
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePairUI(pair: BridgePair) {
|
||||
when (pair) {
|
||||
BridgePair.DOT_HEZ -> {
|
||||
binder.bridgePairDotHez.setBackgroundResource(R.drawable.bg_button_primary)
|
||||
binder.bridgePairDotHez.setTextColor(resources.getColor(R.color.text_primary, null))
|
||||
binder.bridgePairUsdt.background = null
|
||||
binder.bridgePairUsdt.setTextColor(resources.getColor(R.color.text_secondary, null))
|
||||
}
|
||||
BridgePair.USDT -> {
|
||||
binder.bridgePairUsdt.setBackgroundResource(R.drawable.bg_button_primary)
|
||||
binder.bridgePairUsdt.setTextColor(resources.getColor(R.color.text_primary, null))
|
||||
binder.bridgePairDotHez.background = null
|
||||
binder.bridgePairDotHez.setTextColor(resources.getColor(R.color.text_secondary, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDirectionUI(direction: BridgeDirection) {
|
||||
val isLeft = direction == BridgeDirection.DOT_TO_HEZ || direction == BridgeDirection.USDT_TO_WUSDT
|
||||
|
||||
if (isLeft) {
|
||||
binder.bridgeDirectionLeft.setBackgroundResource(R.drawable.bg_button_primary)
|
||||
binder.bridgeDirectionLeft.setTextColor(resources.getColor(R.color.text_primary, null))
|
||||
binder.bridgeDirectionRight.background = null
|
||||
binder.bridgeDirectionRight.setTextColor(resources.getColor(R.color.text_secondary, null))
|
||||
} else {
|
||||
binder.bridgeDirectionRight.setBackgroundResource(R.drawable.bg_button_primary)
|
||||
binder.bridgeDirectionRight.setTextColor(resources.getColor(R.color.text_primary, null))
|
||||
binder.bridgeDirectionLeft.background = null
|
||||
binder.bridgeDirectionLeft.setTextColor(resources.getColor(R.color.text_secondary, null))
|
||||
viewModel.maxAmountDisplay.observe { display ->
|
||||
binder.bridgeFromMaxAmount.setMaxAmountDisplay(display)
|
||||
}
|
||||
|
||||
when (direction) {
|
||||
BridgeDirection.DOT_TO_HEZ -> {
|
||||
binder.bridgeDirectionLeft.text = "DOT → HEZ"
|
||||
binder.bridgeDirectionRight.text = "HEZ → DOT"
|
||||
binder.bridgeFromToken.text = "DOT"
|
||||
binder.bridgeToToken.text = "HEZ"
|
||||
}
|
||||
BridgeDirection.HEZ_TO_DOT -> {
|
||||
binder.bridgeDirectionLeft.text = "DOT → HEZ"
|
||||
binder.bridgeDirectionRight.text = "HEZ → DOT"
|
||||
binder.bridgeFromToken.text = "HEZ"
|
||||
binder.bridgeToToken.text = "DOT"
|
||||
}
|
||||
BridgeDirection.USDT_TO_WUSDT -> {
|
||||
binder.bridgeDirectionLeft.text = "USDT(Pol) → USDT(Pez)"
|
||||
binder.bridgeDirectionRight.text = "USDT(Pez) → USDT(Pol)"
|
||||
binder.bridgeFromToken.text = "USDT"
|
||||
binder.bridgeToToken.text = "USDT"
|
||||
}
|
||||
BridgeDirection.WUSDT_TO_USDT -> {
|
||||
binder.bridgeDirectionLeft.text = "USDT(Pol) → USDT(Pez)"
|
||||
binder.bridgeDirectionRight.text = "USDT(Pez) → USDT(Pol)"
|
||||
binder.bridgeFromToken.text = "USDT"
|
||||
binder.bridgeToToken.text = "USDT"
|
||||
}
|
||||
viewModel.insufficientBalanceError.observe { error ->
|
||||
binder.bridgeFromCard.setError(error)
|
||||
}
|
||||
|
||||
viewModel.fillAmountEvent.observeEvent { amount ->
|
||||
binder.bridgeFromCard.amountInput.setText(amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package io.novafoundation.nova.feature_assets.presentation.bridge
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.utils.images.Icon
|
||||
import io.novafoundation.nova.common.utils.images.setIconOrMakeGone
|
||||
import io.novafoundation.nova.common.utils.inflater
|
||||
import io.novafoundation.nova.common.view.bottomSheet.list.dynamic.DynamicListBottomSheet
|
||||
import io.novafoundation.nova.common.view.bottomSheet.list.dynamic.DynamicListSheetAdapter
|
||||
import io.novafoundation.nova.common.view.bottomSheet.list.dynamic.HolderCreator
|
||||
import io.novafoundation.nova.feature_assets.R
|
||||
import io.novafoundation.nova.feature_assets.databinding.ItemBridgePairListBinding
|
||||
|
||||
data class BridgePairUi(
|
||||
val pair: BridgePair,
|
||||
val icon: Icon,
|
||||
val title: String
|
||||
)
|
||||
|
||||
class BridgePairListBottomSheet(
|
||||
context: Context,
|
||||
data: List<BridgePairUi>,
|
||||
onClicked: (BridgePairUi) -> Unit
|
||||
) : DynamicListBottomSheet<BridgePairUi>(
|
||||
context,
|
||||
Payload(data),
|
||||
BridgePairDiffCallback,
|
||||
onClicked = { _, item -> onClicked(item) }
|
||||
) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setTitle(R.string.bridge_title)
|
||||
}
|
||||
|
||||
override fun holderCreator(): HolderCreator<BridgePairUi> = {
|
||||
BridgePairListHolder(ItemBridgePairListBinding.inflate(it.inflater(), it, false))
|
||||
}
|
||||
}
|
||||
|
||||
class BridgePairListHolder(
|
||||
private val binder: ItemBridgePairListBinding
|
||||
) : DynamicListSheetAdapter.Holder<BridgePairUi>(binder.root) {
|
||||
|
||||
private val imageLoader: ImageLoader by lazy(LazyThreadSafetyMode.NONE) {
|
||||
FeatureUtils.getCommonApi(binder.root.context).imageLoader()
|
||||
}
|
||||
|
||||
override fun bind(item: BridgePairUi, isSelected: Boolean, handler: DynamicListSheetAdapter.Handler<BridgePairUi>) {
|
||||
binder.itemBridgePairIcon.setIconOrMakeGone(item.icon, imageLoader)
|
||||
binder.itemBridgePairTitle.text = item.title
|
||||
binder.root.setOnClickListener { handler.itemClicked(item) }
|
||||
}
|
||||
}
|
||||
|
||||
private object BridgePairDiffCallback : DiffUtil.ItemCallback<BridgePairUi>() {
|
||||
override fun areItemsTheSame(oldItem: BridgePairUi, newItem: BridgePairUi): Boolean {
|
||||
return oldItem.pair == newItem.pair
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: BridgePairUi, newItem: BridgePairUi): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
+125
@@ -3,9 +3,14 @@ package io.novafoundation.nova.feature_assets.presentation.bridge
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.presentation.AssetIconProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.images.Icon
|
||||
import io.novafoundation.nova.common.view.ButtonState
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.getAssetIconOrFallback
|
||||
import io.novafoundation.nova.feature_assets.R
|
||||
import io.novafoundation.nova.feature_assets.domain.WalletInteractor
|
||||
import io.novafoundation.nova.feature_assets.domain.bridge.multisig.BridgeMultisigInteractor
|
||||
import io.novafoundation.nova.feature_assets.domain.bridge.multisig.BridgeSignerState
|
||||
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
|
||||
@@ -13,9 +18,11 @@ import io.novafoundation.nova.feature_assets.presentation.send.amount.SendPayloa
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload
|
||||
import io.novafoundation.nova.runtime.ext.ChainGeneses
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.ext.displayNameWithAssetStandard
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
@@ -27,6 +34,8 @@ class BridgeViewModel(
|
||||
private val router: AssetsRouter,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val assetIconProvider: AssetIconProvider,
|
||||
private val walletInteractor: WalletInteractor,
|
||||
private val bridgeMultisigInteractor: BridgeMultisigInteractor
|
||||
) : BaseViewModel() {
|
||||
|
||||
@@ -91,15 +100,37 @@ class BridgeViewModel(
|
||||
private val _signButtonLabel = MutableLiveData("")
|
||||
val signButtonLabel: LiveData<String> = _signButtonLabel
|
||||
|
||||
private val _fromCard = MutableLiveData<BridgeAssetCardUi>()
|
||||
val fromCard: LiveData<BridgeAssetCardUi> = _fromCard
|
||||
|
||||
private val _toCard = MutableLiveData<BridgeAssetCardUi>()
|
||||
val toCard: LiveData<BridgeAssetCardUi> = _toCard
|
||||
|
||||
private val _pairOptions = MutableLiveData<List<BridgePairUi>>(emptyList())
|
||||
val pairOptions: LiveData<List<BridgePairUi>> = _pairOptions
|
||||
|
||||
private val _maxAmountDisplay = MutableLiveData<String?>(null)
|
||||
val maxAmountDisplay: LiveData<String?> = _maxAmountDisplay
|
||||
|
||||
private val _insufficientBalanceError = MutableLiveData<String?>(null)
|
||||
val insufficientBalanceError: LiveData<String?> = _insufficientBalanceError
|
||||
|
||||
private val _fillAmountEvent = MutableLiveData<Event<String>>()
|
||||
val fillAmountEvent: LiveData<Event<String>> = _fillAmountEvent
|
||||
|
||||
private var currentAmount: Double = 0.0
|
||||
private var dotToHezRate: Double = FALLBACK_RATE
|
||||
private var isHezToDotActive: Boolean = false
|
||||
private var isWusdtToUsdtActive: Boolean = false
|
||||
private var availableBalance: BigDecimal = BigDecimal.ZERO
|
||||
private var balanceJob: Job? = null
|
||||
|
||||
init {
|
||||
fetchExchangeRate()
|
||||
fetchBridgeStatus()
|
||||
refreshSignerState()
|
||||
updateCards()
|
||||
loadPairOptions()
|
||||
}
|
||||
|
||||
fun setPair(newPair: BridgePair) {
|
||||
@@ -113,6 +144,7 @@ class BridgeViewModel(
|
||||
updateUI()
|
||||
calculateOutput()
|
||||
updateWarningState()
|
||||
updateCards()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +159,7 @@ class BridgeViewModel(
|
||||
updateUI()
|
||||
calculateOutput()
|
||||
updateWarningState()
|
||||
updateCards()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,15 +174,21 @@ class BridgeViewModel(
|
||||
updateUI()
|
||||
calculateOutput()
|
||||
updateWarningState()
|
||||
updateCards()
|
||||
}
|
||||
}
|
||||
|
||||
fun setAmount(amount: Double) {
|
||||
currentAmount = amount
|
||||
calculateOutput()
|
||||
updateInsufficientBalanceState()
|
||||
updateButtonState()
|
||||
}
|
||||
|
||||
fun maxClicked() {
|
||||
_fillAmountEvent.value = Event(availableBalance.stripTrailingZeros().toPlainString())
|
||||
}
|
||||
|
||||
fun swapClicked() {
|
||||
val dir = _direction.value ?: return
|
||||
if (currentAmount <= 0) return
|
||||
@@ -329,12 +368,21 @@ class BridgeViewModel(
|
||||
_buttonState.value = when {
|
||||
currentAmount <= 0 -> ButtonState.DISABLED
|
||||
currentAmount < minimum -> ButtonState.DISABLED
|
||||
BigDecimal.valueOf(currentAmount) > availableBalance -> ButtonState.DISABLED
|
||||
dir == BridgeDirection.HEZ_TO_DOT && !isHezToDotActive -> ButtonState.DISABLED
|
||||
dir == BridgeDirection.WUSDT_TO_USDT && !isWusdtToUsdtActive -> ButtonState.DISABLED
|
||||
else -> ButtonState.NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateInsufficientBalanceState() {
|
||||
_insufficientBalanceError.value = if (currentAmount > 0 && BigDecimal.valueOf(currentAmount) > availableBalance) {
|
||||
resourceManager.getString(R.string.bridge_insufficient_balance)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshBridgeStatus() {
|
||||
fetchBridgeStatus()
|
||||
refreshSignerState()
|
||||
@@ -388,4 +436,81 @@ class BridgeViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCards() {
|
||||
val dir = _direction.value ?: return
|
||||
|
||||
// Same chainId/assetId mapping already used by swapClicked() to resolve the origin side -
|
||||
// mirrored here (plus its destination counterpart) purely to display logos/names, no new business rule.
|
||||
val originChainId = when (dir) {
|
||||
BridgeDirection.DOT_TO_HEZ, BridgeDirection.USDT_TO_WUSDT -> POLKADOT_ASSET_HUB_ID
|
||||
BridgeDirection.HEZ_TO_DOT, BridgeDirection.WUSDT_TO_USDT -> PEZKUWI_ASSET_HUB_ID
|
||||
}
|
||||
val destChainId = when (dir) {
|
||||
BridgeDirection.DOT_TO_HEZ, BridgeDirection.USDT_TO_WUSDT -> PEZKUWI_ASSET_HUB_ID
|
||||
BridgeDirection.HEZ_TO_DOT, BridgeDirection.WUSDT_TO_USDT -> POLKADOT_ASSET_HUB_ID
|
||||
}
|
||||
val originAssetId = when (dir) {
|
||||
BridgeDirection.DOT_TO_HEZ, BridgeDirection.HEZ_TO_DOT -> UTILITY_ASSET_ID
|
||||
BridgeDirection.USDT_TO_WUSDT -> POLKADOT_USDT_ASSET_ID
|
||||
BridgeDirection.WUSDT_TO_USDT -> PEZKUWI_USDT_ASSET_ID
|
||||
}
|
||||
val destAssetId = when (dir) {
|
||||
BridgeDirection.DOT_TO_HEZ, BridgeDirection.HEZ_TO_DOT -> UTILITY_ASSET_ID
|
||||
BridgeDirection.USDT_TO_WUSDT -> PEZKUWI_USDT_ASSET_ID
|
||||
BridgeDirection.WUSDT_TO_USDT -> POLKADOT_USDT_ASSET_ID
|
||||
}
|
||||
|
||||
launch {
|
||||
_fromCard.value = cardUiFor(originChainId, originAssetId)
|
||||
_toCard.value = cardUiFor(destChainId, destAssetId)
|
||||
}
|
||||
|
||||
observeOriginBalance(originChainId, originAssetId)
|
||||
}
|
||||
|
||||
private fun observeOriginBalance(chainId: String, assetId: Int) {
|
||||
balanceJob?.cancel()
|
||||
balanceJob = launch {
|
||||
walletInteractor.assetFlow(chainId, assetId).collect { asset ->
|
||||
availableBalance = asset.transferable
|
||||
_maxAmountDisplay.postValue(
|
||||
"${availableBalance.setScale(6, RoundingMode.DOWN).stripTrailingZeros().toPlainString()} ${asset.token.configuration.symbol.value}"
|
||||
)
|
||||
updateInsufficientBalanceState()
|
||||
updateButtonState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPairOptions() {
|
||||
launch {
|
||||
val dotHezIcon = cardUiFor(POLKADOT_ASSET_HUB_ID, UTILITY_ASSET_ID).assetIcon
|
||||
val usdtIcon = cardUiFor(POLKADOT_ASSET_HUB_ID, POLKADOT_USDT_ASSET_ID).assetIcon
|
||||
|
||||
_pairOptions.value = listOf(
|
||||
BridgePairUi(BridgePair.DOT_HEZ, dotHezIcon, resourceManager.getString(R.string.bridge_pair_dot_hez)),
|
||||
BridgePairUi(BridgePair.USDT, usdtIcon, resourceManager.getString(R.string.bridge_pair_usdt))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun cardUiFor(chainId: String, assetId: Int): BridgeAssetCardUi {
|
||||
val chain = chainRegistry.getChain(chainId)
|
||||
val asset = chain.assetsById.getValue(assetId)
|
||||
|
||||
return BridgeAssetCardUi(
|
||||
assetIcon = assetIconProvider.getAssetIconOrFallback(asset),
|
||||
chainIconUrl = chain.icon,
|
||||
symbol = asset.symbol.value,
|
||||
chainName = chain.displayNameWithAssetStandard()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class BridgeAssetCardUi(
|
||||
val assetIcon: Icon,
|
||||
val chainIconUrl: String?,
|
||||
val symbol: String,
|
||||
val chainName: String
|
||||
)
|
||||
|
||||
+5
-1
@@ -8,9 +8,11 @@ import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.presentation.AssetIconProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||
import io.novafoundation.nova.feature_assets.domain.WalletInteractor
|
||||
import io.novafoundation.nova.feature_assets.domain.bridge.multisig.BridgeMultisigInteractor
|
||||
import io.novafoundation.nova.feature_assets.domain.bridge.multisig.RealBridgeMultisigInteractor
|
||||
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
|
||||
@@ -40,9 +42,11 @@ class BridgeModule {
|
||||
router: AssetsRouter,
|
||||
resourceManager: ResourceManager,
|
||||
chainRegistry: ChainRegistry,
|
||||
assetIconProvider: AssetIconProvider,
|
||||
walletInteractor: WalletInteractor,
|
||||
bridgeMultisigInteractor: BridgeMultisigInteractor
|
||||
): ViewModel {
|
||||
return BridgeViewModel(router, resourceManager, chainRegistry, bridgeMultisigInteractor)
|
||||
return BridgeViewModel(router, resourceManager, chainRegistry, assetIconProvider, walletInteractor, bridgeMultisigInteractor)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package io.novafoundation.nova.feature_assets.presentation.bridge.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.EditText
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.utils.WithContextExtensions
|
||||
import io.novafoundation.nova.common.utils.images.asUrlIcon
|
||||
import io.novafoundation.nova.common.utils.images.setIconOrMakeGone
|
||||
import io.novafoundation.nova.common.utils.inflater
|
||||
import io.novafoundation.nova.common.utils.setVisible
|
||||
import io.novafoundation.nova.common.view.shape.getInputBackground
|
||||
import io.novafoundation.nova.common.view.shape.getInputBackgroundError
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.setTokenIcon
|
||||
import io.novafoundation.nova.feature_assets.databinding.ViewBridgeAssetInputBinding
|
||||
import io.novafoundation.nova.feature_assets.presentation.bridge.BridgeAssetCardUi
|
||||
|
||||
class BridgeAssetInputView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr),
|
||||
WithContextExtensions by WithContextExtensions(context) {
|
||||
|
||||
private val binder = ViewBridgeAssetInputBinding.inflate(inflater(), this)
|
||||
|
||||
val amountInput: EditText
|
||||
get() = binder.bridgeAssetInputField
|
||||
|
||||
private val imageLoader: ImageLoader by lazy(LazyThreadSafetyMode.NONE) {
|
||||
FeatureUtils.getCommonApi(context).imageLoader()
|
||||
}
|
||||
|
||||
init {
|
||||
binder.bridgeAssetInputContainer.background = context.getInputBackground()
|
||||
}
|
||||
|
||||
fun setCardClickListener(listener: OnClickListener) {
|
||||
binder.bridgeAssetInputContainer.setOnClickListener(listener)
|
||||
binder.bridgeAssetInputChevron.isVisible = true
|
||||
}
|
||||
|
||||
fun setEditable(editable: Boolean) {
|
||||
amountInput.isFocusable = editable
|
||||
amountInput.isFocusableInTouchMode = editable
|
||||
amountInput.isClickable = editable
|
||||
amountInput.isCursorVisible = editable
|
||||
}
|
||||
|
||||
fun setAmountText(text: CharSequence) {
|
||||
if (amountInput.text?.toString() != text.toString()) {
|
||||
amountInput.setText(text)
|
||||
}
|
||||
}
|
||||
|
||||
fun setModel(model: BridgeAssetCardUi) {
|
||||
binder.bridgeAssetInputImage.setTokenIcon(model.assetIcon, imageLoader)
|
||||
binder.bridgeAssetInputToken.text = model.symbol
|
||||
binder.bridgeAssetInputSubtitle.text = model.chainName
|
||||
binder.bridgeAssetInputSubtitleImage.setIconOrMakeGone(model.chainIconUrl?.asUrlIcon(), imageLoader)
|
||||
}
|
||||
|
||||
fun setError(message: String?) {
|
||||
binder.bridgeAssetInputContainer.background = if (message != null) {
|
||||
context.getInputBackgroundError()
|
||||
} else {
|
||||
context.getInputBackground()
|
||||
}
|
||||
|
||||
binder.bridgeAssetInputError.text = message
|
||||
binder.bridgeAssetInputError.setVisible(message != null)
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -16,6 +16,7 @@ import io.novafoundation.nova.feature_assets.presentation.flow.network.model.Net
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.formatAmountToAmountModel
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.model.AmountConfig
|
||||
import io.novafoundation.nova.runtime.ext.displayNameWithAssetStandard
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.asset
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -73,7 +74,7 @@ abstract class NetworkFlowViewModel(
|
||||
NetworkFlowRvItem(
|
||||
it.chain.id,
|
||||
it.asset.token.configuration.id,
|
||||
it.chain.name,
|
||||
it.chain.displayNameWithAssetStandard(),
|
||||
it.chain.icon,
|
||||
amountFormatter.formatAmountToAmountModel(
|
||||
amount = getAssetBalance(it).amount,
|
||||
|
||||
@@ -25,212 +25,65 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Pair Selector -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/bg_primary_list_item"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp">
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgePairDotHez"
|
||||
android:id="@+id/bridgeFromLabel"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_button_primary"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:text="DOT / HEZ"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
android:text="@string/bridge_you_send"
|
||||
android:textColor="@color/text_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgePairUsdt"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:text="USDT"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="13sp" />
|
||||
<io.novafoundation.nova.feature_wallet_api.presentation.view.amount.MaxAmountView
|
||||
android:id="@+id/bridgeFromMaxAmount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Direction Toggle -->
|
||||
<LinearLayout
|
||||
<io.novafoundation.nova.feature_assets.presentation.bridge.view.BridgeAssetInputView
|
||||
android:id="@+id/bridgeFromCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/bg_primary_list_item"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp">
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeDirectionLeft"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_button_primary"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="DOT → HEZ"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeDirectionRight"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="HEZ → DOT"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- From Section -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:cardBackgroundColor="@color/block_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeFromLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/bridge_you_send"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/bridgeFromAmount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@null"
|
||||
android:hint="0.0"
|
||||
android:inputType="numberDecimal"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textColorHint="@color/text_secondary"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeFromToken"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="DOT"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeFromBalance"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp"
|
||||
tools:text="Balance: 10.5 DOT" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Swap Icon -->
|
||||
<ImageView
|
||||
android:id="@+id/bridgeFlipButton"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginVertical="4dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_bridge"
|
||||
app:tint="@color/icon_secondary" />
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_flip_swap" />
|
||||
|
||||
<!-- To Section -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
<TextView
|
||||
android:id="@+id/bridgeToLabel"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/bridge_you_receive"
|
||||
android:textColor="@color/text_secondary" />
|
||||
|
||||
<io.novafoundation.nova.feature_assets.presentation.bridge.view.BridgeAssetInputView
|
||||
android:id="@+id/bridgeToCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/block_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeToLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/bridge_you_receive"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeToAmount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="0.0"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeToToken"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="HEZ"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<!-- Rate Info -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardBackgroundColor="@color/block_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="0dp">
|
||||
@@ -317,18 +170,14 @@
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Warning for HEZ to DOT -->
|
||||
<TextView
|
||||
android:id="@+id/bridgeHezToDotWarning"
|
||||
<!-- Warning for HEZ to DOT / USDT liquidity -->
|
||||
<io.novafoundation.nova.common.view.AlertView
|
||||
android:id="@+id/bridgeWarningAlert"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bg_warning"
|
||||
android:padding="12dp"
|
||||
android:text="@string/bridge_hez_to_dot_note"
|
||||
android:textColor="@color/text_warning"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone" />
|
||||
android:visibility="gone"
|
||||
app:alertMode="warning" />
|
||||
|
||||
<!-- Multisig signatory-only bridge allowance sign button. Only visible to a wallet
|
||||
that is one of the bridge multisig's 5 known signatories. -->
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="12dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/itemBridgePairIcon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/bg_token_container"
|
||||
android:scaleType="centerInside"
|
||||
tools:src="@drawable/ic_pezkuwi_logo" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemBridgePairTitle"
|
||||
style="@style/TextAppearance.NovaFoundation.SemiBold.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:textColor="@color/text_primary"
|
||||
tools:text="HEZ ⇄ DOT" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/bridgeAssetInputContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:background="@color/input_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bridgeAssetInputImage"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/bg_token_container"
|
||||
android:scaleType="centerInside"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_token_dot_colored" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bridgeAssetInputSubtitleImage"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/bridgeAssetInputSubtitle"
|
||||
app:layout_constraintStart_toEndOf="@+id/bridgeAssetInputImageBarrier"
|
||||
app:layout_constraintTop_toTopOf="@+id/bridgeAssetInputSubtitle"
|
||||
tools:src="@drawable/ic_pezkuwi_logo" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/bridgeAssetInputImageBarrier"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
app:barrierDirection="end"
|
||||
app:barrierMargin="12dp"
|
||||
app:constraint_referenced_ids="bridgeAssetInputImage" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeAssetInputToken"
|
||||
style="@style/TextAppearance.NovaFoundation.SemiBold.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/text_primary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/bridgeAssetInputSubtitle"
|
||||
app:layout_constraintStart_toEndOf="@id/bridgeAssetInputImage"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="DOT" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeAssetInputSubtitle"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/text_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/bridgeAssetInputSubtitleImage"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bridgeAssetInputToken"
|
||||
tools:text="Polkadot Asset Hub" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bridgeAssetInputChevron"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_chevron_right"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/bridgeAssetInputToken"
|
||||
app:layout_constraintStart_toEndOf="@+id/bridgeAssetInputToken"
|
||||
app:layout_constraintTop_toTopOf="@+id/bridgeAssetInputToken"
|
||||
app:tint="@color/icon_primary" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/bridgeAssetInputField"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Title2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@null"
|
||||
android:gravity="end"
|
||||
android:hint="0"
|
||||
android:inputType="numberDecimal"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:saveEnabled="false"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textColorHint="@color/hint_text"
|
||||
android:textCursorDrawable="@null"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/bridgeAssetInputChevron"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="4.508614" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeAssetInputError"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Caption1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="@color/text_negative"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bridgeAssetInputContainer" />
|
||||
|
||||
</merge>
|
||||
Reference in New Issue
Block a user