mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
* Centering Fixes
* Restrict node details to last thousand positions instead of the last day * Fix battery level display on node metrics view
This commit is contained in:
parent
9fc6609cec
commit
5a73b846f2
4 changed files with 82 additions and 101 deletions
|
|
@ -12,38 +12,38 @@ func degreesToRadians(_ number: Double) -> Double {
|
|||
}
|
||||
|
||||
struct MapViewSwiftUI: UIViewRepresentable {
|
||||
|
||||
|
||||
var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
|
||||
var onWaypointEdit: (_ waypointId: Int ) -> Void
|
||||
let mapView = MKMapView()
|
||||
let colors: [UIColor] = [UIColor.systemIndigo, UIColor.orange, UIColor.green, UIColor.brown, UIColor.purple, UIColor.systemMint, UIColor.cyan, UIColor.magenta, UIColor.systemPink, UIColor.blue]
|
||||
// Parameters
|
||||
let positions: [PositionEntity]
|
||||
let waypoints: [WaypointEntity]
|
||||
let mapViewType: MKMapType
|
||||
let userTrackingMode: MKUserTrackingMode
|
||||
let showNodeHistory: Bool
|
||||
let showRouteLines: Bool
|
||||
let colors: [UIColor] = [UIColor.systemIndigo, UIColor.orange, UIColor.green, UIColor.brown, UIColor.purple, UIColor.systemMint, UIColor.cyan, UIColor.magenta, UIColor.systemPink, UIColor.blue]
|
||||
|
||||
@AppStorage("meshMapRecentering") private var recenter: Bool = false
|
||||
// Offline Maps
|
||||
// make this view dependent on the UserDefault that is updated when importing a new map file
|
||||
// Offline Map Tiles
|
||||
@AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
|
||||
@State private var loadedLastUpdatedLocalMapFile = 0
|
||||
var customMapOverlay: CustomMapOverlay?
|
||||
@State private var presentCustomMapOverlayHash: CustomMapOverlay?
|
||||
|
||||
|
||||
func makeUIView(context: Context) -> MKMapView {
|
||||
// Map View Parameters
|
||||
mapView.mapType = mapViewType
|
||||
mapView.addAnnotations(waypoints)
|
||||
// Do the initial map centering
|
||||
let latest = positions
|
||||
.filter { $0.latest == true }
|
||||
.sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
|
||||
let center = LocationHelper.currentLocation
|
||||
let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation
|
||||
let region = MKCoordinateRegion(center: center, span: span)
|
||||
mapView.setRegion(region, animated: true)
|
||||
// Set user (phone gps) tracking options
|
||||
let latest = positions.filter { $0.latest == true }
|
||||
mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
if userTrackingMode == MKUserTrackingMode.none {
|
||||
mapView.showsUserLocation = false
|
||||
|
|
@ -51,6 +51,15 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
mapView.showsUserLocation = true
|
||||
}
|
||||
mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
// Set user (phone gps) tracking options
|
||||
mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
if userTrackingMode == MKUserTrackingMode.none {
|
||||
mapView.fitAllAnnotations()
|
||||
mapView.showsUserLocation = false
|
||||
} else {
|
||||
mapView.showsUserLocation = true
|
||||
}
|
||||
mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
// Other MKMapView Settings
|
||||
mapView.preferredConfiguration.elevationStyle = .realistic// .flat
|
||||
mapView.isPitchEnabled = true
|
||||
|
|
@ -60,14 +69,14 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
mapView.showsBuildings = true
|
||||
mapView.showsScale = true
|
||||
mapView.showsTraffic = true
|
||||
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
// Show the default always visible compass and the mac only controls
|
||||
mapView.showsCompass = true
|
||||
mapView.showsZoomControls = true
|
||||
mapView.showsPitchControl = true
|
||||
#else
|
||||
|
||||
|
||||
#if os(iOS)
|
||||
// Hide the default compass that only appears when you are not going north and instead always show the compass in the bottom right corner of the map
|
||||
mapView.showsCompass = false
|
||||
|
|
@ -78,19 +87,19 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
compassButton.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true
|
||||
compassButton.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -25).isActive = true
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
mapView.delegate = context.coordinator
|
||||
return mapView
|
||||
}
|
||||
|
||||
|
||||
func updateUIView(_ mapView: MKMapView, context: Context) {
|
||||
mapView.mapType = mapViewType
|
||||
|
||||
|
||||
if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile {
|
||||
mapView.removeOverlays(mapView.overlays)
|
||||
if self.customMapOverlay != nil {
|
||||
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path
|
||||
|
|
@ -109,40 +118,38 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let latest = positions
|
||||
.filter { $0.latest == true }
|
||||
.sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
|
||||
if showRouteLines {
|
||||
// Remove all existing PolyLine Overlays
|
||||
for overlay in mapView.overlays {
|
||||
if overlay is MKPolyline {
|
||||
mapView.removeOverlay(overlay)
|
||||
let annotationCount = waypoints.count + (showNodeHistory || showRouteLines ? positions.count : latest.count)
|
||||
if annotationCount > mapView.annotations.count {
|
||||
if showRouteLines {
|
||||
// Remove all existing PolyLine Overlays
|
||||
for overlay in mapView.overlays {
|
||||
if overlay is MKPolyline {
|
||||
mapView.removeOverlay(overlay)
|
||||
}
|
||||
}
|
||||
var lineIndex = 0
|
||||
for position in latest {
|
||||
|
||||
let nodePositions = positions.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 }
|
||||
let lineCoords = nodePositions.map ({
|
||||
(position) -> CLLocationCoordinate2D in
|
||||
return position.nodeCoordinate!
|
||||
})
|
||||
let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count)
|
||||
polyline.title = "\(String(position.nodePosition?.num ?? 0))-\(String(lineIndex))"
|
||||
mapView.addOverlay(polyline)
|
||||
lineIndex += 1
|
||||
// There are 10 colors for lines, start over if we are at index 10
|
||||
if lineIndex > 9 {
|
||||
lineIndex = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
var lineIndex = 0
|
||||
for position in latest {
|
||||
|
||||
let nodePositions = positions.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 }
|
||||
let lineCoords = nodePositions.map ({
|
||||
(position) -> CLLocationCoordinate2D in
|
||||
return position.nodeCoordinate!
|
||||
})
|
||||
let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count)
|
||||
polyline.title = "\(String(position.nodePosition?.num ?? 0))-\(String(lineIndex))"
|
||||
mapView.addOverlay(polyline)
|
||||
lineIndex += 1
|
||||
// There are 10 colors for lines, start over if we are at index 10
|
||||
if lineIndex > 9 {
|
||||
lineIndex = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let annotationCount = waypoints.count + positions.count
|
||||
if annotationCount != mapView.annotations.count {
|
||||
mapView.removeAnnotations(mapView.annotations)
|
||||
mapView.addAnnotations(waypoints)
|
||||
mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
|
|
@ -152,7 +159,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
if recenter {
|
||||
if showRouteLines || showNodeHistory {
|
||||
mapView.fit(annotations: showNodeHistory ? positions : positions, andShow: false)
|
||||
mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: false)
|
||||
} else {
|
||||
mapView.fitAllAnnotations()
|
||||
}
|
||||
|
|
@ -165,16 +172,16 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func makeCoordinator() -> MapCoordinator {
|
||||
return Coordinator(self)
|
||||
}
|
||||
|
||||
|
||||
final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate {
|
||||
|
||||
|
||||
var parent: MapViewSwiftUI
|
||||
var longPressRecognizer = UILongPressGestureRecognizer()
|
||||
|
||||
|
||||
init(_ parent: MapViewSwiftUI) {
|
||||
self.parent = parent
|
||||
super.init()
|
||||
|
|
@ -184,16 +191,16 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
self.longPressRecognizer.delegate = self
|
||||
self.parent.mapView.addGestureRecognizer(longPressRecognizer)
|
||||
}
|
||||
|
||||
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
|
||||
|
||||
switch annotation {
|
||||
case let positionAnnotation as PositionEntity:
|
||||
let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0)
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID )
|
||||
annotationView.tag = -1
|
||||
annotationView.canShowCallout = true
|
||||
|
||||
|
||||
if positionAnnotation.latest {
|
||||
annotationView.markerTintColor = .systemRed
|
||||
annotationView.displayPriority = .required
|
||||
|
|
@ -216,7 +223,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
let distanceFormatter = MKDistanceFormatter()
|
||||
subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n"
|
||||
if positionAnnotation.nodePosition?.metadata != nil {
|
||||
|
||||
|
||||
if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client ||
|
||||
DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute ||
|
||||
DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient {
|
||||
|
|
@ -230,7 +237,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.sensor {
|
||||
annotationView.glyphImage = UIImage(systemName: "sensor")
|
||||
}
|
||||
|
||||
|
||||
let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3))
|
||||
if pf.contains(.Satsinview) {
|
||||
subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n"
|
||||
|
|
@ -239,7 +246,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
subtitle.text! += "Sequence: \(String(positionAnnotation.seqNo)) \n"
|
||||
}
|
||||
if pf.contains(.Heading) {
|
||||
|
||||
|
||||
if parent.userTrackingMode != MKUserTrackingMode.followWithHeading {
|
||||
annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading))))
|
||||
subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n"
|
||||
|
|
@ -255,7 +262,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
}
|
||||
subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n"
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
// node metadata is nil
|
||||
annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
|
|
@ -316,23 +323,23 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
|
||||
// Only Allow Edit for waypoint annotations with a id
|
||||
if view.tag > 0 {
|
||||
parent.onWaypointEdit(view.tag)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) {
|
||||
|
||||
|
||||
if gesture.state != UIGestureRecognizer.State.ended {
|
||||
return
|
||||
} else if gesture.state != UIGestureRecognizer.State.began {
|
||||
|
||||
|
||||
// Screen Position - CGPoint
|
||||
let location = longPressRecognizer.location(in: self.parent.mapView)
|
||||
|
||||
|
||||
// Map Coordinate - CLLocationCoordinate2D
|
||||
let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView)
|
||||
let annotation = MKPointAnnotation()
|
||||
|
|
@ -343,9 +350,9 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
parent.onLongPress(coordinate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
|
||||
|
||||
if let tileOverlay = overlay as? MKTileOverlay {
|
||||
return MKTileOverlayRenderer(tileOverlay: tileOverlay)
|
||||
} else {
|
||||
|
|
@ -362,18 +369,18 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// is supposed to be located in the folder with the map name
|
||||
public struct DefaultTile: Hashable {
|
||||
let tileName: String
|
||||
let tileType: String
|
||||
|
||||
|
||||
public init(tileName: String, tileType: String) {
|
||||
self.tileName = tileName
|
||||
self.tileType = tileType
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct CustomMapOverlay: Equatable, Hashable {
|
||||
let mapName: String
|
||||
let tileType: String
|
||||
|
|
@ -381,7 +388,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
var minimumZoomLevel: Int?
|
||||
var maximumZoomLevel: Int?
|
||||
let defaultTile: DefaultTile?
|
||||
|
||||
|
||||
public init(
|
||||
mapName: String,
|
||||
tileType: String,
|
||||
|
|
@ -397,7 +404,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
self.maximumZoomLevel = maximumZoomLevel
|
||||
self.defaultTile = defaultTile
|
||||
}
|
||||
|
||||
|
||||
public init?(
|
||||
mapName: String?,
|
||||
tileType: String,
|
||||
|
|
@ -417,15 +424,15 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
self.defaultTile = defaultTile
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class CustomMapOverlaySource: MKTileOverlay {
|
||||
|
||||
|
||||
// requires folder: tiles/{mapName}/z/y/y,{tileType}
|
||||
private var parent: MapViewSwiftUI
|
||||
private let mapName: String
|
||||
private let tileType: String
|
||||
private let defaultTile: DefaultTile?
|
||||
|
||||
|
||||
public init(
|
||||
parent: MapViewSwiftUI,
|
||||
mapName: String,
|
||||
|
|
@ -438,7 +445,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
self.defaultTile = defaultTile
|
||||
super.init(urlTemplate: "")
|
||||
}
|
||||
|
||||
|
||||
public override func url(forTilePath path: MKTileOverlayPath) -> URL {
|
||||
if let tileUrl = Bundle.main.url(
|
||||
forResource: "\(path.y)",
|
||||
|
|
@ -460,31 +467,4 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// public struct Overlay {
|
||||
//
|
||||
// public static func == (lhs: MapViewSwiftUI.Overlay, rhs: MapViewSwiftUI.Overlay) -> Bool {
|
||||
// // maybe to use in the future for comparison of full array
|
||||
// lhs.shape.coordinate.latitude == rhs.shape.coordinate.latitude &&
|
||||
// lhs.shape.coordinate.longitude == rhs.shape.coordinate.longitude &&
|
||||
// lhs.fillColor == rhs.fillColor
|
||||
// }
|
||||
//
|
||||
// var shape: MKOverlay
|
||||
// var fillColor: UIColor?
|
||||
// var strokeColor: UIColor?
|
||||
// var lineWidth: CGFloat
|
||||
//
|
||||
// public init(
|
||||
// shape: MKOverlay,
|
||||
// fillColor: UIColor? = nil,
|
||||
// strokeColor: UIColor? = nil,
|
||||
// lineWidth: CGFloat = 0
|
||||
// ) {
|
||||
// self.shape = shape
|
||||
// self.fillColor = fillColor
|
||||
// self.strokeColor = strokeColor
|
||||
// self.lineWidth = lineWidth
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ struct DeviceMetricsLog: View {
|
|||
ForEach(node.telemetries?.reversed() as? [TelemetryEntity] ?? [], id: \.self) { (dm: TelemetryEntity) in
|
||||
if dm.metricsType == 0 {
|
||||
GridRow {
|
||||
if dm.batteryLevel == 0 {
|
||||
if dm.batteryLevel == 111 {
|
||||
Text("USB")
|
||||
.font(.caption)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ struct NodeDetail: View {
|
|||
if node.positions?.count ?? 0 > 0 {
|
||||
ZStack {
|
||||
let positionArray = node.positions?.array as? [PositionEntity] ?? []
|
||||
let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) }
|
||||
let lastThousand = Array(positionArray.prefix(1000))
|
||||
// let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) }
|
||||
ZStack {
|
||||
MapViewSwiftUI(onLongPress: { coord in
|
||||
waypointCoordinate = coord
|
||||
|
|
@ -71,7 +72,7 @@ struct NodeDetail: View {
|
|||
editingWaypoint = wpId
|
||||
presentingWaypointForm = true
|
||||
}
|
||||
}, positions: todaysPositions, waypoints: Array(waypoints),
|
||||
}, positions: lastThousand, waypoints: Array(waypoints),
|
||||
mapViewType: mapType,
|
||||
userTrackingMode: MKUserTrackingMode.none,
|
||||
showNodeHistory: meshMapShowNodeHistory,
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ struct NodeInfoView: View {
|
|||
} else {
|
||||
Text("Not Connected")
|
||||
.multilineTextAlignment(.leading)
|
||||
.font(.callout)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundStyle(.tint)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue