import SwiftUI import ProxyCore import GRDB struct ComposeListView: View { @State private var requests: [ComposeRequest] = [] @State private var showClearConfirmation = false @State private var showTemplatePicker = false @State private var observation: AnyDatabaseCancellable? private let composeRepo = ComposeRepository() var body: some View { Group { if requests.isEmpty { EmptyStateView( icon: "square.and.pencil", title: "No Requests", subtitle: "Create a new request to get started.", actionTitle: "New Request" ) { createEmptyRequest() } } else { List { ForEach(requests) { request in NavigationLink(value: request.id) { HStack { MethodBadge(method: request.method) VStack(alignment: .leading, spacing: 2) { Text(request.name) .font(.subheadline.weight(.medium)) if let url = request.url, !url.isEmpty { Text(url) .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) } if let ts = request.lastSentAt { Text(formatTimestamp(ts)) .font(.caption2) .foregroundStyle(.tertiary) } } Spacer() if let status = request.responseStatus { StatusBadge(statusCode: status) } } } } .onDelete { indexSet in for index in indexSet { if let id = requests[index].id { try? composeRepo.delete(id: id) } } } } .navigationDestination(for: Int64?.self) { id in if let id { ComposeEditorView(requestId: id) } } } } .navigationTitle("Compose") .toolbar { ToolbarItem(placement: .topBarLeading) { Button { showClearConfirmation = true } label: { Image(systemName: "trash") } .disabled(requests.isEmpty) } ToolbarItem(placement: .topBarTrailing) { Menu { Button("Empty Request") { createEmptyRequest() } Button("GET with Query") { createTemplate(method: "GET", name: "GET with Query") } Button("POST with JSON") { createTemplate(method: "POST", name: "POST with JSON", contentType: "application/json") } Button("POST with Form") { createTemplate(method: "POST", name: "POST with Form", contentType: "application/x-www-form-urlencoded") } Divider() Button("Import from cURL") { showTemplatePicker = true } } label: { Image(systemName: "plus") } } } .confirmationDialog("Clear History", isPresented: $showClearConfirmation) { Button("Clear All", role: .destructive) { try? composeRepo.deleteAll() } } message: { Text("This will permanently delete all compose requests.") } .sheet(isPresented: $showTemplatePicker) { CURLImportView { parsed in var request = ComposeRequest( name: "Imported Request", method: parsed.method, url: parsed.url, headers: encodeHeaders(parsed.headers), body: parsed.body ) try? composeRepo.insert(&request) showTemplatePicker = false } } .task { observation = composeRepo.observeRequests() .start(in: DatabaseManager.shared.dbPool) { error in print("Compose observation error: \(error)") } onChange: { newRequests in withAnimation { requests = newRequests } } } } private func createEmptyRequest() { var request = ComposeRequest() try? composeRepo.insert(&request) } private func createTemplate(method: String, name: String, contentType: String? = nil) { var headers: String? if let contentType { headers = encodeHeaders([(key: "Content-Type", value: contentType)]) } var request = ComposeRequest(name: name, method: method, headers: headers) try? composeRepo.insert(&request) } private func formatTimestamp(_ ts: Double) -> String { let date = Date(timeIntervalSince1970: ts) let formatter = RelativeDateTimeFormatter() formatter.unitsStyle = .abbreviated return "Sent \(formatter.localizedString(for: date, relativeTo: Date()))" } private func encodeHeaders(_ headers: [(key: String, value: String)]) -> String? { var dict: [String: String] = [:] for h in headers { dict[h.key] = h.value } guard let data = try? JSONEncoder().encode(dict) else { return nil } return String(data: data, encoding: .utf8) } }