patch contact
This commit is contained in:
parent
d85921e9fe
commit
430f21bf62
@ -2483,9 +2483,6 @@
|
|||||||
},
|
},
|
||||||
"Пропустить" : {
|
"Пропустить" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Просмотр \"%1$@\" появится позже." : {
|
|
||||||
"comment" : "Contacts placeholder message"
|
|
||||||
},
|
},
|
||||||
"Профиль" : {
|
"Профиль" : {
|
||||||
"comment" : "Message profile navigation title",
|
"comment" : "Message profile navigation title",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import SwiftUI
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct ContactsTab: View {
|
struct ContactsTab: View {
|
||||||
|
@ObservedObject private var loginViewModel: LoginViewModel
|
||||||
@State private var contacts: [Contact] = []
|
@State private var contacts: [Contact] = []
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@State private var loadError: String?
|
@State private var loadError: String?
|
||||||
@ -9,16 +10,29 @@ struct ContactsTab: View {
|
|||||||
@State private var activeAlert: ContactsAlert?
|
@State private var activeAlert: ContactsAlert?
|
||||||
@State private var hasMore = true
|
@State private var hasMore = true
|
||||||
@State private var offset = 0
|
@State private var offset = 0
|
||||||
|
@State private var creatingChatForContactId: UUID?
|
||||||
|
@State private var pendingChatItem: PrivateChatListItem?
|
||||||
|
@State private var isPendingChatActive = false
|
||||||
|
|
||||||
private let contactsService = ContactsService()
|
private let contactsService = ContactsService()
|
||||||
|
private let chatService = ChatService()
|
||||||
private let pageSize = 25
|
private let pageSize = 25
|
||||||
|
|
||||||
|
private var currentUserId: String? {
|
||||||
|
let identifier = loginViewModel.userId
|
||||||
|
return identifier.isEmpty ? nil : identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
init(viewModel: LoginViewModel) {
|
||||||
|
self._loginViewModel = ObservedObject(wrappedValue: viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
if isLoading && contacts.isEmpty {
|
if isLoading && contacts.isEmpty {
|
||||||
loadingState
|
loadingState
|
||||||
}
|
}
|
||||||
|
|
||||||
if let loadError, contacts.isEmpty {
|
if let loadError, contacts.isEmpty {
|
||||||
errorState(loadError)
|
errorState(loadError)
|
||||||
} else if contacts.isEmpty {
|
} else if contacts.isEmpty {
|
||||||
@ -26,13 +40,13 @@ struct ContactsTab: View {
|
|||||||
} else {
|
} else {
|
||||||
ForEach(Array(contacts.enumerated()), id: \.element.id) { index, contact in
|
ForEach(Array(contacts.enumerated()), id: \.element.id) { index, contact in
|
||||||
Button {
|
Button {
|
||||||
showContactPlaceholder(for: contact)
|
openChat(for: contact)
|
||||||
} label: {
|
} label: {
|
||||||
ContactRow(contact: contact)
|
ContactRow(contact: contact, isLoading: creatingChatForContactId == contact.id)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
// .disabled(contact.isDeleted)
|
.disabled(contact.isDeleted || creatingChatForContactId == contact.id)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button {
|
Button {
|
||||||
handleContactAction(.edit, for: contact)
|
handleContactAction(.edit, for: contact)
|
||||||
@ -106,6 +120,7 @@ struct ContactsTab: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.overlay(pendingChatNavigationLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var loadingState: some View {
|
private var loadingState: some View {
|
||||||
@ -219,26 +234,126 @@ struct ContactsTab: View {
|
|||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showContactPlaceholder(for contact: Contact) {
|
|
||||||
activeAlert = .info(
|
|
||||||
title: NSLocalizedString("Скоро", comment: "Contacts placeholder title"),
|
|
||||||
message: String(
|
|
||||||
format: NSLocalizedString("Просмотр \"%1$@\" появится позже.", comment: "Contacts placeholder message"),
|
|
||||||
contact.displayName
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleContactAction(_ action: ContactAction, for contact: Contact) {
|
private func handleContactAction(_ action: ContactAction, for contact: Contact) {
|
||||||
activeAlert = .info(
|
activeAlert = .info(
|
||||||
title: NSLocalizedString("Скоро", comment: "Contacts placeholder title"),
|
title: NSLocalizedString("Скоро", comment: "Contacts placeholder title"),
|
||||||
message: action.placeholderMessage(for: contact)
|
message: action.placeholderMessage(for: contact)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var pendingChatNavigationLink: some View {
|
||||||
|
NavigationLink(
|
||||||
|
destination: pendingChatDestination,
|
||||||
|
isActive: Binding(
|
||||||
|
get: { isPendingChatActive && pendingChatItem != nil },
|
||||||
|
set: { newValue in
|
||||||
|
if !newValue {
|
||||||
|
isPendingChatActive = false
|
||||||
|
pendingChatItem = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
.hidden()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var pendingChatDestination: some View {
|
||||||
|
if let pendingChatItem {
|
||||||
|
PrivateChatView(chat: pendingChatItem, currentUserId: currentUserId)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openChat(for contact: Contact) {
|
||||||
|
guard creatingChatForContactId == nil else { return }
|
||||||
|
guard !contact.isDeleted else { return }
|
||||||
|
|
||||||
|
creatingChatForContactId = contact.id
|
||||||
|
|
||||||
|
chatService.createOrFindPrivateChat(targetUserId: contact.id.uuidString) { result in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
creatingChatForContactId = nil
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let data):
|
||||||
|
let chatItem = PrivateChatListItem(
|
||||||
|
chatId: data.chatId,
|
||||||
|
chatType: data.chatType,
|
||||||
|
chatData: chatProfile(for: contact),
|
||||||
|
lastMessage: nil,
|
||||||
|
createdAt: nil,
|
||||||
|
unreadCount: 0
|
||||||
|
)
|
||||||
|
pendingChatItem = chatItem
|
||||||
|
isPendingChatActive = true
|
||||||
|
case .failure(let error):
|
||||||
|
activeAlert = .error(message: friendlyChatCreationMessage(for: error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func chatProfile(for contact: Contact) -> ChatProfile {
|
||||||
|
ChatProfile(
|
||||||
|
userId: contact.id.uuidString,
|
||||||
|
login: contact.login,
|
||||||
|
fullName: contact.fullName,
|
||||||
|
customName: contact.customName,
|
||||||
|
createdAt: contact.createdAt,
|
||||||
|
isOfficial: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func friendlyChatCreationMessage(for error: Error) -> String {
|
||||||
|
if let chatError = error as? ChatServiceError {
|
||||||
|
return chatError.errorDescription ?? NSLocalizedString("Не удалось открыть чат.", comment: "Chat creation fallback")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let networkError = error as? NetworkError {
|
||||||
|
switch networkError {
|
||||||
|
case .unauthorized:
|
||||||
|
return NSLocalizedString("Сессия истекла. Войдите снова.", comment: "Chat creation unauthorized")
|
||||||
|
case .invalidURL, .noResponse:
|
||||||
|
return NSLocalizedString("Ошибка соединения с сервером.", comment: "Chat creation connection")
|
||||||
|
case .network(let underlying):
|
||||||
|
return String(format: NSLocalizedString("Ошибка сети: %@", comment: "Chat creation network error"), underlying.localizedDescription)
|
||||||
|
case .server(let statusCode, let data):
|
||||||
|
if let data {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||||
|
if let payload = try? decoder.decode(ErrorResponse.self, from: data) {
|
||||||
|
if let detail = payload.detail?.trimmingCharacters(in: .whitespacesAndNewlines), !detail.isEmpty {
|
||||||
|
return detail
|
||||||
|
}
|
||||||
|
if let message = payload.data?.message?.trimmingCharacters(in: .whitespacesAndNewlines), !message.isEmpty {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let raw = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String(format: NSLocalizedString("Ошибка сервера (%@).", comment: "Chat creation server status"), "\(statusCode)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NSLocalizedString("Произошла неизвестная ошибка. Попробуйте позже.", comment: "Chat creation unknown error")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ContactRow: View {
|
private struct ContactRow: View {
|
||||||
let contact: Contact
|
let contact: Contact
|
||||||
|
let isLoading: Bool
|
||||||
|
|
||||||
|
init(contact: Contact, isLoading: Bool = false) {
|
||||||
|
self.contact = contact
|
||||||
|
self.isLoading = isLoading
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .top, spacing: 10) {
|
HStack(alignment: .top, spacing: 10) {
|
||||||
@ -289,6 +404,12 @@ private struct ContactRow: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
if isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.scaleEffect(0.8)
|
||||||
|
.padding(.top, 4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.vertical, 6)
|
.padding(.vertical, 6)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,7 +69,7 @@ struct MainView: View {
|
|||||||
.opacity(selectedTab == 2 ? 1 : 0)
|
.opacity(selectedTab == 2 ? 1 : 0)
|
||||||
.allowsHitTesting(selectedTab == 2)
|
.allowsHitTesting(selectedTab == 2)
|
||||||
|
|
||||||
ContactsTab()
|
ContactsTab(viewModel: viewModel)
|
||||||
.opacity(selectedTab == 4 ? 1 : 0)
|
.opacity(selectedTab == 4 ? 1 : 0)
|
||||||
|
|
||||||
SettingsView(viewModel: viewModel)
|
SettingsView(viewModel: viewModel)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user