mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
V 1.37 New maps, ran SwiftLint -fix
This commit is contained in:
parent
d3c561362e
commit
d7f5509b27
23 changed files with 665 additions and 664 deletions
|
|
@ -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 = "<group>"; };
|
||||
DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||
DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD5394FF276C452400AD86B1 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = "<group>"; };
|
||||
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = "<group>"; };
|
||||
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -344,7 +342,6 @@
|
|||
DDAF8C6D26ED19040058C060 /* Extensions.swift */,
|
||||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */,
|
||||
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */,
|
||||
DD5394FF276C452400AD86B1 /* Preferences.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
|
||||
let fetchConnectedPeripheralRequest: NSFetchRequest<NSFetchRequestResult> = 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<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = 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<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
|
||||
let fetchNodeRequest: NSFetchRequest<NSFetchRequestResult> = 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<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = 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<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = 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<NSFetchRequestResult> = NSFetchRequest.init(entityName: "UserEntity")
|
||||
messageUsers.predicate = NSPredicate(format:"num IN %@", [decodedInfo.packet.to, decodedInfo.packet.from])
|
||||
|
||||
|
||||
let messageUsers: NSFetchRequest<NSFetchRequestResult> = 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<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
|
||||
let fetchNodeInfoAppRequest: NSFetchRequest<NSFetchRequestResult> = 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<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
let fetchNodePositionRequest: NSFetchRequest<NSFetchRequestResult> = 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<NSFetchRequestResult> = NSFetchRequest.init(entityName: "UserEntity")
|
||||
messageUsers.predicate = NSPredicate(format:"num IN %@", [fromUserNum, Int64(toUserNum)])
|
||||
|
||||
|
||||
let fromUserNum: Int64 = self.connectedPeripheral.num
|
||||
|
||||
let messageUsers: NSFetchRequest<NSFetchRequestResult> = 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) ?? ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ import Combine
|
|||
import CoreBluetooth
|
||||
|
||||
final class BluetoothManager: NSObject {
|
||||
|
||||
|
||||
private var centralManager: CBCentralManager!
|
||||
|
||||
|
||||
var stateSubject: PassthroughSubject<CBManagerState, Never> = .init()
|
||||
var peripheralSubject: PassthroughSubject<CBPeripheral, Never> = .init()
|
||||
|
||||
|
||||
func start() {
|
||||
centralManager = .init(delegate: self, queue: .main)
|
||||
}
|
||||
|
||||
|
||||
func connect(_ peripheral: CBPeripheral) {
|
||||
centralManager.stopScan()
|
||||
peripheral.delegate = self
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// }
|
||||
//}
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import Foundation
|
|||
import MapKit
|
||||
|
||||
struct MapLocation: Identifiable {
|
||||
|
||||
|
||||
let id = UUID()
|
||||
let name: String
|
||||
let coordinate: CLLocationCoordinate2D
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ struct ConnectedDevice: View {
|
|||
.imageScale(.medium)
|
||||
.foregroundColor(.red)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Disconnected").font(.subheadline).foregroundColor(.gray)
|
||||
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<NodeInfoEntity>
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UserEntity>
|
||||
|
||||
|
||||
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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 : "???")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<MKCoordinateRegion>(
|
||||
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<MKCoordinateRegion>(
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<NodeInfoEntity>
|
||||
|
||||
|
||||
//@FetchRequest(
|
||||
|
||||
// @FetchRequest(
|
||||
// sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)],
|
||||
// animation: .default) var nodes: FetchedResults<NodeInfoEntity>
|
||||
|
||||
|
||||
@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 {
|
||||
|
|
|
|||
|
|
@ -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<NodeInfoEntity>
|
||||
|
||||
|
||||
var annotations: [MapLocation] = [MapLocation]()
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
||||
let location = LocationHelper.currentLocation
|
||||
let currentCoordinatePosition = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
|
||||
let regionBinding = Binding<MKCoordinateRegion>(
|
||||
|
|
@ -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])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue