From d7f5509b276d10c6f11f4942c371e64a39eeb43e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 25 Dec 2021 23:48:12 -0800 Subject: [PATCH] V 1.37 New maps, ran SwiftLint -fix --- Meshtastic Client.xcodeproj/project.pbxproj | 8 +- MeshtasticClient/Helpers/BLEManager.swift | 333 +++++++------- .../Helpers/BluetoothManager.swift | 8 +- MeshtasticClient/Helpers/MeshLogger.swift | 2 +- MeshtasticClient/Helpers/Preferences.swift | 12 +- MeshtasticClient/MeshtasticClientApp.swift | 10 +- MeshtasticClient/Model/MapLocation.swift | 2 +- .../Persistence/Persistence.swift | 6 +- .../Persistence/PositionEntityExtension.swift | 10 +- .../Views/Bluetooth/Connect.swift | 10 +- MeshtasticClient/Views/ContentView.swift | 28 +- .../Views/Helpers/ConnectedDevice.swift | 1 - .../Views/Helpers/MessageBubble.swift | 2 +- .../Map/Custom/PositionAnnotationView.swift | 17 +- MeshtasticClient/Views/Map/MapView.swift | 63 ++- .../Views/Messages/Contacts.swift | 174 ++++---- .../Views/Messages/Messages.swift | 14 +- .../Views/Messages/UserMessageList.swift | 54 ++- MeshtasticClient/Views/Nodes/NodeDetail.swift | 422 +++++++++--------- MeshtasticClient/Views/Nodes/NodeList.swift | 57 ++- MeshtasticClient/Views/Nodes/NodeMap.swift | 54 +-- MeshtasticClient/Views/Nodes/NodeRow.swift | 12 +- .../Views/Settings/AppSettings.swift | 30 +- 23 files changed, 665 insertions(+), 664 deletions(-) diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index d41a44d7..7a116f72 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911D2708C65400501B7E /* AppSettings.swift */; }; DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD5394FB276993AD00AD86B1 /* SwiftProtobuf */; }; DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */; }; - DD539500276C452400AD86B1 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5394FF276C452400AD86B1 /* Preferences.swift */; }; DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD539501276DAA6A00AD86B1 /* MapLocation.swift */; }; DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; }; DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; }; @@ -83,7 +82,6 @@ DD47E3DC26F390A000029299 /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; - DD5394FF276C452400AD86B1 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; @@ -344,7 +342,6 @@ DDAF8C6D26ED19040058C060 /* Extensions.swift */, DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */, DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */, - DD5394FF276C452400AD86B1 /* Preferences.swift */, ); path = Helpers; sourceTree = ""; @@ -541,7 +538,6 @@ DD47E3D626F17ED900029299 /* CircleText.swift in Sources */, DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */, DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */, - DD539500276C452400AD86B1 /* Preferences.swift in Sources */, C9483F6D2773017500998F6B /* MapView.swift in Sources */, DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */, DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, @@ -723,7 +719,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -750,7 +746,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index 0f2ba231..5da4139f 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -9,7 +9,7 @@ import SwiftUI class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate { static let shared = BLEManager() - + private static var documentsFolder: URL { do { return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) @@ -17,11 +17,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph fatalError("Can't find documents directory.") } } - + var context: NSManagedObjectContext? - + private var centralManager: CBCentralManager! - + @Published var peripherals = [Peripheral]() @Published var connectedPeripheral: Peripheral! @@ -47,10 +47,10 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph 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") - + private var meshLoggingEnabled: Bool = true let meshLog = documentsFolder.appendingPathComponent("meshlog.txt") - + // MARK: init BLEManager override init() { @@ -58,7 +58,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.lastConnectedPeripheral = "" self.lastConnectionError = "" super.init() - //let bleQueue: DispatchQueue = DispatchQueue(label: "CentralManager") + // let bleQueue: DispatchQueue = DispatchQueue(label: "CentralManager") centralManager = CBCentralManager(delegate: self, queue: nil) } @@ -79,10 +79,10 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph func startScanning() { if isSwitchedOn { - + centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID], options: nil) self.isScanning = self.centralManager.isScanning - + print("✅ Scanning Started") } } @@ -94,7 +94,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.centralManager.stopScan() self.isScanning = self.centralManager.isScanning - + print("🛑 Stopped Scanning") } } @@ -160,7 +160,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) } - //Called each time a peripheral is discovered + // Called each time a peripheral is discovered func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { var peripheralName: String = peripheral.name ?? "Unknown" @@ -178,7 +178,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph peripherals.remove(at: peripheralIndex!) peripherals.append(newPeripheral) print("â„šī¸ Updating peripheral: \(peripheralName)") - + } else { if newPeripheral.peripheral.state != CBPeripheralState.connected { @@ -202,15 +202,15 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Map the peripheral to the connectedNode and connectedPeripheral ObservedObjects connectedPeripheral = peripherals.filter({ $0.peripheral.identifier == peripheral.identifier }).first connectedPeripheral.peripheral.delegate = self - - let fetchConnectedPeripheralRequest:NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + + let fetchConnectedPeripheralRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchConnectedPeripheralRequest.predicate = NSPredicate(format: "bleName MATCHES %@", String(peripheral.name ?? "???")) - + do { let fetchedNode = try context?.fetch(fetchConnectedPeripheralRequest) as! [NodeInfoEntity] - + if fetchedNode.count == 1 { - + connectedPeripheral.num = fetchedNode[0].user!.num connectedPeripheral.shortName = fetchedNode[0].user!.shortName! connectedPeripheral.longName = fetchedNode[0].user!.longName! @@ -221,7 +221,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("đŸ’Ĩ Fetch NodeInfo Failed") if meshLoggingEnabled { MeshLogger.log("đŸ’Ĩ Fetch NodeInfo Failed") } } - + lastConnectedPeripheral = peripheral.identifier.uuidString // Discover Services @@ -265,19 +265,19 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work. lastConnectionError = e.localizedDescription - + 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 // 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." - + if meshLoggingEnabled { MeshLogger.log("đŸšĢ BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(lastConnectionError)") } } else { lastConnectionError = e.localizedDescription - + 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)") } } @@ -290,7 +290,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } - //MARK: Peripheral Services functions + // MARK: Peripheral Services functions func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let e = error { @@ -311,7 +311,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } - //MARK: Discover Characteristics Event + // MARK: Discover Characteristics Event func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { if let e = error { @@ -356,15 +356,15 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("â„šī¸ didUpdateNotificationStateFor char: \(characteristic.uuid.uuidString) \(characteristic.isNotifying)") if meshLoggingEnabled { MeshLogger.log("â„šī¸ didUpdateNotificationStateFor char: \(characteristic.uuid.uuidString) \(characteristic.isNotifying)") } - + if let errorText = error?.localizedDescription { print("đŸšĢ didUpdateNotificationStateFor error: \(errorText)") } } - //MARK: Data Read / Update Characteristic Event - //TODO: Convert to CoreData - //FIXME: Remove broken JSON file data layer implementation + // MARK: Data Read / Update Characteristic Event + // TODO: Convert to CoreData + // FIXME: Remove broken JSON file data layer implementation func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let e = error { @@ -379,7 +379,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph let bigEndianUInt32 = characteristicValue.withUnsafeBytes { $0.load(as: UInt32.self) } let returnValue = CFByteOrderGetCurrent() == CFByteOrder(CFByteOrderLittleEndian.rawValue) ? UInt32(bigEndian: bigEndianUInt32) : bigEndianUInt32 - //print(returnValue) + // print(returnValue) case FROMRADIO_UUID: if characteristic.value == nil || characteristic.value!.isEmpty { @@ -390,15 +390,15 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph var decodedInfo = FromRadio() decodedInfo = try! FromRadio(serializedData: characteristic.value!) - //print("Print DecodedInfo") - //print(decodedInfo) + // print("Print DecodedInfo") + // print(decodedInfo) // MyInfo Data if decodedInfo.myInfo.myNodeNum != 0 { - - let fetchMyInfoRequest:NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") + + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.myInfo.myNodeNum)) - + do { let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity] // Not Found Insert @@ -414,9 +414,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph connectedPeripheral.num = myInfo.myNodeNum connectedPeripheral.firmwareVersion = myInfo.firmwareVersion ?? "Unknown" - } - else { - + } else { + fetchedMyInfo[0].myNodeNum = Int64(decodedInfo.myInfo.myNodeNum) fetchedMyInfo[0].hasGps = decodedInfo.myInfo.hasGps_p fetchedMyInfo[0].numBands = Int32(bitPattern: decodedInfo.myInfo.numBands) @@ -426,37 +425,37 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph fetchedMyInfo[0].maxChannels = Int32(bitPattern: decodedInfo.myInfo.maxChannels) } do { - + try context!.save() print("💾 Saved a myInfo for \(decodedInfo.myInfo.myNodeNum)") if meshLoggingEnabled { MeshLogger.log("💾 Saved a myInfo for \(peripheral.name ?? String(decodedInfo.myInfo.myNodeNum))") } - + } catch { - + context!.rollback() - + let nsError = error as NSError print("đŸ’Ĩ Error Saving CoreData MyInfoEntity: \(nsError)") } - + } catch { - + print("đŸ’Ĩ Fetch MyInfo Error") } } // NodeInfo Data if decodedInfo.nodeInfo.num != 0 { - - let fetchNodeRequest:NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + + let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.nodeInfo.num)) - + do { - + let fetchedNode = try context?.fetch(fetchNodeRequest) as! [NodeInfoEntity] // Not Found Insert - if fetchedNode.isEmpty && decodedInfo.nodeInfo.hasUser { - + if fetchedNode.isEmpty && decodedInfo.nodeInfo.hasUser { + let newNode = NodeInfoEntity(context: context!) newNode.id = Int64(decodedInfo.nodeInfo.num) newNode.num = Int64(decodedInfo.nodeInfo.num) @@ -464,19 +463,19 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.lastHeard))) } newNode.snr = decodedInfo.nodeInfo.snr - + if self.connectedPeripheral != nil && self.connectedPeripheral.num == newNode.id { - + newNode.bleName = self.connectedPeripheral.peripheral.name if decodedInfo.nodeInfo.hasUser { - + connectedPeripheral.name = decodedInfo.nodeInfo.user.longName connectedPeripheral.longName = decodedInfo.nodeInfo.user.longName connectedPeripheral.shortName = decodedInfo.nodeInfo.user.shortName connectedPeripheral.num = Int64(decodedInfo.nodeInfo.num) } } - + if decodedInfo.nodeInfo.hasUser { let newUser = UserEntity(context: context!) @@ -489,42 +488,42 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newUser.team = (String(describing: decodedInfo.nodeInfo.user.team)) newNode.user = newUser } - + let position = PositionEntity(context: context!) position.latitudeI = decodedInfo.nodeInfo.position.latitudeI position.longitudeI = decodedInfo.nodeInfo.position.longitudeI position.altitude = decodedInfo.nodeInfo.position.altitude - + position.batteryLevel = decodedInfo.nodeInfo.position.batteryLevel position.time = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.position.time))) var newPostions = [PositionEntity]() newPostions.append(position) - newNode.positions? = NSOrderedSet(array : newPostions) - + newNode.positions? = NSOrderedSet(array: newPostions) + // Look for a MyInfo - let fetchMyInfoRequest:NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.nodeInfo.num)) - + do { - + let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity] if fetchedMyInfo.count > 0 { newNode.myInfo = fetchedMyInfo[0] - + } - + } catch { print("đŸ’Ĩ Fetch MyInfo Error") } - - } else if decodedInfo.nodeInfo.hasUser { - + + } else if decodedInfo.nodeInfo.hasUser { + fetchedNode[0].id = Int64(decodedInfo.nodeInfo.num) fetchedNode[0].num = Int64(decodedInfo.nodeInfo.num) fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.lastHeard))) fetchedNode[0].snr = decodedInfo.nodeInfo.snr - + if decodedInfo.nodeInfo.hasUser { fetchedNode[0].user!.userId = decodedInfo.nodeInfo.user.id @@ -533,68 +532,68 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph fetchedNode[0].user!.hwModel = String(describing: decodedInfo.nodeInfo.user.hwModel).uppercased() fetchedNode[0].user!.team = (String(describing: decodedInfo.nodeInfo.user.team)) } - + let position = PositionEntity(context: context!) position.latitudeI = decodedInfo.nodeInfo.position.latitudeI position.longitudeI = decodedInfo.nodeInfo.position.longitudeI position.altitude = decodedInfo.nodeInfo.position.altitude position.batteryLevel = decodedInfo.nodeInfo.position.batteryLevel position.time = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.position.time))) - + let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet mutablePositions.add(position) - + if position.coordinate == nil { var newPostions = [PositionEntity]() newPostions.append(position) - fetchedNode[0].positions? = NSOrderedSet(array : newPostions) - + fetchedNode[0].positions? = NSOrderedSet(array: newPostions) + } else { - + fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet } - + // Look for a MyInfo - let fetchMyInfoRequest:NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.nodeInfo.num)) - + do { - + let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity] if fetchedMyInfo.count > 0 { - + fetchedNode[0].myInfo = fetchedMyInfo[0] } - + } catch { print("đŸ’Ĩ Fetch MyInfo Error") } } do { - + try context!.save() print("💾 Saved a nodeInfo for \(decodedInfo.nodeInfo.num)") - + } catch { - + context!.rollback() - + let nsError = error as NSError print("đŸ’Ĩ Error Saving CoreData NodeInfoEntity: \(nsError)") } - + } catch { - + print("đŸ’Ĩ Fetch NodeInfoEntity Error") } - + if decodedInfo.nodeInfo.hasUser { - + print("💾 BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") if meshLoggingEnabled { MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") } - + } else { - + print("💾 BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.num)") if meshLoggingEnabled { MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.num)") } } @@ -603,7 +602,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if decodedInfo.packet.id != 0 { do { - + // Text Message App - Primary Broadcast Channel if decodedInfo.packet.decoded.portnum == PortNum.textMessageApp { @@ -611,22 +610,22 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("đŸ’Ŧ BLE FROMRADIO received for text message app \(messageText)") if meshLoggingEnabled { MeshLogger.log("đŸ’Ŧ BLE FROMRADIO received for text message app \(messageText)") } - - let messageUsers:NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") - messageUsers.predicate = NSPredicate(format:"num IN %@", [decodedInfo.packet.to, decodedInfo.packet.from]) - + + let messageUsers: NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") + messageUsers.predicate = NSPredicate(format: "num IN %@", [decodedInfo.packet.to, decodedInfo.packet.from]) + do { - + let fetchedUsers = try context?.fetch(messageUsers) as! [UserEntity] - + let newMessage = MessageEntity(context: context!) newMessage.messageId = Int64(decodedInfo.packet.id) newMessage.messageTimestamp = Int32(bitPattern: decodedInfo.packet.rxTime) newMessage.receivedACK = false newMessage.direction = "IN" - + if decodedInfo.packet.to == broadcastNodeNum && fetchedUsers.count == 1 { - + // Save the broadcast user if it does not exist let bcu: UserEntity = UserEntity(context: context!) bcu.shortName = "ALL" @@ -635,21 +634,21 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph bcu.num = Int64(broadcastNodeNum) bcu.userId = "BROADCASTNODE" newMessage.toUser = bcu - + } else { newMessage.toUser = fetchedUsers.first(where: { $0.num == decodedInfo.packet.to }) } - + newMessage.fromUser = fetchedUsers.first(where: { $0.num == decodedInfo.packet.from }) newMessage.messagePayload = messageText - + do { - + try context!.save() print("💾 Saved a new message for \(decodedInfo.packet.id)") if meshLoggingEnabled { MeshLogger.log("💾 Saved a new message for \(decodedInfo.packet.id)") } - + // Create an iOS Notification for the received message and schedule it immediately let manager = LocalNotificationManager() @@ -662,27 +661,27 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph ] manager.schedule() if meshLoggingEnabled { MeshLogger.log("đŸ’Ŧ iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown") \(messageText)") } - + } catch { - + context!.rollback() - + let nsError = error as NSError print("đŸ’Ĩ Failed to save new MessageEntity \(nsError)") } - + } catch { - + print("đŸ’Ĩ Fetch Message To and From Users Error") } } } else if decodedInfo.packet.decoded.portnum == PortNum.nodeinfoApp { - - let fetchNodeInfoAppRequest:NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + + let fetchNodeInfoAppRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.packet.from)) - + do { - + let fetchedNode = try context?.fetch(fetchNodeInfoAppRequest) as! [NodeInfoEntity] if fetchedNode.count == 1 { @@ -690,91 +689,89 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph fetchedNode[0].num = Int64(decodedInfo.packet.from) fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.packet.rxTime))) fetchedNode[0].snr = decodedInfo.packet.rxSnr - - } - else { + + } else { return } do { - + try context!.save() - + if meshLoggingEnabled { MeshLogger.log("💾 Updated NodeInfo SNR and Time from Node Info App Packet For: \(Int64(decodedInfo.nodeInfo.num))")} print("💾 Updated NodeInfo SNR and Time from Packet For: \(fetchedNode[0].num)") - + } catch { - + context!.rollback() - + let nsError = error as NSError print("đŸ’Ĩ Error Saving NodeInfoEntity from NODEINFO_APP \(nsError)") - + } } catch { - + print("đŸ’Ĩ Error Fetching NodeInfoEntity for NODEINFO_APP") } - + } else if decodedInfo.packet.decoded.portnum == PortNum.positionApp { - let fetchNodePositionRequest:NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + let fetchNodePositionRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.packet.from)) - + do { - + let fetchedNode = try context?.fetch(fetchNodePositionRequest) as! [NodeInfoEntity] - + if fetchedNode.count == 1 { fetchedNode[0].id = Int64(decodedInfo.packet.from) fetchedNode[0].num = Int64(decodedInfo.packet.from) - if(decodedInfo.packet.rxTime == 0) { - + if decodedInfo.packet.rxTime == 0 { + fetchedNode[0].lastHeard = Date() - + } else { - + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.packet.rxTime))) - + } fetchedNode[0].snr = decodedInfo.packet.rxSnr - - } - else { + + } else { return } do { - + try context!.save() - + if meshLoggingEnabled { MeshLogger.log("💾 Updated NodeInfo SNR and Time from Node Info App Packet For: \(fetchedNode[0].num)") } print("💾 Updated NodeInfo SNR and Time from Position Packet For: \(fetchedNode[0].num)") - + } catch { - + context!.rollback() - + let nsError = error as NSError print("đŸ’Ĩ Error Saving NodeInfoEntity from NODEINFO_APP \(nsError)") } } catch { - + print("đŸ’Ĩ Error Fetching NodeInfoEntity for NODEINFO_APP") } - + } else if decodedInfo.packet.decoded.portnum == PortNum.adminApp { if meshLoggingEnabled { MeshLogger.log("🚨 MESH PACKET received for Admin App UNHANDLED \(try decodedInfo.packet.jsonString())") } print("🚨 MESH PACKET received for Admin App UNHANDLED \(try decodedInfo.packet.jsonString())") - + } else if decodedInfo.packet.decoded.portnum == PortNum.routingApp { if meshLoggingEnabled { MeshLogger.log("🚨 MESH PACKET received for Routing App UNHANDLED \(try decodedInfo.packet.jsonString())") } print("🚨 MESH PACKET received for Routing App UNHANDLED \(try decodedInfo.packet.jsonString())") } else { - + if meshLoggingEnabled { MeshLogger.log("🚨 MESH PACKET received for Other App UNHANDLED \(try decodedInfo.packet.jsonString())") } print("🚨 MESH PACKET received for Other App UNHANDLED \(try decodedInfo.packet.jsonString())") @@ -787,7 +784,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } 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 @@ -825,32 +822,31 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } print("đŸšĢ Message Send Failed, not properly connected to \(lastConnectedPeripheral)") if meshLoggingEnabled { MeshLogger.log("đŸšĢ Message Send Failed, not properly connected to \(lastConnectedPeripheral)") } - + success = false } else if message.count < 1 { - + // Don't send an empty message print("đŸšĢ Don't Send an Empty Message") success = false - + } else { - - let fromUserNum:Int64 = self.connectedPeripheral.num - - let messageUsers:NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") - messageUsers.predicate = NSPredicate(format:"num IN %@", [fromUserNum, Int64(toUserNum)]) - + + let fromUserNum: Int64 = self.connectedPeripheral.num + + let messageUsers: NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") + messageUsers.predicate = NSPredicate(format: "num IN %@", [fromUserNum, Int64(toUserNum)]) + do { - + let fetchedUsers = try context?.fetch(messageUsers) as! [UserEntity] - + if fetchedUsers.isEmpty { - + print("đŸšĢ Message Users Not Found, Fail") success = false - } - else if fetchedUsers.count >= 1 { - + } else if fetchedUsers.count >= 1 { + let newMessage = MessageEntity(context: context!) newMessage.messageId = nextSentMessageId newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) @@ -858,7 +854,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newMessage.direction = "IN" newMessage.toUser = fetchedUsers.first(where: { $0.num == toUserNum }) if newMessage.toUser == nil { - + let bcu: UserEntity = UserEntity(context: context!) bcu.shortName = "ALL" bcu.longName = "Primary - Broadcast" @@ -869,7 +865,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } newMessage.fromUser = fetchedUsers.first(where: { $0.num == fromUserNum }) newMessage.messagePayload = message - + let dataType = PortNum.textMessageApp let payloadData: Data = message.data(using: String.Encoding.utf8)! @@ -888,39 +884,38 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - + if meshLoggingEnabled { MeshLogger.log("📲 New message sent to \(newMessage.toUser?.longName! ?? "Unknown")") } print("📲 New message sent to \(newMessage.toUser?.longName! ?? "Unknown")") - + if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) do { - + try context!.save() print("💾 Saved a new sent message from \(newMessage.fromUser?.longName! ?? "Unknown")") if meshLoggingEnabled { MeshLogger.log("💾 Saved a new sent message from \(connectedPeripheral.num)") } success = true nextSentMessageId+=1 - + } catch { - + context!.rollback() - + let nsError = error as NSError print("đŸšĢ Unresolved error \(nsError)") } } } - + } catch { - + } } return success } } - func bytes2String(_ array: [UInt8]) -> String { return String(data: Data(bytes: array, count: array.count), encoding: .utf8) ?? "" } diff --git a/MeshtasticClient/Helpers/BluetoothManager.swift b/MeshtasticClient/Helpers/BluetoothManager.swift index 06964e62..dc86b613 100644 --- a/MeshtasticClient/Helpers/BluetoothManager.swift +++ b/MeshtasticClient/Helpers/BluetoothManager.swift @@ -9,16 +9,16 @@ import Combine import CoreBluetooth final class BluetoothManager: NSObject { - + private var centralManager: CBCentralManager! - + var stateSubject: PassthroughSubject = .init() var peripheralSubject: PassthroughSubject = .init() - + func start() { centralManager = .init(delegate: self, queue: .main) } - + func connect(_ peripheral: CBPeripheral) { centralManager.stopScan() peripheral.delegate = self diff --git a/MeshtasticClient/Helpers/MeshLogger.swift b/MeshtasticClient/Helpers/MeshLogger.swift index 76d7ef8f..2dc383d6 100644 --- a/MeshtasticClient/Helpers/MeshLogger.swift +++ b/MeshtasticClient/Helpers/MeshLogger.swift @@ -1,7 +1,7 @@ import Foundation class MeshLogger { - + static var logFile: URL? { guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil } let fileName = "mesh.log" diff --git a/MeshtasticClient/Helpers/Preferences.swift b/MeshtasticClient/Helpers/Preferences.swift index 3ff67222..93ff482a 100644 --- a/MeshtasticClient/Helpers/Preferences.swift +++ b/MeshtasticClient/Helpers/Preferences.swift @@ -4,12 +4,12 @@ // // Created by Garth Vander Houwen on 12/16/21. // -//import Foundation -//import Combine -//import SwiftUI +// import Foundation +// import Combine +// import SwiftUI // -//class Prefs -//{ +// class Prefs +// { // private let defaults = UserDefaults.standard // // private let keyIntExample = "intExample" @@ -30,4 +30,4 @@ // // return Static.instance // } -//} +// } diff --git a/MeshtasticClient/MeshtasticClientApp.swift b/MeshtasticClient/MeshtasticClientApp.swift index 8bf762bc..f7222a60 100644 --- a/MeshtasticClient/MeshtasticClientApp.swift +++ b/MeshtasticClient/MeshtasticClientApp.swift @@ -3,9 +3,9 @@ import CoreData @main struct MeshtasticClientApp: App { - + let persistenceController = PersistenceController.shared - + @ObservedObject private var bleManager: BLEManager = BLEManager.shared @ObservedObject private var userSettings: UserSettings = UserSettings() @@ -23,12 +23,12 @@ struct MeshtasticClientApp: App { case .background: print("â„šī¸ Scene is in the background") do { - + try persistenceController.container.viewContext.save() print("💾 Saved CoreData ViewContext when the app went to the background.") - + } catch { - + print("đŸ’Ĩ Failed to save viewContext when the app goes to the background.") } case .inactive: diff --git a/MeshtasticClient/Model/MapLocation.swift b/MeshtasticClient/Model/MapLocation.swift index 8e8745f5..58d2c770 100644 --- a/MeshtasticClient/Model/MapLocation.swift +++ b/MeshtasticClient/Model/MapLocation.swift @@ -8,7 +8,7 @@ import Foundation import MapKit struct MapLocation: Identifiable { - + let id = UUID() let name: String let coordinate: CLLocationCoordinate2D diff --git a/MeshtasticClient/Persistence/Persistence.swift b/MeshtasticClient/Persistence/Persistence.swift index 7f7a3a37..4b5cee4b 100644 --- a/MeshtasticClient/Persistence/Persistence.swift +++ b/MeshtasticClient/Persistence/Persistence.swift @@ -8,7 +8,7 @@ import CoreData class PersistenceController { - + static let shared = PersistenceController() static var preview: PersistenceController = { @@ -36,10 +36,10 @@ class PersistenceController { if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") } - container.loadPersistentStores(completionHandler: { (storeDescription, error) in + container.loadPersistentStores(completionHandler: { (_, error) in // Merge policy that favors in memory data over data in the db self.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - + if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. diff --git a/MeshtasticClient/Persistence/PositionEntityExtension.swift b/MeshtasticClient/Persistence/PositionEntityExtension.swift index 250241c1..036e118b 100644 --- a/MeshtasticClient/Persistence/PositionEntityExtension.swift +++ b/MeshtasticClient/Persistence/PositionEntityExtension.swift @@ -4,7 +4,7 @@ import MapKit import SwiftUI extension PositionEntity { - + var latitude: Double? { let d = Double(latitudeI) @@ -13,7 +13,7 @@ extension PositionEntity { } return d / 1e7 } - + var longitude: Double? { let d = Double(longitudeI) @@ -22,7 +22,7 @@ extension PositionEntity { } return d / 1e7 } - + var coordinate: CLLocationCoordinate2D? { if latitudeI != 0 && longitudeI != 0 { let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!) @@ -32,7 +32,7 @@ extension PositionEntity { return nil } } - + var annotaton: MKPointAnnotation { let pointAnn = MKPointAnnotation() if coordinate != nil { @@ -41,5 +41,5 @@ extension PositionEntity { } return pointAnn } - + } diff --git a/MeshtasticClient/Views/Bluetooth/Connect.swift b/MeshtasticClient/Views/Bluetooth/Connect.swift index 6dfbed6d..897f8f21 100644 --- a/MeshtasticClient/Views/Bluetooth/Connect.swift +++ b/MeshtasticClient/Views/Bluetooth/Connect.swift @@ -53,7 +53,7 @@ struct Connect: View { if bleManager.connectedPeripheral != nil { Text(bleManager.connectedPeripheral.longName).font(.title2) - + } else { Text(String(bleManager.connectedPeripheral.peripheral.name ?? "Unknown")).font(.title2) @@ -81,12 +81,12 @@ struct Connect: View { if value { if bleManager.connectedPeripheral != nil { - + let deviceName = (bleManager.connectedPeripheral.peripheral.name ?? "") userSettings.preferredPeripheralName = deviceName - + } else { - + userSettings.preferredPeripheralName = bleManager.connectedPeripheral.longName } @@ -202,7 +202,7 @@ struct Connect: View { .navigationBarItems(trailing: ZStack { - + ConnectedDevice( bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, diff --git a/MeshtasticClient/Views/ContentView.swift b/MeshtasticClient/Views/ContentView.swift index ca20500d..db8610ae 100644 --- a/MeshtasticClient/Views/ContentView.swift +++ b/MeshtasticClient/Views/ContentView.swift @@ -19,21 +19,21 @@ struct ContentView: View { var body: some View { TabView(selection: $selection) { -// Contacts() -// .tabItem { -// Label("Contacts", systemImage: "person.crop.circle") -// .symbolRenderingMode(.hierarchical) -// .symbolVariant(.none) -// -// } -// .tag(Tab.contacts) - Channels() - .tabItem { - Label("Messages", systemImage: "text.bubble") + Contacts() + .tabItem { + Label("Messages", systemImage: "text.bubble") .symbolRenderingMode(.hierarchical) - .symbolVariant(.none) - } - .tag(Tab.messages) + .symbolVariant(.none) + + } + .tag(Tab.contacts) +// Channels() +// .tabItem { +// Label("Messages", systemImage: "text.bubble") +// .symbolRenderingMode(.hierarchical) +// .symbolVariant(.none) +// } +// .tag(Tab.messages) Connect() .tabItem { Label("Bluetooth", systemImage: "dot.radiowaves.left.and.right") diff --git a/MeshtasticClient/Views/Helpers/ConnectedDevice.swift b/MeshtasticClient/Views/Helpers/ConnectedDevice.swift index 680fd96f..4d73f9de 100644 --- a/MeshtasticClient/Views/Helpers/ConnectedDevice.swift +++ b/MeshtasticClient/Views/Helpers/ConnectedDevice.swift @@ -27,7 +27,6 @@ struct ConnectedDevice: View { .imageScale(.medium) .foregroundColor(.red) .symbolRenderingMode(.hierarchical) - Text("Disconnected").font(.subheadline).foregroundColor(.gray) } } else { diff --git a/MeshtasticClient/Views/Helpers/MessageBubble.swift b/MeshtasticClient/Views/Helpers/MessageBubble.swift index 3950d1b1..8a2352b9 100644 --- a/MeshtasticClient/Views/Helpers/MessageBubble.swift +++ b/MeshtasticClient/Views/Helpers/MessageBubble.swift @@ -43,7 +43,7 @@ struct MessageBubble: View { Spacer() } .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")) { print("OK button tapped") diff --git a/MeshtasticClient/Views/Map/Custom/PositionAnnotationView.swift b/MeshtasticClient/Views/Map/Custom/PositionAnnotationView.swift index ea2005dd..8e97bb98 100644 --- a/MeshtasticClient/Views/Map/Custom/PositionAnnotationView.swift +++ b/MeshtasticClient/Views/Map/Custom/PositionAnnotationView.swift @@ -8,19 +8,19 @@ import UIKit import MapKit -//a simple circle annotation, with a string in it +// a simple circle annotation, with a string in it class PositionAnnotation: NSObject, MKAnnotation { - + // This property must be key-value observable, which the `@objc dynamic` attributes provide. @objc dynamic var coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0) - + // Required if you set the annotation view's `canShowCallout` property to `true` - //this string fills the callout label when you tap an annotation + // this string fills the callout label when you tap an annotation var title: String? - - //the text to appear inside the little circle + + // the text to appear inside the little circle var shortName: String? - + } class PositionAnnotationView: MKAnnotationView { @@ -55,10 +55,9 @@ class PositionAnnotationView: MKAnnotationView { let circleRect = CGRect(x: 1, y: 1, width: 30, height: 30) context.setFillColor(CGColor(red: 0, green: 0.5, blue: 1.0, alpha: 1.0)) - + context.fillEllipse(in: circleRect) } - } diff --git a/MeshtasticClient/Views/Map/MapView.swift b/MeshtasticClient/Views/Map/MapView.swift index ebdd6742..94b2e428 100644 --- a/MeshtasticClient/Views/Map/MapView.swift +++ b/MeshtasticClient/Views/Map/MapView.swift @@ -11,43 +11,43 @@ import MapKit import SwiftUI import CoreData -//wrap a MKMapView into something we can use in SwiftUI +// wrap a MKMapView into something we can use in SwiftUI struct MapView: UIViewRepresentable { - + var nodes: FetchedResults - - let mapViewDelegate = MapViewDelegate() - - //observe changes to the key in UserDefaults + + weak var mapViewDelegate = MapViewDelegate() + + // observe changes to the key in UserDefaults @AppStorage("meshMapType") var type: String = "hybrid" - + func makeUIView(context: Context) -> MKMapView { - + let map = MKMapView(frame: .zero) - + map.userTrackingMode = .follow - + let region = MKCoordinateRegion( center: map.centerCoordinate, latitudinalMeters: CLLocationDistance(exactly: 500)!, longitudinalMeters: CLLocationDistance(exactly: 500)!) map.setRegion(map.regionThatFits(region), animated: false) - - //self.updateMapType(map) - + + // self.updateMapType(map) + map.register(PositionAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(PositionAnnotationView.self)) - + return map } func updateUIView(_ view: MKMapView, context: Context) { view.delegate = mapViewDelegate // (1) This should be set in makeUIView, but it is getting reset to `nil` view.translatesAutoresizingMaskIntoConstraints = false // (2) In the absence of this, we get constraints error on rotation; and again, it seems one should do this in makeUIView, but has to be here - + self.updateMapType(view) - + self.showNodePositions(to: view) } - + func updateMapType(_ map: MKMapView) { - + switch self.type { case "satellite": map.mapType = .satellite @@ -67,20 +67,20 @@ struct MapView: UIViewRepresentable { private extension MapView { func showNodePositions(to view: MKMapView) { - - //clear any existing annotations + + // clear any existing annotations if !view.annotations.isEmpty { view.removeAnnotations(view.annotations) } - + for node in self.nodes { - //try and get the last position + // try and get the last position if (node.positions?.count ?? 0) > 0 && (node.positions!.lastObject as! PositionEntity).coordinate != nil { let annotation = PositionAnnotation() annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate! annotation.title = node.user?.longName ?? "Unknown" annotation.shortName = node.user?.shortName?.uppercased() ?? "???" - + view.addAnnotation(annotation) } } @@ -88,33 +88,32 @@ private extension MapView { } class MapViewDelegate: NSObject, MKMapViewDelegate { - + func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { guard !annotation.isKind(of: MKUserLocation.self) else { // Make a fast exit if the annotation is the `MKUserLocation`, as it's not an annotation view we wish to customize. return nil } - + var annotationView: MKAnnotationView? - + if let annotation = annotation as? PositionAnnotation { annotationView = self.setupPositionAnnotationView(for: annotation, on: mapView) } - + return annotationView } - + private func setupPositionAnnotationView(for annotation: PositionAnnotation, on mapView: MKMapView) -> PositionAnnotationView { let identifier = NSStringFromClass(PositionAnnotationView.self) let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? PositionAnnotationView ?? PositionAnnotationView() - + annotationView.name = annotation.shortName ?? "???" - + annotationView.canShowCallout = true - - + return annotationView } } diff --git a/MeshtasticClient/Views/Messages/Contacts.swift b/MeshtasticClient/Views/Messages/Contacts.swift index fbb05da6..56e5dc9d 100644 --- a/MeshtasticClient/Views/Messages/Contacts.swift +++ b/MeshtasticClient/Views/Messages/Contacts.swift @@ -8,112 +8,120 @@ import SwiftUI struct Contacts: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - + @FetchRequest( sortDescriptors: [NSSortDescriptor(key: "longName", ascending: true)], animation: .default) - + private var users: FetchedResults - + var body: some View { - + NavigationView { - + List(users) { user in - + if user.receivedMessages?.count ?? 0 > 0 { - + let mostRecent = user.receivedMessages?.lastObject as! MessageEntity let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64(mostRecent.messageTimestamp))) let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 - - HStack { - - VStack { - - CircleText(text: user.shortName ?? "???", color: Color.blue) - } - .padding([.leading, .trailing]) - - VStack { - - HStack { - VStack { - - Text(user.longName ?? "Unknown").font(.headline).fixedSize() - } - - VStack { - - if lastMessageDay == currentDay { - - Text(lastMessageTime, style: .time ) - .font(.caption) - .foregroundColor(.gray) - - } else if ( lastMessageDay == (currentDay - 1)) { - - Text("Yesterday") - .font(.callout) - .foregroundColor(.gray) - - } else if ( lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) ) { - - Text(lastMessageTime, style: .date) - - } else { - - Text(lastMessageTime, style: .date) + NavigationLink(destination: UserMessageList(user: user)) { + + HStack { + + VStack { + + CircleText(text: user.shortName ?? "???", color: Color.blue) + } + .padding([.leading, .trailing]) + + VStack { + + HStack { + + VStack { + + Text(user.longName ?? "Unknown").font(.headline).fixedSize() } - }.frame(maxWidth: .infinity, alignment: .trailing) - } - .listRowSeparator(.hidden).frame(height: 5) - - HStack (alignment: .top) { - Text(mostRecent.messagePayload ?? "EMPTY MESSSAGE") - .frame(height: 60) - .truncationMode(.tail) - .foregroundColor(Color.gray) - .frame(maxWidth: .infinity, alignment: .leading) - } - }.padding(.top, 15) - } - } else { - - HStack { - - VStack { - - CircleText(text: user.shortName ?? "???", color: Color.blue) - } - .padding(.trailing) - - VStack { - - HStack{ - VStack { - - Text(user.longName ?? "Unknown").font(.headline).fixedSize() + VStack { + + if lastMessageDay == currentDay { + + Text(lastMessageTime, style: .time ) + .font(.caption) + .foregroundColor(.gray) + + } else if lastMessageDay == (currentDay - 1) { + + Text("Yesterday") + .font(.callout) + .foregroundColor(.gray) + + } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { + + Text(lastMessageTime, style: .date) + + } else { + + Text(lastMessageTime, style: .date) + } + }.frame(maxWidth: .infinity, alignment: .trailing) } - - VStack { - Text(" ") + .listRowSeparator(.hidden).frame(height: 5) + + HStack(alignment: .top) { + Text(mostRecent.messagePayload ?? "EMPTY MESSSAGE") + .frame(height: 60) + .truncationMode(.tail) + .foregroundColor(Color.gray) + .frame(maxWidth: .infinity, alignment: .leading) } - .frame(maxWidth: .infinity, alignment: .trailing) - } - .listRowSeparator(.hidden).frame(height: 5) + }.padding(.top, 15) } - }.padding() + + } + + } else { + + NavigationLink(destination: UserMessageList(user: user)) { + + HStack { + + VStack { + + CircleText(text: user.shortName ?? "???", color: Color.blue) + } + .padding(.trailing) + + VStack { + + HStack { + + VStack { + + Text(user.longName ?? "Unknown").font(.headline).fixedSize() + } + + VStack { + Text(" ") + } + .frame(maxWidth: .infinity, alignment: .trailing) + } + .listRowSeparator(.hidden).frame(height: 5) + } + }.padding() + } } - //NavigationLink(note.title, destination: NoteEditor(id: note.id)) } .navigationTitle("Contacts") + .navigationBarTitleDisplayMode(.inline) } .listStyle(PlainListStyle()) } diff --git a/MeshtasticClient/Views/Messages/Messages.swift b/MeshtasticClient/Views/Messages/Messages.swift index 990f13a4..f8d9b9dd 100644 --- a/MeshtasticClient/Views/Messages/Messages.swift +++ b/MeshtasticClient/Views/Messages/Messages.swift @@ -8,7 +8,7 @@ struct Messages: View { enum Field: Hashable { case messageText } - + // CoreData @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -57,7 +57,7 @@ struct Messages: View { print("I want to delete message: \(message.messageId)") self.showDeleteMessageAlert = true self.deleteMessageId = message.messageId - + print(deleteMessageId) }) @@ -91,18 +91,18 @@ struct Messages: View { if deleteMessageId > 0 { let message = messages.first(where: { $0.messageId == deleteMessageId }) - + context.delete(message!) do { try context.save() - + deleteMessageId = 0 messageCount = messages.count - + } catch { print("Failed to delete message \(deleteMessageId)") } - + } }, secondaryButton: .cancel() @@ -116,7 +116,7 @@ struct Messages: View { if messageCount > 0 { scrollView.scrollTo(messages[messageCount-1].id, anchor: .bottom) } - + }) } .onReceive(timer) { _ in diff --git a/MeshtasticClient/Views/Messages/UserMessageList.swift b/MeshtasticClient/Views/Messages/UserMessageList.swift index 56411298..245f1973 100644 --- a/MeshtasticClient/Views/Messages/UserMessageList.swift +++ b/MeshtasticClient/Views/Messages/UserMessageList.swift @@ -6,15 +6,55 @@ // import SwiftUI +import CoreData struct UserMessageList: View { - var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - } -} -struct UserMessageList_Previews: PreviewProvider { - static var previews: some View { - UserMessageList() + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + var user: UserEntity + + var body: some View { + + HStack { + + List { + + ScrollViewReader { _ in + + ScrollView { + + if user.receivedMessages != nil && user.receivedMessages!.count > 0 { + + ForEach( user.receivedMessages?.array as! [MessageEntity], id: \.self) { (_: MessageEntity) in + + } + } + } + } + } + } + .navigationViewStyle(.stack) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + + HStack { + + CircleText(text: user.shortName ?? "???", color: .blue).fixedSize() + Text(user.longName ?? "Unknown").foregroundColor(.gray).font(.caption2).fixedSize() + } + } + ToolbarItem(placement: .navigationBarTrailing) { + ZStack { + + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "???") + } + } + } } } diff --git a/MeshtasticClient/Views/Nodes/NodeDetail.swift b/MeshtasticClient/Views/Nodes/NodeDetail.swift index eec2b014..6b6cb28f 100644 --- a/MeshtasticClient/Views/Nodes/NodeDetail.swift +++ b/MeshtasticClient/Views/Nodes/NodeDetail.swift @@ -16,260 +16,262 @@ struct NodeDetail: View { var body: some View { - GeometryReader { bounds in + HStack { - VStack { + GeometryReader { bounds in - if node.positions?.count ?? 0 > 0 { - - let mostRecent = node.positions?.lastObject as! PositionEntity - - if mostRecent.coordinate != nil { + VStack { - let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!) + if node.positions?.count ?? 0 > 0 { - let regionBinding = Binding( - get: { - MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) - }, - set: { _ in } - ) - let annotations = [MapLocation(name: node.user!.shortName ?? "???", coordinate: mostRecent.coordinate!)] + let mostRecent = node.positions?.lastObject as! PositionEntity - Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in - MapAnnotation( - coordinate: location.coordinate, - content: { - CircleText(text: node.user!.shortName ?? "???", color: .accentColor) - } + if mostRecent.coordinate != nil { + + let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!) + + let regionBinding = Binding( + get: { + MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) + }, + set: { _ in } ) + let annotations = [MapLocation(name: node.user!.shortName ?? "???", coordinate: mostRecent.coordinate!)] + + Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in + MapAnnotation( + coordinate: location.coordinate, + content: { + CircleText(text: node.user!.shortName ?? "???", color: .accentColor) + } + ) + } + .frame(idealWidth: bounds.size.width, maxHeight: bounds.size.height / 3) + } else { + + Image(node.user?.hwModel ?? "UNSET") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: bounds.size.width, height: bounds.size.height / 2) } - .frame(idealWidth: bounds.size.width, maxHeight: bounds.size.height / 3) } else { - + Image(node.user?.hwModel ?? "UNSET") .resizable() .aspectRatio(contentMode: .fit) .frame(width: bounds.size.width, height: bounds.size.height / 2) } - } else { - - Image(node.user?.hwModel ?? "UNSET") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: bounds.size.width, height: bounds.size.height / 2) - } - ScrollView { - - if node.lastHeard != nil { + ScrollView { - HStack { + if node.lastHeard != nil { - Image(systemName: "clock.badge.checkmark.fill") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.title3) - } - .padding() - Divider() - } + HStack { - HStack { - - VStack(alignment: .center) { - Text("AKA").font(.title2).fixedSize() - CircleText(text: node.user?.shortName ?? "???", color: .accentColor) - .offset(y: 10) - } - .padding(5) - - Divider() - - VStack { - - Image(node.user!.hwModel ?? "UNSET") - .resizable() - .frame(width: 50, height: 50) - .cornerRadius(5) - - Text(String(node.user!.hwModel ?? "UNSET")) - .font(.callout).fixedSize() - } - .padding(5) - - if node.snr > 0 { - Divider() - VStack(alignment: .center) { - - Image(systemName: "waveform.path") + Image(systemName: "clock.badge.checkmark.fill") .font(.title) .foregroundColor(.accentColor) .symbolRenderingMode(.hierarchical) - Text("SNR").font(.title2).fixedSize() - Text(String(node.snr)) - .font(.title2) - .foregroundColor(.gray) - .fixedSize() + Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.title3) + } + .padding() + Divider() + } + + HStack { + + VStack(alignment: .center) { + Text("AKA").font(.title2).fixedSize() + CircleText(text: node.user?.shortName ?? "???", color: .accentColor) + .offset(y: 10) } .padding(5) - } - - if node.positions!.count > 0 { - - let mostRecent = node.positions?.lastObject as! PositionEntity - + Divider() - - VStack(alignment: .center) { - - BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor) - .padding(.bottom) - if mostRecent.batteryLevel > 0 { - - Text("Battery") - .font(.title2) - .fixedSize() - .textCase(.uppercase) - Text(String(mostRecent.batteryLevel) + "%") + + VStack { + + Image(node.user!.hwModel ?? "UNSET") + .resizable() + .frame(width: 50, height: 50) + .cornerRadius(5) + + Text(String(node.user!.hwModel ?? "UNSET")) + .font(.callout).fixedSize() + } + .padding(5) + + if node.snr > 0 { + Divider() + VStack(alignment: .center) { + + Image(systemName: "waveform.path") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("SNR").font(.title2).fixedSize() + Text(String(node.snr)) .font(.title2) .foregroundColor(.gray) - .symbolRenderingMode(.hierarchical) - } else { - - Text("Powered") - .font(.callout) .fixedSize() - .textCase(.uppercase) } + .padding(5) } - .padding(5) - } - } - .padding(4) - - Divider() - HStack(alignment: .center) { - VStack { - HStack { - Image(systemName: "person") - .font(.title2) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Unique Id:").font(.title2) - } - Text(node.user?.userId ?? "??????").font(.title3).foregroundColor(.gray) - } - Divider() - VStack { - HStack { - Image(systemName: "number") - .font(.title2) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Node Number:").font(.title2) - } - Text(String(node.num)).font(.title3).foregroundColor(.gray) - } - }.padding() - - if node.positions?.count ?? 0 > 1 { - - Divider() - - HStack { - - Image(systemName: "map.circle.fill") - .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Location History").font(.title2) - } - .padding() - - Divider() - - ForEach(node.positions!.array as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in + if node.positions!.count > 0 { + + let mostRecent = node.positions?.lastObject as! PositionEntity - if mappin.coordinate != nil { - - VStack { - - HStack { - - Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) //.font(.subheadline) - Text("Lat/Long:").font(.caption) - Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") - .foregroundColor(.gray) - .font(.caption) - - Text("Altitude:") - .font(.caption) - - Text("\(String(mappin.altitude))m") - .foregroundColor(.gray) - .font(.caption) - } - HStack { - - Image(systemName: "clock.badge.checkmark.fill") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .font(.caption) - Text("\(mappin.time!, style: .date) \(mappin.time!, style: .time)") - .foregroundColor(.gray) - .font(.caption) - Divider() - - Text("Battery").font(.caption).fixedSize() - Text(String(mappin.batteryLevel) + "%") - .font(.caption) - .foregroundColor(.gray) - .symbolRenderingMode(.hierarchical) - } - } - .padding(1) Divider() + + VStack(alignment: .center) { + + BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor) + .padding(.bottom) + if mostRecent.batteryLevel > 0 { + + Text("Battery") + .font(.title2) + .fixedSize() + .textCase(.uppercase) + Text(String(mostRecent.batteryLevel) + "%") + .font(.title2) + .foregroundColor(.gray) + .symbolRenderingMode(.hierarchical) + } else { + + Text("Powered") + .font(.callout) + .fixedSize() + .textCase(.uppercase) + } + } + .padding(5) } } - .padding(.bottom, 5) // Without some padding here there is a transparent contentview bug + .padding(4) + + Divider() + + HStack(alignment: .center) { + VStack { + HStack { + Image(systemName: "person") + .font(.title2) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Unique Id:").font(.title2) + } + Text(node.user?.userId ?? "??????").font(.title3).foregroundColor(.gray) + } + Divider() + VStack { + HStack { + Image(systemName: "number") + .font(.title2) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Node Number:").font(.title2) + } + Text(String(node.num)).font(.title3).foregroundColor(.gray) + } + }.padding() + + if node.positions?.count ?? 0 > 1 { + + Divider() + + HStack { + + Image(systemName: "map.circle.fill") + .font(.title) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Location History").font(.title2) + } + .padding() + + Divider() + + ForEach(node.positions!.array as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in + + if mappin.coordinate != nil { + + VStack { + + HStack { + + Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline) + Text("Lat/Long:").font(.caption) + Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") + .foregroundColor(.gray) + .font(.caption) + + Text("Altitude:") + .font(.caption) + + Text("\(String(mappin.altitude))m") + .foregroundColor(.gray) + .font(.caption) + } + HStack { + + Image(systemName: "clock.badge.checkmark.fill") + .font(.subheadline) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Time:") + .font(.caption) + Text("\(mappin.time!, style: .date) \(mappin.time!, style: .time)") + .foregroundColor(.gray) + .font(.caption) + Divider() + + Text("Battery").font(.caption).fixedSize() + Text(String(mappin.batteryLevel) + "%") + .font(.caption) + .foregroundColor(.gray) + .symbolRenderingMode(.hierarchical) + } + } + .padding(1) + Divider() + } + } + .padding(.bottom, 5) // Without some padding here there is a transparent contentview bug + } } - } + }.ignoresSafeArea(.all, edges: [.leading, .trailing]) } - .navigationTitle(node.user!.longName ?? "Unknown") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(trailing: - - ZStack { - - ConnectedDevice( - bluetoothOn: bleManager.isSwitchedOn, - deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "???") - } - ) - .onAppear(perform: { - - self.bleManager.context = context - - }) } - .ignoresSafeArea(.all, edges: [.leading, .trailing]) + .navigationTitle(node.user!.longName ?? "Unknown") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "???") + } + ) + .onAppear(perform: { + + self.bleManager.context = context + + }) } } struct NodeInfoEntityDetail_Previews: PreviewProvider { - + static let bleManager = BLEManager() static var previews: some View { Group { - - //NodeDetail(node: node) + + // NodeDetail(node: node) } } } diff --git a/MeshtasticClient/Views/Nodes/NodeList.swift b/MeshtasticClient/Views/Nodes/NodeList.swift index 124b5644..61ce98a7 100644 --- a/MeshtasticClient/Views/Nodes/NodeList.swift +++ b/MeshtasticClient/Views/Nodes/NodeList.swift @@ -11,31 +11,30 @@ import SwiftUI struct NodeList: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - + @FetchRequest( sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], animation: .default) - + private var nodes: FetchedResults - - - //@FetchRequest( + + // @FetchRequest( // sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], // animation: .default) var nodes: FetchedResults - + @State private var selection: String? var body: some View { - + NavigationView { List { if nodes.count == 0 { - + Text("Scan for Radios").font(.largeTitle) Text("No LoRa Mesh Nodes Found").font(.title2) Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your phone or tablet.") @@ -43,18 +42,18 @@ struct NodeList: View { Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.") Text("Views with bluetooth functionality will show an indicator in the upper right hand corner show if bluetooth is on, and if a device is connected.") .listRowSeparator(.visible) - + } else { ForEach( nodes ) { node in - + let index = nodes.firstIndex(where: { $0.id == node.id }) - + NavigationLink(destination: NodeDetail(node: node), tag: String(index!), selection: $selection) { - + let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.name == node.bleName) - + VStack(alignment: .leading) { - + HStack { CircleText(text: node.user?.shortName ?? "???", color: Color.accentColor).offset(y: 1).padding(.trailing, 5) @@ -67,21 +66,21 @@ struct NodeList: View { } } .padding(.bottom, 10) - + if connected { HStack(alignment: .bottom) { - + Image(systemName: "repeat.circle.fill").font(.title3) .foregroundColor(.accentColor).symbolRenderingMode(.hierarchical) Text("Currently Connected").font(.title3).foregroundColor(Color.accentColor) } Spacer() } - + HStack(alignment: .bottom) { Image(systemName: "clock.badge.checkmark.fill").font(.title3).foregroundColor(.accentColor).symbolRenderingMode(.hierarchical) - + if node.lastHeard != nil { Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.subheadline).foregroundColor(.gray) } else { @@ -92,23 +91,23 @@ struct NodeList: View { .padding([.leading, .top, .bottom]) } .swipeActions { - + Button { - + context.delete(node) - + do { - + try context.save() print("Successfully Deleted NodeInfoEntiy: \(node.num)") - + } catch { - + print("Failed to save context after deleting NodeInfoEntity Num: \(node.num)") } - + } label: { - + Label("Delete from app", systemImage: "trash") } .tint(.red) @@ -117,8 +116,8 @@ struct NodeList: View { } } .navigationTitle("All Nodes") - .onAppear{ - //self.nodes.returnsObjectsAsFaults = false + .onAppear { + // self.nodes.returnsObjectsAsFaults = false self.bleManager.context = context if UIDevice.current.userInterfaceIdiom == .pad { diff --git a/MeshtasticClient/Views/Nodes/NodeMap.swift b/MeshtasticClient/Views/Nodes/NodeMap.swift index c2cca6e3..552076c1 100644 --- a/MeshtasticClient/Views/Nodes/NodeMap.swift +++ b/MeshtasticClient/Views/Nodes/NodeMap.swift @@ -15,22 +15,20 @@ struct NodeMap: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - - //@AppStorage("meshMapType") var meshMapType: String = "hybrid" - + + // @AppStorage("meshMapType") var meshMapType: String = "hybrid" + @State private var showLabels: Bool = false - + @State private var annotationItems: [MapLocation] = [] @FetchRequest( sortDescriptors: [NSSortDescriptor(keyPath: \NodeInfoEntity.lastHeard, ascending: false)], animation: .default) - - - + private var locationNodes: FetchedResults - + var annotations: [MapLocation] = [MapLocation]() var body: some View { - + let location = LocationHelper.currentLocation let currentCoordinatePosition = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude) let regionBinding = Binding( @@ -39,45 +37,13 @@ struct NodeMap: View { }, set: { _ in } ) - - /*ForEach ( locationNodes ) { node in - let mostRecent = node.positions?.lastObject as! PositionEntity - if mostRecent.coordinate != nil { - - annotations.append(MapLocation(name: node.user?.shortName! ?? "???", coordinate: mostRecent.coordinate!)) - - } - }*/ - + NavigationView { - ZStack { - - - - /*Map(coordinateRegion: regionBinding, - interactionModes: [.all], - showsUserLocation: true, - userTrackingMode: .constant(.follow), - annotationItems: self.locationNodes.filter({ nodeinfo in - return nodeinfo.positions != nil && nodeinfo.positions!.count > 0// && (nodeinfo.positions?.lastObject as? AnyObject)?.coordinate != nil - }) - ) { locationNode in - - return MapAnnotation( - coordinate: (locationNode.positions!.lastObject as! PositionEntity).coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0), - content: { - CircleText(text: locationNode.user!.shortName ?? "???", color: .accentColor) - } - ) - - - }*/ - - MapView(nodes: self.locationNodes)//.environmentObject(userSettings) - //} + MapView(nodes: self.locationNodes)// .environmentObject(userSettings) + // } .frame(maxHeight: .infinity) .ignoresSafeArea(.all, edges: [.leading, .trailing]) } diff --git a/MeshtasticClient/Views/Nodes/NodeRow.swift b/MeshtasticClient/Views/Nodes/NodeRow.swift index f45267b0..91d70de8 100644 --- a/MeshtasticClient/Views/Nodes/NodeRow.swift +++ b/MeshtasticClient/Views/Nodes/NodeRow.swift @@ -21,17 +21,17 @@ struct NodeRow: View { } } .padding(.bottom, 10) - + if connected { HStack(alignment: .bottom) { - + Image(systemName: "repeat.circle.fill").font(.title3) .foregroundColor(.accentColor).symbolRenderingMode(.hierarchical) Text("Currently Connected").font(.title3).foregroundColor(Color.accentColor) } Spacer() } - + HStack(alignment: .bottom) { Image(systemName: "clock.badge.checkmark.fill").font(.title3).foregroundColor(.accentColor).symbolRenderingMode(.hierarchical) @@ -46,7 +46,7 @@ struct NodeRow: View { } } else { - + if node.lastHeard != nil { Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.subheadline).foregroundColor(.gray) } else { @@ -59,11 +59,11 @@ struct NodeRow: View { } struct NodeRow_Previews: PreviewProvider { - //static var nodes = BLEManager().meshData.nodes + // static var nodes = BLEManager().meshData.nodes static var previews: some View { Group { - //NodeRow(node: nodes[0], connected: true) + // NodeRow(node: nodes[0], connected: true) } .previewLayout(.fixed(width: 300, height: 70)) } diff --git a/MeshtasticClient/Views/Settings/AppSettings.swift b/MeshtasticClient/Views/Settings/AppSettings.swift index 3710d2eb..98690e2a 100644 --- a/MeshtasticClient/Views/Settings/AppSettings.swift +++ b/MeshtasticClient/Views/Settings/AppSettings.swift @@ -32,13 +32,13 @@ enum KeyboardType: Int, CaseIterable, Identifiable { } enum MeshMapType: String, CaseIterable, Identifiable { - + case satellite = "satellite" case hybrid = "hybrid" case standard = "standard" - + var id: String { self.rawValue } - + var description: String { get { switch self { @@ -84,21 +84,19 @@ class UserSettings: ObservableObject { UserDefaults.standard.set(meshActivityLog, forKey: "meshActivityLog") } } - + @Published var meshMapType: String { didSet { UserDefaults.standard.set(meshMapType, forKey: "meshMapType") } } - - 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 + // self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0 self.meshActivityLog = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? false self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "hybrid" @@ -160,12 +158,12 @@ struct AppSettings: View { .pickerStyle(DefaultPickerStyle()) } Section(header: Text("MESH NETWORK OPTIONS")) { - //Toggle(isOn: $userSettings.meshActivityLog) { + // Toggle(isOn: $userSettings.meshActivityLog) { // Label("Log all Mesh activity", systemImage: "network") - //} - //.toggleStyle(SwitchToggleStyle(tint: .accentColor)) - if true {//userSettings.meshActivityLog { + // } + // .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + if true {// userSettings.meshActivityLog { NavigationLink(destination: MeshLog()) { Text("View Mesh Log") } @@ -186,10 +184,10 @@ struct AppSettings: View { .navigationBarItems(trailing: ZStack { - + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "???") }) - .onAppear{ + .onAppear { self.bleManager.context = context } @@ -199,7 +197,7 @@ struct AppSettings: View { } struct AppSettings_Previews: PreviewProvider { - + static var previews: some View { Group { AppSettings()