gitbay/Views/Account/NotificationsView.swift

v1.2.0
gitbay-ios/gitbay/Views/Account/NotificationsView.swift history · blame · raw

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}