Compare commits
No commits in common. "a66eb0448949a97922c9dcaea2876f1e52c923a5" and "430f21bf62c5e445b852762c3ef688b010005c1c" have entirely different histories.
a66eb04489
...
430f21bf62
@ -678,11 +678,7 @@ struct MessageProfileView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var shouldShowRelationshipQuickActions: Bool {
|
private var shouldShowRelationshipQuickActions: Bool {
|
||||||
if isDeletedUser { return false }
|
|
||||||
|
|
||||||
guard let relationship = currentChatProfile?.relationship else { return false }
|
guard let relationship = currentChatProfile?.relationship else { return false }
|
||||||
|
|
||||||
// показываем только если НЕ в контактах
|
|
||||||
return !relationship.isTargetInContactsOfCurrentUser
|
return !relationship.isTargetInContactsOfCurrentUser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,13 +13,9 @@ struct ContactsTab: View {
|
|||||||
@State private var creatingChatForContactId: UUID?
|
@State private var creatingChatForContactId: UUID?
|
||||||
@State private var pendingChatItem: PrivateChatListItem?
|
@State private var pendingChatItem: PrivateChatListItem?
|
||||||
@State private var isPendingChatActive = false
|
@State private var isPendingChatActive = false
|
||||||
@State private var contactAvatars: [UUID: AvatarInfo] = [:]
|
|
||||||
@State private var avatarLoadedIds: Set<UUID> = []
|
|
||||||
@State private var avatarLoadingIds: Set<UUID> = []
|
|
||||||
|
|
||||||
private let contactsService = ContactsService()
|
private let contactsService = ContactsService()
|
||||||
private let chatService = ChatService()
|
private let chatService = ChatService()
|
||||||
private let profileService = ProfileService()
|
|
||||||
private let pageSize = 25
|
private let pageSize = 25
|
||||||
|
|
||||||
private var currentUserId: String? {
|
private var currentUserId: String? {
|
||||||
@ -46,17 +42,11 @@ struct ContactsTab: View {
|
|||||||
Button {
|
Button {
|
||||||
openChat(for: contact)
|
openChat(for: contact)
|
||||||
} label: {
|
} label: {
|
||||||
ContactRow(
|
ContactRow(contact: contact, isLoading: creatingChatForContactId == contact.id)
|
||||||
contact: contact,
|
|
||||||
avatarInfo: contactAvatars[contact.id],
|
|
||||||
currentUserId: currentUserId,
|
|
||||||
isLoading: creatingChatForContactId == contact.id
|
|
||||||
)
|
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.disabled(creatingChatForContactId == contact.id)
|
.disabled(contact.isDeleted || creatingChatForContactId == contact.id)
|
||||||
// .disabled(contact.isDeleted || creatingChatForContactId == contact.id)
|
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button {
|
Button {
|
||||||
handleContactAction(.edit, for: contact)
|
handleContactAction(.edit, for: contact)
|
||||||
@ -90,7 +80,6 @@ struct ContactsTab: View {
|
|||||||
}
|
}
|
||||||
.listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
|
.listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
loadAvatarIfNeeded(for: contact)
|
|
||||||
if index >= contacts.count - 5 {
|
if index >= contacts.count - 5 {
|
||||||
Task {
|
Task {
|
||||||
await loadContacts(reset: false)
|
await loadContacts(reset: false)
|
||||||
@ -112,6 +101,9 @@ struct ContactsTab: View {
|
|||||||
.task {
|
.task {
|
||||||
await loadContacts(reset: false)
|
await loadContacts(reset: false)
|
||||||
}
|
}
|
||||||
|
.refreshable {
|
||||||
|
await refreshContacts()
|
||||||
|
}
|
||||||
.alert(item: $activeAlert) { alert in
|
.alert(item: $activeAlert) { alert in
|
||||||
switch alert {
|
switch alert {
|
||||||
case .error(_, let message):
|
case .error(_, let message):
|
||||||
@ -278,6 +270,7 @@ struct ContactsTab: View {
|
|||||||
|
|
||||||
private func openChat(for contact: Contact) {
|
private func openChat(for contact: Contact) {
|
||||||
guard creatingChatForContactId == nil else { return }
|
guard creatingChatForContactId == nil else { return }
|
||||||
|
guard !contact.isDeleted else { return }
|
||||||
|
|
||||||
creatingChatForContactId = contact.id
|
creatingChatForContactId = contact.id
|
||||||
|
|
||||||
@ -351,56 +344,35 @@ struct ContactsTab: View {
|
|||||||
|
|
||||||
return NSLocalizedString("Произошла неизвестная ошибка. Попробуйте позже.", comment: "Chat creation unknown error")
|
return NSLocalizedString("Произошла неизвестная ошибка. Попробуйте позже.", comment: "Chat creation unknown error")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadAvatarIfNeeded(for contact: Contact) {
|
|
||||||
guard !contact.isDeleted else { return }
|
|
||||||
let contactId = contact.id
|
|
||||||
if avatarLoadedIds.contains(contactId) || avatarLoadingIds.contains(contactId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
avatarLoadingIds.insert(contactId)
|
|
||||||
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
let profile = try await profileService.fetchProfile(userId: contactId)
|
|
||||||
await MainActor.run {
|
|
||||||
if let info = profile.avatars?.current {
|
|
||||||
contactAvatars[contactId] = info
|
|
||||||
}
|
|
||||||
avatarLoadedIds.insert(contactId)
|
|
||||||
avatarLoadingIds.remove(contactId)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
if AppConfig.DEBUG {
|
|
||||||
print("[ContactsTab] load avatar failed for \(contactId): \(error)")
|
|
||||||
}
|
|
||||||
await MainActor.run {
|
|
||||||
avatarLoadingIds.remove(contactId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ContactRow: View {
|
private struct ContactRow: View {
|
||||||
let contact: Contact
|
let contact: Contact
|
||||||
let avatarInfo: AvatarInfo?
|
|
||||||
let currentUserId: String?
|
|
||||||
let isLoading: Bool
|
let isLoading: Bool
|
||||||
|
|
||||||
private let avatarSize: CGFloat = 40
|
init(contact: Contact, isLoading: Bool = false) {
|
||||||
|
|
||||||
init(contact: Contact, avatarInfo: AvatarInfo? = nil, currentUserId: String? = nil, isLoading: Bool = false) {
|
|
||||||
self.contact = contact
|
self.contact = contact
|
||||||
self.avatarInfo = avatarInfo
|
|
||||||
self.currentUserId = currentUserId
|
|
||||||
self.isLoading = isLoading
|
self.isLoading = isLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .top, spacing: 12) {
|
HStack(alignment: .top, spacing: 10) {
|
||||||
avatarView
|
Circle()
|
||||||
|
.fill(contact.isDeleted ? Color(.systemGray5) : Color.accentColor.opacity(0.15))
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
.overlay(
|
||||||
|
Group {
|
||||||
|
if contact.isDeleted {
|
||||||
|
Image(systemName: "person.slash")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(Color(.systemGray2))
|
||||||
|
} else {
|
||||||
|
Text(contact.initials)
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 3) {
|
VStack(alignment: .leading, spacing: 3) {
|
||||||
HStack(alignment: .firstTextBaseline) {
|
HStack(alignment: .firstTextBaseline) {
|
||||||
@ -442,56 +414,6 @@ private struct ContactRow: View {
|
|||||||
.padding(.vertical, 6)
|
.padding(.vertical, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var avatarView: some View {
|
|
||||||
if let fileId = avatarInfo?.fileId,
|
|
||||||
let url = avatarURL(for: fileId),
|
|
||||||
let currentUserId {
|
|
||||||
CachedAvatarView(url: url, fileId: fileId, userId: currentUserId) {
|
|
||||||
placeholderAvatar
|
|
||||||
}
|
|
||||||
.aspectRatio(contentMode: .fill)
|
|
||||||
.frame(width: avatarSize, height: avatarSize)
|
|
||||||
.clipShape(Circle())
|
|
||||||
} else {
|
|
||||||
placeholderAvatar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func avatarURL(for fileId: String) -> URL? {
|
|
||||||
let userId = contact.id.uuidString
|
|
||||||
let path = "\(AppConfig.API_SERVER)/v1/storage/download/avatar/\(userId)?file_id=\(fileId)"
|
|
||||||
return URL(string: path)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var placeholderAvatar: some View {
|
|
||||||
Circle()
|
|
||||||
.fill(avatarBackgroundColor)
|
|
||||||
.frame(width: avatarSize, height: avatarSize)
|
|
||||||
.overlay(
|
|
||||||
Group {
|
|
||||||
if contact.isDeleted {
|
|
||||||
Image(systemName: "person.slash")
|
|
||||||
.symbolRenderingMode(.hierarchical)
|
|
||||||
.font(.system(size: avatarSize * 0.45, weight: .semibold))
|
|
||||||
.foregroundColor(avatarTextColor)
|
|
||||||
} else {
|
|
||||||
Text(contact.initials)
|
|
||||||
.font(.system(size: avatarSize * 0.5, weight: .semibold))
|
|
||||||
.foregroundColor(avatarTextColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var avatarBackgroundColor: Color {
|
|
||||||
contact.isDeleted ? Color(.systemGray5) : Color.accentColor.opacity(0.15)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var avatarTextColor: Color {
|
|
||||||
contact.isDeleted ? Color.accentColor : Color.accentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
private var friendCodeBadge: some View {
|
private var friendCodeBadge: some View {
|
||||||
Text(NSLocalizedString("Код дружбы", comment: "Friend code badge"))
|
Text(NSLocalizedString("Код дружбы", comment: "Friend code badge"))
|
||||||
.font(.caption2.weight(.medium))
|
.font(.caption2.weight(.medium))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user