feat: auto-load wallet transactions on scroll

Add infinite scroll pagination to the wallet transactions screen.
When the user scrolls within 5 items of the bottom, the next page of
transactions is automatically fetched and appended. Uses the existing
NWC list_transactions limit/offset support and total_count to know
when all transactions have been loaded.

https://claude.ai/code/session_01CdNY7qYDRHJr1jhAFvVgzj
This commit is contained in:
Claude
2026-03-17 02:09:44 +00:00
parent d0297b7e34
commit 4a430e6663
2 changed files with 97 additions and 7 deletions
@@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDownward
@@ -49,6 +50,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -86,6 +88,24 @@ fun WalletTransactionsScreen(
val transactions by walletViewModel.transactions.collectAsState()
val isLoading by walletViewModel.isLoading.collectAsState()
val isLoadingMore by walletViewModel.isLoadingMore.collectAsState()
val hasMore by walletViewModel.hasMoreTransactions.collectAsState()
val listState = rememberLazyListState()
val shouldLoadMore by remember {
derivedStateOf {
val lastVisibleIndex = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
val totalItems = listState.layoutInfo.totalItemsCount
lastVisibleIndex >= totalItems - 5 && !isLoadingMore && hasMore && transactions.isNotEmpty()
}
}
LaunchedEffect(shouldLoadMore) {
if (shouldLoadMore) {
walletViewModel.loadMoreTransactions()
}
}
Scaffold(
topBar = {
@@ -144,11 +164,25 @@ fun WalletTransactionsScreen(
} else {
LazyColumn(
modifier = Modifier.padding(padding),
state = listState,
) {
items(transactions) { tx ->
TransactionItem(tx, accountViewModel, nav)
HorizontalDivider()
}
if (isLoadingMore) {
item {
Column(
modifier =
Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
}
}
}
}
}
}
@@ -88,6 +88,14 @@ class WalletViewModel : ViewModel() {
private val _isLoading = MutableStateFlow(false)
val isLoading = _isLoading.asStateFlow()
private val _isLoadingMore = MutableStateFlow(false)
val isLoadingMore = _isLoadingMore.asStateFlow()
private val _hasMoreTransactions = MutableStateFlow(true)
val hasMoreTransactions = _hasMoreTransactions.asStateFlow()
private val pageSize = 20
private val _error = MutableStateFlow<String?>(null)
val error = _error.asStateFlow()
@@ -153,24 +161,30 @@ class WalletViewModel : ViewModel() {
}
}
fun fetchTransactions(
limit: Int = 20,
offset: Int = 0,
) {
fun fetchTransactions() {
val acc = account ?: return
viewModelScope.launch(Dispatchers.IO) {
_isLoading.value = true
_hasMoreTransactions.value = true
try {
acc.sendNwcRequest(
ListTransactionsMethod.create(
limit = limit,
offset = offset,
limit = pageSize,
offset = 0,
unpaid = false,
),
) { response ->
when (response) {
is ListTransactionsSuccessResponse -> {
_transactions.value = response.result?.transactions ?: emptyList()
val txs = response.result?.transactions ?: emptyList()
_transactions.value = txs
val totalCount = response.result?.total_count
_hasMoreTransactions.value =
if (totalCount != null) {
txs.size < totalCount
} else {
txs.size >= pageSize
}
}
is NwcErrorResponse -> {
@@ -188,6 +202,48 @@ class WalletViewModel : ViewModel() {
}
}
fun loadMoreTransactions() {
if (_isLoadingMore.value || !_hasMoreTransactions.value) return
val acc = account ?: return
val currentOffset = _transactions.value.size
viewModelScope.launch(Dispatchers.IO) {
_isLoadingMore.value = true
try {
acc.sendNwcRequest(
ListTransactionsMethod.create(
limit = pageSize,
offset = currentOffset,
unpaid = false,
),
) { response ->
when (response) {
is ListTransactionsSuccessResponse -> {
val newTxs = response.result?.transactions ?: emptyList()
_transactions.value = _transactions.value + newTxs
val totalCount = response.result?.total_count
_hasMoreTransactions.value =
if (totalCount != null) {
_transactions.value.size < totalCount
} else {
newTxs.size >= pageSize
}
}
is NwcErrorResponse -> {
_error.value = response.error?.message ?: "Failed to load more transactions"
}
else -> {}
}
_isLoadingMore.value = false
}
} catch (e: Exception) {
_error.value = e.message
_isLoadingMore.value = false
}
}
}
fun sendPayment(bolt11: String) {
val acc = account ?: return
viewModelScope.launch(Dispatchers.IO) {