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

View File

@ -16,10 +16,18 @@ struct ContactsTab: View {
@State private var contactAvatars: [UUID: AvatarInfo] = [:]
@State private var avatarLoadedIds: 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 chatService = ChatService()
private let profileService = ProfileService()
private let blockedUsersService = BlockedUsersService()
private let pageSize = 25
private var currentUserId: String? {
@ -50,13 +58,12 @@ struct ContactsTab: View {
contact: contact,
avatarInfo: contactAvatars[contact.id],
currentUserId: currentUserId,
isLoading: creatingChatForContactId == contact.id
isLoading: isRowBusy(contact)
)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.disabled(creatingChatForContactId == contact.id)
// .disabled(contact.isDeleted || creatingChatForContactId == contact.id)
.disabled(isRowBusy(contact))
.contextMenu {
Button {
handleContactAction(.edit, for: contact)
@ -86,7 +93,6 @@ struct ContactsTab: View {
systemImage: "trash"
)
}
// .disabled(contact.isDeleted)
}
.listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
.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)
}
@ -243,10 +302,17 @@ struct ContactsTab: View {
}
private func handleContactAction(_ action: ContactAction, for contact: Contact) {
activeAlert = .info(
title: NSLocalizedString("Скоро", comment: "Contacts placeholder title"),
message: action.placeholderMessage(for: contact)
)
guard !isRowBusy(contact) else { return }
switch action {
case .edit:
contactToEdit = contact
case .block:
contactPendingBlock = contact
showBlockConfirmation = true
case .delete:
contactPendingDelete = contact
showDeleteConfirmation = true
}
}
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 {
@ -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 = {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .short
@ -598,24 +764,4 @@ private enum ContactAction {
case edit
case block
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
)
}
}
}