From cb6e1fe69600806309a4849e7c68df7fcda43b06 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 16:18:33 +0000 Subject: [PATCH] fix: keep filter chips visible on empty onchain transactions list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a filter (e.g. Non-Zaps) excluded every loaded transaction, the empty-state composable replaced the whole screen body — including the filter chip row — so the user couldn't switch back to All or Zaps without leaving the screen. Expose `hasAnyTransactions` from the ViewModel so the screen distinguishes "no chain rows at all" from "no rows for this filter": the chips stay rendered as long as any transaction has loaded, and the LazyColumn shows a per-filter empty message inline beneath them. Also drop the bc1 address header from the list since the screen title already identifies the wallet. --- .../wallet/OnchainTransactionsScreen.kt | 57 ++++++++++--------- .../wallet/OnchainTransactionsViewModel.kt | 11 ++++ amethyst/src/main/res/values/strings.xml | 1 + 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt index 0e15cf8e01..72181d1936 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt @@ -97,6 +97,7 @@ fun OnchainTransactionsScreen( ) val transactions by viewModel.filteredTransactions.collectAsState() + val hasAnyTransactions by viewModel.hasAnyTransactions.collectAsState() val isLoading by viewModel.isLoading.collectAsState() val isLoadingMore by viewModel.isLoadingMore.collectAsState() val hasMore by viewModel.hasMoreTransactions.collectAsState() @@ -150,7 +151,7 @@ fun OnchainTransactionsScreen( address == null -> { EmptyMessage(padding, stringRes(R.string.wallet_onchain_no_address)) } - isLoading && transactions.isEmpty() -> { + isLoading && !hasAnyTransactions -> { Column( modifier = Modifier @@ -167,13 +168,13 @@ fun OnchainTransactionsScreen( ) } } - error != null && transactions.isEmpty() -> { + error != null && !hasAnyTransactions -> { EmptyMessage( padding, error ?: stringRes(R.string.wallet_onchain_no_backend), ) } - transactions.isEmpty() -> { + !hasAnyTransactions -> { EmptyMessage(padding, stringRes(R.string.wallet_no_transactions)) } else -> { @@ -182,18 +183,35 @@ fun OnchainTransactionsScreen( modifier = Modifier.padding(padding), state = listState, ) { - item { AddressHeader(address) } item { TransactionFilterRow(currentFilter) { viewModel.setTransactionFilter(it) } } - items(transactions, key = { it.tx.txid }) { txView -> - OnchainTransactionItem( - view = txView, - accountViewModel = accountViewModel, - nav = nav, - onClick = { handleTxClick(txView, nav, uriHandler) }, - ) - HorizontalDivider() + if (transactions.isEmpty()) { + item { + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + stringRes(R.string.wallet_no_transactions_for_filter), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } else { + items(transactions, key = { it.tx.txid }) { txView -> + OnchainTransactionItem( + view = txView, + accountViewModel = accountViewModel, + nav = nav, + onClick = { handleTxClick(txView, nav, uriHandler) }, + ) + HorizontalDivider() + } } if (isLoadingMore) { item { @@ -236,21 +254,6 @@ private fun EmptyMessage( } } -@Composable -private fun AddressHeader(address: String?) { - if (address.isNullOrBlank()) return - Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { - Text( - text = address, - style = MaterialTheme.typography.bodySmall, - fontFamily = FontFamily.Monospace, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } -} - @Composable private fun TransactionFilterRow( currentFilter: TransactionFilter, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt index 23567fc242..190ed1b690 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt @@ -103,6 +103,17 @@ class OnchainTransactionsViewModel : ViewModel() { } }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) + /** + * Whether the unfiltered chain-side list has any rows. Drives whether the + * filter chips stay on screen — once we've loaded at least one transaction + * the chips should remain visible even if the current filter excludes + * everything, so the user can switch filters without the row disappearing. + */ + val hasAnyTransactions: StateFlow = + chainTxs + .map { it.isNotEmpty() } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false) + private val _isLoading = MutableStateFlow(false) val isLoading = _isLoading.asStateFlow() diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 39efc0ce4f..9f310a9be2 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -1823,6 +1823,7 @@ Creating invoice… Copy Invoice No transactions yet + No transactions match this filter Loading… Received Sent