gitbay/Views/Discovery/ProfileEditSheet.swift
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}