Cluster for map pins on the node details view

This commit is contained in:
Garth Vander Houwen 2023-01-10 06:49:19 -08:00
parent cd0fdec368
commit 8bc645412b
7 changed files with 144 additions and 266 deletions

View file

@ -7,7 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
C9483F6D2773017500998F6B /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9483F6C2773017500998F6B /* MapView.swift */; };
C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; };
C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; };
C9A7BC1027759A9600760B50 /* PositionAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */; };
@ -22,6 +21,7 @@
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; };
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; };
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; };
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */; };
DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E65252767A01F00E45FC5 /* NodeDetail.swift */; };
DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; };
DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35018A2852FC79000FC853 /* UserSettings.swift */; };
@ -127,7 +127,6 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
C9483F6C2773017500998F6B /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; };
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = "<group>"; };
C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAnnotationView.swift; sourceTree = "<group>"; };
C9A88B54278B503C00BD810A /* MapViewModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapViewModule.swift; sourceTree = "<group>"; };
@ -141,6 +140,7 @@
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = "<group>"; };
DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = "<group>"; };
DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = "<group>"; };
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = "<group>"; };
DD2E65252767A01F00E45FC5 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = "<group>"; };
DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
DD35018A2852FC79000FC853 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = "<group>"; };
@ -270,9 +270,9 @@
isa = PBXGroup;
children = (
C9A7BC0E27759A6800760B50 /* Custom */,
C9483F6C2773017500998F6B /* MapView.swift */,
C9A88B54278B503C00BD810A /* MapViewModule.swift */,
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */,
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */,
);
path = Map;
sourceTree = "<group>";
@ -744,6 +744,7 @@
DDC4D568275499A500A4208E /* Persistence.swift in Sources */,
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */,
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */,
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */,
DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */,
DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */,
@ -783,7 +784,6 @@
DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */,
DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */,
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */,
C9483F6D2773017500998F6B /* MapView.swift in Sources */,
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */,
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */,
DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */,
@ -1006,7 +1006,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.10;
MARKETING_VERSION = 2.0.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1039,7 +1039,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.10;
MARKETING_VERSION = 2.0.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;

View file

@ -23,7 +23,7 @@ extension PositionEntity {
return d / 1e7
}
var coordinate: CLLocationCoordinate2D? {
var nodeCoordinate: CLLocationCoordinate2D? {
if latitudeI != 0 && longitudeI != 0 {
let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
return coord
@ -31,12 +31,17 @@ extension PositionEntity {
return nil
}
}
var annotaton: MKPointAnnotation {
let pointAnn = MKPointAnnotation()
if coordinate != nil {
pointAnn.coordinate = coordinate!
if nodeCoordinate != nil {
pointAnn.coordinate = nodeCoordinate!
}
return pointAnn
}
}
extension PositionEntity: MKAnnotation {
public var coordinate: CLLocationCoordinate2D { nodeCoordinate! }
public var subtitle: String? { time?.formatted() }
}

View file

@ -1,173 +0,0 @@
//
// MapView.swift
// MeshtasticApple
//
// Created by Joshua Pirihi on 22/12/21.
//
import Foundation
import UIKit
import MapKit
import SwiftUI
import CoreData
#if false
// wrap a MKMapView into something we can use in SwiftUI
struct MapView: UIViewRepresentable {
var nodes: FetchedResults<NodeInfoEntity>
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.showNodePositions(to: map)
self.moveToMeshRegion(in: map)
map.register(PositionAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(PositionAnnotationView.self))
let overlay = MKTileOverlay(urlTemplate: //"http://tiles-a.data-cdn.linz.govt.nz/services;key=7fa19132d53240708c4ff436df5b9800/tiles/v4/layer=50767/EPSG:3857/{z}/{x}/{y}.png")
"http://10.147.253.250:5050/local/map/{z}/{x}/{y}.png")
overlay.canReplaceMapContent = true
self.mapViewDelegate.renderer = MKTileOverlayRenderer(tileOverlay: overlay)
map.addOverlay(overlay)
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) {
// self.moveToMeshRegion(in: view)
// self.needToMoveToMeshRegion = false
//}
}
func moveToMeshRegion(in mapView: MKMapView) {
//go through the annotations and create a bounding box that encloses them
var minLat: CLLocationDegrees = 90.0
var maxLat: CLLocationDegrees = -90.0
var minLon: CLLocationDegrees = 180.0
var maxLon: CLLocationDegrees = -180.0
for annotation in mapView.annotations {
if annotation.isKind(of: PositionAnnotation.self) {
minLat = min(minLat, annotation.coordinate.latitude)
maxLat = max(maxLat, annotation.coordinate.latitude)
minLon = min(minLon, annotation.coordinate.longitude)
maxLon = max(maxLon, annotation.coordinate.longitude)
}
}
//check if the mesh region looks sensible before we move to it. Otherwise we won't move the map (leave it at the current location)
if maxLat < minLat || (maxLat-minLat) > 5 || maxLon < minLon || (maxLon-minLon) > 5 {
return
}
let centerCoord = CLLocationCoordinate2D(latitude: (minLat+maxLat)/2, longitude: (minLon+maxLon)/2)
let span = MKCoordinateSpan(latitudeDelta: (maxLat-minLat)*1.5, longitudeDelta: (maxLon-minLon)*1.5)
let region = mapView.regionThatFits(MKCoordinateRegion(center: centerCoord, span: span))
mapView.setRegion(region, animated: true)
}
func updateMapType(_ map: MKMapView) {
switch self.type {
case "satellite":
map.mapType = .satellite
break
case "standard":
map.mapType = .standard
break
case "hybrid":
map.mapType = .hybrid
break
default:
map.mapType = .hybrid
}
}
}
private extension MapView {
func showNodePositions(to view: MKMapView) {
// clear any existing annotations
if !view.annotations.isEmpty {
view.removeAnnotations(view.annotations)
}
for node in self.nodes {
// 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 ?? NSLocalizedString("unknown", comment: "Unknown")
annotation.shortName = node.user?.shortName?.uppercased() ?? "???"
view.addAnnotation(annotation)
}
}
}
}
class MapViewDelegate: NSObject, MKMapViewDelegate {
var renderer: MKTileOverlayRenderer?
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
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
return self.renderer!
}
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
}
}
#endif

View file

@ -223,7 +223,7 @@ public struct MapView: UIViewRepresentable {
}
let annotation = PositionAnnotation()
annotation.coordinate = position.coordinate!
annotation.coordinate = position.nodeCoordinate!
annotation.title = position.nodePosition!.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")
annotation.shortName = position.nodePosition!.user?.shortName?.uppercased() ?? "???"

View file

@ -0,0 +1,57 @@
//
// MapViewSwitUI.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 1/9/23.
//
import SwiftUI
import MapKit
struct MapViewSwiftUI: UIViewRepresentable {
let positions: [PositionEntity]
let region: MKCoordinateRegion
let mapViewType: MKMapType
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.mapType = mapViewType
mapView.setRegion(region, animated: true)
mapView.isRotateEnabled = true
mapView.addAnnotations(positions)
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.mapType = mapViewType
}
func makeCoordinator() -> MapCoordinator {
.init()
}
final class MapCoordinator: NSObject, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
switch annotation {
case _ as MKClusterAnnotation:
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "nodeGroup")
annotationView.markerTintColor = .darkGray
return annotationView
case _ as PositionEntity:
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "Node")
annotationView.canShowCallout = true
annotationView.glyphText = "📟"
annotationView.clusteringIdentifier = "nodeGroup"
annotationView.markerTintColor = UIColor(.accentColor)
annotationView.titleVisibility = .visible
return annotationView
default: return nil
}
}
}
}

View file

@ -1,73 +1,62 @@
/*
Abstract:
A view showing the details for a node.
*/
Abstract:
A view showing the details for a node.
*/
import SwiftUI
import MapKit
import CoreLocation
struct NodeDetail: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State private var mapType: MKMapType = .standard
@State private var showingDetailsPopover = false
@State var satsInView = 0
@State private var showingShutdownConfirm: Bool = false
@State private var showingRebootConfirm: Bool = false
var node: NodeInfoEntity
var body: some View {
let hwModelString = node.user?.hwModel ?? "UNSET"
NavigationStack {
GeometryReader { bounds in
VStack {
if node.positions?.count ?? 0 > 0 {
let mostRecent = node.positions?.lastObject as! PositionEntity
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 nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!)
ZStack {
let annotations = node.positions?.array as! [PositionEntity]
ZStack {
let annotations = node.positions?.array as! [PositionEntity]
Map(coordinateRegion: regionBinding,
interactionModes: [.all],
showsUserLocation: true,
userTrackingMode: .constant(.follow),
annotationItems: annotations) { location in
MapViewSwiftUI(positions: annotations, region: MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)), mapViewType: mapType)
VStack {
Spacer()
Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ")
.font(.caption2)
return MapAnnotation(
coordinate: location.coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0),
content: {
NodeAnnotation(time: location.time!)
}
)
Picker("", selection: $mapType) {
Text("Standard").tag(MKMapType.standard)
Text("Hybrid").tag(MKMapType.hybrid)
Text("Satellite").tag(MKMapType.satellite)
}
.pickerStyle(SegmentedPickerStyle())
}
.ignoresSafeArea(.all, edges: [.leading, .trailing])
.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2)
}
Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ")
.offset( y:-40)
.ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65)
}
} else {
HStack {
}
.padding([.top], 60)
.padding([.top], 20)
}
ScrollView {
@ -80,13 +69,13 @@ struct NodeDetail: View {
Divider()
VStack {
if node.user != nil {
Image(hwModelString)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.cornerRadius(5)
Text(String(hwModelString))
.foregroundColor(.gray)
.font(.largeTitle).fixedSize()
@ -96,7 +85,7 @@ struct NodeDetail: View {
if node.snr > 0 {
Divider()
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.accentColor)
@ -109,15 +98,15 @@ struct NodeDetail: View {
.fixedSize()
}
}
if node.telemetries?.count ?? 0 >= 1 {
let mostRecent = node.telemetries?.lastObject as! TelemetryEntity
Divider()
VStack(alignment: .center) {
BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel))
if mostRecent.voltage > 0 {
Text(String(format: "%.2f", mostRecent.voltage) + " V")
.font(.title)
.foregroundColor(.gray)
@ -147,7 +136,7 @@ struct NodeDetail: View {
Divider()
VStack {
HStack {
Image(systemName: "number")
Image(systemName: "number")
.font(.title2)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
@ -160,8 +149,8 @@ struct NodeDetail: View {
HStack {
Image(systemName: "globe")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("MAC Address: ").font(.title)
}
@ -174,8 +163,8 @@ struct NodeDetail: View {
HStack {
Image(systemName: "clock.badge.checkmark.fill")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("heard.last").font(.title)+Text(":").font(.title)
}
@ -190,7 +179,7 @@ struct NodeDetail: View {
} else {
HStack {
VStack(alignment: .center) {
CircleText(text: node.user?.shortName ?? "???", color: .accentColor)
}
@ -210,7 +199,7 @@ struct NodeDetail: View {
if node.snr > 0 {
Divider()
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.accentColor)
@ -223,15 +212,15 @@ struct NodeDetail: View {
}
.padding(5)
}
if node.telemetries?.count ?? 0 >= 1 {
let mostRecent = node.telemetries?.lastObject as! TelemetryEntity
Divider()
VStack(alignment: .center) {
BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel))
if mostRecent.voltage > 0 {
@ -262,7 +251,7 @@ struct NodeDetail: View {
Divider()
VStack {
HStack {
Image(systemName: "number")
Image(systemName: "number")
.font(.title2)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
@ -275,9 +264,9 @@ struct NodeDetail: View {
Divider()
HStack {
Image(systemName: "globe")
.font(.headline)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
.font(.headline)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("MAC Address: ")
Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray)
}
@ -334,16 +323,16 @@ struct NodeDetail: View {
}
if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num {
HStack {
if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") {
Button(action: {
showingShutdownConfirm = true
}) {
Label("Power Off", systemImage: "power")
}
.buttonStyle(.bordered)
@ -361,13 +350,13 @@ struct NodeDetail: View {
}
}
}
Button(action: {
showingRebootConfirm = true
}) {
Label("reboot", systemImage: "arrow.triangle.2.circlepath")
}
.buttonStyle(.bordered)
@ -376,31 +365,31 @@ struct NodeDetail: View {
.padding()
.confirmationDialog("are.you.sure",
isPresented: $showingRebootConfirm
) {
Button("reboot.node", role: .destructive) {
if !bleManager.sendReboot(fromUser: node.user!, toUser: node.user!) {
print("Reboot Failed")
}
}
isPresented: $showingRebootConfirm
) {
Button("reboot.node", role: .destructive) {
if !bleManager.sendReboot(fromUser: node.user!, toUser: node.user!) {
print("Reboot Failed")
}
}
}
}
.padding(5)
}
}
.offset( y:-40)
//.offset( y:-40)
}
.edgesIgnoringSafeArea([.leading, .trailing])
.navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "")), displayMode: .inline)
.padding(.bottom, 10)
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
}
ZStack {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
}
)
.onAppear {
self.bleManager.context = context

View file

@ -53,8 +53,8 @@ struct NodeList: View {
HStack(alignment: .bottom) {
let lastPostion = node.positions!.reversed()[0] as! PositionEntity
let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)
if lastPostion.coordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude {
let nodeCoord = CLLocation(latitude: lastPostion.coordinate!.latitude, longitude: lastPostion.coordinate!.longitude)
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude {
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
let metersAway = nodeCoord.distance(from: myCoord)
Image(systemName: "lines.measurement.horizontal")
.font(.title3)