From a5b2c2702c52f42406013589bb39d3bd220ea0cd Mon Sep 17 00:00:00 2001 From: cheykrym Date: Tue, 7 Oct 2025 05:35:10 +0300 Subject: [PATCH] add patch to search --- yobble/Network/SearchModels.swift | 81 ++++++++++++++++++++++++++----- yobble/Views/Tab/ChatsTab.swift | 44 ++++++++++++----- 2 files changed, 101 insertions(+), 24 deletions(-) diff --git a/yobble/Network/SearchModels.swift b/yobble/Network/SearchModels.swift index c6876af..3c84a25 100644 --- a/yobble/Network/SearchModels.swift +++ b/yobble/Network/SearchModels.swift @@ -27,28 +27,85 @@ struct SearchProfile: Decodable { extension UserSearchResult { var displayName: String { - if let customName = customName, !customName.isEmpty { - return customName + if let custom = preferredCustomName { + return custom } - if let fullName = fullName, !fullName.isEmpty { - return fullName + if let official = officialFullName { + return official } - if let profileName = profile?.customName, !profileName.isEmpty { - return profileName + if let profileCustom = trimmed(profile?.customName) { + return profileCustom } - if let profileFullName = profile?.fullName, !profileFullName.isEmpty { - return profileFullName + if let profileFull = trimmed(profile?.fullName) { + return profileFull } - return "@\(login)" + return loginHandle } var secondaryText: String? { - if let fullName = fullName, !fullName.isEmpty, fullName != displayName { - return fullName + // Отдельное поле для совместимости с существующими вызовами + if let official = officialFullName, official != displayName { + return official } - if let profileLogin = profile?.login, !profileLogin.isEmpty, profileLogin != login { + if let profileLogin = trimmed(profile?.login), profileLogin != login { return "@\(profileLogin)" } return nil } + + var officialFullName: String? { + trimmed(fullName) ?? trimmed(profile?.fullName) + } + + var preferredCustomName: String? { + trimmed(customName) ?? trimmed(profile?.customName) + } + + var loginHandle: String { + "@\(login)" + } + + var secondaryLabelForSearch: String? { + if let official = officialFullName { + if let custom = preferredCustomName, custom != official { + return custom + } + if official != loginHandle { + return loginHandle + } + } + + if let secondary = secondaryText, secondary != displayName { + return secondary + } + + if displayName != loginHandle { + return loginHandle + } + + return nil + } + + var avatarInitial: String { + let source = officialFullName + ?? preferredCustomName + ?? trimmed(profile?.login) + ?? login + + if let character = source.first(where: { !$0.isWhitespace && $0 != "@" }) { + return String(character).uppercased() + } + return "?" + } + + var isOfficial: Bool { + officialFullName != nil + } + + private func trimmed(_ value: String?) -> String? { + guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else { + return nil + } + return value + } } diff --git a/yobble/Views/Tab/ChatsTab.swift b/yobble/Views/Tab/ChatsTab.swift index 9ec4fae..461b14b 100644 --- a/yobble/Views/Tab/ChatsTab.swift +++ b/yobble/Views/Tab/ChatsTab.swift @@ -361,22 +361,42 @@ struct ChatsTab: View { } private func globalSearchRow(for user: UserSearchResult) -> some View { - VStack(alignment: .leading, spacing: 4) { - Text(user.displayName) - .font(.headline) + HStack(spacing: 12) { + Circle() + .fill(user.isOfficial ? Color.accentColor.opacity(0.85) : Color.accentColor.opacity(0.15)) + .frame(width: 44, height: 44) + .overlay( + Text(user.avatarInitial) + .font(.headline) + .foregroundColor(user.isOfficial ? .white : Color.accentColor) + ) - if user.displayName != "@\(user.login)" { - Text("@\(user.login)") - .font(.subheadline) - .foregroundColor(.secondary) - } else if let secondary = user.secondaryText { - Text(secondary) - .font(.subheadline) - .foregroundColor(.secondary) + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 6) { + Text(user.displayName) + .fontWeight(user.isOfficial ? .semibold : .regular) + .foregroundColor(.primary) + .lineLimit(1) + .truncationMode(.tail) + + if user.isOfficial { + Image(systemName: "checkmark.seal.fill") + .foregroundColor(Color.accentColor) + .font(.caption) + } + } + + if let secondary = user.secondaryLabelForSearch { + Text(secondary) + .font(.footnote) + .foregroundColor(.secondary) + .lineLimit(1) + .truncationMode(.tail) + } } + .frame(maxWidth: .infinity, alignment: .leading) } .padding(.vertical, 8) - .frame(maxWidth: .infinity, alignment: .leading) .listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) } }