add work button in contact
This commit is contained in:
parent
a66eb04489
commit
8ba51f026c
@ -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"
|
||||||
|
|||||||
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user