diff options
Diffstat (limited to 'octosentry/SecurityEventListView.swift')
| -rw-r--r-- | octosentry/SecurityEventListView.swift | 123 |
1 files changed, 116 insertions, 7 deletions
diff --git a/octosentry/SecurityEventListView.swift b/octosentry/SecurityEventListView.swift index d817370..84e6638 100644 --- a/octosentry/SecurityEventListView.swift +++ b/octosentry/SecurityEventListView.swift @@ -9,6 +9,7 @@ import SwiftUI struct SecurityEventListView: View { var store: SecurityEventStore var authStore: AuthStore + var updateStore: UpdateStore var isStandaloneWindow: Bool = false @State private var showingRepoManager = false @Environment(\.openWindow) private var openWindow @@ -16,6 +17,9 @@ struct SecurityEventListView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { header + if let release = updateStore.availableRelease { + UpdateBanner(release: release) + } Divider() if !authStore.isSignedIn { SignInView(authStore: authStore) @@ -30,6 +34,9 @@ struct SecurityEventListView: View { await store.refresh() store.startPolling() } + .task { + await updateStore.checkForUpdate() + } } private var header: some View { @@ -142,6 +149,10 @@ private struct RepoManagerView: View { var store: SecurityEventStore var authStore: AuthStore @State private var newRepoText = "" + @State private var isBrowsingRepos = false + @State private var availableRepos: [String] = [] + @State private var isLoadingRepos = false + @State private var browseErrorMessage: String? var body: some View { VStack(alignment: .leading, spacing: 10) { @@ -171,13 +182,24 @@ private struct RepoManagerView: View { Divider() - HStack { - TextField("owner/repo", text: $newRepoText) - .textFieldStyle(.roundedBorder) - .onSubmit(addRepo) + if isBrowsingRepos { + browsingContent + } else { + HStack { + TextField("owner/repo", text: $newRepoText) + .textFieldStyle(.roundedBorder) + .onSubmit(addRepo) + + Button("Add", action: addRepo) + .disabled(newRepoText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } - Button("Add", action: addRepo) - .disabled(newRepoText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + Button(action: startBrowsing) { + Label("Browse your repos", systemImage: "list.bullet") + .font(.caption) + } + .buttonStyle(.plain) + .foregroundStyle(Color.accentColor) } if let errorMessage = store.watchListErrorMessage { @@ -200,6 +222,75 @@ private struct RepoManagerView: View { .frame(maxWidth: .infinity, alignment: .leading) } + @ViewBuilder + private var browsingContent: some View { + VStack(alignment: .leading, spacing: 6) { + HStack { + Text("Your Repositories") + .font(.caption.weight(.semibold)) + Spacer() + Button { + isBrowsingRepos = false + } label: { + Image(systemName: "xmark.circle") + } + .buttonStyle(.plain) + } + + if isLoadingRepos { + ProgressView() + .controlSize(.small) + .frame(maxWidth: .infinity) + } else if let browseErrorMessage { + Text(browseErrorMessage) + .font(.caption2) + .foregroundStyle(.red) + } else { + let selectableRepos = availableRepos.filter { !store.watchedRepos.contains($0) } + if selectableRepos.isEmpty { + Text("All visible repos are already watched.") + .font(.caption2) + .foregroundStyle(.secondary) + } else { + ScrollView { + LazyVStack(alignment: .leading, spacing: 4) { + ForEach(selectableRepos, id: \.self) { repo in + Button { + Task { await store.addRepo(repo) } + isBrowsingRepos = false + } label: { + Text(repo) + .font(.callout) + .frame(maxWidth: .infinity, alignment: .leading) + } + .buttonStyle(.plain) + } + } + } + .frame(maxHeight: 160) + } + } + } + } + + private func startBrowsing() { + guard authStore.hasRepoAccess else { + authStore.requestRepoAccess() + return + } + isBrowsingRepos = true + isLoadingRepos = true + browseErrorMessage = nil + Task { + do { + availableRepos = try await store.fetchAccessibleRepos() + } catch { + browseErrorMessage = (error as? LocalizedError)?.errorDescription ?? error.localizedDescription + } + isLoadingRepos = false + } + } + private func addRepo() { let text = newRepoText newRepoText = "" @@ -207,6 +298,24 @@ private struct RepoManagerView: View { } } +private struct UpdateBanner: View { + let release: UpdateChecker.LatestRelease + + var body: some View { + Button { + NSWorkspace.shared.open(release.htmlURL) + } label: { + Label("Update available: \(release.version)", systemImage: "arrow.down.circle.fill") + .font(.caption) + .frame(maxWidth: .infinity, alignment: .leading) + } + .buttonStyle(.plain) + .foregroundStyle(.blue) + .padding(8) + .background(.blue.opacity(0.1)) + } +} + private struct ErrorBanner: View { let messages: [String] @@ -262,6 +371,6 @@ private struct StatusView: View { } #Preview { - SecurityEventListView(store: SecurityEventStore(), authStore: AuthStore()) + SecurityEventListView(store: SecurityEventStore(), authStore: AuthStore(), updateStore: UpdateStore()) .frame(width: 380, height: 420) } |
