mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #744 from meshtastic/multiselect_node_filters
Multiselect node filters
This commit is contained in:
commit
26d02beb5d
6 changed files with 130 additions and 58 deletions
|
|
@ -139,9 +139,15 @@
|
|||
},
|
||||
"%d%%" : {
|
||||
|
||||
},
|
||||
"%lf" : {
|
||||
|
||||
},
|
||||
"%lld" : {
|
||||
|
||||
},
|
||||
"%lld or less hops away" : {
|
||||
|
||||
},
|
||||
"%lld Readings Total" : {
|
||||
|
||||
|
|
@ -166,6 +172,15 @@
|
|||
},
|
||||
"1 byte" : {
|
||||
|
||||
},
|
||||
"1 hop away" : {
|
||||
|
||||
},
|
||||
"7" : {
|
||||
|
||||
},
|
||||
"8" : {
|
||||
|
||||
},
|
||||
"25" : {
|
||||
|
||||
|
|
@ -666,10 +681,10 @@
|
|||
"Alert when receiving a message" : {
|
||||
|
||||
},
|
||||
"All device and app data will be deleted. You will also need to forget your devices under Settings > Bluetooth." : {
|
||||
"All" : {
|
||||
|
||||
},
|
||||
"All Roles" : {
|
||||
"All device and app data will be deleted. You will also need to forget your devices under Settings > Bluetooth." : {
|
||||
|
||||
},
|
||||
"Allow Position Requests" : {
|
||||
|
|
@ -873,9 +888,6 @@
|
|||
},
|
||||
"An open source, off-grid, decentralized, mesh network that runs on affordable, low-power radios." : {
|
||||
|
||||
},
|
||||
"Any" : {
|
||||
|
||||
},
|
||||
"Any missed messages will be delivered again." : {
|
||||
|
||||
|
|
@ -17386,6 +17398,9 @@
|
|||
},
|
||||
"Role: %@" : {
|
||||
|
||||
},
|
||||
"Roles" : {
|
||||
|
||||
},
|
||||
"Root Topic" : {
|
||||
|
||||
|
|
@ -22405,4 +22420,4 @@
|
|||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -645,6 +645,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
do {
|
||||
disconnectPeripheral(reconnect: false)
|
||||
try container.restorePersistentStore(from: databasePath)
|
||||
context?.refreshAllObjects()
|
||||
let request = MyInfoEntity.fetchRequest()
|
||||
try context?.fetch(request)
|
||||
UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ struct UserList: View {
|
|||
@State private var isFavorite = false
|
||||
@State private var distanceFilter = false
|
||||
@State private var maxDistance: Double = 800000
|
||||
@State private var hopsAway: Int = -1
|
||||
@State private var deviceRole: Int = -1
|
||||
@State private var hopsAway: Double = -1.0
|
||||
@State private var roleFilter = false
|
||||
@State private var deviceRoles: Set<Int> = []
|
||||
@State var isEditingFilters = false
|
||||
|
||||
@FetchRequest(
|
||||
|
|
@ -170,7 +171,7 @@ struct UserList: View {
|
|||
.listStyle(.plain)
|
||||
.navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1)))
|
||||
.sheet(isPresented: $isEditingFilters) {
|
||||
NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isFavorite: $isFavorite, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, deviceRole: $deviceRole)
|
||||
NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isFavorite: $isFavorite, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles)
|
||||
}
|
||||
.onChange(of: searchText) { _ in
|
||||
searchUserList()
|
||||
|
|
@ -187,7 +188,7 @@ struct UserList: View {
|
|||
}
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: deviceRole) { _ in
|
||||
.onChange(of: [deviceRoles]) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: hopsAway) { _ in
|
||||
|
|
@ -259,17 +260,25 @@ struct UserList: View {
|
|||
predicates.append(mqttPredicate)
|
||||
}
|
||||
}
|
||||
/// Role
|
||||
if deviceRole > -1 {
|
||||
let rolePredicate = NSPredicate(format: "role == %i", Int32(deviceRole))
|
||||
predicates.append(rolePredicate)
|
||||
/// Roles
|
||||
if roleFilter && deviceRoles.count > 0 {
|
||||
var rolesArray: [NSPredicate] = []
|
||||
for dr in deviceRoles {
|
||||
let deviceRolePredicate = NSPredicate(format: "role == %i", Int32(dr))
|
||||
rolesArray.append(deviceRolePredicate)
|
||||
}
|
||||
let compoundPredicate = NSCompoundPredicate(type: .or, subpredicates: rolesArray)
|
||||
predicates.append(compoundPredicate)
|
||||
}
|
||||
/// Hops Away
|
||||
if hopsAway > 0 {
|
||||
if hopsAway == 0.0 {
|
||||
let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway == %i", Int32(hopsAway))
|
||||
predicates.append(hopsAwayPredicate)
|
||||
} else if hopsAway > -1.0 {
|
||||
let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway > 0 AND userNode.hopsAway <= %i", Int32(hopsAway))
|
||||
predicates.append(hopsAwayPredicate)
|
||||
}
|
||||
}
|
||||
|
||||
/// Online
|
||||
if isOnline {
|
||||
let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
|||
|
||||
struct NodeListFilter: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
/// Filters
|
||||
@State var editMode = EditMode.active
|
||||
var filterTitle = "Node Filters"
|
||||
@Binding var viaLora: Bool
|
||||
@Binding var viaMqtt: Bool
|
||||
|
|
@ -18,8 +18,9 @@ struct NodeListFilter: View {
|
|||
@Binding var isFavorite: Bool
|
||||
@Binding var distanceFilter: Bool
|
||||
@Binding var maximumDistance: Double
|
||||
@Binding var hopsAway: Int
|
||||
@Binding var deviceRole: Int
|
||||
@Binding var hopsAway: Double
|
||||
@Binding var roleFilter: Bool
|
||||
@Binding var deviceRoles: Set<Int>
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -97,34 +98,50 @@ struct NodeListFilter: View {
|
|||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Label("Hops Away", systemImage: "hare")
|
||||
Picker("", selection: $hopsAway) {
|
||||
Text("Any")
|
||||
.tag(-1)
|
||||
Text("Direct")
|
||||
.tag(0)
|
||||
ForEach(1..<8) {
|
||||
Text("\($0)")
|
||||
.tag($0)
|
||||
}
|
||||
Slider(
|
||||
value: $hopsAway,
|
||||
in: -1...7,
|
||||
step: 1
|
||||
) {
|
||||
Text("Speed")
|
||||
} minimumValueLabel: {
|
||||
Text("All")
|
||||
} maximumValueLabel: {
|
||||
Text("7")
|
||||
}
|
||||
if hopsAway >= 0 {
|
||||
if hopsAway == 0 {
|
||||
Text("Direct")
|
||||
} else if hopsAway == 1 {
|
||||
Text("1 hop away")
|
||||
} else {
|
||||
Text("\(Int(hopsAway)) or less hops away") }
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
HStack {
|
||||
Label("Device Role", systemImage: "apps.iphone")
|
||||
Picker("", selection: $deviceRole) {
|
||||
Text("All Roles")
|
||||
.tag(-1)
|
||||
ForEach(DeviceRoles.allCases) { dr in
|
||||
Toggle(isOn: $roleFilter) {
|
||||
|
||||
Label {
|
||||
Text("Roles")
|
||||
} icon: {
|
||||
Image(systemName: "apps.iphone")
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
if roleFilter {
|
||||
VStack {
|
||||
List(DeviceRoles.allCases, selection: $deviceRoles) { dr in
|
||||
Label {
|
||||
Text(" \(dr.name)")
|
||||
} icon: {
|
||||
Image(systemName: dr.systemName)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.environment(\.editMode, $editMode) /// bind it here!
|
||||
.frame(minHeight: 490, maxHeight: .infinity)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -141,7 +158,7 @@ struct NodeListFilter: View {
|
|||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.fraction(0.6), .fraction(0.75)])
|
||||
.presentationDetents([.fraction(roleFilter ? 1.0 : 0.65)])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ struct NodeList: View {
|
|||
@State private var isFavorite = false
|
||||
@State private var distanceFilter = false
|
||||
@State private var maxDistance: Double = 800000
|
||||
@State private var hopsAway: Int = -1
|
||||
@State private var deviceRole: Int = -1
|
||||
@State private var hopsAway: Double = -1.0
|
||||
@State private var roleFilter = false
|
||||
@State private var deviceRoles: Set<Int> = []
|
||||
|
||||
@State var isEditingFilters = false
|
||||
|
||||
|
|
@ -188,7 +189,7 @@ struct NodeList: View {
|
|||
}
|
||||
}
|
||||
.sheet(isPresented: $isEditingFilters) {
|
||||
NodeListFilter(viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isFavorite: $isFavorite, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, deviceRole: $deviceRole)
|
||||
NodeListFilter(viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isFavorite: $isFavorite, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles)
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, alignment: .trailing) {
|
||||
HStack {
|
||||
|
|
@ -283,37 +284,55 @@ struct NodeList: View {
|
|||
}
|
||||
.navigationSplitViewStyle(.balanced)
|
||||
.onChange(of: searchText) { _ in
|
||||
searchNodeList()
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: viaLora) { _ in
|
||||
if !viaLora && !viaMqtt {
|
||||
viaMqtt = true
|
||||
}
|
||||
searchNodeList()
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: viaMqtt) { _ in
|
||||
if !viaLora && !viaMqtt {
|
||||
viaLora = true
|
||||
}
|
||||
searchNodeList()
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: deviceRole) { _ in
|
||||
searchNodeList()
|
||||
.onChange(of: [deviceRoles]) { _ in
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: hopsAway) { _ in
|
||||
searchNodeList()
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: isOnline) { _ in
|
||||
searchNodeList()
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: isFavorite) { _ in
|
||||
searchNodeList()
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: maxDistance) { _ in
|
||||
searchNodeList()
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: distanceFilter) { _ in
|
||||
searchNodeList()
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: (appState.navigationPath)) { newPath in
|
||||
|
||||
|
|
@ -338,11 +357,13 @@ struct NodeList: View {
|
|||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
searchNodeList()
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func searchNodeList() {
|
||||
private func searchNodeList() async -> Void {
|
||||
/// Case Insensitive Search Text Predicates
|
||||
let searchPredicates = ["user.userId", "user.numString", "user.hwModel", "user.longName", "user.shortName"].map { property in
|
||||
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
|
||||
|
|
@ -362,14 +383,23 @@ struct NodeList: View {
|
|||
}
|
||||
}
|
||||
/// Role
|
||||
if deviceRole > -1 {
|
||||
let rolePredicate = NSPredicate(format: "user.role == %i", Int32(deviceRole))
|
||||
predicates.append(rolePredicate)
|
||||
if roleFilter && deviceRoles.count > 0 {
|
||||
var rolesArray: [NSPredicate] = []
|
||||
for dr in deviceRoles {
|
||||
let deviceRolePredicate = NSPredicate(format: "user.role == %i", Int32(dr))
|
||||
rolesArray.append(deviceRolePredicate)
|
||||
}
|
||||
let compoundPredicate = NSCompoundPredicate(type: .or, subpredicates: rolesArray)
|
||||
predicates.append(compoundPredicate)
|
||||
}
|
||||
/// Hops Away
|
||||
if hopsAway > 0 {
|
||||
if hopsAway == 0.0 {
|
||||
let hopsAwayPredicate = NSPredicate(format: "hopsAway == %i", Int32(hopsAway))
|
||||
predicates.append(hopsAwayPredicate)
|
||||
} else if hopsAway > -1.0 {
|
||||
let hopsAwayPredicate = NSPredicate(format: "hopsAway > 0 AND hopsAway <= %i", Int32(hopsAway))
|
||||
predicates.append(hopsAwayPredicate)
|
||||
}
|
||||
}
|
||||
|
||||
/// Online
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ struct AppLogFilter: View {
|
|||
var filterTitle = "App Log Filters"
|
||||
@Binding var categories: Set<Int>
|
||||
@Binding var levels: Set<Int>
|
||||
@State var editMode = EditMode.active /// the edit mode
|
||||
@State var editMode = EditMode.active
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue