add delete contact

This commit is contained in:
cheykrym 2025-12-11 04:53:42 +03:00
parent a1446ec8bf
commit bb08452ff9
4 changed files with 185 additions and 8 deletions

View File

@ -40,6 +40,10 @@ private struct ContactCreateRequestPayload: Encodable {
let customName: String? let customName: String?
} }
private struct ContactDeleteRequestPayload: Encodable {
let userId: UUID
}
final class ContactsService { final class ContactsService {
private let client: NetworkClient private let client: NetworkClient
private let decoder: JSONDecoder private let decoder: JSONDecoder
@ -159,6 +163,58 @@ final class ContactsService {
} }
} }
func removeContact(userId: UUID, completion: @escaping (Result<Void, Error>) -> Void) {
let request = ContactDeleteRequestPayload(userId: userId)
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/remove",
method: .delete,
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 delete 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 delete 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 removeContact(userId: UUID) async throws {
try await withCheckedThrowingContinuation { continuation in
removeContact(userId: userId) { 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

@ -938,6 +938,9 @@
} }
} }
}, },
"Контакт \"%1$@\" будет удалён из списка." : {
"comment" : "Contact delete confirmation message"
},
"Контактов пока нет" : { "Контактов пока нет" : {
"comment" : "Contacts empty state title" "comment" : "Contacts empty state title"
}, },
@ -1441,6 +1444,9 @@
} }
} }
}, },
"Не удалось определить контакт." : {
"comment" : "Contact delete invalid user id error"
},
"Не удалось определить пользователя для блокировки." : { "Не удалось определить пользователя для блокировки." : {
"comment" : "Message profile missing user id error" "comment" : "Message profile missing user id error"
}, },
@ -1484,6 +1490,9 @@
"Не удалось сохранить изменения профиля." : { "Не удалось сохранить изменения профиля." : {
"comment" : "Profile update unexpected status" "comment" : "Profile update unexpected status"
}, },
"Не удалось удалить контакт." : {
"comment" : "Contacts service delete unexpected status"
},
"Не удалось удалить пользователя из списка." : { "Не удалось удалить пользователя из списка." : {
"comment" : "Blocked users delete unexpected status" "comment" : "Blocked users delete unexpected status"
}, },
@ -2998,6 +3007,7 @@
}, },
"Удаление контакта появится позже." : { "Удаление контакта появится позже." : {
"comment" : "Contact edit delete placeholder message", "comment" : "Contact edit delete placeholder message",
"extractionState" : "stale",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -3007,12 +3017,18 @@
} }
} }
}, },
"Удалить" : {
"comment" : "Contact delete confirm action"
},
"Удалить из заблокированных?" : { "Удалить из заблокированных?" : {
"comment" : "Unblock confirmation title" "comment" : "Unblock confirmation title"
}, },
"Удалить контакт" : { "Удалить контакт" : {
"comment" : "Contact edit delete action\nContacts context action delete" "comment" : "Contact edit delete action\nContacts context action delete"
}, },
"Удалить контакт?" : {
"comment" : "Contact delete confirmation title"
},
"Удалить фото" : { "Удалить фото" : {
"comment" : "Avatar delete" "comment" : "Avatar delete"
}, },
@ -3026,6 +3042,9 @@
} }
} }
}, },
"Удаляем..." : {
"comment" : "Contact delete in progress"
},
"Удалять аккаунт через %lld дн." : { "Удалять аккаунт через %lld дн." : {
"localizations" : { "localizations" : {
"en" : { "en" : {

View File

@ -48,7 +48,9 @@ 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)) {
handleContactDeleted()
}
} label: { } label: {
Text(NSLocalizedString("Изменить", comment: "Message profile edit contact button")) Text(NSLocalizedString("Изменить", comment: "Message profile edit contact button"))
} }
@ -596,6 +598,37 @@ struct MessageProfileView: View {
chatProfile = updatedProfile chatProfile = updatedProfile
} }
private func handleContactDeleted() {
guard let profile = currentChatProfile else { return }
let existingRelationship = profile.relationship
let updatedRelationship = RelationshipStatus(
isTargetInContactsOfCurrentUser: false,
isCurrentUserInContactsOfTarget: existingRelationship?.isCurrentUserInContactsOfTarget ?? false,
isTargetUserBlockedByCurrentUser: existingRelationship?.isTargetUserBlockedByCurrentUser ?? false,
isCurrentUserInBlacklistOfTarget: existingRelationship?.isCurrentUserInBlacklistOfTarget ?? false
)
let updatedProfile = ChatProfile(
userId: profile.userId,
login: profile.login,
fullName: profile.fullName,
customName: nil,
bio: profile.bio,
lastSeen: profile.lastSeen,
createdAt: profile.createdAt,
avatars: profile.avatars,
stories: profile.stories,
permissions: profile.permissions,
profilePermissions: profile.profilePermissions,
relationship: updatedRelationship,
rating: profile.rating,
isOfficial: profile.isOfficial
)
chatProfile = updatedProfile
}
private func handleEditContactTap() { private func handleEditContactTap() {
showPlaceholderAction( showPlaceholderAction(
title: NSLocalizedString("Ошибка", comment: "Common error title"), title: NSLocalizedString("Ошибка", comment: "Common error title"),

View File

@ -55,12 +55,19 @@ struct ContactEditInfo {
struct ContactEditView: View { struct ContactEditView: View {
let contact: ContactEditInfo let contact: ContactEditInfo
let onContactDeleted: (() -> Void)?
@Environment(\.dismiss) private var dismiss
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 isDeleting = false
@State private var showDeleteConfirmation = false
init(contact: ContactEditInfo) { init(contact: ContactEditInfo, onContactDeleted: (() -> Void)? = nil) {
self.contact = contact self.contact = contact
self.onContactDeleted = onContactDeleted
let initialName = contact.preferredName let initialName = contact.preferredName
_displayName = State(initialValue: initialName) _displayName = State(initialValue: initialName)
} }
@ -77,9 +84,9 @@ struct ContactEditView: View {
Button(role: .destructive) { Button(role: .destructive) {
handleDeleteTap() handleDeleteTap()
} label: { } label: {
Text(NSLocalizedString("Удалить контакт", comment: "Contact edit delete action")) deleteButtonLabel
.frame(maxWidth: .infinity, alignment: .center)
} }
.disabled(isDeleting)
} }
} }
.navigationTitle(NSLocalizedString("Контакт", comment: "Contact edit title")) .navigationTitle(NSLocalizedString("Контакт", comment: "Contact edit title"))
@ -99,6 +106,37 @@ struct ContactEditView: View {
dismissButton: .default(Text(NSLocalizedString("Понятно", comment: "Placeholder alert dismiss"))) dismissButton: .default(Text(NSLocalizedString("Понятно", comment: "Placeholder alert dismiss")))
) )
} }
.confirmationDialog(
NSLocalizedString("Удалить контакт?", comment: "Contact delete confirmation title"),
isPresented: $showDeleteConfirmation,
titleVisibility: .visible
) {
Button(NSLocalizedString("Удалить", comment: "Contact delete confirm action"), role: .destructive) {
confirmDelete()
}
Button(NSLocalizedString("Отмена", comment: "Common cancel"), role: .cancel) {
showDeleteConfirmation = false
}
} message: {
Text(String(
format: NSLocalizedString("Контакт \"%1$@\" будет удалён из списка.", comment: "Contact delete confirmation message"),
contact.preferredName
))
}
}
@ViewBuilder
private var deleteButtonLabel: some View {
if isDeleting {
HStack(spacing: 8) {
ProgressView()
Text(NSLocalizedString("Удаляем...", comment: "Contact delete in progress"))
}
.frame(maxWidth: .infinity, alignment: .center)
} else {
Text(NSLocalizedString("Удалить контакт", comment: "Contact edit delete action"))
.frame(maxWidth: .infinity, alignment: .center)
}
} }
private var avatarSection: some View { private var avatarSection: some View {
@ -185,10 +223,41 @@ struct ContactEditView: View {
} }
private func handleDeleteTap() { private func handleDeleteTap() {
guard !isDeleting else { return }
showDeleteConfirmation = true
}
private func confirmDelete() {
guard !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: "Contact edit delete placeholder message") message: NSLocalizedString("Не удалось определить контакт.", comment: "Contact delete invalid user id error")
) )
return
}
isDeleting = true
showDeleteConfirmation = false
Task {
do {
try await contactsService.removeContact(userId: userId)
await MainActor.run {
isDeleting = false
onContactDeleted?()
dismiss()
}
} catch {
await MainActor.run {
isDeleting = false
activeAlert = ContactEditAlert(
title: NSLocalizedString("Ошибка", comment: "Common error title"),
message: error.localizedDescription
)
}
}
}
} }
} }