diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index e2a7efd6..208e0b8d 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ DD007BB02AA5981000F5FA12 /* NodeInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */; }; DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; }; DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */; }; - DD14E72E2A82A614006E39BC /* RemoteHardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD14E72D2A82A614006E39BC /* RemoteHardware.swift */; }; DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */; }; DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B828CDA93900720036 /* SerialConfigEnums.swift */; }; DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; @@ -138,6 +137,7 @@ DDDB263F2AABEE20003AFCB7 /* NodeListSplit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB263E2AABEE20003AFCB7 /* NodeListSplit.swift */; }; DDDB26422AABF655003AFCB7 /* NodeListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26412AABF655003AFCB7 /* NodeListItem.swift */; }; DDDB26442AAC0206003AFCB7 /* NodeDetailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26432AAC0206003AFCB7 /* NodeDetailItem.swift */; }; + DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */; }; DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB443529F6287000EE2349 /* MapButtons.swift */; }; DDDB443D29F6592F00EE2349 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB443C29F6592F00EE2349 /* NetworkManager.swift */; }; DDDB444029F79AB000EE2349 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB443F29F79AB000EE2349 /* UserDefaults.swift */; }; @@ -217,7 +217,6 @@ DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV14.xcdatamodel; sourceTree = ""; }; DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminMessageList.swift; sourceTree = ""; }; DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV15.xcdatamodel; sourceTree = ""; }; - DD14E72D2A82A614006E39BC /* RemoteHardware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteHardware.swift; sourceTree = ""; }; DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfigEnums.swift; sourceTree = ""; }; DD1925B828CDA93900720036 /* SerialConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfigEnums.swift; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; @@ -357,6 +356,7 @@ DDDB263E2AABEE20003AFCB7 /* NodeListSplit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListSplit.swift; sourceTree = ""; }; DDDB26412AABF655003AFCB7 /* NodeListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListItem.swift; sourceTree = ""; }; DDDB26432AAC0206003AFCB7 /* NodeDetailItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetailItem.swift; sourceTree = ""; }; + DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoItem.swift; sourceTree = ""; }; DDDB443529F6287000EE2349 /* MapButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapButtons.swift; sourceTree = ""; }; DDDB443C29F6592F00EE2349 /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; DDDB443F29F79AB000EE2349 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; @@ -471,7 +471,6 @@ DD47E3CD26F103C600029299 /* NodeList.swift */, DD90860D26F69BAE00DC5189 /* NodeMap.swift */, DD73FD1028750779000852D6 /* PositionLog.swift */, - DD14E72D2A82A614006E39BC /* RemoteHardware.swift */, 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */, DDDB263E2AABEE20003AFCB7 /* NodeListSplit.swift */, ); @@ -809,8 +808,9 @@ DDDB26402AABEF7B003AFCB7 /* Helpers */ = { isa = PBXGroup; children = ( - DDDB26412AABF655003AFCB7 /* NodeListItem.swift */, DDDB26432AAC0206003AFCB7 /* NodeDetailItem.swift */, + DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */, + DDDB26412AABF655003AFCB7 /* NodeListItem.swift */, ); path = Helpers; sourceTree = ""; @@ -1093,6 +1093,7 @@ DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */, DDDB445429F8AD1600EE2349 /* Data.swift in Sources */, + DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */, DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */, DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */, DD5E5208298EE33B00D21B61 /* rtttl.pb.swift in Sources */, @@ -1194,7 +1195,6 @@ DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */, - DD14E72E2A82A614006E39BC /* RemoteHardware.swift in Sources */, DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */, DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */, DD5E5204298EE33B00D21B61 /* xmodem.pb.swift in Sources */, diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift index c415f8fb..444083ae 100644 --- a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift +++ b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift @@ -26,6 +26,7 @@ struct LoRaSignalStrengthMeter: View { .foregroundColor(getRssiColor(rssi: rssi)) .font(.caption2) } + .padding(.bottom, 2) } else { Gauge(value: Double(signalStrength.rawValue), in: 0...3) { } currentValueLabel: { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift index dc22ca2d..84b0a3b6 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift @@ -1,9 +1,7 @@ -// -// NodeDetailItem.swift -// Meshtastic -// -// Created by Garth Vander Houwen on 9/8/23. -// +/* + Abstract: + A view showing the details for a node. + */ import SwiftUI import WeatherKit @@ -11,20 +9,115 @@ import MapKit import CoreLocation struct NodeDetailItem: View { - - var node: NodeInfoEntity + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @Environment(\.colorScheme) var colorScheme: ColorScheme + @AppStorage("meshMapType") private var meshMapType = 0 + @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false + @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false + @State private var selectedMapLayer: MapLayer = .standard + @State var waypointCoordinate: WaypointCoordinate? + @State var editingWaypoint: Int = 0 + @State private var loadedWeather: Bool = false + @State private var showingDetailsPopover = false + @State private var showingForecast = false + @State private var showingShutdownConfirm: Bool = false + @State private var showingRebootConfirm: Bool = false + @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( + mapName: "offlinemap", + tileType: "png", + canReplaceMapContent: true + ) + @ObservedObject var node: NodeInfoEntity @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], predicate: NSPredicate( format: "expire == nil || expire >= %@", Date() as NSDate ), animation: .none) private var waypoints: FetchedResults - - + /// The current weather condition for the city. + @State private var condition: WeatherCondition? + @State private var temperature: Measurement? + @State private var humidity: Int? + @State private var symbolName: String = "cloud.fill" + + @State private var attributionLink: URL? + @State private var attributionLogo: URL? + var body: some View { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) NavigationStack { - + GeometryReader { bounds in + VStack { + ScrollView { + NodeInfoItem(node: node) + if self.bleManager.connectedPeripheral != nil && node.metadata != nil { + HStack { + if node.metadata?.canShutdown ?? false { + + Button(action: { + showingShutdownConfirm = true + }) { + Label("Power Off", systemImage: "power") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "are.you.sure", + isPresented: $showingShutdownConfirm + ) { + Button("Shutdown Node?", role: .destructive) { + if !bleManager.sendShutdown(fromUser: connectedNode!.user!, toUser: node.user!, adminIndex: connectedNode!.myInfo!.adminIndex) { + print("Shutdown Failed") + } + } + } + } + + Button(action: { + showingRebootConfirm = true + }) { + Label("reboot", systemImage: "arrow.triangle.2.circlepath") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog("are.you.sure", + isPresented: $showingRebootConfirm + ) { + Button("reboot.node", role: .destructive) { + if !bleManager.sendReboot(fromUser: connectedNode!.user!, toUser: node.user!, adminIndex: connectedNode!.myInfo!.adminIndex) { + print("Reboot Failed") + } + } + } + } + .padding(5) + Divider() + } + } + } + .edgesIgnoringSafeArea([.leading, .trailing]) + .sheet(item: $waypointCoordinate, content: { wpc in + WaypointFormView(coordinate: wpc) + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.automatic) + }) + .navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline) + .navigationBarItems(trailing: + ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") + }) + } + .padding(.bottom, 2) } } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift new file mode 100644 index 00000000..26c3c2fc --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -0,0 +1,174 @@ +// +// NodeInfoItem.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 9/9/23. +// + +import SwiftUI +import CoreLocation +import MapKit + +struct NodeInfoItem: View { + + var node: NodeInfoEntity + + enum SelectedDetail { + case positionLog + case nodeMap + case deviceMetricsLog + case environmentMetricsLog + case detectionSensorLog + } + + var body: some View { + + Divider() + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + + } else { + + } + + HStack { + + VStack(alignment: .center) { + CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65) + } + if node.user != nil { + Divider() + VStack { + Image(node.user!.hwModel ?? "unset".localized) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 75, height: 75) + .cornerRadius(5) + Text(String(node.user!.hwModel ?? "unset".localized)) + .font(.caption2).fixedSize() + } + } + if node.snr != 0 { + Divider() + VStack(alignment: .center) { + let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate) + LoRaSignalStrengthIndicator(signalStrength: signalStrength) + Text("Signal \(signalStrength.description)").font(.footnote) + Text("SNR \(String(format: "%.2f", node.snr))dB") + .foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate)) + .font(.caption2) + Text("RSSI \(node.rssi)dB") + .foregroundColor(getRssiColor(rssi: node.rssi)) + .font(.caption2) + } + } + let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) + if deviceMetrics?.count ?? 0 >= 1 { + Divider() + let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity + VStack(alignment: .center) { + BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) + if mostRecent?.voltage ?? 0 > 0 { + + Text(String(format: "%.2f", mostRecent?.voltage ?? 0) + " V") + .font(.callout) + .foregroundColor(.gray) + .fixedSize() + } + } + } + } + Divider() + VStack(alignment: .center) { + VStack { + HStack { + Image(systemName: "number") + .font(.title2) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Node Number:").font(.title2) + } + Text(String(node.num)).font(.title3).foregroundColor(.gray) + } + Divider() + VStack { + HStack { + Image(systemName: "person") + .font(.title2) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("User Id:").font(.title2) + } + Text(node.user?.userId ?? "?").font(.title3).foregroundColor(.gray) + } + Divider() + } + + VStack { + // List { + if node.hasPositions { + + NavigationLink { + PositionLog(node: node) + .onAppear { + + } + } label: { + + Image(systemName: "building.columns") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Position Log") + .font(.title3) + } + .fixedSize(horizontal: false, vertical: true) + Divider() + } + + if node.hasDeviceMetrics { + + NavigationLink { + DeviceMetricsLog(node: node) + } label: { + + Image(systemName: "flipphone") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Device Metrics Log") + .font(.title3) + } + Divider() + } + if node.hasEnvironmentMetrics { + NavigationLink { + EnvironmentMetricsLog(node: node) + } label: { + + Image(systemName: "chart.xyaxis.line") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Environment Metrics Log") + .font(.title3) + } + Divider() + } + NavigationLink { + DetectionSensorLog(node: node) + } label: { + + Image(systemName: "sensor") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Detection Sensor Log") + .font(.title3) + } + .fixedSize(horizontal: false, vertical: true) + Divider() + // } + // .listStyle(.plain) + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 5574e1dc..d619c379 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -6,18 +6,18 @@ // import SwiftUI - - - +import CoreLocation struct NodeListItem: View { - @StateObject var node: NodeInfoEntity + public var node: NodeInfoEntity + var connected: Bool + var connectedNode: Int64 + var modemPreset: Int var body: some View { NavigationLink(value: node) { - let connected: Bool = false //(bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num) LazyVStack(alignment: .leading) { HStack { VStack(alignment: .leading) { @@ -50,20 +50,20 @@ struct NodeListItem: View { LastHeardText(lastHeard: node.lastHeard) .font(.caption) } -// if node.positions?.count ?? 0 > 0 && (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 != node.num) { -// HStack(alignment: .bottom) { -// let lastPostion = node.positions!.reversed()[0] as! PositionEntity -// let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) -// if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { -// let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) -// let metersAway = nodeCoord.distance(from: myCoord) -// Image(systemName: "lines.measurement.horizontal") -// .font(.footnote) -// .symbolRenderingMode(.hierarchical) -// DistanceText(meters: metersAway).font(.caption) -// } -// } -// } + if node.positions?.count ?? 0 > 0 && connectedNode != node.num { + HStack(alignment: .bottom) { + let lastPostion = node.positions!.reversed()[0] as! PositionEntity + let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) + if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { + let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) + let metersAway = nodeCoord.distance(from: myCoord) + Image(systemName: "lines.measurement.horizontal") + .font(.footnote) + .symbolRenderingMode(.hierarchical) + DistanceText(meters: metersAway).font(.caption) + } + } + } if node.channel > 0 { HStack(alignment: .bottom) { Image(systemName: "fibrechannel") @@ -74,11 +74,12 @@ struct NodeListItem: View { } } -// if !connected { -// HStack(alignment: .bottom) { let preset = ModemPresets(rawValue: Int(connectedNode?.loRaConfig?.modemPreset ?? 0)) -// LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) -// } -// } + if !connected { + HStack(alignment: .bottom) { + let preset = ModemPresets(rawValue: Int(modemPreset)) + LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) + } + } } .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 99ffcd04..356e9e59 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -44,7 +44,7 @@ struct NodeDetail: View { @State private var attributionLink: URL? @State private var attributionLogo: URL? - + var body: some View { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) diff --git a/Meshtastic/Views/Nodes/NodeListSplit.swift b/Meshtastic/Views/Nodes/NodeListSplit.swift index b181b859..a83667d1 100644 --- a/Meshtastic/Views/Nodes/NodeListSplit.swift +++ b/Meshtastic/Views/Nodes/NodeListSplit.swift @@ -39,21 +39,20 @@ struct NodeListSplit: View { let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) List(nodes, id: \.self, selection: $selection) { node in - NodeListItem(node: node) + NodeListItem(node: node, connected: bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num, connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1), modemPreset: Int(connectedNode?.loRaConfig?.modemPreset ?? 0)) } .searchable(text: nodesQuery, prompt: "Find a node") .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) .listStyle(.plain) - .navigationSplitViewColumnWidth(300) + .navigationSplitViewColumnWidth(min: 100, ideal: 250, max: 500) .navigationBarItems(leading: MeshtasticLogo() ) } content: { if let node = selection { - //NodeDetailItem(node: node) - NodeDetail(node: node) - .navigationSplitViewColumnWidth(300) + NodeDetailItem(node: node) + } else { Text("select.node") } diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 4cd48761..2322a8d2 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -19,11 +19,8 @@ struct PositionLog: View { @State var exportString = "" var node: NodeInfoEntity @State private var isPresentingClearLogConfirm = false - //@State private var sortOrder = [KeyPathComparator(\PositionEntity.latitude)] + @State private var sortOrder = [KeyPathComparator(\PositionEntity.time)] - @State var sortOrder: [KeyPathComparator] = [ - .init(\.latitude, order: SortOrder.forward) - ] var body: some View { NavigationStack { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) diff --git a/Meshtastic/Views/Nodes/RemoteHardware.swift b/Meshtastic/Views/Nodes/RemoteHardware.swift deleted file mode 100644 index 42ddc4d4..00000000 --- a/Meshtastic/Views/Nodes/RemoteHardware.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// RemoteHardware.swift -// Meshtastic -// -// Created by Garth Vander Houwen on 8/8/23. -// - -import Foundation