From 2eabbd59c3519a81deae8169ff3ab3b0740fc758 Mon Sep 17 00:00:00 2001 From: cheykrym Date: Thu, 23 Oct 2025 22:37:23 +0300 Subject: [PATCH] add delete user from blacklist --- yobble/Network/BlockedUsersService.swift | 61 +++++++++++++++++ yobble/Resources/Localizable.xcstrings | 7 +- .../Views/Tab/Settings/BlockedUsersView.swift | 67 +++++++++++++++---- 3 files changed, 120 insertions(+), 15 deletions(-) diff --git a/yobble/Network/BlockedUsersService.swift b/yobble/Network/BlockedUsersService.swift index 5e11cea..1875644 100644 --- a/yobble/Network/BlockedUsersService.swift +++ b/yobble/Network/BlockedUsersService.swift @@ -3,6 +3,7 @@ import Foundation enum BlockedUsersServiceError: LocalizedError { case unexpectedStatus(String) case decoding(debugDescription: String) + case encoding(String) var errorDescription: String? { switch self { @@ -12,6 +13,8 @@ enum BlockedUsersServiceError: LocalizedError { return AppConfig.DEBUG ? debugDescription : NSLocalizedString("Не удалось загрузить список.", comment: "Blocked users service decoding error") + case .encoding(let message): + return message } } } @@ -78,6 +81,60 @@ final class BlockedUsersService { } } + func remove(userId: UUID, completion: @escaping (Result) -> Void) { + let request = BlockedUserDeleteRequest(userId: userId) + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + + guard let body = try? encoder.encode(request) else { + let message = NSLocalizedString("Не удалось подготовить данные запроса.", comment: "Blocked users delete encoding error") + completion(.failure(BlockedUsersServiceError.encoding(message))) + return + } + + client.request( + path: "/v1/user/blacklist/remove", + method: .delete, + body: body, + requiresAuth: true + ) { [decoder] result in + switch result { + case .success(let response): + do { + let apiResponse = try decoder.decode(APIResponse.self, from: response.data) + guard apiResponse.status == "fine" else { + let message = apiResponse.detail ?? NSLocalizedString("Не удалось удалить пользователя из списка.", comment: "Blocked users delete unexpected status") + completion(.failure(BlockedUsersServiceError.unexpectedStatus(message))) + return + } + completion(.success(apiResponse.data.message)) + } catch { + let debugMessage = Self.describeDecodingError(error: error, data: response.data) + if AppConfig.DEBUG { + print("[BlockedUsersService] decode delete response failed: \(debugMessage)") + } + completion(.failure(BlockedUsersServiceError.decoding(debugDescription: debugMessage))) + } + case .failure(let error): + if case let NetworkError.server(_, data) = error, + let data, + let message = Self.errorMessage(from: data) { + completion(.failure(BlockedUsersServiceError.unexpectedStatus(message))) + return + } + completion(.failure(error)) + } + } + } + + func remove(userId: UUID) async throws -> String { + try await withCheckedThrowingContinuation { continuation in + remove(userId: userId) { result in + continuation.resume(with: result) + } + } + } + private static func decodeDate(from decoder: Decoder) throws -> Date { let container = try decoder.singleValueContainer() let string = try container.decode(String.self) @@ -170,3 +227,7 @@ final class BlockedUsersService { return formatter }() } + +private struct BlockedUserDeleteRequest: Encodable { + let userId: UUID +} diff --git a/yobble/Resources/Localizable.xcstrings b/yobble/Resources/Localizable.xcstrings index ce1b9e1..1322eca 100644 --- a/yobble/Resources/Localizable.xcstrings +++ b/yobble/Resources/Localizable.xcstrings @@ -993,7 +993,7 @@ }, "Не удалось подготовить данные запроса." : { - "comment" : "Profile update encoding error" + "comment" : "Blocked users delete encoding error\nProfile update encoding error" }, "Не удалось сериализовать данные запроса." : { "localizations" : { @@ -1011,6 +1011,9 @@ "Не удалось сохранить изменения профиля." : { "comment" : "Profile update unexpected status" }, + "Не удалось удалить пользователя из списка." : { + "comment" : "Blocked users delete unexpected status" + }, "Неверный запрос (400)." : { "localizations" : { "en" : { @@ -1323,7 +1326,7 @@ } }, "Ошибка" : { - "comment" : "Profile update error title", + "comment" : "Common error title\nProfile update error title", "localizations" : { "en" : { "stringUnit" : { diff --git a/yobble/Views/Tab/Settings/BlockedUsersView.swift b/yobble/Views/Tab/Settings/BlockedUsersView.swift index 3e6dca2..d30dee5 100644 --- a/yobble/Views/Tab/Settings/BlockedUsersView.swift +++ b/yobble/Views/Tab/Settings/BlockedUsersView.swift @@ -4,9 +4,10 @@ struct BlockedUsersView: View { @State private var blockedUsers: [BlockedUser] = [] @State private var isLoading = false @State private var loadError: String? - @State private var showAddBlockedUserAlert = false @State private var pendingUnblock: BlockedUser? @State private var showUnblockConfirmation = false + @State private var removingUserIds: Set = [] + @State private var activeAlert: ActiveAlert? private let blockedUsersService = BlockedUsersService() @@ -49,6 +50,7 @@ struct BlockedUsersView: View { } label: { Label(NSLocalizedString("Разблокировать", comment: ""), systemImage: "person.crop.circle.badge.xmark") } + .disabled(removingUserIds.contains(user.id)) } } } @@ -59,7 +61,7 @@ struct BlockedUsersView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { - showAddBlockedUserAlert = true + activeAlert = .addPlaceholder } label: { Image(systemName: "plus") } @@ -71,10 +73,21 @@ struct BlockedUsersView: View { .refreshable { await loadBlockedUsers() } - .alert(NSLocalizedString("Скоро", comment: "Add blocked user placeholder title"), isPresented: $showAddBlockedUserAlert) { - Button(NSLocalizedString("OK", comment: "Common OK"), role: .cancel) {} - } message: { - Text(NSLocalizedString("Добавление новых блокировок появится позже.", comment: "Add blocked user placeholder message")) + .alert(item: $activeAlert) { alert in + switch alert { + case .addPlaceholder: + return Alert( + title: Text(NSLocalizedString("Скоро", comment: "Add blocked user placeholder title")), + message: Text(NSLocalizedString("Добавление новых блокировок появится позже.", comment: "Add blocked user placeholder message")), + dismissButton: .default(Text(NSLocalizedString("OK", comment: "Common OK"))) + ) + case .error(_, let message): + return Alert( + title: Text(NSLocalizedString("Ошибка", comment: "Common error title")), + message: Text(message), + dismissButton: .default(Text(NSLocalizedString("OK", comment: "Common OK"))) + ) + } } .confirmationDialog( NSLocalizedString("Удалить из заблокированных?", comment: "Unblock confirmation title"), @@ -82,11 +95,15 @@ struct BlockedUsersView: View { presenting: pendingUnblock ) { user in Button(NSLocalizedString("Разблокировать", comment: "Unblock confirmation action"), role: .destructive) { - unblock(user) pendingUnblock = nil + showUnblockConfirmation = false + Task { + await unblock(user) + } } Button(NSLocalizedString("Отмена", comment: "Common cancel"), role: .cancel) { pendingUnblock = nil + showUnblockConfirmation = false } } message: { user in Text(String(format: NSLocalizedString("Пользователь \"%1$@\" будет удалён из списка заблокированных.", comment: "Unblock confirmation message"), user.displayName)) @@ -141,17 +158,27 @@ struct BlockedUsersView: View { blockedUsers = payloads.map(BlockedUser.init) } catch { loadError = error.localizedDescription - if AppConfig.DEBUG { - print("[BlockedUsersView] load blocked users failed: \(error)") - } + activeAlert = .error(message: error.localizedDescription) + if AppConfig.DEBUG { print("[BlockedUsersView] load blocked users failed: \(error)") } } isLoading = false } - private func unblock(_ user: BlockedUser) { - // TODO: implement unblock logic when backend is ready - blockedUsers.removeAll { $0.id == user.id } + @MainActor + private func unblock(_ user: BlockedUser) async { + guard !removingUserIds.contains(user.id) else { return } + + removingUserIds.insert(user.id) + defer { removingUserIds.remove(user.id) } + + do { + _ = try await blockedUsersService.remove(userId: user.id) + blockedUsers.removeAll { $0.id == user.id } + } catch { + activeAlert = .error(message: error.localizedDescription) + if AppConfig.DEBUG { print("[BlockedUsersView] unblock failed: \(error)") } + } } } @@ -206,3 +233,17 @@ private struct BlockedUser: Identifiable, Equatable { } } } + +private enum ActiveAlert: Identifiable { + case addPlaceholder + case error(id: UUID = UUID(), message: String) + + var id: String { + switch self { + case .addPlaceholder: + return "addPlaceholder" + case .error(let id, _): + return id.uuidString + } + } +}