diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d0e7e0ff..6fb06ca4 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -36,7 +36,7 @@ DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158B2824A91E0032668E /* module_config.pb.swift */; }; DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158D2824AA7E0032668E /* config.pb.swift */; }; DD4DED9027AD2975004BA27E /* cannedmessages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4DED8F27AD2975004BA27E /* cannedmessages.pb.swift */; }; - DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */; }; + DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */; }; DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD5394FB276993AD00AD86B1 /* SwiftProtobuf */; }; DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */; }; DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD539501276DAA6A00AD86B1 /* MapLocation.swift */; }; @@ -44,7 +44,8 @@ DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; DD6B85A828009258000ACD6B /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannels.swift */; }; - DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* LocationHistory.swift */; }; + DD73FD1128750779000852D6 /* PositionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* PositionLog.swift */; }; + DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */; }; DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; }; DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; }; DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FE272476C700F4AB02 /* LogDocument.swift */; }; @@ -149,7 +150,7 @@ DD4C158B2824A91E0032668E /* module_config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = module_config.pb.swift; sourceTree = ""; }; DD4C158D2824AA7E0032668E /* config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = config.pb.swift; sourceTree = ""; }; DD4DED8F27AD2975004BA27E /* cannedmessages.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = cannedmessages.pb.swift; sourceTree = ""; }; - DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryLog.swift; sourceTree = ""; }; + DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentMetricsLog.swift; sourceTree = ""; }; DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = ""; }; DD5929A528C0F292003DB21D /* MeshtasticDataModel v 9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 9.xcdatamodel"; sourceTree = ""; }; @@ -158,7 +159,8 @@ DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; DD6B85A728009258000ACD6B /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; - DD73FD1028750779000852D6 /* LocationHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHistory.swift; sourceTree = ""; }; + DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = ""; }; + DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = ""; }; @@ -273,14 +275,23 @@ path = Custom; sourceTree = ""; }; + DD2160AC28C5019400C17253 /* Messages */ = { + isa = PBXGroup; + children = ( + DDA1C48628D82022009933EC /* MessageTemplate.swift */, + ); + path = Messages; + sourceTree = ""; + }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( DD47E3CD26F103C600029299 /* NodeList.swift */, DD90860D26F69BAE00DC5189 /* NodeMap.swift */, DD2E65252767A01F00E45FC5 /* NodeDetail.swift */, - DD73FD1028750779000852D6 /* LocationHistory.swift */, - DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */, + DD73FD1028750779000852D6 /* PositionLog.swift */, + DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */, + DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */, ); path = Nodes; sourceTree = ""; @@ -705,14 +716,16 @@ DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */, + DDA1C48728D82022009933EC /* MessageTemplate.swift in Sources */, DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */, DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */, DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */, DDB6ABE828B141AF00384BA1 /* WiFiModes.swift in Sources */, - DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */, + DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */, DD6B85A828009258000ACD6B /* ShareChannels.swift in Sources */, DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, + DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */, @@ -773,7 +786,7 @@ DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */, DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, - DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */, + DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, @@ -952,7 +965,7 @@ ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -984,7 +997,7 @@ ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/1024.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/1024.png deleted file mode 100644 index 403f4f2f..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/1024.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/120-1.png deleted file mode 100644 index 6e571624..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120-1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/120.png deleted file mode 100644 index 6e571624..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/152.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/152.png deleted file mode 100644 index a930e23a..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/152.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/167.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/167.png deleted file mode 100644 index 35658530..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/167.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/180.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/180.png deleted file mode 100644 index fff06a76..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/180.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/20.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/20.png deleted file mode 100644 index 988c8ade..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/20.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/29.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/29.png deleted file mode 100644 index 553bb7a3..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/29.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-1.png deleted file mode 100644 index e78f4105..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-2.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-2.png deleted file mode 100644 index e78f4105..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/40.png deleted file mode 100644 index e78f4105..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/58-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/58-1.png deleted file mode 100644 index ea3829e7..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/58-1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/58.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/58.png deleted file mode 100644 index ea3829e7..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/58.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/60.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/60.png deleted file mode 100644 index f384f426..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/60.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/76.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/76.png deleted file mode 100644 index bd7db1b2..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/76.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/80-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/80-1.png deleted file mode 100644 index ed42f91e..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/80-1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/80.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/80.png deleted file mode 100644 index ed42f91e..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/80.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/87.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/87.png deleted file mode 100644 index a3720d5a..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/87.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json b/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json index 1cd4ae9f..937092d6 100644 --- a/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,111 +1,9 @@ { "images" : [ { - "filename" : "40-2.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "60.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "58.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "87.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "80.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "120-1.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "120.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "180.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "40-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "58-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "80-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "152.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "167.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", + "filename" : "logo-3.png", + "idiom" : "universal", + "platform" : "ios", "size" : "1024x1024" } ], diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png new file mode 100644 index 00000000..4e2504d4 Binary files /dev/null and b/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png differ diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 5ced286b..a918898c 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -29,7 +29,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph @Published var connectedPeripheral: Peripheral! @Published var lastConnectionError: String - @Published var connectedVersion: String + @State var connectedVersion: String @Published var isSwitchedOn: Bool = false @Published var isScanning: Bool = false @@ -114,7 +114,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if centralManager.isScanning { self.centralManager.stopScan() - self.isScanning = self.centralManager.isScanning + isScanning = self.centralManager.isScanning print("🛑 Stopped Scanning") } } @@ -416,6 +416,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } if (![FROMNUM_characteristic, FROMNUM_characteristic, TORADIO_characteristic].contains(nil)) { + sendWantConfig() } } @@ -1320,7 +1321,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph var adminPacket = AdminMessage() adminPacket.getChannelRequest = channelIndex - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(connectedPeripheral.num) meshPacket.from = 0 //UInt32(connectedPeripheral.num) @@ -1342,23 +1342,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { - - do { - - try context!.save() - if meshLoggingEnabled { MeshLogger.log("💾 Saved a Get Channel Request Admin Message for node: \(String(connectedPeripheral.num))") } + if meshLoggingEnabled { MeshLogger.log("🛎️ Send Get Channel \(channelIndex) Request Admin Message for node: \(String(connectedPeripheral.num))") } connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) return true - - } catch { - - context!.rollback() - - let nsError = error as NSError - print("💥 Error Inserting New Core Data MessageEntity: \(nsError)") - } } return false diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 9fc23784..82b73f2d 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1072,9 +1072,9 @@ func nodeInfoAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManage } func adminAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { - + if let channelMessage = try? Channel(serializedData: packet.decoded.payload) { - + let fetchedMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(packet.from)) @@ -1140,11 +1140,10 @@ func adminAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb let nsError = error as NSError print("💥 Error Saving MyInfo Channel from ADMIN_APP \(nsError)") } - - if meshLogging { MeshLogger.log("ℹ️ Channel Message received for Admin App \(try! channelMessage.jsonString())") } } } + func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { let fetchNodePositionRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") @@ -1284,7 +1283,6 @@ func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObj let nsError = error as NSError print("💥 Error Saving ACK for message MessageID \(packet.id) Error: \(nsError)") } - } } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents index b1c358d7..9819e4ef 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -210,24 +210,4 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 679ebb1d..227f70c4 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -31,7 +31,7 @@ struct MeshtasticAppleApp: App { saveQR = true } - print("User wants to open URL: \(channelUrl?.relativeString)") + print("User wants to open URL: \(String(describing: channelUrl?.relativeString))") } .sheet(isPresented: $saveQR) { diff --git a/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift b/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift new file mode 100644 index 00000000..5cb63190 --- /dev/null +++ b/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift @@ -0,0 +1,38 @@ +// +// MessageTemplate.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 9/18/22. +// +import SwiftUI + +struct MessageTemplate: View { + + var user: UserEntity + var message: MessageEntity + var messageReply: MessageEntity? + + var body: some View { + + // Display the message being replied to and the arrow + if message.replyID > 0 { + + HStack { + + Text(messageReply?.messagePayload ?? "EMPTY MESSAGE").foregroundColor(.blue).font(.caption2) + .padding(10) + .overlay( + RoundedRectangle(cornerRadius: 18) + .stroke(Color.blue, lineWidth: 0.5) + ) + Image(systemName: "arrowshape.turn.up.left.fill") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.blue) + .padding(.trailing) + } + } + + // Message + + } +} diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 45646651..dc2a8127 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -359,6 +359,7 @@ struct UserMessageList: View { .listRowSeparator(.hidden) } } + .scrollDismissesKeyboard(.immediately) .onAppear(perform: { self.bleManager.context = context diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift new file mode 100644 index 00000000..77c6dfd9 --- /dev/null +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -0,0 +1,153 @@ +// +// DeviceMetricsLog.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 7/7/22. +// +import SwiftUI +import Charts + +struct DeviceMetricsLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @State var isExporting = false + @State var exportString = "" + + var node: NodeInfoEntity + + struct MountPrice: Identifiable { + var id = UUID() + var mount: String + var value: Double + } + + var body: some View { + + NavigationStack { + + let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) + + let data = node.telemetries!.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg)) + + if data.count > 0 { + + GroupBox(label: Label("Battery Level Trend", systemImage: "battery.100")) { + + Chart(data.array as! [TelemetryEntity], id: \.self) { + LineMark( + x: .value("Hour", $0.time!.formattedDate(format: "ha")), + y: .value("Value", $0.batteryLevel) + ) + PointMark( + x: .value("Hour", $0.time!.formattedDate(format: "ha")), + y: .value("Value", $0.batteryLevel) + ) + } + .frame(height: 150) + } + } + + ScrollView { + + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + GridRow { + + Text("Batt") + .font(.callout) + .fontWeight(.bold) + Text("Voltage") + .font(.callout) + .fontWeight(.bold) + Text("ChUtil") + .font(.callout) + .fontWeight(.bold) + Text("AirTm") + .font(.callout) + .fontWeight(.bold) + Text("Timestamp") + .font(.callout) + .fontWeight(.bold) + } + Divider() + ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (dm: TelemetryEntity) in + + if dm.metricsType == 0 { + + GridRow { + + if dm.batteryLevel == 0 { + + Text("USB") + .font(.callout) + + } else { + + Text("\(String(dm.batteryLevel))%") + .font(.callout) + } + + Text(String(dm.voltage)) + .font(.callout) + Text("\(String(format: "%.2f", dm.channelUtilization))%") + .font(.callout) + Text("\(String(format: "%.2f", dm.airUtilTx))%") + .font(.callout) + Text(dm.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.callout) + } + } + } + } + .padding(.leading, 15) + .padding(.trailing, 5) + } + } + Button { + + exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .navigationTitle("Device Metrics Log") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + self.bleManager.context = context + } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user!.longName ?? "Node") Device Telemetry Log"), + onCompletion: { result in + + if case .success = result { + + print("Device Telemetry log download succeeded.") + + self.isExporting = false + + } else { + + print("Device Telemetry log download failed: \(result).") + } + } + ) + } +} diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift new file mode 100644 index 00000000..655e693e --- /dev/null +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -0,0 +1,130 @@ +// +// DeviceMetricsLog.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 7/7/22. +// +import SwiftUI + +struct EnvironmentMetricsLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @State var isExporting = false + @State var exportString = "" + + var node: NodeInfoEntity + + var body: some View { + + NavigationStack { + + ScrollView { + + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + GridRow { + + Text("Temp") + .font(.caption) + .fontWeight(.bold) + Text("Hum") + .font(.caption) + .fontWeight(.bold) + Text("Bar") + .font(.caption) + .fontWeight(.bold) + Text("Gas") + .font(.caption) + .fontWeight(.bold) + Text("Gas") + .font(.caption) + .fontWeight(.bold) + Text("DC") + .font(.caption) + .fontWeight(.bold) + Text("Volt") + .font(.caption) + .fontWeight(.bold) + Text("Timestamp") + .font(.caption) + .fontWeight(.bold) + } + Divider() + ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (em: TelemetryEntity) in + + if em.metricsType == 1 { + + let tempReadingType = (!(node.telemetryConfig?.environmentDisplayFahrenheit ?? false)) ? "°C" : "°F" + + GridRow { + + Text("\(String(format: "%.2f", em.temperature))\(tempReadingType)") + .font(.caption) + Text("\(String(format: "%.2f", em.relativeHumidity))") + .font(.caption) + Text("\(String(format: "%.2f", em.barometricPressure))") + .font(.caption) + Text("\(String(format: "%.2f", em.gasResistance))") + .font(.caption) + Text("\(String(format: "%.2f", em.current))") + .font(.caption) + Text("\(String(format: "%.2f", em.voltage))") + .font(.caption) + Text(em.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.caption) + } + } + } + } + .padding(.leading, 15) + .padding(.trailing, 5) + } + } + Button { + + exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 1) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .navigationTitle("Environment Metrics Log") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + self.bleManager.context = context + } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user!.longName ?? "Node") Environment Metrics Log"), + onCompletion: { result in + + if case .success = result { + + print("Environment metrics log download succeeded.") + + self.isExporting = false + + } else { + + print("Environment metrics log download failed: \(result).") + } + } + ) + } +} diff --git a/Meshtastic/Views/Nodes/LocationHistory.swift b/Meshtastic/Views/Nodes/LocationHistory.swift deleted file mode 100644 index 52165e2d..00000000 --- a/Meshtastic/Views/Nodes/LocationHistory.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// LocationHistory.swift -// Meshtastic -// -// Copyright(c) Garth Vander Houwen 7/5/22. -// -import SwiftUI - -struct LocationHistory: View { - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - - @State var isExporting = false - @State var exportString = "" - - var node: NodeInfoEntity - - var body: some View { - - VStack { - - List { - - ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in - - VStack { - - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - - HStack { - - Image(systemName: "mappin.and.ellipse") - .foregroundColor(.accentColor) - .font(.callout) - - Text("Lat/Long:").font(.callout) - Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") - .foregroundColor(.gray) - .font(.callout) - - Image(systemName: "arrow.up.arrow.down.circle") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Alt:") - .font(.callout) - - Text("\(String(mappin.altitude))m") - .foregroundColor(.gray) - .font(.callout) - Image(systemName: "clock.badge.checkmark.fill") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .font(.callout) - DateTimeText(dateTime: mappin.time) - .foregroundColor(.gray) - .font(.callout) - - } - - } else { - - HStack { - - Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline) - Text("Lat/Long:").font(.caption) - Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") - .foregroundColor(.gray) - .font(.caption) - - } - - HStack { - - Image(systemName: "arrow.up.arrow.down.circle") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Alt:") - .font(.caption) - Text("\(String(mappin.altitude))m") - .foregroundColor(.gray) - .font(.caption) - Image(systemName: "clock.badge.checkmark.fill") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - DateTimeText(dateTime: mappin.time) - .foregroundColor(.gray) - .font(.caption) - } - } - } - } - } - Button { - - exportString = PositionToCsvFile(positions: node.positions!.array as! [PositionEntity]) - isExporting = true - - } label: { - - Label("Save", systemImage: "square.and.arrow.down") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - } - .fileExporter( - isPresented: $isExporting, - document: CsvDocument(emptyCsv: exportString), - contentType: .commaSeparatedText, - defaultFilename: String("\(node.user?.longName ?? "Node") Position Log"), - onCompletion: { result in - - if case .success = result { - - print("Position log download succeeded.") - self.isExporting = false - - } else { - - print("Position log download failed: \(result).") - } - } - ) - .navigationTitle("Location History \(node.positions?.count ?? 0) Points") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(trailing: - - ZStack { - - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - }) - .onAppear { - - self.bleManager.context = context - } - } -} diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 80b9ccbb..405a8777 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -12,10 +12,12 @@ struct NodeDetail: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @State private var showingDetailsPopover = false + @State var initialLoad: Bool = true @State var satsInView = 0 - @State private var isPresentingShutdownConfirm: Bool = false - @State private var isPresentingRebootConfirm: Bool = false + @State private var showingShutdownConfirm: Bool = false + @State private var showingRebootConfirm: Bool = false var node: NodeInfoEntity @@ -23,7 +25,7 @@ struct NodeDetail: View { let hwModelString = node.user?.hwModel ?? "UNSET" - ZStack { + NavigationStack { GeometryReader { bounds in @@ -63,36 +65,30 @@ struct NodeDetail: View { } ) } - .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.6) + .ignoresSafeArea(.all, edges: [.leading, .trailing]) + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.70) } } Text("Sats: \(mostRecent.satsInView)").offset( y:-40) } else { HStack { - Image(node.user?.hwModel ?? "UNSET") - .resizable() - .aspectRatio(contentMode: .fit) - .cornerRadius(10) - .frame(width: bounds.size.width, height: bounds.size.height / 2.3) - .padding([.top], 40) + } - .offset( y:-40) + .padding([.top], 40) } ScrollView { if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { - - Divider() + HStack { if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { Button(action: { - isPresentingShutdownConfirm = true + showingShutdownConfirm = true }) { Label("Power Off", systemImage: "power") @@ -103,7 +99,7 @@ struct NodeDetail: View { .padding() .confirmationDialog( "Are you sure?", - isPresented: $isPresentingShutdownConfirm + isPresented: $showingShutdownConfirm ) { Button("Shutdown Node?", role: .destructive) { @@ -117,7 +113,7 @@ struct NodeDetail: View { Button(action: { - isPresentingRebootConfirm = true + showingRebootConfirm = true }) { @@ -130,7 +126,7 @@ struct NodeDetail: View { .confirmationDialog( "Are you sure?", - isPresented: $isPresentingRebootConfirm + isPresented: $showingRebootConfirm ) { Button("Reboot Node?", role: .destructive) { @@ -145,13 +141,10 @@ struct NodeDetail: View { .padding(5) } + Divider() + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - - // Add a divider if there is no map - if (node.positions?.count ?? 0) == 0 { - - Divider() - } + HStack { @@ -159,7 +152,7 @@ struct NodeDetail: View { Text("AKA").font(.largeTitle) .foregroundColor(.gray).fixedSize() - .offset(y:20) + .offset(y:5) CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 75, fontSize: 26) } .padding() @@ -172,8 +165,8 @@ struct NodeDetail: View { Image(hwModelString) .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 90, height: 90) + .aspectRatio(contentMode: .fill) + .frame(width: 200, height: 200) .cornerRadius(5) Text(String(hwModelString)) @@ -229,10 +222,15 @@ struct NodeDetail: View { } .padding() } - Divider() + } .padding() + .onLongPressGesture(minimumDuration: 2) { + + print("Long pressed!") + } + Divider() HStack(alignment: .center) { VStack { @@ -364,8 +362,6 @@ struct NodeDetail: View { } .padding(4) - Divider() - HStack(alignment: .center) { VStack { HStack { @@ -399,70 +395,84 @@ struct NodeDetail: View { Text("MAC Address: ") Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) } + .padding([.bottom], 0) } - List { + VStack { if (node.positions?.count ?? 0) > 0 { NavigationLink { - LocationHistory(node: node) + PositionLog(node: node) } label: { - + Image(systemName: "building.columns") .symbolRenderingMode(.hierarchical) .font(.title) - - Text("Position History \(node.positions?.count ?? 0) Points") + + Text("Position Log (\(node.positions?.count ?? 0) Points)") .font(.title3) } .fixedSize(horizontal: false, vertical: true) + Divider() } + if (node.telemetries?.count ?? 0) > 0 { + NavigationLink { - TelemetryLog(node: node) + DeviceMetricsLog(node: node) } label: { - + + Image(systemName: "flipphone") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Device Metrics Log") + .font(.title3) + } + Divider() + NavigationLink { + EnvironmentMetricsLog(node: node) + } label: { + Image(systemName: "chart.xyaxis.line") .symbolRenderingMode(.hierarchical) .font(.title) - - Text("Telemetry Log \(node.telemetries?.count ?? 0) Readings") + + Text("Environment Metrics Log") .font(.title3) } - .fixedSize(horizontal: false, vertical: true) + Divider() } } - .listStyle(GroupedListStyle()) - .frame(minHeight: 170) - .padding(0) } - .offset( y: (node.myInfo?.hasGps ?? false ? 0 : -40)) + .offset( y:-40) + .padding(.bottom, -40) } .edgesIgnoringSafeArea([.leading, .trailing]) + .navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown") + .navigationBarTitleDisplayMode(.inline) + .padding(.bottom, 10) + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + } + ) + .onAppear { + + if self.initialLoad{ + + self.bleManager.context = context + self.initialLoad = false + } + } } } - .navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(trailing: - - ZStack { - - ConnectedDevice( - bluetoothOn: bleManager.isSwitchedOn, - deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - } - ) - .onAppear { - - if self.initialLoad{ - - self.bleManager.context = context - self.initialLoad = false - } - } - } } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 80239cf4..2ab5327a 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -20,35 +20,35 @@ struct NodeList: View { @State var initialLoad: Bool = true @FetchRequest( - sortDescriptors: [NSSortDescriptor(key: "user.shortName", ascending: true)], + sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], animation: .default) private var nodes: FetchedResults - @State private var selection: String? = "" + @State private var selection: NodeInfoEntity? = nil // Nothing selected by default. var body: some View { - - NavigationView { - - List { + + NavigationSplitView { + + List (nodes, id: \.self, selection: $selection) { node in if nodes.count == 0 { - Text("Scan for Radios").font(.largeTitle) - Text("No Meshtastic Nodes Found").font(.title2) - Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your iPhone, iPad or Mac.") - .font(.body) - Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.") - Text("Views with bluetooth functionality will show an indicator in the upper right hand corner show if bluetooth is on, and if a device is connected.") + Text("Scan for Radios").font(.largeTitle) + Text("No Meshtastic Nodes Found").font(.title2) + Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your iPhone, iPad or Mac.") + .font(.body) + Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.") + Text("Views with bluetooth functionality will show an indicator in the upper right hand corner showing if bluetooth is on, and if a device is connected.") .listRowSeparator(.visible) - } else { - ForEach( nodes ) { node in + } else { + //ForEach( nodes ) { node in - let index = nodes.firstIndex(where: { $0.id == node.id }) + // let index = nodes.firstIndex(where: { $0.id == node.id }) - NavigationLink(destination: NodeDetail(node: node), tag: String(index!), selection: $selection) { + NavigationLink(value: node) { let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == node.num) @@ -123,11 +123,10 @@ struct NodeList: View { } .padding([.leading, .top, .bottom]) } - .isDetailLink(false) - } - } - } - .navigationTitle("All Nodes") + //} + } + } + .navigationTitle("All Nodes") .onAppear { if initialLoad { @@ -137,8 +136,16 @@ struct NodeList: View { self.initialLoad = false } } - } - .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .navigationViewStyle(DoubleColumnNavigationViewStyle()) + } detail: { + + if let node = selection { + + NodeDetail(node:node) + + } else { + + Text("Select a node") + } + } } } diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift new file mode 100644 index 00000000..e5e6d54c --- /dev/null +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -0,0 +1,107 @@ +// +// LocationHistory.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 7/5/22. +// +import SwiftUI + +struct PositionLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @State var isExporting = false + @State var exportString = "" + + var node: NodeInfoEntity + + var body: some View { + + NavigationStack { + + ScrollView { + + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + //Add a table for mac and ipad + } + + GridRow { + + Text("Latitude") + .font(.caption) + .fontWeight(.bold) + Text("Longitude") + .font(.caption) + .fontWeight(.bold) + Text("Alt.") + .font(.caption) + .fontWeight(.bold) + Text("Timestamp") + .font(.caption) + .fontWeight(.bold) + } + Divider() + ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in + GridRow { + Text(String(mappin.latitude ?? 0)) + .font(.caption) + Text(String(mappin.longitude ?? 0)) + .font(.caption) + Text(String(mappin.altitude)) + .font(.caption) + Text(mappin.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.caption) + } + } + } + .padding(.leading, 15) + .padding(.trailing, 5) + } + Button { + + exportString = PositionToCsvFile(positions: node.positions!.array as! [PositionEntity]) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user?.longName ?? "Node") Position Log"), + onCompletion: { result in + + if case .success = result { + + print("Position log download succeeded.") + self.isExporting = false + + } else { + + print("Position log download failed: \(result).") + } + } + ) + .navigationTitle("Position Log \(node.positions?.count ?? 0) Points") + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + self.bleManager.context = context + } + } +} diff --git a/Meshtastic/Views/Nodes/TelemetryLog.swift b/Meshtastic/Views/Nodes/TelemetryLog.swift deleted file mode 100644 index b43cfc09..00000000 --- a/Meshtastic/Views/Nodes/TelemetryLog.swift +++ /dev/null @@ -1,396 +0,0 @@ -// -// TelemetryLog.swift -// Meshtastic -// -// Copyright(c) Garth Vander Houwen 7/7/22. -// -import SwiftUI - -struct TelemetryLog: View { - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - - @State var isExporting = false - @State var exportString = "" - - var node: NodeInfoEntity - - var body: some View { - - List { - - ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (tel: TelemetryEntity) in - - VStack (alignment: .leading) { - - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - - - if tel.metricsType == 0 { - - // Device Metrics - HStack { - - Text("Device Metrics") - .font(.title) - - BatteryIcon(batteryLevel: tel.batteryLevel, font: .callout, color: .accentColor) - if tel.batteryLevel == 0 { - - Text("Plugged In") - .font(.callout) - .foregroundColor(.gray) - - } else { - - Text("Battery Level: \(String(tel.batteryLevel))%") - .font(.callout) - .foregroundColor(.gray) - } - if tel.batteryLevel > 0 { - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - Text("Channel Utilization: \(String(format: "%.2f", tel.channelUtilization))%") - .foregroundColor(.gray) - .font(.callout) - - Text("Airtime Utilization: \(String(format: "%.2f", tel.airUtilTx))%") - .foregroundColor(.gray) - .font(.callout) - - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - } - - } else if tel.metricsType == 1 { - - // Environment Metrics - HStack { - - Text("Environment Metrics") - .font(.title) - - - let tempReadingType = (!(node.telemetryConfig?.environmentDisplayFahrenheit ?? false)) ? "°C" : "°F" - - if tel.temperature > 0 { - - Image(systemName: "thermometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Temperature: \(String(format: "%.2f", tel.temperature))\(tempReadingType)") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.relativeHumidity > 0 { - - Image(systemName: "humidity") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Relative Humidity: \(String(format: "%.2f", tel.relativeHumidity))") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.barometricPressure > 0 { - - Image(systemName: "barometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Barometric Pressure: \(String(format: "%.2f", tel.barometricPressure))") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.gasResistance > 0 { - - Image(systemName: "aqi.medium") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Gas Resistance: \(String(format: "%.2f", tel.gasResistance))") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.current > 0 { - - Image(systemName: "directcurrent") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Current: \(String(format: "%.2f", tel.current))") - .foregroundColor(.gray) - .font(.callout) - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(format: "%.2f", tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - - } - } - - } else { - - if tel.metricsType == 0 { - - // Device Metrics iPhone Template - VStack { - - HStack { - - Spacer() - Text("Device Metrics") - .font(.title3) - Spacer() - } - - - HStack { - BatteryIcon(batteryLevel: tel.batteryLevel, font: .callout, color: .accentColor) - - if tel.batteryLevel == 0 { - - Text("Plugged In") - .font(.callout) - .foregroundColor(.gray) - - } else { - - Text("Battery Level: \(String(tel.batteryLevel))%") - .font(.callout) - .foregroundColor(.gray) - } - } - HStack { - if tel.batteryLevel > 0 { - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - } - - Text("Channel Utilization: \(String(format: "%.2f", tel.channelUtilization))%") - .foregroundColor(.gray) - .font(.callout) - - Text("Airtime Utilization: \(String(format: "%.2f", tel.airUtilTx))%") - .foregroundColor(.gray) - .font(.callout) - HStack { - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - } - } - } else if tel.metricsType == 1 { - - // Environment Metrics - let tempReadingType = (!(node.telemetryConfig?.environmentDisplayFahrenheit ?? true)) ? "°C" : "°F" - - - // Environment Metrics iPhone Template - VStack { - - HStack { - - Spacer() - Text("Environment Metrics") - .font(.title3) - Spacer() - } - - HStack { - - if tel.temperature > 0 { - - Image(systemName: "thermometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Temperature: \(String(format: "%.2f", tel.temperature))\(tempReadingType)") - .foregroundColor(.gray) - .font(.callout) - } - } - - HStack { - - if tel.relativeHumidity > 0 { - - Image(systemName: "humidity") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Relative Humidity: \(String(format: "%.2f", tel.relativeHumidity))") - .foregroundColor(.gray) - .font(.callout) - } - } - - if tel.current > 0 { - - HStack { - - Image(systemName: "directcurrent") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Current: \(String(format: "%.2f", tel.current))") - .foregroundColor(.gray) - .font(.callout) - } - - HStack { - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(format: "%.2f", tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - } - - if tel.barometricPressure > 0 { - - HStack { - - Image(systemName: "barometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Barometric Pressure: \(String(format: "%.2f", tel.barometricPressure))") - .foregroundColor(.gray) - .font(.callout) - } - } - - if tel.gasResistance > 0 { - - HStack { - - Image(systemName: "aqi.medium") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Gas Resistance: \(String(format: "%.2f", tel.gasResistance))") - .foregroundColor(.gray) - .font(.callout) - } - } - - HStack { - - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - } - } - } - } - } - } - } - Button { - - exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0) - isExporting = true - - } label: { - - Label("Save", systemImage: "square.and.arrow.down") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .navigationTitle("Telemetry Log \(node.telemetries?.count ?? 0) Readings") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(trailing: - - ZStack { - - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - }) - .onAppear { - - self.bleManager.context = context - } - .fileExporter( - isPresented: $isExporting, - document: CsvDocument(emptyCsv: exportString), - contentType: .commaSeparatedText, - defaultFilename: String("\(node.user!.longName ?? "Node") Telemetry Log"), - onCompletion: { result in - - if case .success = result { - - print("Telemetry log download succeeded.") - - self.isExporting = false - - } else { - - print("Telemetry log download failed: \(result).") - } - } - ) - } -} diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index abe7683a..1ddeed80 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -90,6 +90,8 @@ struct LoRaConfig: View { lc.hopLimit = UInt32(hopLimit) lc.region = RegionCodes(rawValue: region)!.protoEnumValue() lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() + lc.usePreset = true + lc.txEnabled = true let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: node!.user!, toUser: node!.user!) diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index ef2c77a1..a0ab47ff 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -205,6 +205,7 @@ struct CannedMessagesConfig: View { } .disabled(configPreset > 0) } + .scrollDismissesKeyboard(.immediately) .disabled(bleManager.connectedPeripheral == nil) Button { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 7f5fd3d5..4182c287 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -80,8 +80,9 @@ struct MQTTConfig: View { hasChanges = true }) .foregroundColor(.gray) + .keyboardType(.default) } - .keyboardType(.default) + .autocorrectionDisabled() HStack { Label("Username", systemImage: "person.text.rectangle") @@ -109,6 +110,7 @@ struct MQTTConfig: View { .foregroundColor(.gray) } .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) HStack { @@ -137,8 +139,10 @@ struct MQTTConfig: View { .foregroundColor(.gray) } .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) } } + .scrollDismissesKeyboard(.interactively) .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) Button { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 70ac4680..f593e068 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -112,7 +112,7 @@ struct TelemetryConfig: View { Section(header: Text("Sensor Options")) { - Text("I2C Connected sensors will be detected automatically. Supported sensors are BMP280, BME280, BME680, MCP9808, INA219 and INA260.") + Text("Supported I2C Connected sensors will be detected automatically, sensors are BMP280, BME280, BME680, MCP9808, INA219 and INA260.") .font(.caption) Toggle(isOn: $environmentMeasurementEnabled) { diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index a80e58e0..f34e0963 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -103,6 +103,7 @@ struct NetworkConfig: View { .keyboardType(.default) } } + .scrollDismissesKeyboard(.interactively) .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) Button { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index d0eb1e05..b998e532 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -38,11 +38,11 @@ struct Settings: View { Section("Radio Configuration") { NavigationLink { - ShareChannel(node: nodes.first(where: { $0.num == connectedNodeNum })) + ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "qrcode") .symbolRenderingMode(.hierarchical) - Text("Share Channel QR Code") + Text("Share Channels QR Code") } .disabled(bleManager.connectedPeripheral == nil) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 9ad31411..74bf5b40 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -31,7 +31,7 @@ struct QrCodeImage { } } -struct ShareChannel: View { +struct ShareChannels: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -89,7 +89,6 @@ struct ShareChannel: View { Text("Channel: \(channel.index) Name: \(channel.name ?? "")") } } - } } .frame(width: bounds.size.width, height: bounds.size.height) }