ios_app_v2/yobble/Views/Contacts/ContactAddView.swift

197 lines
7.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
struct ContactAddView: View {
let contact: ContactEditInfo
let onContactAdded: ((ContactPayload) -> Void)?
@Environment(\.dismiss) private var dismiss
private let contactsService = ContactsService()
private let initialName: String
@State private var displayName: String
@State private var activeAlert: ContactAddAlert?
@State private var isSaving = false
init(contact: ContactEditInfo, onContactAdded: ((ContactPayload) -> Void)? = nil) {
self.contact = contact
self.onContactAdded = onContactAdded
// let initialName = contact.preferredName
self.initialName = contact.preferredName
_displayName = State(initialValue: "")
}
var body: some View {
Form {
avatarSection
Section(header: Text(NSLocalizedString("Публичная информация", comment: "Contact add public info section title"))) {
// TextField(NSLocalizedString("Отображаемое имя", comment: "Display name field placeholder"), text: $displayName)
TextField(NSLocalizedString("\(initialName)", comment: "Display name field placeholder"), text: $displayName)
.disabled(isSaving)
}
}
.navigationTitle(NSLocalizedString("Новый контакт", comment: "Contact add title"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
if isSaving {
ProgressView()
} else {
Button(NSLocalizedString("Сохранить", comment: "Contact add save button")) {
handleSaveTap()
}
.disabled(!hasChanges)
}
}
}
.alert(item: $activeAlert) { item in
Alert(
title: Text(item.title),
message: Text(item.message),
dismissButton: .default(Text(NSLocalizedString("Понятно", comment: "Placeholder alert dismiss")))
)
}
}
private var avatarSection: some View {
Section {
HStack {
Spacer()
VStack(spacing: 8) {
avatarView
.frame(width: 120, height: 120)
.clipShape(Circle())
Button(NSLocalizedString("Изменить фото", comment: "Edit avatar button title")) {
showAvatarUnavailableAlert()
}
}
Spacer()
}
}
.listRowBackground(Color(UIColor.systemGroupedBackground))
}
@ViewBuilder
private var avatarView: some View {
if let url = avatarURL,
let fileId = contact.avatarFileId {
CachedAvatarView(url: url, fileId: fileId, userId: contact.userId) {
avatarPlaceholder
}
.aspectRatio(contentMode: .fill)
} else {
avatarPlaceholder
}
}
private var avatarPlaceholder: some View {
Circle()
.fill(Color.accentColor.opacity(0.15))
.overlay(
Text(avatarInitial)
.font(.system(size: 48, weight: .semibold))
.foregroundColor(.accentColor)
)
}
private var avatarInitial: String {
let trimmedName = displayName.trimmedNonEmpty ?? contact.preferredName
if let initials = initials(from: trimmedName) {
return initials
}
if let login = contact.login?.trimmingCharacters(in: .whitespacesAndNewlines), !login.isEmpty {
return String(login.prefix(1)).uppercased()
}
return "?"
}
private var avatarURL: URL? {
guard let fileId = contact.avatarFileId else { return nil }
return URL(string: "\(AppConfig.API_SERVER)/v1/storage/download/avatar/\(contact.userId)?file_id=\(fileId)")
}
private var hasChanges: Bool {
let trimmed = displayName.trimmingCharacters(in: .whitespacesAndNewlines)
// guard !trimmed.isEmpty else { return false }
if let existing = contact.customName?.trimmedNonEmpty {
return trimmed != existing
}
return true
}
private func showAvatarUnavailableAlert() {
activeAlert = ContactAddAlert(
title: NSLocalizedString("Изменение фото недоступно", comment: "Contact add avatar unavailable title"),
message: NSLocalizedString("Мы пока не можем обновить фото контакта.", comment: "Contact add avatar unavailable message")
)
}
private func handleSaveTap() {
guard !isSaving else { return }
guard let userId = UUID(uuidString: contact.userId) else {
activeAlert = ContactAddAlert(
title: NSLocalizedString("Ошибка", comment: "Common error title"),
message: NSLocalizedString("Не удалось определить пользователя для добавления.", comment: "Contact add invalid user id error")
)
return
}
let trimmedName = displayName.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
// guard !trimmedName.isEmpty else {
// activeAlert = ContactAddAlert(
// title: NSLocalizedString("Ошибка", comment: "Common error title"),
// message: NSLocalizedString("Имя не может быть пустым.", comment: "Contact add empty name error")
// )
// return
// }
isSaving = true
let customName = trimmedName
Task {
do {
let payload = try await contactsService.addContact(userId: userId, customName: customName)
await MainActor.run {
isSaving = false
onContactAdded?(payload)
dismiss()
}
} catch {
await MainActor.run {
isSaving = false
activeAlert = ContactAddAlert(
title: NSLocalizedString("Ошибка", comment: "Common error title"),
message: error.localizedDescription
)
}
}
}
}
}
private struct ContactAddAlert: Identifiable {
let id = UUID()
let title: String
let message: String
}
private extension String {
var trimmedNonEmpty: String? {
let value = trimmingCharacters(in: .whitespacesAndNewlines)
return value.isEmpty ? nil : value
}
}
private func initials(from text: String) -> String? {
let components = text
.split { $0.isWhitespace }
.filter { !$0.isEmpty }
let letters = components.prefix(2).compactMap { $0.first }
guard !letters.isEmpty else { return nil }
return letters.map { String($0).uppercased() }.joined()
}