summaryrefslogtreecommitdiff
path: root/Hutch/Views/Tickets/TicketListViewModel.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-13 13:35:03 -0500
committerChristian Cleberg <[email protected]>2026-04-13 13:35:03 -0500
commit0b84ecaf7ba0fb2cd3f3867da8dc732450b55b50 (patch)
treec2d92448661d9e6ca76bd7c69e9d51131305214e /Hutch/Views/Tickets/TicketListViewModel.swift
parent06b24715cd244475ded5482e926721a57278a3cf (diff)
downloadhutch-0b84ecaf7ba0fb2cd3f3867da8dc732450b55b50.tar.gz
hutch-0b84ecaf7ba0fb2cd3f3867da8dc732450b55b50.tar.bz2
hutch-0b84ecaf7ba0fb2cd3f3867da8dc732450b55b50.zip
perf: cache filtered collections and add equatable row views
Convert filteredJobs, filteredPastes, and filteredTickets from computed properties to stored properties updated via didSet on their inputs. Each update function guards with an equality check before assigning, so @Observable skips the notification when the filtered result hasn't changed — preventing list re-renders on auto-refresh when no visible data has actually changed. Add Equatable conformance to BuildRowView, PasteRowView, TicketRowView, and SelectableTicketRow, and apply .equatable() at each ForEach call site. When a list does re-render, SwiftUI now skips body evaluation for rows whose model value is identical to the previous pass. Implements: https://todo.sr.ht/~ccleberg/hutch/57
Diffstat (limited to 'Hutch/Views/Tickets/TicketListViewModel.swift')
-rw-r--r--Hutch/Views/Tickets/TicketListViewModel.swift20
1 files changed, 15 insertions, 5 deletions
diff --git a/Hutch/Views/Tickets/TicketListViewModel.swift b/Hutch/Views/Tickets/TicketListViewModel.swift
index e7cc565..e23cc52 100644
--- a/Hutch/Views/Tickets/TicketListViewModel.swift
+++ b/Hutch/Views/Tickets/TicketListViewModel.swift
@@ -83,7 +83,9 @@ final class TicketListViewModel {
let trackerId: Int
let trackerRid: String
- private(set) var tickets: [TicketSummary] = []
+ private(set) var tickets: [TicketSummary] = [] {
+ didSet { updateFilteredTickets() }
+ }
private(set) var isLoading = false
private(set) var isLoadingMore = false
private(set) var isCreatingTicket = false
@@ -97,15 +99,21 @@ final class TicketListViewModel {
var filter: TicketFilter = .open {
didSet {
persistFilterState()
+ updateFilteredTickets()
}
}
var selectedLabelIDs: Set<Int> = [] {
didSet {
persistFilterState()
+ updateFilteredTickets()
}
}
- var searchText = ""
+ var searchText = "" {
+ didSet { updateFilteredTickets() }
+ }
private(set) var activeSavedFilterID: SavedTicketFilter.ID?
+ // Cached filtered result. See updateFilteredTickets().
+ private(set) var filteredTickets: [TicketSummary] = []
private var cursor: String?
private var hasMore = true
@@ -268,9 +276,11 @@ final class TicketListViewModel {
!currentFilterState.isDefault
}
- /// Tickets filtered by the selected status and label filters.
- var filteredTickets: [TicketSummary] {
- Self.filterTickets(tickets, state: currentFilterState, query: searchText)
+ private func updateFilteredTickets() {
+ let updated = Self.filterTickets(tickets, state: currentFilterState, query: searchText)
+ if updated != filteredTickets {
+ filteredTickets = updated
+ }
}
// MARK: - Public API