diff --git a/bitchat/Views/BridgePeopleList.swift b/bitchat/Views/BridgePeopleList.swift index 89162397..0ab2463d 100644 --- a/bitchat/Views/BridgePeopleList.swift +++ b/bitchat/Views/BridgePeopleList.swift @@ -39,6 +39,8 @@ struct PeopleSectionHeader: View { struct BridgePeopleList: View { @ObservedObject private var bridgeService = BridgeService.shared @ThemedPalette private var palette + /// People-sheet search query; empty means show every row. + var nameFilter: String = "" private enum Strings { static let sectionTitle = String(localized: "bridge_people.section_title", defaultValue: "across the bridge", comment: "Section header in the people sheet for participants reachable via the mesh bridge") @@ -50,7 +52,10 @@ struct BridgePeopleList: View { // (a serving neighbor's carriers) even while this device's own // bridge is off — whoever is visible in the timeline belongs in the // sheet. - if !bridgeService.bridgedParticipants.isEmpty { + let people = bridgeService.bridgedParticipants.filter { + PeopleNameFilter.matches($0.displayName, query: nameFilter) + } + if !people.isEmpty { VStack(alignment: .leading, spacing: 0) { PeopleSectionHeader( icon: "network", @@ -58,7 +63,7 @@ struct BridgePeopleList: View { title: Strings.sectionTitle ) - ForEach(bridgeService.bridgedParticipants) { person in + ForEach(people) { person in HStack(spacing: 4) { Text(person.displayName) .bitchatFont(size: 14) diff --git a/bitchat/Views/GeohashPeopleList.swift b/bitchat/Views/GeohashPeopleList.swift index 9613f227..70b8189c 100644 --- a/bitchat/Views/GeohashPeopleList.swift +++ b/bitchat/Views/GeohashPeopleList.swift @@ -4,13 +4,16 @@ struct GeohashPeopleList: View { @EnvironmentObject private var peerListModel: PeerListModel @ThemedPalette private var palette let onTapPerson: () -> Void + /// People-sheet search query; empty means show every row. + var nameFilter: String = "" @Environment(\.colorScheme) var colorScheme @State private var orderedIDs: [String] = [] private enum Strings { static let noneNearby: LocalizedStringKey = "geohash_people.none_nearby" + static let noMatches: LocalizedStringKey = "content.people.search.no_matches" static let youSuffix: LocalizedStringKey = "geohash_people.you_suffix" - static let blockedTooltip = String(localized: "geohash_people.tooltip.blocked", comment: "Tooltip shown next to users blocked in geohash channels") + static let blockedTooltip = String(localized: "geohash_people.tooltip.blocked", comment: "Tooltip shown next to people blocked in geohash channels") static let unblock: LocalizedStringKey = "geohash_people.action.unblock" static let block: LocalizedStringKey = "geohash_people.action.block" static let unblockText = String(localized: "geohash_people.action.unblock", comment: "Context menu action to unblock a person") @@ -32,7 +35,19 @@ struct GeohashPeopleList: View { .padding(.top, 12) } } else { - let people = peerListModel.geohashPeople + let people = peerListModel.geohashPeople.filter { + PeopleNameFilter.matches($0.displayName, query: nameFilter) + } + let filterActive = !nameFilter.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + if people.isEmpty && filterActive { + VStack(alignment: .leading, spacing: 0) { + Text(Strings.noMatches) + .bitchatFont(size: 14) + .foregroundColor(palette.secondary) + .padding(.horizontal) + .padding(.top, 12) + } + } else { let currentIDs = people.map(\.id) let displayIDs = orderedIDs.filter { currentIDs.contains($0) } + currentIDs.filter { !orderedIDs.contains($0) } @@ -152,6 +167,7 @@ struct GeohashPeopleList: View { for id in ids where !newOrder.contains(id) { newOrder.append(id) } if newOrder != orderedIDs { orderedIDs = newOrder } } + } } } diff --git a/bitchat/Views/GroupChatList.swift b/bitchat/Views/GroupChatList.swift index 4dff5c1d..e79d75b7 100644 --- a/bitchat/Views/GroupChatList.swift +++ b/bitchat/Views/GroupChatList.swift @@ -8,6 +8,8 @@ struct GroupChatList: View { let groups: [GroupChatRow] let onTapGroup: (PeerID) -> Void + /// People-sheet search query; empty means show every row. + var nameFilter: String = "" private enum Strings { static let header = String(localized: "groups.section.header", comment: "Section header above the private groups list") @@ -19,7 +21,8 @@ struct GroupChatList: View { } var body: some View { - if !groups.isEmpty { + let visible = groups.filter { PeopleNameFilter.matches($0.name, query: nameFilter) } + if !visible.isEmpty { VStack(alignment: .leading, spacing: 0) { // Same glyph+label header shape as #mesh / across the bridge. PeopleSectionHeader( @@ -28,7 +31,7 @@ struct GroupChatList: View { title: Strings.header ) - ForEach(groups) { group in + ForEach(visible) { group in HStack(spacing: 4) { Text("#\(group.name)") .bitchatFont(size: 14) diff --git a/bitchat/Views/MeshPeerList.swift b/bitchat/Views/MeshPeerList.swift index 1d94af5f..6dc42f09 100644 --- a/bitchat/Views/MeshPeerList.swift +++ b/bitchat/Views/MeshPeerList.swift @@ -10,12 +10,15 @@ struct MeshPeerList: View { /// Optional so existing call sites (and previews/tests) keep compiling; /// when absent the block/unblock context-menu entry is hidden. var onToggleBlock: ((MeshPeerRow) -> Void)? = nil + /// People-sheet search query; empty means show every row. + var nameFilter: String = "" @Environment(\.colorScheme) var colorScheme @State private var orderedIDs: [String] = [] private enum Strings { static let noneNearby: LocalizedStringKey = "geohash_people.none_nearby" + static let noMatches: LocalizedStringKey = "content.people.search.no_matches" static let blockedTooltip = String(localized: "geohash_people.tooltip.blocked", comment: "Tooltip shown next to a blocked peer indicator") static let newMessagesTooltip = String(localized: "mesh_peers.tooltip.new_messages", comment: "Tooltip for the unread messages indicator") static let connected = String(localized: "content.accessibility.connected_mesh", comment: "Accessibility label for mesh-connected peer indicator") @@ -41,7 +44,8 @@ struct MeshPeerList: View { let displayIDs = orderedIDs.filter { currentIDs.contains($0) } + currentIDs.filter { !orderedIDs.contains($0) } let peers: [MeshPeerRow] = displayIDs.compactMap { id in peerListModel.meshRows.first(where: { $0.id == id }) - } + }.filter { PeopleNameFilter.matches($0.displayName, query: nameFilter) } + let filterActive = !nameFilter.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty if peerListModel.meshRows.isEmpty { // Match the section's row rhythm (same size, indent, and vertical @@ -52,6 +56,12 @@ struct MeshPeerList: View { .foregroundColor(palette.secondary) .padding(.horizontal) .padding(.vertical, 4) + } else if peers.isEmpty && filterActive { + Text(Strings.noMatches) + .bitchatFont(size: 14) + .foregroundColor(palette.secondary) + .padding(.horizontal) + .padding(.vertical, 4) } else { VStack(alignment: .leading, spacing: 0) { ForEach(0..