gitbay/Views/Account/NotificationsView.swift
103 lines · 3685 bytes
1import SwiftUI
2
3/// The notification inbox: `notifications list [--all]`, paginated, with
4/// a switch between the default unread-only listing and everything.
5struct NotificationsView: View {
6
7 @State private var model: NotificationsViewModel
8
9 init(client: GitbayClient) {
10 _model = State(initialValue: NotificationsViewModel(client: client))
11 }
12
13 var body: some View {
14 List {
15 Picker("Filter", selection: Bindable(model).showAll) {
16 Text("Unread").tag(false)
17 Text("All").tag(true)
18 }
19 .pickerStyle(.segmented)
20 .listRowBackground(Color.clear)
21 .listRowInsets(EdgeInsets())
22
23 if let actionError = model.actionError {
24 Section { GBNotice(actionError) }
25 }
26
27 ForEach(model.state.value ?? []) { notification in
28 NotificationLink(notification: notification)
29 .swipeActions {
30 if notification.isUnread {
31 Button("Mark read") {
32 Task { await model.markRead(notification.id) }
33 }
34 .tint(.gbAccent)
35 }
36 }
37 }
38 PageFooter(list: model.list)
39 }
40 .overlay { LoadStateOverlay(state: model.state) }
41 .navigationTitle("Notifications")
42 .navigationBarTitleDisplayMode(.inline)
43 .toolbar {
44 ToolbarItem(placement: .topBarTrailing) {
45 Button("Mark all read") {
46 Task { await model.markAllRead() }
47 }
48 .disabled(model.working)
49 }
50 }
51 .task { await model.load() }
52 .refreshable { await model.load() }
53 }
54}
55
56/// Routes to `destination` when the path resolves to a screen; a
57/// notification the app cannot route renders plainly and is not
58/// tappable.
59private struct NotificationLink: View {
60 let notification: InboxNotification
61
62 @ViewBuilder
63 var body: some View {
64 switch notification.destination {
65 case .issue(let repo, let number):
66 NavigationLink(value: IssueRoute.issue(repo: repo, number: number)) {
67 NotificationRow(notification: notification)
68 }
69 case .mr(let repo, let number):
70 NavigationLink(value: MRRoute.mr(repo: repo, number: number)) {
71 NotificationRow(notification: notification)
72 }
73 case .build(let repo, let number):
74 NavigationLink(value: BuildRoute.detail(repo: repo, number: number)) {
75 NotificationRow(notification: notification)
76 }
77 case nil:
78 NotificationRow(notification: notification)
79 }
80 }
81}
82
83private struct NotificationRow: View {
84 let notification: InboxNotification
85
86 var body: some View {
87 VStack(alignment: .leading, spacing: 4) {
88 HStack(alignment: .firstTextBaseline) {
89 Text(notification.actor)
90 .font(.gbSans(.subheadline).weight(notification.isUnread ? .semibold : .regular))
91 Spacer()
92 Text(notification.createdAt, format: .relative(presentation: .named))
93 .font(.gbSans(.caption))
94 .foregroundStyle(.secondary)
95 }
96 Text(notification.summary)
97 .font(.gbSans(.subheadline).weight(notification.isUnread ? .semibold : .regular))
98 .foregroundStyle(notification.isUnread ? .primary : .secondary)
99 .lineLimit(2)
100 }
101 .padding(.vertical, 2)
102 }
103}