Merge branch 'main' into mesh-map-bounding-region

This commit is contained in:
joshpirihi 2021-12-27 08:34:24 +13:00 committed by GitHub
commit 23eb96ac0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 883 additions and 665 deletions

View file

@ -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;

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-4.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "TLORA_gray-1.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "TLORA_gray-2.jpg",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "TLORA_olive.jpg",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View file

@ -1,14 +1,17 @@
{
"images" : [
{
"filename" : "play_store_icon_114px-4.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "play_store_icon_114px-3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "play_store_icon_114px-2.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -11,7 +11,7 @@
"scale" : "2x"
},
{
"filename" : "TLORA_purple.jpg",
"filename" : "TLORA_gray-1.jpg",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

View file

@ -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) ?? ""
}

View file

@ -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

View file

@ -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"

View file

@ -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
// }
//}
// }

View file

@ -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:

View file

@ -8,7 +8,7 @@ import Foundation
import MapKit
struct MapLocation: Identifiable {
let id = UUID()
let name: String
let coordinate: CLLocationCoordinate2D

View file

@ -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.

View file

@ -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
}
}

View file

@ -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,

View file

@ -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")

View file

@ -27,7 +27,6 @@ struct ConnectedDevice: View {
.imageScale(.medium)
.foregroundColor(.red)
.symbolRenderingMode(.hierarchical)
Text("Disconnected").font(.subheadline).foregroundColor(.gray)
}
} else {

View file

@ -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")

View file

@ -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)
}
}

View file

@ -11,42 +11,40 @@ 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"
//@State var needToMoveToMeshRegion: Bool = true
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.showNodePositions(to: map)
self.moveToMeshRegion(in: 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)
//if (self.needToMoveToMeshRegion) {
@ -81,9 +79,9 @@ struct MapView: UIViewRepresentable {
}
func updateMapType(_ map: MKMapView) {
switch self.type {
case "satellite":
map.mapType = .satellite
@ -103,20 +101,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)
}
}
@ -124,33 +122,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
}
}

View file

@ -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())
}

View file

@ -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

View file

@ -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 : "???")
}
}
}
}
}

View file

@ -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)
}
}
}

View file

@ -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 {

View file

@ -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])
}

View file

@ -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))
}

View file

@ -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()

View file

@ -1,5 +1,14 @@
# Meshtastic Client
## OS Requirements
Requires iOS 15 +
## Code Standards
* Use SwiftUI whenever possible
* Use Hierarchical icons
- Requires SwiftLint - see https://github.com/realm/SwiftLint
## To update protobufs: