diff --git a/Localizable.xcstrings b/Localizable.xcstrings index eec7ee0d..b5d0c59d 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -11388,6 +11388,9 @@ } } } + }, + "Expiration" : { + }, "Expire" : { "localizations" : { @@ -15747,6 +15750,12 @@ } } } + }, + "Latitude in degrees (e.g., 37.7749)" : { + + }, + "Latitude must be between -90 and 90 degrees" : { + }, "LED Heartbeat" : { "localizations" : { @@ -16055,6 +16064,7 @@ } }, "Location" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -16493,6 +16503,12 @@ } } } + }, + "Longitude in degrees (e.g., -122.4194)" : { + + }, + "Longitude must be between -180 and 180 degrees" : { + }, "LoRa" : { "localizations" : { @@ -27872,6 +27888,9 @@ } } } + }, + "Set to current location" : { + }, "Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully. O hop broadcast messages will not get ACKs." : { "localizations" : { @@ -34253,6 +34272,9 @@ } } } + }, + "Waypoint Failed to Send" : { + }, "Waypoint Options" : { "localizations" : { diff --git a/Meshtastic/AppIntents/SendWaypointIntent.swift b/Meshtastic/AppIntents/SendWaypointIntent.swift index 4352c548..fb0f97c3 100644 --- a/Meshtastic/AppIntents/SendWaypointIntent.swift +++ b/Meshtastic/AppIntents/SendWaypointIntent.swift @@ -11,6 +11,8 @@ import AppIntents import MeshtasticProtobufs struct SendWaypointIntent: AppIntent { + + var defaultDate = Date.now.addingTimeInterval(60 * 480) static var title = LocalizedStringResource("Send a Waypoint") @@ -23,13 +25,24 @@ struct SendWaypointIntent: AppIntent { @Parameter(title: "Emoji", default: "šŸ“") var emojiParameter: String? - @Parameter(title: "Location") - var locationParameter: CLPlacemark + // Replace CLPlacemark with latitude and longitude parameters + @Parameter(title: "Latitude", description: "Latitude in degrees (e.g., 37.7749)") + var latitudeParameter: Double + + @Parameter(title: "Longitude", description: "Longitude in degrees (e.g., -122.4194)") + var longitudeParameter: Double + + @Parameter(title: "Locked", default: false) + var isLocked: Bool + + @Parameter(title: "Expiration") + var expiration: Date? func perform() async throws -> some IntentResult { if !BLEManager.shared.isConnected { throw AppIntentErrors.AppIntentError.notConnected } + // Provide default values if parameters are nil let name = nameParameter ?? "Dropped Pin" let description = descriptionParameter ?? "" @@ -50,24 +63,39 @@ struct SendWaypointIntent: AppIntent { throw $emojiParameter.needsValueError("Must be a single emoji") } + // Validate latitude and longitude + guard abs(latitudeParameter) <= 90 else { + throw $latitudeParameter.needsValueError("Latitude must be between -90 and 90 degrees") + } + guard abs(longitudeParameter) <= 180 else { + throw $longitudeParameter.needsValueError("Longitude must be between -180 and 180 degrees") + } + var newWaypoint = Waypoint() - if let latitude = locationParameter.location?.coordinate.latitude { - newWaypoint.latitudeI = Int32(latitude * 10_000_000) - } - - if let longitude = locationParameter.location?.coordinate.longitude { - newWaypoint.longitudeI = Int32(longitude * 10_000_000) - } + // Set latitude and longitude directly + newWaypoint.latitudeI = Int32(latitudeParameter * 10_000_000) + newWaypoint.longitudeI = Int32(longitudeParameter * 10_000_000) newWaypoint.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - // This regex pattern is for matching a single emoji let emojiPattern = "^([\\p{So}\\p{Cn}])$" let regex = try? NSRegularExpression(pattern: emojiPattern, options: []) let matches = regex?.matches(in: emoji, options: [], range: NSRange(location: 0, length: emoji.utf16.count)) - return matches?.count == 1 } } diff --git a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift index 2f538b62..4f2923eb 100644 --- a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift @@ -14,9 +14,6 @@ extension WaypointEntity { static func allWaypointssFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = WaypointEntity.fetchRequest() request.fetchLimit = 50 - // request.fetchBatchSize = 1 - // request.returnsObjectsAsFaults = false - // request.includesSubentities = true request.returnsDistinctResults = true request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: false)] request.predicate = NSPredicate(format: "expire == nil || expire >= %@", Date() as NSDate) @@ -24,7 +21,6 @@ extension WaypointEntity { } var latitude: Double? { - let d = Double(latitudeI) if d == 0 { return 0 @@ -33,7 +29,6 @@ extension WaypointEntity { } var longitude: Double? { - let d = Double(longitudeI) if d == 0 { return 0 @@ -46,7 +41,7 @@ extension WaypointEntity { let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!) return coord } else { - return nil + return nil } } @@ -60,16 +55,29 @@ extension WaypointEntity { } extension WaypointEntity: MKAnnotation { - public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationsHandler.DefaultLocation } - public var title: String? { name ?? "Dropped Pin" } + @MainActor + public var coordinate: CLLocationCoordinate2D { + get { + waypointCoordinate ?? LocationsHandler.DefaultLocation + } + set { + latitudeI = Int32(newValue.latitude * 1e7) + longitudeI = Int32(newValue.longitude * 1e7) + } + } + + public var title: String? { + name ?? "Dropped Pin" + } + public var subtitle: String? { (longDescription ?? "") + String(expire != nil ? "\nāŒ› Expires \(String(describing: expire?.formatted()))" : "") + - String(locked > 0 ? "\nšŸ”’ Locked" : "") } + String(locked > 0 ? "\nšŸ”’ Locked" : "") + } } struct WaypointCoordinate: Identifiable { - let id: UUID let coordinate: CLLocationCoordinate2D? let waypointId: Int64 diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 050958e0..59571549 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1183,7 +1183,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } public func sendWaypoint(waypoint: Waypoint) -> Bool { - if waypoint.latitudeI == 373346000 && waypoint.longitudeI == -1220090000 { + if waypoint.latitudeI == 0 && waypoint.longitudeI == 0 { return false } var success = false diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index ad342cbc..5279dfe2 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -30,6 +30,7 @@ struct WaypointForm: View { @State private var lockedTo: Int64 = 0 @State private var detents: Set = [.medium, .fraction(0.85)] @State private var selectedDetent: PresentationDetent = .medium + @State private var waypointFailedAlert: Bool = false var body: some View { NavigationStack { @@ -47,6 +48,15 @@ struct WaypointForm: View { .textSelection(.enabled) .foregroundColor(.secondary) .font(.caption) + + Button { + let currentLoc = LocationsHandler.currentLocation + waypoint.coordinate.longitude = currentLoc.longitude + waypoint.coordinate.latitude = currentLoc.latitude + } label: { + Image(systemName: "location") + } + .accessibilityLabel("Set to current location") } HStack { if waypoint.coordinate.latitude != 0 && waypoint.coordinate.longitude != 0 { @@ -72,6 +82,7 @@ struct WaypointForm: View { name = String(name.dropLast()) totalBytes = name.utf8.count } + waypoint.name = name.count > 0 ? name : "Dropped Pin" } } HStack { @@ -167,8 +178,8 @@ struct WaypointForm: View { if bleManager.sendWaypoint(waypoint: newWaypoint) { dismiss() } else { - dismiss() Logger.mesh.warning("Send waypoint failed") + waypointFailedAlert = true } } else { Logger.mesh.warning("Send waypoint failed, node not connected") @@ -233,8 +244,8 @@ struct WaypointForm: View { } dismiss() } else { - dismiss() Logger.mesh.warning("Send waypoint failed") + waypointFailedAlert = true } }) } @@ -256,8 +267,8 @@ struct WaypointForm: View { Text(waypoint.name ?? "?") .font(.largeTitle) Spacer() - if waypoint.locked > 0 { - Image(systemName: "lock.fill" ) + if waypoint.locked > 0 && waypoint.locked != UInt32(BLEManager.shared.connectedPeripheral?.num ?? 0) { + Image(systemName: "lock.fill") .font(.largeTitle) } else { Button { @@ -368,6 +379,17 @@ struct WaypointForm: View { } } } + .alert("Waypoint Failed to Send", isPresented: $waypointFailedAlert) { + Button("OK", role: .cancel) { + bleManager.context.delete(waypoint) + do { + try bleManager.context.save() + } catch { + bleManager.context.rollback() + } + dismiss() + } + } .onDisappear { if waypoint.id == 0 { // New, unsent waypoint created by the user: delete it