gitbay/Views/Discovery/ProfileEditSheet.swift

v1.1.0
gitbay-ios/gitbay/Views/Discovery/ProfileEditSheet.swift history · blame · raw

118 lines · 4537 bytes

  1import SwiftUI
  2
  3/// `profile set` — always the caller's own. Description and website are
  4/// single lines; about is long-form prose in whichever format the
  5/// profile already carries, since switching formats reinterprets text
  6/// that was already written; links are capped at five, matching the
  7/// server.
  8struct ProfileEditSheet: View {
  9
 10    let model: ProfileViewModel
 11    let profile: ProfileViewModel.Profile
 12
 13    @Environment(\.dismiss) private var dismiss
 14    @State private var edit: ProfileEdit
 15
 16    init(model: ProfileViewModel, profile: ProfileViewModel.Profile) {
 17        self.model = model
 18        self.profile = profile
 19        _edit = State(initialValue: ProfileEdit(from: profile))
 20    }
 21
 22    private var localError: String? { edit.validationError }
 23
 24    var body: some View {
 25        NavigationStack {
 26            Form {
 27                Section("Description") {
 28                    TextField("Description", text: $edit.description)
 29                        .accessibilityIdentifier("profile-edit-description")
 30                }
 31                Section("Website") {
 32                    TextField("https://example.com", text: $edit.website)
 33                        .keyboardType(.URL)
 34                        .autocorrectionDisabled()
 35                        .textInputAutocapitalization(.never)
 36                        .accessibilityIdentifier("profile-edit-website")
 37                }
 38                Section {
 39                    Picker("Format", selection: $edit.aboutFormat) {
 40                        Text("Markdown").tag("md")
 41                        Text("Org").tag("org")
 42                    }
 43                    .pickerStyle(.segmented)
 44                    .accessibilityIdentifier("profile-edit-format")
 45                    TextEditor(text: $edit.about)
 46                        .font(.gbMono(.caption))
 47                        .frame(minHeight: 160)
 48                        .autocorrectionDisabled()
 49                        .accessibilityIdentifier("profile-edit-about")
 50                } header: {
 51                    Text("About")
 52                } footer: {
 53                    Text("Changing the format reinterprets text that is already written.")
 54                }
 55                linksSection
 56                if let error = localError ?? model.actionError {
 57                    Section {
 58                        GBNotice(error, .gbWarn)
 59                    }
 60                }
 61            }
 62            .navigationTitle("Edit Profile")
 63            .navigationBarTitleDisplayMode(.inline)
 64            .toolbar {
 65                ToolbarItem(placement: .cancellationAction) {
 66                    Button("Cancel") { dismiss() }
 67                }
 68                ToolbarItem(placement: .confirmationAction) {
 69                    if model.working {
 70                        ProgressView()
 71                    } else {
 72                        Button("Save") { save() }
 73                            .disabled(localError != nil)
 74                            .accessibilityIdentifier("profile-edit-save")
 75                    }
 76                }
 77            }
 78            .interactiveDismissDisabled(model.working)
 79        }
 80    }
 81
 82    private var linksSection: some View {
 83        Section {
 84            ForEach($edit.links) { $link in
 85                let index = edit.links.firstIndex { $0.id == link.id } ?? 0
 86                VStack(alignment: .leading, spacing: 4) {
 87                    TextField("Label", text: $link.label)
 88                        .autocorrectionDisabled()
 89                        .accessibilityIdentifier("profile-edit-link-label-\(index)")
 90                    TextField("https://example.com", text: $link.url)
 91                        .keyboardType(.URL)
 92                        .autocorrectionDisabled()
 93                        .textInputAutocapitalization(.never)
 94                        .accessibilityIdentifier("profile-edit-link-url-\(index)")
 95                }
 96            }
 97            .onDelete { edit.links.remove(atOffsets: $0) }
 98            if edit.links.count < 5 {
 99                Button {
100                    edit.links.append(.init(label: "", url: ""))
101                } label: {
102                    Label("Add Link", systemImage: "plus.circle")
103                }
104                .accessibilityIdentifier("profile-edit-add-link")
105            }
106        } header: {
107            Text("Links")
108        } footer: {
109            Text("Up to 5 links.")
110        }
111    }
112
113    private func save() {
114        Task {
115            if await model.saveProfile(edit) { dismiss() }
116        }
117    }
118}