add work button in contact

This commit is contained in:
cheykrym 2025-12-13 04:55:22 +03:00
parent a66eb04489
commit 8ba51f026c
2 changed files with 185 additions and 42 deletions

View File

@ -276,9 +276,6 @@
}, },
"Биография" : { "Биография" : {
},
"Блокировка контакта \"%1$@\" появится позже." : {
"comment" : "Contacts block placeholder message"
}, },
"Больше сообщений нет" : { "Больше сообщений нет" : {
"comment" : "Chat history top reached" "comment" : "Chat history top reached"
@ -648,11 +645,14 @@
}, },
"Заблокировать" : { "Заблокировать" : {
"comment" : "Blocked users add confirm\nBlocked users add title\nMessage profile block title" "comment" : "Blocked users add confirm\nBlocked users add title\nContacts block confirm action\nMessage profile block title"
}, },
"Заблокировать контакт" : { "Заблокировать контакт" : {
"comment" : "Contacts context action block" "comment" : "Contacts context action block"
}, },
"Заблокировать контакт?" : {
"comment" : "Contacts block confirmation title"
},
"Забыли пароль? Сбросить" : { "Забыли пароль? Сбросить" : {
}, },
@ -811,9 +811,6 @@
"Избранные сообщения" : { "Избранные сообщения" : {
"comment" : "Saved messages title" "comment" : "Saved messages title"
}, },
"Изменение контакта \"%1$@\" появится позже." : {
"comment" : "Contacts edit placeholder message"
},
"Изменение пароля" : { "Изменение пароля" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -957,7 +954,7 @@
} }
}, },
"Контакт \"%1$@\" будет удалён из списка." : { "Контакт \"%1$@\" будет удалён из списка." : {
"comment" : "Contact delete confirmation message" "comment" : "Contact delete confirmation message\nContacts delete confirmation message"
}, },
"Контактов пока нет" : { "Контактов пока нет" : {
"comment" : "Contacts empty state title" "comment" : "Contacts empty state title"
@ -2241,6 +2238,9 @@
} }
} }
}, },
"Пользователь \"%1$@\" будет добавлен в чёрный список." : {
"comment" : "Contacts block confirmation message"
},
"Пользователь \"%1$@\" будет удалён из списка заблокированных." : { "Пользователь \"%1$@\" будет удалён из списка заблокированных." : {
"comment" : "Unblock confirmation message" "comment" : "Unblock confirmation message"
}, },
@ -2792,7 +2792,7 @@
"comment" : "Кнопка копирования кода восстановления" "comment" : "Кнопка копирования кода восстановления"
}, },
"Скоро" : { "Скоро" : {
"comment" : "Contacts placeholder title\nЗаголовок заглушки" "comment" : "Заголовок заглушки"
}, },
"Скоро можно будет искать сообщения, ссылки и файлы в этом чате." : { "Скоро можно будет искать сообщения, ссылки и файлы в этом чате." : {
"comment" : "Message profile search action description" "comment" : "Message profile search action description"
@ -3056,9 +3056,6 @@
"Удаление аватара пока недоступно." : { "Удаление аватара пока недоступно." : {
"comment" : "Avatar delete placeholder" "comment" : "Avatar delete placeholder"
}, },
"Удаление контакта \"%1$@\" появится позже." : {
"comment" : "Contacts delete placeholder message"
},
"Удаление контакта появится позже." : { "Удаление контакта появится позже." : {
"comment" : "Contact edit delete placeholder message", "comment" : "Contact edit delete placeholder message",
"extractionState" : "stale", "extractionState" : "stale",
@ -3072,7 +3069,7 @@
} }
}, },
"Удалить" : { "Удалить" : {
"comment" : "Contact delete confirm action" "comment" : "Contact delete confirm action\nContacts delete confirm action"
}, },
"Удалить из заблокированных?" : { "Удалить из заблокированных?" : {
"comment" : "Unblock confirmation title" "comment" : "Unblock confirmation title"
@ -3081,7 +3078,7 @@
"comment" : "Contact edit delete action\nContacts context action delete" "comment" : "Contact edit delete action\nContacts context action delete"
}, },
"Удалить контакт?" : { "Удалить контакт?" : {
"comment" : "Contact delete confirmation title" "comment" : "Contact delete confirmation title\nContacts delete confirmation title"
}, },
"Удалить фото" : { "Удалить фото" : {
"comment" : "Avatar delete" "comment" : "Avatar delete"

View File

@ -16,10 +16,18 @@ struct ContactsTab: View {
@State private var contactAvatars: [UUID: AvatarInfo] = [:] @State private var contactAvatars: [UUID: AvatarInfo] = [:]
@State private var avatarLoadedIds: Set<UUID> = [] @State private var avatarLoadedIds: Set<UUID> = []
@State private var avatarLoadingIds: Set<UUID> = [] @State private var avatarLoadingIds: Set<UUID> = []
@State private var contactToEdit: Contact?
@State private var contactPendingBlock: Contact?
@State private var contactPendingDelete: Contact?
@State private var showBlockConfirmation = false
@State private var showDeleteConfirmation = false
@State private var blockingContactIds: Set<UUID> = []
@State private var deletingContactIds: Set<UUID> = []
private let contactsService = ContactsService() private let contactsService = ContactsService()
private let chatService = ChatService() private let chatService = ChatService()
private let profileService = ProfileService() private let profileService = ProfileService()
private let blockedUsersService = BlockedUsersService()
private let pageSize = 25 private let pageSize = 25
private var currentUserId: String? { private var currentUserId: String? {
@ -50,13 +58,12 @@ struct ContactsTab: View {
contact: contact, contact: contact,
avatarInfo: contactAvatars[contact.id], avatarInfo: contactAvatars[contact.id],
currentUserId: currentUserId, currentUserId: currentUserId,
isLoading: creatingChatForContactId == contact.id isLoading: isRowBusy(contact)
) )
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.disabled(creatingChatForContactId == contact.id) .disabled(isRowBusy(contact))
// .disabled(contact.isDeleted || creatingChatForContactId == contact.id)
.contextMenu { .contextMenu {
Button { Button {
handleContactAction(.edit, for: contact) handleContactAction(.edit, for: contact)
@ -86,7 +93,6 @@ struct ContactsTab: View {
systemImage: "trash" systemImage: "trash"
) )
} }
// .disabled(contact.isDeleted)
} }
.listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12)) .listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
.onAppear { .onAppear {
@ -128,6 +134,59 @@ struct ContactsTab: View {
) )
} }
} }
.sheet(item: $contactToEdit) { contact in
NavigationView {
ContactEditView(
contact: contactEditInfo(for: contact),
onContactDeleted: {
handleContactRemoved(contact.id)
},
onContactUpdated: { newName in
handleContactRenamed(contact.id, newName: newName)
}
)
}
}
.confirmationDialog(
NSLocalizedString("Заблокировать контакт?", comment: "Contacts block confirmation title"),
isPresented: $showBlockConfirmation,
presenting: contactPendingBlock
) { contact in
Button(NSLocalizedString("Заблокировать", comment: "Contacts block confirm action"), role: .destructive) {
showBlockConfirmation = false
contactPendingBlock = nil
performBlockContact(contact)
}
Button(NSLocalizedString("Отмена", comment: "Common cancel"), role: .cancel) {
showBlockConfirmation = false
contactPendingBlock = nil
}
} message: { contact in
Text(String(
format: NSLocalizedString("Пользователь \"%1$@\" будет добавлен в чёрный список.", comment: "Contacts block confirmation message"),
contact.displayName
))
}
.confirmationDialog(
NSLocalizedString("Удалить контакт?", comment: "Contacts delete confirmation title"),
isPresented: $showDeleteConfirmation,
presenting: contactPendingDelete
) { contact in
Button(NSLocalizedString("Удалить", comment: "Contacts delete confirm action"), role: .destructive) {
showDeleteConfirmation = false
contactPendingDelete = nil
performDeleteContact(contact)
}
Button(NSLocalizedString("Отмена", comment: "Common cancel"), role: .cancel) {
showDeleteConfirmation = false
contactPendingDelete = nil
}
} message: { contact in
Text(String(
format: NSLocalizedString("Контакт \"%1$@\" будет удалён из списка.", comment: "Contacts delete confirmation message"),
contact.displayName
))
}
.overlay(pendingChatNavigationLink) .overlay(pendingChatNavigationLink)
} }
@ -243,10 +302,17 @@ struct ContactsTab: View {
} }
private func handleContactAction(_ action: ContactAction, for contact: Contact) { private func handleContactAction(_ action: ContactAction, for contact: Contact) {
activeAlert = .info( guard !isRowBusy(contact) else { return }
title: NSLocalizedString("Скоро", comment: "Contacts placeholder title"), switch action {
message: action.placeholderMessage(for: contact) case .edit:
) contactToEdit = contact
case .block:
contactPendingBlock = contact
showBlockConfirmation = true
case .delete:
contactPendingDelete = contact
showDeleteConfirmation = true
}
} }
private var pendingChatNavigationLink: some View { private var pendingChatNavigationLink: some View {
@ -381,6 +447,92 @@ struct ContactsTab: View {
} }
} }
} }
private func performBlockContact(_ contact: Contact) {
let contactId = contact.id
guard !blockingContactIds.contains(contactId) else { return }
blockingContactIds.insert(contactId)
Task {
do {
_ = try await blockedUsersService.add(userId: contactId)
await MainActor.run {
blockingContactIds.remove(contactId)
handleContactRemoved(contactId)
}
} catch {
await MainActor.run {
blockingContactIds.remove(contactId)
activeAlert = .error(message: error.localizedDescription)
}
}
}
}
private func performDeleteContact(_ contact: Contact) {
let contactId = contact.id
guard !deletingContactIds.contains(contactId) else { return }
deletingContactIds.insert(contactId)
Task {
do {
try await contactsService.removeContact(userId: contactId)
await MainActor.run {
deletingContactIds.remove(contactId)
handleContactRemoved(contactId)
}
} catch {
await MainActor.run {
deletingContactIds.remove(contactId)
activeAlert = .error(message: error.localizedDescription)
}
}
}
}
private func contactEditInfo(for contact: Contact) -> ContactEditInfo {
ContactEditInfo(
userId: contact.id,
login: contact.login,
fullName: contact.fullName,
customName: contact.customName,
avatarFileId: contactAvatars[contact.id]?.fileId
)
}
private func handleContactRenamed(_ contactId: UUID, newName: String) {
guard let index = contacts.firstIndex(where: { $0.id == contactId }) else { return }
contacts[index] = contacts[index].updatingCustomName(newName)
}
private func handleContactRemoved(_ contactId: UUID) {
contacts.removeAll { $0.id == contactId }
contactAvatars.removeValue(forKey: contactId)
avatarLoadedIds.remove(contactId)
avatarLoadingIds.remove(contactId)
if creatingChatForContactId == contactId {
creatingChatForContactId = nil
}
blockingContactIds.remove(contactId)
deletingContactIds.remove(contactId)
if contactToEdit?.id == contactId {
contactToEdit = nil
}
if contactPendingBlock?.id == contactId {
contactPendingBlock = nil
showBlockConfirmation = false
}
if contactPendingDelete?.id == contactId {
contactPendingDelete = nil
showDeleteConfirmation = false
}
}
private func isRowBusy(_ contact: Contact) -> Bool {
creatingChatForContactId == contact.id
|| blockingContactIds.contains(contact.id)
|| deletingContactIds.contains(contact.id)
}
} }
private struct ContactRow: View { private struct ContactRow: View {
@ -575,6 +727,20 @@ private struct Contact: Identifiable, Equatable {
} }
} }
func updatingCustomName(_ newName: String) -> Contact {
let trimmed = newName.trimmingCharacters(in: .whitespacesAndNewlines)
let updatedCustomName = trimmed.isEmpty ? nil : trimmed
let payload = ContactPayload(
userId: id,
login: login,
fullName: fullName,
customName: updatedCustomName,
friendCode: friendCode,
createdAt: createdAt
)
return Contact(payload: payload)
}
private static let relativeFormatter: RelativeDateTimeFormatter = { private static let relativeFormatter: RelativeDateTimeFormatter = {
let formatter = RelativeDateTimeFormatter() let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .short formatter.unitsStyle = .short
@ -598,24 +764,4 @@ private enum ContactAction {
case edit case edit
case block case block
case delete case delete
func placeholderMessage(for contact: Contact) -> String {
switch self {
case .edit:
return String(
format: NSLocalizedString("Изменение контакта \"%1$@\" появится позже.", comment: "Contacts edit placeholder message"),
contact.displayName
)
case .block:
return String(
format: NSLocalizedString("Блокировка контакта \"%1$@\" появится позже.", comment: "Contacts block placeholder message"),
contact.displayName
)
case .delete:
return String(
format: NSLocalizedString("Удаление контакта \"%1$@\" появится позже.", comment: "Contacts delete placeholder message"),
contact.displayName
)
}
}
} }