From 707d1baba4fba18925ca3a2011fb2a6581b57d3a Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Fri, 17 Jul 2026 13:03:12 -0700 Subject: [PATCH] Add a Pending Signatures card to the wallet home screen Surfaces multisig operations awaiting the current signatory's own approval directly on the main balance screen, instead of requiring navigation to the separate pending-operations list. Each row shows the formatted amount, destination, chain, and approval progress, with a red Sign button that opens the existing operation-details/approve flow (fee estimation, signatory balance check, and validation are all already handled there - not reimplemented here). Reuses the already-tested MultisigCallFormatter from feature-multisig:operations (added as a new feature-assets dependency, exposed via the standard FeatureApi/FeatureContainer pattern - see AssetsFeatureHolder/AssetsFeatureComponent/AssetsFeatureDependencies) rather than re-deriving call-preview formatting independently. The card follows the same SingleItemAdapter + ConcatAdapter pattern as the existing Pezkuwi Dashboard card on the same screen. Once a pending operation is actually signed, it naturally drops off this list on the next sync (no separate client-side "signed" state is tracked in parallel with the real on-chain approval status). String only added to the base (English) strings.xml for now - not yet translated into the other ~14 locales this app otherwise maintains. --- .../root/navigation/navigators/Navigator.kt | 8 ++ common/src/main/res/values/strings.xml | 3 + feature-assets/build.gradle | 1 + .../di/AssetsFeatureComponent.kt | 4 +- .../di/AssetsFeatureDependencies.kt | 3 + .../feature_assets/di/AssetsFeatureHolder.kt | 2 + .../presentation/AssetsRouter.kt | 3 + .../balance/list/BalanceListFragment.kt | 32 +++++++- .../balance/list/BalanceListViewModel.kt | 43 +++++++++++ .../balance/list/di/BalanceListModule.kt | 3 + .../list/model/PendingSignatureModel.kt | 13 ++++ .../list/view/PendingSignaturesAdapter.kt | 74 +++++++++++++++++++ .../res/layout/item_pending_signature_row.xml | 64 ++++++++++++++++ .../layout/item_pending_signatures_card.xml | 35 +++++++++ 14 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/model/PendingSignatureModel.kt create mode 100644 feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/view/PendingSignaturesAdapter.kt create mode 100644 feature-assets/src/main/res/layout/item_pending_signature_row.xml create mode 100644 feature-assets/src/main/res/layout/item_pending_signatures_card.xml diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/Navigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/Navigator.kt index 93aa25fe..e5a970b2 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/Navigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/navigators/Navigator.kt @@ -108,6 +108,8 @@ import io.novafoundation.nova.feature_ledger_impl.presentation.account.addChain. import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.selectLedger.SelectLedgerPayload import io.novafoundation.nova.feature_multisig_operations.presentation.created.MultisigCreatedBottomSheet import io.novafoundation.nova.feature_multisig_operations.presentation.created.MultisigCreatedPayload +import io.novafoundation.nova.feature_multisig_operations.presentation.details.general.MultisigOperationDetailsFragment +import io.novafoundation.nova.feature_multisig_operations.presentation.details.general.MultisigOperationDetailsPayload import io.novafoundation.nova.feature_onboarding_impl.OnboardingRouter import io.novafoundation.nova.feature_onboarding_impl.presentation.welcome.WelcomeFragment import io.novafoundation.nova.feature_swap_api.presentation.model.SwapSettingsPayload @@ -983,6 +985,12 @@ class Navigator( .navigateInFirstAttachedContext() } + override fun openMultisigOperationDetails(payload: MultisigOperationDetailsPayload) { + navigationBuilder().action(R.id.action_multisigOperationDetailsFragment) + .setArgs(MultisigOperationDetailsFragment.createPayload(payload)) + .navigateInFirstAttachedContext() + } + override fun openMainWithFinishMultisigTransaction(accountWasSwitched: Boolean) { val payload = MultisigCreatedBottomSheet.createPayload(MultisigCreatedPayload(accountWasSwitched)) openSplitScreenWithInstantAction(R.id.action_open_multisigCreatedDialog, nestedActionExtras = payload) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 8854432a..c7e95933 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -2778,6 +2778,9 @@ Score tracking started! Hejmara Kurd Le Cihane + Pending Signatures + Sign + Citizenship Application Full Name diff --git a/feature-assets/build.gradle b/feature-assets/build.gradle index 5d2c584f..eb8ffa47 100644 --- a/feature-assets/build.gradle +++ b/feature-assets/build.gradle @@ -48,6 +48,7 @@ dependencies { implementation project(':feature-deep-linking') implementation project(':feature-ahm-api') implementation project(':feature-gift-api') + implementation project(':feature-multisig:operations') implementation kotlinDep diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt index 2b7f4c82..d71111c6 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt @@ -53,6 +53,7 @@ import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi import io.novafoundation.nova.feature_gift_api.di.GiftFeatureApi +import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi import io.novafoundation.nova.feature_nft_api.NftFeatureApi import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi import io.novafoundation.nova.feature_swap_api.di.SwapFeatureApi @@ -175,7 +176,8 @@ interface AssetsFeatureComponent : AssetsFeatureApi { BannersFeatureApi::class, DeepLinkingFeatureApi::class, ChainMigrationFeatureApi::class, - GiftFeatureApi::class + GiftFeatureApi::class, + MultisigOperationsFeatureApi::class ] ) interface AssetsFeatureDependenciesComponent : AssetsFeatureDependencies diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt index 1b91d945..90b388c5 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt @@ -99,6 +99,7 @@ import io.novafoundation.nova.feature_account_api.data.fee.capability.CustomFeeC import io.novafoundation.nova.feature_ahm_api.domain.ChainMigrationInfoUseCase import io.novafoundation.nova.feature_gift_api.domain.GiftsAccountSupportedUseCase import io.novafoundation.nova.feature_gift_api.domain.AvailableGiftAssetsUseCase +import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter import io.novafoundation.nova.feature_wallet_api.domain.SendUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin @@ -240,6 +241,8 @@ interface AssetsFeatureDependencies { val multisigPendingOperationsService: MultisigPendingOperationsService + val multisigCallFormatter: MultisigCallFormatter + val automaticInteractionGate: AutomaticInteractionGate val linkBuilderFactory: LinkBuilderFactory diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureHolder.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureHolder.kt index 2f0ac8c0..5b176d94 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureHolder.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureHolder.kt @@ -15,6 +15,7 @@ import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi import io.novafoundation.nova.feature_gift_api.di.GiftFeatureApi +import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi import io.novafoundation.nova.feature_nft_api.NftFeatureApi import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi import io.novafoundation.nova.feature_swap_api.di.SwapFeatureApi @@ -51,6 +52,7 @@ class AssetsFeatureHolder @Inject constructor( .deepLinkingFeatureApi(getFeature(DeepLinkingFeatureApi::class.java)) .chainMigrationFeatureApi(getFeature(ChainMigrationFeatureApi::class.java)) .giftFeatureApi(getFeature(GiftFeatureApi::class.java)) + .multisigOperationsFeatureApi(getFeature(MultisigOperationsFeatureApi::class.java)) .build() return DaggerAssetsFeatureComponent.factory() diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/AssetsRouter.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/AssetsRouter.kt index e9a8f688..dadbfede 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/AssetsRouter.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/AssetsRouter.kt @@ -11,6 +11,7 @@ import io.novafoundation.nova.feature_assets.presentation.tokens.add.enterInfo.A import io.novafoundation.nova.feature_assets.presentation.tokens.manage.chain.ManageChainTokensPayload import io.novafoundation.nova.feature_assets.presentation.trade.webInterface.TradeWebPayload import io.novafoundation.nova.feature_assets.presentation.transaction.filter.TransactionHistoryFilterPayload +import io.novafoundation.nova.feature_multisig_operations.presentation.details.general.MultisigOperationDetailsPayload import io.novafoundation.nova.feature_swap_api.presentation.model.SwapSettingsPayload import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload @@ -120,6 +121,8 @@ interface AssetsRouter { fun openPendingMultisigOperations() + fun openMultisigOperationDetails(payload: MultisigOperationDetailsPayload) + fun openAssetDetailsFromDeepLink(payload: AssetPayload) fun openGifts() diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListFragment.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListFragment.kt index 1278f1e4..25e481e7 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListFragment.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListFragment.kt @@ -36,6 +36,9 @@ import android.content.Intent 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.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_banners_api.presentation.BannerHolder import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannerAdapter @@ -49,6 +52,7 @@ class BalanceListFragment : AssetsHeaderAdapter.Handler, ManageAssetsAdapter.Handler, PezkuwiDashboardAdapter.Handler, + PendingSignaturesAdapter.Handler, CitizenshipBottomSheet.CitizenshipDismissListener { override fun createBinding() = FragmentBalanceListBinding.inflate(layoutInflater) @@ -66,6 +70,10 @@ class BalanceListFragment : PezkuwiDashboardAdapter(this) } + private val pendingSignaturesAdapter by lazy(LazyThreadSafetyMode.NONE) { + PendingSignaturesAdapter(imageLoader, this) + } + private val bannerAdapter: PromotionBannerAdapter by lazy(LazyThreadSafetyMode.NONE) { PromotionBannerAdapter(closable = true) } @@ -86,7 +94,15 @@ class BalanceListFragment : } private val adapter by lazy(LazyThreadSafetyMode.NONE) { - ConcatAdapter(headerAdapter, pezkuwiDashboardAdapter, bannerAdapter, manageAssetsAdapter, emptyAssetsPlaceholder, assetsAdapter) + ConcatAdapter( + headerAdapter, + pezkuwiDashboardAdapter, + pendingSignaturesAdapter, + bannerAdapter, + manageAssetsAdapter, + emptyAssetsPlaceholder, + assetsAdapter + ) } override fun applyInsets(rootView: View) { @@ -143,6 +159,11 @@ class BalanceListFragment : } } + viewModel.pendingSignaturesFlow.observe { models -> + pendingSignaturesAdapter.setModels(models) + pendingSignaturesAdapter.show(models.isNotEmpty()) + } + viewModel.bannersMixin.bindWithAdapter(bannerAdapter) { binder.balanceListAssets.invalidateItemDecorations() } @@ -301,6 +322,10 @@ class BalanceListFragment : viewModel.startTrackingClicked() } + override fun onSignClicked(model: PendingSignatureModel) { + viewModel.pendingSignatureSignClicked(model) + } + private fun setupRecyclerViewSpacing() { binder.balanceListAssets.addSpaceItemDecoration { add(SpaceBetween(AssetsHeaderHolder, PezkuwiDashboardHolder, spaceDp = 8)) @@ -309,6 +334,11 @@ class BalanceListFragment : add(SpaceBetween(AssetsHeaderHolder, BannerHolder, spaceDp = 4)) add(SpaceBetween(BannerHolder, ManageAssetsHolder, spaceDp = 4)) 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)) } } diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListViewModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListViewModel.kt index 03d4305e..7b18ac21 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListViewModel.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListViewModel.kt @@ -20,10 +20,15 @@ import io.novafoundation.nova.common.utils.formatting.formatAsPercentage import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.launchUnit 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.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.defaultSubstrateAddress +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdKeyIn import io.novafoundation.nova.feature_assets.R import io.novafoundation.nova.feature_assets.domain.WalletInteractor import io.novafoundation.nova.feature_assets.domain.assets.list.AssetsListInteractor @@ -42,6 +47,7 @@ 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.citizenship.PendingCitizenshipReferrer 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.view.AssetViewModeModel import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PendingOperationsCountModel @@ -52,6 +58,10 @@ 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.model.Currency 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_swap_api.domain.interactor.SwapAvailabilityInteractor import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter @@ -108,6 +118,7 @@ class BalanceListViewModel( private val maskableValueFormatterProvider: MaskableValueFormatterProvider, private val buySellSelectorMixinFactory: BuySellSelectorMixinFactory, private val multisigPendingOperationsService: MultisigPendingOperationsService, + private val multisigCallFormatter: MultisigCallFormatter, private val novaCardRestrictionCheckMixin: NovaCardRestrictionCheckMixin, private val maskingModeUseCase: MaskingModeUseCase, private val giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin, @@ -248,6 +259,38 @@ class BalanceListViewModel( .combine(maskableAmountFormatterFlow, ::formatPendingOperationsCount) .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 .combine(dashboardRefreshSignal) { account, _ -> account } .mapLatest { metaAccount -> diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/di/BalanceListModule.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/di/BalanceListModule.kt index eb4ddf0d..9c9d51b0 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/di/BalanceListModule.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/di/BalanceListModule.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.domain.usecase.MaskingModeUseCase import io.novafoundation.nova.common.resources.ResourceManager 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.SelectedAccountUseCase import io.novafoundation.nova.feature_assets.data.repository.PezkuwiDashboardRepository @@ -122,6 +123,7 @@ class BalanceListModule { amountFormatter: AmountFormatter, buySellSelectorMixinFactory: BuySellSelectorMixinFactory, multisigPendingOperationsService: MultisigPendingOperationsService, + multisigCallFormatter: MultisigCallFormatter, novaCardRestrictionCheckMixin: NovaCardRestrictionCheckMixin, maskableValueFormatterProvider: MaskableValueFormatterProvider, maskingModeUseCase: MaskingModeUseCase, @@ -148,6 +150,7 @@ class BalanceListModule { maskableValueFormatterProvider = maskableValueFormatterProvider, buySellSelectorMixinFactory = buySellSelectorMixinFactory, multisigPendingOperationsService = multisigPendingOperationsService, + multisigCallFormatter = multisigCallFormatter, novaCardRestrictionCheckMixin = novaCardRestrictionCheckMixin, maskingModeUseCase = maskingModeUseCase, fiatFormatter = fiatFormatter, diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/model/PendingSignatureModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/model/PendingSignatureModel.kt new file mode 100644 index 00000000..b0d44863 --- /dev/null +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/model/PendingSignatureModel.kt @@ -0,0 +1,13 @@ +package io.novafoundation.nova.feature_assets.presentation.balance.list.model + +import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperationId +import io.novafoundation.nova.feature_account_api.presenatation.chain.ChainUi + +data class PendingSignatureModel( + val id: PendingMultisigOperationId, + val chain: ChainUi, + val title: String, + val subtitle: String?, + val primaryValue: CharSequence?, + val progress: String, +) diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/view/PendingSignaturesAdapter.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/view/PendingSignaturesAdapter.kt new file mode 100644 index 00000000..251a86eb --- /dev/null +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/view/PendingSignaturesAdapter.kt @@ -0,0 +1,74 @@ +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(isShownByDefault = false) { + + interface Handler { + fun onSignClicked(model: PendingSignatureModel) + } + + private var models: List = emptyList() + + fun setModels(models: List) { + 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) { + 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) + } + } +} diff --git a/feature-assets/src/main/res/layout/item_pending_signature_row.xml b/feature-assets/src/main/res/layout/item_pending_signature_row.xml new file mode 100644 index 00000000..ca2245d7 --- /dev/null +++ b/feature-assets/src/main/res/layout/item_pending_signature_row.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + diff --git a/feature-assets/src/main/res/layout/item_pending_signatures_card.xml b/feature-assets/src/main/res/layout/item_pending_signatures_card.xml new file mode 100644 index 00000000..cc208529 --- /dev/null +++ b/feature-assets/src/main/res/layout/item_pending_signatures_card.xml @@ -0,0 +1,35 @@ + + + + + + + + + + +