diff options
Diffstat (limited to 'Hutch/Views/Tickets/TicketListViewModel.swift')
| -rw-r--r-- | Hutch/Views/Tickets/TicketListViewModel.swift | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/Hutch/Views/Tickets/TicketListViewModel.swift b/Hutch/Views/Tickets/TicketListViewModel.swift index 1f44993..2727dcc 100644 --- a/Hutch/Views/Tickets/TicketListViewModel.swift +++ b/Hutch/Views/Tickets/TicketListViewModel.swift @@ -33,6 +33,23 @@ private struct LabelMutationResponse: Decodable, Sendable { let unlabelTicket: EventRef? } +private struct TrackerSubscriptionStateResponse: Decodable, Sendable { + let tracker: TrackerSubscriptionWrapper +} + +private struct TrackerSubscriptionWrapper: Decodable, Sendable { + /// Null when the authenticated user is not subscribed to this tracker. + let subscription: TrackerSubscriptionIdPayload? +} + +private struct TrackerSubscriptionResponse: Decodable, Sendable { + let subscription: TrackerSubscriptionIdPayload +} + +private struct TrackerSubscriptionIdPayload: Decodable, Sendable { + let id: Int +} + private struct TrackerLabelsResponse: Decodable, Sendable { let tracker: TrackerLabelsWrapper } @@ -90,6 +107,9 @@ final class TicketListViewModel { private(set) var isLoadingMore = false private(set) var isCreatingTicket = false private(set) var isPerformingAction = false + /// Whether the authenticated user receives email for this tracker. Mirrors + /// `Tracker.subscription`, which is null when not subscribed. + private(set) var isSubscribed = false private(set) var trackerLabels: [TicketLabel] = [] private(set) var recentSearches: [ScopedSearchHistoryEntry] private(set) var savedFilters: [SavedTicketFilter] @@ -216,6 +236,28 @@ final class TicketListViewModel { } """ + /// Kept separate from `query` above, which is paginated and cached — the + /// subscription is per-user state and should not ride along in page payloads. + private static let trackerSubscriptionQuery = """ + query trackerSubscription($rid: ID!) { + tracker(rid: $rid) { + subscription { id } + } + } + """ + + private static let trackerSubscribeMutation = """ + mutation trackerSubscribe($trackerId: Int!) { + subscription: trackerSubscribe(trackerId: $trackerId) { id } + } + """ + + private static let trackerUnsubscribeMutation = """ + mutation trackerUnsubscribe($trackerId: Int!, $tickets: Boolean!) { + subscription: trackerUnsubscribe(trackerId: $trackerId, tickets: $tickets) { id } + } + """ + private static let trackerLabelsQuery = """ query trackerLabels($rid: ID!) { tracker(rid: $rid) { @@ -520,6 +562,52 @@ final class TicketListViewModel { isPerformingAction = false } + /// Reads whether the user is subscribed to this tracker. Uncached: it is + /// per-user state that must be accurate the moment the menu opens. + func loadSubscriptionState() async { + do { + let response = try await client.execute( + service: .todo, + query: Self.trackerSubscriptionQuery, + variables: ["rid": trackerRid], + responseType: TrackerSubscriptionStateResponse.self + ) + isSubscribed = response.tracker.subscription != nil + } catch { + // Leave the last known value alone; the toggle reports its own errors. + } + } + + /// Subscribes to or unsubscribes from email notifications for this tracker. + /// Unsubscribing leaves individual ticket subscriptions intact. + func toggleSubscription() async { + guard !isPerformingAction else { return } + isPerformingAction = true + error = nil + defer { isPerformingAction = false } + + let wasSubscribed = isSubscribed + isSubscribed.toggle() + + var variables: [String: any Sendable] = ["trackerId": trackerId] + if wasSubscribed { + variables["tickets"] = false + } + + do { + _ = try await client.execute( + service: .todo, + query: wasSubscribed ? Self.trackerUnsubscribeMutation : Self.trackerSubscribeMutation, + variables: variables, + responseType: TrackerSubscriptionResponse.self + ) + await client.invalidateCache(prefix: APICacheKeys.prefix(SRHTService.todo.rawValue, "tracker")) + } catch { + isSubscribed = wasSubscribed + self.error = error.userFacingMessage + } + } + func loadTrackerLabels() async { do { let cached = try await client.executeCached( |
