Add node filters

This commit is contained in:
Garth Vander Houwen 2024-03-26 07:54:16 -07:00
parent 525b1b2509
commit 496451c15c
10 changed files with 344 additions and 146 deletions

View file

@ -186,6 +186,7 @@
DDDB445229F8ACF900EE2349 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB445129F8ACF900EE2349 /* Date.swift */; };
DDDB445429F8AD1600EE2349 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB445329F8AD1600EE2349 /* Data.swift */; };
DDDC22382BA92344002C44F1 /* MeshMapContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDC22372BA92344002C44F1 /* MeshMapContent.swift */; };
DDDCD5702BB26F5C00BE6B60 /* NodeListFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDCD56F2BB26F5C00BE6B60 /* NodeListFilter.swift */; };
DDDE59F529AF163D00490C6C /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD41A61C29AE7E8E003C5A37 /* WidgetKit.framework */; };
DDDE59F629AF163D00490C6C /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD41A61E29AE7E8F003C5A37 /* SwiftUI.framework */; };
DDDE59F929AF163D00490C6C /* WidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE59F829AF163D00490C6C /* WidgetsBundle.swift */; };
@ -458,6 +459,7 @@
DDDC22312BA76701002C44F1 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
DDDC22322BA76961002C44F1 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
DDDC22372BA92344002C44F1 /* MeshMapContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshMapContent.swift; sourceTree = "<group>"; };
DDDCD56F2BB26F5C00BE6B60 /* NodeListFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListFilter.swift; sourceTree = "<group>"; };
DDDD527729B5B83F0045BC3C /* MeshtasticDataModelV9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV9.xcdatamodel; sourceTree = "<group>"; };
DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
DDDE59F829AF163D00490C6C /* WidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsBundle.swift; sourceTree = "<group>"; };
@ -960,6 +962,7 @@
DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */,
DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */,
DDDB26412AABF655003AFCB7 /* NodeListItem.swift */,
DDDCD56F2BB26F5C00BE6B60 /* NodeListFilter.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -1355,6 +1358,7 @@
DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */,
DD5E520E298EE33B00D21B61 /* mqtt.pb.swift in Sources */,
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */,
DDDCD5702BB26F5C00BE6B60 /* NodeListFilter.swift in Sources */,
DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */,
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
DDDB443D29F6592F00EE2349 /* NetworkManager.swift in Sources */,

View file

@ -56,7 +56,7 @@ enum MeshMapDistances: Double, CaseIterable, Identifiable {
case twoHundredMiles = 321869
case fiveHundredMiles = 804672
case oneThousandMiles = 1609000
case twoThousandMiles = 3218688
case twentyFiveHundredMiles = 4023360
var id: Double { self.rawValue }
var description: String {
let distanceFormatter = MKDistanceFormatter()

View file

@ -74,11 +74,13 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
var systemName: String {
switch self {
case .client:
return "iphone.gen3.radiowaves.left.and.right"
return "apps.iphone"
case .clientMute:
return "speaker.slash"
case .router, .routerClient, .repeater:
case .router, .routerClient:
return "wifi.router"
case .repeater:
return "repeat"
case .tracker:
return "mappin.and.ellipse.circle"
case .sensor:

View file

@ -26,7 +26,6 @@ extension PositionEntity {
let pointOfInterest = LocationHelper.currentLocation
if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude {
/// Lets just get nodes within about 500 miles
let D: Double = UserDefaults.meshMapDistance * 1.1
let R: Double = 6371009
let meanLatitidue = pointOfInterest.latitude * .pi / 180

View file

@ -205,6 +205,5 @@ struct MeshMapContent: MapContent {
@MapContentBuilder
var body: some MapContent {
meshMap
}
}

View file

@ -0,0 +1,117 @@
//
// NodeListFilter.swift
// Meshtastic
//
// Created by Garth Vander Houwen on 3/25/24.
//
import Foundation
import SwiftUI
struct NodeListFilter: View {
@Environment(\.dismiss) private var dismiss
/// Filters
@Binding var viaLora: Bool
@Binding var viaMqtt: Bool
@Binding var distanceFilter: Bool
@Binding var maximumDistance: Double
@Binding var hopsAway: Int
@Binding var deviceRole: Int
var body: some View {
NavigationStack {
Form {
Section(header: Text("Node Filters")) {
Toggle(isOn: $viaLora) {
Label {
Text("Via Lora")
} icon: {
Image(systemName: "dot.radiowaves.left.and.right")
.rotationEffect(.degrees(-90))
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $viaMqtt) {
Label {
Text("Via Mqtt")
} icon: {
Image(systemName: "dot.radiowaves.up.forward")
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
// Toggle(isOn: $distanceFilter) {
//
// Label {
// Text("Distance")
// } icon: {
// Image(systemName: "map")
// }
// }
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
//
// .listRowSeparator(distanceFilter ? .hidden : .visible)
// if distanceFilter {
// HStack {
// Label("Show nodes", systemImage: "lines.measurement.horizontal")
// Picker("", selection: $maximumDistance) {
// ForEach(MeshMapDistances.allCases) { di in
// Text(di.description)
// .tag(di.id)
// }
// }
// .pickerStyle(DefaultPickerStyle())
// }
// }
HStack {
Label("Hops Away", systemImage: "hare")
Picker("", selection: $hopsAway) {
Text("Any")
.tag(-1)
Text("Direct")
.tag(0)
ForEach(1..<8) {
Text("\($0)")
.tag($0)
}
}
.pickerStyle(DefaultPickerStyle())
}
HStack {
Label("Device Role", systemImage: "apps.iphone")
Picker("", selection: $deviceRole) {
Text("All Roles")
.tag(-1)
ForEach(DeviceRoles.allCases) { dr in
Label {
Text(" \(dr.name)")
} icon: {
Image(systemName: dr.systemName)
}
}
}
.pickerStyle(DefaultPickerStyle())
}
}
}
#if targetEnvironment(macCatalyst)
Spacer()
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding(.bottom)
#endif
}
.presentationDetents([.fraction(0.35), .fraction(0.45)])
.presentationDragIndicator(.visible)
}
}

View file

@ -132,7 +132,7 @@ struct NodeListItem: View {
}
if node.viaMqtt && connectedNode != node.num {
Image(systemName: "network")
Image(systemName: "dot.radiowaves.up.forward")
.symbolRenderingMode(.hierarchical)
.font(.callout)
.frame(width: 30)

View file

@ -7,28 +7,6 @@
import SwiftUI
import CoreLocation
struct NodeSearchState {
var searchText = ""
var searchScope = SearchScopes.all
var predicate: NSPredicate = .init()
enum SearchScopes: CaseIterable, Identifiable {
case all
case lora
case mqtt
var id: Self { self }
var title: LocalizedStringKey {
switch self {
case .all: return "All"
case .lora: return "LoRa"
case .mqtt: return "MQTT"
}
}
}
}
struct NodeList: View {
@State private var columnVisibility = NavigationSplitViewVisibility.all
@ -38,11 +16,17 @@ struct NodeList: View {
@State private var isPresentingDeleteNodeAlert = false
@State private var isPresentingPositionSentAlert = false
@State private var deleteNodeId: Int64 = 0
@State private var searchState = NodeSearchState()
@State private var searchText = ""
@State private var viaLora = true
@State private var viaMqtt = true
@State private var distanceFilter = false
@State private var maxDistance: Double = 800000
@State private var hopsAway: Int = -1
@State private var deviceRole: Int = -1
@State var isEditingFilters = false
@SceneStorage("selectedDetailView") var selectedDetailView: String?
@State private var searchText = ""
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@ -159,14 +143,17 @@ struct NodeList: View {
Text("Any missed messages will be delivered again.")
}
}
.sheet(isPresented: $isEditingFilters) {
NodeListFilter(viaLora: $viaLora, viaMqtt: $viaMqtt, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, deviceRole: $deviceRole)
}
.safeAreaInset(edge: .bottom, alignment: .trailing) {
HStack {
Button(action: {
withAnimation {
//isEditingSettings = !isEditingSettings
isEditingFilters = !isEditingFilters
}
}) {
Image(systemName: true ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill")
Image(systemName: !isEditingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill")
.padding(.vertical, 5)
}
.tint(Color(UIColor.secondarySystemBackground))
@ -178,14 +165,9 @@ struct NodeList: View {
.padding(5)
}
.padding(.bottom, 5)
.searchable(text: $searchState.searchText, placement: nodes.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a node")
.searchable(text: $searchText, placement: .automatic, prompt: "Find a node")
.disableAutocorrection(true)
.scrollDismissesKeyboard(.immediately)
.searchScopes($searchState.searchScope) {
ForEach(NodeSearchState.SearchScopes.allCases) { scope in
Text(scope.title).tag(scope)
}
}
.navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count)))
.listStyle(.plain)
.confirmationDialog(
@ -256,10 +238,19 @@ struct NodeList: View {
}
.navigationSplitViewStyle(.balanced)
.onChange(of: searchState.searchText) { _ in
.onChange(of: searchText) { _ in
searchNodeList()
}
.onChange(of: searchState.searchScope) { _ in
.onChange(of: viaLora) { _ in
searchNodeList()
}
.onChange(of: viaMqtt) { _ in
searchNodeList()
}
.onChange(of: deviceRole) { _ in
searchNodeList()
}
.onChange(of: hopsAway) { _ in
searchNodeList()
}
.onAppear {
@ -272,35 +263,63 @@ struct NodeList: View {
private func searchNodeList() {
/// Case Insensitive Search Text Predicates
let searchPredicates = ["user.userId", "user.hwModel", "user.longName", "user.shortName"].map { property in
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchState.searchText)
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
var predicates: [NSPredicate] = []
/// Mqtt
if !(viaLora && viaMqtt) {
if viaLora {
let loraPredicate = NSPredicate(format: "viaMqtt == NO")
predicates.append(loraPredicate)
} else {
let mqttPredicate = NSPredicate(format: "viaMqtt == YES")
predicates.append(mqttPredicate)
}
}
/// Role
if deviceRole > 0 {
let rolePredicate = NSPredicate(format: "user.role == %i", Int32(deviceRole))
predicates.append(rolePredicate)
}
/// Hops Away
if hopsAway > 0 {
let hopsAwayPredicate = NSPredicate(format: "hopsAway == %i", Int32(hopsAway))
predicates.append(hopsAwayPredicate)
}
/// Distance
if distanceFilter {
let pointOfInterest = LocationHelper.currentLocation
/// Set the predicate to nil if the search string is empty
if searchState.searchText.isEmpty {
nodes.nsPredicate = nil
return
if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude {
let D: Double = maxDistance * 1.1
let R: Double = 6371009
let meanLatitidue = pointOfInterest.latitude * .pi / 180
let deltaLatitude = D / R * 180 / .pi
let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi
let minLatitude: Double = pointOfInterest.latitude - deltaLatitude
let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude
let minLongitude: Double = pointOfInterest.longitude - deltaLongitude
let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude
let distancePredicate = NSPredicate(format: "(%lf <= (positions[first].longitudeI / 1e7))", minLongitude, maxLongitude,minLatitude, maxLatitude)
//let distancePredicate = NSPredicate(format: "(%lf <= (positions[LAST].longitudeI / 1e7)) AND ((positions[LAST].longitudeI / 1e7) <= %lf) AND (%lf <= (positions[LAST].latitudeI / 1e7)) AND ((positions[LAST].latitudeI / 1e7) <= %lf)", minLongitude, maxLongitude,minLatitude, maxLatitude)
//predicates.append(distancePredicate)
}
}
/// Add a predicate for the search scope if selected
if searchState.searchScope != .all {
if predicates.count > 0 {
if searchState.searchScope == .lora {
let loraPredicate = NSPredicate(format: "viaMqtt == NO")
let scopePredicate = NSCompoundPredicate(type: .and, subpredicates: [loraPredicate])
nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, scopePredicate])
return
} else if searchState.searchScope == .mqtt {
let mqttPredicate = NSPredicate(format: "viaMqtt == YES")
let scopePredicate = NSCompoundPredicate(type: .and, subpredicates: [mqttPredicate])
nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, scopePredicate])
return
if !searchText.isEmpty {
let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates)
nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, filterPredicates])
} else {
nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
}
} else {
/// Use the text search predicate
nodes.nsPredicate = textSearchPredicate
nodes.nsPredicate = nil
}
}
}

View file

@ -47,7 +47,7 @@ struct MQTTConfig: View {
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "dot.radiowaves.right")
Label("enabled", systemImage: "dot.radiowaves.up.forward")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))

View file

@ -50,34 +50,43 @@ struct Settings: View {
NavigationLink {
AboutMeshtastic()
} label: {
Image(systemName: "questionmark.app")
.symbolRenderingMode(.hierarchical)
Text("about.meshtastic")
Label {
Text("about.meshtastic")
} icon: {
Image(systemName: "questionmark.app")
}
}
.tag(SettingsSidebar.about)
NavigationLink {
AppSettings()
} label: {
Image(systemName: "gearshape")
.symbolRenderingMode(.hierarchical)
Text("appsettings")
Label {
Text("appsettings")
} icon: {
Image(systemName: "gearshape")
}
}
.tag(SettingsSidebar.appSettings)
if #available(iOS 17.0, macOS 14.0, *) {
NavigationLink {
Routes()
} label: {
Image(systemName: "road.lanes.curved.right")
.symbolRenderingMode(.hierarchical)
Text("routes")
Label {
Text("routes")
} icon: {
Image(systemName: "road.lanes.curved.right")
}
}
.tag(SettingsSidebar.routes)
NavigationLink {
RouteRecorder()
} label: {
Image(systemName: "record.circle")
.symbolRenderingMode(.hierarchical)
Text("route.recorder")
Label {
Text("route.recorder")
} icon: {
Image(systemName: "record.circle")
.foregroundColor(.red)
}
}
.tag(SettingsSidebar.routeRecorder)
}
@ -152,26 +161,33 @@ struct Settings: View {
NavigationLink {
LoRaConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "dot.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
Text("lora")
Label {
Text("lora")
} icon: {
Image(systemName: "dot.radiowaves.left.and.right")
.rotationEffect(.degrees(-90))
}
}
.tag(SettingsSidebar.loraConfig)
NavigationLink {
Channels(node: nodes.first(where: { $0.num == preferredNodeNum }))
} label: {
Image(systemName: "fibrechannel")
.symbolRenderingMode(.hierarchical)
Text("channels")
Label {
Text("channels")
} icon: {
Image(systemName: "fibrechannel")
}
}
.tag(SettingsSidebar.channelConfig)
.disabled(selectedNode > 0 && selectedNode != preferredNodeNum)
NavigationLink {
ShareChannels(node: nodes.first(where: { $0.num == preferredNodeNum }))
} label: {
Image(systemName: "qrcode")
.symbolRenderingMode(.hierarchical)
Text("share.channels")
Label {
Text("share.channels")
} icon: {
Image(systemName: "qrcode")
}
}
.tag(SettingsSidebar.shareChannels)
.disabled(selectedNode > 0 && selectedNode != preferredNodeNum)
@ -180,58 +196,72 @@ struct Settings: View {
NavigationLink {
UserConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "person.crop.rectangle.fill")
.symbolRenderingMode(.hierarchical)
Text("user")
Label {
Text("user")
} icon: {
Image(systemName: "person.crop.rectangle.fill")
}
}
.tag(SettingsSidebar.userConfig)
NavigationLink {
BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "antenna.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
Text("bluetooth")
Label {
Text("bluetooth")
} icon: {
Image(systemName: "antenna.radiowaves.left.and.right")
}
}
.tag(SettingsSidebar.bluetoothConfig)
NavigationLink {
DeviceConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "flipphone")
.symbolRenderingMode(.hierarchical)
Text("device")
Label {
Text("device")
} icon: {
Image(systemName: "flipphone")
}
}
.tag(SettingsSidebar.deviceConfig)
NavigationLink {
DisplayConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "display")
.symbolRenderingMode(.hierarchical)
Text("display")
Label {
Text("display")
} icon: {
Image(systemName: "display")
}
}
.tag(SettingsSidebar.displayConfig)
NavigationLink {
NetworkConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "network")
.symbolRenderingMode(.hierarchical)
Text("network")
Label {
Text("network")
} icon: {
Image(systemName: "network")
}
}
.tag(SettingsSidebar.networkConfig)
NavigationLink {
PositionConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "location")
.symbolRenderingMode(.hierarchical)
Text("position")
Label {
Text("position")
} icon: {
Image(systemName: "location")
}
}
.tag(SettingsSidebar.positionConfig)
NavigationLink {
PowerConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "bolt.fill")
.symbolRenderingMode(.hierarchical)
Text("config.power.settings")
Label {
Text("config.power.settings")
} icon: {
Image(systemName: "bolt.fill")
}
}
.tag(SettingsSidebar.powerConfig)
}
@ -240,92 +270,114 @@ struct Settings: View {
NavigationLink {
AmbientLightingConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "light.max")
.symbolRenderingMode(.hierarchical)
Text("ambient.lighting")
Label {
Text("ambient.lighting")
} icon: {
Image(systemName: "light.max")
}
}
.tag(SettingsSidebar.ambientLightingConfig)
}
NavigationLink {
CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "list.bullet.rectangle.fill")
.symbolRenderingMode(.hierarchical)
Text("canned.messages")
Label {
Text("canned.messages")
} icon: {
Image(systemName: "list.bullet.rectangle.fill")
}
}
.tag(SettingsSidebar.cannedMessagesConfig)
NavigationLink {
DetectionSensorConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "sensor")
.symbolRenderingMode(.hierarchical)
Text("detection.sensor")
Label {
Text("detection.sensor")
} icon: {
Image(systemName: "sensor")
}
}
.tag(SettingsSidebar.detectionSensorConfig)
NavigationLink {
ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "megaphone")
.symbolRenderingMode(.hierarchical)
Text("external.notification")
Label {
Text("external.notification")
} icon: {
Image(systemName: "megaphone")
}
}
.tag(SettingsSidebar.externalNotificationConfig)
NavigationLink {
MQTTConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "dot.radiowaves.right")
.symbolRenderingMode(.hierarchical)
Text("mqtt")
Label {
Text("mqtt")
} icon: {
Image(systemName: "dot.radiowaves.up.forward")
}
}
.tag(SettingsSidebar.mqttConfig)
NavigationLink {
RangeTestConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "point.3.connected.trianglepath.dotted")
.symbolRenderingMode(.hierarchical)
Text("range.test")
Label {
Text("range.test")
} icon: {
Image(systemName: "point.3.connected.trianglepath.dotted")
}
}
.tag(SettingsSidebar.rangeTestConfig)
if node?.metadata?.hasWifi ?? false {
NavigationLink {
PaxCounterConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "figure.walk.motion")
.symbolRenderingMode(.hierarchical)
Text("config.module.paxcounter.settings")
Label {
Text("config.module.paxcounter.setting")
} icon: {
Image(systemName: "figure.walk.motion")
}
}
.tag(SettingsSidebar.paxCounterConfig)
}
NavigationLink {
RtttlConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "music.note.list")
.symbolRenderingMode(.hierarchical)
Text("ringtone")
Label {
Text("ringtone")
} icon: {
Image(systemName: "music.note.list")
}
}
.tag(SettingsSidebar.ringtoneConfig)
NavigationLink {
SerialConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "terminal")
.symbolRenderingMode(.hierarchical)
Text("serial")
Label {
Text("serial")
} icon: {
Image(systemName: "terminal")
}
}
.tag(SettingsSidebar.serialConfig)
NavigationLink {
StoreForwardConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "envelope.arrow.triangle.branch")
.symbolRenderingMode(.hierarchical)
Text("storeforward")
Label {
Text("storeforward")
} icon: {
Image(systemName: "envelope.arrow.triangle.branch")
}
}
.tag(SettingsSidebar.storeAndForwardConfig)
NavigationLink {
TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode }))
} label: {
Image(systemName: "chart.xyaxis.line")
.symbolRenderingMode(.hierarchical)
Text("telemetry")
Label {
Text("telemetry")
} icon: {
Image(systemName: "chart.xyaxis.line")
}
}
.tag(SettingsSidebar.telemetryConfig)
}
@ -333,18 +385,22 @@ struct Settings: View {
NavigationLink {
MeshLog()
} label: {
Image(systemName: "list.bullet.rectangle")
.symbolRenderingMode(.hierarchical)
Text("mesh.log")
Label {
Text("mesh.log")
} icon: {
Image(systemName: "list.bullet.rectangle")
}
}
.tag(SettingsSidebar.meshLog)
NavigationLink {
let connectedNode = nodes.first(where: { $0.num == preferredNodeNum })
AdminMessageList(user: connectedNode?.user)
} label: {
Image(systemName: "building.columns")
.symbolRenderingMode(.hierarchical)
Text("admin.log")
Label {
Text("admin.log")
} icon: {
Image(systemName: "building.columns")
}
}
.tag(SettingsSidebar.adminMessageLog)
}
@ -352,9 +408,11 @@ struct Settings: View {
NavigationLink {
Firmware(node: nodes.first(where: { $0.num == preferredNodeNum }))
} label: {
Image(systemName: "arrow.up.arrow.down.square")
.symbolRenderingMode(.hierarchical)
Text("Firmware Updates")
Label {
Text("Firmware Updates")
} icon: {
Image(systemName: "arrow.up.arrow.down.square")
}
}
.tag(SettingsSidebar.about)
.disabled(selectedNode > 0 && selectedNode != preferredNodeNum)