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:
+34
@@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.ArrowDownward
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -86,6 +88,24 @@ fun WalletTransactionsScreen(
|
|||||||
|
|
||||||
val transactions by walletViewModel.transactions.collectAsState()
|
val transactions by walletViewModel.transactions.collectAsState()
|
||||||
val isLoading by walletViewModel.isLoading.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(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -144,11 +164,25 @@ fun WalletTransactionsScreen(
|
|||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.padding(padding),
|
modifier = Modifier.padding(padding),
|
||||||
|
state = listState,
|
||||||
) {
|
) {
|
||||||
items(transactions) { tx ->
|
items(transactions) { tx ->
|
||||||
TransactionItem(tx, accountViewModel, nav)
|
TransactionItem(tx, accountViewModel, nav)
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
|
if (isLoadingMore) {
|
||||||
|
item {
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-7
@@ -88,6 +88,14 @@ class WalletViewModel : ViewModel() {
|
|||||||
private val _isLoading = MutableStateFlow(false)
|
private val _isLoading = MutableStateFlow(false)
|
||||||
val isLoading = _isLoading.asStateFlow()
|
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)
|
private val _error = MutableStateFlow<String?>(null)
|
||||||
val error = _error.asStateFlow()
|
val error = _error.asStateFlow()
|
||||||
|
|
||||||
@@ -153,24 +161,30 @@ class WalletViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchTransactions(
|
fun fetchTransactions() {
|
||||||
limit: Int = 20,
|
|
||||||
offset: Int = 0,
|
|
||||||
) {
|
|
||||||
val acc = account ?: return
|
val acc = account ?: return
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
_isLoading.value = true
|
_isLoading.value = true
|
||||||
|
_hasMoreTransactions.value = true
|
||||||
try {
|
try {
|
||||||
acc.sendNwcRequest(
|
acc.sendNwcRequest(
|
||||||
ListTransactionsMethod.create(
|
ListTransactionsMethod.create(
|
||||||
limit = limit,
|
limit = pageSize,
|
||||||
offset = offset,
|
offset = 0,
|
||||||
unpaid = false,
|
unpaid = false,
|
||||||
),
|
),
|
||||||
) { response ->
|
) { response ->
|
||||||
when (response) {
|
when (response) {
|
||||||
is ListTransactionsSuccessResponse -> {
|
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 -> {
|
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) {
|
fun sendPayment(bolt11: String) {
|
||||||
val acc = account ?: return
|
val acc = account ?: return
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|||||||
Reference in New Issue
Block a user