// // 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 // for // the full license text. For commercial licensing, contact Rune // Olsen via . 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 } }