From 272921dcdf573af990ed03c1d74f88944178f1ba Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Wed, 15 Jul 2026 21:47:32 -0700 Subject: [PATCH] Match the execution screen's visual polish to the app's own Swap flow The structural separation (dedicated screen, no amount input) was already in place, but the screen itself was much sparser than Swap's own execution UI - just a label and a timer, no "Do not close the app!" warning and no summary of what's actually being bridged. Added both, reusing components already proven on Bridge's own input screen (BridgeAssetInputView in non-editable mode, same as the main screen's fromCard/toCard) rather than reaching into feature-swap-impl's Swap-specific components. Also added the signatories' contact channel (t.me/pezkuwidestek) to the existing "still processing" message, and made explicit that funds are safe and on-chain even when auto-pay doesn't complete within the wait window - this is the message shown when a real transfer lands in manual review, not just the pre-submit consent gate. --- common/src/main/res/values/strings.xml | 3 +- .../execution/BridgeExecutionFragment.kt | 24 +++++ .../execution/BridgeExecutionViewModel.kt | 46 +++++++++ .../execution/di/BridgeExecutionModule.kt | 3 + .../res/layout/fragment_bridge_execution.xml | 95 ++++++++++++++----- 5 files changed, 147 insertions(+), 24 deletions(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 47386504..8854432a 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -375,10 +375,11 @@ Signing failed, tap to retry Bridge complete — funds delivered - Still processing - this can take longer if it needs manual multisig review. Check back in a few minutes. + Still processing - this can take longer if it needs manual multisig review. Your funds are safe and recorded on-chain. Check back in a few minutes, or reach the signatories at t.me/pezkuwidestek. Submitting transfer… Waiting for confirmation on %s… + Do not close the app! Buy/Sell diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/BridgeExecutionFragment.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/BridgeExecutionFragment.kt index 9b0d0b62..becccf48 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/BridgeExecutionFragment.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/BridgeExecutionFragment.kt @@ -28,6 +28,9 @@ class BridgeExecutionFragment : BaseFragment binder.bridgeExecutionDoneButton.visibility = if (visible) View.VISIBLE else View.GONE + // "Do not close the app!" only makes sense while something is genuinely still in + // flight - doneButtonVisible becomes true exactly once the operation has resolved + // (success, pending review, or failure), so its inverse is the same signal without a + // separate LiveData to keep in sync. + binder.bridgeExecutionDoNotClose.visibility = if (visible) View.GONE else View.VISIBLE + } + + viewModel.fromCard.observe { model -> + binder.bridgeExecutionFromCard.setModel(model) + } + + viewModel.toCard.observe { model -> + binder.bridgeExecutionToCard.setModel(model) + } + + viewModel.fromAmountText.observe { text -> + binder.bridgeExecutionFromCard.setAmountText(text) + } + + viewModel.toAmountText.observe { text -> + binder.bridgeExecutionToCard.setAmountText(text) } // Single source of truth for the alert banner - only OriginFailed (error) and diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/BridgeExecutionViewModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/BridgeExecutionViewModel.kt index d3b1bdab..6ff98740 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/BridgeExecutionViewModel.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/BridgeExecutionViewModel.kt @@ -4,23 +4,29 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope 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.view.ExecutionTimerView import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentCurrency import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase +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.send.SendInteractor import io.novafoundation.nova.feature_assets.presentation.AssetsRouter +import io.novafoundation.nova.feature_assets.presentation.bridge.BridgeAssetCardUi import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.WeightedAssetTransfer import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.buildAssetTransfer import io.novafoundation.nova.feature_wallet_api.domain.SendUseCase +import io.novafoundation.nova.runtime.ext.displayNameWithAssetStandard import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull import java.math.BigDecimal +import java.math.RoundingMode import kotlin.time.Duration.Companion.seconds /** @@ -35,6 +41,7 @@ class BridgeExecutionViewModel( private val payload: BridgeExecutionPayload, private val resourceManager: ResourceManager, private val chainRegistry: ChainRegistry, + private val assetIconProvider: AssetIconProvider, private val walletInteractor: WalletInteractor, private val sendInteractor: SendInteractor, private val sendUseCase: SendUseCase, @@ -66,7 +73,22 @@ class BridgeExecutionViewModel( private val _doneButtonVisible = MutableLiveData(false) val doneButtonVisible: LiveData = _doneButtonVisible + private val _fromCard = MutableLiveData() + val fromCard: LiveData = _fromCard + + private val _toCard = MutableLiveData() + val toCard: LiveData = _toCard + + private val _fromAmountText = MutableLiveData("") + val fromAmountText: LiveData = _fromAmountText + + private val _toAmountText = MutableLiveData("") + val toAmountText: LiveData = _toAmountText + init { + launch { + loadCards() + } launch { submit() } @@ -76,6 +98,30 @@ class BridgeExecutionViewModel( router.back() } + private suspend fun loadCards() { + val originChain = chainRegistry.getChain(payload.originChainId) + val destChain = chainRegistry.getChain(payload.destChainId) + + _fromCard.postValue(cardUiFor(originChain, payload.originAssetId)) + _toCard.postValue(cardUiFor(destChain, payload.destAssetId)) + + _fromAmountText.postValue(BigDecimal.valueOf(payload.amount).stripTrailingZeros().toPlainString()) + + val netOutput = payload.amount * (1 - FEE_PERCENT) + _toAmountText.postValue(BigDecimal(netOutput).setScale(6, RoundingMode.DOWN).stripTrailingZeros().toPlainString()) + } + + private suspend fun cardUiFor(chain: Chain, assetId: Int): BridgeAssetCardUi { + val asset = chain.assetsById.getValue(assetId) + + return BridgeAssetCardUi( + assetIcon = assetIconProvider.getAssetIconOrFallback(asset), + chainIconUrl = chain.icon, + symbol = asset.symbol.value, + chainName = chain.displayNameWithAssetStandard() + ) + } + private suspend fun submit() { _state.postValue(BridgeExecutionState.SubmittingOrigin) _label.postValue(resourceManager.getString(R.string.bridge_execution_submitting_label)) diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/di/BridgeExecutionModule.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/di/BridgeExecutionModule.kt index cdac55e0..f99dec80 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/di/BridgeExecutionModule.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/execution/di/BridgeExecutionModule.kt @@ -8,6 +8,7 @@ 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.domain.interfaces.SelectedAccountUseCase import io.novafoundation.nova.feature_assets.domain.WalletInteractor @@ -28,6 +29,7 @@ class BridgeExecutionModule { payload: BridgeExecutionPayload, resourceManager: ResourceManager, chainRegistry: ChainRegistry, + assetIconProvider: AssetIconProvider, walletInteractor: WalletInteractor, sendInteractor: SendInteractor, sendUseCase: SendUseCase, @@ -38,6 +40,7 @@ class BridgeExecutionModule { payload, resourceManager, chainRegistry, + assetIconProvider, walletInteractor, sendInteractor, sendUseCase, diff --git a/feature-assets/src/main/res/layout/fragment_bridge_execution.xml b/feature-assets/src/main/res/layout/fragment_bridge_execution.xml index fbf8ea54..1d925390 100644 --- a/feature-assets/src/main/res/layout/fragment_bridge_execution.xml +++ b/feature-assets/src/main/res/layout/fragment_bridge_execution.xml @@ -13,39 +13,88 @@ app:layout_constraintTop_toTopOf="parent" app:titleText="@string/bridge_title" /> - - + android:orientation="vertical" + android:padding="16dp"> - + + - + - + + + + + + + + + + + + + + +