diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 3da89de8..cbf12dd5 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */; }; DD1933782B084F4200771CD5 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933772B084F4200771CD5 /* Measurement.swift */; }; DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; + DD21007F2B0E571300F2F116 /* Settings2.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD21007E2B0E571300F2F116 /* Settings2.swift */; }; DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; }; DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; }; @@ -231,6 +232,7 @@ DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAltitudeChart.swift; sourceTree = ""; }; DD1933772B084F4200771CD5 /* Measurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; + DD21007E2B0E571300F2F116 /* Settings2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings2.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; @@ -482,6 +484,21 @@ path = CoreData; sourceTree = ""; }; + DD2100802B0E676E00F2F116 /* Routes */ = { + isa = PBXGroup; + children = ( + DD2100832B0E67AD00F2F116 /* RouteMap */, + ); + path = Routes; + sourceTree = ""; + }; + DD2100832B0E67AD00F2F116 /* RouteMap */ = { + isa = PBXGroup; + children = ( + ); + path = RouteMap; + sourceTree = ""; + }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( @@ -509,6 +526,7 @@ DD4A911C2708C57100501B7E /* Settings */ = { isa = PBXGroup; children = ( + DD2100802B0E676E00F2F116 /* Routes */, DD97E96728EFE9A00056DDA4 /* About.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, @@ -518,6 +536,7 @@ DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DD3501882852FC3B000FC853 /* Settings.swift */, + DD21007E2B0E571300F2F116 /* Settings2.swift */, DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD61937A2863876A00E59241 /* Config */, @@ -1091,6 +1110,7 @@ DD457188293C7E63000C49FB /* BLESignalStrengthIndicator.swift in Sources */, DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */, DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */, + DD21007F2B0E571300F2F116 /* Settings2.swift in Sources */, DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */, DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 07ef857a..f49dcc78 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -612,7 +612,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } case .neighborinfoApp: - MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + if let neighborInfo = try? NeighborInfo(serializedData: decodedInfo.packet.decoded.payload) { + MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App App UNHANDLED \(neighborInfo)") + } case .UNRECOGNIZED: MeshLogger.log("🕸️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .max: diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift index c9c351ba..f094d5e8 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift @@ -22,7 +22,7 @@ struct MapSettingsForm: View { var body: some View { - VStack { + NavigationStack { Form { Section(header: Text("Map Options")) { Picker(selection: $mapLayer, label: Text("")) { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 8f4ae4e2..a5b5a3db 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -80,6 +80,7 @@ struct NodeListItem: View { HStack { let preset = ModemPresets(rawValue: Int(modemPreset)) LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) + .padding(.top, 2) } } HStack { diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 3e090f35..621e9f08 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -54,6 +54,10 @@ struct MeshMap: View { format: "expire == nil || expire >= %@", Date() as NSDate ), animation: .none) private var waypoints: FetchedResults + + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)], + predicate: NSPredicate(format: "enabled == true", ""), animation: .none) + private var routes: FetchedResults var body: some View { @@ -123,7 +127,39 @@ struct MeshMap: View { selectedPosition = (selectedPosition == position ? nil : position) } } - /// Route Lines + /// Routes + ForEach(Array(routes), id: \.id) { route in + let routeLocations = Array(route.locations!) as! [LocationEntity] + let routeCoords = routeLocations.compactMap({(loc) -> CLLocationCoordinate2D in + return loc.locationCoordinate ?? LocationHelper.DefaultLocation + }) + Annotation("Start", coordinate: routeCoords.first ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.green)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) + } + } + .annotationTitles(.automatic) + Annotation("Finish", coordinate: routeCoords.last ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.black)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) + } + } + .annotationTitles(.automatic) + let dashed = StrokeStyle( + lineWidth: 3, + lineCap: .round, lineJoin: .round, dash: [7, 10] + ) + MapPolyline(coordinates: routeCoords) + .stroke(Color(UIColor(hex: UInt32(route.color))), style: dashed) + + } + /// Node Route Lines if showRouteLines { let nodePositions = Array(position.nodePosition!.positions!) as! [PositionEntity] let routeCoords = nodePositions.compactMap({(pos) -> CLLocationCoordinate2D in @@ -173,6 +209,19 @@ struct MeshMap: View { } } } + .mapScope(mapScope) + .mapStyle(mapStyle) + .mapControls { + MapScaleView(scope: mapScope) + .mapControlVisibility(.automatic) + MapUserLocationButton(scope: mapScope) + .mapControlVisibility(showUserLocation ? .visible : .hidden) + MapPitchToggle(scope: mapScope) + .mapControlVisibility(.automatic) + MapCompass(scope: mapScope) + .mapControlVisibility(.automatic) + } + .controlSize(.regular) .onTapGesture(count: 1, perform: { location in newWaypointCoord = reader.convert(location , from: .local) }) @@ -185,23 +234,10 @@ struct MeshMap: View { editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480) editingWaypoint!.id = 0 } + } } - .mapScope(mapScope) - .mapStyle(mapStyle) - .mapControls { - MapScaleView(scope: mapScope) - .mapControlVisibility(.visible) - if showUserLocation { - MapUserLocationButton(scope: mapScope) - .mapControlVisibility(.visible) - } - MapPitchToggle(scope: mapScope) - .mapControlVisibility(.visible) - MapCompass(scope: mapScope) - .mapControlVisibility(.visible) - } - .controlSize(.regular) + .sheet(item: $selectedPosition) { selection in PositionPopover(position: selection, popover: false) .padding() diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 03f48f56..737a90d3 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -142,7 +142,7 @@ struct Channels: View { Picker("Key Size", selection: $channelKeySize) { Text("Empty").tag(0) Text("Default").tag(-1) - Text("1 bit").tag(1) + Text("1 byte").tag(1) Text("128 bit").tag(16) Text("192 bit").tag(24) Text("256 bit").tag(32) diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 4c78a446..11b2e723 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -19,11 +19,12 @@ struct Routes: View { @State private var importing = false @State private var isShowingBadFileAlert = false - @FetchRequest(sortDescriptors: [], animation: .default) + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "enabled", ascending: false), NSSortDescriptor(key: "name", ascending: true), NSSortDescriptor(key: "date", ascending: false)], animation: .default) var routes: FetchedResults var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { + //VStack { Button("Import Route") { importing = true } @@ -152,6 +153,7 @@ struct Routes: View { } .navigationTitle("Route List") } detail: { + VStack { if selectedRoute != nil { let locationArray = selectedRoute?.locations?.array as? [LocationEntity] ?? [] @@ -169,7 +171,7 @@ struct Routes: View { } } .annotationTitles(.automatic) - Annotation("Finish", coordinate: locationArray.last?.locationCoordinate ?? LocationHelper.DefaultLocation) { + Annotation("Finish", coordinate: lineCoords.last ?? LocationHelper.DefaultLocation) { ZStack { Circle() .fill(Color(.black)) diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 2d21c447..7fb37da8 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -278,17 +278,17 @@ struct Settings: View { } .tag(SettingsSidebar.adminMessageLog) } - Section(header: Text("Firmware")) { - NavigationLink { - Firmware(node: nodes.first(where: { $0.num == preferredNodeNum })) - } label: { - Image(systemName: "arrow.up.arrow.down.square") - .symbolRenderingMode(.hierarchical) - Text("Firmware Updates") - } - .tag(SettingsSidebar.about) - .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) - } +// Section(header: Text("Firmware")) { +// NavigationLink { +// Firmware(node: nodes.first(where: { $0.num == preferredNodeNum })) +// } label: { +// Image(systemName: "arrow.up.arrow.down.square") +// .symbolRenderingMode(.hierarchical) +// Text("Firmware Updates") +// } +// .tag(SettingsSidebar.about) +// .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) +// } } } .onAppear { diff --git a/Meshtastic/Views/Settings/Settings2.swift b/Meshtastic/Views/Settings/Settings2.swift new file mode 100644 index 00000000..22fcb6c5 --- /dev/null +++ b/Meshtastic/Views/Settings/Settings2.swift @@ -0,0 +1,171 @@ +// +// Settings.swift +// MeshtasticApple +// +// Copyright (c) Garth Vander Houwen 6/9/22. +// + +import SwiftUI + +enum SettingsSidebar: CaseIterable { + case about + case appSettings + case routes + case radioConfig + case moduleConfig + case meshLog + case adminMessageLog + var name: String { + switch self { + case .about: + return "about.meshtastic".localized + case .appSettings: + return "app.settings".localized + case .routes: + return "routes".localized + case .radioConfig: + return "radio.configuration".localized + case .moduleConfig: + return "module.configuration".localized + case .meshLog: + return "mesh.log".localized + case .adminMessageLog: + return "admin.log".localized + } + } + var icon: String { + switch self { + case .about: + return "questionmark.app" + case .appSettings: + return "gearshape".localized + case .routes: + return "routes".localized + case .radioConfig: + return "flipphone".localized + case .moduleConfig: + return "module.configuration".localized + case .meshLog: + return "mesh.log".localized + case .adminMessageLog: + return "admin.log".localized + } + } +} +extension SettingsSidebar: Identifiable { + var id: Self { self } +} + +@available(iOS 17.0, macOS 14.0, *) +struct Settings2: View { + @State private var compactColumn = NavigationSplitViewColumn.detail + @State private var columnVisibility = NavigationSplitViewVisibility.automatic + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default) + private var nodes: FetchedResults + @State private var selectedNode: Int = 0 + @State private var preferredNodeNum: Int = 0 + @State private var selection: SettingsSidebar = .about + + enum SettingsContent { + case appSettings + case routes + case shareChannels + case userConfig + case loraConfig + case channelConfig + case bluetoothConfig + case deviceConfig + case displayConfig + case networkConfig + case positionConfig + case cannedMessagesConfig + case detectionSensorConfig + case externalNotificationConfig + case mqttConfig + case rangeTestConfig + case ringtoneConfig + case serialConfig + case telemetryConfig + case meshLog + case adminMessageLog + case about + } + var body: some View { + NavigationSplitView(columnVisibility: $columnVisibility, preferredCompactColumn: $compactColumn) { + + List(SettingsSidebar.allCases) { item in + switch(item) { + case .about: + NavigationLink { AboutMeshtastic() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .appSettings: + NavigationLink { AppSettings() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .routes: + NavigationLink { Routes() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .radioConfig: + NavigationLink { Routes() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .moduleConfig: + NavigationLink { Routes() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .meshLog: + NavigationLink { MeshLog() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .adminMessageLog: + NavigationLink { AdminMessageList() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + } + } + .listStyle(GroupedListStyle()) + .navigationTitle("settings") + .navigationBarItems(leading: MeshtasticLogo()) + } content: { + List { + if selection == .routes { + Text("Routes Bitechs") + } + } + } + detail: { + Text("Detail") + ContentUnavailableView("select.menu.item", systemImage: "gear") + } + .onChange(of: selection) { value in + columnVisibility = .doubleColumn + compactColumn = .sidebar + + } + } +}