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,43 +362,58 @@ struct ChatsTab: View {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private func globalSearchRow(for user: UserSearchResult) -> some View {
 | 
					    private func globalSearchRow(for user: UserSearchResult) -> some View {
 | 
				
			||||||
        HStack(spacing: 12) {
 | 
					        Button {
 | 
				
			||||||
            Circle()
 | 
					            selectedSearchUserId = user.userId
 | 
				
			||||||
                .fill(user.isOfficial ? Color.accentColor.opacity(0.85) : Color.accentColor.opacity(0.15))
 | 
					        } label: {
 | 
				
			||||||
                .frame(width: 44, height: 44)
 | 
					            HStack(spacing: 12) {
 | 
				
			||||||
                .overlay(
 | 
					                Circle()
 | 
				
			||||||
                    Text(user.avatarInitial)
 | 
					                    .fill(user.isOfficial ? Color.accentColor.opacity(0.85) : Color.accentColor.opacity(0.15))
 | 
				
			||||||
                        .font(.headline)
 | 
					                    .frame(width: 44, height: 44)
 | 
				
			||||||
                        .foregroundColor(user.isOfficial ? .white : Color.accentColor)
 | 
					                    .overlay(
 | 
				
			||||||
                )
 | 
					                        Text(user.avatarInitial)
 | 
				
			||||||
 | 
					                            .font(.headline)
 | 
				
			||||||
 | 
					                            .foregroundColor(user.isOfficial ? .white : Color.accentColor)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            VStack(alignment: .leading, spacing: 4) {
 | 
					                VStack(alignment: .leading, spacing: 4) {
 | 
				
			||||||
                HStack(spacing: 6) {
 | 
					                    HStack(spacing: 6) {
 | 
				
			||||||
                    Text(user.displayName)
 | 
					                        Text(user.displayName)
 | 
				
			||||||
                        .fontWeight(user.isOfficial ? .semibold : .regular)
 | 
					                            .fontWeight(user.isOfficial ? .semibold : .regular)
 | 
				
			||||||
                        .foregroundColor(.primary)
 | 
					                            .foregroundColor(.primary)
 | 
				
			||||||
                        .lineLimit(1)
 | 
					                            .lineLimit(1)
 | 
				
			||||||
                        .truncationMode(.tail)
 | 
					                            .truncationMode(.tail)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if user.isOfficial {
 | 
					                        if user.isOfficial {
 | 
				
			||||||
                        Image(systemName: "checkmark.seal.fill")
 | 
					                            Image(systemName: "checkmark.seal.fill")
 | 
				
			||||||
                            .foregroundColor(Color.accentColor)
 | 
					                                .foregroundColor(Color.accentColor)
 | 
				
			||||||
                            .font(.caption)
 | 
					                                .font(.caption)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if let secondary = secondaryLine(for: user) {
 | 
				
			||||||
 | 
					                        Text(secondary)
 | 
				
			||||||
 | 
					                            .font(.footnote)
 | 
				
			||||||
 | 
					                            .foregroundColor(.secondary)
 | 
				
			||||||
 | 
					                            .lineLimit(1)
 | 
				
			||||||
 | 
					                            .truncationMode(.tail)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                .frame(maxWidth: .infinity, alignment: .leading)
 | 
				
			||||||
                if let secondary = user.secondaryLabelForSearch {
 | 
					 | 
				
			||||||
                    Text(secondary)
 | 
					 | 
				
			||||||
                        .font(.footnote)
 | 
					 | 
				
			||||||
                        .foregroundColor(.secondary)
 | 
					 | 
				
			||||||
                        .lineLimit(1)
 | 
					 | 
				
			||||||
                        .truncationMode(.tail)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            .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