Files
andros d26b9ab3b6 Add boost, poll, migration, pinned posts, edit profile, follow/unfollow, groups, visibility badge, mood, scheduled posts
- Library: NewPostOptions factories for boost/poll/vote/migration/reaction; PostWriter emits new properties; ProfileWriter for header manipulation; OrgSocialPollVote model; RelayClient.fetchPollVotes/fetchGroupList/fetchGroupPostURLs
- Tests: ProfileWriterTests (14), SpecialPostTests (17) — 117 total, all passing
- App: ComposeView/ViewModel support poll options, poll end date, scheduled posts; PostRowView shows boost confirm, poll with vote bars, reaction/migration/visibility badges, mood; BoostViewModel, EditProfileViewModel with async load, GroupsViewModel; EditProfileView, GroupsView with group post list; ProfileView toolbar with follow/unfollow and edit button, pinned post section; full uploader support across all views; Groups tab in RootView
2026-04-19 18:46:01 +02:00

85 lines
3.1 KiB
Swift

import SwiftUI
struct EditProfileView: View {
@Environment(\.dismiss) private var dismiss
@State private var viewModel = EditProfileViewModel()
var body: some View {
NavigationStack {
Form {
Section("Identity") {
field("Nick", placeholder: "@username", text: $viewModel.nick)
field("Name / Title", placeholder: "My Feed", text: $viewModel.title)
field("Bio", placeholder: "A short description", text: $viewModel.description)
}
Section("Appearance") {
field("Avatar URL", placeholder: "https://example.com/avatar.jpg", text: $viewModel.avatar)
}
Section("Info") {
field("Location", placeholder: "City, Country", text: $viewModel.location)
field("Birthday", placeholder: "YYYY-MM-DD", text: $viewModel.birthday)
field("Languages", placeholder: "en es", text: $viewModel.language)
}
Section {
TextEditor(text: $viewModel.linksText)
.frame(minHeight: 60)
} header: {
Text("Links (one URL per line)")
}
Section {
TextEditor(text: $viewModel.contactsText)
.frame(minHeight: 60)
} header: {
Text("Contacts (one per line, e.g. mailto:)")
}
if let error = viewModel.errorMessage {
Section {
Label(error, systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.red).font(.subheadline)
}
}
}
.navigationTitle("Edit Profile")
.navigationBarTitleDisplayMode(.inline)
.task { await viewModel.load() }
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
if viewModel.isSaving {
ProgressView()
} else {
Button("Save") {
Task {
await viewModel.save()
if viewModel.saved { dismiss() }
}
}
.fontWeight(.semibold)
.disabled(viewModel.nick.isEmpty || viewModel.title.isEmpty)
}
}
}
}
}
private func field(_ label: String, placeholder: String, text: Binding<String>) -> some View {
HStack {
Text(label)
Spacer()
TextField(placeholder, text: text)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.multilineTextAlignment(.trailing)
.foregroundStyle(.secondary)
.frame(maxWidth: 220)
}
}
}