import SwiftUI /// `profile set` — always the caller's own. Description and website are /// single lines; about is long-form prose in whichever format the /// profile already carries, since switching formats reinterprets text /// that was already written; links are capped at five, matching the /// server. struct ProfileEditSheet: View { let model: ProfileViewModel let profile: ProfileViewModel.Profile @Environment(\.dismiss) private var dismiss @State private var edit: ProfileEdit init(model: ProfileViewModel, profile: ProfileViewModel.Profile) { self.model = model self.profile = profile _edit = State(initialValue: ProfileEdit(from: profile)) } private var localError: String? { edit.validationError } var body: some View { NavigationStack { Form { Section("Description") { TextField("Description", text: $edit.description) .accessibilityIdentifier("profile-edit-description") } Section("Website") { TextField("https://example.com", text: $edit.website) .keyboardType(.URL) .autocorrectionDisabled() .textInputAutocapitalization(.never) .accessibilityIdentifier("profile-edit-website") } Section { Picker("Format", selection: $edit.aboutFormat) { Text("Markdown").tag("md") Text("Org").tag("org") } .pickerStyle(.segmented) .accessibilityIdentifier("profile-edit-format") TextEditor(text: $edit.about) .font(.gbMono(.caption)) .frame(minHeight: 160) .autocorrectionDisabled() .accessibilityIdentifier("profile-edit-about") } header: { Text("About") } footer: { Text("Changing the format reinterprets text that is already written.") } linksSection if let error = localError ?? model.actionError { Section { GBNotice(error, .gbWarn) } } } .navigationTitle("Edit Profile") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .confirmationAction) { if model.working { ProgressView() } else { Button("Save") { save() } .disabled(localError != nil) .accessibilityIdentifier("profile-edit-save") } } } .interactiveDismissDisabled(model.working) } } private var linksSection: some View { Section { ForEach($edit.links) { $link in let index = edit.links.firstIndex { $0.id == link.id } ?? 0 VStack(alignment: .leading, spacing: 4) { TextField("Label", text: $link.label) .autocorrectionDisabled() .accessibilityIdentifier("profile-edit-link-label-\(index)") TextField("https://example.com", text: $link.url) .keyboardType(.URL) .autocorrectionDisabled() .textInputAutocapitalization(.never) .accessibilityIdentifier("profile-edit-link-url-\(index)") } } .onDelete { edit.links.remove(atOffsets: $0) } if edit.links.count < 5 { Button { edit.links.append(.init(label: "", url: "")) } label: { Label("Add Link", systemImage: "plus.circle") } .accessibilityIdentifier("profile-edit-add-link") } } header: { Text("Links") } footer: { Text("Up to 5 links.") } } private func save() { Task { if await model.saveProfile(edit) { dismiss() } } } }