Files
oai-swift/oAI/Views/Screens/ReleaseNotesView.swift
T
rune 32e6ce3c37 Show release notes in-app instead of opening the web releases page
New "Read Release Notes" entries in the Help menu (current installed
version) and the "Check for Updates" alert (the new, not-yet-installed
version) render a release's markdown notes in a Confab modal.

- UpdateCheckService.fetchReleaseNotes(forTag:) fetches a release's
  title + body from Gitea's public releases-by-tag API, caching the
  result by version tag in the settings table (a published release's
  notes don't change, so no need to refetch on every view).
- ReleaseNotesView reuses the existing MarkdownContentView renderer;
  shows a friendly "not available yet" state for versions with no
  published Gitea release (e.g. a dev build ahead of the last release).
- ReleaseNotesRequest carries which version to show atomically via
  .sheet(item:), per this project's established sheet-timing pattern.
- Removed the redundant "Release Page" button from the update alert
  now that notes show in-app; added an explicit .keyboardShortcut
  (.cancelAction) to its cancel button so Escape actually closes it —
  role: .cancel alone didn't do it, since NSAlert only auto-binds
  Escape to a button literally titled "Cancel".
2026-08-03 13:13:36 +02:00

97 lines
3.3 KiB
Swift

//
// ReleaseNotesView.swift
// Confab
//
// Shows a Gitea release's markdown notes in-app, instead of opening the web releases page
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of Confab.
//
// Confab is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling Confab or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://confab.no>.
import SwiftUI
struct ReleaseNotesView: View {
let request: ReleaseNotesRequest
@Environment(\.dismiss) var dismiss
@State private var notes: ReleaseNotes?
@State private var isLoading = true
@State private var error: ReleaseNotesError?
var body: some View {
NavigationStack {
Group {
if isLoading {
ProgressView("Loading release notes...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if let error {
errorView(error)
} else if let notes {
ScrollView {
MarkdownContentView(content: notes.body, fontSize: 13)
.padding()
}
}
}
.navigationTitle(request.isCurrentlyInstalled ? "Release Notes" : "What's New")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") { dismiss() }
}
}
}
.frame(minWidth: 520, idealWidth: 600, minHeight: 480, idealHeight: 620)
.task {
await fetchNotes()
}
}
@ViewBuilder
private func errorView(_ error: ReleaseNotesError) -> some View {
VStack(spacing: 12) {
Image(systemName: "doc.text.magnifyingglass")
.font(.system(size: 40))
.foregroundStyle(.secondary)
switch error {
case .notFound:
Text("No release notes are available yet for \(request.versionTag).")
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
case .networkError:
Text("Couldn't load release notes. Check your internet connection and try again.")
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
Button("Retry") {
Task { await fetchNotes() }
}
}
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private func fetchNotes() async {
isLoading = true
error = nil
switch await UpdateCheckService.shared.fetchReleaseNotes(forTag: request.versionTag) {
case .success(let fetched):
notes = fetched
case .failure(let fetchError):
error = fetchError
}
isLoading = false
}
}