feat: add filtering by status

This commit is contained in:
2026-06-02 20:50:49 +02:00
parent ecd01349ec
commit 5d118cd396
2 changed files with 106 additions and 27 deletions
@@ -56,6 +56,7 @@ fun HistoryScreen(
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val isFiltered = filter.direction != DirectionFilter.ALL
|| filter.status != StatusFilter.ALL
Scaffold(
topBar = {
@@ -100,25 +101,50 @@ fun HistoryScreen(
.padding(horizontal = 16.dp, vertical = 4.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
InputChip(
selected = true,
onClick = { vm.setDirectionFilter(DirectionFilter.ALL) },
label = {
Text(
when (filter.direction) {
DirectionFilter.OUTGOING -> "Outgoing"
else -> "Incoming"
}
)
},
trailingIcon = {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Clear direction filter",
modifier = Modifier.size(16.dp)
)
}
)
if (filter.direction != DirectionFilter.ALL) {
InputChip(
selected = true,
onClick = { vm.setDirectionFilter(DirectionFilter.ALL) },
label = {
Text(
when (filter.direction) {
DirectionFilter.OUTGOING -> "Outgoing"
else -> "Incoming"
}
)
},
trailingIcon = {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Clear direction filter",
modifier = Modifier.size(16.dp)
)
}
)
}
if (filter.status != StatusFilter.ALL) {
InputChip(
selected = true,
onClick = { vm.setStatusFilter(StatusFilter.ALL) },
label = {
Text(
when (filter.status) {
StatusFilter.COMPLETED -> "Completed"
StatusFilter.PENDING -> "Pending"
StatusFilter.FAILED -> "Failed"
StatusFilter.ALL -> ""
}
)
},
trailingIcon = {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Clear status filter",
modifier = Modifier.size(16.dp)
)
}
)
}
}
}
@@ -220,6 +246,10 @@ fun HistoryScreen(
onDirectionSelected = { direction ->
vm.setDirectionFilter(direction)
dismissFilterSheet()
},
onStatusSelected = { status ->
vm.setStatusFilter(status)
dismissFilterSheet()
}
)
}
@@ -233,7 +263,8 @@ private fun FilterBottomSheet(
currentFilter : PaymentFilter,
sheetState : SheetState,
onDismiss : () -> Unit,
onDirectionSelected: (DirectionFilter) -> Unit
onDirectionSelected: (DirectionFilter) -> Unit,
onStatusSelected : (StatusFilter) -> Unit
) {
ModalBottomSheet(
onDismissRequest = onDismiss,
@@ -272,6 +303,29 @@ private fun FilterBottomSheet(
)
}
}
Spacer(Modifier.height(20.dp))
// ── Status ───────────────────────────────────────────────────────
Text(
text = "Status",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(Modifier.height(12.dp))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
StatusFilter.entries.forEach { option ->
val label = when (option) {
StatusFilter.ALL -> "All"
StatusFilter.COMPLETED -> "Completed"
StatusFilter.PENDING -> "Pending"
StatusFilter.FAILED -> "Failed"
}
FilterChip(
selected = currentFilter.status == option,
onClick = { onStatusSelected(option) },
label = { Text(label) }
)
}
}
}
}
}
@@ -548,7 +602,7 @@ private fun PaymentDetailContent(
DetailRow("Date", formatTimestamp(payment.time))
DetailRow("Memo", payment.memo?.takeIf { it.isNotBlank() } ?: "")
if (payment.extra?.tag != null) {
DetailRow("Type", payment.extra.tag.uppercase())
DetailRow("Type", payment.extra.tag)
}
if (payment.extra?.comment?.isNotBlank() == true) {
DetailRow("Comment", payment.extra.comment)
@@ -22,10 +22,14 @@ import kotlinx.coroutines.launch
// ── Filter model ──────────────────────────────────────────────────────────────
data class PaymentFilter(
val direction: DirectionFilter = DirectionFilter.ALL
val direction: DirectionFilter = DirectionFilter.ALL,
val status: StatusFilter = StatusFilter.ALL
)
enum class DirectionFilter { ALL, OUTGOING, INCOMING }
enum class StatusFilter { ALL, COMPLETED, PENDING, FAILED }
// ── ViewModel ─────────────────────────────────────────────────────────────────
@@ -48,18 +52,35 @@ class HistoryViewModel(
fun setDirectionFilter(direction: DirectionFilter) {
_filter.update { it.copy(direction = direction) }
}
fun setStatusFilter(status: StatusFilter) {
_filter.update { it.copy(status = status) }
}
/** The list shown in the UI — raw state with filter applied. */
val filteredState: StateFlow<HistoryState> = combine(_state, _filter) { s, f ->
if (s !is HistoryState.Success) return@combine s
val filtered = when (f.direction) {
DirectionFilter.ALL -> s.payments
DirectionFilter.OUTGOING -> s.payments.filter { it.isOutgoing }
DirectionFilter.INCOMING -> s.payments.filter { !it.isOutgoing }
}
val filtered = s.payments
.let { list ->
when (f.direction) {
DirectionFilter.ALL -> list
DirectionFilter.OUTGOING -> list.filter { it.isOutgoing }
DirectionFilter.INCOMING -> list.filter { !it.isOutgoing }
}
}
.let { list ->
when (f.status) {
StatusFilter.ALL -> list
StatusFilter.COMPLETED -> list.filter { isCompletedStatus(it.status) }
StatusFilter.PENDING -> list.filter { isPendingStatus(it.status) }
StatusFilter.FAILED -> list.filter { isFailedStatus(it.status) }
}
}
s.copy(payments = filtered)
}.stateIn(viewModelScope, SharingStarted.Eagerly, HistoryState.Loading)
private var currentOffset = 0
private var loadJob: Job? = null
@@ -275,6 +296,10 @@ class HistoryViewModel(
companion object {
private const val TAG = "HistoryViewModel"
fun isCompletedStatus(s: String) = s.lowercase() in setOf("success", "complete", "paid")
fun isPendingStatus(s: String) = s.lowercase() in setOf("pending", "in_flight", "inflight")
fun isFailedStatus(s: String) = s.lowercase() in setOf("failed", "error", "expired")
}
}