176 lines
6.7 KiB
Swift
176 lines
6.7 KiB
Swift
import SwiftUI
|
||
|
||
struct EditProfileView: View {
|
||
// State for form fields
|
||
@State private var displayName = ""
|
||
@State private var description = ""
|
||
|
||
// State for profile data and avatar
|
||
@State private var profile: ProfileDataPayload?
|
||
@State private var avatarImage: UIImage?
|
||
@State private var showImagePicker = false
|
||
|
||
// State for loading and errors
|
||
@State private var isLoading = false
|
||
@State private var alertMessage: String?
|
||
@State private var showAlert = false
|
||
|
||
private let profileService = ProfileService()
|
||
private let descriptionLimit = 1024
|
||
|
||
var body: some View {
|
||
ZStack {
|
||
Form {
|
||
Section {
|
||
HStack {
|
||
Spacer()
|
||
VStack {
|
||
if let image = avatarImage {
|
||
Image(uiImage: image)
|
||
.resizable()
|
||
.scaledToFill()
|
||
.frame(width: 120, height: 120)
|
||
.clipShape(Circle())
|
||
} else if let profile = profile,
|
||
let fileId = profile.avatars?.current?.fileId,
|
||
let url = avatarUrl(for: profile, fileId: fileId) {
|
||
CachedAvatarView(url: url, fileId: fileId, userId: profile.userId.uuidString) {
|
||
avatarPlaceholder
|
||
}
|
||
.aspectRatio(contentMode: .fill)
|
||
.frame(width: 120, height: 120)
|
||
.clipShape(Circle())
|
||
} else {
|
||
avatarPlaceholder
|
||
}
|
||
|
||
Button("Изменить фото") {
|
||
showImagePicker = true
|
||
}
|
||
.padding(.top, 8)
|
||
}
|
||
Spacer()
|
||
}
|
||
}
|
||
.listRowBackground(Color(UIColor.systemGroupedBackground))
|
||
|
||
Section(header: Text("Публичная информация")) {
|
||
TextField("Отображаемое имя", text: $displayName)
|
||
|
||
VStack(alignment: .leading, spacing: 5) {
|
||
Text("Описание")
|
||
.font(.caption)
|
||
.foregroundColor(.secondary)
|
||
TextEditor(text: $description)
|
||
.frame(height: 150)
|
||
.onChange(of: description) { newValue in
|
||
if newValue.count > descriptionLimit {
|
||
description = String(newValue.prefix(descriptionLimit))
|
||
}
|
||
}
|
||
HStack {
|
||
Spacer()
|
||
Text("\(description.count) / \(descriptionLimit)")
|
||
.font(.caption)
|
||
.foregroundColor(description.count > descriptionLimit ? .red : .secondary)
|
||
}
|
||
}
|
||
}
|
||
|
||
Button(action: {
|
||
// Действие для сохранения профиля
|
||
print("DisplayName: \(displayName)")
|
||
print("Description: \(description)")
|
||
}) {
|
||
Text("Применить")
|
||
}
|
||
}
|
||
.navigationTitle("Редактировать профиль")
|
||
.onAppear(perform: loadProfile)
|
||
.sheet(isPresented: $showImagePicker) {
|
||
ImagePicker(image: $avatarImage)
|
||
}
|
||
.alert("Ошибка", isPresented: $showAlert, presenting: alertMessage) { _ in
|
||
Button("OK") {}
|
||
} message: { message in
|
||
Text(message)
|
||
}
|
||
|
||
if isLoading {
|
||
Color.black.opacity(0.4).ignoresSafeArea()
|
||
ProgressView("Загрузка...")
|
||
.padding()
|
||
.background(Color.secondary.colorInvert())
|
||
.cornerRadius(10)
|
||
}
|
||
}
|
||
}
|
||
|
||
private var avatarPlaceholder: some View {
|
||
Circle()
|
||
.fill(Color.secondary.opacity(0.2))
|
||
.frame(width: 120, height: 120)
|
||
.overlay(
|
||
Image(systemName: "person.fill")
|
||
.font(.system(size: 60))
|
||
.foregroundColor(.gray)
|
||
)
|
||
}
|
||
|
||
private func avatarUrl(for profile: ProfileDataPayload, fileId: String) -> URL? {
|
||
return URL(string: "\(AppConfig.API_SERVER)/v1/storage/download/avatar/\(profile.userId)?file_id=\(fileId)")
|
||
}
|
||
|
||
private func loadProfile() {
|
||
isLoading = true
|
||
Task {
|
||
do {
|
||
let profile = try await profileService.fetchMyProfile()
|
||
await MainActor.run {
|
||
self.profile = profile
|
||
self.displayName = profile.fullName ?? ""
|
||
self.description = profile.bio ?? ""
|
||
self.isLoading = false
|
||
}
|
||
} catch {
|
||
await MainActor.run {
|
||
self.alertMessage = error.localizedDescription
|
||
self.showAlert = true
|
||
self.isLoading = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
struct ImagePicker: UIViewControllerRepresentable {
|
||
@Binding var image: UIImage?
|
||
@Environment(\.presentationMode) private var presentationMode
|
||
|
||
func makeUIViewController(context: Context) -> UIImagePickerController {
|
||
let picker = UIImagePickerController()
|
||
picker.delegate = context.coordinator
|
||
return picker
|
||
}
|
||
|
||
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
|
||
|
||
func makeCoordinator() -> Coordinator {
|
||
Coordinator(self)
|
||
}
|
||
|
||
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
|
||
let parent: ImagePicker
|
||
|
||
init(_ parent: ImagePicker) {
|
||
self.parent = parent
|
||
}
|
||
|
||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
|
||
if let uiImage = info[.originalImage] as? UIImage {
|
||
parent.image = uiImage
|
||
}
|
||
parent.presentationMode.wrappedValue.dismiss()
|
||
}
|
||
}
|
||
} |