gitbay/Views/Discovery/ProfileEditSheet.swift

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

100 lines · 3681 bytes

  1import SwiftUI
  2
  3/// `profile set`, or `org profile` for an organization. Description and
  4/// website are single lines; links are capped at five, matching the
  5/// server. The about text is a file and is edited from the profile
  6/// screen, not here.
  7struct ProfileEditSheet: View {
  8
  9    let model: ProfileViewModel
 10    let profile: ProfileViewModel.Profile
 11
 12    @Environment(\.dismiss) private var dismiss
 13    @State private var edit: ProfileEdit
 14
 15    init(model: ProfileViewModel, profile: ProfileViewModel.Profile) {
 16        self.model = model
 17        self.profile = profile
 18        _edit = State(initialValue: ProfileEdit(from: profile))
 19    }
 20
 21    private var localError: String? { edit.validationError }
 22
 23    var body: some View {
 24        NavigationStack {
 25            Form {
 26                Section("Description") {
 27                    TextField("Description", text: $edit.description)
 28                        .accessibilityIdentifier("profile-edit-description")
 29                }
 30                Section("Website") {
 31                    TextField("https://example.com", text: $edit.website)
 32                        .keyboardType(.URL)
 33                        .autocorrectionDisabled()
 34                        .textInputAutocapitalization(.never)
 35                        .accessibilityIdentifier("profile-edit-website")
 36                }
 37                linksSection
 38                if let error = localError ?? model.actionError {
 39                    Section {
 40                        GBNotice(error, .gbWarn)
 41                    }
 42                }
 43            }
 44            .navigationTitle("Edit Profile")
 45            .navigationBarTitleDisplayMode(.inline)
 46            .toolbar {
 47                ToolbarItem(placement: .cancellationAction) {
 48                    Button("Cancel") { dismiss() }
 49                }
 50                ToolbarItem(placement: .confirmationAction) {
 51                    if model.working {
 52                        ProgressView()
 53                    } else {
 54                        Button("Save") { save() }
 55                            .disabled(localError != nil)
 56                            .accessibilityIdentifier("profile-edit-save")
 57                    }
 58                }
 59            }
 60            .interactiveDismissDisabled(model.working)
 61        }
 62    }
 63
 64    private var linksSection: some View {
 65        Section {
 66            ForEach($edit.links) { $link in
 67                let index = edit.links.firstIndex { $0.id == link.id } ?? 0
 68                VStack(alignment: .leading, spacing: 4) {
 69                    TextField("Label", text: $link.label)
 70                        .autocorrectionDisabled()
 71                        .accessibilityIdentifier("profile-edit-link-label-\(index)")
 72                    TextField("https://example.com", text: $link.url)
 73                        .keyboardType(.URL)
 74                        .autocorrectionDisabled()
 75                        .textInputAutocapitalization(.never)
 76                        .accessibilityIdentifier("profile-edit-link-url-\(index)")
 77                }
 78            }
 79            .onDelete { edit.links.remove(atOffsets: $0) }
 80            if edit.links.count < 5 {
 81                Button {
 82                    edit.links.append(.init(label: "", url: ""))
 83                } label: {
 84                    Label("Add Link", systemImage: "plus.circle")
 85                }
 86                .accessibilityIdentifier("profile-edit-add-link")
 87            }
 88        } header: {
 89            Text("Links")
 90        } footer: {
 91            Text("Up to 5 links.")
 92        }
 93    }
 94
 95    private func save() {
 96        Task {
 97            if await model.saveProfile(edit) { dismiss() }
 98        }
 99    }
100}