diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 97cdca18..6ea795ab 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannel.swift */; }; + DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* LocationHistory.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 */; }; @@ -127,6 +128,7 @@ 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 /* ShareChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannel.swift; sourceTree = ""; }; + DD73FD1028750779000852D6 /* LocationHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHistory.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 = ""; }; @@ -225,6 +227,7 @@ DD47E3CD26F103C600029299 /* NodeList.swift */, DD90860D26F69BAE00DC5189 /* NodeMap.swift */, DD2E65252767A01F00E45FC5 /* NodeDetail.swift */, + DD73FD1028750779000852D6 /* LocationHistory.swift */, ); path = Nodes; sourceTree = ""; @@ -661,6 +664,7 @@ DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */, DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, + DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */, C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 81176b23..5362d09a 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -660,7 +660,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessage.messageId = Int64(UInt32.random(in: UInt32(UInt8.max).. Int64 { + public func saveUser(config: User, fromUser: UserEntity?, toUser: UserEntity?, wantResponse: Bool) -> Int64 { var newMessageId: Int64 = 0 @@ -964,18 +1000,17 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "OUT" newMessage.admin = true - newMessage.adminDescription = "Saved User Config for \(toUser.longName ?? "Unknown")" + newMessage.adminDescription = "Saved User Config for \(toUser!.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { try context!.save() - if meshLoggingEnabled { MeshLogger.log("💾 Saved a new User Config Admin Message for node: \(String(toUser.num))") } + if meshLoggingEnabled { MeshLogger.log("💾 Saved a new User Config Admin Message for node: \(String(toUser!.num))") } connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) } catch { @@ -1024,12 +1059,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "OUT" newMessage.admin = true newMessage.adminDescription = "Saved Device Config for \(toUser.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { @@ -1083,12 +1117,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "OUT" newMessage.admin = true newMessage.adminDescription = "Saved Display Config for \(toUser.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { @@ -1143,12 +1176,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "IN" newMessage.admin = true newMessage.adminDescription = "Saved LoRa Config for \(toUser.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { @@ -1202,12 +1234,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "OUT" newMessage.admin = true newMessage.adminDescription = "Saved Position Config for \(toUser.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { @@ -1261,12 +1292,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "OUT" newMessage.admin = true newMessage.adminDescription = "Saved Canned Message Module Config for \(toUser.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { @@ -1319,12 +1349,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "OUT" newMessage.admin = true newMessage.adminDescription = "Saved External Notification Module Config for \(toUser.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { @@ -1341,9 +1370,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("💥 Error Inserting New Core Data MessageEntity: \(nsError)") } } - return newMessageId - } public func saveRangeTestModuleConfig(config: ModuleConfig.RangeTestConfig, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Int64 { @@ -1379,12 +1406,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "OUT" newMessage.admin = true newMessage.adminDescription = "Saved Range Test Module Config for \(toUser.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { @@ -1401,9 +1427,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("💥 Error Inserting New Core Data MessageEntity: \(nsError)") } } - return newMessageId - } public func saveSerialModuleConfig(config: ModuleConfig.SerialConfig, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Int64 { @@ -1440,12 +1464,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "OUT" newMessage.admin = true newMessage.adminDescription = "Saved Serial Module Config for \(toUser.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { @@ -1462,7 +1485,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("💥 Error Inserting New Core Data MessageEntity: \(nsError)") } } - return newMessageId } @@ -1499,12 +1521,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessageId = newMessage.messageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) newMessage.receivedACK = false - newMessage.direction = "OUT" newMessage.admin = true newMessage.adminDescription = "Saved Telemetry Module Config for \(toUser.longName ?? "Unknown")" newMessage.fromUser = fromUser newMessage.toUser = toUser - newMessage.messagePayload = try! dataMessage.jsonString() + newMessage.messagePayload = try! config.jsonString() do { @@ -1521,8 +1542,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("💥 Error Inserting New Core Data MessageEntity: \(nsError)") } } - return newMessageId - } } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 6f30c135..82c6addf 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -896,15 +896,18 @@ func nodeInfoPacket (nodeInfo: NodeInfo, meshLogging: Bool, context: NSManagedOb newNode.user = newUser } - let position = PositionEntity(context: context) - position.latitudeI = nodeInfo.position.latitudeI - position.longitudeI = nodeInfo.position.longitudeI - position.altitude = nodeInfo.position.altitude - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) - - var newPostions = [PositionEntity]() - newPostions.append(position) - newNode.positions? = NSOrderedSet(array: newPostions) + if nodeInfo.position.latitudeI > 0 || nodeInfo.position.longitudeI > 0 { + + let position = PositionEntity(context: context) + position.latitudeI = nodeInfo.position.latitudeI + position.longitudeI = nodeInfo.position.longitudeI + position.altitude = nodeInfo.position.altitude + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) + + var newPostions = [PositionEntity]() + newPostions.append(position) + newNode.positions? = NSOrderedSet(array: newPostions) + } // Look for a MyInfo let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") @@ -1132,54 +1135,59 @@ func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb let fetchNodePositionRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - + do { - - let fetchedNode = try context.fetch(fetchNodePositionRequest) as! [NodeInfoEntity] - - if fetchedNode.count == 1 { - - fetchedNode[0].id = Int64(packet.from) - fetchedNode[0].num = Int64(packet.from) - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) - fetchedNode[0].snr = packet.rxSnr - - if let positionMessage = try? Position(serializedData: packet.decoded.payload) { - - let position = PositionEntity(context: context) - position.latitudeI = positionMessage.latitudeI - position.longitudeI = positionMessage.longitudeI - position.altitude = positionMessage.altitude - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) - - let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet - mutablePositions.add(position) - - fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet - } - - } - do { + if let positionMessage = try? Position(serializedData: packet.decoded.payload) { + + // Don't save empty position packets + if positionMessage.longitudeI > 0 || positionMessage.latitudeI > 0 { + + let fetchedNode = try context.fetch(fetchNodePositionRequest) as! [NodeInfoEntity] - try context.save() + if fetchedNode.count == 1 { + + let position = PositionEntity(context: context) + position.latitudeI = positionMessage.latitudeI + position.longitudeI = positionMessage.longitudeI + position.altitude = positionMessage.altitude + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) - if meshLogging { - MeshLogger.log("💾 Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)") + let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet + mutablePositions.add(position) + + fetchedNode[0].id = Int64(packet.from) + fetchedNode[0].num = Int64(packet.from) + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + fetchedNode[0].snr = packet.rxSnr + fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet + + do { + + try context.save() + + if meshLogging { + MeshLogger.log("💾 Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)") + } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)") + } + } + } else { + + print("💥 Empty POSITION_APP Packet") } - - } catch { - - context.rollback() - - let nsError = error as NSError - print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)") } + } catch { print("💥 Error Fetching NodeInfoEntity for POSITION_APP") } - } func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { @@ -1332,7 +1340,6 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, meshLogging: newMessage.messageId = Int64(packet.id) newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime) newMessage.receivedACK = false - newMessage.direction = "IN" newMessage.isEmoji = packet.decoded.emoji == 1 if packet.decoded.replyID > 0 { diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents index 960df98a..956e7d02 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents @@ -50,7 +50,6 @@ - @@ -195,17 +194,17 @@ + - + + + - - - \ No newline at end of file diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index d0b56c58..a3e89b8b 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -1,13 +1,10 @@ // // Connect.swift -// MeshtasticApple +// Meshtastic Apple // -// Created by Garth Vander Houwen on 8/18/21. +// Copyright(c) Garth Vander Houwen 8/18/21. // -// Abstract: -// A view allowing you to interact with nearby meshtastic nodes - import SwiftUI import MapKit import CoreLocation @@ -20,7 +17,6 @@ struct Connect: View { @EnvironmentObject var userSettings: UserSettings @State var initialLoad: Bool = true - @State var isPreferredRadio: Bool = false var body: some View { @@ -32,158 +28,167 @@ struct Connect: View { NavigationView { VStack { - if bleManager.isSwitchedOn { - List { + List { + + if bleManager.isSwitchedOn { - if supportedVersion == false { + + + if supportedVersion == false { - Section(header: Text("Upgrade your Firmware").font(.title)) { + Section(header: Text("Upgrade your Firmware").font(.title)) { - Text("🚨 1.3 ALPHA PREVIEW this version of the app supports only version \(minimumVersion).").font(.subheadline).foregroundColor(.red) - } - .textCase(nil) + Text("🚨 1.3 ALPHA PREVIEW this version of the app supports only version \(minimumVersion).").font(.subheadline).foregroundColor(.red) } - - if bleManager.lastConnectionError.count > 0 { + .textCase(nil) + } + + if bleManager.lastConnectionError.count > 0 { - Section(header: Text("Connection Error").font(.title)) { + Section(header: Text("Connection Error").font(.title)) { - Text(bleManager.lastConnectionError).font(.title3).foregroundColor(.red) - } - .textCase(nil) + Text(bleManager.lastConnectionError).font(.title3).foregroundColor(.red) } + .textCase(nil) + } - Section(header: Text("Connected Radio").font(.title)) { + Section(header: Text("Connected Radio").font(.title)) { - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected { - HStack { + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected { + HStack { - Image(systemName: "antenna.radiowaves.left.and.right") - .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.green) - .padding(.trailing) + Image(systemName: "antenna.radiowaves.left.and.right") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.green) + .padding(.trailing) - VStack(alignment: .leading) { + VStack(alignment: .leading) { - if bleManager.connectedPeripheral != nil { + if bleManager.connectedPeripheral != nil { - Text(bleManager.connectedPeripheral.longName).font(.title2) + Text(bleManager.connectedPeripheral.longName).font(.title2) - } - Text("BLE Name: ").font(.caption)+Text(bleManager.connectedPeripheral.peripheral.name ?? "Unknown") - .font(.caption).foregroundColor(Color.gray) - if bleManager.connectedPeripheral != nil { - Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.firmwareVersion) - .font(.caption).foregroundColor(Color.gray) - Text("Bitrate: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00)) - .font(.caption).foregroundColor(Color.gray) - - - Text("Channel Utilization: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00)) - .font(.caption).foregroundColor(Color.gray) - Text("Air Time: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00)) - .font(.caption).foregroundColor(Color.gray) - } - if bleManager.connectedPeripheral.subscribed { - Text("Properly Subscribed").font(.caption) - } } - Spacer() + Text("BLE Name: ").font(.caption)+Text(bleManager.connectedPeripheral.peripheral.name ?? "Unknown") + .font(.caption).foregroundColor(Color.gray) + if bleManager.connectedPeripheral != nil { + Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.firmwareVersion) + .font(.caption).foregroundColor(Color.gray) + Text("Bitrate: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00)) + .font(.caption).foregroundColor(Color.gray) + + + Text("Channel Utilization: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00)) + .font(.caption).foregroundColor(Color.gray) + Text("Air Time: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00)) + .font(.caption).foregroundColor(Color.gray) + } + if bleManager.connectedPeripheral.subscribed { + Text("Properly Subscribed").font(.caption) + } + } + Spacer() - VStack(alignment: .center) { + VStack(alignment: .center) { - Text("Preferred").font(.caption2) - Text("Radio").font(.caption2) - Toggle("Preferred Radio", isOn: $isPreferredRadio) - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .labelsHidden() - .onChange(of: isPreferredRadio) { value in - if value { + Text("Preferred").font(.caption2) + Text("Radio").font(.caption2) + Toggle("Preferred Radio", isOn: $isPreferredRadio) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .labelsHidden() + .onChange(of: isPreferredRadio) { value in + if value { - if bleManager.connectedPeripheral != nil { + if bleManager.connectedPeripheral != nil { - let deviceName = (bleManager.connectedPeripheral.peripheral.name ?? "") - userSettings.preferredPeripheralName = deviceName - - } else { - - userSettings.preferredPeripheralName = bleManager.connectedPeripheral.longName - } - - userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString + let deviceName = (bleManager.connectedPeripheral.peripheral.name ?? "") + userSettings.preferredPeripheralName = deviceName } else { - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { - - userSettings.preferredPeripheralId = "" - userSettings.preferredPeripheralName = "" + userSettings.preferredPeripheralName = bleManager.connectedPeripheral.longName } - } - } - } - - } - .swipeActions { - Button(role: .destructive) { - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { - bleManager.disconnectPeripheral() - isPreferredRadio = false - } - } label: { - Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") - } - } - .padding([.top, .bottom]) - - - } else { - HStack { - Image(systemName: "antenna.radiowaves.left.and.right.slash") - .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.red) - .padding(.trailing) - Text("No device connected").font(.title3) - } - .padding() - } + userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString - } - .textCase(nil) - - if bleManager.peripherals.count > 0 { - Section(header: Text("Available Radios").font(.title)) { - ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.name < $1.name })) { peripheral in - HStack { - Image(systemName: "circle.fill") - .imageScale(.large).foregroundColor(.gray) - .padding(.trailing) - Button(action: { - self.bleManager.stopScanning() - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { - - self.bleManager.disconnectPeripheral() - } - self.bleManager.connectTo(peripheral: peripheral.peripheral) - if userSettings.preferredPeripheralId == peripheral.peripheral.identifier.uuidString { - - isPreferredRadio = true } else { - isPreferredRadio = false + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { + + userSettings.preferredPeripheralId = "" + userSettings.preferredPeripheralName = "" } - }) { - Text(peripheral.name).font(.title3) } - Spacer() - Text(String(peripheral.rssi) + " dB").font(.title3) - }.padding([.bottom, .top]) + } } - }.textCase(nil) + + } + .swipeActions { + + Button(role: .destructive) { + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { + bleManager.disconnectPeripheral() + isPreferredRadio = false + } + } label: { + Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") + } + } + .padding([.top, .bottom]) + + + } else { + HStack { + Image(systemName: "antenna.radiowaves.left.and.right.slash") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.red) + .padding(.trailing) + Text("No device connected").font(.title3) + } + .padding() } - } + + } + .textCase(nil) + + if bleManager.peripherals.count > 0 { + Section(header: Text("Available Radios").font(.title)) { + ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.name < $1.name })) { peripheral in + HStack { + Image(systemName: "circle.fill") + .imageScale(.large).foregroundColor(.gray) + .padding(.trailing) + Button(action: { + self.bleManager.stopScanning() + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { + + self.bleManager.disconnectPeripheral() + } + self.bleManager.connectTo(peripheral: peripheral.peripheral) + if userSettings.preferredPeripheralId == peripheral.peripheral.identifier.uuidString { + + isPreferredRadio = true + } else { + + isPreferredRadio = false + } + }) { + Text(peripheral.name).font(.title3) + } + Spacer() + Text(String(peripheral.rssi) + " dB").font(.title3) + }.padding([.bottom, .top]) + } + }.textCase(nil) + } + + } else { + Text("Bluetooth: OFF") + .foregroundColor(.red) + .font(.title) + } + } HStack(alignment: .center) { @@ -250,11 +255,7 @@ struct Connect: View { } .padding(.bottom, 10) - } else { - Text("Bluetooth: OFF") - .foregroundColor(.red) - .font(.title) - } + } .navigationTitle("Bluetooth Radios") .navigationBarItems(trailing: @@ -272,11 +273,11 @@ struct Connect: View { .navigationViewStyle(StackNavigationViewStyle()) .onAppear(perform: { - self.bleManager.context = context - self.bleManager.userSettings = userSettings - if initialLoad { + self.bleManager.context = context + self.bleManager.userSettings = userSettings + // Ask for notification permission UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in if success { diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index e7d331ce..558c027d 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -21,7 +21,7 @@ struct ContentView: View { TabView(selection: $selection) { Contacts() .tabItem { - Label("Messages", systemImage: "text.bubble") + Label("Messages", systemImage: "message") .symbolRenderingMode(.hierarchical) .symbolVariant(.none) diff --git a/Meshtastic/Views/Nodes/LocationHistory.swift b/Meshtastic/Views/Nodes/LocationHistory.swift new file mode 100644 index 00000000..3a34d5ac --- /dev/null +++ b/Meshtastic/Views/Nodes/LocationHistory.swift @@ -0,0 +1,115 @@ +// +// LocationHistory.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 7/5/22. +// +import SwiftUI + +struct LocationHistory: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + var node: NodeInfoEntity + + var body: some View { + + VStack { + + List { + + ForEach(node.positions!.array 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) + + 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) + } + + HStack { + + Image(systemName: "clock.badge.checkmark.fill") + .font(.subheadline) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Time:") + .font(.caption) + DateTimeText(dateTime: mappin.time) + .foregroundColor(.gray) + .font(.caption) + + } + } + } + } + } + } + .padding() + .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 acfa3103..1dcf72ad 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -11,7 +11,8 @@ struct NodeDetail: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @EnvironmentObject var userSettings: UserSettings + + @State var initialLoad: Bool = true @State private var isPresentingShutdownConfirm: Bool = false @State private var isPresentingRebootConfirm: Bool = false @@ -28,8 +29,8 @@ struct NodeDetail: View { VStack { - if node.positions?.count ?? 0 >= 1 { - + if node.positions?.count ?? 0 > 0 { + let mostRecent = node.positions?.lastObject as! PositionEntity if mostRecent.coordinate != nil { @@ -51,43 +52,50 @@ struct NodeDetail: View { interactionModes: [.all], showsUserLocation: true, userTrackingMode: .constant(.follow), - annotationItems: annotations - - ) + annotationItems: annotations) { location in return MapAnnotation( - coordinate: location.coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0), - - content: { - - NodeAnnotation(time: location.time!) - } + coordinate: location.coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0), + content: { + + NodeAnnotation(time: location.time!) + } ) } - .frame(idealWidth: bounds.size.width, maxHeight: bounds.size.height / 2) + .ignoresSafeArea(.all, edges: [.leading, .trailing]) + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2) } - } else { + + NavigationLink { + LocationHistory(node: node) + } label: { - Image(node.user?.hwModel ?? "UNSET") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: bounds.size.width, height: bounds.size.height / 2) + Image(systemName: "building.columns") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Position History \(node.positions?.count ?? 0) Points") + .font(.title2) + } } } else { - + Image(node.user?.hwModel ?? "UNSET") .resizable() .aspectRatio(contentMode: .fit) + .cornerRadius(10) .frame(width: bounds.size.width, height: bounds.size.height / 2) } - + + + ScrollView { - - HStack { - if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { + + if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { + HStack { if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { @@ -108,7 +116,7 @@ struct NodeDetail: View { ) { Button("Shutdown Node?", role: .destructive) { - if !bleManager.sendShutdown(destNum: node.num, wantResponse: false) { + if !bleManager.sendShutdown(destNum: node.num, wantResponse: true) { print("Shutdown Failed") } @@ -129,215 +137,284 @@ struct NodeDetail: View { .controlSize(.large) .padding() .confirmationDialog( + "Are you sure?", isPresented: $isPresentingRebootConfirm ) { + Button("Reboot Node?", role: .destructive) { - if !bleManager.sendReboot(destNum: node.num, wantResponse: false) { + if !bleManager.sendReboot(destNum: node.num, wantResponse: true) { print("Reboot Failed") } } } } - } - .padding(5) - Divider() - HStack { - - Image(systemName: "clock.badge.checkmark.fill") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - - LastHeardText(lastHeard: node.lastHeard).font(.title3) - } - .padding() - Divider() - - HStack { - - VStack(alignment: .center) { - Text("AKA").font(.title2).fixedSize() - CircleText(text: node.user?.shortName ?? "???", color: .accentColor) - .offset(y: 10) - } .padding(5) - Divider() - - VStack { - - if node.user != nil { - - Image(node.user!.hwModel ?? "UNSET") - .resizable() - .frame(width: 50, height: 50) - .cornerRadius(5) - - Text(String(node.user!.hwModel ?? "UNSET")) - .font(.callout).fixedSize() - } - } - .padding(5) + } + + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + // Add a divider if there is no map + if (node.positions?.count ?? 0) == 0 { + + Divider() + } - if node.snr > 0 { - Divider() - VStack(alignment: .center) { - - Image(systemName: "waveform.path") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("SNR").font(.title2).fixedSize() - Text(String(node.snr)) - .font(.title2) - .foregroundColor(.gray) - .fixedSize() - } - .padding(5) - } - - if node.telemetries?.count ?? 0 >= 1 { - - let mostRecent = node.telemetries?.lastObject as! TelemetryEntity - - Divider() - - VStack(alignment: .center) { - - BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor) - .padding(.bottom) - - if mostRecent.batteryLevel > 0 { - Text(String(mostRecent.batteryLevel) + "%") - .font(.title3) - .foregroundColor(.gray) - .fixedSize() - } - if mostRecent.voltage > 0 { - - Text(String(format: "%.2f", mostRecent.voltage) + " V") - .font(.title3) - .foregroundColor(.gray) - .fixedSize() - } - } - .padding(5) - } - } - .padding(4) - - 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) - } - } - .padding(5) - Divider() - HStack { - Image(systemName: "globe") - .font(.headline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("MAC Address: ") - Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) - } - .padding() - - if node.positions?.count ?? 0 >= 1 { - - Divider() - HStack { + + VStack(alignment: .center) { + + Text("AKA").font(.largeTitle) + .foregroundColor(.gray).fixedSize() + .offset(y:20) + CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 75, fontSize: 26) + } + .padding() - Image(systemName: "location.circle.fill") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Location History").font(.title2) + Divider() + + VStack { + + if node.user != nil { + + Image(hwModelString) + .resizable() + .frame(width: 90, height: 90) + .cornerRadius(5) + + Text(String(hwModelString)) + .foregroundColor(.gray) + .font(.largeTitle).fixedSize() + } + } + .padding() + + + if node.snr > 0 { + Divider() + VStack(alignment: .center) { + + Image(systemName: "waveform.path") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + .padding(.bottom, 10) + Text("SNR").font(.largeTitle).fixedSize() + Text(String(node.snr)) + .font(.largeTitle) + .foregroundColor(.gray) + .fixedSize() + } + } + + if node.telemetries?.count ?? 0 >= 1 { + + let mostRecent = node.telemetries?.lastObject as! TelemetryEntity + + Divider() + + VStack(alignment: .center) { + + BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .largeTitle, color: .accentColor) + .padding(.bottom, 10) + + if mostRecent.batteryLevel > 0 { + Text(String(mostRecent.batteryLevel) + "%") + .font(.largeTitle) + .frame(width: 100) + .foregroundColor(.gray) + .fixedSize() + } + if mostRecent.voltage > 0 { + + Text(String(format: "%.2f", mostRecent.voltage) + " V") + .font(.largeTitle) + .foregroundColor(.gray) + .fixedSize() + } + } + .padding() + } } .padding() Divider() - ForEach(node.positions!.array as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in - - if mappin.coordinate != nil { - - VStack { - - 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) - - 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) - } - - HStack { - - Image(systemName: "clock.badge.checkmark.fill") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .font(.caption) - DateTimeText(dateTime: mappin.time) - .foregroundColor(.gray) - .font(.caption) - Divider() - } + HStack(alignment: .center) { + VStack { + HStack { + Image(systemName: "person") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("User Id:").font(.title) } + Text(node.user?.userId ?? "??????").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: "globe") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("MAC Address: ").font(.title) + + } + Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")) + .font(.title) + .foregroundColor(.gray) + } + Divider() + VStack{ + HStack { + Image(systemName: "clock.badge.checkmark.fill") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Last Heard: ").font(.title) + + } + DateTimeText(dateTime: node.lastHeard) + .font(.title) + .foregroundColor(.gray) } } + .padding() + Divider() + + } else { + + HStack { + + VStack(alignment: .center) { + Text("AKA").font(.title2).fixedSize() + CircleText(text: node.user?.shortName ?? "???", color: .accentColor) + .offset(y: 10) + } + .padding(5) + + Divider() + + VStack { + + if node.user != nil { + + Image(node.user!.hwModel ?? "UNSET") + .resizable() + .frame(width: 50, height: 50) + .cornerRadius(5) + + Text(String(node.user!.hwModel ?? "UNSET")) + .font(.callout).fixedSize() + } + } + .padding(5) + + + if node.snr > 0 { + Divider() + VStack(alignment: .center) { + + Image(systemName: "waveform.path") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("SNR").font(.title2).fixedSize() + Text(String(node.snr)) + .font(.title2) + .foregroundColor(.gray) + .fixedSize() + } + .padding(5) + } + + if node.telemetries?.count ?? 0 >= 1 { + + let mostRecent = node.telemetries?.lastObject as! TelemetryEntity + + Divider() + + VStack(alignment: .center) { + + BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor) + .padding(.bottom) + + if mostRecent.batteryLevel > 0 { + Text(String(mostRecent.batteryLevel) + "%") + .font(.title3) + .foregroundColor(.gray) + .fixedSize() + } + if mostRecent.voltage > 0 { + + Text(String(format: "%.2f", mostRecent.voltage) + " V") + .font(.title3) + .foregroundColor(.gray) + .fixedSize() + } + } + .padding(5) + } + } + .padding(4) + + 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) + } + } + .padding(5) + Divider() + HStack { + Image(systemName: "globe") + .font(.headline) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("MAC Address: ") + Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) + } + .padding() } } } .edgesIgnoringSafeArea([.leading, .trailing]) - .padding(1) } } .navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown") - .navigationBarTitleDisplayMode(.inline) + .navigationBarTitleDisplayMode(.automatic) .navigationBarItems(trailing: ZStack { @@ -348,12 +425,15 @@ struct NodeDetail: View { name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") } ) - .onAppear(perform: { + .onAppear { - self.bleManager.context = context - self.bleManager.userSettings = userSettings + 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 544a80fa..94c6bd06 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -15,14 +15,16 @@ struct NodeList: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings + + @State var initialLoad: Bool = true @FetchRequest( sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], animation: .default) - private var nodes: FetchedResults + private var nodes: FetchedResults - @State private var selection: String? + @State private var selection: String? = "" var body: some View { @@ -102,13 +104,17 @@ struct NodeList: View { .navigationTitle("All Nodes") .onAppear { - self.bleManager.context = context - self.bleManager.userSettings = userSettings + if initialLoad { + + self.bleManager.context = context + self.bleManager.userSettings = userSettings + self.initialLoad = false - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - if nodes.count > 0 { - selection = "0" + if nodes.count > 0 { + selection = "0" + } } } } diff --git a/Meshtastic/Views/Settings/AdminMessageList.swift b/Meshtastic/Views/Settings/AdminMessageList.swift index 582a5251..e2353961 100644 --- a/Meshtastic/Views/Settings/AdminMessageList.swift +++ b/Meshtastic/Views/Settings/AdminMessageList.swift @@ -16,7 +16,6 @@ import CoreLocation struct AdminMessageList: View { @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager var user: UserEntity? @@ -38,10 +37,7 @@ struct AdminMessageList: View { Image(systemName: "checkmark.square") .foregroundColor(.gray) .font(.caption) - Text("Acknowledged: ") - .foregroundColor(.gray) - .font(.caption) - DateTimeText(dateTime: Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp))) + Text("Acknowledged: \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)), style: .time)") .foregroundColor(.gray) .font(.caption) @@ -49,7 +45,7 @@ struct AdminMessageList: View { Image(systemName: "square") .foregroundColor(.gray) .font(.caption) - Text("Acknowledged") + Text("Not Acknowledged") .foregroundColor(.gray) .font(.caption) } @@ -57,7 +53,7 @@ struct AdminMessageList: View { } } } - .navigationTitle("Admin Message History") + .navigationTitle("Admin Message Log") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index a9f6dc04..5bce4f0b 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -119,28 +119,8 @@ struct AppSettings: View { .foregroundColor(.gray) } - Section(header: Text("OPTIONS")) { + Section(header: Text("Options")) { - Toggle(isOn: $userSettings.provideLocation) { - - Label("Provide location to mesh", systemImage: "location.circle.fill") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - - if userSettings.provideLocation { - - Picker(" Update Interval", selection: $userSettings.provideLocationInterval) { - ForEach(LocationUpdateInterval.allCases) { lu in - Text(lu.description) - } - } - .pickerStyle(DefaultPickerStyle()) - - Text("How frequently your phone will send your location to the device, location updates to the mesh are managed by the device.") - .font(.caption) - .listRowSeparator(.visible) - } - Picker("Keyboard Type", selection: $userSettings.keyboardType) { ForEach(KeyboardType.allCases) { kb in Text(kb.description) @@ -156,6 +136,28 @@ struct AppSettings: View { .pickerStyle(DefaultPickerStyle()) } + + Section(header: Text("Phone GPS")) { + + Toggle(isOn: $userSettings.provideLocation) { + + Label("Provide location to mesh", systemImage: "location.circle.fill") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + if userSettings.provideLocation { + + Picker(" Update Interval", selection: $userSettings.provideLocationInterval) { + ForEach(LocationUpdateInterval.allCases) { lu in + Text(lu.description) + } + } + .pickerStyle(DefaultPickerStyle()) + + Text("How frequently your phone will send your location to the device, location updates to the mesh are managed by the device.") + .font(.caption) + .listRowSeparator(.visible) + } + } } } .navigationTitle("App Settings") diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 951e85d8..24a71383 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -51,7 +51,7 @@ struct DeviceConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? @State private var isPresentingFactoryResetConfirm: Bool = false @State private var isPresentingSaveConfirm: Bool = false @@ -124,7 +124,7 @@ struct DeviceConfig: View { dc.serialDisabled = !serialEnabled dc.debugLogEnabled = debugLogEnabled - let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0 { @@ -153,7 +153,7 @@ struct DeviceConfig: View { ) { Button("Erase all device settings?", role: .destructive) { - if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: true) { print("Factory Reset Failed") } @@ -176,33 +176,24 @@ struct DeviceConfig: View { self.bleManager.context = context - self.deviceRole = Int(node.deviceConfig?.role ?? 0) - self.serialEnabled = (node.deviceConfig?.serialEnabled ?? true) - self.debugLogEnabled = node.deviceConfig?.debugLogEnabled ?? false + self.deviceRole = Int(node!.deviceConfig?.role ?? 0) + self.serialEnabled = (node!.deviceConfig?.serialEnabled ?? true) + self.debugLogEnabled = node!.deviceConfig?.debugLogEnabled ?? false self.hasChanges = false self.initialLoad = false } } .onChange(of: deviceRole) { newRole in - if newRole != node.deviceConfig!.role { - - hasChanges = true - } + if newRole != node!.deviceConfig!.role { hasChanges = true } } .onChange(of: serialEnabled) { newSerial in - if newSerial != node.deviceConfig!.serialEnabled { - - hasChanges = true - } + if newSerial != node!.deviceConfig!.serialEnabled { hasChanges = true } } .onChange(of: debugLogEnabled) { newDebugLog in - if newDebugLog != node.deviceConfig!.debugLogEnabled { - - hasChanges = true - } + if newDebugLog != node!.deviceConfig!.debugLogEnabled { hasChanges = true } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 051671fa..e15ebc8a 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -58,22 +58,18 @@ enum GpsFormats: Int, CaseIterable, Identifiable { // Default of 0 is One Minute enum ScreenOnIntervals: Int, CaseIterable, Identifiable { - case fifteenSeconds = 15 - case thirtySeconds = 30 - case oneMinute = 0 + case oneMinute = 60 case fiveMinutes = 300 - case tenMinutes = 600 + case tenMinutes = 0 case fifteenMinutes = 900 - case max = 2147483647 + case thirtyMinutes = 1800 + case oneHour = 3600 + case max = 31536000 // One Year var id: Int { self.rawValue } var description: String { get { switch self { - case .fifteenSeconds: - return "Fifteen Seconds" - case .thirtySeconds: - return "Thirty Seconds" case .oneMinute: return "One Minute" case .fiveMinutes: @@ -82,6 +78,10 @@ enum ScreenOnIntervals: Int, CaseIterable, Identifiable { return "Ten Minutes" case .fifteenMinutes: return "Fifteen Minutes" + case .thirtyMinutes: + return "Thirty Minutes" + case .oneHour: + return "One Hour" case .max: return "Always On" } @@ -125,7 +125,8 @@ struct DisplayConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? + @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true @State var hasChanges = false @@ -203,7 +204,7 @@ struct DisplayConfig: View { dc.screenOnSecs = UInt32(screenOnSeconds) dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval) - let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0 { @@ -230,30 +231,30 @@ struct DisplayConfig: View { self.bleManager.context = context - self.gpsFormat = Int(node.displayConfig?.gpsFormat ?? 0) - self.screenOnSeconds = Int(node.displayConfig?.screenOnSeconds ?? 0) - self.screenCarouselInterval = Int(node.displayConfig?.screenCarouselInterval ?? 0) + self.gpsFormat = Int(node!.displayConfig?.gpsFormat ?? 0) + self.screenOnSeconds = Int(node!.displayConfig?.screenOnSeconds ?? 0) + self.screenCarouselInterval = Int(node!.displayConfig?.screenCarouselInterval ?? 0) self.hasChanges = false self.initialLoad = false } } .onChange(of: screenOnSeconds) { newScreenSecs in - if newScreenSecs != node.displayConfig!.screenOnSeconds { + if newScreenSecs != node!.displayConfig!.screenOnSeconds { hasChanges = true } } .onChange(of: screenCarouselInterval) { newCarouselSecs in - if newCarouselSecs != node.displayConfig!.screenCarouselInterval { + if newCarouselSecs != node!.displayConfig!.screenCarouselInterval { hasChanges = true } } .onChange(of: gpsFormat) { newGpsFormat in - if newGpsFormat != node.displayConfig!.gpsFormat { + if newGpsFormat != node!.displayConfig!.gpsFormat { hasChanges = true } diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index b1c576bd..58ec7f02 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -96,8 +96,8 @@ enum ModemPresets : Int, CaseIterable, Identifiable { case LongFast = 0 case LongSlow = 1 case VLongSlow = 2 - case MidSlow = 3 - case MidFast = 4 + case MedSlow = 3 + case MedFast = 4 case ShortSlow = 5 case ShortFast = 6 @@ -112,9 +112,9 @@ enum ModemPresets : Int, CaseIterable, Identifiable { return "Long Range - Slow" case .VLongSlow: return "Very Long Range - Slow" - case .MidSlow: + case .MedSlow: return "Medium Range - Slow" - case .MidFast: + case .MedFast: return "Medium Range - Fast" case .ShortSlow: return "Short Range - Slow" @@ -133,9 +133,9 @@ enum ModemPresets : Int, CaseIterable, Identifiable { return Config.LoRaConfig.ModemPreset.longSlow case .VLongSlow: return Config.LoRaConfig.ModemPreset.vlongSlow - case .MidSlow: + case .MedSlow: return Config.LoRaConfig.ModemPreset.medSlow - case .MidFast: + case .MedFast: return Config.LoRaConfig.ModemPreset.medFast case .ShortSlow: return Config.LoRaConfig.ModemPreset.shortSlow @@ -185,7 +185,7 @@ struct LoRaConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true @@ -263,7 +263,7 @@ struct LoRaConfig: View { lc.region = RegionCodes(rawValue: region)!.protoEnumValue() lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() - let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0 { @@ -290,35 +290,29 @@ struct LoRaConfig: View { if self.initialLoad{ self.bleManager.context = context - self.hopLimit = Int(node.loRaConfig?.hopLimit ?? 0) - self.region = Int(node.loRaConfig?.regionCode ?? 0) - self.modemPreset = Int(node.loRaConfig?.modemPreset ?? 0) + self.hopLimit = Int(node!.loRaConfig?.hopLimit ?? 0) + self.region = Int(node!.loRaConfig?.regionCode ?? 0) + self.modemPreset = Int(node!.loRaConfig?.modemPreset ?? 0) self.hasChanges = false self.initialLoad = false } } .onChange(of: region) { newRegion in - if node.loRaConfig != nil { - - if newRegion != node.loRaConfig!.regionCode { - - hasChanges = true - } + if node!.loRaConfig != nil { + if newRegion != node!.loRaConfig!.regionCode { hasChanges = true } } } .onChange(of: modemPreset) { newModemPreset in - if newModemPreset != node.loRaConfig!.modemPreset { - - hasChanges = true + if node!.loRaConfig != nil { + if newModemPreset != node!.loRaConfig!.modemPreset { hasChanges = true } } } .onChange(of: hopLimit) { newHopLimit in - if newHopLimit != node.loRaConfig!.hopLimit { - - hasChanges = true + if node!.loRaConfig != nil { + if newHopLimit != node!.loRaConfig!.hopLimit { hasChanges = true } } } .navigationViewStyle(StackNavigationViewStyle()) diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index cabbc687..834101c3 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -13,6 +13,7 @@ enum ConfigPresets : Int, CaseIterable, Identifiable { case rakRotaryEncoder = 1 case tbeamThreeButtonScreen = 2 case cardKB = 3 + case facesKB = 4 var id: Int { self.rawValue } var description: String { @@ -26,7 +27,9 @@ enum ConfigPresets : Int, CaseIterable, Identifiable { case .tbeamThreeButtonScreen: return "TBEAM 3 Button OLED Screen" case .cardKB: - return "Card KB" + return "M5 Stack Card KeyBoard" + case .facesKB: + return "M5 Stack Faces KeyBoard" } } } @@ -97,7 +100,7 @@ struct CannedMessagesConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true @@ -260,7 +263,6 @@ struct CannedMessagesConfig: View { } .disabled(configPreset > 0) } - .disabled(!(node.myInfo?.hasWifi ?? false)) Button { @@ -270,7 +272,7 @@ struct CannedMessagesConfig: View { Label("Save", systemImage: "square.and.arrow.down") } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node.myInfo?.hasWifi ?? false)) + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) @@ -290,7 +292,7 @@ struct CannedMessagesConfig: View { if rotary1Enabled { /// Input event origin accepted by the canned messages - /// Can be e.g. "rotEnc1", "upDownEnc1" or keyword "_any" + /// Can be e.g. "rotEnc1", "upDownEnc1", "cardkb", "faceskb" 623or keyword "_any" cmc.allowInputSource = "rotEnc1" } else if updown1Enabled { @@ -308,7 +310,7 @@ struct CannedMessagesConfig: View { cmc.inputbrokerEventCcw = InputEventChars(rawValue: inputbrokerEventCcw)!.protoEnumValue() cmc.inputbrokerEventPress = InputEventChars(rawValue: inputbrokerEventPress)!.protoEnumValue() - let adminMessageId = bleManager.saveCannedMessageModuleConfig(config: cmc, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.saveCannedMessageModuleConfig(config: cmc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true @@ -331,13 +333,13 @@ struct CannedMessagesConfig: View { if self.initialLoad{ self.bleManager.context = context - self.enabled = node.cannedMessageConfig?.enabled ?? false - self.sendBell = node.cannedMessageConfig?.sendBell ?? false - self.rotary1Enabled = node.cannedMessageConfig?.rotary1Enabled ?? false - self.updown1Enabled = node.cannedMessageConfig?.updown1Enabled ?? false - self.inputbrokerPinA = Int(node.cannedMessageConfig?.inputbrokerPinA ?? 0) - self.inputbrokerPinB = Int(node.cannedMessageConfig?.inputbrokerPinB ?? 0) - self.inputbrokerPinPress = Int(node.cannedMessageConfig?.inputbrokerPinPress ?? 0) + self.enabled = node!.cannedMessageConfig?.enabled ?? false + self.sendBell = node!.cannedMessageConfig?.sendBell ?? false + self.rotary1Enabled = node!.cannedMessageConfig?.rotary1Enabled ?? false + self.updown1Enabled = node!.cannedMessageConfig?.updown1Enabled ?? false + self.inputbrokerPinA = Int(node!.cannedMessageConfig?.inputbrokerPinA ?? 0) + self.inputbrokerPinB = Int(node!.cannedMessageConfig?.inputbrokerPinB ?? 0) + self.inputbrokerPinPress = Int(node!.cannedMessageConfig?.inputbrokerPinPress ?? 0) self.hasChanges = false self.initialLoad = false } @@ -361,35 +363,35 @@ struct CannedMessagesConfig: View { } .onChange(of: enabled) { newEnabled in - if newEnabled != node.cannedMessageConfig!.enabled { hasChanges = true } + if newEnabled != node!.cannedMessageConfig!.enabled { hasChanges = true } } .onChange(of: sendBell) { newBell in - if newBell != node.cannedMessageConfig!.sendBell { hasChanges = true } + if newBell != node!.cannedMessageConfig!.sendBell { hasChanges = true } } .onChange(of: inputbrokerPinA) { newPinA in - if newPinA != node.cannedMessageConfig!.inputbrokerPinA { hasChanges = true } + if newPinA != node!.cannedMessageConfig!.inputbrokerPinA { hasChanges = true } } .onChange(of: inputbrokerPinB) { newPinB in - if newPinB != node.cannedMessageConfig!.inputbrokerPinB { hasChanges = true } + if newPinB != node!.cannedMessageConfig!.inputbrokerPinB { hasChanges = true } } .onChange(of: inputbrokerPinPress) { newPinPress in - if newPinPress != node.cannedMessageConfig!.inputbrokerPinPress { hasChanges = true } + if newPinPress != node!.cannedMessageConfig!.inputbrokerPinPress { hasChanges = true } } .onChange(of: inputbrokerEventCw) { newKeyA in - if newKeyA != node.cannedMessageConfig!.inputbrokerEventCw { hasChanges = true } + if newKeyA != node!.cannedMessageConfig!.inputbrokerEventCw { hasChanges = true } } .onChange(of: inputbrokerEventCcw) { newKeyB in - if newKeyB != node.cannedMessageConfig!.inputbrokerEventCcw { hasChanges = true } + if newKeyB != node!.cannedMessageConfig!.inputbrokerEventCcw { hasChanges = true } } .onChange(of: inputbrokerEventPress) { newKeyPress in - if newKeyPress != node.cannedMessageConfig!.inputbrokerEventPress { hasChanges = true } + if newKeyPress != node!.cannedMessageConfig!.inputbrokerEventPress { hasChanges = true } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 8a52e813..2c5462f5 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -51,7 +51,7 @@ struct ExternalNotificationConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true @@ -138,7 +138,7 @@ struct ExternalNotificationConfig: View { Label("Save", systemImage: "square.and.arrow.down") } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node.myInfo?.hasWifi ?? false)) + .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node!.myInfo?.hasWifi ?? false)) .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) @@ -158,7 +158,7 @@ struct ExternalNotificationConfig: View { enc.output = UInt32(output) enc.outputMs = UInt32(outputMilliseconds) - let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0{ @@ -185,12 +185,12 @@ struct ExternalNotificationConfig: View { self.bleManager.context = context - self.enabled = node.externalNotificationConfig?.enabled ?? false - self.alertBell = node.externalNotificationConfig?.alertBell ?? false - self.alertMessage = node.externalNotificationConfig?.alertMessage ?? false - self.active = node.externalNotificationConfig?.active ?? false - self.output = Int(node.externalNotificationConfig?.output ?? 0) - self.outputMilliseconds = Int(node.externalNotificationConfig?.outputMilliseconds ?? 0) + self.enabled = node!.externalNotificationConfig?.enabled ?? false + self.alertBell = node!.externalNotificationConfig?.alertBell ?? false + self.alertMessage = node!.externalNotificationConfig?.alertMessage ?? false + self.active = node!.externalNotificationConfig?.active ?? false + self.output = Int(node!.externalNotificationConfig?.output ?? 0) + self.outputMilliseconds = Int(node!.externalNotificationConfig?.outputMilliseconds ?? 0) self.hasChanges = false self.initialLoad = false @@ -198,27 +198,27 @@ struct ExternalNotificationConfig: View { } .onChange(of: enabled) { newEnabled in - if newEnabled != node.externalNotificationConfig!.enabled { hasChanges = true } + if newEnabled != node!.externalNotificationConfig!.enabled { hasChanges = true } } .onChange(of: alertBell) { newAlertBell in - if newAlertBell != node.externalNotificationConfig!.alertBell { hasChanges = true } + if newAlertBell != node!.externalNotificationConfig!.alertBell { hasChanges = true } } .onChange(of: alertMessage) { newAlertMessage in - if newAlertMessage != node.externalNotificationConfig!.alertMessage { hasChanges = true } + if newAlertMessage != node!.externalNotificationConfig!.alertMessage { hasChanges = true } } .onChange(of: active) { newActuve in - if newActuve != node.externalNotificationConfig!.active { hasChanges = true } + if newActuve != node!.externalNotificationConfig!.active { hasChanges = true } } .onChange(of: output) { newOutput in - if newOutput != node.externalNotificationConfig!.output { hasChanges = true } + if newOutput != node!.externalNotificationConfig!.output { hasChanges = true } } .onChange(of: outputMilliseconds) { newOutputMs in - if newOutputMs != node.externalNotificationConfig!.outputMilliseconds { hasChanges = true } + if newOutputMs != node!.externalNotificationConfig!.outputMilliseconds { hasChanges = true } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 8c7c672e..ccd29da8 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -42,7 +42,7 @@ struct RangeTestConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true @@ -80,13 +80,13 @@ struct RangeTestConfig: View { Label("Save", systemImage: "square.and.arrow.down.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .disabled(!(node.myInfo?.hasWifi ?? false)) + .disabled(!(node!.myInfo?.hasWifi ?? false)) Text("Saves a CSV with the range test message details, currently only available on ESP32 devices with a web server.") .font(.caption) } } - .disabled(!(node.myInfo?.hasWifi ?? false)) + .disabled(!(node!.myInfo?.hasWifi ?? false)) Button { @@ -96,7 +96,7 @@ struct RangeTestConfig: View { Label("Save", systemImage: "square.and.arrow.down") } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node.myInfo?.hasWifi ?? false)) + .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node!.myInfo?.hasWifi ?? false)) .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) @@ -113,7 +113,7 @@ struct RangeTestConfig: View { rtc.save = save rtc.sender = UInt32(sender) - let adminMessageId = bleManager.saveRangeTestModuleConfig(config: rtc, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.saveRangeTestModuleConfig(config: rtc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0 { @@ -139,33 +139,24 @@ struct RangeTestConfig: View { if self.initialLoad{ self.bleManager.context = context - self.enabled = node.rangeTestConfig?.enabled ?? false - self.save = node.rangeTestConfig?.save ?? false - self.sender = Int(node.rangeTestConfig?.sender ?? 0) + self.enabled = node!.rangeTestConfig?.enabled ?? false + self.save = node!.rangeTestConfig?.save ?? false + self.sender = Int(node!.rangeTestConfig?.sender ?? 0) self.hasChanges = false self.initialLoad = false } } .onChange(of: enabled) { newEnabled in - if newEnabled != node.rangeTestConfig!.enabled { - - hasChanges = true - } + if newEnabled != node!.rangeTestConfig!.enabled { hasChanges = true } } .onChange(of: save) { newSave in - if newSave != node.rangeTestConfig!.save { - - hasChanges = true - } + if newSave != node!.rangeTestConfig!.save { hasChanges = true } } .onChange(of: sender) { newSender in - if newSender != node.rangeTestConfig!.sender { - - hasChanges = true - } + if newSender != node!.rangeTestConfig!.sender { hasChanges = true } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 780879c7..78182f36 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -178,7 +178,7 @@ struct SerialConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true @@ -302,7 +302,7 @@ struct SerialConfig: View { sc.timeout = UInt32(timeout) sc.mode = SerialModeTypes(rawValue: mode)!.protoEnumValue() - let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0 { @@ -329,13 +329,13 @@ struct SerialConfig: View { self.bleManager.context = context - self.enabled = node.serialConfig?.enabled ?? false - self.echo = node.serialConfig?.echo ?? false - self.rxd = Int(node.serialConfig?.rxd ?? 0) - self.txd = Int(node.serialConfig?.txd ?? 0) - self.baudRate = Int(node.serialConfig?.baudRate ?? 0) - self.timeout = Int(node.serialConfig?.timeout ?? 0) - self.mode = Int(node.serialConfig?.mode ?? 0) + self.enabled = node!.serialConfig?.enabled ?? false + self.echo = node!.serialConfig?.echo ?? false + self.rxd = Int(node!.serialConfig?.rxd ?? 0) + self.txd = Int(node!.serialConfig?.txd ?? 0) + self.baudRate = Int(node!.serialConfig?.baudRate ?? 0) + self.timeout = Int(node!.serialConfig?.timeout ?? 0) + self.mode = Int(node!.serialConfig?.mode ?? 0) self.hasChanges = false self.initialLoad = false @@ -343,31 +343,31 @@ struct SerialConfig: View { } .onChange(of: enabled) { newEnabled in - if newEnabled != node.serialConfig!.enabled { hasChanges = true } + if newEnabled != node!.serialConfig!.enabled { hasChanges = true } } .onChange(of: echo) { newEcho in - if newEcho != node.serialConfig!.echo { hasChanges = true } + if newEcho != node!.serialConfig!.echo { hasChanges = true } } .onChange(of: rxd) { newRxd in - if newRxd != node.serialConfig!.rxd { hasChanges = true } + if newRxd != node!.serialConfig!.rxd { hasChanges = true } } .onChange(of: txd) { newTxd in - if newTxd != node.serialConfig!.txd { hasChanges = true } + if newTxd != node!.serialConfig!.txd { hasChanges = true } } .onChange(of: baudRate) { newBaud in - if newBaud != node.serialConfig!.baudRate { hasChanges = true } + if newBaud != node!.serialConfig!.baudRate { hasChanges = true } } .onChange(of: timeout) { newTimeout in - if newTimeout != node.serialConfig!.timeout { hasChanges = true } + if newTimeout != node!.serialConfig!.timeout { hasChanges = true } } .onChange(of: mode) { newMode in - if newMode != node.serialConfig!.mode { hasChanges = true } + if newMode != node!.serialConfig!.mode { hasChanges = true } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index f8005744..188f6d22 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -179,7 +179,7 @@ struct TelemetryConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true @@ -289,7 +289,7 @@ struct TelemetryConfig: View { Label("Save", systemImage: "square.and.arrow.down") } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node.myInfo?.hasWifi ?? false)) + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) @@ -311,7 +311,7 @@ struct TelemetryConfig: View { tc.environmentRecoveryInterval = UInt32(environmentRecoveryInterval) tc.environmentReadErrorCountThreshold = UInt32(environmentReadErrorCountThreshold) - let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0 { @@ -338,14 +338,14 @@ struct TelemetryConfig: View { self.bleManager.context = context - self.deviceUpdateInterval = Int(node.telemetryConfig?.deviceUpdateInterval ?? 0) - self.environmentUpdateInterval = Int(node.telemetryConfig?.environmentUpdateInterval ?? 0) - self.environmentMeasurementEnabled = node.telemetryConfig?.environmentMeasurementEnabled ?? false - self.environmentSensorType = Int(node.telemetryConfig?.environmentSensorType ?? 0) - self.environmentScreenEnabled = node.telemetryConfig?.environmentScreenEnabled ?? false - self.environmentDisplayFahrenheit = node.telemetryConfig?.environmentDisplayFahrenheit ?? false - self.environmentRecoveryInterval = Int(node.telemetryConfig?.environmentRecoveryInterval ?? 0) - self.environmentReadErrorCountThreshold = Int(node.telemetryConfig?.environmentReadErrorCountThreshold ?? 0) + self.deviceUpdateInterval = Int(node!.telemetryConfig?.deviceUpdateInterval ?? 0) + self.environmentUpdateInterval = Int(node!.telemetryConfig?.environmentUpdateInterval ?? 0) + self.environmentMeasurementEnabled = node!.telemetryConfig?.environmentMeasurementEnabled ?? false + self.environmentSensorType = Int(node!.telemetryConfig?.environmentSensorType ?? 0) + self.environmentScreenEnabled = node!.telemetryConfig?.environmentScreenEnabled ?? false + self.environmentDisplayFahrenheit = node!.telemetryConfig?.environmentDisplayFahrenheit ?? false + self.environmentRecoveryInterval = Int(node!.telemetryConfig?.environmentRecoveryInterval ?? 0) + self.environmentReadErrorCountThreshold = Int(node!.telemetryConfig?.environmentReadErrorCountThreshold ?? 0) self.hasChanges = false self.initialLoad = false @@ -353,35 +353,35 @@ struct TelemetryConfig: View { } .onChange(of: deviceUpdateInterval) { newDeviceInterval in - if newDeviceInterval != node.telemetryConfig!.deviceUpdateInterval { hasChanges = true } + if newDeviceInterval != node!.telemetryConfig!.deviceUpdateInterval { hasChanges = true } } .onChange(of: environmentUpdateInterval) { newEnvInterval in - if newEnvInterval != node.telemetryConfig!.environmentUpdateInterval { hasChanges = true } + if newEnvInterval != node!.telemetryConfig!.environmentUpdateInterval { hasChanges = true } } .onChange(of: environmentMeasurementEnabled) { newEnvEnabled in - if newEnvEnabled != node.telemetryConfig!.environmentMeasurementEnabled { hasChanges = true } + if newEnvEnabled != node!.telemetryConfig!.environmentMeasurementEnabled { hasChanges = true } } .onChange(of: environmentSensorType) { newEnvSensorType in - if newEnvSensorType != node.telemetryConfig!.environmentSensorType { hasChanges = true } + if newEnvSensorType != node!.telemetryConfig!.environmentSensorType { hasChanges = true } } .onChange(of: environmentScreenEnabled) { newEnvScreenEnabled in - if newEnvScreenEnabled != node.telemetryConfig!.environmentScreenEnabled { hasChanges = true } + if newEnvScreenEnabled != node!.telemetryConfig!.environmentScreenEnabled { hasChanges = true } } .onChange(of: environmentDisplayFahrenheit) { newEnvDisplayF in - if newEnvDisplayF != node.telemetryConfig!.environmentDisplayFahrenheit { hasChanges = true } + if newEnvDisplayF != node!.telemetryConfig!.environmentDisplayFahrenheit { hasChanges = true } } .onChange(of: environmentRecoveryInterval) { newEnvRecoveryInterval in - if newEnvRecoveryInterval != node.telemetryConfig!.environmentRecoveryInterval { hasChanges = true } + if newEnvRecoveryInterval != node!.telemetryConfig!.environmentRecoveryInterval { hasChanges = true } } .onChange(of: environmentReadErrorCountThreshold) { newEnvReadErrorCountThreshold in - if newEnvReadErrorCountThreshold != node.telemetryConfig!.environmentReadErrorCountThreshold { hasChanges = true } + if newEnvReadErrorCountThreshold != node!.telemetryConfig!.environmentReadErrorCountThreshold { hasChanges = true } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 0ce2174d..3d1b96c7 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -111,7 +111,8 @@ struct PositionConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? + @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true @State var hasChanges = false @@ -282,7 +283,7 @@ struct PositionConfig: View { pc.gpsAttemptTime = UInt32(gpsAttemptTime) pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds) - let adminMessageId = bleManager.savePositionConfig(config: pc, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.savePositionConfig(config: pc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0{ @@ -308,36 +309,27 @@ struct PositionConfig: View { if self.initialLoad{ self.bleManager.context = context - self.smartPositionEnabled = node.positionConfig?.smartPositionEnabled ?? true - self.deviceGpsEnabled = node.positionConfig?.deviceGpsEnabled ?? true - self.fixedPosition = node.positionConfig?.fixedPosition ?? false - self.gpsUpdateInterval = Int(node.positionConfig?.gpsUpdateInterval ?? 0) - self.gpsAttemptTime = Int(node.positionConfig?.gpsAttemptTime ?? 0) - self.positionBroadcastSeconds = Int(node.positionConfig?.positionBroadcastSeconds ?? 0) + self.smartPositionEnabled = node!.positionConfig?.smartPositionEnabled ?? true + self.deviceGpsEnabled = node!.positionConfig?.deviceGpsEnabled ?? true + self.fixedPosition = node!.positionConfig?.fixedPosition ?? false + self.gpsUpdateInterval = Int(node!.positionConfig?.gpsUpdateInterval ?? 0) + self.gpsAttemptTime = Int(node!.positionConfig?.gpsAttemptTime ?? 0) + self.positionBroadcastSeconds = Int(node!.positionConfig?.positionBroadcastSeconds ?? 0) self.hasChanges = false self.initialLoad = false } } .onChange(of: smartPositionEnabled) { newSmartPosition in - if newSmartPosition != node.positionConfig!.smartPositionEnabled { - - hasChanges = true - } + if newSmartPosition != node!.positionConfig!.smartPositionEnabled { hasChanges = true } } .onChange(of: deviceGpsEnabled) { newDeviceGps in - if newDeviceGps != node.positionConfig!.deviceGpsEnabled { - - hasChanges = true - } + if newDeviceGps != node!.positionConfig!.deviceGpsEnabled { hasChanges = true } } .onChange(of: fixedPosition) { newFixed in - if newFixed != node.positionConfig!.fixedPosition { - - hasChanges = true - } + if newFixed != node!.positionConfig!.fixedPosition { hasChanges = true } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/Meshtastic/Views/Settings/MeshLog.swift b/Meshtastic/Views/Settings/MeshLog.swift index f1b9a5f7..1728f5ee 100644 --- a/Meshtastic/Views/Settings/MeshLog.swift +++ b/Meshtastic/Views/Settings/MeshLog.swift @@ -74,7 +74,7 @@ struct MeshLog: View { } label: { - Label("Download Log", systemImage: "arrow.down.circle.fill") + Label("Save Log", systemImage: "square.and.arrow.down") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 76614ed6..8ed698c9 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -19,18 +19,17 @@ struct Settings: View { private var nodes: FetchedResults - @State private var selection: String? = "" - var body: some View { NavigationView { + List { let connectedNodeNum = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0 - NavigationLink(tag: String("0"), selection: $selection) { + NavigationLink() { AppSettings() } label: { - + Image(systemName: "gearshape") .symbolRenderingMode(.hierarchical) Text("App Settings") @@ -39,7 +38,7 @@ struct Settings: View { Section("Radio Configuration") { NavigationLink { - ShareChannel(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + ShareChannel(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "qrcode") .symbolRenderingMode(.hierarchical) @@ -48,7 +47,7 @@ struct Settings: View { .disabled(bleManager.connectedPeripheral == nil) NavigationLink { - UserConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + UserConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "person.crop.rectangle.fill") @@ -60,7 +59,7 @@ struct Settings: View { NavigationLink() { - LoRaConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + LoRaConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "dot.radiowaves.left.and.right") @@ -71,7 +70,7 @@ struct Settings: View { .disabled(bleManager.connectedPeripheral == nil) NavigationLink { - DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "flipphone") @@ -81,7 +80,7 @@ struct Settings: View { .disabled(bleManager.connectedPeripheral == nil) NavigationLink { - DisplayConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + DisplayConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "display") @@ -91,7 +90,7 @@ struct Settings: View { .disabled(bleManager.connectedPeripheral == nil) NavigationLink { - PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "location") @@ -108,7 +107,7 @@ struct Settings: View { Section("Module Configuration") { NavigationLink { - CannedMessagesConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + CannedMessagesConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "list.bullet.rectangle.fill") @@ -119,7 +118,7 @@ struct Settings: View { .disabled(bleManager.connectedPeripheral == nil) NavigationLink { - ExternalNotificationConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + ExternalNotificationConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "megaphone") @@ -130,7 +129,7 @@ struct Settings: View { .disabled(bleManager.connectedPeripheral == nil) NavigationLink { - RangeTestConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + RangeTestConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "point.3.connected.trianglepath.dotted") @@ -139,11 +138,9 @@ struct Settings: View { Text("Range Test") } .disabled(bleManager.connectedPeripheral == nil) - //nodes.first(where: { $0.num == connectedNodeNum })?.myInfo?.hasWifi ?? true)//|| - // nodes.first(where: { $0.num == connectedNodeNum })!.rangeTestConfig != nil) NavigationLink { - SerialConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + SerialConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "terminal") @@ -155,7 +152,7 @@ struct Settings: View { NavigationLink { - TelemetryConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + TelemetryConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "chart.xyaxis.line") @@ -183,7 +180,7 @@ struct Settings: View { let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) - AdminMessageList(user: connectedNode?.user ?? UserEntity()) + AdminMessageList(user: connectedNode?.user) } label: { Image(systemName: "building.columns") @@ -203,13 +200,7 @@ struct Settings: View { self.bleManager.context = context self.bleManager.userSettings = userSettings - - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - - if nodes.count > 0 { - selection = "0" - } - } + } .listStyle(GroupedListStyle()) .navigationTitle("Settings") diff --git a/Meshtastic/Views/Settings/ShareChannel.swift b/Meshtastic/Views/Settings/ShareChannel.swift index e53f979a..c5d7e436 100644 --- a/Meshtastic/Views/Settings/ShareChannel.swift +++ b/Meshtastic/Views/Settings/ShareChannel.swift @@ -37,7 +37,7 @@ struct ShareChannel: View { @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings - var node: NodeInfoEntity + var node: NodeInfoEntity? @State private var text = "https://meshtastic.org/e/#test" var qrCodeImage = QrCodeImage() diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index 73134b70..21742f45 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -11,7 +11,7 @@ struct UserConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - var node: NodeInfoEntity + var node: NodeInfoEntity? @State private var isPresentingFactoryResetConfirm: Bool = false @State private var isPresentingSaveConfirm: Bool = false @@ -112,7 +112,7 @@ struct UserConfig: View { u.shortName = shortName u.longName = longName - let adminMessageId = bleManager.saveUser(config: u, fromUser: node.user!, toUser: node.user!, wantResponse: true) + let adminMessageId = bleManager.saveUser(config: u, fromUser: node?.user, toUser: node?.user, wantResponse: true) if adminMessageId > 0 { @@ -137,22 +137,22 @@ struct UserConfig: View { self.bleManager.context = context - self.shortName = node.user!.shortName ?? "" - self.longName = node.user!.longName ?? "" + self.shortName = node?.user!.shortName ?? "" + self.longName = node?.user!.longName ?? "" self.hasChanges = false self.initialLoad = false } } .onChange(of: shortName) { newShort in - if newShort != node.user!.shortName { + if newShort != node?.user!.shortName { hasChanges = true } } .onChange(of: longName) { newLong in - if newLong != node.user!.longName { + if newLong != node?.user!.longName { hasChanges = true }