add update contact

This commit is contained in:
cheykrym 2025-12-11 05:04:26 +03:00
parent bb08452ff9
commit 997ddea9c4
4 changed files with 165 additions and 14 deletions

View File

@ -44,6 +44,11 @@ private struct ContactDeleteRequestPayload: Encodable {
let userId: UUID let userId: UUID
} }
private struct ContactUpdateRequestPayload: Encodable {
let userId: UUID
let customName: String?
}
final class ContactsService { final class ContactsService {
private let client: NetworkClient private let client: NetworkClient
private let decoder: JSONDecoder private let decoder: JSONDecoder
@ -215,6 +220,58 @@ final class ContactsService {
} }
} }
func updateContact(userId: UUID, customName: String?, completion: @escaping (Result<Void, Error>) -> Void) {
let request = ContactUpdateRequestPayload(userId: userId, customName: customName)
guard let body = try? encoder.encode(request) else {
let message = NSLocalizedString("Не удалось подготовить данные запроса.", comment: "Contacts service encoding error")
completion(.failure(ContactsServiceError.encoding(message)))
return
}
client.request(
path: "/v1/user/contact/update",
method: .patch,
body: body,
requiresAuth: true
) { [decoder] result in
switch result {
case .success(let response):
do {
let apiResponse = try decoder.decode(APIResponse<MessagePayload>.self, from: response.data)
guard apiResponse.status == "fine" else {
let message = apiResponse.detail ?? NSLocalizedString("Не удалось обновить контакт.", comment: "Contacts service update unexpected status")
completion(.failure(ContactsServiceError.unexpectedStatus(message)))
return
}
completion(.success(()))
} catch {
let debugMessage = Self.describeDecodingError(error: error, data: response.data)
if AppConfig.DEBUG {
print("[ContactsService] decode contact update failed: \(debugMessage)")
}
completion(.failure(ContactsServiceError.decoding(debugDescription: debugMessage)))
}
case .failure(let error):
if case let NetworkError.server(_, data) = error,
let data,
let message = Self.errorMessage(from: data) {
completion(.failure(ContactsServiceError.unexpectedStatus(message)))
return
}
completion(.failure(error))
}
}
}
func updateContact(userId: UUID, customName: String?) async throws {
try await withCheckedThrowingContinuation { continuation in
updateContact(userId: userId, customName: customName) { result in
continuation.resume(with: result)
}
}
}
private static func decodeDate(from decoder: Decoder) throws -> Date { private static func decodeDate(from decoder: Decoder) throws -> Date {
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
let string = try container.decode(String.self) let string = try container.decode(String.self)

View File

@ -853,9 +853,12 @@
}, },
"Имя в чате" : { "Имя в чате" : {
},
"Имя контакта должно быть короче 32 символов." : {
"comment" : "Contact edit name too long message"
}, },
"Имя не может быть пустым." : { "Имя не может быть пустым." : {
"comment" : "Contact add empty name error" "comment" : "Contact add empty name error\nContact edit empty name error"
}, },
"Имя, логин и статус — как в профиле Telegram." : { "Имя, логин и статус — как в профиле Telegram." : {
"comment" : "Message profile about description" "comment" : "Message profile about description"
@ -1410,6 +1413,9 @@
"Не удалось обновить аватар." : { "Не удалось обновить аватар." : {
"comment" : "Avatar upload unexpected status" "comment" : "Avatar upload unexpected status"
}, },
"Не удалось обновить контакт." : {
"comment" : "Contacts service update unexpected status"
},
"Не удалось обновить пароль." : { "Не удалось обновить пароль." : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -1445,7 +1451,7 @@
} }
}, },
"Не удалось определить контакт." : { "Не удалось определить контакт." : {
"comment" : "Contact delete invalid user id error" "comment" : "Contact delete invalid user id error\nContact edit invalid user id error"
}, },
"Не удалось определить пользователя для блокировки." : { "Не удалось определить пользователя для блокировки." : {
"comment" : "Message profile missing user id error" "comment" : "Message profile missing user id error"
@ -2599,6 +2605,7 @@
}, },
"Редактирование контакта появится позже." : { "Редактирование контакта появится позже." : {
"comment" : "Message profile edit contact alert message", "comment" : "Message profile edit contact alert message",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -2744,7 +2751,7 @@
"comment" : "Кнопка копирования кода восстановления" "comment" : "Кнопка копирования кода восстановления"
}, },
"Скоро" : { "Скоро" : {
"comment" : "Add blocked user placeholder title\nCommon soon title\nContacts placeholder title\nЗаголовок заглушки" "comment" : "Add blocked user placeholder title\nContacts placeholder title\nЗаголовок заглушки"
}, },
"Скоро можно будет искать сообщения, ссылки и файлы в этом чате." : { "Скоро можно будет искать сообщения, ссылки и файлы в этом чате." : {
"comment" : "Message profile search action description" "comment" : "Message profile search action description"
@ -2754,6 +2761,9 @@
}, },
"Скрыть" : { "Скрыть" : {
},
"Слишком длинное имя" : {
"comment" : "Contact edit name too long title"
}, },
"Слишком много запросов." : { "Слишком много запросов." : {
"localizations" : { "localizations" : {

View File

@ -48,9 +48,15 @@ struct MessageProfileView: View {
if canEditContact { if canEditContact {
if let profile = currentChatProfile { if let profile = currentChatProfile {
NavigationLink { NavigationLink {
ContactEditView(contact: ContactEditInfo(profile: profile)) { ContactEditView(
contact: ContactEditInfo(profile: profile),
onContactDeleted: {
handleContactDeleted() handleContactDeleted()
},
onContactUpdated: { newName in
handleContactUpdated(newName)
} }
)
} label: { } label: {
Text(NSLocalizedString("Изменить", comment: "Message profile edit contact button")) Text(NSLocalizedString("Изменить", comment: "Message profile edit contact button"))
} }
@ -598,6 +604,29 @@ struct MessageProfileView: View {
chatProfile = updatedProfile chatProfile = updatedProfile
} }
private func handleContactUpdated(_ newName: String) {
guard let profile = currentChatProfile else { return }
let updatedProfile = ChatProfile(
userId: profile.userId,
login: profile.login,
fullName: profile.fullName,
customName: newName,
bio: profile.bio,
lastSeen: profile.lastSeen,
createdAt: profile.createdAt,
avatars: profile.avatars,
stories: profile.stories,
permissions: profile.permissions,
profilePermissions: profile.profilePermissions,
relationship: profile.relationship,
rating: profile.rating,
isOfficial: profile.isOfficial
)
chatProfile = updatedProfile
}
private func handleContactDeleted() { private func handleContactDeleted() {
guard let profile = currentChatProfile else { return } guard let profile = currentChatProfile else { return }

View File

@ -56,18 +56,25 @@ struct ContactEditInfo {
struct ContactEditView: View { struct ContactEditView: View {
let contact: ContactEditInfo let contact: ContactEditInfo
let onContactDeleted: (() -> Void)? let onContactDeleted: (() -> Void)?
let onContactUpdated: ((String) -> Void)?
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
private let contactsService = ContactsService() private let contactsService = ContactsService()
@State private var displayName: String @State private var displayName: String
@State private var activeAlert: ContactEditAlert? @State private var activeAlert: ContactEditAlert?
@State private var isSaving = false
@State private var isDeleting = false @State private var isDeleting = false
@State private var showDeleteConfirmation = false @State private var showDeleteConfirmation = false
init(contact: ContactEditInfo, onContactDeleted: (() -> Void)? = nil) { init(
contact: ContactEditInfo,
onContactDeleted: (() -> Void)? = nil,
onContactUpdated: ((String) -> Void)? = nil
) {
self.contact = contact self.contact = contact
self.onContactDeleted = onContactDeleted self.onContactDeleted = onContactDeleted
self.onContactUpdated = onContactUpdated
let initialName = contact.preferredName let initialName = contact.preferredName
_displayName = State(initialValue: initialName) _displayName = State(initialValue: initialName)
} }
@ -78,6 +85,7 @@ struct ContactEditView: View {
Section(header: Text(NSLocalizedString("Публичная информация", comment: "Profile info section title"))) { Section(header: Text(NSLocalizedString("Публичная информация", comment: "Profile info section title"))) {
TextField(NSLocalizedString("Отображаемое имя", comment: "Display name field placeholder"), text: $displayName) TextField(NSLocalizedString("Отображаемое имя", comment: "Display name field placeholder"), text: $displayName)
.disabled(isSaving || isDeleting)
} }
Section { Section {
@ -93,10 +101,14 @@ struct ContactEditView: View {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .confirmationAction) {
if isSaving {
ProgressView()
} else {
Button(NSLocalizedString("Сохранить", comment: "Contact edit save button")) { Button(NSLocalizedString("Сохранить", comment: "Contact edit save button")) {
handleSaveTap() handleSaveTap()
} }
.disabled(!hasChanges) .disabled(!hasChanges || isDeleting)
}
} }
} }
.alert(item: $activeAlert) { item in .alert(item: $activeAlert) { item in
@ -216,10 +228,53 @@ struct ContactEditView: View {
} }
private func handleSaveTap() { private func handleSaveTap() {
guard !isSaving, !isDeleting else { return }
guard let userId = UUID(uuidString: contact.userId) else {
activeAlert = ContactEditAlert( activeAlert = ContactEditAlert(
title: NSLocalizedString("Скоро", comment: "Common soon title"), title: NSLocalizedString("Ошибка", comment: "Common error title"),
message: NSLocalizedString("Редактирование контакта появится позже.", comment: "Message profile edit contact alert message") message: NSLocalizedString("Не удалось определить контакт.", comment: "Contact edit invalid user id error")
) )
return
}
let trimmed = displayName.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else {
activeAlert = ContactEditAlert(
title: NSLocalizedString("Ошибка", comment: "Common error title"),
message: NSLocalizedString("Имя не может быть пустым.", comment: "Contact edit empty name error")
)
return
}
if trimmed.count > 32 {
activeAlert = ContactEditAlert(
title: NSLocalizedString("Слишком длинное имя", comment: "Contact edit name too long title"),
message: NSLocalizedString("Имя контакта должно быть короче 32 символов.", comment: "Contact edit name too long message")
)
return
}
isSaving = true
Task {
do {
try await contactsService.updateContact(userId: userId, customName: trimmed)
await MainActor.run {
isSaving = false
onContactUpdated?(trimmed)
dismiss()
}
} catch {
await MainActor.run {
isSaving = false
activeAlert = ContactEditAlert(
title: NSLocalizedString("Ошибка", comment: "Common error title"),
message: error.localizedDescription
)
}
}
}
} }
private func handleDeleteTap() { private func handleDeleteTap() {