diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index ea9b95f4..e2e69453 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -25,7 +25,7 @@ DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; }; DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */; }; DD2DC2C029BCD8AB003B383C /* HardwareModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2DC2BF29BCD8AB003B383C /* HardwareModels.swift */; }; - DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E65252767A01F00E45FC5 /* NodeDetail.swift */; }; + DD2E65262767A01F00E45FC5 /* NodeDetailOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E65252767A01F00E45FC5 /* NodeDetailOld.swift */; }; DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; }; DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */; }; DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; }; @@ -37,7 +37,7 @@ DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582928585C32009B0E59 /* RangeTestConfig.swift */; }; DD41A61529AB0035003C5A37 /* NodeWeatherForecast.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */; }; DD457188293C7E63000C49FB /* BLESignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */; }; - DD47E3CE26F103C600029299 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeList.swift */; }; + DD47E3CE26F103C600029299 /* NodeListOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeListOld.swift */; }; DD47E3D626F17ED900029299 /* CircleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D526F17ED900029299 /* CircleText.swift */; }; DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911D2708C65400501B7E /* AppSettings.swift */; }; DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */; }; @@ -134,9 +134,9 @@ DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD6EEAE29BC024700383354 /* Firmware.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; }; - DDDB263F2AABEE20003AFCB7 /* NodeListSplit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB263E2AABEE20003AFCB7 /* NodeListSplit.swift */; }; + DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB263E2AABEE20003AFCB7 /* NodeList.swift */; }; DDDB26422AABF655003AFCB7 /* NodeListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26412AABF655003AFCB7 /* NodeListItem.swift */; }; - DDDB26442AAC0206003AFCB7 /* NodeDetailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26432AAC0206003AFCB7 /* NodeDetailItem.swift */; }; + DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */; }; DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */; }; DDDB26482AACD6D1003AFCB7 /* NodeMapControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26472AACD6D1003AFCB7 /* NodeMapControl.swift */; }; DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB443529F6287000EE2349 /* MapButtons.swift */; }; @@ -227,7 +227,7 @@ DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = ""; }; DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = ""; }; DD2DC2BF29BCD8AB003B383C /* HardwareModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardwareModels.swift; sourceTree = ""; }; - DD2E65252767A01F00E45FC5 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = ""; }; + DD2E65252767A01F00E45FC5 /* NodeDetailOld.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetailOld.swift; sourceTree = ""; }; DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = ""; }; @@ -242,7 +242,7 @@ DD41A61E29AE7E8F003C5A37 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLESignalStrengthIndicator.swift; sourceTree = ""; }; DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV5.xcdatamodel; sourceTree = ""; }; - DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = ""; }; + DD47E3CD26F103C600029299 /* NodeListOld.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListOld.swift; sourceTree = ""; }; DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = ""; }; DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentMetricsLog.swift; sourceTree = ""; }; @@ -354,11 +354,12 @@ DDD6EEAE29BC024700383354 /* Firmware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Firmware.swift; sourceTree = ""; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = ""; }; - DDDB263E2AABEE20003AFCB7 /* NodeListSplit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListSplit.swift; sourceTree = ""; }; + DDDB263E2AABEE20003AFCB7 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.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 = ""; }; + DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = ""; }; DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoItem.swift; sourceTree = ""; }; DDDB26472AACD6D1003AFCB7 /* NodeMapControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapControl.swift; sourceTree = ""; }; + DDDB26492AAD743E003AFCB7 /* MeshtasticDataModelV18.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV18.xcdatamodel; 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 = ""; }; @@ -467,14 +468,14 @@ isa = PBXGroup; children = ( DDDB26402AABEF7B003AFCB7 /* Helpers */, - DD2E65252767A01F00E45FC5 /* NodeDetail.swift */, - DD47E3CD26F103C600029299 /* NodeList.swift */, - DD90860D26F69BAE00DC5189 /* NodeMap.swift */, + DDDB263E2AABEE20003AFCB7 /* NodeList.swift */, + 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */, DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */, DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */, + DD90860D26F69BAE00DC5189 /* NodeMap.swift */, DD73FD1028750779000852D6 /* PositionLog.swift */, - 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */, - DDDB263E2AABEE20003AFCB7 /* NodeListSplit.swift */, + DD2E65252767A01F00E45FC5 /* NodeDetailOld.swift */, + DD47E3CD26F103C600029299 /* NodeListOld.swift */, ); path = Nodes; sourceTree = ""; @@ -810,7 +811,7 @@ DDDB26402AABEF7B003AFCB7 /* Helpers */ = { isa = PBXGroup; children = ( - DDDB26432AAC0206003AFCB7 /* NodeDetailItem.swift */, + DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */, DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */, DDDB26412AABF655003AFCB7 /* NodeListItem.swift */, DDDB26472AACD6D1003AFCB7 /* NodeMapControl.swift */, @@ -1101,7 +1102,7 @@ DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */, DD5E5208298EE33B00D21B61 /* rtttl.pb.swift in Sources */, DD6193792863875F00E59241 /* SerialConfig.swift in Sources */, - DDDB263F2AABEE20003AFCB7 /* NodeListSplit.swift in Sources */, + DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */, DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */, DDB8F4102A9EE5B400230ECE /* Messages.swift in Sources */, DDDB26482AACD6D1003AFCB7 /* NodeMapControl.swift in Sources */, @@ -1135,7 +1136,7 @@ DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */, DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */, 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */, - DD47E3CE26F103C600029299 /* NodeList.swift in Sources */, + DD47E3CE26F103C600029299 /* NodeListOld.swift in Sources */, DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */, DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */, DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */, @@ -1159,7 +1160,7 @@ DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */, DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, - DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */, + DD2E65262767A01F00E45FC5 /* NodeDetailOld.swift in Sources */, DDDE5A1029AFE69700490C6C /* MeshActivityAttributes.swift in Sources */, DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */, DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */, @@ -1191,7 +1192,7 @@ DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */, DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */, - DDDB26442AAC0206003AFCB7 /* NodeDetailItem.swift in Sources */, + DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */, DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */, DD77093F2AA1B146007A8BF0 /* UIColor.swift in Sources */, DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */, @@ -1713,6 +1714,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDDB26492AAD743E003AFCB7 /* MeshtasticDataModelV18.xcdatamodel */, DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */, DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */, DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */, @@ -1731,7 +1733,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */; + currentVersion = DDDB26492AAD743E003AFCB7 /* MeshtasticDataModelV18.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index b57e6ff4..c041a7dd 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -24,6 +24,9 @@ extension NodeInfoEntity { let environmentMetrics = telemetries?.filter{ ($0 as AnyObject).metricsType == 1 } return environmentMetrics?.count ?? 0 > 0 } + var hasDetectionSensorMetrics: Bool { + return user?.sensorMessageList.count ?? 0 > 0 + } var isOnline: Bool { diff --git a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift index 927c0372..ecf4e5e2 100644 --- a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift @@ -8,6 +8,7 @@ import Foundation extension UserEntity { + var messageList: [MessageEntity] { self.value(forKey: "allMessages") as? [MessageEntity] ?? [MessageEntity]() @@ -17,6 +18,10 @@ extension UserEntity { self.value(forKey: "adminMessages") as? [MessageEntity] ?? [MessageEntity]() } + var sensorMessageList: [MessageEntity] { + self.value(forKey: "detectionSensorMessages") as? [MessageEntity] ?? [MessageEntity]() + } + var unreadMessages: Int { let unreadMessages = messageList.filter{ ($0 as AnyObject).read == false } return unreadMessages.count diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 6b72864b..348d9e99 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV17.xcdatamodel + MeshtasticDataModelV18.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents index db7917c1..d7f42c7b 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents @@ -347,6 +347,9 @@ + + + diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV18.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV18.xcdatamodel/contents new file mode 100644 index 00000000..c30e2f4b --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV18.xcdatamodel/contents @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Helpers/ConnectedDevice.swift b/Meshtastic/Views/Helpers/ConnectedDevice.swift index 9e4d1085..3a3b6d2a 100644 --- a/Meshtastic/Views/Helpers/ConnectedDevice.swift +++ b/Meshtastic/Views/Helpers/ConnectedDevice.swift @@ -10,35 +10,39 @@ struct ConnectedDevice: View { var deviceConnected: Bool var name: String var mqttProxyConnected: Bool = false + var phoneOnly: Bool = false var body: some View { HStack { - if bluetoothOn { - if deviceConnected && mqttProxyConnected { - if mqttProxyConnected { - Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill") + + if (phoneOnly && UIDevice.current.userInterfaceIdiom == .phone) || !phoneOnly { + if bluetoothOn { + if deviceConnected && mqttProxyConnected { + if mqttProxyConnected { + Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill") + .imageScale(.large) + .foregroundColor(.green) + .symbolRenderingMode(.hierarchical) + } + } + if deviceConnected { + Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") .imageScale(.large) .foregroundColor(.green) .symbolRenderingMode(.hierarchical) - } - } - if deviceConnected { - Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") - .imageScale(.large) - .foregroundColor(.green) - .symbolRenderingMode(.hierarchical) - Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray) - } else { + Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray) + } else { - Image(systemName: "antenna.radiowaves.left.and.right.slash") - .imageScale(.medium) - .foregroundColor(.red) - .symbolRenderingMode(.hierarchical) - } - } else { - Text("bluetooth.off").font(.subheadline).foregroundColor(.red) - } + Image(systemName: "antenna.radiowaves.left.and.right.slash") + .imageScale(.medium) + .foregroundColor(.red) + .symbolRenderingMode(.hierarchical) + } + } else { + Text("bluetooth.off").font(.subheadline).foregroundColor(.red) + } + } } } } diff --git a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift index 362de937..94dfdf4f 100644 --- a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift +++ b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift @@ -1,261 +1,261 @@ +//// +//// NodeInfoView.swift +//// Meshtastic +//// +//// Created by Garth Vander Houwen on 4/2/23. +//// // -// NodeInfoView.swift -// Meshtastic +//// +//// DistanceText.swift +//// Meshtastic +//// +//// Copyright(c) Garth Vander Houwen 8/19/22. +//// // -// Created by Garth Vander Houwen on 4/2/23. +//import SwiftUI +//import CoreLocation +//import MapKit // - +//struct NodeInfoView: View { // -// DistanceText.swift -// Meshtastic +// var node: NodeInfoEntity // -// Copyright(c) Garth Vander Houwen 8/19/22. +// var body: some View { +// let hwModelString = node.user?.hwModel ?? "UNSET" // - -import SwiftUI -import CoreLocation -import MapKit - -struct NodeInfoView: View { - - var node: NodeInfoEntity - - var body: some View { - let hwModelString = node.user?.hwModel ?? "UNSET" - - Divider() - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - HStack { - VStack(alignment: .center) { - CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 150) - } - Divider() - VStack { - if node.user != nil { - Image(hwModelString) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 100, height: 100) - .cornerRadius(5) - - Text(String(hwModelString)) - .foregroundColor(.gray) - .font(.title).fixedSize() - } - } - Divider() - if node.snr != 0 { - VStack(alignment: .center) { - let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate) - LoRaSignalStrengthIndicator(signalStrength: signalStrength) - Text("Signal \(signalStrength.description)").font(.title) - Text("SNR \(String(format: "%.2f", node.snr))dB") - .foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate)) - .font(.title3) - Text("RSSI \(node.rssi)dB") - .foregroundColor(getRssiColor(rssi: node.rssi)) - .font(.title3) - } - Divider() - } - - if node.hasDeviceMetrics { - let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) - let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity - VStack(alignment: .center) { - BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) - if mostRecent?.voltage ?? 0 > 0.0 { - - Text(String(format: "%.2f", mostRecent?.voltage ?? 0.0) + " V") - .font(.title) - .foregroundColor(.gray) - .fixedSize() - } - } - .padding() - } - } - .padding() - - Divider() - HStack(alignment: .center) { - - VStack { - HStack { - Image(systemName: "person") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("user").font(.title)+Text(":").font(.title) - } - Text("!\(String(format: "%02x", node.num))") - .font(.title).foregroundColor(.gray) - } - Divider() - VStack { - HStack { - Image(systemName: "number") - .font(.title2) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Node Number:").font(.title) - } - Text(String(node.num)).font(.title).foregroundColor(.gray) - } - Divider() - VStack { - HStack { - Image(systemName: "clock.badge.checkmark.fill") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("heard.last").font(.title)+Text(":").font(.title) - - } - DateTimeText(dateTime: node.lastHeard) - .font(.title3) - .foregroundColor(.gray) - } - } - Divider() - - } 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() - HStack(alignment: .center) { - 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 { - 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 { - - if node.hasPositions{ - - NavigationLink { - PositionLog(node: node) - } 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() - } - } -} +// Divider() +// if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { +// HStack { +// VStack(alignment: .center) { +// CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 150) +// } +// Divider() +// VStack { +// if node.user != nil { +// Image(hwModelString) +// .resizable() +// .aspectRatio(contentMode: .fit) +// .frame(width: 100, height: 100) +// .cornerRadius(5) +// +// Text(String(hwModelString)) +// .foregroundColor(.gray) +// .font(.title).fixedSize() +// } +// } +// Divider() +// if node.snr != 0 { +// VStack(alignment: .center) { +// let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate) +// LoRaSignalStrengthIndicator(signalStrength: signalStrength) +// Text("Signal \(signalStrength.description)").font(.title) +// Text("SNR \(String(format: "%.2f", node.snr))dB") +// .foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate)) +// .font(.title3) +// Text("RSSI \(node.rssi)dB") +// .foregroundColor(getRssiColor(rssi: node.rssi)) +// .font(.title3) +// } +// Divider() +// } +// +// if node.hasDeviceMetrics { +// let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) +// let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity +// VStack(alignment: .center) { +// BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) +// if mostRecent?.voltage ?? 0 > 0.0 { +// +// Text(String(format: "%.2f", mostRecent?.voltage ?? 0.0) + " V") +// .font(.title) +// .foregroundColor(.gray) +// .fixedSize() +// } +// } +// .padding() +// } +// } +// .padding() +// +// Divider() +// HStack(alignment: .center) { +// +// VStack { +// HStack { +// Image(systemName: "person") +// .font(.title) +// .foregroundColor(.accentColor) +// .symbolRenderingMode(.hierarchical) +// Text("user").font(.title)+Text(":").font(.title) +// } +// Text("!\(String(format: "%02x", node.num))") +// .font(.title).foregroundColor(.gray) +// } +// Divider() +// VStack { +// HStack { +// Image(systemName: "number") +// .font(.title2) +// .foregroundColor(.accentColor) +// .symbolRenderingMode(.hierarchical) +// Text("Node Number:").font(.title) +// } +// Text(String(node.num)).font(.title).foregroundColor(.gray) +// } +// Divider() +// VStack { +// HStack { +// Image(systemName: "clock.badge.checkmark.fill") +// .font(.title) +// .foregroundColor(.accentColor) +// .symbolRenderingMode(.hierarchical) +// Text("heard.last").font(.title)+Text(":").font(.title) +// +// } +// DateTimeText(dateTime: node.lastHeard) +// .font(.title3) +// .foregroundColor(.gray) +// } +// } +// Divider() +// +// } 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() +// HStack(alignment: .center) { +// 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 { +// 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 { +// +// if node.hasPositions{ +// +// NavigationLink { +// PositionLog(node: node) +// } 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() +// } +// } +//} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift similarity index 97% rename from Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift rename to Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 7c4d04f4..fee6fdbe 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -8,7 +8,7 @@ import WeatherKit import MapKit import CoreLocation -struct NodeDetailItem: View { +struct NodeDetail: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -39,18 +39,6 @@ struct NodeDetailItem: View { } .disabled(!node.hasDeviceMetrics) Divider() - NavigationLink { - EnvironmentMetricsLog(node: node) - } label: { - Image(systemName: "chart.xyaxis.line") - .symbolRenderingMode(.hierarchical) - .font(.title) - - Text("Environment Metrics Log") - .font(.title3) - } - .disabled(!node.hasEnvironmentMetrics) - Divider() NavigationLink { NodeMapControl(node: node) } label: { @@ -75,6 +63,18 @@ struct NodeDetailItem: View { } .disabled(!node.hasPositions) Divider() + NavigationLink { + EnvironmentMetricsLog(node: node) + } label: { + Image(systemName: "chart.xyaxis.line") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Environment Metrics Log") + .font(.title3) + } + .disabled(!node.hasEnvironmentMetrics) + Divider() NavigationLink { DetectionSensorLog(node: node) } label: { @@ -85,6 +85,7 @@ struct NodeDetailItem: View { Text("Detection Sensor Log") .font(.title3) } + .disabled(!node.hasDetectionSensorMetrics) Divider() } @@ -150,7 +151,7 @@ struct NodeDetailItem: View { ConnectedDevice( bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", phoneOnly: true) }) } .padding(.bottom, 2) diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetailOld.swift similarity index 100% rename from Meshtastic/Views/Nodes/NodeDetail.swift rename to Meshtastic/Views/Nodes/NodeDetailOld.swift diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index c47b1909..08b6ff7c 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -1,136 +1,109 @@ -//// -//// NodeList.swift -//// Meshtastic -//// -//// Copyright(c) Garth Vander Houwen 8/7/21. -//// // -//// Abstract: -//// A view showing a list of devices that have been seen on the mesh network from the perspective of the connected device. +// NodeListSplit.swift +// Meshtastic // -//import SwiftUI -//import CoreLocation +// Created by Garth Vander Houwen on 9/8/23. // -//struct NodeList: View { -// -// @State private var searchText = "" -// var nodesQuery: Binding { -// Binding { -// searchText -// } set: { newValue in -// searchText = newValue -// nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue) -// } -// } -// -// @Environment(\.managedObjectContext) var context -// @EnvironmentObject var bleManager: BLEManager -// -// @FetchRequest( -// sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], -// animation: .default) -// -// private var nodes: FetchedResults -// -// @State private var selection: NodeInfoEntity? // Nothing selected by default. -// -// var body: some View { -// -// NavigationSplitView { -// let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) -// let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) -// List(nodes, id: \.self, selection: $selection) { node in -// if nodes.count == 0 { -// Text("no.nodes").font(.title) -// } else { -// NavigationLink(value: node) { -// let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num) -// LazyVStack(alignment: .leading) { -// HStack { -// VStack(alignment: .leading) { -// CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65) -// .padding(.trailing, 5) -// let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) -// if deviceMetrics?.count ?? 0 >= 1 { -// let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity -// BatteryLevelCompact(batteryLevel: mostRecent?.batteryLevel, font: .caption2, iconFont: .callout, color: .accentColor) -// } -// } -// VStack(alignment: .leading) { -// Text(node.user?.longName ?? "unknown".localized) -// .fontWeight(.medium) -// .font(.callout) -// if connected { -// HStack(alignment: .bottom) { -// Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") -// .font(.footnote) -// .symbolRenderingMode(.hierarchical) -// .foregroundColor(.green) -// Text("connected").font(.caption) -// } -// } -// HStack(alignment: .bottom) { -// Image(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill") -// .font(.footnote) -// .symbolRenderingMode(.hierarchical) -// .foregroundColor(node.isOnline ? .green : .orange) -// 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.channel > 0 { -// HStack(alignment: .bottom) { -// Image(systemName: "fibrechannel") -// .font(.footnote) -// .symbolRenderingMode(.hierarchical) -// Text("Channel: \(node.channel)") -// .font(.footnote) -// } -// } -// 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) -// } -// } -// } -// .frame(maxWidth: .infinity, alignment: .leading) -// } -// } -// } -// .padding([.top, .bottom]) +import SwiftUI +import CoreLocation + +enum SelectedDetail { + case positionLog + case nodeMap + case deviceMetricsLog + case environmentMetricsLog + case detectionSensorLog +} + + + +struct NodeListSplit: View { + + @State private var columnVisibility = NavigationSplitViewVisibility.all + @State private var selectedNode: NodeInfoEntity? + @State private var selectedDetail: SelectedDetail? + + @SceneStorage("selectedDetailView") var selectedDetailView: String? + + @State private var searchText = "" + var nodesQuery: Binding { + Binding { + searchText + } set: { newValue in + searchText = newValue + nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue) + } + } + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @FetchRequest( + sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], + animation: .default) + + private var nodes: FetchedResults + + + + var body: some View { + NavigationSplitView(columnVisibility: $columnVisibility) { + + let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) + let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) + List(nodes, id: \.self, selection: $selectedNode) { node in + + 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(min: 100, ideal: 250, max: 500) + .navigationBarItems(leading: + MeshtasticLogo(), + trailing: + ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", phoneOnly: true) + }) + } content: { + + if let node = selectedNode { + NodeDetail(node: node) + + } else { + Text("select.node") + } + + } detail: { + Text("Select something to view") + } + .navigationSplitViewStyle(.balanced) + .onChange(of: selectedNode) { _ in + selectedDetail = nil + } + .onAppear { + if self.bleManager.context == nil { + self.bleManager.context = context + } + } + +// } detail: { +// VStack { +// Button("Detail Only") { +// columnVisibility = .detailOnly // } -// } -// .listStyle(.plain) -// .navigationSplitViewColumnWidth(300) -// .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) -// .navigationBarItems(leading: -// MeshtasticLogo() -// ) -// .onAppear { -// if self.bleManager.context == nil { -// self.bleManager.context = context +// +// Button("Content and Detail") { +// columnVisibility = .doubleColumn +// } +// +// Button("Show All") { +// columnVisibility = .all // } // } -// } detail: { -// if let node = selection { -// NodeDetail(node: node) -// } else { -// Text("select.node") -// } -// } -// .searchable(text: nodesQuery, prompt: "Find a node") -// } -//} +// } + } +} diff --git a/Meshtastic/Views/Nodes/NodeListOld.swift b/Meshtastic/Views/Nodes/NodeListOld.swift new file mode 100644 index 00000000..c47b1909 --- /dev/null +++ b/Meshtastic/Views/Nodes/NodeListOld.swift @@ -0,0 +1,136 @@ +//// +//// NodeList.swift +//// Meshtastic +//// +//// Copyright(c) Garth Vander Houwen 8/7/21. +//// +// +//// Abstract: +//// A view showing a list of devices that have been seen on the mesh network from the perspective of the connected device. +// +//import SwiftUI +//import CoreLocation +// +//struct NodeList: View { +// +// @State private var searchText = "" +// var nodesQuery: Binding { +// Binding { +// searchText +// } set: { newValue in +// searchText = newValue +// nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue) +// } +// } +// +// @Environment(\.managedObjectContext) var context +// @EnvironmentObject var bleManager: BLEManager +// +// @FetchRequest( +// sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], +// animation: .default) +// +// private var nodes: FetchedResults +// +// @State private var selection: NodeInfoEntity? // Nothing selected by default. +// +// var body: some View { +// +// NavigationSplitView { +// let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) +// let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) +// List(nodes, id: \.self, selection: $selection) { node in +// if nodes.count == 0 { +// Text("no.nodes").font(.title) +// } else { +// NavigationLink(value: node) { +// let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num) +// LazyVStack(alignment: .leading) { +// HStack { +// VStack(alignment: .leading) { +// CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65) +// .padding(.trailing, 5) +// let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) +// if deviceMetrics?.count ?? 0 >= 1 { +// let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity +// BatteryLevelCompact(batteryLevel: mostRecent?.batteryLevel, font: .caption2, iconFont: .callout, color: .accentColor) +// } +// } +// VStack(alignment: .leading) { +// Text(node.user?.longName ?? "unknown".localized) +// .fontWeight(.medium) +// .font(.callout) +// if connected { +// HStack(alignment: .bottom) { +// Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") +// .font(.footnote) +// .symbolRenderingMode(.hierarchical) +// .foregroundColor(.green) +// Text("connected").font(.caption) +// } +// } +// HStack(alignment: .bottom) { +// Image(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill") +// .font(.footnote) +// .symbolRenderingMode(.hierarchical) +// .foregroundColor(node.isOnline ? .green : .orange) +// 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.channel > 0 { +// HStack(alignment: .bottom) { +// Image(systemName: "fibrechannel") +// .font(.footnote) +// .symbolRenderingMode(.hierarchical) +// Text("Channel: \(node.channel)") +// .font(.footnote) +// } +// } +// 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) +// } +// } +// } +// .frame(maxWidth: .infinity, alignment: .leading) +// } +// } +// } +// .padding([.top, .bottom]) +// } +// } +// .listStyle(.plain) +// .navigationSplitViewColumnWidth(300) +// .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) +// .navigationBarItems(leading: +// MeshtasticLogo() +// ) +// .onAppear { +// if self.bleManager.context == nil { +// self.bleManager.context = context +// } +// } +// } detail: { +// if let node = selection { +// NodeDetail(node: node) +// } else { +// Text("select.node") +// } +// } +// .searchable(text: nodesQuery, prompt: "Find a node") +// } +//} diff --git a/Meshtastic/Views/Nodes/NodeListSplit.swift b/Meshtastic/Views/Nodes/NodeListSplit.swift deleted file mode 100644 index 8ec52f3b..00000000 --- a/Meshtastic/Views/Nodes/NodeListSplit.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// NodeListSplit.swift -// Meshtastic -// -// Created by Garth Vander Houwen on 9/8/23. -// -import SwiftUI -import CoreLocation - -enum SelectedDetail { - case positionLog - case nodeMap - case deviceMetricsLog - case environmentMetricsLog - case detectionSensorLog -} - -struct NodeListSplit: View { - - @State private var columnVisibility = NavigationSplitViewVisibility.all - @State private var selectedNode: NodeInfoEntity? - @State private var selectedDetail: SelectedDetail? - - @SceneStorage("selectedDetailView") var selectedDetailView: String? - - @State private var searchText = "" - var nodesQuery: Binding { - Binding { - searchText - } set: { newValue in - searchText = newValue - nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue) - } - } - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - - @FetchRequest( - sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], - animation: .default) - - private var nodes: FetchedResults - - - - var body: some View { - NavigationSplitView(columnVisibility: $columnVisibility) { - - let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) - let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) - List(nodes, id: \.self, selection: $selectedNode) { node in - - 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(min: 100, ideal: 250, max: 500) - .navigationBarItems(leading: - MeshtasticLogo() - ) - } content: { - - if let node = selectedNode { - NodeDetailItem(node: node) - - } else { - Text("select.node") - } - - } detail: { - Text("Select something to view") - } - .navigationSplitViewStyle(.balanced) - .onChange(of: selectedNode) { _ in - selectedDetail = nil - } - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } - -// } detail: { -// VStack { -// Button("Detail Only") { -// columnVisibility = .detailOnly -// } -// -// Button("Content and Detail") { -// columnVisibility = .doubleColumn -// } -// -// Button("Show All") { -// columnVisibility = .all -// } -// } -// } - } -} diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 2100f955..0ccbd6cd 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -24,12 +24,6 @@ struct PositionLog: View { var body: some View { NavigationStack { - - if node.hasPositions { - - } else { - Text("Node has no positions.") - } let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") if UIDevice.current.userInterfaceIdiom == .pad && !useGrid || UIDevice.current.userInterfaceIdiom == .mac {