diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e43b0f98 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..b6d6ee29 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,48 @@ +line_length: 400 + +type_name: + min_length: 1 + max_length: + warning: 60 + error: 70 + excluded: iPhone # excluded via string + allowed_symbols: ["_"] # these are allowed in type names +identifier_name: + min_length: 1 + max_length: + warning: 60 + allowed_symbols: ["_"] # these are allowed in type names + +# TODO: should review +force_try: + severity: warning # explicitly + +# TODO: should review +file_length: + warning: 2500 + error: 3000 + +# TODO: should review +cyclomatic_complexity: + warning: 60 + error: 70 + ignores_case_statements: true + +# TODO: should review +function_body_length: + warning: 200 + +# TODO: should review +type_body_length: + warning: 400 + +# TODO: should review +disabled_rules: # rule identifiers to exclude from running + - operator_whitespace + - multiple_closures_with_trailing_closure + - todo + +# TODO: should review +nesting: + type_level: + warning: 3 diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 6e75fc9a..8104211f 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -352,6 +352,7 @@ DDC2E15026CE248E0042C5E4 /* Sources */, DDC2E15126CE248E0042C5E4 /* Frameworks */, DDC2E15226CE248E0042C5E4 /* Resources */, + BB450974275599CE00509624 /* ShellScript */, ); buildRules = ( ); @@ -472,6 +473,26 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + BB450974275599CE00509624 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ DDC2E15026CE248E0042C5E4 /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-1.png b/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-1.png new file mode 100644 index 00000000..b80e4b0c Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-1.png differ diff --git a/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-2.png b/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-2.png new file mode 100644 index 00000000..b80e4b0c Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6-2.png differ diff --git a/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6.png b/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6.png new file mode 100644 index 00000000..b80e4b0c Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/655DCEC0-309D-430A-AF50-2453B6ADB1F6.png differ diff --git a/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/Contents.json b/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/Contents.json new file mode 100644 index 00000000..f321d22c --- /dev/null +++ b/MeshtasticClient/Assets.xcassets/HELTECV21.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "655DCEC0-309D-430A-AF50-2453B6ADB1F6.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "655DCEC0-309D-430A-AF50-2453B6ADB1F6-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "655DCEC0-309D-430A-AF50-2453B6ADB1F6-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index ed1f7401..f2f03e80 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -3,11 +3,11 @@ import CoreData import CoreBluetooth import SwiftUI -//--------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------- // Meshtastic BLE Device Manager -//--------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------- class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate { - + private static var documentsFolder: URL { do { return try FileManager.default.url( @@ -19,48 +19,48 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph fatalError("Can't find documents directory.") } } - + // Core Data @Environment(\.managedObjectContext) private var viewContext - - @Published var meshData : MeshData - @Published var messageData : MessageData - + + @Published var meshData: MeshData + @Published var messageData: MessageData + private var centralManager: CBCentralManager! - + @Published var connectedPeripheral: Peripheral! @Published var connectedNode: NodeInfoModel! @Published var lastConnectedPeripheral: String @Published var lastConnectionError: String - + @Published var isSwitchedOn: Bool = false @Published var isScanning: Bool = false @Published var isConnected: Bool = false - + @Published var peripherals = [Peripheral]() var timeoutTimer: Timer? var timeoutTimerCount = 0 - + private var meshLoggingEnabled: Bool = false - + private var broadcastNodeId: UInt32 = 4294967295 /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! var FROMRADIO_characteristic: CBCharacteristic! var FROMNUM_characteristic: CBCharacteristic! - + let meshtasticServiceCBUUID = CBUUID(string: "0x6BA1B218-15A8-461F-9FA8-5DCAE273EAFD") let TORADIO_UUID = CBUUID(string: "0xF75C76D2-129E-4DAD-A1DD-7866124401E7") let FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5") let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453") - + let meshLog = documentsFolder.appendingPathComponent("meshlog.txt") - + /* init BLEManager */ override init() { - + self.meshLoggingEnabled = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? false self.meshData = MeshData() self.messageData = MessageData() @@ -75,111 +75,108 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // called when bluetooth is enabled/disabled for the app func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == .poweredOn { - + isSwitchedOn = true startScanning() - } - else { - + } else { + isSwitchedOn = false } } - + // Scan for nearby BLE devices using the Meshtastic BLE service ID func startScanning() { - + if isSwitchedOn { - + centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID], options: nil) self.isScanning = self.centralManager.isScanning print("Scanning Started") } } - + // Stop Scanning For BLE Devices func stopScanning() { - + if centralManager.isScanning { - + self.centralManager.stopScan() self.isScanning = self.centralManager.isScanning print("Stopped Scanning") } } - + /// The action after the timeout-timer has fired /// /// - Parameters: /// - timer: The time that fired the event /// - @objc func timeoutTimerFired(timer: Timer) - { + @objc func timeoutTimerFired(timer: Timer) { guard let context = timer.userInfo as? [String: String] else { return } - let name : String = context["name", default: "Unknown"] - + let name: String = context["name", default: "Unknown"] + self.timeoutTimerCount += 1 if timeoutTimerCount == 6 { - + if connectedPeripheral != nil { - + self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) } connectedNode = nil connectedPeripheral = nil - + self.lastConnectionError = "BLE Connecting Timeout after making \(timeoutTimerCount) attempts to connect to \(name)." print("BLE Connecting Timeout after making \(timeoutTimerCount) attempts to connect to \(name).") if meshLoggingEnabled { MeshLogger.log("BLE Connecting Timeout after making \(timeoutTimerCount) attempts to connect to \(String(name)).") } - + self.timeoutTimerCount = 0 self.timeoutTimer?.invalidate() - - } - else { + + } else { print("BLE Connecting 2 Second Timeout Timer Fired \(timeoutTimerCount) Time(s): \(name)") if meshLoggingEnabled { MeshLogger.log("BLE Connecting 2 Second Timeout Timer Fired \(timeoutTimerCount) Time(s): \(name)") } } } - + // Connect to a specific peripheral func connectTo(peripheral: CBPeripheral) { - + if meshLoggingEnabled { MeshLogger.log("BLE Connecting: \(peripheral.name ?? "Unknown")") } print("BLE Connecting: \(peripheral.name ?? "Unknown")") - + stopScanning() - + if self.connectedPeripheral != nil { self.disconnectPeripheral() } - + self.centralManager?.connect(peripheral) - + // Use a timer to keep track of connecting peripherals, context to pass the radio name with the timer and the RunLoop to prevent // the timer from running on the main UI thread let context = ["name": "@\(peripheral.name ?? "Unknown")"] self.timeoutTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(timeoutTimerFired), userInfo: context, repeats: true) RunLoop.current.add(self.timeoutTimer!, forMode: .common) } - + // Disconnect Peripheral function - func disconnectPeripheral(){ - + func disconnectPeripheral() { + guard let connectedPeripheral = connectedPeripheral else { return } self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) - + } - + // Called each time a peripheral is discovered - func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { - + func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { + var peripheralName: String = peripheral.name ?? "Unknown" - + if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String { peripheralName = name } - + var newPeripheral = Peripheral(id: peripheral.identifier.uuidString, name: peripheralName, rssi: RSSI.intValue, subscribed: false, peripheral: peripheral, myInfo: nil) let peripheralIndex = peripherals.firstIndex(where: { $0.id == newPeripheral.id }) @@ -189,29 +186,28 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph peripherals[peripheralIndex!] = newPeripheral peripherals.remove(at: peripheralIndex!) peripherals.append(newPeripheral) - print("Updating peripheral: \(peripheralName)"); - } - else { - + print("Updating peripheral: \(peripheralName)") + } else { + if newPeripheral.peripheral.state != CBPeripheralState.connected { - + peripherals.append(newPeripheral) - print("Adding peripheral: \(peripheralName)"); + print("Adding peripheral: \(peripheralName)") } } } - + // called when a peripheral is connected func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { - //guard let connectedPeripheral = connectedPeripheral else { return } + // guard let connectedPeripheral = connectedPeripheral else { return } self.isConnected = true // Invalidate and reset connection timer count, remove any connection errors self.lastConnectionError = "" self.timeoutTimer!.invalidate() self.timeoutTimerCount = 0 - + // Map the peripheral to the connectedNode and connectedPeripheral ObservedObjects connectedPeripheral = peripherals.filter({ $0.peripheral.identifier == peripheral.identifier }).first connectedPeripheral.peripheral.delegate = self @@ -219,36 +215,35 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph let peripheralLast4: String = String(deviceName.suffix(4)) connectedNode = self.meshData.nodes.first(where: { $0.user.id.contains(peripheralLast4) }) lastConnectedPeripheral = peripheral.identifier.uuidString - + // Discover Services peripheral.discoverServices([meshtasticServiceCBUUID]) if meshLoggingEnabled { MeshLogger.log("BLE Connected: \(peripheral.name ?? "Unknown")") } print("BLE Connected: \(peripheral.name ?? "Unknown")") } - + func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { - + if meshLoggingEnabled { MeshLogger.log("BLE Failed to Connect: \(peripheral.name ?? "Unknown")") } print("BLE Failed to Connect: \(peripheral.name ?? "Unknown")") disconnectPeripheral() } // Disconnect Peripheral Event - func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) - { + func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { // Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake self.startScanning() self.connectedPeripheral = nil - + if let e = error { - + // https://developer.apple.com/documentation/corebluetooth/cberror/code let errorCode = (e as NSError).code // unknown = 0, - + if errorCode == 6 { // CBError.Code.connectionTimeout The connection has timed out unexpectedly. - + // Happens when device is manually reset / powered off // We will try and re-connect to this device lastConnectionError = "\(e.localizedDescription) The app will automatically reconnect to the preferred radio if it reappears within 10 seconds." @@ -257,31 +252,28 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("BLE Reconnecting: \(peripheral.name ?? "Unknown")") self.connectTo(peripheral: peripheral) } - } - else if errorCode == 7 { //CBError.Code.peripheralDisconnected The specified device has disconnected from us. - + } else if errorCode == 7 { // CBError.Code.peripheralDisconnected The specified device has disconnected from us. + // Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work. lastConnectionError = e.localizedDescription self.connectedNode = nil print("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") if meshLoggingEnabled { MeshLogger.log("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") } - } - else if errorCode == 14 { // Peer removed pairing information - + } else if errorCode == 14 { // Peer removed pairing information + // Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that lastConnectionError = "\(e.localizedDescription) This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio." self.connectedNode = nil if meshLoggingEnabled { MeshLogger.log("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(lastConnectionError)") } - } - else { - + } else { + lastConnectionError = e.localizedDescription self.connectedNode = nil print("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") if meshLoggingEnabled { MeshLogger.log("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") } } } else { - + // Disconnected without error which indicates user intent to disconnect // Happens when swiping to disconnect self.connectedNode = nil @@ -289,22 +281,20 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect") } } - + // Discover Services Event func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { - + if let e = error { - + print("Discover Services error \(e)") } - - guard let services = peripheral.services else { return } - - for service in services - { - if (service.uuid == meshtasticServiceCBUUID) - { + guard let services = peripheral.services else { return } + + for service in services { + + if service.uuid == meshtasticServiceCBUUID { print("Meshtastic service discovered OK") if meshLoggingEnabled { MeshLogger.log("BLE Service for Meshtastic discovered by \(peripheral.name ?? "Unknown")") } peripheral.discoverCharacteristics(nil, for: service) @@ -312,368 +302,346 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } } - + // Discover Characteristics Event - func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) - { + func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { if let e = error { - + print("Discover Characteristics error \(e)") if meshLoggingEnabled { MeshLogger.log("BLE didDiscoverCharacteristicsFor error by \(peripheral.name ?? "Unknown") \(e)") } } - + guard let characteristics = service.characteristics else { return } for characteristic in characteristics { - - switch characteristic.uuid - { - case TORADIO_UUID: - print("TORADIO characteristic OK") + + switch characteristic.uuid { + case TORADIO_UUID: + print("TORADIO characteristic OK") if meshLoggingEnabled { MeshLogger.log("BLE did discover TORADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") } - TORADIO_characteristic = characteristic - var toRadio: ToRadio = ToRadio() - toRadio.wantConfigID = 32168 - let binaryData: Data = try! toRadio.serializedData() - peripheral.writeValue(binaryData, for: characteristic, type: .withResponse) - break - - case FROMRADIO_UUID: - print("FROMRADIO characteristic OK") + TORADIO_characteristic = characteristic + var toRadio: ToRadio = ToRadio() + toRadio.wantConfigID = 32168 + let binaryData: Data = try! toRadio.serializedData() + peripheral.writeValue(binaryData, for: characteristic, type: .withResponse) + + case FROMRADIO_UUID: + print("FROMRADIO characteristic OK") if meshLoggingEnabled { MeshLogger.log("BLE did discover FROMRADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") } - FROMRADIO_characteristic = characteristic - peripheral.readValue(for: FROMRADIO_characteristic) - break - - case FROMNUM_UUID: - print("FROMNUM (Notify) characteristic OK") + FROMRADIO_characteristic = characteristic + peripheral.readValue(for: FROMRADIO_characteristic) + + case FROMNUM_UUID: + print("FROMNUM (Notify) characteristic OK") if meshLoggingEnabled { MeshLogger.log("BLE did discover FROMNUM (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") } - FROMNUM_characteristic = characteristic - peripheral.setNotifyValue(true, for: characteristic) - break - - default: - break - } - + FROMNUM_characteristic = characteristic + peripheral.setNotifyValue(true, for: characteristic) + + default: + break + } + } } - + func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { - + print("didUpdateNotificationStateFor char: \(characteristic.uuid.uuidString) \(characteristic.isNotifying)") if meshLoggingEnabled { MeshLogger.log("didUpdateNotificationStateFor char: \(characteristic.uuid.uuidString) \(characteristic.isNotifying)") } - if let errorText = error?.localizedDescription - { + if let errorText = error?.localizedDescription { print("didUpdateNotificationStateFor error: \(errorText)") } - //commandLock.lock() + // commandLock.lock() - //if let index = commandConditions.firstIndex(where: { (condition) -> Bool in + // if let index = commandConditions.firstIndex(where: { (condition) -> Bool in // if case .notificationStateUpdate(characteristic: characteristic, enabled: characteristic.isNotifying) = condition { // return true // } else { // return false // } - //}) { + // }) { // commandConditions.remove(at: index) // commandError = error // if commandConditions.isEmpty { // commandLock.broadcast() // } - //} + // } - //commandLock.unlock() - //?.peripheralManager(self, didUpdateNotificationStateFor: characteristic) + // commandLock.unlock() + // ?.peripheralManager(self, didUpdateNotificationStateFor: characteristic) } - + // Data Read / Update Characteristic Event - func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) - { + func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let e = error { - + print("didUpdateValueFor Characteristic error \(e)") if meshLoggingEnabled { MeshLogger.log("BLE didUpdateValueFor characteristic error by \(peripheral.name ?? "Unknown") \(e)") } } - - switch characteristic.uuid - { - case FROMNUM_UUID: - peripheral.readValue(for: FROMNUM_characteristic) - //let byteArrayFromData: [UInt8] = [UInt8](characteristic.value!) - //let stringFromByteArray = String(data: Data(_: byteArrayFromData), encoding: .utf8) - //print("string array data \(stringFromByteArray!)") - //print(characteristic.value?. ?? "no value") - - - case FROMRADIO_UUID: - if (characteristic.value == nil || characteristic.value!.isEmpty) - { - return - } - //print(characteristic.value ?? "no value") - //print(characteristic.value?.hexDescription ?? "no value") - var decodedInfo = FromRadio() - - decodedInfo = try! FromRadio(serializedData: characteristic.value!) - print("Print DecodedInfo") - print(decodedInfo) - - if decodedInfo.myInfo.myNodeNum != 0 - { - - // Create a MyInfoModel - let myInfoModel = MyInfoModel( - myNodeNum: decodedInfo.myInfo.myNodeNum, - hasGps: decodedInfo.myInfo.hasGps_p, - numBands: decodedInfo.myInfo.numBands, - maxChannels: decodedInfo.myInfo.maxChannels, - firmwareVersion: decodedInfo.myInfo.firmwareVersion, - messageTimeoutMsec: decodedInfo.myInfo.messageTimeoutMsec, - minAppVersion: decodedInfo.myInfo.minAppVersion) - - // Save it to the connected nodeInfo - if connectedPeripheral != nil { - connectedPeripheral.myInfo = myInfoModel - // Save it to the connected node - connectedNode = meshData.nodes.first(where: {$0.num == myInfoModel.myNodeNum}) - - } - // Since the data is from the device itself we save all myInfo objects since they are always the most up to date - if connectedNode != nil { - - connectedNode.myInfo = myInfoModel - let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.myInfo.myNodeNum }) - //meshData.nodes.remove(at: nodeIndex!) - //meshData.nodes.append(connectedNode) - if nodeIndex != nil { - meshData.nodes[nodeIndex!] = connectedNode - meshData.save() - } - print("Saved a myInfo for \(decodedInfo.myInfo.myNodeNum)") - if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and myInfo saved for \(peripheral.name ?? "Unknown")") } - } - } - - if decodedInfo.nodeInfo.num != 0 { - - print("Save a nodeInfo") - print(decodedInfo.nodeInfo) - if meshData.nodes.contains(where: {$0.id == decodedInfo.nodeInfo.num}) { - - // Found a matching node lets update it - let nodeMatch = meshData.nodes.first(where: { $0.id == decodedInfo.nodeInfo.num }) - if connectedPeripheral != nil && connectedPeripheral.myInfo?.myNodeNum == nodeMatch?.num { - connectedNode = nodeMatch - } - - if nodeMatch?.lastHeard ?? 0 < decodedInfo.nodeInfo.lastHeard && nodeMatch?.user != nil && nodeMatch?.user.longName.count ?? 0 > 0 { - // The data coming from the device is newer - - let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.nodeInfo.num }) - meshData.nodes.remove(at: nodeIndex!) - meshData.save() - } - else { - - // Data is older than what the app already has - return - } - } - // Set the connected node if the nodeInfo is for the connected node. - if connectedPeripheral != nil && connectedPeripheral.myInfo?.myNodeNum == decodedInfo.nodeInfo.num { - - let nodeMatch = meshData.nodes.first(where: { $0.id == decodedInfo.nodeInfo.num }) - if nodeMatch != nil { - connectedNode = nodeMatch - } - } - if decodedInfo.nodeInfo.hasUser { - - meshData.nodes.append( - NodeInfoModel( - num: decodedInfo.nodeInfo.num, - user: NodeInfoModel.User( - id: decodedInfo.nodeInfo.user.id, - longName: decodedInfo.nodeInfo.user.longName, - shortName: decodedInfo.nodeInfo.user.shortName, - //macaddr: decodedInfo.nodeInfo.user.macaddr, - hwModel: String(describing: decodedInfo.nodeInfo.user.hwModel) - .uppercased()), - - position: NodeInfoModel.Position( - latitudeI: decodedInfo.nodeInfo.position.latitudeI, - longitudeI: decodedInfo.nodeInfo.position.longitudeI, - altitude: decodedInfo.nodeInfo.position.altitude, - batteryLevel: decodedInfo.nodeInfo.position.batteryLevel, - time: decodedInfo.nodeInfo.position.time), - - lastHeard: decodedInfo.nodeInfo.lastHeard, - snr: decodedInfo.nodeInfo.snr) - ) - meshData.save() - if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") } - - - - if connectedNode == nil { - - //connectedNode = meshData.nodes.first(where: {$0.num == connectedPeripheral.myInfo!.myNodeNum}) - } - } - } - // Handle assorted app packets - if decodedInfo.packet.id != 0 { - - print("Handle a Packet") - do { - // Text Message App - Primary Broadcast Channel - if decodedInfo.packet.decoded.portnum == PortNum.textMessageApp { - - if let messageText = String(bytes: decodedInfo.packet.decoded.payload, encoding: .utf8) { - - print("Message Text: \(messageText)") + switch characteristic.uuid { + case FROMNUM_UUID: + peripheral.readValue(for: FROMNUM_characteristic) + // let byteArrayFromData: [UInt8] = [UInt8](characteristic.value!) + // let stringFromByteArray = String(data: Data(_: byteArrayFromData), encoding: .utf8) + // print("string array data \(stringFromByteArray!)") + // print(characteristic.value?. ?? "no value") + + case FROMRADIO_UUID: + if characteristic.value == nil || characteristic.value!.isEmpty { + return + } + // print(characteristic.value ?? "no value") + // print(characteristic.value?.hexDescription ?? "no value") + var decodedInfo = FromRadio() + + decodedInfo = try! FromRadio(serializedData: characteristic.value!) + print("Print DecodedInfo") + print(decodedInfo) + + if decodedInfo.myInfo.myNodeNum != 0 { + + // Create a MyInfoModel + let myInfoModel = MyInfoModel( + myNodeNum: decodedInfo.myInfo.myNodeNum, + hasGps: decodedInfo.myInfo.hasGps_p, + numBands: decodedInfo.myInfo.numBands, + maxChannels: decodedInfo.myInfo.maxChannels, + firmwareVersion: decodedInfo.myInfo.firmwareVersion, + messageTimeoutMsec: decodedInfo.myInfo.messageTimeoutMsec, + minAppVersion: decodedInfo.myInfo.minAppVersion) + + // Save it to the connected nodeInfo + if connectedPeripheral != nil { + connectedPeripheral.myInfo = myInfoModel + // Save it to the connected node + connectedNode = meshData.nodes.first(where: {$0.num == myInfoModel.myNodeNum}) + + } + // Since the data is from the device itself we save all myInfo objects since they are always the most up to date + if connectedNode != nil { + + connectedNode.myInfo = myInfoModel + let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.myInfo.myNodeNum }) + // meshData.nodes.remove(at: nodeIndex!) + // meshData.nodes.append(connectedNode) + if nodeIndex != nil { + meshData.nodes[nodeIndex!] = connectedNode + meshData.save() + } + print("Saved a myInfo for \(decodedInfo.myInfo.myNodeNum)") + if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and myInfo saved for \(peripheral.name ?? "Unknown")") } + } + } + + if decodedInfo.nodeInfo.num != 0 { + + print("Save a nodeInfo") + print(decodedInfo.nodeInfo) + if meshData.nodes.contains(where: {$0.id == decodedInfo.nodeInfo.num}) { + + // Found a matching node lets update it + let nodeMatch = meshData.nodes.first(where: { $0.id == decodedInfo.nodeInfo.num }) + if connectedPeripheral != nil && connectedPeripheral.myInfo?.myNodeNum == nodeMatch?.num { + connectedNode = nodeMatch + } + + if nodeMatch?.lastHeard ?? 0 < decodedInfo.nodeInfo.lastHeard && nodeMatch?.user != nil && nodeMatch?.user.longName.count ?? 0 > 0 { + // The data coming from the device is newer + + let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.nodeInfo.num }) + meshData.nodes.remove(at: nodeIndex!) + meshData.save() + + } else { + + // Data is older than what the app already has + return + } + } + // Set the connected node if the nodeInfo is for the connected node. + if connectedPeripheral != nil && connectedPeripheral.myInfo?.myNodeNum == decodedInfo.nodeInfo.num { + + let nodeMatch = meshData.nodes.first(where: { $0.id == decodedInfo.nodeInfo.num }) + if nodeMatch != nil { + connectedNode = nodeMatch + } + } + if decodedInfo.nodeInfo.hasUser { + + meshData.nodes.append( + NodeInfoModel( + num: decodedInfo.nodeInfo.num, + user: NodeInfoModel.User( + id: decodedInfo.nodeInfo.user.id, + longName: decodedInfo.nodeInfo.user.longName, + shortName: decodedInfo.nodeInfo.user.shortName, + // macaddr: decodedInfo.nodeInfo.user.macaddr, + hwModel: String(describing: decodedInfo.nodeInfo.user.hwModel) + .uppercased()), + + position: NodeInfoModel.Position( + latitudeI: decodedInfo.nodeInfo.position.latitudeI, + longitudeI: decodedInfo.nodeInfo.position.longitudeI, + altitude: decodedInfo.nodeInfo.position.altitude, + batteryLevel: decodedInfo.nodeInfo.position.batteryLevel, + time: decodedInfo.nodeInfo.position.time), + + lastHeard: decodedInfo.nodeInfo.lastHeard, + snr: decodedInfo.nodeInfo.snr) + ) + meshData.save() + if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") } + + if connectedNode == nil { + + // connectedNode = meshData.nodes.first(where: {$0.num == connectedPeripheral.myInfo!.myNodeNum}) + } + } + } + // Handle assorted app packets + if decodedInfo.packet.id != 0 { + + print("Handle a Packet") + do { + // Text Message App - Primary Broadcast Channel + if decodedInfo.packet.decoded.portnum == PortNum.textMessageApp { + + if let messageText = String(bytes: decodedInfo.packet.decoded.payload, encoding: .utf8) { + + print("Message Text: \(messageText)") if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received for text message app \(messageText)") } - let fromUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from }) + let fromUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from }) - var toUserLongName: String = "Broadcast" - var toUserShortName: String = "BC" + var toUserLongName: String = "Broadcast" + var toUserShortName: String = "BC" - if decodedInfo.packet.to != broadcastNodeId { + if decodedInfo.packet.to != broadcastNodeId { - let toUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from }) - toUserLongName = toUser?.user.longName ?? "Unknown" - toUserShortName = toUser?.user.shortName ?? "???" - } - - // Add the received message to the local messages list / file and save - messageData.messages.append( - MessageModel( - messageId: decodedInfo.packet.id, - messageTimeStamp: decodedInfo.packet.rxTime, - fromUserId: decodedInfo.packet.from, - toUserId: decodedInfo.packet.to, - fromUserLongName: fromUser?.user.longName ?? "Unknown", - toUserLongName: toUserLongName, - fromUserShortName: fromUser?.user.shortName ?? "???", - toUserShortName: toUserShortName, - receivedACK: decodedInfo.packet.decoded.wantResponse, - messagePayload: messageText, - direction: "IN") - ) - messageData.save() - - // Create an iOS Notification for the received message and schedule it immediately - let manager = LocalNotificationManager() - - manager.notifications = [ - Notification( - id: ("notification.id.\(decodedInfo.packet.id)"), - title: "\(fromUser?.user.longName ?? "Unknown")", - subtitle: "AKA \(fromUser?.user.shortName ?? "???")", - content: messageText) - ] - manager.schedule() + let toUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from }) + toUserLongName = toUser?.user.longName ?? "Unknown" + toUserShortName = toUser?.user.shortName ?? "???" + } + + // Add the received message to the local messages list / file and save + messageData.messages.append( + MessageModel( + messageId: decodedInfo.packet.id, + messageTimeStamp: decodedInfo.packet.rxTime, + fromUserId: decodedInfo.packet.from, + toUserId: decodedInfo.packet.to, + fromUserLongName: fromUser?.user.longName ?? "Unknown", + toUserLongName: toUserLongName, + fromUserShortName: fromUser?.user.shortName ?? "???", + toUserShortName: toUserShortName, + receivedACK: decodedInfo.packet.decoded.wantResponse, + messagePayload: messageText, + direction: "IN") + ) + messageData.save() + + // Create an iOS Notification for the received message and schedule it immediately + let manager = LocalNotificationManager() + + manager.notifications = [ + Notification( + id: ("notification.id.\(decodedInfo.packet.id)"), + title: "\(fromUser?.user.longName ?? "Unknown")", + subtitle: "AKA \(fromUser?.user.shortName ?? "???")", + content: messageText) + ] + manager.schedule() if meshLoggingEnabled { MeshLogger.log("iOS Notification Scheduled for text message from \(fromUser?.user.longName ?? "Unknown") \(messageText)") } - } - } - else if decodedInfo.packet.decoded.portnum == PortNum.nodeinfoApp { + } + } else if decodedInfo.packet.decoded.portnum == PortNum.nodeinfoApp { - var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from }) + var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from }) if updatedNode != nil { if meshLoggingEnabled { MeshLogger.log("BLE FROMRADIO received for node info app for \(updatedNode!.user.longName)") } } - if updatedNode != nil { - updatedNode!.snr = decodedInfo.packet.rxSnr - updatedNode!.lastHeard = decodedInfo.packet.rxTime - //updatedNode!.update(from: updatedNode!.data) - let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.packet.from }) - meshData.nodes.remove(at: nodeIndex!) - meshData.nodes.append(updatedNode!) - meshData.save() + if updatedNode != nil { + updatedNode!.snr = decodedInfo.packet.rxSnr + updatedNode!.lastHeard = decodedInfo.packet.rxTime + // updatedNode!.update(from: updatedNode!.data) + let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.packet.from }) + meshData.nodes.remove(at: nodeIndex!) + meshData.nodes.append(updatedNode!) + meshData.save() if meshLoggingEnabled { MeshLogger.log("MESH PACKET Updated NodeInfo SNR and Time from Node Info App Packet For: \(updatedNode!.user.longName)") } - print("Updated NodeInfo SNR and Time from Packet For: \(updatedNode!.user.longName)") - } - } - else if decodedInfo.packet.decoded.portnum == PortNum.positionApp { + print("Updated NodeInfo SNR and Time from Packet For: \(updatedNode!.user.longName)") + } + } else if decodedInfo.packet.decoded.portnum == PortNum.positionApp { - var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from }) + var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from }) - if updatedNode != nil { - updatedNode!.snr = decodedInfo.packet.rxSnr - updatedNode!.lastHeard = decodedInfo.packet.rxTime - //updatedNode!.update(from: updatedNode!.data) - let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.packet.from }) - meshData.nodes.remove(at: nodeIndex!) - meshData.nodes.append(updatedNode!) - meshData.save() + if updatedNode != nil { + updatedNode!.snr = decodedInfo.packet.rxSnr + updatedNode!.lastHeard = decodedInfo.packet.rxTime + // updatedNode!.update(from: updatedNode!.data) + let nodeIndex = meshData.nodes.firstIndex(where: { $0.id == decodedInfo.packet.from }) + meshData.nodes.remove(at: nodeIndex!) + meshData.nodes.append(updatedNode!) + meshData.save() if meshLoggingEnabled { MeshLogger.log("MESH PACKET Updated NodeInfo SNR and Time from Position App Packet For: \(updatedNode!.user.longName)") } - print("Updated Position SNR and Time from Packet For: \(updatedNode!.user.longName)") - } + print("Updated Position SNR and Time from Packet For: \(updatedNode!.user.longName)") + } print("Postion Payload") print(try decodedInfo.packet.jsonString()) - } - else if decodedInfo.packet.decoded.portnum == PortNum.adminApp { - + } else if decodedInfo.packet.decoded.portnum == PortNum.adminApp { + if meshLoggingEnabled { MeshLogger.log("MESH PACKET received for Admin App UNHANDLED \(try decodedInfo.packet.jsonString())") } print("Admin App Packet") print(try decodedInfo.packet.jsonString()) - } - else if decodedInfo.packet.decoded.portnum == PortNum.routingApp { - + } else if decodedInfo.packet.decoded.portnum == PortNum.routingApp { + if meshLoggingEnabled { MeshLogger.log("MESH PACKET received for Routing App UNHANDLED \(try decodedInfo.packet.jsonString())") } print("Routing App Packet") print(try decodedInfo.packet.jsonString()) - } - else - { + } else { if meshLoggingEnabled { MeshLogger.log("MESH PACKET received for Other App UNHANDLED \(try decodedInfo.packet.jsonString())") } print("Other App Packet") print(try decodedInfo.packet.jsonString()) } - - } catch { - if meshLoggingEnabled { MeshLogger.log("Fatal Error: Failed to decode json") } - fatalError("Failed to decode json") - } - } - - if decodedInfo.configCompleteID != 0 { - if meshLoggingEnabled { MeshLogger.log("BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)") } - print("BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)") - self.connectedPeripheral.subscribed = true - } - - default: - - if meshLoggingEnabled { MeshLogger.log("Unhandled Characteristic UUID: \(characteristic.uuid)") } - print("Unhandled Characteristic UUID: \(characteristic.uuid)") + + } catch { + if meshLoggingEnabled { MeshLogger.log("Fatal Error: Failed to decode json") } + fatalError("Failed to decode json") + } + } + + if decodedInfo.configCompleteID != 0 { + if meshLoggingEnabled { MeshLogger.log("BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)") } + print("BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)") + self.connectedPeripheral.subscribed = true + } + + default: + if meshLoggingEnabled { MeshLogger.log("Unhandled Characteristic UUID: \(characteristic.uuid)") } + print("Unhandled Characteristic UUID: \(characteristic.uuid)") } peripheral.readValue(for: FROMRADIO_characteristic) } - + // Send Broadcast Message - public func sendMessage(message: String) -> Bool - { + public func sendMessage(message: String) -> Bool { var success = false - + // Return false if we are not properly connected to a device, handle retry logic in the view for now if connectedPeripheral == nil || connectedPeripheral!.peripheral.state != CBPeripheralState.connected { - + self.disconnectPeripheral() self.startScanning() - + // Try and connect to the preferredPeripherial first let preferredPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" }).first if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil { connectTo(peripheral: preferredPeripheral!.peripheral) } else { - + // Try and connect to the last connected device let lastConnectedPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == self.lastConnectedPeripheral }).first if lastConnectedPeripheral != nil && lastConnectedPeripheral?.peripheral != nil { @@ -681,43 +649,40 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } success = false - } - else if message.count < 1 { + } else if message.count < 1 { // Don's send an empty message success = false - } - else { + } else { var longName: String = self.connectedPeripheral.name var shortName: String = "???" var nodeNum: UInt32 = self.connectedPeripheral.myInfo?.myNodeNum ?? 0 - + if connectedNode != nil { longName = connectedNode.user.longName shortName = connectedNode.user.shortName nodeNum = connectedNode.num } - + let messageModel = MessageModel(messageId: 0, messageTimeStamp: UInt32(Date().timeIntervalSince1970), fromUserId: nodeNum, toUserId: broadcastNodeId, fromUserLongName: longName, toUserLongName: "Broadcast", fromUserShortName: shortName, toUserShortName: "BC", receivedACK: false, messagePayload: message, direction: "OUT") let dataType = PortNum.textMessageApp let payloadData: Data = message.data(using: String.Encoding.utf8)! - + var dataMessage = DataMessage() dataMessage.payload = payloadData dataMessage.portnum = dataType - + var meshPacket = MeshPacket() meshPacket.to = broadcastNodeId meshPacket.decoded = dataMessage meshPacket.wantAck = true - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - if (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) - { + if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) messageData.messages.append(messageModel) messageData.save() @@ -726,7 +691,5 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } return success } - - - + } diff --git a/MeshtasticClient/Helpers/Extensions.swift b/MeshtasticClient/Helpers/Extensions.swift index 4de6f3ec..8f7eb6ee 100644 --- a/MeshtasticClient/Helpers/Extensions.swift +++ b/MeshtasticClient/Helpers/Extensions.swift @@ -1,24 +1,19 @@ import Foundation import SwiftUI -extension Data -{ - var hexDescription: String - { +extension Data { + var hexDescription: String { return reduce("") {$0 + String(format: "%02x", $1)} } } -extension Date -{ - static var currentTimeStamp: Int64 - { +extension Date { + static var currentTimeStamp: Int64 { return Int64(Date().timeIntervalSince1970 * 1000) } } -extension String -{ +extension String { /// Create `Data` from hexadecimal string representation /// @@ -26,8 +21,7 @@ extension String /// /// - returns: Data represented by this hexadecimal string. - var hexadecimal: Data? - { + var hexadecimal: Data? { var data = Data(capacity: count / 2) let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive) @@ -48,36 +42,36 @@ typealias NodeColor = Int extension NodeColor { // a color to represent an individual meshtastic device -var color: Color { - switch self { - case 0: - return Color(.red) - case 1: - return Color(.blue) - case 2: - return Color(.yellow) - case 3: - return Color(.green) - case 4: - return Color(.purple) - case 5: - return Color(.systemPink) - case 6: - return Color(.systemTeal) - case 7: - return Color(.black) - case 8: - return Color(.gray) - case 9: - return Color(.brown) - case 10: - return Color(.magenta) - case 11: - return Color(.orange) - case 12: - return Color(.cyan) - default: - return Color(.blue) - } - } + var color: Color { + switch self { + case 0: + return Color(.red) + case 1: + return Color(.blue) + case 2: + return Color(.yellow) + case 3: + return Color(.green) + case 4: + return Color(.purple) + case 5: + return Color(.systemPink) + case 6: + return Color(.systemTeal) + case 7: + return Color(.black) + case 8: + return Color(.gray) + case 9: + return Color(.brown) + case 10: + return Color(.magenta) + case 11: + return Color(.orange) + case 12: + return Color(.cyan) + default: + return Color(.blue) + } + } } diff --git a/MeshtasticClient/Helpers/HelperFunctions.swift b/MeshtasticClient/Helpers/HelperFunctions.swift index 16b5bc61..bc929c0f 100644 --- a/MeshtasticClient/Helpers/HelperFunctions.swift +++ b/MeshtasticClient/Helpers/HelperFunctions.swift @@ -7,5 +7,3 @@ import Foundation import SwiftUI - - diff --git a/MeshtasticClient/Helpers/LocalNotificationManager.swift b/MeshtasticClient/Helpers/LocalNotificationManager.swift index 73b601a0..ce4cfff1 100644 --- a/MeshtasticClient/Helpers/LocalNotificationManager.swift +++ b/MeshtasticClient/Helpers/LocalNotificationManager.swift @@ -1,14 +1,12 @@ import Foundation import SwiftUI - class LocalNotificationManager { var notifications = [Notification]() - + // Step 1 Request Permissions for notifications - private func requestAuthorization() - { + private func requestAuthorization() { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in if granted == true && error == nil { @@ -16,9 +14,8 @@ class LocalNotificationManager { } } } - - func schedule() - { + + func schedule() { UNUserNotificationCenter.current().getNotificationSettings { settings in switch settings.authorizationStatus { case .notDetermined: @@ -30,12 +27,10 @@ class LocalNotificationManager { } } } - + // This function iterates over the Notification objects in the notifications array and schedules them for delivery in the future - private func scheduleNotifications() - { - for notification in notifications - { + private func scheduleNotifications() { + for notification in notifications { let content = UNMutableNotificationContent() content.subtitle = notification.subtitle content.title = notification.title @@ -43,7 +38,6 @@ class LocalNotificationManager { content.sound = .default content.interruptionLevel = .timeSensitive - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger) @@ -55,10 +49,9 @@ class LocalNotificationManager { } } } - + // Check and debug what local notifications have been scheduled - func listScheduledNotifications() - { + func listScheduledNotifications() { UNUserNotificationCenter.current().getPendingNotificationRequests { notifications in for notification in notifications { @@ -66,8 +59,7 @@ class LocalNotificationManager { } } } - - + } struct Notification { diff --git a/MeshtasticClient/Helpers/LocationHelper.swift b/MeshtasticClient/Helpers/LocationHelper.swift index 902cb137..cb06da1c 100644 --- a/MeshtasticClient/Helpers/LocationHelper.swift +++ b/MeshtasticClient/Helpers/LocationHelper.swift @@ -1,24 +1,24 @@ import CoreLocation class LocationHelper: NSObject, ObservableObject { - + static let shared = LocationHelper() - + // Mount Rainier static let DefaultLocation = CLLocationCoordinate2D(latitude: 46.879967, longitude: -121.726906) - + static var currentLocation: CLLocationCoordinate2D { - + guard let location = shared.locationManager.location else { return DefaultLocation } return location.coordinate } - + private let locationManager = CLLocationManager() - + private override init() { - + super.init() locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyBest @@ -29,11 +29,11 @@ class LocationHelper: NSObject, ObservableObject { extension LocationHelper: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { } - + public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("Location manager failed with error: \(error.localizedDescription)") } - + public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { print("Location manager changed the status: \(status)") } diff --git a/MeshtasticClient/Model/MeshData.swift b/MeshtasticClient/Model/MeshData.swift index f2ba5a2d..75e7fffd 100644 --- a/MeshtasticClient/Model/MeshData.swift +++ b/MeshtasticClient/Model/MeshData.swift @@ -12,11 +12,11 @@ class MeshData: ObservableObject { fatalError("Can't find documents directory.") } } - + private static var fileURL: URL { return documentsFolder.appendingPathComponent("nodeinfo.data") } - + @Published var nodes: [NodeInfoModel] = [] func load() { @@ -33,12 +33,11 @@ class MeshData: ObservableObject { do { // If the file is borked delete it so we stop crashing try FileManager.default.removeItem(at: Self.fileURL) - } - catch { - + } catch { + fatalError("Can't delete saved node data.") } - + fatalError("Can't decode saved node data.") } DispatchQueue.main.async { diff --git a/MeshtasticClient/Model/MessageData.swift b/MeshtasticClient/Model/MessageData.swift index a6356bfa..49955964 100644 --- a/MeshtasticClient/Model/MessageData.swift +++ b/MeshtasticClient/Model/MessageData.swift @@ -1,7 +1,7 @@ import Foundation class MessageData: ObservableObject { - + private static var documentsFolder: URL { do { return try FileManager.default.url(for: .documentDirectory, @@ -12,13 +12,13 @@ class MessageData: ObservableObject { fatalError("Can't find documents directory.") } } - + private static var fileURL: URL { return documentsFolder.appendingPathComponent("messages.data") } - + @Published var messages: [MessageModel] = [] - + func load() { DispatchQueue.global(qos: .background).async { [weak self] in guard let data = try? Data(contentsOf: Self.fileURL) else { diff --git a/MeshtasticClient/Model/MessageModel.swift b/MeshtasticClient/Model/MessageModel.swift index ec844a79..bd31d05f 100644 --- a/MeshtasticClient/Model/MessageModel.swift +++ b/MeshtasticClient/Model/MessageModel.swift @@ -6,8 +6,7 @@ // import Foundation -struct MessageModel : Identifiable, Codable -{ +struct MessageModel: Identifiable, Codable { let id: UUID var messageId: UInt32 var messageTimestamp: UInt32 @@ -20,9 +19,8 @@ struct MessageModel : Identifiable, Codable var receivedACK: Bool var messagePayload: String var direction: String - - init(id: UUID = UUID(), messageId: UInt32, messageTimeStamp: UInt32, fromUserId: UInt32, toUserId: UInt32, fromUserLongName: String, toUserLongName: String, fromUserShortName: String, toUserShortName: String, receivedACK: Bool, messagePayload: String, direction: String) - { + + init(id: UUID = UUID(), messageId: UInt32, messageTimeStamp: UInt32, fromUserId: UInt32, toUserId: UInt32, fromUserLongName: String, toUserLongName: String, fromUserShortName: String, toUserShortName: String, receivedACK: Bool, messagePayload: String, direction: String) { self.id = id self.messageId = messageId self.messageTimestamp = messageTimeStamp @@ -43,7 +41,7 @@ extension MessageModel { static var data: [MessageModel] { [ - //MessageModel(messageId: 3773493338, messageTimeStamp: 1632407404, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "GVH", toUserShortName: "U1", receivedACK: false, messagePayload: "yo", direction: "received") + // MessageModel(messageId: 3773493338, messageTimeStamp: 1632407404, fromUserId: 2930161432, toUserId: 4294967295, fromUserLongName: "TBEAM ARMY GREEN", toUserLongName: "Unknown 1", fromUserShortName: "GVH", toUserShortName: "U1", receivedACK: false, messagePayload: "yo", direction: "received") ] } } @@ -62,7 +60,7 @@ extension MessageModel { var receivedACK: Bool var messagePayload: String var direction: String - + } var data: Data { diff --git a/MeshtasticClient/Model/MyInfoModel.swift b/MeshtasticClient/Model/MyInfoModel.swift index b40fdc9c..f4101d86 100644 --- a/MeshtasticClient/Model/MyInfoModel.swift +++ b/MeshtasticClient/Model/MyInfoModel.swift @@ -1,7 +1,7 @@ import Foundation struct MyInfoModel: Identifiable, Codable { - + // Uses the BLE Peripheral identifier as the ID // So myInfo can map between Peripherals and Nodes var id: UInt32 @@ -12,9 +12,9 @@ struct MyInfoModel: Identifiable, Codable { var firmwareVersion: String var messageTimeoutMsec: UInt32 var minAppVersion: UInt32 - + init(myNodeNum: UInt32, hasGps: Bool, numBands: UInt32, maxChannels: UInt32, firmwareVersion: String, messageTimeoutMsec: UInt32, minAppVersion: UInt32) { - + self.id = myNodeNum self.myNodeNum = myNodeNum self.hasGps = hasGps diff --git a/MeshtasticClient/Model/NodeInfoModel.swift b/MeshtasticClient/Model/NodeInfoModel.swift index a0f45c9d..f2feccf2 100644 --- a/MeshtasticClient/Model/NodeInfoModel.swift +++ b/MeshtasticClient/Model/NodeInfoModel.swift @@ -5,7 +5,7 @@ import SwiftUI import CoreLocation struct NodeInfoModel: Identifiable, Codable { - + var id: UInt32 var num: UInt32 var myInfo: MyInfoModel? @@ -15,7 +15,7 @@ struct NodeInfoModel: Identifiable, Codable { var longName: String var shortName: String var hwModel: String - + init(id: String, longName: String, shortName: String, hwModel: String) { self.id = id self.longName = longName @@ -33,8 +33,7 @@ struct NodeInfoModel: Identifiable, Codable { return nil } return d / 1e7 - } - else { + } else { return nil } } @@ -46,25 +45,23 @@ struct NodeInfoModel: Identifiable, Codable { return nil } return d / 1e7 - } - else { + } else { return nil } } var coordinate: CLLocationCoordinate2D? { if latitude != nil && longitude != nil { let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!) - + return coord - } - else { + } else { return nil } } var altitude: Int32? var batteryLevel: Int32? var time: UInt32? - + init(latitudeI: Int32?, longitudeI: Int32?, altitude: Int32?, batteryLevel: Int32?, time: UInt32? ) { self.latitudeI = latitudeI self.longitudeI = longitudeI @@ -73,11 +70,10 @@ struct NodeInfoModel: Identifiable, Codable { self.time = time } } - + var lastHeard: UInt32 var snr: Float? - init(num: UInt32, user: User, position: Position, lastHeard: UInt32, snr: Float?) { self.id = num self.num = num @@ -106,7 +102,7 @@ extension NodeInfoModel { var postion: Position var lastHeard: UInt32 var snr: Float? - + } var data: Data { diff --git a/MeshtasticClient/Model/PeripheralModel.swift b/MeshtasticClient/Model/PeripheralModel.swift index e21f4bf3..919d7236 100644 --- a/MeshtasticClient/Model/PeripheralModel.swift +++ b/MeshtasticClient/Model/PeripheralModel.swift @@ -7,9 +7,9 @@ struct Peripheral: Identifiable { var rssi: Int var subscribed: Bool var peripheral: CBPeripheral - + var myInfo: MyInfoModel? - + init(id: String, name: String, rssi: Int, subscribed: Bool, peripheral: CBPeripheral, myInfo: MyInfoModel?) { self.id = id self.name = name diff --git a/MeshtasticClient/Protobufs/admin.pb.swift b/MeshtasticClient/Protobufs/admin.pb.swift index 8c864002..5e48f5ba 100644 --- a/MeshtasticClient/Protobufs/admin.pb.swift +++ b/MeshtasticClient/Protobufs/admin.pb.swift @@ -15,7 +15,7 @@ import SwiftProtobuf // incompatible with the version of SwiftProtobuf to which you are linking. // Please ensure that you are building against the same version of the API // that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { +private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} typealias Version = _2 } @@ -29,7 +29,7 @@ struct AdminMessage { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var variant: AdminMessage.OneOf_Variant? = nil + var variant: AdminMessage.OneOf_Variant? /// /// set the radio provisioning for this node @@ -258,7 +258,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 32: .standard(proto: "confirm_set_channel"), 33: .standard(proto: "confirm_set_radio"), 34: .standard(proto: "exit_simulator"), - 35: .standard(proto: "reboot_seconds"), + 35: .standard(proto: "reboot_seconds") ] mutating func decodeMessage(decoder: inout D) throws { diff --git a/MeshtasticClient/Protobufs/channel.pb.swift b/MeshtasticClient/Protobufs/channel.pb.swift index be4ded9b..6fa4799f 100644 --- a/MeshtasticClient/Protobufs/channel.pb.swift +++ b/MeshtasticClient/Protobufs/channel.pb.swift @@ -34,7 +34,7 @@ import SwiftProtobuf // incompatible with the version of SwiftProtobuf to which you are linking. // Please ensure that you are building against the same version of the API // that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { +private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} typealias Version = _2 } @@ -230,7 +230,7 @@ extension ChannelSettings.ModemConfig: CaseIterable { .bw125Cr45Sf128, .bw500Cr45Sf128, .bw3125Cr48Sf512, - .bw125Cr48Sf4096, + .bw125Cr48Sf4096 ] } @@ -319,7 +319,7 @@ struct Channel { init() {} - fileprivate var _settings: ChannelSettings? = nil + fileprivate var _settings: ChannelSettings? } #if swift(>=4.2) @@ -329,7 +329,7 @@ extension Channel.Role: CaseIterable { static var allCases: [Channel.Role] = [ .disabled, .primary, - .secondary, + .secondary ] } @@ -350,7 +350,7 @@ extension ChannelSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen 5: .same(proto: "name"), 10: .same(proto: "id"), 16: .standard(proto: "uplink_enabled"), - 17: .standard(proto: "downlink_enabled"), + 17: .standard(proto: "downlink_enabled") ] mutating func decodeMessage(decoder: inout D) throws { @@ -434,7 +434,7 @@ extension ChannelSettings.ModemConfig: SwiftProtobuf._ProtoNameProviding { 0: .same(proto: "Bw125Cr45Sf128"), 1: .same(proto: "Bw500Cr45Sf128"), 2: .same(proto: "Bw31_25Cr48Sf512"), - 3: .same(proto: "Bw125Cr48Sf4096"), + 3: .same(proto: "Bw125Cr48Sf4096") ] } @@ -443,7 +443,7 @@ extension Channel: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "index"), 2: .same(proto: "settings"), - 3: .same(proto: "role"), + 3: .same(proto: "role") ] mutating func decodeMessage(decoder: inout D) throws { @@ -486,6 +486,6 @@ extension Channel.Role: SwiftProtobuf._ProtoNameProviding { static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "DISABLED"), 1: .same(proto: "PRIMARY"), - 2: .same(proto: "SECONDARY"), + 2: .same(proto: "SECONDARY") ] } diff --git a/MeshtasticClient/Protobufs/deviceonly.pb.swift b/MeshtasticClient/Protobufs/deviceonly.pb.swift index b44ea5c8..5cca37f9 100644 --- a/MeshtasticClient/Protobufs/deviceonly.pb.swift +++ b/MeshtasticClient/Protobufs/deviceonly.pb.swift @@ -15,7 +15,7 @@ import SwiftProtobuf // incompatible with the version of SwiftProtobuf to which you are linking. // Please ensure that you are building against the same version of the API // that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { +private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} typealias Version = _2 } @@ -56,7 +56,7 @@ struct LegacyRadioConfig { init() {} - fileprivate var _preferences: LegacyRadioConfig.LegacyPreferences? = nil + fileprivate var _preferences: LegacyRadioConfig.LegacyPreferences? } /// @@ -180,7 +180,7 @@ struct ChannelFile { extension LegacyRadioConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = "LegacyRadioConfig" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "preferences"), + 1: .same(proto: "preferences") ] mutating func decodeMessage(decoder: inout D) throws { @@ -212,7 +212,7 @@ extension LegacyRadioConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem extension LegacyRadioConfig.LegacyPreferences: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = LegacyRadioConfig.protoMessageName + ".LegacyPreferences" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 15: .same(proto: "region"), + 15: .same(proto: "region") ] mutating func decodeMessage(decoder: inout D) throws { @@ -252,17 +252,17 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati 8: .same(proto: "version"), 7: .standard(proto: "rx_text_message"), 9: .standard(proto: "no_save"), - 11: .standard(proto: "did_gps_reset"), + 11: .standard(proto: "did_gps_reset") ] fileprivate class _StorageClass { - var _legacyRadio: LegacyRadioConfig? = nil - var _myNode: MyNodeInfo? = nil - var _owner: User? = nil + var _legacyRadio: LegacyRadioConfig? + var _myNode: MyNodeInfo? + var _owner: User? var _nodeDb: [NodeInfo] = [] var _receiveQueue: [MeshPacket] = [] var _version: UInt32 = 0 - var _rxTextMessage: MeshPacket? = nil + var _rxTextMessage: MeshPacket? var _noSave: Bool = false var _didGpsReset: Bool = false @@ -372,7 +372,7 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati extension ChannelFile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = "ChannelFile" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "channels"), + 1: .same(proto: "channels") ] mutating func decodeMessage(decoder: inout D) throws { diff --git a/MeshtasticClient/Protobufs/environmental_measurement.pb.swift b/MeshtasticClient/Protobufs/environmental_measurement.pb.swift index 73e7ef02..8fd5f131 100644 --- a/MeshtasticClient/Protobufs/environmental_measurement.pb.swift +++ b/MeshtasticClient/Protobufs/environmental_measurement.pb.swift @@ -15,7 +15,7 @@ import SwiftProtobuf // incompatible with the version of SwiftProtobuf to which you are linking. // Please ensure that you are building against the same version of the API // that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { +private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} typealias Version = _2 } @@ -43,7 +43,7 @@ extension EnvironmentalMeasurement: SwiftProtobuf.Message, SwiftProtobuf._Messag static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "temperature"), 2: .standard(proto: "relative_humidity"), - 3: .standard(proto: "barometric_pressure"), + 3: .standard(proto: "barometric_pressure") ] mutating func decodeMessage(decoder: inout D) throws { diff --git a/MeshtasticClient/Protobufs/mesh.pb.swift b/MeshtasticClient/Protobufs/mesh.pb.swift index 4a43e8af..83cd53fb 100644 --- a/MeshtasticClient/Protobufs/mesh.pb.swift +++ b/MeshtasticClient/Protobufs/mesh.pb.swift @@ -34,7 +34,7 @@ import SwiftProtobuf // incompatible with the version of SwiftProtobuf to which you are linking. // Please ensure that you are building against the same version of the API // that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { +private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} typealias Version = _2 } @@ -152,7 +152,7 @@ extension HardwareModel: CaseIterable { .genieblocks, .nrf52Unknown, .portduino, - .androidSim, + .androidSim ] } @@ -203,7 +203,7 @@ extension Constants: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. static var allCases: [Constants] = [ .unused, - .dataPayloadLen, + .dataPayloadLen ] } @@ -261,7 +261,7 @@ enum CriticalErrorCode: SwiftProtobuf.Enum { case sx1262Failure // = 10 /// A (likely software but possibly hardware) failure was detected while trying to send packets. If this occurs on your board, please - ///post in the forum so that we can ask you to collect some information to allow fixing this bug + /// post in the forum so that we can ask you to collect some information to allow fixing this bug case radioSpiBug // = 11 case UNRECOGNIZED(Int) @@ -323,7 +323,7 @@ extension CriticalErrorCode: CaseIterable { .transmitFailed, .brownout, .sx1262Failure, - .radioSpiBug, + .radioSpiBug ] } @@ -450,13 +450,13 @@ struct RouteDiscovery { } /// -///A Routing control Data packet handled by the routing plugin +/// A Routing control Data packet handled by the routing plugin struct Routing { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var variant: Routing.OneOf_Variant? = nil + var variant: Routing.OneOf_Variant? /// /// A route request going from the requester @@ -635,7 +635,7 @@ extension Routing.Error: CaseIterable { .tooLarge, .noResponse, .badRequest, - .notAuthorized, + .notAuthorized ] } @@ -719,7 +719,7 @@ struct MeshPacket { /// This 'trick' is only used while the payloadVariant is an 'encrypted'. var channel: UInt32 = 0 - var payloadVariant: MeshPacket.OneOf_PayloadVariant? = nil + var payloadVariant: MeshPacket.OneOf_PayloadVariant? var decoded: DataMessage { get { @@ -915,7 +915,7 @@ extension MeshPacket.Priority: CaseIterable { .default, .reliable, .ack, - .max, + .max ] } @@ -988,8 +988,8 @@ struct NodeInfo { init() {} - fileprivate var _user: User? = nil - fileprivate var _position: Position? = nil + fileprivate var _user: User? + fileprivate var _position: Position? } /// @@ -1169,7 +1169,7 @@ extension LogRecord.Level: CaseIterable { .warning, .info, .debug, - .trace, + .trace ] } @@ -1341,7 +1341,7 @@ struct ToRadio { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - var payloadVariant: ToRadio.OneOf_PayloadVariant? = nil + var payloadVariant: ToRadio.OneOf_PayloadVariant? /// /// send this packet on the mesh @@ -1495,14 +1495,14 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 35: .same(proto: "GENIEBLOCKS"), 36: .same(proto: "NRF52_UNKNOWN"), 37: .same(proto: "PORTDUINO"), - 38: .same(proto: "ANDROID_SIM"), + 38: .same(proto: "ANDROID_SIM") ] } extension Constants: SwiftProtobuf._ProtoNameProviding { static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "Unused"), - 237: .same(proto: "DATA_PAYLOAD_LEN"), + 237: .same(proto: "DATA_PAYLOAD_LEN") ] } @@ -1519,7 +1519,7 @@ extension CriticalErrorCode: SwiftProtobuf._ProtoNameProviding { 8: .same(proto: "TransmitFailed"), 9: .same(proto: "Brownout"), 10: .same(proto: "SX1262Failure"), - 11: .same(proto: "RadioSpiBug"), + 11: .same(proto: "RadioSpiBug") ] } @@ -1530,7 +1530,7 @@ extension Position: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB 2: .standard(proto: "longitude_i"), 3: .same(proto: "altitude"), 4: .standard(proto: "battery_level"), - 9: .same(proto: "time"), + 9: .same(proto: "time") ] mutating func decodeMessage(decoder: inout D) throws { @@ -1587,7 +1587,7 @@ extension User: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, 3: .standard(proto: "short_name"), 4: .same(proto: "macaddr"), 6: .standard(proto: "hw_model"), - 7: .standard(proto: "is_licensed"), + 7: .standard(proto: "is_licensed") ] mutating func decodeMessage(decoder: inout D) throws { @@ -1644,7 +1644,7 @@ extension User: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, extension RouteDiscovery: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = "RouteDiscovery" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 2: .same(proto: "route"), + 2: .same(proto: "route") ] mutating func decodeMessage(decoder: inout D) throws { @@ -1678,7 +1678,7 @@ extension Routing: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "route_request"), 2: .standard(proto: "route_reply"), - 3: .standard(proto: "error_reason"), + 3: .standard(proto: "error_reason") ] mutating func decodeMessage(decoder: inout D) throws { @@ -1767,7 +1767,7 @@ extension Routing.Error: SwiftProtobuf._ProtoNameProviding { 7: .same(proto: "TOO_LARGE"), 8: .same(proto: "NO_RESPONSE"), 32: .same(proto: "BAD_REQUEST"), - 33: .same(proto: "NOT_AUTHORIZED"), + 33: .same(proto: "NOT_AUTHORIZED") ] } @@ -1779,7 +1779,7 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati 3: .standard(proto: "want_response"), 4: .same(proto: "dest"), 5: .same(proto: "source"), - 6: .standard(proto: "request_id"), + 6: .standard(proto: "request_id") ] mutating func decodeMessage(decoder: inout D) throws { @@ -1847,7 +1847,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 10: .standard(proto: "hop_limit"), 11: .standard(proto: "want_ack"), 12: .same(proto: "priority"), - 13: .standard(proto: "rx_rssi"), + 13: .standard(proto: "rx_rssi") ] mutating func decodeMessage(decoder: inout D) throws { @@ -1965,7 +1965,7 @@ extension MeshPacket.Priority: SwiftProtobuf._ProtoNameProviding { 64: .same(proto: "DEFAULT"), 70: .same(proto: "RELIABLE"), 120: .same(proto: "ACK"), - 127: .same(proto: "MAX"), + 127: .same(proto: "MAX") ] } @@ -1976,7 +1976,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB 2: .same(proto: "user"), 3: .same(proto: "position"), 7: .same(proto: "snr"), - 4: .standard(proto: "last_heard"), + 4: .standard(proto: "last_heard") ] mutating func decodeMessage(decoder: inout D) throws { @@ -2040,7 +2040,7 @@ extension MyNodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 9: .standard(proto: "error_count"), 10: .standard(proto: "reboot_count"), 13: .standard(proto: "message_timeout_msec"), - 14: .standard(proto: "min_app_version"), + 14: .standard(proto: "min_app_version") ] mutating func decodeMessage(decoder: inout D) throws { @@ -2135,7 +2135,7 @@ extension LogRecord: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 1: .same(proto: "message"), 2: .same(proto: "time"), 3: .same(proto: "source"), - 4: .same(proto: "level"), + 4: .same(proto: "level") ] mutating func decodeMessage(decoder: inout D) throws { @@ -2187,7 +2187,7 @@ extension LogRecord.Level: SwiftProtobuf._ProtoNameProviding { 20: .same(proto: "INFO"), 30: .same(proto: "WARNING"), 40: .same(proto: "ERROR"), - 50: .same(proto: "CRITICAL"), + 50: .same(proto: "CRITICAL") ] } @@ -2200,7 +2200,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 4: .standard(proto: "node_info"), 7: .standard(proto: "log_record"), 8: .standard(proto: "config_complete_id"), - 9: .same(proto: "rebooted"), + 9: .same(proto: "rebooted") ] fileprivate class _StorageClass { @@ -2368,7 +2368,7 @@ extension ToRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa 2: .same(proto: "packet"), 3: .standard(proto: "peer_info"), 100: .standard(proto: "want_config_id"), - 104: .same(proto: "disconnect"), + 104: .same(proto: "disconnect") ] mutating func decodeMessage(decoder: inout D) throws { @@ -2461,7 +2461,7 @@ extension ToRadio.PeerInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme static let protoMessageName: String = ToRadio.protoMessageName + ".PeerInfo" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "app_version"), - 2: .standard(proto: "mqtt_gateway"), + 2: .standard(proto: "mqtt_gateway") ] mutating func decodeMessage(decoder: inout D) throws { diff --git a/MeshtasticClient/Protobufs/mqtt.pb.swift b/MeshtasticClient/Protobufs/mqtt.pb.swift index f4870c0b..3c1ece18 100644 --- a/MeshtasticClient/Protobufs/mqtt.pb.swift +++ b/MeshtasticClient/Protobufs/mqtt.pb.swift @@ -15,7 +15,7 @@ import SwiftProtobuf // incompatible with the version of SwiftProtobuf to which you are linking. // Please ensure that you are building against the same version of the API // that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { +private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} typealias Version = _2 } @@ -68,11 +68,11 @@ extension ServiceEnvelope: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "packet"), 2: .standard(proto: "channel_id"), - 3: .standard(proto: "gateway_id"), + 3: .standard(proto: "gateway_id") ] fileprivate class _StorageClass { - var _packet: MeshPacket? = nil + var _packet: MeshPacket? var _channelID: String = String() var _gatewayID: String = String() diff --git a/MeshtasticClient/Protobufs/portnums.pb.swift b/MeshtasticClient/Protobufs/portnums.pb.swift index d168ec01..10a790df 100644 --- a/MeshtasticClient/Protobufs/portnums.pb.swift +++ b/MeshtasticClient/Protobufs/portnums.pb.swift @@ -15,7 +15,7 @@ import SwiftProtobuf // incompatible with the version of SwiftProtobuf to which you are linking. // Please ensure that you are building against the same version of the API // that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { +private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} typealias Version = _2 } @@ -197,7 +197,7 @@ extension PortNum: CaseIterable { .environmentalMeasurementApp, .privateApp, .atakForwarder, - .max, + .max ] } @@ -222,6 +222,6 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding { 67: .same(proto: "ENVIRONMENTAL_MEASUREMENT_APP"), 256: .same(proto: "PRIVATE_APP"), 257: .same(proto: "ATAK_FORWARDER"), - 511: .same(proto: "MAX"), + 511: .same(proto: "MAX") ] } diff --git a/MeshtasticClient/Protobufs/radioconfig.pb.swift b/MeshtasticClient/Protobufs/radioconfig.pb.swift index d037e53b..decb9ff7 100644 --- a/MeshtasticClient/Protobufs/radioconfig.pb.swift +++ b/MeshtasticClient/Protobufs/radioconfig.pb.swift @@ -34,7 +34,7 @@ import SwiftProtobuf // incompatible with the version of SwiftProtobuf to which you are linking. // Please ensure that you are building against the same version of the API // that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { +private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} typealias Version = _2 } @@ -114,7 +114,7 @@ extension RegionCode: CaseIterable { .anz, .kr, .tw, - .ru, + .ru ] } @@ -217,7 +217,7 @@ extension ChargeCurrent: CaseIterable { .ma1080, .ma1160, .ma1240, - .ma1320, + .ma1320 ] } @@ -292,7 +292,7 @@ extension GpsOperation: CaseIterable { .gpsOpStationary, .gpsOpMobile, .gpsOpTimeOnly, - .gpsOpDisabled, + .gpsOpDisabled ] } @@ -375,7 +375,7 @@ extension GpsCoordinateFormat: CaseIterable { .gpsFormatUtm, .gpsFormatMgrs, .gpsFormatOlc, - .gpsFormatOsgr, + .gpsFormatOsgr ] } @@ -430,7 +430,7 @@ extension LocationSharing: CaseIterable { static var allCases: [LocationSharing] = [ .locUnset, .locEnabled, - .locDisabled, + .locDisabled ] } @@ -789,7 +789,7 @@ struct RadioConfig { } /// - ///Preferences for the RangeTestPlugin + /// Preferences for the RangeTestPlugin /// FIXME - Move this out of UserPreferences and into a section for plugin configuration. var rangeTestPluginEnabled: Bool { get {return _storage._rangeTestPluginEnabled} @@ -808,7 +808,7 @@ struct RadioConfig { /// /// Preferences for the StoreForwardPlugin - ///FIXME - Move this out of UserPreferences and into a section for plugin configuration. (was 136) + /// FIXME - Move this out of UserPreferences and into a section for plugin configuration. (was 136) var storeForwardPluginEnabled: Bool { get {return _storage._storeForwardPluginEnabled} set {_uniqueStorage()._storeForwardPluginEnabled = newValue} @@ -917,7 +917,7 @@ struct RadioConfig { init() {} - fileprivate var _preferences: RadioConfig.UserPreferences? = nil + fileprivate var _preferences: RadioConfig.UserPreferences? } #if swift(>=4.2) @@ -925,7 +925,7 @@ struct RadioConfig { extension RadioConfig.UserPreferences.EnvironmentalMeasurementSensorType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. static var allCases: [RadioConfig.UserPreferences.EnvironmentalMeasurementSensorType] = [ - .dht11, + .dht11 ] } @@ -944,7 +944,7 @@ extension RegionCode: SwiftProtobuf._ProtoNameProviding { 6: .same(proto: "ANZ"), 7: .same(proto: "KR"), 8: .same(proto: "TW"), - 9: .same(proto: "RU"), + 9: .same(proto: "RU") ] } @@ -966,7 +966,7 @@ extension ChargeCurrent: SwiftProtobuf._ProtoNameProviding { 13: .same(proto: "MA1080"), 14: .same(proto: "MA1160"), 15: .same(proto: "MA1240"), - 16: .same(proto: "MA1320"), + 16: .same(proto: "MA1320") ] } @@ -976,7 +976,7 @@ extension GpsOperation: SwiftProtobuf._ProtoNameProviding { 1: .same(proto: "GpsOpStationary"), 2: .same(proto: "GpsOpMobile"), 3: .same(proto: "GpsOpTimeOnly"), - 4: .same(proto: "GpsOpDisabled"), + 4: .same(proto: "GpsOpDisabled") ] } @@ -987,7 +987,7 @@ extension GpsCoordinateFormat: SwiftProtobuf._ProtoNameProviding { 2: .same(proto: "GpsFormatUTM"), 3: .same(proto: "GpsFormatMGRS"), 4: .same(proto: "GpsFormatOLC"), - 5: .same(proto: "GpsFormatOSGR"), + 5: .same(proto: "GpsFormatOSGR") ] } @@ -995,14 +995,14 @@ extension LocationSharing: SwiftProtobuf._ProtoNameProviding { static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "LocUnset"), 1: .same(proto: "LocEnabled"), - 2: .same(proto: "LocDisabled"), + 2: .same(proto: "LocDisabled") ] } extension RadioConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = "RadioConfig" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "preferences"), + 1: .same(proto: "preferences") ] mutating func decodeMessage(decoder: inout D) throws { @@ -1088,7 +1088,7 @@ extension RadioConfig.UserPreferences: SwiftProtobuf.Message, SwiftProtobuf._Mes 144: .standard(proto: "environmental_measurement_plugin_recovery_interval"), 145: .standard(proto: "environmental_measurement_plugin_display_farenheit"), 146: .standard(proto: "environmental_measurement_plugin_sensor_type"), - 147: .standard(proto: "environmental_measurement_plugin_sensor_pin"), + 147: .standard(proto: "environmental_measurement_plugin_sensor_pin") ] fileprivate class _StorageClass { @@ -1529,6 +1529,6 @@ extension RadioConfig.UserPreferences: SwiftProtobuf.Message, SwiftProtobuf._Mes extension RadioConfig.UserPreferences.EnvironmentalMeasurementSensorType: SwiftProtobuf._ProtoNameProviding { static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "DHT11"), + 0: .same(proto: "DHT11") ] } diff --git a/MeshtasticClient/Protobufs/remote_hardware.pb.swift b/MeshtasticClient/Protobufs/remote_hardware.pb.swift index f2a300da..445db300 100644 --- a/MeshtasticClient/Protobufs/remote_hardware.pb.swift +++ b/MeshtasticClient/Protobufs/remote_hardware.pb.swift @@ -15,7 +15,7 @@ import SwiftProtobuf // incompatible with the version of SwiftProtobuf to which you are linking. // Please ensure that you are building against the same version of the API // that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { +private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} typealias Version = _2 } @@ -126,7 +126,7 @@ extension HardwareMessage.TypeEnum: CaseIterable { .watchGpios, .gpiosChanged, .readGpios, - .readGpiosReply, + .readGpiosReply ] } @@ -139,7 +139,7 @@ extension HardwareMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "typ"), 2: .standard(proto: "gpio_mask"), - 3: .standard(proto: "gpio_value"), + 3: .standard(proto: "gpio_value") ] mutating func decodeMessage(decoder: inout D) throws { @@ -185,6 +185,6 @@ extension HardwareMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding { 2: .same(proto: "WATCH_GPIOS"), 3: .same(proto: "GPIOS_CHANGED"), 4: .same(proto: "READ_GPIOS"), - 5: .same(proto: "READ_GPIOS_REPLY"), + 5: .same(proto: "READ_GPIOS_REPLY") ] } diff --git a/MeshtasticClient/Views/Bluetooth/Connect.swift b/MeshtasticClient/Views/Bluetooth/Connect.swift index 9550a579..637ba3cb 100644 --- a/MeshtasticClient/Views/Bluetooth/Connect.swift +++ b/MeshtasticClient/Views/Bluetooth/Connect.swift @@ -14,47 +14,46 @@ import CoreLocation import CoreBluetooth struct Connect: View { - + @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings - + @State var isPreferredRadio: Bool = false - + var body: some View { - + NavigationView { - + VStack { if bleManager.isSwitchedOn { - + List { if bleManager.lastConnectionError.count > 0 { - + Section(header: Text("Connection Error").font(.title)) { - + Text(bleManager.lastConnectionError).font(.title3).foregroundColor(.red) } .textCase(nil) } - + Section(header: Text("Connected Radio").font(.title)) { - + 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) - - VStack (alignment: .leading) { - + + VStack(alignment: .leading) { + if bleManager.connectedNode != nil { - + Text(bleManager.connectedNode.user.longName).font(.title2) - } - else { - + } else { + Text(String(bleManager.connectedPeripheral.peripheral.name ?? "Unknown")).font(.title2) } if bleManager.connectedNode != nil { @@ -69,9 +68,9 @@ struct Connect: View { } } Spacer() - - VStack (alignment: .center) { - + + VStack(alignment: .center) { + Text("Preferred").font(.caption2) Text("Radio").font(.caption2) Toggle("Preferred Radio", isOn: $isPreferredRadio) @@ -79,7 +78,7 @@ struct Connect: View { .labelsHidden() .onChange(of: isPreferredRadio) { value in if value { - + if bleManager.connectedNode != nil { let deviceName = "\(bleManager.connectedNode.user.longName) / \(bleManager.connectedPeripheral.peripheral.name ?? "")" @@ -87,13 +86,13 @@ struct Connect: View { } else { userSettings.preferredPeripheralName = bleManager.connectedPeripheral.peripheral.name ?? "Unknown Device" } - + userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString } else { - + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { - + userSettings.preferredPeripheralId = "" userSettings.preferredPeripheralName = "" } @@ -102,10 +101,9 @@ struct Connect: View { } } .swipeActions { - + Button(role: .destructive) { - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected - { + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { bleManager.disconnectPeripheral() isPreferredRadio = false } @@ -114,9 +112,8 @@ struct Connect: View { } } .padding([.top, .bottom]) - } - else { - HStack{ + } else { + HStack { Image(systemName: "antenna.radiowaves.left.and.right.slash") .symbolRenderingMode(.hierarchical) .imageScale(.large).foregroundColor(.red) @@ -125,10 +122,10 @@ struct Connect: View { } .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 @@ -138,18 +135,16 @@ struct Connect: View { .padding(.trailing) Button(action: { self.bleManager.stopScanning() - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected - { - + 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 { - + } else { + isPreferredRadio = false } }) { @@ -157,13 +152,13 @@ struct Connect: View { } Spacer() Text(String(peripheral.rssi) + " dB").font(.title3) - }.padding([.bottom,.top]) + }.padding([.bottom, .top]) } }.textCase(nil) } } - - HStack (alignment: .center) { + + HStack(alignment: .center) { Spacer() Button(action: { self.bleManager.startScanning() @@ -193,9 +188,8 @@ struct Connect: View { Spacer() } .padding(.bottom, 10) - - } - else { + + } else { Text("Bluetooth: OFF") .foregroundColor(.red) .font(.title) @@ -203,7 +197,7 @@ struct Connect: View { } .navigationTitle("Bluetooth Radios") .navigationBarItems(trailing: - + ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") ) @@ -215,8 +209,7 @@ struct Connect: View { if bleManager.connectedPeripheral != nil && userSettings.preferredPeripheralId == bleManager.connectedPeripheral.peripheral.identifier.uuidString { isPreferredRadio = true - } - else { + } else { isPreferredRadio = false } }) diff --git a/MeshtasticClient/Views/ContentView.swift b/MeshtasticClient/Views/ContentView.swift index 84cbd0b3..98821a16 100644 --- a/MeshtasticClient/Views/ContentView.swift +++ b/MeshtasticClient/Views/ContentView.swift @@ -16,7 +16,7 @@ struct ContentView: View { } var body: some View { - + TabView(selection: $selection) { Channels() .tabItem { diff --git a/MeshtasticClient/Views/Helpers/BatteryIcon.swift b/MeshtasticClient/Views/Helpers/BatteryIcon.swift index 8a67e957..a67e580d 100644 --- a/MeshtasticClient/Views/Helpers/BatteryIcon.swift +++ b/MeshtasticClient/Views/Helpers/BatteryIcon.swift @@ -4,46 +4,41 @@ struct BatteryIcon: View { var batteryLevel: Int32? var font: Font var color: Color - + var body: some View { - + if batteryLevel == 100 { - + Image(systemName: "battery.100.bolt") .font(font) .foregroundColor(color) .symbolRenderingMode(.hierarchical) - } - else if batteryLevel! < 100 && batteryLevel! > 74 { - + } else if batteryLevel! < 100 && batteryLevel! > 74 { + Image(systemName: "battery.75") .font(font) .foregroundColor(color) .symbolRenderingMode(.hierarchical) - } - else if batteryLevel! < 75 && batteryLevel! > 49 { - + } else if batteryLevel! < 75 && batteryLevel! > 49 { + Image(systemName: "battery.50") .font(font) .foregroundColor(color) .symbolRenderingMode(.hierarchical) - } - else if batteryLevel! < 50 && batteryLevel! > 14 { - + } else if batteryLevel! < 50 && batteryLevel! > 14 { + Image(systemName: "battery.25") .font(font) .foregroundColor(color) .symbolRenderingMode(.hierarchical) - } - else if batteryLevel! == 0 { - + } else if batteryLevel! == 0 { + Image(systemName: "powerplug") .font(font) .foregroundColor(color) .symbolRenderingMode(.hierarchical) - } - else { - + } else { + Image(systemName: "battery.0") .font(font) .foregroundColor(color) diff --git a/MeshtasticClient/Views/Helpers/ConnectedDevice.swift b/MeshtasticClient/Views/Helpers/ConnectedDevice.swift index 352d6e29..680fd96f 100644 --- a/MeshtasticClient/Views/Helpers/ConnectedDevice.swift +++ b/MeshtasticClient/Views/Helpers/ConnectedDevice.swift @@ -11,7 +11,7 @@ struct ConnectedDevice: View { var name: String? var body: some View { - + HStack { if bluetoothOn { @@ -21,18 +21,16 @@ struct ConnectedDevice: View { .foregroundColor(.green) .symbolRenderingMode(.hierarchical) Text(name!).font(.subheadline).foregroundColor(.gray) - } - else { - + } else { + Image(systemName: "antenna.radiowaves.left.and.right.slash") .imageScale(.medium) .foregroundColor(.red) .symbolRenderingMode(.hierarchical) Text("Disconnected").font(.subheadline).foregroundColor(.gray) - + } - } - else { + } else { Text("Bluetooth Off").font(.subheadline).foregroundColor(.red) } } @@ -43,9 +41,9 @@ struct ConnectedDevice_Previews: PreviewProvider { static var previews: some View { ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "Yellow Beam") .previewLayout(.fixed(width: 80, height: 70)) - - ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "Yellow Beam") + + ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "Yellow Beam") .previewLayout(.fixed(width: 80, height: 70)) } - + } diff --git a/MeshtasticClient/Views/Helpers/MessageBubble.swift b/MeshtasticClient/Views/Helpers/MessageBubble.swift index e7b94e72..ae626620 100644 --- a/MeshtasticClient/Views/Helpers/MessageBubble.swift +++ b/MeshtasticClient/Views/Helpers/MessageBubble.swift @@ -1,42 +1,40 @@ import SwiftUI struct MessageBubble: View { - + @State var showAlert = false var contentMessage: String var isCurrentUser: Bool var time: Int32 var shortName: String var id: UInt32 - + var body: some View { - - HStack (alignment: .top) { - + + HStack(alignment: .top) { + CircleText(text: shortName, color: isCurrentUser ? Color.blue : Color(.darkGray)).padding(.all, 5) .gesture(LongPressGesture(minimumDuration: 2) .onEnded {_ in print("I want to delete message: \(id)") self.showAlert = true }) - - - VStack (alignment: .leading) { + + VStack(alignment: .leading) { Text(contentMessage) .textSelection(.enabled) .padding(10) .foregroundColor(.white) .background(isCurrentUser ? Color.blue : Color(.darkGray)) .cornerRadius(10) - HStack (spacing: 4) { - + HStack(spacing: 4) { + let messageDate = Date(timeIntervalSince1970: TimeInterval(time)) if time != 0 { Text(messageDate, style: .date).font(.caption2).foregroundColor(.gray) Text(messageDate, style: .time).font(.caption2).foregroundColor(.gray) - } - else { + } else { Text("Unknown").font(.caption2).foregroundColor(.gray) } } @@ -46,11 +44,11 @@ struct MessageBubble: View { } .alert(isPresented: $showAlert) { Alert(title: Text("Are you sure you want to delete this message?"), message: Text("This action is permanent."), - primaryButton: .destructive (Text("OK")) { + primaryButton: .destructive(Text("OK")) { print("OK button tapped") - //let messageIndex = meshData.nodes.firstIndex(where: { $0.id == node.id }) - //meshData.nodes.remove(at: nodeIndex!) - //meshData.save() + // let messageIndex = meshData.nodes.firstIndex(where: { $0.id == node.id }) + // meshData.nodes.remove(at: nodeIndex!) + // meshData.save() }, secondaryButton: .cancel() ) @@ -66,5 +64,3 @@ struct MessageBubble_Previews: PreviewProvider { .previewLayout(.fixed(width: 300, height: 100)) } } - - diff --git a/MeshtasticClient/Views/Messages/Channels.swift b/MeshtasticClient/Views/Messages/Channels.swift index 2207fad3..a749a8c1 100644 --- a/MeshtasticClient/Views/Messages/Channels.swift +++ b/MeshtasticClient/Views/Messages/Channels.swift @@ -3,31 +3,30 @@ import SwiftUI import CoreBluetooth struct Channels: View { - + @State private var isShowingDetailView = true - + var body: some View { - - + NavigationView { - - GeometryReader { bounds in - + + GeometryReader { _ in + NavigationLink(destination: Messages(), isActive: $isShowingDetailView) { - - List{ - + + List { + HStack { - + Image(systemName: "dial.max.fill") .font(.system(size: 62)) .symbolRenderingMode(.hierarchical) .padding(.trailing) .foregroundColor(.accentColor) - + Text("Primary") .font(.largeTitle) - + }.padding() } } @@ -39,7 +38,7 @@ struct Channels: View { } struct MessageList_Previews: PreviewProvider { - + static let meshData = MeshData() static var previews: some View { diff --git a/MeshtasticClient/Views/Messages/Messages.swift b/MeshtasticClient/Views/Messages/Messages.swift index 6edf108c..21a674e2 100644 --- a/MeshtasticClient/Views/Messages/Messages.swift +++ b/MeshtasticClient/Views/Messages/Messages.swift @@ -4,78 +4,76 @@ import Foundation import CoreLocation struct Messages: View { - + enum Field: Hashable { case messageText } - + // Keyboard State @State var typingMessage: String = "" @State private var totalBytes = 0 private var maxbytes = 228 @State private var lastTypingMessage = "" @FocusState private var focusedField: Field? - + @Namespace var topId @Namespace var bottomId - + @State var showDeleteMessageAlert = false - @State private var deleteMessageId : UInt32 = 0 - + @State private var deleteMessageId: UInt32 = 0 + // Message Data and Bluetooth @EnvironmentObject var bleManager: BLEManager - + public var broadcastNodeId: UInt32 = 4294967295 let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() - @State var messageCount: Int = 0; - + @State var messageCount: Int = 0 + var body: some View { - + Text("\(messageCount) Messages").font(.caption) GeometryReader { bounds in - + VStack { - + ScrollViewReader { scrollView in - + if self.bleManager.messageData.messages.count > 0 { - + ScrollView { - + Text("Hidden Top Anchor").hidden().frame(height: 0).id(topId) - + ForEach(bleManager.messageData.messages.sorted(by: { $0.messageTimestamp < $1.messageTimestamp })) { message in - - HStack (alignment: .top) { + + HStack(alignment: .top) { let currentUser: Bool = (bleManager.connectedNode != nil) && ((bleManager.connectedNode.id) == message.fromUserId) - + CircleText(text: message.fromUserShortName, color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5) .gesture(LongPressGesture(minimumDuration: 2) .onEnded {_ in print("I want to delete message: \(message.messageId)") self.showDeleteMessageAlert = true self.deleteMessageId = message.messageId - + }) - - - VStack (alignment: .leading) { + + VStack(alignment: .leading) { Text(message.messagePayload) .textSelection(.enabled) .padding(10) .foregroundColor(.white) .background(currentUser ? Color.blue : Color(.darkGray)) .cornerRadius(10) - HStack (spacing: 4) { - + HStack(spacing: 4) { + let time = Int32(message.messageTimestamp) let messageDate = Date(timeIntervalSince1970: TimeInterval(time)) if time != 0 { Text(messageDate, style: .date).font(.caption2).foregroundColor(.gray) Text(messageDate, style: .time).font(.caption2).foregroundColor(.gray) - } - else { + } else { Text("Unknown").font(.caption2).foregroundColor(.gray) } } @@ -85,10 +83,10 @@ struct Messages: View { } .alert(isPresented: $showDeleteMessageAlert) { Alert(title: Text("Are you sure you want to delete this message?"), message: Text("This action is permanent."), - primaryButton: .destructive (Text("Delete")) { + primaryButton: .destructive(Text("Delete")) { print("OK button tapped") if deleteMessageId > 0 { - + let messageIndex = bleManager.messageData.messages.firstIndex(where: { $0.messageId == deleteMessageId }) bleManager.messageData.messages.remove(at: messageIndex!) bleManager.messageData.save() @@ -101,13 +99,13 @@ struct Messages: View { ) } } - .onAppear(perform: { scrollView.scrollTo(bottomId) } ) + .onAppear(perform: { scrollView.scrollTo(bottomId) }) Text("Hidden Bottom Anchor").hidden().frame(height: 0).id(bottomId) } - .onReceive(timer) { input in - + .onReceive(timer) { _ in + if messageCount < bleManager.messageData.messages.count { - + bleManager.messageData.load() scrollView.scrollTo(bottomId) messageCount = bleManager.messageData.messages.count @@ -116,9 +114,9 @@ struct Messages: View { .padding(.horizontal) } } - - HStack (alignment: .top) { - + + HStack(alignment: .top) { + ZStack { let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0) @@ -130,24 +128,22 @@ struct Messages: View { if totalBytes <= maxbytes { // Allow the user to type lastTypingMessage = typingMessage - } - else { + } else { // Set the message back and remove the bytes over the count self.typingMessage = lastTypingMessage } }) .keyboardType(kbType!) - .toolbar - { + .toolbar { ToolbarItemGroup(placement: .keyboard) { - + Button("Dismiss Keyboard") { focusedField = nil } .font(.subheadline) - + Spacer() - + ProgressView("Bytes: \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) .frame(width: 130) .padding(5) @@ -159,32 +155,30 @@ struct Messages: View { .focused($focusedField, equals: .messageText) .multilineTextAlignment(.leading) .frame(minHeight: bounds.size.height / 4, maxHeight: bounds.size.height / 4) - - + Text(typingMessage).opacity(0).padding(.all, 0) - + } .overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1)) .padding(.bottom, 15) - + Button(action: { if bleManager.sendMessage(message: typingMessage) { typingMessage = "" - } - else { - - let _ = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { (timer) in - + } else { + + _ = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { (_) in + if bleManager.sendMessage(message: typingMessage) { typingMessage = "" } } } - - } ) { + + }) { Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.blue) } - + } .padding(.all, 15) } @@ -192,13 +186,13 @@ struct Messages: View { .navigationTitle("Channel - Primary") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: - + ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") ) }) .onAppear { - + messageCount = bleManager.messageData.messages.count } } diff --git a/MeshtasticClient/Views/Nodes/NodeDetail.swift b/MeshtasticClient/Views/Nodes/NodeDetail.swift index 25eb6cb6..e624d622 100644 --- a/MeshtasticClient/Views/Nodes/NodeDetail.swift +++ b/MeshtasticClient/Views/Nodes/NodeDetail.swift @@ -8,27 +8,27 @@ import MapKit import CoreLocation struct NodeDetail: View { - - @EnvironmentObject var bleManager :BLEManager - + + @EnvironmentObject var bleManager: BLEManager + var node: NodeInfoModel - + struct MapLocation: Identifiable { let id = UUID() let name: String let coordinate: CLLocationCoordinate2D } - + var body: some View { GeometryReader { bounds in - + VStack { - - if(node.position.coordinate != nil) { - + + if node.position.coordinate != nil { + let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: node.position.latitude!, longitude: node.position.longitude!) - + let regionBinding = Binding( get: { MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) @@ -36,7 +36,7 @@ struct NodeDetail: View { set: { _ in } ) let annotations = [MapLocation(name: node.user.shortName, coordinate: node.position.coordinate!)] - + Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in MapAnnotation( coordinate: location.coordinate, @@ -44,29 +44,27 @@ struct NodeDetail: View { CircleText(text: node.user.shortName, color: .accentColor) } ) - }.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2) - } - else - { + }.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2) + } else { Image(node.user.hwModel) .resizable() .aspectRatio(contentMode: .fit) .frame(width: bounds.size.width, height: bounds.size.height / 2) } ScrollView { - + HStack { - + VStack(alignment: .center) { Text("AKA").font(.title2).fixedSize() CircleText(text: node.user.shortName, color: .accentColor) - .offset(y:10) + .offset(y: 10) } .padding([.leading, .trailing, .bottom]) Divider() if node.snr != nil && node.snr! > 0 { VStack(alignment: .center) { - + Image(systemName: "waveform.path") .font(.title) .foregroundColor(.accentColor) @@ -86,38 +84,37 @@ struct NodeDetail: View { .font(.title2) .foregroundColor(.gray) .symbolRenderingMode(.hierarchical) - } - else { + } else { Text("Powered").font(.title2) } } - + }.padding(4) Divider() HStack { - + Image(node.user.hwModel) .resizable() - .frame(width:60, height: 60) + .frame(width: 60, height: 60) .cornerRadius(5) - + Text("Model: " + String(node.user.hwModel)) .font(.title3) } .padding() Divider() - + if node.lastHeard > 0 { - - HStack{ - + + HStack { + Image(systemName: "clock").font(.title2).foregroundColor(.accentColor) let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard)) Text("Last Heard: \(lastHeard, style: .relative) ago").font(.title3) }.padding() Divider() } - + if node.position.coordinate != nil { HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) { Image(systemName: "mappin").font(.title).foregroundColor(.accentColor) @@ -138,9 +135,9 @@ struct NodeDetail: View { }.padding() Divider() } - HStack (alignment: .center) { + HStack(alignment: .center) { VStack { - HStack{ + HStack { Image(systemName: "person").font(.title3).foregroundColor(.accentColor) Text("Unique Id:").font(.title3) } @@ -159,15 +156,14 @@ struct NodeDetail: View { }.navigationTitle(node.user.longName) .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: - + ZStack { - //ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") ) + // ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") ) }) }.ignoresSafeArea(.all, edges: [.leading, .trailing]) } } - struct NodeDetail_Previews: PreviewProvider { static let bleManager = BLEManager() diff --git a/MeshtasticClient/Views/Nodes/NodeList.swift b/MeshtasticClient/Views/Nodes/NodeList.swift index d743819a..3ad7997e 100644 --- a/MeshtasticClient/Views/Nodes/NodeList.swift +++ b/MeshtasticClient/Views/Nodes/NodeList.swift @@ -11,13 +11,13 @@ import SwiftUI struct NodeList: View { - + @EnvironmentObject var bleManager: BLEManager - - @State private var selection: String? = nil - + + @State private var selection: String? + @State private var showLocationOnly = false - + var filteredDevices: [NodeInfoModel] { bleManager.meshData.nodes.filter { node in (!showLocationOnly || node.position.coordinate != nil) @@ -26,52 +26,49 @@ struct NodeList: View { var body: some View { NavigationView { - + List { if bleManager.meshData.nodes.count == 0 { Text("Scan for Radios").font(.largeTitle) - //.listRowSeparator(.hidden) + // .listRowSeparator(.hidden) Text("No LoRa Mesh Nodes Found").font(.title2) - //.listRowSeparator(.hidden) + // .listRowSeparator(.hidden) 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 phone or tablet.") .font(.body) - //.listRowSeparator(.hidden) + // .listRowSeparator(.hidden) 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.") - //.listRowSeparator(.hidden) + // .listRowSeparator(.hidden) 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.") - //.listRowSeparator(.hidden) + // .listRowSeparator(.hidden) Spacer() - //.listRowSeparator(.hidden) - } - else { + // .listRowSeparator(.hidden) + } else { Toggle(isOn: $showLocationOnly) { Text("Nodes with location only") } ForEach(filteredDevices.sorted(by: { $0.lastHeard > $1.lastHeard })) { node in - - + let index = filteredDevices.sorted(by: { $0.lastHeard > $1.lastHeard }).firstIndex(where: { $0.id == node.id }) - + NavigationLink(destination: NodeDetail(node: node), tag: String(index!), selection: $selection) { - - if(bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.myInfo != nil) { - + + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.myInfo != nil { + let connected: Bool = (bleManager.connectedPeripheral.myInfo!.myNodeNum == node.id) NodeRow(node: node, connected: connected) - } - else { + } else { NodeRow(node: node, connected: false) } - + } - .swipeActions (edge: .trailing) { - Button (role: .destructive) { + .swipeActions(edge: .trailing) { + Button(role: .destructive) { let nodeIndex = bleManager.meshData.nodes.firstIndex(where: { $0.num == node.num }) bleManager.meshData.nodes.remove(at: nodeIndex!) bleManager.meshData.save() } label: { - + Label("Delete from app", systemImage: "trash") } } diff --git a/MeshtasticClient/Views/Nodes/NodeMap.swift b/MeshtasticClient/Views/Nodes/NodeMap.swift index f824e2fc..665639d4 100644 --- a/MeshtasticClient/Views/Nodes/NodeMap.swift +++ b/MeshtasticClient/Views/Nodes/NodeMap.swift @@ -11,9 +11,9 @@ import MapKit import CoreLocation struct NodeMap: View { - - @EnvironmentObject var bleManager :BLEManager - + + @EnvironmentObject var bleManager: BLEManager + var locationNodes: [NodeInfoModel] { bleManager.meshData.nodes.filter { node in (node.position.coordinate != nil) @@ -24,7 +24,7 @@ struct NodeMap: View { let name: String let coordinate: CLLocationCoordinate2D } - + var body: some View { let location = LocationHelper.currentLocation @@ -35,15 +35,15 @@ struct NodeMap: View { }, set: { _ in } ) - + NavigationView { - + ZStack { Map(coordinateRegion: regionBinding, interactionModes: [.all], showsUserLocation: true, userTrackingMode: .constant(.follow), annotationItems: locationNodes) { location in - + MapAnnotation( coordinate: location.position.coordinate!, content: { @@ -51,17 +51,17 @@ struct NodeMap: View { } ) } - .frame(maxHeight:.infinity) + .frame(maxHeight: .infinity) .ignoresSafeArea(.all, edges: [.leading, .trailing]) } .navigationTitle("Mesh Map") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: - + ZStack { // ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedNode != nil) ? bleManager.connectedNode.user.shortName : ((bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.name : "Unknown") ) }) - + } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/MeshtasticClient/Views/Nodes/NodeRow.swift b/MeshtasticClient/Views/Nodes/NodeRow.swift index 8cba7fd2..b5a66351 100644 --- a/MeshtasticClient/Views/Nodes/NodeRow.swift +++ b/MeshtasticClient/Views/Nodes/NodeRow.swift @@ -5,51 +5,45 @@ struct NodeRow: View { var connected: Bool var body: some View { - VStack (alignment: .leading) { - - HStack() { - + VStack(alignment: .leading) { + + HStack { + CircleText(text: node.user.shortName, color: Color.accentColor).offset(y: 1).padding(.trailing, 5) .offset(x: -15) - + if UIDevice.current.userInterfaceIdiom == .pad { Text(node.user.longName).font(.headline) .offset(x: -15) - } - else { + } else { Text(node.user.longName).font(.title) .offset(x: -15) } } .padding(.bottom, 10) - - HStack (alignment: .bottom){ - + + HStack(alignment: .bottom) { + Image(systemName: "clock.badge.checkmark.fill").font(.headline).foregroundColor(.accentColor).symbolRenderingMode(.hierarchical) - if UIDevice.current.userInterfaceIdiom == .pad { - + if connected { Text("Currently Connected").font(.caption).foregroundColor(Color.accentColor) - } - else if node.lastHeard > 0 { + } else if node.lastHeard > 0 { let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard)) Text("Last Heard: \(lastHeard, style: .relative) ago").font(.caption).foregroundColor(.gray) - } - else { + } else { Text("Last Heard: Unknown").font(.caption).foregroundColor(.gray) } - + } else { if connected { Text("Currently Connected").font(.subheadline).foregroundColor(Color.accentColor) - } - else if node.lastHeard > 0 { + } else if node.lastHeard > 0 { let lastHeard = Date(timeIntervalSince1970: TimeInterval(node.lastHeard)) Text("Last Heard: \(lastHeard, style: .relative) ago").font(.subheadline).foregroundColor(.gray) - } - else { + } else { Text("Last Heard: Unknown").font(.subheadline).foregroundColor(.gray) } } diff --git a/MeshtasticClient/Views/Settings/AppSettings.swift b/MeshtasticClient/Views/Settings/AppSettings.swift index 0a24b85d..603193cb 100644 --- a/MeshtasticClient/Views/Settings/AppSettings.swift +++ b/MeshtasticClient/Views/Settings/AppSettings.swift @@ -4,38 +4,38 @@ import SwiftUI import SwiftProtobuf enum KeyboardType: Int, CaseIterable, Identifiable { - + case defaultKeyboard = 0 case asciiCapable = 1 case twitter = 9 case emailAddress = 7 case numbersAndPunctuation = 2 - + var id: Int { self.rawValue } var description: String { get { switch self { - case .defaultKeyboard: - return "Default" - case .asciiCapable: - return "ASCII Capable" - case .twitter: - return "Twitter" - case .emailAddress: - return "Email Address" - case .numbersAndPunctuation: - return "Numbers and Punctuation" + case .defaultKeyboard: + return "Default" + case .asciiCapable: + return "ASCII Capable" + case .twitter: + return "Twitter" + case .emailAddress: + return "Email Address" + case .numbersAndPunctuation: + return "Numbers and Punctuation" } } } } class UserSettings: ObservableObject { - //@Published var meshtasticUsername: String { + // @Published var meshtasticUsername: String { // didSet { // UserDefaults.standard.set(meshtasticUsername, forKey: "meshtasticusername") // } - //} + // } @Published var preferredPeripheralName: String { didSet { UserDefaults.standard.set(preferredPeripheralName, forKey: "preferredPeripheralName") @@ -61,9 +61,9 @@ class UserSettings: ObservableObject { UserDefaults.standard.set(meshActivityLog, forKey: "meshActivityLog") } } - + init() { - //self.meshtasticUsername = UserDefaults.standard.object(forKey: "meshtasticusername") as? String ?? "" + // self.meshtasticUsername = UserDefaults.standard.object(forKey: "meshtasticusername") as? String ?? "" self.preferredPeripheralName = UserDefaults.standard.object(forKey: "preferredPeripheralName") as? String ?? "" self.preferredPeripheralId = UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false @@ -73,33 +73,33 @@ class UserSettings: ObservableObject { } struct AppSettings: View { - + @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings - + @State private var preferredDeviceConnected = false - + var perferredPeripheral: String { UserDefaults.standard.object(forKey: "preferredPeripheralName") as? String ?? "" } - + var body: some View { NavigationView { - - GeometryReader { bounds in - + + GeometryReader { _ in + List { Section(header: Text("USER DETAILS")) { - - //HStack { + + // HStack { // Label("Name", systemImage: "person.crop.rectangle.fill") // TextField("Username", text: $userSettings.meshtasticUsername) // .foregroundColor(.gray) - //} - //.listRowSeparator(.visible) + // } + // .listRowSeparator(.visible) Toggle(isOn: $userSettings.provideLocation) { - + Label("Provide location to mesh", systemImage: "location.circle.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -114,7 +114,7 @@ struct AppSettings: View { Text("The preferred radio will automatically reconnect if it becomes disconnected and is still within range. This device is assumed to be the primary radio used for messaging.") .font(.caption2) .foregroundColor(.gray) - + } Section(header: Text("MESSAGING OPTIONS")) { @@ -127,7 +127,7 @@ struct AppSettings: View { } Section(header: Text("MESH NETWORK OPTIONS")) { Toggle(isOn: $userSettings.meshActivityLog) { - + Label("Log all Mesh activity", systemImage: "network") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -142,10 +142,9 @@ struct AppSettings: View { } .navigationTitle("App Settings") .navigationBarItems(trailing: - - + ZStack { - //ConnectedDevice(bluetoothOn: self.bleManager.isSwitchedOn, deviceConnected: self.bleManager.connectedPeripheral != nil, name: (self.bleManager.connectedNode != nil) ? self.bleManager.connectedNode.user.shortName : ((self.bleManager.connectedPeripheral != nil) ? self.bleManager.connectedPeripheral.name : "Unknown") ) + // ConnectedDevice(bluetoothOn: self.bleManager.isSwitchedOn, deviceConnected: self.bleManager.connectedPeripheral != nil, name: (self.bleManager.connectedNode != nil) ? self.bleManager.connectedNode.user.shortName : ((self.bleManager.connectedPeripheral != nil) ? self.bleManager.connectedPeripheral.name : "Unknown") ) }) } .navigationViewStyle(StackNavigationViewStyle()) diff --git a/MeshtasticClient/Views/Settings/LogDocument.swift b/MeshtasticClient/Views/Settings/LogDocument.swift index 5b65fd73..0732d189 100644 --- a/MeshtasticClient/Views/Settings/LogDocument.swift +++ b/MeshtasticClient/Views/Settings/LogDocument.swift @@ -1,24 +1,24 @@ import SwiftUI import UniformTypeIdentifiers -struct LogDocument: FileDocument{ - static var readableContentTypes:[UTType] {[.plainText]} - +struct LogDocument: FileDocument { + static var readableContentTypes: [UTType] {[.plainText]} + var logFile: String - - init(logFile: String){ + + init(logFile: String) { self.logFile = logFile } - - init(configuration: ReadConfiguration) throws{ + + init(configuration: ReadConfiguration) throws { guard let data = configuration.file.regularFileContents, let string = String(data: data, encoding: .utf8) - else{ + else { throw CocoaError(.fileReadCorruptFile) } logFile = string } - + func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { return FileWrapper(regularFileWithContents: logFile.data(using: .utf8)!) } diff --git a/MeshtasticClient/Views/Settings/MeshLog.swift b/MeshtasticClient/Views/Settings/MeshLog.swift index 1f4a62a2..e69f3475 100644 --- a/MeshtasticClient/Views/Settings/MeshLog.swift +++ b/MeshtasticClient/Views/Settings/MeshLog.swift @@ -8,13 +8,13 @@ struct MeshLog: View { @State private var logs = [String]() @State private var isExporting: Bool = false @State private var document: LogDocument = LogDocument(logFile: "MESHTASTIC MESH ACTIVITY LOG\n") - + var body: some View { List(logs, id: \.self, rowContent: Text.init) .task { do { - + let url = logFile! logs.removeAll() for try await log in url.lines { @@ -31,10 +31,8 @@ struct MeshLog: View { document: document, contentType: UTType.plainText, defaultFilename: "mesh-activity-log", - onCompletion: { + onCompletion: { result in - result in - if case .success = result { print("Mesh activity log download: success.") } else { @@ -42,12 +40,11 @@ struct MeshLog: View { } } ) - - + .textSelection(.enabled) .font(.caption2) - - HStack (alignment: .center) { + + HStack(alignment: .center) { Spacer() Button(action: { let text = "" @@ -57,7 +54,7 @@ struct MeshLog: View { } catch { print(error) } - + }) { Image(systemName: "trash").imageScale(.large).foregroundColor(.gray) Text("Clear Log").font(.caption) @@ -67,9 +64,9 @@ struct MeshLog: View { .padding() .background(Color(.systemGray6)) .clipShape(Capsule()) - + Spacer() - + Button(action: { isExporting = true }) { @@ -81,9 +78,9 @@ struct MeshLog: View { .padding() .background(Color(.systemGray6)) .clipShape(Capsule()) - + Spacer() - + } .padding(.bottom, 10) .navigationTitle("Mesh Activity Log") diff --git a/MeshtasticClientTests/MeshtasticClientTests.swift b/MeshtasticClientTests/MeshtasticClientTests.swift index e08fe973..0e034ada 100644 --- a/MeshtasticClientTests/MeshtasticClientTests.swift +++ b/MeshtasticClientTests/MeshtasticClientTests.swift @@ -6,7 +6,7 @@ // import XCTest -//@testable import MeshtasticClient +// @testable import MeshtasticClient class MeshtasticClientTests: XCTestCase { diff --git a/README.md b/README.md index 35705514..fc64ac8b 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # Meshtastic Client + +- Requires SwiftLint - see https://github.com/realm/SwiftLint