aboutsummaryrefslogtreecommitdiff
path: root/Hutch/Views/Activity/ActivityView.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Hutch/Views/Activity/ActivityView.swift')
-rw-r--r--Hutch/Views/Activity/ActivityView.swift96
1 files changed, 96 insertions, 0 deletions
diff --git a/Hutch/Views/Activity/ActivityView.swift b/Hutch/Views/Activity/ActivityView.swift
new file mode 100644
index 0000000..6379d45
--- /dev/null
+++ b/Hutch/Views/Activity/ActivityView.swift
@@ -0,0 +1,96 @@
+import SwiftUI
+
+struct ActivityView: View {
+ @Environment(AppState.self) private var appState
+ @State private var viewModel: ActivityViewModel?
+
+ var body: some View {
+ Group {
+ if let viewModel {
+ content(viewModel)
+ } else {
+ SRHTLoadingStateView(message: "Loading Activity…")
+ }
+ }
+ .navigationTitle("Activity")
+ .navigationBarTitleDisplayMode(.inline)
+ .task {
+ let model = viewModel ?? ActivityViewModel(client: appState.client)
+ viewModel = model
+ await model.loadIfNeeded()
+ }
+ }
+
+ @ViewBuilder
+ private func content(_ viewModel: ActivityViewModel) -> some View {
+ List {
+ ForEach(viewModel.events) { event in
+ NavigationLink {
+ TicketDetailView(
+ ownerUsername: event.ownerUsername,
+ trackerName: event.trackerName,
+ trackerId: event.trackerID,
+ trackerRid: event.trackerRID,
+ ticketId: event.ticketID
+ )
+ } label: {
+ ActivityRow(event: event)
+ }
+ .themedRow()
+ }
+
+ if viewModel.hasMore {
+ HStack {
+ Spacer()
+ ProgressView()
+ Spacer()
+ }
+ .themedRow()
+ .task { await viewModel.loadMore() }
+ }
+ }
+ .themedList()
+ .listStyle(.plain)
+ .refreshable { await viewModel.load() }
+ .overlay {
+ if viewModel.isLoading, viewModel.events.isEmpty {
+ SRHTLoadingStateView(message: "Loading Activity…")
+ } else if let error = viewModel.error, viewModel.events.isEmpty {
+ SRHTErrorStateView(
+ title: "Couldn't Load Activity",
+ message: error,
+ retryAction: { await viewModel.load() }
+ )
+ } else if viewModel.events.isEmpty {
+ ContentUnavailableView(
+ "No Activity",
+ systemImage: "bell",
+ description: Text("Ticket activity you are subscribed to or involved in appears here.")
+ )
+ }
+ }
+ }
+}
+
+private struct ActivityRow: View {
+ let event: ActivityEvent
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ Text(event.ticketSubject)
+ .font(.subheadline.weight(.medium))
+ .lineLimit(2)
+
+ Text("\(event.summary) • \(event.created.relativeDescription)")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+
+ Text("\(event.trackerOwner.canonicalName)/\(event.trackerName) #\(event.ticketID)")
+ .font(.caption2)
+ .foregroundStyle(.tertiary)
+ }
+ .padding(.vertical, 2)
+ .accessibilityElement(children: .combine)
+ .accessibilityLabel("\(event.ticketSubject), \(event.summary), \(event.created.relativeDescription)")
+ }
+}