Remove location manager updates

This commit is contained in:
Garth Vander Houwen 2023-04-18 00:09:13 -07:00
parent d09523a962
commit d4abfd7729
13 changed files with 296 additions and 280 deletions

View file

@ -156,3 +156,28 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
}
}
}
enum Bandwidths: Int, CaseIterable, Identifiable {
case thirtyOne = 31
case sixtyTwo = 62
case oneHundredTwentyFive = 125
case twoHundredFifty = 250
case fiveHundred = 500
var id: Int { self.rawValue }
var description: String {
switch self {
case .thirtyOne:
return "31 kHz"
case .sixtyTwo:
return "62 kHz"
case .oneHundredTwentyFive:
return "125 kHz"
case .twoHundredFifty:
return "250 kHz"
case .fiveHundred:
return "500 kHz"
}
}
}

View file

@ -36,7 +36,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
var timeoutTimerCount = 0
var timeoutTimerRuns = 0
var positionTimer: Timer?
var lastPosition: CLLocation?
var lastPosition: CLLocationCoordinate2D?
let emptyNodeNum: UInt32 = 4294967295
/* Meshtastic Service Details */
var TORADIO_characteristic: CBCharacteristic!
@ -790,20 +790,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
}
}
}
let locationHelper = LocationHelper()
lastPosition = locationHelper.lastLocation
lastPosition = LocationHelper.currentLocation
var positionPacket = Position()
positionPacket.latitudeI = Int32(locationHelper.lastLocation?.coordinate.latitude ?? 0 * 1e7)
positionPacket.longitudeI = Int32(locationHelper.lastLocation?.coordinate.longitude ?? 0 * 1e7)
positionPacket.time = UInt32(locationHelper.lastLocation?.timestamp.timeIntervalSince1970 ?? 0)
positionPacket.timestamp = UInt32(locationHelper.lastLocation?.timestamp.timeIntervalSince1970 ?? 0)
positionPacket.altitude = Int32(locationHelper.lastLocation?.altitude ?? 0)
positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7)
positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7)
positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970)
positionPacket.timestamp = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970)
positionPacket.altitude = Int32(LocationHelper.currentAltitude)
positionPacket.satsInView = UInt32(LocationHelper.satsInView)
if LocationHelper.currentLocation.speed >= 0 {
positionPacket.groundSpeed = UInt32(locationHelper.lastLocation?.speed ?? 0 * 3.6)
if LocationHelper.currentSpeed >= 0 {
positionPacket.groundSpeed = UInt32(LocationHelper.currentSpeed * 3.6)
}
if LocationHelper.currentLocation.course >= 0 {
positionPacket.groundTrack = UInt32(locationHelper.lastLocation?.course ?? 0)
if LocationHelper.currentHeading >= 0 {
positionPacket.groundTrack = UInt32(LocationHelper.currentHeading)
}
var meshPacket = MeshPacket()
meshPacket.to = UInt32(destNum)

View file

@ -1,45 +1,58 @@
import CoreLocation
class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
class LocationHelper: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
static let shared = LocationHelper()
@Published var lastLocation: CLLocation?
override init() {
static let shared = LocationHelper()
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.allowsBackgroundLocationUpdates = true
locationManager.activityType = .otherNavigation
locationManager.requestWhenInUseAuthorization()
locationManager.distanceFilter = 5
locationManager.startUpdatingLocation()
}
// Apple Park
static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090)
static let DefaultAltitude = CLLocationDistance(integerLiteral: 0)
static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0)
static let DefaultHeading = CLLocationDirection(integerLiteral: 0)
// Apple Park
static let DefaultLocation = CLLocation(latitude: 37.3346, longitude: -122.0090)
static var currentLocation: CLLocation {
static var currentLocation: CLLocationCoordinate2D {
guard let location = shared.locationManager.location else {
return DefaultLocation
}
return location
}
/// Sats In View Estimator using horizontal and vertical accuracy since
/// CoreLocation does not have number of sats available
static var satsInView: Int {
// Invalid Coordinates
if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 || shared.locationManager.location?.horizontalAccuracy ?? 0 < 0 {
return 0
return DefaultLocation
}
return location.coordinate
}
static var currentAltitude: CLLocationDistance {
guard let altitude = shared.locationManager.location?.altitude else {
return DefaultAltitude
}
return altitude
}
static var currentSpeed: CLLocationSpeed {
guard let speed = shared.locationManager.location?.speed else {
return DefaultSpeed
}
return speed
}
static var currentHeading: CLLocationDirection {
guard let heading = shared.locationManager.location?.course else {
return DefaultHeading
}
return heading
}
static var currentTimestamp: Date {
guard let timestamp = shared.locationManager.location?.timestamp else {
return Date.now
}
return timestamp
}
static var satsInView: Int {
// If we have a position we have a sat
var sats = 1
// If we have a 3D fix we have at least 4 sats
if shared.locationManager.location?.verticalAccuracy ?? 0 > 0 {
sats = 4
if 0...5 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 {
@ -53,32 +66,32 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
} else if 46...60 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 {
sats = 5
}
} else if shared.locationManager.location?.horizontalAccuracy ?? 0 <= 300 {
// Need at least 3 sats to be under 300, over that could be wifi or cell triangulation
} else if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 && 60...300 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 {
sats = 3
} else if shared.locationManager.location?.horizontalAccuracy ?? 0 > 300 {
} else if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 && shared.locationManager.location?.horizontalAccuracy ?? 0 > 300 {
sats = 2
}
return sats
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let mostRecentLocation = locations.last else {
return
}
// Extra Smart positioning logic throwing out bad readings from the phone GPS
let age = -mostRecentLocation.timestamp.timeIntervalSinceNow
manager.stopUpdatingLocation()
if age > 10 || mostRecentLocation.horizontalAccuracy < 0 || mostRecentLocation.horizontalAccuracy > 100 {
print("Bad Location: HA-\(mostRecentLocation.horizontalAccuracy) VA-\(mostRecentLocation.verticalAccuracy) AGE-\(age)")
manager.startUpdatingLocation()
} else {
print("Good Location: HA-\(mostRecentLocation.horizontalAccuracy) VA-\(mostRecentLocation.verticalAccuracy) AGE-\(age)")
lastLocation = mostRecentLocation
}
private let locationManager = CLLocationManager()
private override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.allowsBackgroundLocationUpdates = true
locationManager.activityType = .otherNavigation
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
extension LocationHelper: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { }
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location manager failed with error: \(error.localizedDescription)")
}

View file

@ -58,7 +58,7 @@ extension PositionEntity {
}
extension PositionEntity: MKAnnotation {
public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationHelper.DefaultLocation.coordinate }
public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationHelper.DefaultLocation }
public var title: String? { nodePosition?.user?.shortName ?? NSLocalizedString("unknown", comment: "Unknown") }
public var subtitle: String? { time?.formatted() }
}

View file

@ -48,7 +48,7 @@ extension WaypointEntity {
}
extension WaypointEntity: MKAnnotation {
public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationHelper.DefaultLocation.coordinate }
public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationHelper.DefaultLocation }
public var title: String? { name ?? "Dropped Pin" }
public var subtitle: String? {
(longDescription ?? "") +

View file

@ -210,7 +210,7 @@ struct NodeWeatherForecast {
struct NodeWeatherForecastView_Previews: PreviewProvider {
static var previews: some View {
NodeWeatherForecastView(location: CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude) )
NodeWeatherForecastView(location: CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) )
.aspectRatio(2, contentMode: .fit)
.padding()
.previewLayout(.sizeThatFits)

View file

@ -13,29 +13,23 @@ func degreesToRadians(_ number: Double) -> Double {
struct MapViewSwiftUI: UIViewRepresentable {
var onLongPress: ((CLLocationCoordinate2D) -> Void)
var onWaypointEdit: ((Int) -> Void)
var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
var onWaypointEdit: (_ waypointId: Int ) -> Void
let mapView = MKMapView()
// Parameters
let positions: [PositionEntity]
let waypoints: [WaypointEntity]
let mapViewType: MKMapType
let userTrackingMode: MKUserTrackingMode
// User Defaults Values
let showNodeHistory: Bool
let showRouteLines: Bool
@AppStorage("meshMapRecentering") private var recenter: Bool = false
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
// Offline Map Tiles
@AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
@State private var loadedLastUpdatedLocalMapFile = 0
var customMapOverlay: CustomMapOverlay?
@State private var presentCustomMapOverlayHash: CustomMapOverlay?
// Custom Tile Server
@AppStorage("meshMapCustomTileServer") private var tileServerUrl = ""
var tileRenderer: MKTileOverlayRenderer?
let tileServer: MapTileServerLinks = .openStreetMaps
func makeUIView(context: Context) -> MKMapView {
// Map View Parameters
mapView.mapType = mapViewType
@ -45,7 +39,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
.filter { $0.latest == true }
.sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation.coordinate
let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation
let region = MKCoordinateRegion(center: center, span: span)
mapView.addAnnotations(showNodeHistory ? positions : latest)
mapView.setRegion(region, animated: true)
@ -90,10 +84,6 @@ struct MapViewSwiftUI: UIViewRepresentable {
#endif
#endif
if tileServerUrl.count > 0 {
context.coordinator.setupTileServerRenderer()
}
mapView.delegate = context.coordinator
return mapView
}
@ -126,72 +116,54 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
}
let latest = positions
.filter { $0.latest == true }
.sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count)
print("Waypoint Count: \(waypoints.count)")
print("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)")
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(waypoints)
mapView.addAnnotations(showNodeHistory ? positions : latest)
// Remove all existing PolyLine Overlays
for overlay in mapView.overlays {
if overlay is MKPolyline {
mapView.removeOverlay(overlay)
}
}
if showRouteLines {
DispatchQueue.main.async {
let latest = positions
.filter { $0.latest == true }
.sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count)
var lineIndex = 0
for position in latest {
let nodePositions = positions.filter { $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))"
mapView.addOverlay(polyline)
lineIndex += 1
// There are 18 colors for lines, start over if we are at index 17
if lineIndex > 17 {
lineIndex = 0
if annotationCount != mapView.annotations.count {
print("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)")
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(waypoints)
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.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))"
mapView.addOverlay(polyline)
lineIndex += 1
// There are 18 colors for lines, start over if we are at index 17
if lineIndex > 17 {
lineIndex = 0
}
}
}
}
}
if userTrackingMode == MKUserTrackingMode.none {
mapView.showsUserLocation = false
if recenter {
mapView.fit(annotations:showNodeHistory || showRouteLines ? positions : latest, andShow: false)
}
} else {
// Centering Done by tracking mode
mapView.showsUserLocation = true
}
mapView.setUserTrackingMode(userTrackingMode, animated: true)
if tileServerUrl.count > 0 {
tileRenderer?.alpha = 0.0
let overlays = mapView.overlays
if mapView.mapType == .standard {
let overlay = MKTileOverlay(urlTemplate: tileServerUrl)
if overlays.contains(where: {$0 is MKPolyline}) {
mapView.addOverlay(overlay, level: .aboveLabels)
if let poly_overlay = overlays.filter({$0 is MKPolyline}).first {
mapView.addOverlay(poly_overlay, level: .aboveRoads)
if userTrackingMode == MKUserTrackingMode.none {
mapView.showsUserLocation = false
mapView.addAnnotations(showNodeHistory ? positions : latest)
if recenter {
mapView.fit(annotations:showNodeHistory || showRouteLines ? positions : latest, andShow: false)
}
} else {
mapView.addOverlay(overlay, level: .aboveRoads)
}
} else {
for overlay in overlays {
if let ove = overlay as? MKTileOverlay {
mapView.removeOverlay(ove)
}
// Centering Done by tracking mode
mapView.addAnnotations(showNodeHistory ? positions : latest)
mapView.showsUserLocation = true
}
mapView.setUserTrackingMode(userTrackingMode, animated: true)
}
}
}
@ -209,6 +181,9 @@ struct MapViewSwiftUI: UIViewRepresentable {
self.parent = parent
super.init()
self.longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
self.longPressRecognizer.minimumPressDuration = 0.5
self.longPressRecognizer.cancelsTouchesInView = true
self.longPressRecognizer.delegate = self
self.parent.mapView.addGestureRecognizer(longPressRecognizer)
}
@ -226,7 +201,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
annotationView.displayPriority = .required
annotationView.titleVisibility = .visible
} else {
annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0))
annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0))
annotationView.displayPriority = .defaultHigh
annotationView.titleVisibility = .adaptive
}
@ -288,7 +263,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
annotationView.glyphImage = UIImage(systemName: "flipphone")
}
if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation.coordinate)
let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
subtitle.text! += NSLocalizedString("distance", comment: "") + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
}
subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n"
@ -321,7 +296,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
subtitle.text = ""
}
if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation.coordinate)
let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
let distanceFormatter = MKDistanceFormatter()
subtitle.text! += NSLocalizedString("distance", comment: "") + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
}
@ -351,20 +326,23 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
}
@objc func longPressHandler(_ sender: UILongPressGestureRecognizer) {
@objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) {
if sender.state == .began {
let point = sender.location(in: sender.view)
if let mapView = sender.view as? MKMapView {
let coordinate = mapView.convert(point, toCoordinateFrom: mapView)
let annotation = MKPointAnnotation()
print("Handler Coord - \(coordinate)")
annotation.title = "Dropped Pin"
annotation.coordinate = coordinate
parent.mapView.addAnnotation(annotation)
UINotificationFeedbackGenerator().notificationOccurred(.success)
parent.onLongPress(coordinate)
}
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()
annotation.title = "Dropped Pin"
annotation.coordinate = coordinate
parent.mapView.addAnnotation(annotation)
UINotificationFeedbackGenerator().notificationOccurred(.success)
parent.onLongPress(coordinate)
}
}
@ -384,14 +362,6 @@ struct MapViewSwiftUI: UIViewRepresentable {
return MKOverlayRenderer()
}
}
func setupTileServerRenderer() {
//let template = "https://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}.jpg"
//let template = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.jpg"
let overlay = MKTileOverlay(urlTemplate: parent.tileServerUrl)
parent.mapView.addOverlay(overlay, level: .aboveLabels)
parent.tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
}
}
/// is supposed to be located in the folder with the map name

View file

@ -12,29 +12,32 @@ struct WaypointFormView: View {
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var dismiss
@State var coordinate: WaypointCoordinate
@State var coordinate: CLLocationCoordinate2D
@State var waypointId: Int = 0
@FocusState private var iconIsFocused: Bool
@State private var name: String = ""
@State private var description: String = ""
@State private var icon: String = "📍"
@State private var latitude: Double = 0
@State private var longitude: Double = 0
@State private var expires: Bool = false
@State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours
@State private var expire: Date = Date() // = Date.now.addingTimeInterval(60 * 120) // 1 minute * 120 = 2 Hours
@State private var locked: Bool = false
@State private var lockedTo: Int64 = 0
var body: some View {
Form {
let distance = CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude).distance(from: CLLocation(latitude: coordinate.coordinate?.latitude ?? 0, longitude: coordinate.coordinate?.longitude ?? 0))
Section(header: Text((coordinate.waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) {
let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude))
Section(header: Text((waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) {
HStack {
Text("Location: \(String(format: "%.5f", latitude) + "," + String(format: "%.5f", longitude))")
.textSelection(.enabled)
.foregroundColor(Color.gray)
.font(.caption2)
if coordinate.coordinate?.latitude ?? 0 != 0 && coordinate.coordinate?.longitude ?? 0 != 0 {
if coordinate.latitude != LocationHelper.DefaultLocation.latitude && coordinate.longitude != LocationHelper.DefaultLocation.longitude {
DistanceText(meters: distance)
.foregroundColor(Color.gray)
.font(.caption2)
@ -125,26 +128,23 @@ struct WaypointFormView: View {
Button {
var newWaypoint = Waypoint()
// Loading a waypoint from edit
if coordinate.waypointId > 0 {
newWaypoint.id = UInt32(coordinate.waypointId)
let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!)
newWaypoint.latitudeI = waypoint.latitudeI
newWaypoint.longitudeI = waypoint.longitudeI
if waypointId > 0 {
newWaypoint.id = UInt32(waypointId)
} else {
// New waypoint
newWaypoint.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
newWaypoint.latitudeI = Int32(Double(coordinate.coordinate?.latitude ?? 0) * 1e7)
newWaypoint.longitudeI = Int32(Double(coordinate.coordinate?.longitude ?? 0) * 1e7)
}
newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
newWaypoint.description_p = description
newWaypoint.latitudeI = Int32(coordinate.latitude * 1e7)
newWaypoint.longitudeI = Int32(coordinate.longitude * 1e7)
// Unicode scalar value for the icon emoji string
let unicodeScalers = icon.unicodeScalars
// First element as an UInt32
let unicode = unicodeScalers[unicodeScalers.startIndex].value
newWaypoint.icon = unicode
if locked {
if lockedTo == 0 {
newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
} else {
@ -157,8 +157,10 @@ struct WaypointFormView: View {
newWaypoint.expire = 0
}
if bleManager.sendWaypoint(waypoint: newWaypoint) {
waypointId = 0
dismiss()
} else {
waypointId = 0
dismiss()
print("Send waypoint failed")
}
@ -181,11 +183,11 @@ struct WaypointFormView: View {
.controlSize(.large)
.padding(.bottom)
if coordinate.waypointId > 0 {
if waypointId > 0 {
Menu {
Button("For me", action: {
let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!)
let waypoint = getWaypoint(id: Int64(waypointId), context: bleManager.context!)
bleManager.context!.delete(waypoint)
do {
try bleManager.context!.save()
@ -196,19 +198,20 @@ struct WaypointFormView: View {
Button("For everyone", action: {
var newWaypoint = Waypoint()
if coordinate.waypointId > 0 {
newWaypoint.id = UInt32(coordinate.waypointId)
if waypointId > 0 {
newWaypoint.id = UInt32(waypointId)
}
newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
newWaypoint.description_p = description
newWaypoint.latitudeI = Int32(coordinate.coordinate?.latitude ?? 0 * 1e7)
newWaypoint.longitudeI = Int32(coordinate.coordinate?.longitude ?? 0 * 1e7)
newWaypoint.latitudeI = Int32(coordinate.latitude * 1e7)
newWaypoint.longitudeI = Int32(coordinate.longitude * 1e7)
// Unicode scalar value for the icon emoji string
let unicodeScalers = icon.unicodeScalars
// First element as an UInt32
let unicode = unicodeScalers[unicodeScalers.startIndex].value
newWaypoint.icon = unicode
if locked {
if lockedTo == 0 {
newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
} else {
@ -217,8 +220,10 @@ struct WaypointFormView: View {
}
newWaypoint.expire = 1
if bleManager.sendWaypoint(waypoint: newWaypoint) {
waypointId = 0
dismiss()
} else {
waypointId = 0
dismiss()
print("Send waypoint failed")
}
@ -234,9 +239,14 @@ struct WaypointFormView: View {
.padding(.bottom)
}
}
.onChange(of: waypointId) { newId in
print(newId)
}
.onAppear {
if coordinate.waypointId > 0 {
let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!)
if waypointId > 0 {
let waypoint = getWaypoint(id: Int64(waypointId), context: bleManager.context!)
waypointId = Int(waypoint.id)
name = waypoint.name ?? "Dropped Pin"
description = waypoint.longDescription ?? ""
icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")
@ -257,10 +267,10 @@ struct WaypointFormView: View {
description = ""
locked = false
expires = false
expire = Date.now.addingTimeInterval(60 * 480)
expire = Date.now.addingTimeInterval(60 * 120)
icon = "📍"
latitude = coordinate.coordinate?.latitude ?? 0
longitude = coordinate.coordinate?.longitude ?? 0
latitude = coordinate.latitude
longitude = coordinate.longitude
}
}
}

View file

@ -14,8 +14,11 @@ struct NodeDetail: View {
@EnvironmentObject var bleManager: BLEManager
@Environment(\.colorScheme) var colorScheme: ColorScheme
@AppStorage("meshMapType") private var meshMapType = "standard"
@AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
@AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false
@State private var mapType: MKMapType = .standard
@State var waypointCoordinate: WaypointCoordinate?
@State var waypointCoordinate: CLLocationCoordinate2D?
@State var editingWaypoint: Int = 0
@State private var loadedWeather: Bool = false
@State private var showingDetailsPopover = false
@State private var showingForecast = false
@ -61,14 +64,19 @@ struct NodeDetail: View {
// let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) }
ZStack {
MapViewSwiftUI(onLongPress: { coord in
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0)
waypointCoordinate = coord
editingWaypoint = 0
presentingWaypointForm = true
}, onWaypointEdit: { wpId in
if wpId > 0 {
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
editingWaypoint = wpId
presentingWaypointForm = true
}
}, positions: lastTenThousand, waypoints: Array(waypoints),
mapViewType: mapType,
userTrackingMode: MKUserTrackingMode.none,
showNodeHistory: meshMapShowNodeHistory,
showRouteLines: meshMapShowRouteLines,
customMapOverlay: self.customMapOverlay
)
VStack(alignment: .leading) {
@ -100,7 +108,7 @@ struct NodeDetail: View {
.font(.title)
.padding()
let nodeLocation = node.positions?.lastObject as? PositionEntity
NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.coordinate.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.coordinate.longitude) )
NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) )
.frame(height: 250)
}
#else
@ -109,7 +117,7 @@ struct NodeDetail: View {
.font(.title)
.padding()
let nodeLocation = node.positions?.lastObject as? PositionEntity
NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.coordinate.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.coordinate.longitude) ).frame(height: 250)
NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) ).frame(height: 250)
.presentationDetents([.medium])
.presentationDragIndicator(.automatic)
}
@ -202,11 +210,11 @@ struct NodeDetail: View {
}
}
.edgesIgnoringSafeArea([.leading, .trailing])
.sheet(item: $waypointCoordinate, content: { wpc in
WaypointFormView(coordinate: wpc)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.automatic)
})
.sheet(isPresented: $presentingWaypointForm ) {// , onDismiss: didDismissSheet) {
WaypointFormView(coordinate: waypointCoordinate ?? LocationHelper.DefaultLocation, waypointId: editingWaypoint)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.automatic)
}
.navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "")), displayMode: .inline)
.navigationBarItems(trailing:
ZStack {
@ -217,8 +225,22 @@ struct NodeDetail: View {
})
.onAppear {
self.bleManager.context = context
let currentMapType = MeshMapType(rawValue: meshMapType)
mapType = currentMapType?.MKMapTypeValue() ?? .standard
switch meshMapType {
case "standard":
mapType = .standard
case "mutedStandard":
mapType = .mutedStandard
case "hybrid":
mapType = .hybrid
case "hybridFlyover":
mapType = .hybridFlyover
case "satellite":
mapType = .satellite
case "satelliteFlyover":
mapType = .satelliteFlyover
default:
mapType = .hybridFlyover
}
}
.task(id: node.num) {
if !loadedWeather {
@ -228,7 +250,7 @@ struct NodeDetail: View {
let mostRecent = node.positions?.lastObject as? PositionEntity
let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude))
let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude))
condition = weather.currentWeather.condition
temperature = weather.currentWeather.temperature
humidity = Int(weather.currentWeather.humidity * 100)

View file

@ -25,10 +25,10 @@ struct NodeList: View {
@State private var selection: NodeInfoEntity? // Nothing selected by default.
var body: some View {
var body: some View {
NavigationSplitView {
List(nodes, id: \.self, selection: $selection) { node in
List(nodes, id: \.self, selection: $selection) { node in
if nodes.count == 0 {
Text("no.nodes").font(.title)
} else {
@ -52,8 +52,8 @@ struct NodeList: View {
if node.positions?.count ?? 0 > 0 && (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 != node.num) {
HStack(alignment: .bottom) {
let lastPostion = node.positions!.reversed()[0] as! PositionEntity
let myCoord = CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude)
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.coordinate.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.coordinate.latitude {
let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.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")

View file

@ -10,14 +10,6 @@ import MapKit
import CoreLocation
import CoreData
// A simple struct with waypoint data
struct WaypointCoordinate: Identifiable {
let id: UUID
let coordinate: CLLocationCoordinate2D?
let waypointId: Int64
}
struct NodeMap: View {
@Environment(\.managedObjectContext) var context
@ -37,8 +29,10 @@ struct NodeMap: View {
}
}
}
@AppStorage("meshMapType") private var meshMapType = "standard"
@AppStorage("meshMapType") private var meshMapType = "hybridFlyover"
@AppStorage("meshMapUserTrackingMode") private var meshMapUserTrackingMode = 0
@AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
@AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)],
predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none)
@ -52,7 +46,9 @@ struct NodeMap: View {
@State private var mapType: MKMapType = .standard
@State private var userTrackingMode: MKUserTrackingMode = .none
@State var waypointCoordinate: WaypointCoordinate?
@State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation
@State var editingWaypoint: Int = 0
@State private var presentingWaypointForm = false
@State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay(
mapName: "offlinemap",
tileType: "png",
@ -64,17 +60,25 @@ struct NodeMap: View {
NavigationStack {
ZStack {
MapViewSwiftUI(
onLongPress: { coord in
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0)
MapViewSwiftUI(onLongPress: { coord in
waypointCoordinate = coord
editingWaypoint = 0
if waypointCoordinate.distance(from: LocationHelper.DefaultLocation) == 0.0 {
print("Apple Park")
} else {
presentingWaypointForm = true
}
}, onWaypointEdit: { wpId in
if wpId > 0 {
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
editingWaypoint = wpId
presentingWaypointForm = true
}
}, positions: Array(positions),
waypoints: Array(waypoints),
mapViewType: mapType,
userTrackingMode: userTrackingMode,
showNodeHistory: meshMapShowNodeHistory,
showRouteLines: meshMapShowRouteLines,
customMapOverlay: self.customMapOverlay
)
VStack {
@ -91,11 +95,12 @@ struct NodeMap: View {
}
.ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
.frame(maxHeight: .infinity)
.sheet(item: $waypointCoordinate, content: { wpc in
WaypointFormView(coordinate: wpc)
.sheet(isPresented: $presentingWaypointForm ) {// , onDismiss: didDismissSheet) {
WaypointFormView(coordinate: waypointCoordinate, waypointId: editingWaypoint)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.automatic)
})
}
}
.navigationBarItems(leading:
MeshtasticLogo(), trailing:
@ -111,11 +116,25 @@ struct NodeMap: View {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
userTrackingMode = UserTrackingModes(rawValue: meshMapUserTrackingMode)?.MKUserTrackingModeValue() ?? MKUserTrackingMode.none
let currentMapType = MeshMapType(rawValue: meshMapType)
mapType = currentMapType?.MKMapTypeValue() ?? .standard
switch meshMapType {
case "standard":
mapType = .standard
case "mutedStandard":
mapType = .mutedStandard
case "hybrid":
mapType = .hybrid
case "hybridFlyover":
mapType = .hybridFlyover
case "satellite":
mapType = .satellite
case "satelliteFlyover":
mapType = .satelliteFlyover
default:
mapType = .hybridFlyover
}
})
.onDisappear(perform: {
UIApplication.shared.isIdleTimerDisabled = false
})
}
}
}

View file

@ -9,12 +9,11 @@ struct AppSettings: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
@StateObject var locationHelper = LocationHelper()
@State private var isPresentingCoreDataResetConfirm = false
@State private var preferredDeviceConnected = false
var body: some View {
var body: some View {
VStack {
Form {
Section(header: Text("user.details")) {
@ -26,6 +25,7 @@ struct AppSettings: View {
}
.keyboardType(.asciiCapable)
.disableAutocorrection(true)
.listRowSeparator(.visible)
}
Section(header: Text("options")) {
@ -39,30 +39,7 @@ struct AppSettings: View {
}
Section(header: Text("phone.gps")) {
let accuracy = Measurement(value: locationHelper.lastLocation?.horizontalAccuracy ?? 300, unit: UnitLength.meters)
let altitiude = Measurement(value: locationHelper.lastLocation?.altitude ?? 0, unit: UnitLength.meters)
let speed = Measurement(value: locationHelper.lastLocation?.speed ?? 0, unit: UnitSpeed.kilometersPerHour)
HStack {
Label("Accuracy \(accuracy.formatted())", systemImage: "scope")
.font(.callout)
Label("Sats \(LocationHelper.satsInView)", systemImage: "sparkles")
.font(.callout)
}
Label("Coordinates \(String(format: "%.5f", locationHelper.lastLocation?.coordinate.latitude ?? 0)), \(String(format: "%.5f", locationHelper.lastLocation?.coordinate.longitude ?? 0))", systemImage: "mappin")
.font(.callout)
.textSelection(.enabled)
if LocationHelper.currentLocation.verticalAccuracy > 0 {
Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2")
.font(.callout)
}
if locationHelper.lastLocation?.courseAccuracy ?? 0 > 0 {
Label("Heading \(String(format: "%.2f", locationHelper.lastLocation?.course ?? 0))°", systemImage: "location.circle")
.font(.callout)
}
if locationHelper.lastLocation?.speedAccuracy ?? 0 > 0 {
Label("Speed \(speed.formatted())", systemImage: "speedometer")
.font(.callout)
}
Toggle(isOn: $userSettings.provideLocation) {
Label("provide.location", systemImage: "location.circle.fill")
@ -119,19 +96,6 @@ struct AppSettings: View {
Label("Show Route Lines", systemImage: "road.lanes")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
HStack {
Label("Tile Server", systemImage: "square.grid.3x2")
TextField(
"Tile Server",
text: $userSettings.meshMapCustomTileServer,
axis: .vertical
)
.foregroundColor(.gray)
}
.keyboardType(.asciiCapable)
.disableAutocorrection(true)
}
}
HStack {

View file

@ -112,17 +112,11 @@ struct LoRaConfig: View {
if !usePreset {
HStack {
Picker("Bandwidth", selection: $spreadFactor) {
Text("31 kHz")
.tag(31)
Text("62 kHz")
.tag(62)
Text("125 kHz")
.tag(125)
Text("250 kHz")
.tag(0)
Text("500 kHz")
.tag(500)
Picker("Bandwidth", selection: $bandwidth) {
ForEach(Bandwidths.allCases) { bw in
Text(bw.description)
.tag(bw.rawValue == 250 ? 0 : bw.rawValue)
}
}
}
HStack {
@ -143,7 +137,7 @@ struct LoRaConfig: View {
}
}
Picker("Number of hops", selection: $codingRate) {
Picker("Number of hops", selection: $hopLimit) {
ForEach(1..<8) {
Text("\($0)")
.tag($0 == 3 ? 0 : $0)
@ -290,7 +284,7 @@ struct LoRaConfig: View {
}
}
func setLoRaValues() {
self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 3)
self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 0)
self.region = Int(node?.loRaConfig?.regionCode ?? 0)
self.usePreset = node?.loRaConfig?.usePreset ?? true
self.modemPreset = Int(node?.loRaConfig?.modemPreset ?? 0)