patch search
This commit is contained in:
parent
7ce0fb3cd3
commit
f6ca117b8e
@ -395,7 +395,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = yobble/yobble.entitlements;
|
CODE_SIGN_ENTITLEMENTS = yobble/yobble.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_TEAM = V22H44W47J;
|
DEVELOPMENT_TEAM = V22H44W47J;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@ -435,7 +435,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = yobble/yobble.entitlements;
|
CODE_SIGN_ENTITLEMENTS = yobble/yobble.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_TEAM = V22H44W47J;
|
DEVELOPMENT_TEAM = V22H44W47J;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|||||||
@ -26,6 +26,18 @@ struct SearchProfile: Decodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension UserSearchResult {
|
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 {
|
var displayName: String {
|
||||||
if let official = officialFullName {
|
if let official = officialFullName {
|
||||||
return official
|
return official
|
||||||
@ -42,50 +54,6 @@ extension UserSearchResult {
|
|||||||
return loginHandle
|
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 {
|
var avatarInitial: String {
|
||||||
let source = officialFullName
|
let source = officialFullName
|
||||||
?? preferredCustomName
|
?? preferredCustomName
|
||||||
|
|||||||
@ -29,6 +29,9 @@
|
|||||||
},
|
},
|
||||||
"Chat ID:" : {
|
"Chat ID:" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Companion ID" : {
|
||||||
|
"comment" : "Search placeholder companion title"
|
||||||
},
|
},
|
||||||
"Companion ID:" : {
|
"Companion ID:" : {
|
||||||
|
|
||||||
@ -338,6 +341,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Здесь появится информация о собеседнике и существующих чатах." : {
|
||||||
|
"comment" : "Search placeholder description"
|
||||||
|
},
|
||||||
"Идеи" : {
|
"Идеи" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -1082,6 +1088,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Профиль в разработке" : {
|
||||||
|
"comment" : "Search placeholder title"
|
||||||
|
},
|
||||||
"Публичная информация" : {
|
"Публичная информация" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -1204,6 +1213,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Скопировать" : {
|
||||||
|
"comment" : "Search placeholder copy"
|
||||||
|
},
|
||||||
"Скоро появится мини-игра, где можно заработать очки для кастомизации профиля. Следите за обновлениями!" : {
|
"Скоро появится мини-игра, где можно заработать очки для кастомизации профиля. Следите за обновлениями!" : {
|
||||||
"comment" : "Concept tab placeholder description"
|
"comment" : "Concept tab placeholder description"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -23,6 +23,7 @@ struct ChatsTab: View {
|
|||||||
@State private var isGlobalSearchLoading: Bool = false
|
@State private var isGlobalSearchLoading: Bool = false
|
||||||
@State private var globalSearchError: String?
|
@State private var globalSearchError: String?
|
||||||
@State private var globalSearchTask: Task<Void, Never>? = nil
|
@State private var globalSearchTask: Task<Void, Never>? = nil
|
||||||
|
@State private var selectedSearchUserId: UUID?
|
||||||
|
|
||||||
private let searchRevealDistance: CGFloat = 90
|
private let searchRevealDistance: CGFloat = 90
|
||||||
|
|
||||||
@ -361,6 +362,9 @@ struct ChatsTab: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func globalSearchRow(for user: UserSearchResult) -> some View {
|
private func globalSearchRow(for user: UserSearchResult) -> some View {
|
||||||
|
Button {
|
||||||
|
selectedSearchUserId = user.userId
|
||||||
|
} label: {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(user.isOfficial ? Color.accentColor.opacity(0.85) : Color.accentColor.opacity(0.15))
|
.fill(user.isOfficial ? Color.accentColor.opacity(0.85) : Color.accentColor.opacity(0.15))
|
||||||
@ -386,7 +390,7 @@ struct ChatsTab: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let secondary = user.secondaryLabelForSearch {
|
if let secondary = secondaryLine(for: user) {
|
||||||
Text(secondary)
|
Text(secondary)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
@ -397,7 +401,19 @@ struct ChatsTab: View {
|
|||||||
.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))
|
.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 = []
|
globalSearchResults = []
|
||||||
globalSearchError = nil
|
globalSearchError = nil
|
||||||
isGlobalSearchLoading = false
|
isGlobalSearchLoading = false
|
||||||
|
selectedSearchUserId = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,6 +458,7 @@ private extension ChatsTab {
|
|||||||
isGlobalSearchLoading = false
|
isGlobalSearchLoading = false
|
||||||
globalSearchError = nil
|
globalSearchError = nil
|
||||||
globalSearchTask = nil
|
globalSearchTask = nil
|
||||||
|
selectedSearchUserId = nil
|
||||||
}
|
}
|
||||||
} catch is CancellationError {
|
} catch is CancellationError {
|
||||||
// Ignore cancellation
|
// Ignore cancellation
|
||||||
@ -451,6 +469,7 @@ private extension ChatsTab {
|
|||||||
isGlobalSearchLoading = false
|
isGlobalSearchLoading = false
|
||||||
globalSearchError = friendlyErrorMessage(for: error)
|
globalSearchError = friendlyErrorMessage(for: error)
|
||||||
globalSearchTask = nil
|
globalSearchTask = nil
|
||||||
|
selectedSearchUserId = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,8 +481,37 @@ private extension ChatsTab {
|
|||||||
globalSearchResults = []
|
globalSearchResults = []
|
||||||
globalSearchError = nil
|
globalSearchError = nil
|
||||||
isGlobalSearchLoading = false
|
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 {
|
func friendlyErrorMessage(for error: Error) -> String {
|
||||||
if let searchError = error as? SearchServiceError {
|
if let searchError = error as? SearchServiceError {
|
||||||
return searchError.errorDescription ?? NSLocalizedString("Не удалось выполнить поиск.", comment: "Search error fallback")
|
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 {
|
private struct ChatRowView: View {
|
||||||
let chat: PrivateChatListItem
|
let chat: PrivateChatListItem
|
||||||
let currentUserId: String?
|
let currentUserId: String?
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user