import SwiftUI /// The notification inbox: `notifications list [--all]`, paginated, with /// a switch between the default unread-only listing and everything. struct NotificationsView: View { @State private var model: NotificationsViewModel init(client: GitbayClient) { _model = State(initialValue: NotificationsViewModel(client: client)) } var body: some View { List { Picker("Filter", selection: Bindable(model).showAll) { Text("Unread").tag(false) Text("All").tag(true) } .pickerStyle(.segmented) .listRowBackground(Color.clear) .listRowInsets(EdgeInsets()) if let actionError = model.actionError { Section { GBNotice(actionError) } } ForEach(model.state.value ?? []) { notification in NotificationLink(notification: notification) .swipeActions { if notification.isUnread { Button("Mark read") { Task { await model.markRead(notification.id) } } .tint(.gbAccent) } } } PageFooter(list: model.list) } .overlay { LoadStateOverlay(state: model.state) } .navigationTitle("Notifications") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Mark all read") { Task { await model.markAllRead() } } .disabled(model.working) } } .task { await model.load() } .refreshable { await model.load() } } } /// Routes to `destination` when the path resolves to a screen; a /// notification the app cannot route renders plainly and is not /// tappable. private struct NotificationLink: View { let notification: InboxNotification @ViewBuilder var body: some View { switch notification.destination { case .issue(let repo, let number): NavigationLink(value: IssueRoute.issue(repo: repo, number: number)) { NotificationRow(notification: notification) } case .mr(let repo, let number): NavigationLink(value: MRRoute.mr(repo: repo, number: number)) { NotificationRow(notification: notification) } case .build(let repo, let number): NavigationLink(value: BuildRoute.detail(repo: repo, number: number)) { NotificationRow(notification: notification) } case nil: NotificationRow(notification: notification) } } } private struct NotificationRow: View { let notification: InboxNotification var body: some View { VStack(alignment: .leading, spacing: 4) { HStack(alignment: .firstTextBaseline) { Text(notification.actor) .font(.gbSans(.subheadline).weight(notification.isUnread ? .semibold : .regular)) Spacer() Text(notification.createdAt, format: .relative(presentation: .named)) .font(.gbSans(.caption)) .foregroundStyle(.secondary) } Text(notification.summary) .font(.gbSans(.subheadline).weight(notification.isUnread ? .semibold : .regular)) .foregroundStyle(notification.isUnread ? .primary : .secondary) .lineLimit(2) } .padding(.vertical, 2) } }