photo in edit profile

This commit is contained in:
cheykrym 2025-12-09 23:45:59 +03:00
parent aa157031a1
commit 0ea5fbf996
2 changed files with 138 additions and 58 deletions

View File

@ -5,6 +5,7 @@ struct ProfileDataPayload: Decodable {
let login: String let login: String
let fullName: String? let fullName: String?
let bio: String? let bio: String?
let avatars: Avatars?
let balances: [WalletBalancePayload] let balances: [WalletBalancePayload]
let createdAt: Date? let createdAt: Date?
let stories: [JSONValue] let stories: [JSONValue]
@ -15,6 +16,7 @@ struct ProfileDataPayload: Decodable {
case login case login
case fullName case fullName
case bio case bio
case avatars
case balances case balances
case createdAt case createdAt
case stories case stories
@ -27,6 +29,7 @@ struct ProfileDataPayload: Decodable {
self.login = try container.decode(String.self, forKey: .login) self.login = try container.decode(String.self, forKey: .login)
self.fullName = try container.decodeIfPresent(String.self, forKey: .fullName) self.fullName = try container.decodeIfPresent(String.self, forKey: .fullName)
self.bio = try container.decodeIfPresent(String.self, forKey: .bio) self.bio = try container.decodeIfPresent(String.self, forKey: .bio)
self.avatars = try container.decodeIfPresent(Avatars.self, forKey: .avatars)
self.balances = try container.decodeIfPresent([WalletBalancePayload].self, forKey: .balances) ?? [] self.balances = try container.decodeIfPresent([WalletBalancePayload].self, forKey: .balances) ?? []
self.createdAt = try container.decodeIfPresent(Date.self, forKey: .createdAt) self.createdAt = try container.decodeIfPresent(Date.self, forKey: .createdAt)
self.stories = try container.decodeIfPresent([JSONValue].self, forKey: .stories) ?? [] self.stories = try container.decodeIfPresent([JSONValue].self, forKey: .stories) ?? []
@ -34,6 +37,19 @@ struct ProfileDataPayload: Decodable {
} }
} }
//struct AvatarInfo: Decodable {
// let fileId: String
// let mime: String?
// let size: Int?
// let width: Int?
// let height: Int?
// let createdAt: Date?
//}
//
//struct Avatars: Decodable {
// let current: AvatarInfo?
//}
struct WalletBalancePayload: Decodable { struct WalletBalancePayload: Decodable {
let currency: String let currency: String
let balance: Decimal let balance: Decimal

View File

@ -1,80 +1,144 @@
import SwiftUI import SwiftUI
struct EditProfileView: View { struct EditProfileView: View {
// State for form fields
@State private var displayName = "" @State private var displayName = ""
@State private var description = "" @State private var description = ""
// State for profile data and avatar
@State private var profile: ProfileDataPayload?
@State private var avatarImage: UIImage? @State private var avatarImage: UIImage?
@State private var showImagePicker = false @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 private let descriptionLimit = 1024
var body: some View { var body: some View {
Form { ZStack {
Section { Form {
HStack { Section {
Spacer()
VStack {
if let image = avatarImage {
Image(uiImage: image)
.resizable()
.scaledToFill()
.frame(width: 120, height: 120)
.clipShape(Circle())
} else {
Circle()
.fill(Color.secondary.opacity(0.2))
.frame(width: 120, height: 120)
.overlay(
Image(systemName: "person.fill")
.font(.system(size: 60))
.foregroundColor(.gray)
)
}
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 { HStack {
Spacer() Spacer()
Text("\(description.count) / \(descriptionLimit)") VStack {
.font(.caption) if let image = avatarImage {
.foregroundColor(description.count > descriptionLimit ? .red : .secondary) 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)
} }
Button(action: { if isLoading {
// Действие для сохранения профиля Color.black.opacity(0.4).ignoresSafeArea()
print("DisplayName: \(displayName)") ProgressView("Загрузка...")
print("Description: \(description)") .padding()
}) { .background(Color.secondary.colorInvert())
Text("Применить") .cornerRadius(10)
} }
} }
.navigationTitle("Редактировать профиль") }
.sheet(isPresented: $showImagePicker) {
ImagePicker(image: $avatarImage) 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
}
}
} }
} }
} }