import SwiftUI struct ReleaseView: View { @State private var model: ReleaseDetailViewModel @State private var editing = false @State private var draftTitle = "" @State private var draftNotes = "" init(client: GitbayClient, repo: String, tag: String) { _model = State(initialValue: ReleaseDetailViewModel( client: client, repoPath: repo, tag: tag )) } var body: some View { List { if let release = model.state.value { if let error = model.actionError { Section { GBNotice(error, .gbWarn) } } Section { VStack(alignment: .leading, spacing: 6) { Text(release.title.isEmpty ? release.tag : release.title) .font(.gbSans(.headline)) HStack(spacing: 6) { Text(release.tag) .font(.gbMono(.caption)) if let author = release.author { Text("by \(author)") } Text(release.createdAt, format: .relative(presentation: .named)) .foregroundStyle(.tertiary) } .font(.gbSans(.caption)) .foregroundStyle(.secondary) } .padding(.vertical, 2) } if let notes = release.notes, !notes.isEmpty { Section { MarkdownView(markdown: notes) .padding(.vertical, 4) } } if let assets = release.assets, !assets.isEmpty { Section("Assets") { ForEach(assets) { asset in Link(destination: model.downloadURL(for: asset)) { HStack { VStack(alignment: .leading, spacing: 2) { Text(asset.name) .font(.gbMono(.caption)) .foregroundStyle(.primary) .lineLimit(1) Text(String(asset.sha256.prefix(16))) .font(.gbMono(.caption2)) .foregroundStyle(.tertiary) } Spacer() Text(asset.size.formatted(.byteCount(style: .file))) .font(.gbSans(.caption)) .foregroundStyle(.secondary) Image(systemName: "arrow.down.circle") .foregroundStyle(.secondary) } } } } } } } .overlay { LoadStateOverlay(state: model.state) } .navigationTitle(model.tag) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { if let release = model.state.value { Button("Edit") { draftTitle = release.title draftNotes = release.notes ?? "" editing = true } .disabled(model.working) .accessibilityIdentifier("release-edit-button") } } } .sheet(isPresented: $editing) { ComposeSheet( heading: "Edit \(model.tag)", submitLabel: "Save", working: model.working, errorMessage: model.actionError, title: $draftTitle, bodyText: $draftNotes ) { Task { await model.edit(title: draftTitle, notes: draftNotes) if model.actionError == nil { editing = false } } } } .task { await model.load() } .refreshable { await model.load() } } }