diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index dd0f04a5..aa9ee08a 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 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 */; }; DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FE272476C700F4AB02 /* LogDocument.swift */; }; @@ -78,6 +79,7 @@ DD4A911F2708C66600501B7E /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; DD5394FF276C452400AD86B1 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = ""; }; @@ -276,6 +278,7 @@ isa = PBXGroup; children = ( DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */, + DD539501276DAA6A00AD86B1 /* MapLocation.swift */, ); path = Model; sourceTree = ""; @@ -517,6 +520,7 @@ DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */, DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, + DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */, DDAF8C6726ED0C8C0058C060 /* remote_hardware.pb.swift in Sources */, DDAF8C6526ED0A490058C060 /* channel.pb.swift in Sources */, DD47E3DD26F390A000029299 /* Messages.swift in Sources */, diff --git a/Meshtastic Client.xcodeproj/xcuserdata/garthvanderhouwen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Meshtastic Client.xcodeproj/xcuserdata/garthvanderhouwen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 858a7984..14488c79 100644 --- a/Meshtastic Client.xcodeproj/xcuserdata/garthvanderhouwen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Meshtastic Client.xcodeproj/xcuserdata/garthvanderhouwen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -112,38 +112,6 @@ landmarkType = "24"> - - - - - - - - + + + + diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index ebcd52d2..8f7690e3 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -480,7 +480,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph newNode.user = newUser } - if false && decodedInfo.nodeInfo.hasPosition && decodedInfo.nodeInfo.position.latitudeI != 0 { + if decodedInfo.nodeInfo.hasPosition && decodedInfo.nodeInfo.position.latitudeI != 0 { let position = PositionEntity(context: context!) position.latitudeI = decodedInfo.nodeInfo.position.latitudeI @@ -491,7 +491,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph var newPostions = [PositionEntity]() newPostions.append(position) - newNode.positions? = NSSet(array : newPostions) + newNode.positions? = NSOrderedSet(array : newPostions) } // Look for a MyInfo @@ -534,9 +534,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph position.time = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.position.time))) if position.latitudeI != 0 { - let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableSet + let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet mutablePositions.add(position) - fetchedNode[0].positions = mutablePositions.copy() as? NSSet + fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet } } // Look for a MyInfo @@ -769,7 +769,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if meshLoggingEnabled { MeshLogger.log("BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)") } print("BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)") self.connectedPeripheral.subscribed = true - peripherals.removeAll(where: { $0.peripheral.state != CBPeripheralState.connected }) + peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) } default: diff --git a/MeshtasticClient/Model/MapLocation.swift b/MeshtasticClient/Model/MapLocation.swift new file mode 100644 index 00000000..8e8745f5 --- /dev/null +++ b/MeshtasticClient/Model/MapLocation.swift @@ -0,0 +1,15 @@ +// +// MapLocation.swift +// MeshtasticClient +// +// Created by Garth Vander Houwen on 12/17/21. +// +import Foundation +import MapKit + +struct MapLocation: Identifiable { + + let id = UUID() + let name: String + let coordinate: CLLocationCoordinate2D +} diff --git a/MeshtasticClient/Views/Bluetooth/Connect.swift b/MeshtasticClient/Views/Bluetooth/Connect.swift index d13995b2..6dfbed6d 100644 --- a/MeshtasticClient/Views/Bluetooth/Connect.swift +++ b/MeshtasticClient/Views/Bluetooth/Connect.swift @@ -81,11 +81,13 @@ struct Connect: View { if value { if bleManager.connectedPeripheral != nil { + let deviceName = (bleManager.connectedPeripheral.peripheral.name ?? "") - userSettings.preferredPeripheralName = deviceName + } else { - userSettings.preferredPeripheralName = bleManager.connectedPeripheral.peripheral.name ?? "Unknown Device" + + userSettings.preferredPeripheralName = bleManager.connectedPeripheral.longName } userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString diff --git a/MeshtasticClient/Views/Messages/Messages.swift b/MeshtasticClient/Views/Messages/Messages.swift index 93f52739..97159fe3 100644 --- a/MeshtasticClient/Views/Messages/Messages.swift +++ b/MeshtasticClient/Views/Messages/Messages.swift @@ -48,7 +48,7 @@ struct Messages: View { ForEach(messages) { message in HStack(alignment: .top) { - let currentUser: Bool = (message.fromUser != nil && bleManager.connectedPeripheral.num == message.fromUser!.num) + let currentUser: Bool = false// (message.fromUser != nil && bleManager.connectedPeripheral.num == message.fromUser!.num) CircleText(text: (message.fromUser?.longName ?? "???"), color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5) diff --git a/MeshtasticClient/Views/Nodes/NodeDetail.swift b/MeshtasticClient/Views/Nodes/NodeDetail.swift index 5f5a60cc..87816587 100644 --- a/MeshtasticClient/Views/Nodes/NodeDetail.swift +++ b/MeshtasticClient/Views/Nodes/NodeDetail.swift @@ -9,19 +9,14 @@ import CoreLocation struct NodeDetail: View { - // CoreData @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager var node: NodeInfoEntity - struct MapLocation: Identifiable { - let id = UUID() - let name: String - let coordinate: CLLocationCoordinate2D - } - var body: some View { + + let mostRecent = node.positions?.lastObject as! PositionEntity GeometryReader { bounds in @@ -29,34 +24,46 @@ struct NodeDetail: View { if node.positions != nil && node.positions!.count > 0 { -// let mostRecentPositions = node.positions.max(by: { -// $0. < $1.timeIntervalSinceReferenceDate -// }) -// let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: node.positions.latitude!, longitude: node.position.longitude!) -// -// let regionBinding = Binding( -// get: { -// MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) -// }, -// set: { _ in } -// ) -// let annotations = [MapLocation(name: node.user!.shortName, coordinate: node.positions?.first(where: <#T##(Any) throws -> Bool#>).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, minHeight: bounds.size.height / 2) + let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!) + + let regionBinding = Binding( + get: { + MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) + }, + set: { _ in } + ) + let annotations = [MapLocation(name: node.user!.shortName ?? "???", coordinate: mostRecent.coordinate!)] + + Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in + MapAnnotation( + coordinate: location.coordinate, + content: { + CircleText(text: node.user!.shortName ?? "???", color: .accentColor) + } + ) + } + .frame(idealWidth: bounds.size.width, maxHeight: bounds.size.height / 3) + } else { + Image(node.user?.hwModel ?? "UNSET") .resizable() .aspectRatio(contentMode: .fit) - .frame(width: bounds.size.width, height: bounds.size.height / 2) + .frame(width: bounds.size.width, height: bounds.size.height / 3) } + ScrollView { + + if node.lastHeard != nil { + + HStack { + + Image(systemName: "clock").font(.title2).foregroundColor(.accentColor) + Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.title3) + } + .padding() + Divider() + } HStack { @@ -67,7 +74,20 @@ struct NodeDetail: View { } .padding([.leading, .trailing, .bottom]) Divider() - if node.snr > 0 { + VStack { + + Image(node.user!.hwModel ?? "UNSET") + .resizable() + .frame(width: 50, height: 50) + .cornerRadius(5) + + Text(String(node.user!.hwModel ?? "UNSET")) + .font(.subheadline).fixedSize() + } + .padding() + + if true {//node.snr > 0 { + Divider() VStack(alignment: .center) { Image(systemName: "waveform.path") @@ -78,67 +98,39 @@ struct NodeDetail: View { Text(String(node.snr)) .font(.title2) .foregroundColor(.gray) + .fixedSize() } - Divider() + .padding([.leading, .trailing, .bottom]) } -// VStack(alignment: .center) { -// BatteryIcon(batteryLevel: node.position.batteryLevel, font: .title, color: .accentColor) -// if node.position.batteryLevel != nil && node.position.batteryLevel! > 0 { -// Text("Battery").font(.title2).fixedSize() -// Text(String(node.position.batteryLevel!) + "%") -// .font(.title2) -// .foregroundColor(.gray) -// .symbolRenderingMode(.hierarchical) -// } else { -// Text("Powered").font(.title2) -// } -// } - - }.padding(4) - Divider() - HStack { - Image(node.user!.hwModel ?? "UNSET") - .resizable() - .frame(width: 60, height: 60) - .cornerRadius(5) - - Text("Model: " + String(node.user!.hwModel ?? "UNSET")) - .font(.title3) + if node.positions!.count > 0 { + Divider() + + VStack(alignment: .center) { + + BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor) + .padding(.bottom) + if mostRecent.batteryLevel > 0 { + + Text("Battery").font(.title2).fixedSize() + Text(String(mostRecent.batteryLevel) + "%") + .font(.title2) + .foregroundColor(.gray) + .symbolRenderingMode(.hierarchical) + } else { + + Text("Powered") + .font(.callout) + .fixedSize() + } + } + .padding([.leading, .trailing, .bottom]) + } } - .padding() + .padding(4) + Divider() - if node.lastHeard != nil { - - HStack { - - Image(systemName: "clock").font(.title2).foregroundColor(.accentColor) - Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.title3) - }.padding() - Divider() - } - -// if node.position.coordinate != nil { -// HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) { -// Image(systemName: "mappin").font(.title).foregroundColor(.accentColor) -// VStack(alignment: .leading) { -// Text("Latitude").font(.headline) -// Text(String(node.position.latitude ?? 0)).font(.caption).foregroundColor(.gray) -// } -// Divider() -// VStack(alignment: .leading) { -// Text("Longitude").font(.headline) -// Text(String(node.position.longitude ?? 0)).font(.caption).foregroundColor(.gray) -// } -// Divider() -// VStack(alignment: .leading) { -// Text("Altitude").font(.headline) -// Text(String(node.position.altitude ?? 0) + " m").font(.caption).foregroundColor(.gray) -// } -// }.padding() -// Divider() -// } HStack(alignment: .center) { VStack { HStack { @@ -156,6 +148,63 @@ struct NodeDetail: View { Text(String(node.num)).font(.headline).foregroundColor(.gray) } }.padding() + + if node.positions != nil && node.positions!.count > 0 { + + Divider() + + HStack { + + Image(systemName: "map.circle.fill").font(.title).foregroundColor(.accentColor) + Text("Position History (\(node.positions?.count ?? 0) Points)").font(.title2) + } + .padding() + + Divider() + + ForEach(node.positions!.array as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in + + 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([.top, .bottom]) + Divider() + } + } } }.navigationTitle(node.user!.longName ?? "Unknown") .navigationBarTitleDisplayMode(.inline) @@ -174,16 +223,19 @@ struct NodeDetail: View { self.bleManager.context = context }) - }.ignoresSafeArea(.all, edges: [.leading, .trailing]) + } + .ignoresSafeArea(.all, edges: [.leading, .trailing]) } } struct NodeInfoEntityDetail_Previews: PreviewProvider { + static let bleManager = BLEManager() static var previews: some View { Group { - //NodeInfoEntityDetail(node: node) + + //NodeDetail(node: node) } } } diff --git a/MeshtasticClient/Views/Nodes/NodeList.swift b/MeshtasticClient/Views/Nodes/NodeList.swift index adb824cc..124b5644 100644 --- a/MeshtasticClient/Views/Nodes/NodeList.swift +++ b/MeshtasticClient/Views/Nodes/NodeList.swift @@ -19,7 +19,12 @@ struct NodeList: View { sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], animation: .default) - private var nodes: FetchedResults + private var nodes: FetchedResults + + + //@FetchRequest( + // sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], + // animation: .default) var nodes: FetchedResults @State private var selection: String? @@ -113,7 +118,7 @@ struct NodeList: View { } .navigationTitle("All Nodes") .onAppear{ - + //self.nodes.returnsObjectsAsFaults = false self.bleManager.context = context if UIDevice.current.userInterfaceIdiom == .pad {