feat: add filtering by status
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user