photo in edit profile
This commit is contained in:
parent
aa157031a1
commit
0ea5fbf996
@ -5,6 +5,7 @@ struct ProfileDataPayload: Decodable {
|
||||
let login: String
|
||||
let fullName: String?
|
||||
let bio: String?
|
||||
let avatars: Avatars?
|
||||
let balances: [WalletBalancePayload]
|
||||
let createdAt: Date?
|
||||
let stories: [JSONValue]
|
||||
@ -15,6 +16,7 @@ struct ProfileDataPayload: Decodable {
|
||||
case login
|
||||
case fullName
|
||||
case bio
|
||||
case avatars
|
||||
case balances
|
||||
case createdAt
|
||||
case stories
|
||||
@ -27,6 +29,7 @@ struct ProfileDataPayload: Decodable {
|
||||
self.login = try container.decode(String.self, forKey: .login)
|
||||
self.fullName = try container.decodeIfPresent(String.self, forKey: .fullName)
|
||||
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.createdAt = try container.decodeIfPresent(Date.self, forKey: .createdAt)
|
||||
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 {
|
||||
let currency: String
|
||||
let balance: Decimal
|
||||
|
||||
@ -1,80 +1,144 @@
|
||||
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 {
|
||||
Form {
|
||||
Section {
|
||||
HStack {
|
||||
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))
|
||||
}
|
||||
}
|
||||
ZStack {
|
||||
Form {
|
||||
Section {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("\(description.count) / \(descriptionLimit)")
|
||||
.font(.caption)
|
||||
.foregroundColor(description.count > descriptionLimit ? .red : .secondary)
|
||||
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))
|
||||
|
||||
Button(action: {
|
||||
// Действие для сохранения профиля
|
||||
print("DisplayName: \(displayName)")
|
||||
print("Description: \(description)")
|
||||
}) {
|
||||
Text("Применить")
|
||||
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)
|
||||
}
|
||||
}
|
||||
.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user