From 2a68a36d847f7cccda4d7826685c5912474cb4d5 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sun, 9 Feb 2025 11:29:24 -0800 Subject: [PATCH] added intent and button --- .../AppIntents/NavigateToNodeIntent.swift | 61 +++++++++++++++++++ .../Helpers/Actions/NavigateToButton.swift | 58 ++++++++++++++++++ .../Views/Nodes/Helpers/NodeDetail.swift | 3 + Meshtastic/Views/Nodes/PositionLog.swift | 3 + 4 files changed, 125 insertions(+) create mode 100644 Meshtastic/AppIntents/NavigateToNodeIntent.swift create mode 100644 Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift diff --git a/Meshtastic/AppIntents/NavigateToNodeIntent.swift b/Meshtastic/AppIntents/NavigateToNodeIntent.swift new file mode 100644 index 00000000..06283f3f --- /dev/null +++ b/Meshtastic/AppIntents/NavigateToNodeIntent.swift @@ -0,0 +1,61 @@ +// +// NavigateToNodeIntent.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 2/8/25. +// + +import Foundation +import AppIntents +import CoreLocation +import CoreData +import UIKit + +@available(iOS 16.4, *) +struct NavigateToNodeIntent: ForegroundContinuableIntent { + + static var title: LocalizedStringResource = "Navigate to Node Position" + static var openAppWhenRun: Bool = false + + @Parameter(title: "Node Number") + var nodeNum: Int + + @MainActor + func perform() async throws -> some IntentResult & ProvidesDialog { + if !BLEManager.shared.isConnected { + throw AppIntentErrors.AppIntentError.notConnected + } + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + guard let fetchedNode = try PersistenceController.shared.container.viewContext.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity], + fetchedNode.count == 1 else { + throw $nodeNum.needsValueError("Could not find node") + } + + let nodeInfo = fetchedNode[0] + if let latitude = nodeInfo.latestPosition?.coordinate.latitude, + let longitude = nodeInfo.latestPosition?.coordinate.longitude { + + let url = URL(string: "maps://?saddr=&daddr=\(latitude),\(longitude)") + + if let mapURL = url, UIApplication.shared.canOpenURL(mapURL) { + // Request to continue in foreground before opening the app + try await requestToContinueInForeground() + + // Open Apple Maps for navigation + UIApplication.shared.open(mapURL, options: [:], completionHandler: nil) + return .result(dialog: "Navigating to node location.") + } else { + throw AppIntentErrors.AppIntentError.message("Unable to open Apple Maps.") + } + } else { + throw AppIntentErrors.AppIntentError.message("Node does not have a recorded position.") + } + } catch { + throw AppIntentErrors.AppIntentError.message("Failed to fetch node data.") + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift new file mode 100644 index 00000000..e7a3567e --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift @@ -0,0 +1,58 @@ +// +// NavigateToButton.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 2/8/25. +// + +import SwiftUI +import CoreLocation +import CoreData +import OSLog + +struct NavigateToButton: View { + var node: NodeInfoEntity + + var body: some View { + Button { + guard let userNum = node.user?.num else { + Logger.services.error("NavigateToAction: Selected node does not exist") + return + } + + Logger.services.info("Fetching NodeInfoEntity for userNum: \(userNum)") + + let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "NodeInfoEntity") + fetchRequest.predicate = NSPredicate(format: "num == %lld", Int64(userNum)) + + do { + let fetchedNodes = try PersistenceController.shared.container.viewContext.fetch(fetchRequest) + + guard let nodeInfo = fetchedNodes.first else { + Logger.services.error("NavigateToAction: Node with userNum \(userNum) not found in Core Data") + return + } + + if let latitude = nodeInfo.latestPosition?.latitude, + let longitude = nodeInfo.latestPosition?.longitude { + if let url = URL(string: "maps://?saddr=&daddr=\(latitude),\(longitude)") { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } else { + Logger.services.error("Failed to create URL for navigation") + } + } else { + Logger.services.warning("NavigateToAction: Node \(userNum) has invalid or missing coordinates") + } + } catch { + Logger.services.error("NavigateToAction: Failed to fetch node with userNum \(userNum): \(error.localizedDescription)") + } + } label: { + Label { + Text("Navigate to node") + } icon: { + Image(systemName: "map") + .symbolRenderingMode(.hierarchical) + } + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index e251c64c..7b3267bb 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -378,6 +378,9 @@ struct NodeDetail: View { node: node ) } + if node.hasPositions { + NavigateToButton(node: node) + } IgnoreNodeButton( bleManager: bleManager, context: context, diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index ee2709db..becb6a27 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -54,6 +54,7 @@ struct PositionLog: View { let degrees = Angle.degrees(Double(position.heading)) let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees).reciprocal() Text(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0))))) + .textSelection(.enabled) } TableColumn("SNR") { position in Text("\(String(format: "%.2f", position.snr)) dB") @@ -63,6 +64,8 @@ struct PositionLog: View { } .width(min: 180) } + .textSelection(.enabled) + } else { ScrollView {