diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index 75830805..f7166f70 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -8,6 +8,7 @@ import SwiftUI import CoreLocation import OSLog +import CoreData // Shared state that manages the `CLLocationManager` and `CLBackgroundActivitySession`. @MainActor class LocationsHandler: ObservableObject { @@ -109,15 +110,43 @@ import OSLog } else { locationsArray = [location] } + UserDefaults.standard.set(location.coordinate.latitude, forKey: "lastKnownLatitude") + UserDefaults.standard.set(location.coordinate.longitude, forKey: "lastKnownLongitude") + UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastKnownLocationTimestamp") return true } - static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090) static var currentLocation: CLLocationCoordinate2D { - guard let location = shared.manager.location else { + if let location = shared.manager.location { + return location.coordinate + } else { + // Check authorization status + let status = shared.manager.authorizationStatus + switch status { + case .notDetermined: + Logger.services.info("📍 [App] Location permission not determined, requesting authorization") + shared.manager.requestWhenInUseAuthorization() + case .denied, .restricted: + Logger.services.warning("📍 [App] Location access denied or restricted. Please enable location services in Settings to get accurate positioning!") + shared.manager.requestWhenInUseAuthorization() + default: + break + } + // Fallback 1: Last known location from UserDefaults (if within 4 hours) + if let lat = UserDefaults.standard.object(forKey: "lastKnownLatitude") as? Double, + let lon = UserDefaults.standard.object(forKey: "lastKnownLongitude") as? Double, + let timestamp = UserDefaults.standard.object(forKey: "lastKnownLocationTimestamp") as? Double, + lat >= -90 && lat <= 90, + lon >= -180 && lon <= 180, + Date().timeIntervalSince1970 - timestamp <= 14_400 { // 4 hours in seconds + Logger.services.info("📍 [App] Falling back to last known location (age: \(Int(Date().timeIntervalSince1970 - timestamp)) seconds)") + return CLLocationCoordinate2D(latitude: lat, longitude: lon) + } + + // Fallback 2: Default location + Logger.services.warning("📍 [App] No Location and no last known location, something is really wrong. Teleporting user to Apple Park") return DefaultLocation } - return location.coordinate } static var satsInView: Int { diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index f5a7bda9..cb8608bd 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1035,22 +1035,35 @@ func textMessageAppPacket( } } + func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("Waypoint Packet received from node: %@".localized, String(packet.from)) Logger.mesh.info("📍 \(logString, privacy: .public)") - let fetchWaypointRequest = WaypointEntity.fetchRequest() - fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(packet.id)) - do { - if let waypointMessage = try? Waypoint(serializedBytes: packet.decoded.payload) { - let fetchedWaypoint = try context.fetch(fetchWaypointRequest) - if fetchedWaypoint.isEmpty { - let waypoint = WaypointEntity(context: context) + // Fetch waypoint by waypointMessage.id, not packet.id + let fetchWaypointRequest = WaypointEntity.fetchRequest() + fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(waypointMessage.id)) - waypoint.id = Int64(packet.id) + let fetchedWaypoint = try context.fetch(fetchWaypointRequest) + // Fetch the node info to get the short name + var nodeShortName: String = "?" + let fetchNodeRequest = NodeInfoEntity.fetchRequest() + fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + do { + let fetchedNode = try context.fetch(fetchNodeRequest) + if let node = fetchedNode.first, let user = node.user { + nodeShortName = user.shortName ?? node.user?.userId ?? String(packet.from.toHex()) + } + } catch { + Logger.data.error("Failed to fetch NodeInfoEntity for node \(packet.from.toHex(), privacy: .public): \(error)") + } + if fetchedWaypoint.isEmpty { + // Create a new waypoint + let waypoint = WaypointEntity(context: context) + waypoint.id = Int64(waypointMessage.id) // Use waypointMessage.id waypoint.name = waypointMessage.name waypoint.longDescription = waypointMessage.description_p waypoint.latitudeI = waypointMessage.latitudeI @@ -1073,7 +1086,7 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { manager.notifications = [ Notification( id: ("notification.id.\(waypoint.id)"), - title: "New Waypoint Received", + title: "New Waypoint From \(nodeShortName)", subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")", content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")", target: "map", @@ -1088,26 +1101,42 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)") } } else { - fetchedWaypoint[0].id = Int64(packet.id) - fetchedWaypoint[0].name = waypointMessage.name - fetchedWaypoint[0].longDescription = waypointMessage.description_p - fetchedWaypoint[0].latitudeI = waypointMessage.latitudeI - fetchedWaypoint[0].longitudeI = waypointMessage.longitudeI - fetchedWaypoint[0].icon = Int64(waypointMessage.icon) - fetchedWaypoint[0].locked = Int64(waypointMessage.lockedTo) - if waypointMessage.expire >= 1 { - fetchedWaypoint[0].expire = Date(timeIntervalSince1970: TimeInterval(Int64(waypointMessage.expire))) - } else { - fetchedWaypoint[0].expire = nil - } - fetchedWaypoint[0].lastUpdated = Date() - do { - try context.save() - Logger.data.info("💾 Updated Node Waypoint App Packet For: \(fetchedWaypoint[0].id, privacy: .public)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)") + // Update existing waypoint + let existingWaypoint = fetchedWaypoint[0] + if existingWaypoint.locked == 0 || existingWaypoint.locked == packet.from { + let currentTime = Int64(Date().timeIntervalSince1970) + if waypointMessage.expire > 0 && waypointMessage.expire <= currentTime { + context.delete(existingWaypoint) + do { + try context.save() + Logger.data.info("💾 Deleted a waypoint") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)") + } + } else { + existingWaypoint.name = waypointMessage.name + existingWaypoint.longDescription = waypointMessage.description_p + existingWaypoint.latitudeI = waypointMessage.latitudeI + existingWaypoint.longitudeI = waypointMessage.longitudeI + existingWaypoint.icon = Int64(waypointMessage.icon) + existingWaypoint.locked = Int64(waypointMessage.lockedTo) + if waypointMessage.expire >= 1 { + existingWaypoint.expire = Date(timeIntervalSince1970: TimeInterval(Int64(waypointMessage.expire))) + } else { + existingWaypoint.expire = nil + } + existingWaypoint.lastUpdated = Date() + do { + try context.save() + Logger.data.info("💾 Updated Node Waypoint App Packet For: \(existingWaypoint.id, privacy: .public)") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)") + } + } } } }