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".
97 lines
3.3 KiB
Swift
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
|
|
}
|
|
}
|