patch contacts
This commit is contained in:
parent
4e24f468b8
commit
5601f475c8
@ -13,9 +13,13 @@ struct ContactsTab: View {
|
||||
@State private var creatingChatForContactId: UUID?
|
||||
@State private var pendingChatItem: PrivateChatListItem?
|
||||
@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 chatService = ChatService()
|
||||
private let profileService = ProfileService()
|
||||
private let pageSize = 25
|
||||
|
||||
private var currentUserId: String? {
|
||||
@ -42,11 +46,17 @@ struct ContactsTab: View {
|
||||
Button {
|
||||
openChat(for: contact)
|
||||
} label: {
|
||||
ContactRow(contact: contact, isLoading: creatingChatForContactId == contact.id)
|
||||
ContactRow(
|
||||
contact: contact,
|
||||
avatarInfo: contactAvatars[contact.id],
|
||||
currentUserId: currentUserId,
|
||||
isLoading: creatingChatForContactId == contact.id
|
||||
)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(contact.isDeleted || creatingChatForContactId == contact.id)
|
||||
.disabled(creatingChatForContactId == contact.id)
|
||||
// .disabled(contact.isDeleted || creatingChatForContactId == contact.id)
|
||||
.contextMenu {
|
||||
Button {
|
||||
handleContactAction(.edit, for: contact)
|
||||
@ -80,6 +90,7 @@ struct ContactsTab: View {
|
||||
}
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
|
||||
.onAppear {
|
||||
loadAvatarIfNeeded(for: contact)
|
||||
if index >= contacts.count - 5 {
|
||||
Task {
|
||||
await loadContacts(reset: false)
|
||||
@ -341,35 +352,56 @@ struct ContactsTab: View {
|
||||
|
||||
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 {
|
||||
let contact: Contact
|
||||
let avatarInfo: AvatarInfo?
|
||||
let currentUserId: String?
|
||||
let isLoading: Bool
|
||||
|
||||
init(contact: Contact, isLoading: Bool = false) {
|
||||
private let avatarSize: CGFloat = 40
|
||||
|
||||
init(contact: Contact, avatarInfo: AvatarInfo? = nil, currentUserId: String? = nil, isLoading: Bool = false) {
|
||||
self.contact = contact
|
||||
self.avatarInfo = avatarInfo
|
||||
self.currentUserId = currentUserId
|
||||
self.isLoading = isLoading
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top, spacing: 10) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
avatarView
|
||||
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
@ -411,6 +443,56 @@ private struct ContactRow: View {
|
||||
.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 {
|
||||
Text(NSLocalizedString("Код дружбы", comment: "Friend code badge"))
|
||||
.font(.caption2.weight(.medium))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user