mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-11-01 07:53:26 +00:00
feat: filter mesh, geohash, bridge, and group rows by name
Each people-sheet section accepts an optional nameFilter so a shared search field can narrow nicknames without reshaping PeerListModel.
This commit is contained in:
parent
5b5adf2e2e
commit
addcc3e701
@ -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)
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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..<peers.count, id: \.self) { idx in
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user