add contact add
This commit is contained in:
parent
c2177278e2
commit
a1446ec8bf
@ -331,6 +331,20 @@ struct RelationshipStatus: Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
extension RelationshipStatus {
|
||||
init(
|
||||
isTargetInContactsOfCurrentUser: Bool,
|
||||
isCurrentUserInContactsOfTarget: Bool,
|
||||
isTargetUserBlockedByCurrentUser: Bool,
|
||||
isCurrentUserInBlacklistOfTarget: Bool
|
||||
) {
|
||||
self.isTargetInContactsOfCurrentUser = isTargetInContactsOfCurrentUser
|
||||
self.isCurrentUserInContactsOfTarget = isCurrentUserInContactsOfTarget
|
||||
self.isTargetUserBlockedByCurrentUser = isTargetUserBlockedByCurrentUser
|
||||
self.isCurrentUserInBlacklistOfTarget = isCurrentUserInBlacklistOfTarget
|
||||
}
|
||||
}
|
||||
|
||||
enum JSONValue: Decodable {
|
||||
case string(String)
|
||||
case int(Int)
|
||||
|
||||
@ -3,6 +3,7 @@ import Foundation
|
||||
enum ContactsServiceError: LocalizedError {
|
||||
case unexpectedStatus(String)
|
||||
case decoding(debugDescription: String)
|
||||
case encoding(String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
@ -12,6 +13,8 @@ enum ContactsServiceError: LocalizedError {
|
||||
return AppConfig.DEBUG
|
||||
? debugDescription
|
||||
: NSLocalizedString("Не удалось загрузить контакты.", comment: "Contacts service decoding error")
|
||||
case .encoding(let message):
|
||||
return message
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,15 +33,26 @@ struct ContactsListPayload: Decodable {
|
||||
let hasMore: Bool
|
||||
}
|
||||
|
||||
private struct ContactCreateRequestPayload: Encodable {
|
||||
let userId: UUID?
|
||||
let login: String?
|
||||
let friendCode: String?
|
||||
let customName: String?
|
||||
}
|
||||
|
||||
final class ContactsService {
|
||||
private let client: NetworkClient
|
||||
private let decoder: JSONDecoder
|
||||
private let encoder: JSONEncoder
|
||||
|
||||
init(client: NetworkClient = .shared) {
|
||||
self.client = client
|
||||
self.decoder = JSONDecoder()
|
||||
self.decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
self.decoder.dateDecodingStrategy = .custom(Self.decodeDate)
|
||||
|
||||
self.encoder = JSONEncoder()
|
||||
self.encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||
}
|
||||
|
||||
func fetchContacts(limit: Int, offset: Int, completion: @escaping (Result<ContactsListPayload, Error>) -> Void) {
|
||||
@ -88,6 +102,63 @@ final class ContactsService {
|
||||
}
|
||||
}
|
||||
|
||||
func addContact(userId: UUID, customName: String?, completion: @escaping (Result<ContactPayload, Error>) -> Void) {
|
||||
let request = ContactCreateRequestPayload(
|
||||
userId: userId,
|
||||
login: nil,
|
||||
friendCode: nil,
|
||||
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/add",
|
||||
method: .post,
|
||||
body: body,
|
||||
requiresAuth: true
|
||||
) { [decoder] result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
do {
|
||||
let apiResponse = try decoder.decode(APIResponse<ContactPayload>.self, from: response.data)
|
||||
guard apiResponse.status == "fine" else {
|
||||
let message = apiResponse.detail ?? NSLocalizedString("Не удалось добавить контакт.", comment: "Contacts service add unexpected status")
|
||||
completion(.failure(ContactsServiceError.unexpectedStatus(message)))
|
||||
return
|
||||
}
|
||||
completion(.success(apiResponse.data))
|
||||
} catch {
|
||||
let debugMessage = Self.describeDecodingError(error: error, data: response.data)
|
||||
if AppConfig.DEBUG {
|
||||
print("[ContactsService] decode contact add 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 addContact(userId: UUID, customName: String?) async throws -> ContactPayload {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
addContact(userId: userId, customName: customName) { 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)
|
||||
|
||||
@ -581,9 +581,6 @@
|
||||
"Добавить контакт" : {
|
||||
"comment" : "Message profile add contact alert title"
|
||||
},
|
||||
"Добавление контакта появится позже." : {
|
||||
"comment" : "Contact add placeholder message"
|
||||
},
|
||||
"Добавление новых блокировок появится позже." : {
|
||||
"comment" : "Add blocked user placeholder message"
|
||||
},
|
||||
@ -856,6 +853,9 @@
|
||||
},
|
||||
"Имя в чате" : {
|
||||
|
||||
},
|
||||
"Имя не может быть пустым." : {
|
||||
"comment" : "Contact add empty name error"
|
||||
},
|
||||
"Имя, логин и статус — как в профиле Telegram." : {
|
||||
"comment" : "Message profile about description"
|
||||
@ -1337,6 +1337,9 @@
|
||||
"Не удалось выполнить поиск." : {
|
||||
"comment" : "Search error fallback\nSearch service decoding error"
|
||||
},
|
||||
"Не удалось добавить контакт." : {
|
||||
"comment" : "Contacts service add unexpected status"
|
||||
},
|
||||
"Не удалось заблокировать пользователя." : {
|
||||
"comment" : "Blocked users create unexpected status"
|
||||
},
|
||||
@ -1441,6 +1444,9 @@
|
||||
"Не удалось определить пользователя для блокировки." : {
|
||||
"comment" : "Message profile missing user id error"
|
||||
},
|
||||
"Не удалось определить пользователя для добавления." : {
|
||||
"comment" : "Contact add invalid user id error"
|
||||
},
|
||||
"Не удалось открыть чат" : {
|
||||
"comment" : "Chat creation error title"
|
||||
},
|
||||
@ -1454,7 +1460,7 @@
|
||||
|
||||
},
|
||||
"Не удалось подготовить данные запроса." : {
|
||||
"comment" : "Blocked users create encoding error\nBlocked users delete encoding error\nProfile update encoding error"
|
||||
"comment" : "Blocked users create encoding error\nBlocked users delete encoding error\nContacts service encoding error\nProfile update encoding error"
|
||||
},
|
||||
"Не удалось подготовить изображение для загрузки." : {
|
||||
"comment" : "Avatar encoding error"
|
||||
|
||||
@ -289,7 +289,9 @@ struct MessageProfileView: View {
|
||||
rowDivider
|
||||
if let profile = currentChatProfile {
|
||||
NavigationLink {
|
||||
ContactAddView(contact: ContactEditInfo(profile: profile))
|
||||
ContactAddView(contact: ContactEditInfo(profile: profile)) { payload in
|
||||
handleContactAdded(payload)
|
||||
}
|
||||
} label: {
|
||||
filledActionLabel(
|
||||
title: NSLocalizedString("Добавить в контакты", comment: "Message profile add to contacts title"),
|
||||
@ -563,6 +565,37 @@ struct MessageProfileView: View {
|
||||
)
|
||||
}
|
||||
|
||||
private func handleContactAdded(_ payload: ContactPayload) {
|
||||
guard let profile = currentChatProfile else { return }
|
||||
|
||||
let existingRelationship = profile.relationship
|
||||
let updatedRelationship = RelationshipStatus(
|
||||
isTargetInContactsOfCurrentUser: true,
|
||||
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: payload.customName ?? profile.customName,
|
||||
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() {
|
||||
showPlaceholderAction(
|
||||
title: NSLocalizedString("Ошибка", comment: "Common error title"),
|
||||
|
||||
@ -2,12 +2,18 @@ import SwiftUI
|
||||
|
||||
struct ContactAddView: View {
|
||||
let contact: ContactEditInfo
|
||||
let onContactAdded: ((ContactPayload) -> Void)?
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
private let contactsService = ContactsService()
|
||||
|
||||
@State private var displayName: String
|
||||
@State private var activeAlert: ContactAddAlert?
|
||||
@State private var isSaving = false
|
||||
|
||||
init(contact: ContactEditInfo) {
|
||||
init(contact: ContactEditInfo, onContactAdded: ((ContactPayload) -> Void)? = nil) {
|
||||
self.contact = contact
|
||||
self.onContactAdded = onContactAdded
|
||||
let initialName = contact.preferredName
|
||||
_displayName = State(initialValue: initialName)
|
||||
}
|
||||
@ -18,16 +24,21 @@ struct ContactAddView: View {
|
||||
|
||||
Section(header: Text(NSLocalizedString("Публичная информация", comment: "Contact add public info section title"))) {
|
||||
TextField(NSLocalizedString("Отображаемое имя", comment: "Display name field placeholder"), text: $displayName)
|
||||
.disabled(isSaving)
|
||||
}
|
||||
}
|
||||
.navigationTitle(NSLocalizedString("Новый контакт", comment: "Contact add title"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(NSLocalizedString("Сохранить", comment: "Contact add save button")) {
|
||||
handleSaveTap()
|
||||
if isSaving {
|
||||
ProgressView()
|
||||
} else {
|
||||
Button(NSLocalizedString("Сохранить", comment: "Contact add save button")) {
|
||||
handleSaveTap()
|
||||
}
|
||||
.disabled(!hasChanges)
|
||||
}
|
||||
.disabled(!hasChanges)
|
||||
}
|
||||
}
|
||||
.alert(item: $activeAlert) { item in
|
||||
@ -116,10 +127,46 @@ struct ContactAddView: View {
|
||||
}
|
||||
|
||||
private func handleSaveTap() {
|
||||
activeAlert = ContactAddAlert(
|
||||
title: NSLocalizedString("Скоро", comment: "Common soon title"),
|
||||
message: NSLocalizedString("Добавление контакта появится позже.", comment: "Contact add placeholder message")
|
||||
)
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user