From be36dc6a3051f55242195febda376a0fd33287dc Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Tue, 13 May 2025 19:56:06 -0700 Subject: [PATCH 1/2] Import Contact App Shortcut and add a share url button to the page --- Localizable.xcstrings | 116 +++++++++++------- Meshtastic.xcodeproj/project.pbxproj | 4 + Meshtastic/AppIntents/AddContactIntent.swift | 51 ++++++++ .../Nodes/Helpers/ShareContactQRDialog.swift | 7 ++ 4 files changed, 132 insertions(+), 46 deletions(-) create mode 100644 Meshtastic/AppIntents/AddContactIntent.swift diff --git a/Localizable.xcstrings b/Localizable.xcstrings index b97eb676..23cb27cf 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -7201,6 +7201,9 @@ } } } + }, + "Contact URL" : { + }, "Contacts (%@)" : { "localizations" : { @@ -9994,6 +9997,9 @@ } } } + }, + "Done" : { + }, "Double Tap as Button" : { "localizations" : { @@ -15082,6 +15088,12 @@ } } } + }, + "Import Contact" : { + + }, + "Import Meshtastic Node %@ as a contact" : { + }, "Import Route" : { "localizations" : { @@ -22193,6 +22205,52 @@ } } }, + "Position config received: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Positionskonfiguration empfangen: %@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuration de la position reçue : %@" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הגדרות מיקום התקבלו: %@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configurazione della posizione ricevuta: %@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odebrano konfigurację pozycji: %@" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Positionskonfiguration mottagen: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација позиције примљена: %@" + } + } + } + }, "Position Exchange Failed" : { "localizations" : { "it" : { @@ -22467,52 +22525,6 @@ } } }, - "Position config received: %@" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Positionskonfiguration empfangen: %@" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Configuration de la position reçue : %@" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הגדרות מיקום התקבלו: %@" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Configurazione della posizione ricevuta: %@" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Odebrano konfigurację pozycji: %@" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Positionskonfiguration mottagen: %@" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Конфигурација позиције примљена: %@" - } - } - } - }, "Power" : { "localizations" : { "de" : { @@ -26021,6 +26033,9 @@ } } } + }, + "Scan this QR code to add %@ to another device." : { + }, "Screen on for" : { "localizations" : { @@ -28033,6 +28048,9 @@ } } } + }, + "Share Contact QR" : { + }, "Share QR Code" : { "localizations" : { @@ -29711,6 +29729,9 @@ } } } + }, + "Takes a Meshtastic contact URL and saves it to the nodes database" : { + }, "Tapback" : { "localizations" : { @@ -30891,6 +30912,9 @@ } } } + }, + "The URL for the node to import" : { + }, "There has been no response to a request for device metadata over the admin channel for this node." : { "localizations" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 7f4c9e0d..fb48dfe1 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ 8D3F8A412D44C2A6009EAAA4 /* PowerMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D3F8A402D44C2A6009EAAA4 /* PowerMetricsLog.swift */; }; B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B399E8A32B6F486400E4488E /* RetryButton.swift */; }; B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E905B02B71F7F300654D07 /* TextMessageField.swift */; }; + BC10380F2DD4334400B00BFA /* AddContactIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC10380E2DD4333C00B00BFA /* AddContactIntent.swift */; }; BC47C2EF2CE0017D008245CA /* MessageNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */; }; BC6B45FF2CB2F98900723CEB /* SaveChannelSettingsIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6B45FE2CB2F98900723CEB /* SaveChannelSettingsIntent.swift */; }; BCB613812C67290800485544 /* SendWaypointIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613802C67290800485544 /* SendWaypointIntent.swift */; }; @@ -322,6 +323,7 @@ 8D3F8A402D44C2A6009EAAA4 /* PowerMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerMetricsLog.swift; sourceTree = ""; }; B399E8A32B6F486400E4488E /* RetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryButton.swift; sourceTree = ""; }; B3E905B02B71F7F300654D07 /* TextMessageField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageField.swift; sourceTree = ""; }; + BC10380E2DD4333C00B00BFA /* AddContactIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactIntent.swift; sourceTree = ""; }; BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageNodeIntent.swift; sourceTree = ""; }; BC5EBA3B2D002A2000C442FF /* MessageNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageNodeIntent.swift; sourceTree = ""; }; BC6B45FE2CB2F98900723CEB /* SaveChannelSettingsIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveChannelSettingsIntent.swift; sourceTree = ""; }; @@ -676,6 +678,7 @@ BCB6137F2C6728E700485544 /* AppIntents */ = { isa = PBXGroup; children = ( + BC10380E2DD4333C00B00BFA /* AddContactIntent.swift */, BC5EBA3B2D002A2000C442FF /* MessageNodeIntent.swift */, BCB613802C67290800485544 /* SendWaypointIntent.swift */, BCB613822C672A2600485544 /* MessageChannelIntent.swift */, @@ -1551,6 +1554,7 @@ 2344A2AB2D66974300170A77 /* ManagedAttributePropertyWrapper.swift in Sources */, BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */, D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */, + BC10380F2DD4334400B00BFA /* AddContactIntent.swift in Sources */, DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */, DDDB444E29F8AB0E00EE2349 /* Int.swift in Sources */, DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */, diff --git a/Meshtastic/AppIntents/AddContactIntent.swift b/Meshtastic/AppIntents/AddContactIntent.swift new file mode 100644 index 00000000..ff8ca149 --- /dev/null +++ b/Meshtastic/AppIntents/AddContactIntent.swift @@ -0,0 +1,51 @@ +// +// AddContactIntent.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 5/13/25. +// + +import AppIntents +import MeshtasticProtobufs + +struct AddContactIntent: AppIntent { + static var title: LocalizedStringResource = "Import Contact" + static var description: IntentDescription = "Takes a Meshtastic contact URL and saves it to the nodes database" + + @Parameter(title: "Contact URL", description: "The URL for the node to import") + var contactUrl: URL + + // Define the function that performs the main logic + func perform() async throws -> some IntentResult { + // Ensure the BLE Manager is connected + if !BLEManager.shared.isConnected { + throw AppIntentErrors.AppIntentError.notConnected + } + + if contactUrl.absoluteString.lowercased().contains("meshtastic.org/v/#") { + + let components = self.contactUrl.absoluteString.components(separatedBy: "#") + // Extract contact information from the URL + if let contactData = components.last { + + let decodedString = contactData.base64urlToBase64() + if let decodedData = Data(base64Encoded: decodedString) { + do { + let success = BLEManager.shared.addContactFromURL(base64UrlString: contactData) + if !success { + throw AppIntentErrors.AppIntentError.message("Failed to import contact") + } + + } catch { + throw AppIntentErrors.AppIntentError.message("Failed to parse contact data: \(error.localizedDescription)") + + } + } + } + // Return a success result + return .result() + } else { + throw AppIntentErrors.AppIntentError.message("The URL is not a valid Meshtastic contact link") + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/ShareContactQRDialog.swift b/Meshtastic/Views/Nodes/Helpers/ShareContactQRDialog.swift index 76901ff6..e5d679b9 100644 --- a/Meshtastic/Views/Nodes/Helpers/ShareContactQRDialog.swift +++ b/Meshtastic/Views/Nodes/Helpers/ShareContactQRDialog.swift @@ -61,6 +61,13 @@ struct ShareContactQRDialog: View { .font(.subheadline) .multilineTextAlignment(.center) .foregroundColor(.secondary) + ShareLink("Share QR Code & Link", + item: Image(uiImage: qrImage), + subject: Text("Import Meshtastic Node \(node.user.shortName) as a contact"), + message: Text(qrString), + preview: SharePreview("Import Meshtastic Node \(node.user.shortName) as a contact", + image: Image(uiImage: qrImage)) + ) Button("Done") { dismiss() } .buttonStyle(.borderedProminent) .padding(.bottom) From 863f51b697e496a6202a79514ebc2027ac3d0293 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 14 May 2025 15:42:38 -0500 Subject: [PATCH 2/2] Update ShareContactQRDialog.swift --- Meshtastic/Views/Nodes/Helpers/ShareContactQRDialog.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Nodes/Helpers/ShareContactQRDialog.swift b/Meshtastic/Views/Nodes/Helpers/ShareContactQRDialog.swift index e5d679b9..5d361fbe 100644 --- a/Meshtastic/Views/Nodes/Helpers/ShareContactQRDialog.swift +++ b/Meshtastic/Views/Nodes/Helpers/ShareContactQRDialog.swift @@ -63,9 +63,9 @@ struct ShareContactQRDialog: View { .foregroundColor(.secondary) ShareLink("Share QR Code & Link", item: Image(uiImage: qrImage), - subject: Text("Import Meshtastic Node \(node.user.shortName) as a contact"), + subject: Text("Add Meshtastic Node \(node.user.shortName) as a contact"), message: Text(qrString), - preview: SharePreview("Import Meshtastic Node \(node.user.shortName) as a contact", + preview: SharePreview("Add Meshtastic Node \(node.user.shortName) as a contact", image: Image(uiImage: qrImage)) ) Button("Done") { dismiss() }