patch search
This commit is contained in:
parent
7ce0fb3cd3
commit
f6ca117b8e
@ -395,7 +395,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = yobble/yobble.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = V22H44W47J;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@ -435,7 +435,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = yobble/yobble.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = V22H44W47J;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
||||
@ -26,6 +26,18 @@ struct SearchProfile: Decodable {
|
||||
}
|
||||
|
||||
extension UserSearchResult {
|
||||
var officialFullName: String? {
|
||||
trimmed(fullName) ?? trimmed(profile?.fullName)
|
||||
}
|
||||
|
||||
var preferredCustomName: String? {
|
||||
trimmed(customName) ?? trimmed(profile?.customName)
|
||||
}
|
||||
|
||||
var loginHandle: String {
|
||||
"@\(login)"
|
||||
}
|
||||
|
||||
var displayName: String {
|
||||
if let official = officialFullName {
|
||||
return official
|
||||
@ -42,50 +54,6 @@ extension UserSearchResult {
|
||||
return loginHandle
|
||||
}
|
||||
|
||||
var secondaryText: String? {
|
||||
// Отдельное поле для совместимости с существующими вызовами
|
||||
// if let official = officialFullName, official != displayName {
|
||||
// return official
|
||||
// }
|
||||
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
|
||||
|
||||
@ -29,6 +29,9 @@
|
||||
},
|
||||
"Chat ID:" : {
|
||||
|
||||
},
|
||||
"Companion ID" : {
|
||||
"comment" : "Search placeholder companion title"
|
||||
},
|
||||
"Companion ID:" : {
|
||||
|
||||
@ -338,6 +341,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Здесь появится информация о собеседнике и существующих чатах." : {
|
||||
"comment" : "Search placeholder description"
|
||||
},
|
||||
"Идеи" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
@ -1082,6 +1088,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Профиль в разработке" : {
|
||||
"comment" : "Search placeholder title"
|
||||
},
|
||||
"Публичная информация" : {
|
||||
|
||||
},
|
||||
@ -1204,6 +1213,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Скопировать" : {
|
||||
"comment" : "Search placeholder copy"
|
||||
},
|
||||
"Скоро появится мини-игра, где можно заработать очки для кастомизации профиля. Следите за обновлениями!" : {
|
||||
"comment" : "Concept tab placeholder description"
|
||||
},
|
||||
|
||||
@ -23,6 +23,7 @@ struct ChatsTab: View {
|
||||
@State private var isGlobalSearchLoading: Bool = false
|
||||
@State private var globalSearchError: String?
|
||||
@State private var globalSearchTask: Task<Void, Never>? = nil
|
||||
@State private var selectedSearchUserId: UUID?
|
||||
|
||||
private let searchRevealDistance: CGFloat = 90
|
||||
|
||||
@ -361,43 +362,58 @@ struct ChatsTab: View {
|
||||
}
|
||||
|
||||
private func globalSearchRow(for user: UserSearchResult) -> some View {
|
||||
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)
|
||||
)
|
||||
Button {
|
||||
selectedSearchUserId = user.userId
|
||||
} label: {
|
||||
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)
|
||||
)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 6) {
|
||||
Text(user.displayName)
|
||||
.fontWeight(user.isOfficial ? .semibold : .regular)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
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 user.isOfficial {
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.foregroundColor(Color.accentColor)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
||||
if let secondary = secondaryLine(for: user) {
|
||||
Text(secondary)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
}
|
||||
}
|
||||
|
||||
if let secondary = user.secondaryLabelForSearch {
|
||||
Text(secondary)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.buttonStyle(.plain)
|
||||
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
||||
.background(
|
||||
NavigationLink(
|
||||
destination: SearchResultPlaceholderView(userId: user.userId),
|
||||
tag: user.userId,
|
||||
selection: $selectedSearchUserId
|
||||
) {
|
||||
EmptyView()
|
||||
}
|
||||
.hidden()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -423,6 +439,7 @@ private extension ChatsTab {
|
||||
globalSearchResults = []
|
||||
globalSearchError = nil
|
||||
isGlobalSearchLoading = false
|
||||
selectedSearchUserId = nil
|
||||
return
|
||||
}
|
||||
|
||||
@ -441,6 +458,7 @@ private extension ChatsTab {
|
||||
isGlobalSearchLoading = false
|
||||
globalSearchError = nil
|
||||
globalSearchTask = nil
|
||||
selectedSearchUserId = nil
|
||||
}
|
||||
} catch is CancellationError {
|
||||
// Ignore cancellation
|
||||
@ -451,6 +469,7 @@ private extension ChatsTab {
|
||||
isGlobalSearchLoading = false
|
||||
globalSearchError = friendlyErrorMessage(for: error)
|
||||
globalSearchTask = nil
|
||||
selectedSearchUserId = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -462,8 +481,37 @@ private extension ChatsTab {
|
||||
globalSearchResults = []
|
||||
globalSearchError = nil
|
||||
isGlobalSearchLoading = false
|
||||
selectedSearchUserId = nil
|
||||
}
|
||||
|
||||
func secondaryLine(for user: UserSearchResult) -> String? {
|
||||
if let official = user.officialFullName {
|
||||
if let custom = user.preferredCustomName, custom != official {
|
||||
return "\(user.loginHandle) (\(custom))"
|
||||
}
|
||||
// if official != user.loginHandle {
|
||||
// return user.loginHandle
|
||||
// }
|
||||
}
|
||||
else if let custom = user.preferredCustomName, custom != user.displayName {
|
||||
return custom
|
||||
}
|
||||
|
||||
if let profileLogin = user.profile?.login?.trimmingCharacters(in: .whitespacesAndNewlines), !profileLogin.isEmpty {
|
||||
let handle = "@\(profileLogin)"
|
||||
if handle != user.loginHandle {
|
||||
return handle
|
||||
}
|
||||
}
|
||||
|
||||
if user.displayName != user.loginHandle {
|
||||
return user.loginHandle
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func friendlyErrorMessage(for error: Error) -> String {
|
||||
if let searchError = error as? SearchServiceError {
|
||||
return searchError.errorDescription ?? NSLocalizedString("Не удалось выполнить поиск.", comment: "Search error fallback")
|
||||
@ -500,6 +548,52 @@ private struct ScrollDismissesKeyboardModifier: ViewModifier {
|
||||
}
|
||||
}
|
||||
|
||||
private struct SearchResultPlaceholderView: View {
|
||||
let userId: UUID
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
Text(NSLocalizedString("Профиль в разработке", comment: "Search placeholder title"))
|
||||
.font(.headline)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(NSLocalizedString("Companion ID", comment: "Search placeholder companion title"))
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Text(userId.uuidString)
|
||||
.font(.body.monospaced())
|
||||
.contextMenu {
|
||||
Button(action: {
|
||||
#if canImport(UIKit)
|
||||
UIPasteboard.general.string = userId.uuidString
|
||||
#endif
|
||||
}) {
|
||||
Label(NSLocalizedString("Скопировать", comment: "Search placeholder copy"), systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(UIColor.secondarySystemBackground))
|
||||
)
|
||||
|
||||
Text(NSLocalizedString("Здесь появится информация о собеседнике и существующих чатах.", comment: "Search placeholder description"))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 40)
|
||||
.padding(.horizontal, 24)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
.background(Color(UIColor.systemBackground))
|
||||
}
|
||||
}
|
||||
|
||||
private struct ChatRowView: View {
|
||||
let chat: PrivateChatListItem
|
||||
let currentUserId: String?
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user