photo in edit profile
This commit is contained in:
parent
aa157031a1
commit
0ea5fbf996
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user