From 807c747182e8e3e9c76dcd8779b91c1f0bca8d95 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Tue, 10 Jun 2025 13:35:48 -0700 Subject: [PATCH 1/6] fix filter --- Meshtastic/Views/Messages/UserList.swift | 409 +++++++++++------------ 1 file changed, 196 insertions(+), 213 deletions(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index a74e3fd2..45f922e9 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -21,7 +21,6 @@ struct UserList: View { @State private var isPkiEncrypted = false @State private var isFavorite = false @State private var isIgnored = false - @State private var isUnmessagable = false @State private var isEnvironment = false @State private var distanceFilter = false @State private var maxDistance: Double = 800000 @@ -40,18 +39,6 @@ struct UserList: View { roleFilter ]} - @FetchRequest( - sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), - NSSortDescriptor(key: "userNode.favorite", ascending: false), - NSSortDescriptor(key: "pkiEncrypted", ascending: false), - NSSortDescriptor(key: "userNode.lastHeard", ascending: false), - NSSortDescriptor(key: "longName", ascending: true)], - predicate: NSPredicate( - format: "userNode.ignored == NO AND unmessagable = NO" - ), animation: .spring - ) - var users: FetchedResults - @Binding var node: NodeInfoEntity? @Binding var userSelection: UserEntity? @@ -61,196 +48,162 @@ struct UserList: View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY") VStack { - List(users, selection: $userSelection) { (user: UserEntity) in - let mostRecent = user.messageList.last - let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) - let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 - let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 - if user.num != bleManager.connectedPeripheral?.num ?? 0 { - NavigationLink(value: user) { - ZStack { - Image(systemName: "circle.fill") - .opacity(user.unreadMessages > 0 ? 1 : 0) - .font(.system(size: 10)) - .foregroundColor(.accentColor) - .brightness(0.2) - } + FilteredUserList( + searchText: searchText, + viaLora: viaLora, + viaMqtt: viaMqtt, + isOnline: isOnline, + isPkiEncrypted: isPkiEncrypted, + isFavorite: isFavorite, + isIgnored: isIgnored, + isUnmessagable: false, + isEnvironment: isEnvironment, + distanceFilter: distanceFilter, + maxDistance: maxDistance, + hopsAway: hopsAway, + roleFilter: roleFilter, + deviceRoles: deviceRoles, + userSelection: $userSelection + ) { users in + List(users, selection: $userSelection) { (user: UserEntity) in + let mostRecent = user.messageList.last + let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) + let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 + let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 + if user.num != bleManager.connectedPeripheral?.num ?? 0 { + NavigationLink(value: user) { + ZStack { + Image(systemName: "circle.fill") + .opacity(user.unreadMessages > 0 ? 1 : 0) + .font(.system(size: 10)) + .foregroundColor(.accentColor) + .brightness(0.2) + } - CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num)))) + CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num)))) - VStack(alignment: .leading) { - HStack { - if user.pkiEncrypted { - if !user.keyMatch { - /// Public Key on the User and the Public Key on the Last Message don't match - Image(systemName: "key.slash") - .foregroundColor(.red) + VStack(alignment: .leading) { + HStack { + if user.pkiEncrypted { + if !user.keyMatch { + /// Public Key on the User and the Public Key on the Last Message don't match + Image(systemName: "key.slash") + .foregroundColor(.red) + } else { + Image(systemName: "lock.fill") + .foregroundColor(.green) + } } else { - Image(systemName: "lock.fill") - .foregroundColor(.green) + Image(systemName: "lock.open.fill") + .foregroundColor(.yellow) + } + Text(user.longName ?? "Unknown".localized) + .font(.headline) + .allowsTightening(true) + Spacer() + if user.userNode?.favorite ?? false { + Image(systemName: "star.fill") + .foregroundColor(.yellow) + } + if user.messageList.count > 0 { + if lastMessageDay == currentDay { + Text(lastMessageTime, style: .time ) + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay == (currentDay - 1) { + Text("Yesterday") + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { + Text(lastMessageTime.formattedDate(format: dateFormatString)) + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay < (currentDay - 1800) { + Text(lastMessageTime.formattedDate(format: dateFormatString)) + .font(.footnote) + .foregroundColor(.secondary) + } + } + } + + if user.messageList.count > 0 { + HStack(alignment: .top) { + Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") + .font(.footnote) + .foregroundColor(.secondary) + } + } + } + } + .frame(height: 62) + .contextMenu { + Button { + if node != nil && !(user.userNode?.favorite ?? false) { + let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num)) + if success { + user.userNode?.favorite = !(user.userNode?.favorite ?? false) + Logger.data.info("Favorited a node") } } else { - Image(systemName: "lock.open.fill") - .foregroundColor(.yellow) - } - Text(user.longName ?? "Unknown".localized) - .font(.headline) - .allowsTightening(true) - Spacer() - if user.userNode?.favorite ?? false { - Image(systemName: "star.fill") - .foregroundColor(.yellow) - } - if user.messageList.count > 0 { - if lastMessageDay == currentDay { - Text(lastMessageTime, style: .time ) - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay == (currentDay - 1) { - Text("Yesterday") - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { - Text(lastMessageTime.formattedDate(format: dateFormatString)) - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay < (currentDay - 1800) { - Text(lastMessageTime.formattedDate(format: dateFormatString)) - .font(.footnote) - .foregroundColor(.secondary) + let success = bleManager.removeFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num)) + if success { + user.userNode?.favorite = !(user.userNode?.favorite ?? false) + Logger.data.info("Unfavorited a node") } } - } - - if user.messageList.count > 0 { - HStack(alignment: .top) { - Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") - .font(.footnote) - .foregroundColor(.secondary) + context.refresh(user, mergeChanges: true) + do { + try context.save() + } catch { + context.rollback() + Logger.data.error("Save Node Favorite Error") } - } - } - } - .frame(height: 62) - .contextMenu { - Button { - - if node != nil && !(user.userNode?.favorite ?? false) { - let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num)) - if success { - user.userNode?.favorite = !(user.userNode?.favorite ?? true) - Logger.data.info("Favorited a node") - } - } else { - let success = bleManager.removeFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num)) - if success { - user.userNode?.favorite = !(user.userNode?.favorite ?? true) - Logger.data.info("Un Favorited a node") - } - } - context.refresh(user, mergeChanges: true) - do { - try context.save() - } catch { - context.rollback() - Logger.data.error("Save Node Favorite Error") - } - } label: { - Label((user.userNode?.favorite ?? false) ? "Un-Favorite" : "Favorite", systemImage: (user.userNode?.favorite ?? false) ? "star.slash.fill" : "star.fill") - } - Button { - user.mute = !user.mute - do { - try context.save() - } catch { - context.rollback() - Logger.data.error("Save User Mute Error") - } - } label: { - Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash") - } - if user.messageList.count > 0 { - Button(role: .destructive) { - isPresentingDeleteUserMessagesConfirm = true - userSelection = user } label: { - Label("Delete Messages", systemImage: "trash") + Label((user.userNode?.favorite ?? false) ? "Un-Favorite" : "Favorite", systemImage: (user.userNode?.favorite ?? false) ? "star.slash.fill" : "star.fill") + } + Button { + user.mute = !user.mute + do { + try context.save() + } catch { + context.rollback() + Logger.data.error("Save User Mute Error") + } + } label: { + Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash") + } + if user.messageList.count > 0 { + Button(role: .destructive) { + isPresentingDeleteUserMessagesConfirm = true + userSelection = user + } label: { + Label("Delete Messages", systemImage: "trash") + } } } - } - .confirmationDialog( - "This conversation will be deleted.", - isPresented: $isPresentingDeleteUserMessagesConfirm, - titleVisibility: .visible - ) { - Button(role: .destructive) { - deleteUserMessages(user: userSelection!, context: context) - context.refresh(node!.user!, mergeChanges: true) - } label: { - Text("Delete") + .confirmationDialog( + "This conversation will be deleted.", + isPresented: $isPresentingDeleteUserMessagesConfirm, + titleVisibility: .visible + ) { + Button(role: .destructive) { + deleteUserMessages(user: userSelection!, context: context) + context.refresh(node!.user!, mergeChanges: true) + } label: { + Text("Delete") + } } } } + .listStyle(.plain) + .navigationTitle(String.localizedStringWithFormat("Contacts (%@)", String(users.count == 0 ? 0 : users.count - 1))) } - .listStyle(.plain) - .navigationTitle(String.localizedStringWithFormat("Contacts (%@)".localized, String(users.count == 0 ? 0 : users.count - 1))) .sheet(isPresented: $editingFilters) { NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isIgnored: $isIgnored, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles) } .sheet(isPresented: $showingHelp) { DirectMessagesHelp() } - .onChange(of: searchText) { - Task { - await searchUserList() - } - } - .onChange(of: viaLora) { - if !viaLora && !viaMqtt { - viaMqtt = true - } - Task { - await searchUserList() - } - } - .onChange(of: viaMqtt) { - if !viaLora && !viaMqtt { - viaLora = true - } - Task { - await searchUserList() - } - } - .onChange(of: [deviceRoles]) { - Task { - await searchUserList() - } - } - .onChange(of: hopsAway) { - Task { - await searchUserList() - } - } - .onChange(of: [boolFilters]) { - Task { - await searchUserList() - } - } - .onChange(of: maxDistance) { - Task { - await searchUserList() - } - } - .onChange(of: isPkiEncrypted) { - Task { - await searchUserList() - } - } - .onFirstAppear { - Task { - await searchUserList() - } - } .safeAreaInset(edge: .bottom, alignment: .leading) { HStack { Button(action: { @@ -281,36 +234,61 @@ struct UserList: View { .padding(5) } .padding(.bottom, 5) - .padding(.bottom, 5) - .searchable(text: $searchText, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact") + .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Find a contact") .disableAutocorrection(true) .scrollDismissesKeyboard(.immediately) } } - private func searchUserList() async { +} - /// Case Insensitive Search Text Predicates - let searchPredicates = ["userId", "numString", "hwModel", "hwDisplayName", "longName", "shortName"].map { property in - return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText) - } - /// Create a compound predicate using each text search preicate as an OR - let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates) - /// Create an array of predicates to hold our AND predicates +struct FilteredUserList: View { + @FetchRequest var fetchRequest: FetchedResults + let content: (FetchedResults) -> Content + + var body: some View { + content(fetchRequest) + } + + init( + searchText: String, + viaLora: Bool, + viaMqtt: Bool, + isOnline: Bool, + isPkiEncrypted: Bool, + isFavorite: Bool, + isIgnored: Bool, + isUnmessagable: Bool, + isEnvironment: Bool, + distanceFilter: Bool, + maxDistance: Double, + hopsAway: Double, + roleFilter: Bool, + deviceRoles: Set, + userSelection: Binding, + @ViewBuilder content: @escaping (FetchedResults) -> Content + ) { + self.content = content + // Build predicates based on filter variables var predicates: [NSPredicate] = [] - /// Mqtt and lora + // Search text predicates + if !searchText.isEmpty { + let searchPredicates = ["userId", "numString", "hwModel", "hwDisplayName", "longName", "shortName"].map { property in + return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText) + } + let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates) + predicates.append(textSearchPredicate) + } + // Mqtt and lora if !(viaLora && viaMqtt) { if viaLora { let loraPredicate = NSPredicate(format: "userNode.viaMqtt == NO") predicates.append(loraPredicate) } else { - let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES") + let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES AND userNode.hopsAway == 0") predicates.append(mqttPredicate) } - } else { - let mqttPredicate = NSPredicate(format: "NOT (userNode.viaMqtt == YES)") - predicates.append(mqttPredicate) } - /// Roles + // Roles if roleFilter && deviceRoles.count > 0 { var rolesArray: [NSPredicate] = [] for dr in deviceRoles { @@ -320,7 +298,7 @@ struct UserList: View { let compoundPredicate = NSCompoundPredicate(type: .or, subpredicates: rolesArray) predicates.append(compoundPredicate) } - /// Hops Away + // Hops Away if hopsAway == 0.0 { let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway == %i", Int32(hopsAway)) predicates.append(hopsAwayPredicate) @@ -328,32 +306,29 @@ struct UserList: View { let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway > 0 AND userNode.hopsAway <= %i", Int32(hopsAway)) predicates.append(hopsAwayPredicate) } - /// Online + // Online if isOnline { let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -120, to: Date())! as NSDate) predicates.append(isOnlinePredicate) } - /// Encrypted + // Encrypted if isPkiEncrypted { let isPkiEncryptedPredicate = NSPredicate(format: "pkiEncrypted == YES") predicates.append(isPkiEncryptedPredicate) } - /// Favorites + // Favorites if isFavorite { let isFavoritePredicate = NSPredicate(format: "userNode.favorite == YES") predicates.append(isFavoritePredicate) } - /// Ignored + // Always apply these base filters let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == NO") predicates.append(isIgnoredPredicate) - /// Unmessagable let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO") predicates.append(isUnmessagablePredicate) - - /// Distance + // Distance if distanceFilter { let pointOfInterest = LocationsHandler.currentLocation - if pointOfInterest.latitude != LocationsHandler.DefaultLocation.latitude && pointOfInterest.longitude != LocationsHandler.DefaultLocation.longitude { let d: Double = maxDistance * 1.1 let r: Double = 6371009 @@ -368,11 +343,19 @@ struct UserList: View { predicates.append(distancePredicate) } } - if !searchText.isEmpty { - let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates) - users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, filterPredicates]) - } else { - users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates) - } + // Combine all predicates + let finalPredicate = predicates.isEmpty ? NSPredicate(value: true) : NSCompoundPredicate(type: .and, subpredicates: predicates) + // Initialize the fetch request with the combined predicate + _fetchRequest = FetchRequest( + sortDescriptors: [ + NSSortDescriptor(key: "lastMessage", ascending: false), + NSSortDescriptor(key: "userNode.favorite", ascending: false), + NSSortDescriptor(key: "pkiEncrypted", ascending: false), + NSSortDescriptor(key: "userNode.lastHeard", ascending: false), + NSSortDescriptor(key: "longName", ascending: true) + ], + predicate: finalPredicate, + animation: .spring + ) } } From 7a5330dd4b997464cdc4c32196b50991b5f06d3d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 10 Jun 2025 22:47:18 -0700 Subject: [PATCH 2/6] Slight updates to filter and title --- Meshtastic/Views/Messages/UserList.swift | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 45f922e9..b3e6affd 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -196,7 +196,7 @@ struct UserList: View { } } .listStyle(.plain) - .navigationTitle(String.localizedStringWithFormat("Contacts (%@)", String(users.count == 0 ? 0 : users.count - 1))) + .navigationTitle(String.localizedStringWithFormat("Contacts (%@)", String(users.count))) } .sheet(isPresented: $editingFilters) { NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isIgnored: $isIgnored, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles) @@ -244,11 +244,11 @@ struct UserList: View { struct FilteredUserList: View { @FetchRequest var fetchRequest: FetchedResults let content: (FetchedResults) -> Content - + var body: some View { content(fetchRequest) } - + init( searchText: String, viaLora: Bool, @@ -299,7 +299,7 @@ struct FilteredUserList: View { predicates.append(compoundPredicate) } // Hops Away - if hopsAway == 0.0 { + if hopsAway == 0 { let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway == %i", Int32(hopsAway)) predicates.append(hopsAwayPredicate) } else if hopsAway > -1.0 { @@ -321,11 +321,14 @@ struct FilteredUserList: View { let isFavoritePredicate = NSPredicate(format: "userNode.favorite == YES") predicates.append(isFavoritePredicate) } - // Always apply these base filters - let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == NO") - predicates.append(isIgnoredPredicate) - let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO") - predicates.append(isUnmessagablePredicate) + // Ignored + if isIgnored { + let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == YES") + predicates.append(isIgnoredPredicate) + } else if !isIgnored { + let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == NO") + predicates.append(isIgnoredPredicate) + } // Distance if distanceFilter { let pointOfInterest = LocationsHandler.currentLocation @@ -343,6 +346,9 @@ struct FilteredUserList: View { predicates.append(distancePredicate) } } + // Always apply unmessagable filter + let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO") + predicates.append(isUnmessagablePredicate) // Combine all predicates let finalPredicate = predicates.isEmpty ? NSPredicate(value: true) : NSCompoundPredicate(type: .and, subpredicates: predicates) // Initialize the fetch request with the combined predicate From eda9bdf8adbe62308b82c46f69be3151b164778c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 10 Jun 2025 22:57:00 -0700 Subject: [PATCH 3/6] Update Meshtastic/Views/Messages/UserList.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Meshtastic/Views/Messages/UserList.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index b3e6affd..1077f911 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -284,7 +284,7 @@ struct FilteredUserList: View { let loraPredicate = NSPredicate(format: "userNode.viaMqtt == NO") predicates.append(loraPredicate) } else { - let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES AND userNode.hopsAway == 0") + let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES") predicates.append(mqttPredicate) } } From 7b8980f1edff4cbe7cba4811f96e2ea16a625027 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 10 Jun 2025 23:00:26 -0700 Subject: [PATCH 4/6] Remove unmessagable filter as it is static for the contact list --- Meshtastic/Views/Messages/UserList.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index b3e6affd..7cb9ca55 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -56,7 +56,6 @@ struct UserList: View { isPkiEncrypted: isPkiEncrypted, isFavorite: isFavorite, isIgnored: isIgnored, - isUnmessagable: false, isEnvironment: isEnvironment, distanceFilter: distanceFilter, maxDistance: maxDistance, @@ -257,7 +256,6 @@ struct FilteredUserList: View { isPkiEncrypted: Bool, isFavorite: Bool, isIgnored: Bool, - isUnmessagable: Bool, isEnvironment: Bool, distanceFilter: Bool, maxDistance: Double, From 084da97da62ee0fa83c605e462eb2050b17de004 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 10 Jun 2025 23:12:57 -0700 Subject: [PATCH 5/6] Dont be dumb --- Meshtastic/Views/Messages/UserList.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 09bc8045..37749998 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -344,9 +344,11 @@ struct FilteredUserList: View { predicates.append(distancePredicate) } } - // Always apply unmessagable filter + // Always apply unmessagable and connected node filters let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO") predicates.append(isUnmessagablePredicate) + let isConnectedNodePredicate = NSPredicate(format: "NOT (numString CONTAINS \(UserDefaults.preferredPeripheralNum))") + predicates.append(isConnectedNodePredicate) // Combine all predicates let finalPredicate = predicates.isEmpty ? NSPredicate(value: true) : NSCompoundPredicate(type: .and, subpredicates: predicates) // Initialize the fetch request with the combined predicate From 2e92b838c4bd76be1de01298f7f0a98b584181d0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 10 Jun 2025 23:16:19 -0700 Subject: [PATCH 6/6] Update Meshtastic/Views/Messages/UserList.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Meshtastic/Views/Messages/UserList.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 37749998..079b9593 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -347,7 +347,7 @@ struct FilteredUserList: View { // Always apply unmessagable and connected node filters let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO") predicates.append(isUnmessagablePredicate) - let isConnectedNodePredicate = NSPredicate(format: "NOT (numString CONTAINS \(UserDefaults.preferredPeripheralNum))") + let isConnectedNodePredicate = NSPredicate(format: "NOT (numString CONTAINS %@)", UserDefaults.preferredPeripheralNum) predicates.append(isConnectedNodePredicate) // Combine all predicates let finalPredicate = predicates.isEmpty ? NSPredicate(value: true) : NSCompoundPredicate(type: .and, subpredicates: predicates)