mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 14:55:48 +00:00
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.
This commit is contained in:
@@ -375,10 +375,11 @@
|
||||
<string name="bridge_sign_error">Signing failed, tap to retry</string>
|
||||
|
||||
<string name="bridge_deposit_success_label">Bridge complete — funds delivered</string>
|
||||
<string name="bridge_deposit_pending_message">Still processing - this can take longer if it needs manual multisig review. Check back in a few minutes.</string>
|
||||
<string name="bridge_deposit_pending_message">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.</string>
|
||||
|
||||
<string name="bridge_execution_submitting_label">Submitting transfer…</string>
|
||||
<string name="bridge_execution_waiting_destination_label">Waiting for confirmation on %s…</string>
|
||||
<string name="bridge_execution_do_not_close">Do not close the app!</string>
|
||||
|
||||
<string name="wallet_asset_buy_sell">Buy/Sell</string>
|
||||
|
||||
|
||||
+24
@@ -28,6 +28,9 @@ class BridgeExecutionFragment : BaseFragment<BridgeExecutionViewModel, FragmentB
|
||||
binder.bridgeExecutionToolbar.setHomeButtonListener { viewModel.doneClicked() }
|
||||
|
||||
binder.bridgeExecutionDoneButton.setOnClickListener { viewModel.doneClicked() }
|
||||
|
||||
binder.bridgeExecutionFromCard.setEditable(false)
|
||||
binder.bridgeExecutionToCard.setEditable(false)
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
@@ -58,6 +61,27 @@ class BridgeExecutionFragment : BaseFragment<BridgeExecutionViewModel, FragmentB
|
||||
|
||||
viewModel.doneButtonVisible.observe { visible ->
|
||||
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
|
||||
|
||||
+46
@@ -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<Boolean> = _doneButtonVisible
|
||||
|
||||
private val _fromCard = MutableLiveData<BridgeAssetCardUi>()
|
||||
val fromCard: LiveData<BridgeAssetCardUi> = _fromCard
|
||||
|
||||
private val _toCard = MutableLiveData<BridgeAssetCardUi>()
|
||||
val toCard: LiveData<BridgeAssetCardUi> = _toCard
|
||||
|
||||
private val _fromAmountText = MutableLiveData("")
|
||||
val fromAmountText: LiveData<String> = _fromAmountText
|
||||
|
||||
private val _toAmountText = MutableLiveData("")
|
||||
val toAmountText: LiveData<String> = _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))
|
||||
|
||||
+3
@@ -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,
|
||||
|
||||
@@ -13,39 +13,88 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:titleText="@string/bridge_title" />
|
||||
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/bridgeExecutionDoneButton"
|
||||
app:layout_constraintTop_toBottomOf="@id/bridgeExecutionToolbar">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeExecutionLabel"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/text_secondary"
|
||||
tools:text="Waiting for confirmation on Pezkuwi Asset Hub…" />
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<io.novafoundation.nova.common.view.ExecutionTimerView
|
||||
android:id="@+id/bridgeExecutionTimer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp" />
|
||||
<!-- Only visible while a real on-chain operation is genuinely in flight (submitting
|
||||
or waiting on the destination) - gone once resolved, same as the Swap execution
|
||||
screen's equivalent warning. -->
|
||||
<TextView
|
||||
android:id="@+id/bridgeExecutionDoNotClose"
|
||||
style="@style/TextAppearance.NovaFoundation.Bold.Title2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/bridge_execution_do_not_close"
|
||||
android:textColor="@color/text_primary" />
|
||||
|
||||
<io.novafoundation.nova.common.view.AlertView
|
||||
android:id="@+id/bridgeExecutionPendingReviewAlert"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:visibility="gone"
|
||||
app:alertMode="warning" />
|
||||
<TextView
|
||||
android:id="@+id/bridgeExecutionLabel"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/text_secondary"
|
||||
tools:text="Waiting for confirmation on Pezkuwi Asset Hub…" />
|
||||
|
||||
</LinearLayout>
|
||||
<io.novafoundation.nova.common.view.ExecutionTimerView
|
||||
android:id="@+id/bridgeExecutionTimer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
<io.novafoundation.nova.common.view.AlertView
|
||||
android:id="@+id/bridgeExecutionPendingReviewAlert"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone"
|
||||
app:alertMode="warning" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeExecutionFromLabel"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/bridge_you_send"
|
||||
android:textColor="@color/text_secondary" />
|
||||
|
||||
<io.novafoundation.nova.feature_assets.presentation.bridge.view.BridgeAssetInputView
|
||||
android:id="@+id/bridgeExecutionFromCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bridgeExecutionToLabel"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/bridge_you_receive"
|
||||
android:textColor="@color/text_secondary" />
|
||||
|
||||
<io.novafoundation.nova.feature_assets.presentation.bridge.view.BridgeAssetInputView
|
||||
android:id="@+id/bridgeExecutionToCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<io.novafoundation.nova.common.view.PrimaryButton
|
||||
android:id="@+id/bridgeExecutionDoneButton"
|
||||
|
||||
Reference in New Issue
Block a user