mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 16:05:49 +00:00
Move Pending Signatures from the wallet home screen to the Bridge screen
Wrong screen in the previous commit - moved it to sit right below the existing per-leg renewal sign buttons on the Bridge input screen, which is where it was actually asked for: a signatory renewing their own wUSDT/Polkadot USDT allowance there should also see (and be able to jump to) other pending multisig operations - e.g. someone else's large bridge swap - waiting on their signature, without navigating elsewhere. BalanceListFragment/ViewModel/Module are reverted to their prior state. The reusable pieces (PendingSignatureModel, item_pending_signature_row layout) are kept and repurposed for the Bridge screen; the RecyclerView SingleItemAdapter/card layout built for the home screen are removed since Bridge is a plain (non-RecyclerView) fragment - rows are inflated directly into a LinearLayout container instead, same underlying data and MultisigCallFormatter reuse as before. The feature-assets -> feature-multisig:operations dependency and the FeatureApi wiring (AssetsFeatureComponent/Dependencies/Holder) from the previous commit stay as-is - Bridge is the same Gradle module, so no change needed there.
This commit is contained in:
+1
-31
@@ -36,9 +36,6 @@ import android.content.Intent
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PezkuwiDashboardAdapter
|
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PezkuwiDashboardAdapter
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PezkuwiDashboardHolder
|
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PezkuwiDashboardHolder
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PendingSignaturesAdapter
|
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PendingSignaturesHolder
|
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.PendingSignatureModel
|
|
||||||
import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipBottomSheet
|
import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipBottomSheet
|
||||||
import io.novafoundation.nova.feature_banners_api.presentation.BannerHolder
|
import io.novafoundation.nova.feature_banners_api.presentation.BannerHolder
|
||||||
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannerAdapter
|
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannerAdapter
|
||||||
@@ -52,7 +49,6 @@ class BalanceListFragment :
|
|||||||
AssetsHeaderAdapter.Handler,
|
AssetsHeaderAdapter.Handler,
|
||||||
ManageAssetsAdapter.Handler,
|
ManageAssetsAdapter.Handler,
|
||||||
PezkuwiDashboardAdapter.Handler,
|
PezkuwiDashboardAdapter.Handler,
|
||||||
PendingSignaturesAdapter.Handler,
|
|
||||||
CitizenshipBottomSheet.CitizenshipDismissListener {
|
CitizenshipBottomSheet.CitizenshipDismissListener {
|
||||||
|
|
||||||
override fun createBinding() = FragmentBalanceListBinding.inflate(layoutInflater)
|
override fun createBinding() = FragmentBalanceListBinding.inflate(layoutInflater)
|
||||||
@@ -70,10 +66,6 @@ class BalanceListFragment :
|
|||||||
PezkuwiDashboardAdapter(this)
|
PezkuwiDashboardAdapter(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pendingSignaturesAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
|
||||||
PendingSignaturesAdapter(imageLoader, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val bannerAdapter: PromotionBannerAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
private val bannerAdapter: PromotionBannerAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
PromotionBannerAdapter(closable = true)
|
PromotionBannerAdapter(closable = true)
|
||||||
}
|
}
|
||||||
@@ -94,15 +86,7 @@ class BalanceListFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
|
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
ConcatAdapter(
|
ConcatAdapter(headerAdapter, pezkuwiDashboardAdapter, bannerAdapter, manageAssetsAdapter, emptyAssetsPlaceholder, assetsAdapter)
|
||||||
headerAdapter,
|
|
||||||
pezkuwiDashboardAdapter,
|
|
||||||
pendingSignaturesAdapter,
|
|
||||||
bannerAdapter,
|
|
||||||
manageAssetsAdapter,
|
|
||||||
emptyAssetsPlaceholder,
|
|
||||||
assetsAdapter
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyInsets(rootView: View) {
|
override fun applyInsets(rootView: View) {
|
||||||
@@ -159,11 +143,6 @@ class BalanceListFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.pendingSignaturesFlow.observe { models ->
|
|
||||||
pendingSignaturesAdapter.setModels(models)
|
|
||||||
pendingSignaturesAdapter.show(models.isNotEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.bannersMixin.bindWithAdapter(bannerAdapter) {
|
viewModel.bannersMixin.bindWithAdapter(bannerAdapter) {
|
||||||
binder.balanceListAssets.invalidateItemDecorations()
|
binder.balanceListAssets.invalidateItemDecorations()
|
||||||
}
|
}
|
||||||
@@ -322,10 +301,6 @@ class BalanceListFragment :
|
|||||||
viewModel.startTrackingClicked()
|
viewModel.startTrackingClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSignClicked(model: PendingSignatureModel) {
|
|
||||||
viewModel.pendingSignatureSignClicked(model)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupRecyclerViewSpacing() {
|
private fun setupRecyclerViewSpacing() {
|
||||||
binder.balanceListAssets.addSpaceItemDecoration {
|
binder.balanceListAssets.addSpaceItemDecoration {
|
||||||
add(SpaceBetween(AssetsHeaderHolder, PezkuwiDashboardHolder, spaceDp = 8))
|
add(SpaceBetween(AssetsHeaderHolder, PezkuwiDashboardHolder, spaceDp = 8))
|
||||||
@@ -334,11 +309,6 @@ class BalanceListFragment :
|
|||||||
add(SpaceBetween(AssetsHeaderHolder, BannerHolder, spaceDp = 4))
|
add(SpaceBetween(AssetsHeaderHolder, BannerHolder, spaceDp = 4))
|
||||||
add(SpaceBetween(BannerHolder, ManageAssetsHolder, spaceDp = 4))
|
add(SpaceBetween(BannerHolder, ManageAssetsHolder, spaceDp = 4))
|
||||||
add(SpaceBetween(AssetsHeaderHolder, ManageAssetsHolder, spaceDp = 24))
|
add(SpaceBetween(AssetsHeaderHolder, ManageAssetsHolder, spaceDp = 24))
|
||||||
|
|
||||||
add(SpaceBetween(AssetsHeaderHolder, PendingSignaturesHolder, spaceDp = 8))
|
|
||||||
add(SpaceBetween(PezkuwiDashboardHolder, PendingSignaturesHolder, spaceDp = 8))
|
|
||||||
add(SpaceBetween(PendingSignaturesHolder, BannerHolder, spaceDp = 4))
|
|
||||||
add(SpaceBetween(PendingSignaturesHolder, ManageAssetsHolder, spaceDp = 24))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-43
@@ -20,15 +20,10 @@ import io.novafoundation.nova.common.utils.formatting.formatAsPercentage
|
|||||||
import io.novafoundation.nova.common.utils.inBackground
|
import io.novafoundation.nova.common.utils.inBackground
|
||||||
import io.novafoundation.nova.common.utils.launchUnit
|
import io.novafoundation.nova.common.utils.launchUnit
|
||||||
import io.novafoundation.nova.common.utils.withSafeLoading
|
import io.novafoundation.nova.common.utils.withSafeLoading
|
||||||
import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi
|
|
||||||
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
|
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
|
||||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.MultisigAction
|
|
||||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperation
|
|
||||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.userAction
|
|
||||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||||
import io.novafoundation.nova.feature_account_api.domain.model.defaultSubstrateAddress
|
import io.novafoundation.nova.feature_account_api.domain.model.defaultSubstrateAddress
|
||||||
import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdKeyIn
|
|
||||||
import io.novafoundation.nova.feature_assets.R
|
import io.novafoundation.nova.feature_assets.R
|
||||||
import io.novafoundation.nova.feature_assets.domain.WalletInteractor
|
import io.novafoundation.nova.feature_assets.domain.WalletInteractor
|
||||||
import io.novafoundation.nova.feature_assets.domain.assets.list.AssetsListInteractor
|
import io.novafoundation.nova.feature_assets.domain.assets.list.AssetsListInteractor
|
||||||
@@ -47,7 +42,6 @@ import io.novafoundation.nova.feature_assets.presentation.balance.common.buySell
|
|||||||
import io.novafoundation.nova.feature_assets.presentation.balance.common.buySell.BuySellSelectorMixinFactory
|
import io.novafoundation.nova.feature_assets.presentation.balance.common.buySell.BuySellSelectorMixinFactory
|
||||||
import io.novafoundation.nova.feature_assets.presentation.citizenship.PendingCitizenshipReferrer
|
import io.novafoundation.nova.feature_assets.presentation.citizenship.PendingCitizenshipReferrer
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.NftPreviewUi
|
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.NftPreviewUi
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.PendingSignatureModel
|
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.TotalBalanceModel
|
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.TotalBalanceModel
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.AssetViewModeModel
|
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.AssetViewModeModel
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PendingOperationsCountModel
|
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PendingOperationsCountModel
|
||||||
@@ -58,10 +52,6 @@ import io.novafoundation.nova.feature_banners_api.presentation.source.assetsSour
|
|||||||
import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor
|
import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor
|
||||||
import io.novafoundation.nova.feature_currency_api.domain.model.Currency
|
import io.novafoundation.nova.feature_currency_api.domain.model.Currency
|
||||||
import io.novafoundation.nova.feature_currency_api.presentation.formatters.formatAsCurrency
|
import io.novafoundation.nova.feature_currency_api.presentation.formatters.formatAsCurrency
|
||||||
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
|
||||||
import io.novafoundation.nova.feature_multisig_operations.presentation.common.MultisigOperationPayload
|
|
||||||
import io.novafoundation.nova.feature_multisig_operations.presentation.common.fromOperationId
|
|
||||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.general.MultisigOperationDetailsPayload
|
|
||||||
import io.novafoundation.nova.feature_nft_api.data.model.Nft
|
import io.novafoundation.nova.feature_nft_api.data.model.Nft
|
||||||
import io.novafoundation.nova.feature_swap_api.domain.interactor.SwapAvailabilityInteractor
|
import io.novafoundation.nova.feature_swap_api.domain.interactor.SwapAvailabilityInteractor
|
||||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
|
||||||
@@ -118,7 +108,6 @@ class BalanceListViewModel(
|
|||||||
private val maskableValueFormatterProvider: MaskableValueFormatterProvider,
|
private val maskableValueFormatterProvider: MaskableValueFormatterProvider,
|
||||||
private val buySellSelectorMixinFactory: BuySellSelectorMixinFactory,
|
private val buySellSelectorMixinFactory: BuySellSelectorMixinFactory,
|
||||||
private val multisigPendingOperationsService: MultisigPendingOperationsService,
|
private val multisigPendingOperationsService: MultisigPendingOperationsService,
|
||||||
private val multisigCallFormatter: MultisigCallFormatter,
|
|
||||||
private val novaCardRestrictionCheckMixin: NovaCardRestrictionCheckMixin,
|
private val novaCardRestrictionCheckMixin: NovaCardRestrictionCheckMixin,
|
||||||
private val maskingModeUseCase: MaskingModeUseCase,
|
private val maskingModeUseCase: MaskingModeUseCase,
|
||||||
private val giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin,
|
private val giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin,
|
||||||
@@ -259,38 +248,6 @@ class BalanceListViewModel(
|
|||||||
.combine(maskableAmountFormatterFlow, ::formatPendingOperationsCount)
|
.combine(maskableAmountFormatterFlow, ::formatPendingOperationsCount)
|
||||||
.shareInBackground()
|
.shareInBackground()
|
||||||
|
|
||||||
val pendingSignaturesFlow = multisigPendingOperationsService.pendingOperations()
|
|
||||||
.mapLatest { operations ->
|
|
||||||
val account = selectedMetaAccount.first()
|
|
||||||
|
|
||||||
// Only operations still awaiting THIS signatory's own approval - once signed, the
|
|
||||||
// item naturally drops out here on next sync (no client-side "turn green" state to
|
|
||||||
// maintain in parallel with the real, authoritative on-chain approval status).
|
|
||||||
operations
|
|
||||||
.filter { it.userAction() is MultisigAction.CanApprove }
|
|
||||||
.map { it.toPendingSignatureUi(account) }
|
|
||||||
}
|
|
||||||
.shareInBackground()
|
|
||||||
|
|
||||||
fun pendingSignatureSignClicked(model: PendingSignatureModel) {
|
|
||||||
val operationPayload = MultisigOperationPayload.fromOperationId(model.id)
|
|
||||||
router.openMultisigOperationDetails(MultisigOperationDetailsPayload(operationPayload))
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun PendingMultisigOperation.toPendingSignatureUi(selectedAccount: MetaAccount): PendingSignatureModel {
|
|
||||||
val initialOrigin = selectedAccount.requireAccountIdKeyIn(chain)
|
|
||||||
val formattedCall = multisigCallFormatter.formatPreview(call, initialOrigin, chain)
|
|
||||||
|
|
||||||
return PendingSignatureModel(
|
|
||||||
id = operationId,
|
|
||||||
chain = mapChainToUi(chain),
|
|
||||||
title = formattedCall.title,
|
|
||||||
subtitle = formattedCall.subtitle,
|
|
||||||
primaryValue = formattedCall.primaryValue,
|
|
||||||
progress = resourceManager.getString(R.string.multisig_operations_progress, approvals.size.format(), threshold.format())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val pezkuwiDashboardFlow = selectedMetaAccount
|
val pezkuwiDashboardFlow = selectedMetaAccount
|
||||||
.combine(dashboardRefreshSignal) { account, _ -> account }
|
.combine(dashboardRefreshSignal) { account, _ -> account }
|
||||||
.mapLatest { metaAccount ->
|
.mapLatest { metaAccount ->
|
||||||
|
|||||||
-3
@@ -13,7 +13,6 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
|||||||
import io.novafoundation.nova.common.domain.usecase.MaskingModeUseCase
|
import io.novafoundation.nova.common.domain.usecase.MaskingModeUseCase
|
||||||
import io.novafoundation.nova.common.resources.ResourceManager
|
import io.novafoundation.nova.common.resources.ResourceManager
|
||||||
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
|
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
|
||||||
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
|
||||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||||
import io.novafoundation.nova.feature_assets.data.repository.PezkuwiDashboardRepository
|
import io.novafoundation.nova.feature_assets.data.repository.PezkuwiDashboardRepository
|
||||||
@@ -123,7 +122,6 @@ class BalanceListModule {
|
|||||||
amountFormatter: AmountFormatter,
|
amountFormatter: AmountFormatter,
|
||||||
buySellSelectorMixinFactory: BuySellSelectorMixinFactory,
|
buySellSelectorMixinFactory: BuySellSelectorMixinFactory,
|
||||||
multisigPendingOperationsService: MultisigPendingOperationsService,
|
multisigPendingOperationsService: MultisigPendingOperationsService,
|
||||||
multisigCallFormatter: MultisigCallFormatter,
|
|
||||||
novaCardRestrictionCheckMixin: NovaCardRestrictionCheckMixin,
|
novaCardRestrictionCheckMixin: NovaCardRestrictionCheckMixin,
|
||||||
maskableValueFormatterProvider: MaskableValueFormatterProvider,
|
maskableValueFormatterProvider: MaskableValueFormatterProvider,
|
||||||
maskingModeUseCase: MaskingModeUseCase,
|
maskingModeUseCase: MaskingModeUseCase,
|
||||||
@@ -150,7 +148,6 @@ class BalanceListModule {
|
|||||||
maskableValueFormatterProvider = maskableValueFormatterProvider,
|
maskableValueFormatterProvider = maskableValueFormatterProvider,
|
||||||
buySellSelectorMixinFactory = buySellSelectorMixinFactory,
|
buySellSelectorMixinFactory = buySellSelectorMixinFactory,
|
||||||
multisigPendingOperationsService = multisigPendingOperationsService,
|
multisigPendingOperationsService = multisigPendingOperationsService,
|
||||||
multisigCallFormatter = multisigCallFormatter,
|
|
||||||
novaCardRestrictionCheckMixin = novaCardRestrictionCheckMixin,
|
novaCardRestrictionCheckMixin = novaCardRestrictionCheckMixin,
|
||||||
maskingModeUseCase = maskingModeUseCase,
|
maskingModeUseCase = maskingModeUseCase,
|
||||||
fiatFormatter = fiatFormatter,
|
fiatFormatter = fiatFormatter,
|
||||||
|
|||||||
-74
@@ -1,74 +0,0 @@
|
|||||||
package io.novafoundation.nova.feature_assets.presentation.balance.list.view
|
|
||||||
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import coil.ImageLoader
|
|
||||||
import io.novafoundation.nova.common.list.SingleItemAdapter
|
|
||||||
import io.novafoundation.nova.common.utils.inflater
|
|
||||||
import io.novafoundation.nova.common.utils.recyclerView.WithViewType
|
|
||||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.loadChainIcon
|
|
||||||
import io.novafoundation.nova.feature_assets.R
|
|
||||||
import io.novafoundation.nova.feature_assets.databinding.ItemPendingSignatureRowBinding
|
|
||||||
import io.novafoundation.nova.feature_assets.databinding.ItemPendingSignaturesCardBinding
|
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.PendingSignatureModel
|
|
||||||
|
|
||||||
class PendingSignaturesAdapter(
|
|
||||||
private val imageLoader: ImageLoader,
|
|
||||||
private val handler: Handler
|
|
||||||
) : SingleItemAdapter<PendingSignaturesHolder>(isShownByDefault = false) {
|
|
||||||
|
|
||||||
interface Handler {
|
|
||||||
fun onSignClicked(model: PendingSignatureModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var models: List<PendingSignatureModel> = emptyList()
|
|
||||||
|
|
||||||
fun setModels(models: List<PendingSignatureModel>) {
|
|
||||||
this.models = models
|
|
||||||
notifyChangedIfShown()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PendingSignaturesHolder {
|
|
||||||
val binding = ItemPendingSignaturesCardBinding.inflate(parent.inflater(), parent, false)
|
|
||||||
return PendingSignaturesHolder(binding, imageLoader, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: PendingSignaturesHolder, position: Int) {
|
|
||||||
holder.bind(models)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
|
||||||
return PendingSignaturesHolder.viewType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PendingSignaturesHolder(
|
|
||||||
private val binder: ItemPendingSignaturesCardBinding,
|
|
||||||
private val imageLoader: ImageLoader,
|
|
||||||
private val handler: PendingSignaturesAdapter.Handler
|
|
||||||
) : RecyclerView.ViewHolder(binder.root) {
|
|
||||||
|
|
||||||
companion object : WithViewType {
|
|
||||||
override val viewType: Int = R.layout.item_pending_signatures_card
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bind(models: List<PendingSignatureModel>) {
|
|
||||||
val container = binder.pendingSignaturesRowsContainer
|
|
||||||
container.removeAllViews()
|
|
||||||
|
|
||||||
models.forEach { model ->
|
|
||||||
val rowBinding = ItemPendingSignatureRowBinding.inflate(container.inflater(), container, false)
|
|
||||||
|
|
||||||
rowBinding.pendingSignatureChainIcon.loadChainIcon(model.chain.icon, imageLoader)
|
|
||||||
// primaryValue (e.g. "100K USDT") is the amount - the whole point of this card - so it
|
|
||||||
// takes the prominent title line; the call's own title/action name ("Transfer") isn't
|
|
||||||
// shown at all here, only the destination (subtitle) is, to keep the row to two lines.
|
|
||||||
rowBinding.pendingSignatureTitle.text = model.primaryValue ?: model.title
|
|
||||||
rowBinding.pendingSignatureSubtitle.text = listOfNotNull(model.subtitle, model.chain.name, model.progress)
|
|
||||||
.joinToString(separator = " • ")
|
|
||||||
rowBinding.pendingSignatureSignButton.setOnClickListener { handler.onSignClicked(model) }
|
|
||||||
|
|
||||||
container.addView(rowBinding.root)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+36
@@ -3,19 +3,27 @@ package io.novafoundation.nova.feature_assets.presentation.bridge
|
|||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import coil.ImageLoader
|
||||||
import io.novafoundation.nova.common.base.BaseFragment
|
import io.novafoundation.nova.common.base.BaseFragment
|
||||||
import io.novafoundation.nova.common.di.FeatureUtils
|
import io.novafoundation.nova.common.di.FeatureUtils
|
||||||
|
import io.novafoundation.nova.common.utils.inflater
|
||||||
import io.novafoundation.nova.common.utils.setVisible
|
import io.novafoundation.nova.common.utils.setVisible
|
||||||
import io.novafoundation.nova.common.view.AlertView
|
import io.novafoundation.nova.common.view.AlertView
|
||||||
import io.novafoundation.nova.common.view.setState
|
import io.novafoundation.nova.common.view.setState
|
||||||
|
import io.novafoundation.nova.feature_account_api.presenatation.chain.loadChainIcon
|
||||||
import io.novafoundation.nova.feature_assets.R
|
import io.novafoundation.nova.feature_assets.R
|
||||||
import io.novafoundation.nova.feature_assets.databinding.FragmentBridgeBinding
|
import io.novafoundation.nova.feature_assets.databinding.FragmentBridgeBinding
|
||||||
|
import io.novafoundation.nova.feature_assets.databinding.ItemPendingSignatureRowBinding
|
||||||
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
|
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
|
||||||
import io.novafoundation.nova.feature_assets.di.AssetsFeatureComponent
|
import io.novafoundation.nova.feature_assets.di.AssetsFeatureComponent
|
||||||
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.MaxActionAvailability
|
import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.MaxActionAvailability
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class BridgeFragment : BaseFragment<BridgeViewModel, FragmentBridgeBinding>() {
|
class BridgeFragment : BaseFragment<BridgeViewModel, FragmentBridgeBinding>() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var imageLoader: ImageLoader
|
||||||
|
|
||||||
override fun createBinding() = FragmentBridgeBinding.inflate(layoutInflater)
|
override fun createBinding() = FragmentBridgeBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
override fun initViews() {
|
override fun initViews() {
|
||||||
@@ -196,6 +204,34 @@ class BridgeFragment : BaseFragment<BridgeViewModel, FragmentBridgeBinding>() {
|
|||||||
viewModel.fillAmountEvent.observeEvent { amount ->
|
viewModel.fillAmountEvent.observeEvent { amount ->
|
||||||
binder.bridgeFromCard.amountInput.setText(amount)
|
binder.bridgeFromCard.amountInput.setText(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.pendingSignatures.observe { models ->
|
||||||
|
bindPendingSignatures(models)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindPendingSignatures(models: List<PendingSignatureModel>) {
|
||||||
|
val visibility = if (models.isEmpty()) View.GONE else View.VISIBLE
|
||||||
|
binder.bridgePendingSignaturesTitle.visibility = visibility
|
||||||
|
binder.bridgePendingSignaturesContainer.visibility = visibility
|
||||||
|
|
||||||
|
val container = binder.bridgePendingSignaturesContainer
|
||||||
|
container.removeAllViews()
|
||||||
|
|
||||||
|
models.forEach { model ->
|
||||||
|
val rowBinding = ItemPendingSignatureRowBinding.inflate(container.inflater(), container, false)
|
||||||
|
|
||||||
|
rowBinding.pendingSignatureChainIcon.loadChainIcon(model.chain.icon, imageLoader)
|
||||||
|
// primaryValue (e.g. "100K USDT") is the amount - the whole point of this row - so it
|
||||||
|
// takes the prominent title line; the call's own title/action name ("Transfer") isn't
|
||||||
|
// shown at all here, only the destination (subtitle) is, to keep the row to two lines.
|
||||||
|
rowBinding.pendingSignatureTitle.text = model.primaryValue ?: model.title
|
||||||
|
rowBinding.pendingSignatureSubtitle.text = listOfNotNull(model.subtitle, model.chain.name, model.progress)
|
||||||
|
.joinToString(separator = " • ")
|
||||||
|
rowBinding.pendingSignatureSignButton.setOnClickListener { viewModel.pendingSignatureSignClicked(model) }
|
||||||
|
|
||||||
|
container.addView(rowBinding.root)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+58
@@ -6,8 +6,17 @@ import io.novafoundation.nova.common.base.BaseViewModel
|
|||||||
import io.novafoundation.nova.common.presentation.AssetIconProvider
|
import io.novafoundation.nova.common.presentation.AssetIconProvider
|
||||||
import io.novafoundation.nova.common.resources.ResourceManager
|
import io.novafoundation.nova.common.resources.ResourceManager
|
||||||
import io.novafoundation.nova.common.utils.Event
|
import io.novafoundation.nova.common.utils.Event
|
||||||
|
import io.novafoundation.nova.common.utils.formatting.format
|
||||||
import io.novafoundation.nova.common.utils.images.Icon
|
import io.novafoundation.nova.common.utils.images.Icon
|
||||||
import io.novafoundation.nova.common.view.ButtonState
|
import io.novafoundation.nova.common.view.ButtonState
|
||||||
|
import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi
|
||||||
|
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
|
||||||
|
import io.novafoundation.nova.feature_account_api.data.multisig.model.MultisigAction
|
||||||
|
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperation
|
||||||
|
import io.novafoundation.nova.feature_account_api.data.multisig.model.userAction
|
||||||
|
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
||||||
|
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||||
|
import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdKeyIn
|
||||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.getAssetIconOrFallback
|
import io.novafoundation.nova.feature_account_api.presenatation.chain.getAssetIconOrFallback
|
||||||
import io.novafoundation.nova.feature_assets.R
|
import io.novafoundation.nova.feature_assets.R
|
||||||
import io.novafoundation.nova.feature_assets.domain.WalletInteractor
|
import io.novafoundation.nova.feature_assets.domain.WalletInteractor
|
||||||
@@ -16,12 +25,17 @@ import io.novafoundation.nova.feature_assets.domain.bridge.multisig.BridgeMultis
|
|||||||
import io.novafoundation.nova.feature_assets.domain.bridge.multisig.BridgeSignerState
|
import io.novafoundation.nova.feature_assets.domain.bridge.multisig.BridgeSignerState
|
||||||
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
|
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
|
||||||
import io.novafoundation.nova.feature_assets.presentation.bridge.execution.BridgeExecutionPayload
|
import io.novafoundation.nova.feature_assets.presentation.bridge.execution.BridgeExecutionPayload
|
||||||
|
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
||||||
|
import io.novafoundation.nova.feature_multisig_operations.presentation.common.MultisigOperationPayload
|
||||||
|
import io.novafoundation.nova.feature_multisig_operations.presentation.common.fromOperationId
|
||||||
|
import io.novafoundation.nova.feature_multisig_operations.presentation.details.general.MultisigOperationDetailsPayload
|
||||||
import io.novafoundation.nova.runtime.ext.ChainGeneses
|
import io.novafoundation.nova.runtime.ext.ChainGeneses
|
||||||
import io.novafoundation.nova.runtime.ext.addressOf
|
import io.novafoundation.nova.runtime.ext.addressOf
|
||||||
import io.novafoundation.nova.runtime.ext.displayNameWithAssetStandard
|
import io.novafoundation.nova.runtime.ext.displayNameWithAssetStandard
|
||||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||||
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
|
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
@@ -48,6 +62,9 @@ class BridgeViewModel(
|
|||||||
private val assetIconProvider: AssetIconProvider,
|
private val assetIconProvider: AssetIconProvider,
|
||||||
private val walletInteractor: WalletInteractor,
|
private val walletInteractor: WalletInteractor,
|
||||||
private val bridgeMultisigInteractor: BridgeMultisigInteractor,
|
private val bridgeMultisigInteractor: BridgeMultisigInteractor,
|
||||||
|
private val multisigPendingOperationsService: MultisigPendingOperationsService,
|
||||||
|
private val multisigCallFormatter: MultisigCallFormatter,
|
||||||
|
private val selectedAccountUseCase: SelectedAccountUseCase,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -130,6 +147,15 @@ class BridgeViewModel(
|
|||||||
private val _polkadotSignButtonLabel = MutableLiveData("")
|
private val _polkadotSignButtonLabel = MutableLiveData("")
|
||||||
val polkadotSignButtonLabel: LiveData<String> = _polkadotSignButtonLabel
|
val polkadotSignButtonLabel: LiveData<String> = _polkadotSignButtonLabel
|
||||||
|
|
||||||
|
/** Multisig operations (any of them - not just bridge swaps, since a signatory of this bridge
|
||||||
|
* account may also be a signatory of other multisig accounts) still awaiting this wallet's
|
||||||
|
* own approval - shown below the renewal sign buttons above so a signatory doesn't have to
|
||||||
|
* separately navigate to the pending-operations list to find and sign a swap someone else
|
||||||
|
* submitted (see the "Sign" buttons above, which renew this wallet's own on-chain allowance -
|
||||||
|
* this is a different, complementary concern: approving *other people's* pending calls). */
|
||||||
|
private val _pendingSignatures = MutableLiveData<List<PendingSignatureModel>>(emptyList())
|
||||||
|
val pendingSignatures: LiveData<List<PendingSignatureModel>> = _pendingSignatures
|
||||||
|
|
||||||
private val _fromCard = MutableLiveData<BridgeAssetCardUi>()
|
private val _fromCard = MutableLiveData<BridgeAssetCardUi>()
|
||||||
val fromCard: LiveData<BridgeAssetCardUi> = _fromCard
|
val fromCard: LiveData<BridgeAssetCardUi> = _fromCard
|
||||||
|
|
||||||
@@ -452,6 +478,38 @@ class BridgeViewModel(
|
|||||||
fun refreshBridgeStatus() {
|
fun refreshBridgeStatus() {
|
||||||
fetchReserveStatus()
|
fetchReserveStatus()
|
||||||
refreshSignerState()
|
refreshSignerState()
|
||||||
|
refreshPendingSignatures()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshPendingSignatures() {
|
||||||
|
launch {
|
||||||
|
val account = selectedAccountUseCase.getSelectedMetaAccount()
|
||||||
|
|
||||||
|
val models = multisigPendingOperationsService.pendingOperations().first()
|
||||||
|
.filter { it.userAction() is MultisigAction.CanApprove }
|
||||||
|
.map { it.toPendingSignatureUi(account) }
|
||||||
|
|
||||||
|
_pendingSignatures.postValue(models)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pendingSignatureSignClicked(model: PendingSignatureModel) {
|
||||||
|
val operationPayload = MultisigOperationPayload.fromOperationId(model.id)
|
||||||
|
router.openMultisigOperationDetails(MultisigOperationDetailsPayload(operationPayload))
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun PendingMultisigOperation.toPendingSignatureUi(selectedAccount: MetaAccount): PendingSignatureModel {
|
||||||
|
val initialOrigin = selectedAccount.requireAccountIdKeyIn(chain)
|
||||||
|
val formattedCall = multisigCallFormatter.formatPreview(call, initialOrigin, chain)
|
||||||
|
|
||||||
|
return PendingSignatureModel(
|
||||||
|
id = operationId,
|
||||||
|
chain = mapChainToUi(chain),
|
||||||
|
title = formattedCall.title,
|
||||||
|
subtitle = formattedCall.subtitle,
|
||||||
|
primaryValue = formattedCall.primaryValue,
|
||||||
|
progress = resourceManager.getString(R.string.multisig_operations_progress, approvals.size.format(), threshold.format())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshSignerState() {
|
fun refreshSignerState() {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package io.novafoundation.nova.feature_assets.presentation.balance.list.model
|
package io.novafoundation.nova.feature_assets.presentation.bridge
|
||||||
|
|
||||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperationId
|
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperationId
|
||||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.ChainUi
|
import io.novafoundation.nova.feature_account_api.presenatation.chain.ChainUi
|
||||||
+8
@@ -11,12 +11,14 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
|||||||
import io.novafoundation.nova.common.presentation.AssetIconProvider
|
import io.novafoundation.nova.common.presentation.AssetIconProvider
|
||||||
import io.novafoundation.nova.common.resources.ResourceManager
|
import io.novafoundation.nova.common.resources.ResourceManager
|
||||||
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
|
||||||
|
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
|
||||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
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.WalletInteractor
|
||||||
import io.novafoundation.nova.feature_assets.domain.bridge.multisig.BridgeMultisigInteractor
|
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.domain.bridge.multisig.RealBridgeMultisigInteractor
|
||||||
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
|
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
|
||||||
import io.novafoundation.nova.feature_assets.presentation.bridge.BridgeViewModel
|
import io.novafoundation.nova.feature_assets.presentation.bridge.BridgeViewModel
|
||||||
|
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
||||||
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
|
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
|
||||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||||
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
||||||
@@ -45,6 +47,9 @@ class BridgeModule {
|
|||||||
assetIconProvider: AssetIconProvider,
|
assetIconProvider: AssetIconProvider,
|
||||||
walletInteractor: WalletInteractor,
|
walletInteractor: WalletInteractor,
|
||||||
bridgeMultisigInteractor: BridgeMultisigInteractor,
|
bridgeMultisigInteractor: BridgeMultisigInteractor,
|
||||||
|
multisigPendingOperationsService: MultisigPendingOperationsService,
|
||||||
|
multisigCallFormatter: MultisigCallFormatter,
|
||||||
|
selectedAccountUseCase: SelectedAccountUseCase,
|
||||||
): ViewModel {
|
): ViewModel {
|
||||||
return BridgeViewModel(
|
return BridgeViewModel(
|
||||||
router,
|
router,
|
||||||
@@ -53,6 +58,9 @@ class BridgeModule {
|
|||||||
assetIconProvider,
|
assetIconProvider,
|
||||||
walletInteractor,
|
walletInteractor,
|
||||||
bridgeMultisigInteractor,
|
bridgeMultisigInteractor,
|
||||||
|
multisigPendingOperationsService,
|
||||||
|
multisigCallFormatter,
|
||||||
|
selectedAccountUseCase,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -214,6 +214,26 @@
|
|||||||
android:text="@string/bridge_sign_button"
|
android:text="@string/bridge_sign_button"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<!-- Pending multisig operations awaiting this signatory's own approval (someone
|
||||||
|
else's swap, not this wallet's own renewal above) - one row per operation,
|
||||||
|
populated dynamically, see BridgeViewModel.pendingSignatures. -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bridgePendingSignaturesTitle"
|
||||||
|
style="@style/TextAppearance.NovaFoundation.SemiBold.Caption1"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/pending_signatures_title"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/bridgePendingSignaturesContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
app:cardBackgroundColor="@android:color/transparent"
|
|
||||||
app:cardCornerRadius="20dp"
|
|
||||||
app:cardElevation="0dp"
|
|
||||||
app:strokeWidth="0dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/bg_pezkuwi_dashboard"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingVertical="12dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextAppearance.NovaFoundation.SemiBold.Caption1"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:text="@string/pending_signatures_title"
|
|
||||||
android:textColor="@color/text_secondary" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/pendingSignaturesRowsContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
Reference in New Issue
Block a user