From 9982f24a514abd2e8c623195faa04e209df45437 Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Tue, 14 Oct 2025 00:05:44 -0400 Subject: [PATCH] Add SaveChannelLinkData so MessageText and MeshtasticApp can use .sheet(item: ...) and avoid infinite loop hang due to Binding rebuild --- Meshtastic/MeshtasticApp.swift | 53 ++++++++----------- Meshtastic/Views/Messages/MessageText.swift | 33 ++++-------- .../Views/Settings/SaveChannelQRCode.swift | 6 +++ 3 files changed, 39 insertions(+), 53 deletions(-) diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index a83ddb9b..fccf9a7c 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -19,10 +19,8 @@ struct MeshtasticAppleApp: App { private let persistenceController: PersistenceController private let accessoryManager: AccessoryManager @Environment(\.scenePhase) var scenePhase - @State var saveChannels = false + @State var saveChannelLink: SaveChannelLinkData? @State var incomingUrl: URL? - @State var channelSettings: String? - @State var addChannels = false init() { @@ -36,7 +34,7 @@ struct MeshtasticAppleApp: App { let appID = "79fe92a9-74c9-4c8f-ba63-6308384ecfa9" let clientToken = "pub4427bea20dbdb08a6af68034de22cd3b" var environment = "AppStore" - + #if DEBUG environment = "Local" #else @@ -44,7 +42,8 @@ struct MeshtasticAppleApp: App { environment = "TestFlight" } #endif - + +#if false Datadog.initialize( with: Datadog.Configuration( clientToken: clientToken, @@ -81,6 +80,7 @@ struct MeshtasticAppleApp: App { ) ) } +#endif accessoryManager = AccessoryManager.shared accessoryManager.appState = appState @@ -110,20 +110,11 @@ struct MeshtasticAppleApp: App { appState: appState, router: appState.router ) - .sheet(isPresented: Binding( - get: { - saveChannels && !(channelSettings == nil) - }, - set: { newValue in - saveChannels = newValue - if !newValue { - channelSettings = nil - } - } - )) { + .sheet(item: $saveChannelLink + ) { link in SaveChannelQRCode( - channelSetLink: channelSettings ?? "Empty Channel URL", - addChannels: addChannels, + channelSetLink: link.data, + addChannels: link.add, accessoryManager: accessoryManager ) .presentationDetents([.large]) .presentationDragIndicator(.visible) @@ -131,54 +122,54 @@ struct MeshtasticAppleApp: App { .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in Logger.mesh.debug("URL received \(userActivity, privacy: .public)") self.incomingUrl = userActivity.webpageURL - self.saveChannels = false + self.saveChannelLink = nil + var addChannels = false if self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/v/#") == true { ContactURLHandler.handleContactUrl(url: self.incomingUrl!, accessoryManager: accessoryManager) } else if self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/") == true { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { - self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false + addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false if (self.incomingUrl?.absoluteString.lowercased().contains("?")) != nil { guard let cs = components.last!.components(separatedBy: "?").first else { return } - self.channelSettings = cs + self.saveChannelLink = SaveChannelLinkData(data: cs, add: addChannels) } else { guard let cs = components.first else { return } - self.channelSettings = cs + self.saveChannelLink = SaveChannelLinkData(data: cs, add: addChannels) } - Logger.services.debug("Add Channel \(self.addChannels, privacy: .public)") + Logger.services.debug("Add Channel \(addChannels, privacy: .public)") } - self.saveChannels = true Logger.mesh.debug("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link")") } - if self.saveChannels { + if self.saveChannelLink != nil { Logger.mesh.debug("User wants to open Channel Settings URL: \(String(describing: self.incomingUrl!.relativeString), privacy: .public)") } } .onOpenURL(perform: { (url) in Logger.mesh.debug("Some sort of URL was received \(url, privacy: .public)") self.incomingUrl = url + var addChannels = false if url.absoluteString.lowercased().contains("meshtastic.org/v/#") { ContactURLHandler.handleContactUrl(url: url, accessoryManager: accessoryManager) } else if url.absoluteString.lowercased().contains("meshtastic.org/e/") { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { - self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false + addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false if self.incomingUrl?.absoluteString.lowercased().contains("?") != nil { guard let cs = components.last!.components(separatedBy: "?").first else { return } - self.channelSettings = cs + self.saveChannelLink = SaveChannelLinkData(data: cs, add: addChannels) } else { guard let cs = components.first else { return } - self.channelSettings = cs + self.saveChannelLink = SaveChannelLinkData(data: cs, add: addChannels) } - Logger.services.debug("Add Channel \(self.addChannels, privacy: .public)") + Logger.services.debug("Add Channel \(addChannels, privacy: .public)") } - self.saveChannels = true Logger.mesh.debug("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link", privacy: .public)") } else if url.absoluteString.lowercased().contains("meshtastic:///") { appState.router.route(url: url) @@ -221,7 +212,7 @@ struct MeshtasticAppleApp: App { .environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(appState) .environmentObject(accessoryManager) - .environmentObject(appState.router) + .environmentObject(appState.router) } } diff --git a/Meshtastic/Views/Messages/MessageText.swift b/Meshtastic/Views/Messages/MessageText.swift index 19d0c715..28df8fba 100644 --- a/Meshtastic/Views/Messages/MessageText.swift +++ b/Meshtastic/Views/Messages/MessageText.swift @@ -25,9 +25,7 @@ struct MessageText: View { let isCurrentUser: Bool let onReply: () -> Void // State for handling channel URL sheet - @State private var saveChannels = false - @State private var channelSettings: String? - @State private var addChannels = false + @State private var saveChannelLink: SaveChannelLinkData? @State private var isShowingDeleteConfirmation = false var body: some View { @@ -97,7 +95,8 @@ struct MessageText: View { ) } .environment(\.openURL, OpenURLAction { url in - channelSettings = nil + saveChannelLink = nil + var addChannels = false if url.absoluteString.lowercased().contains("meshtastic.org/v/#") { // Handle contact URL ContactURLHandler.handleContactUrl(url: url, accessoryManager: AccessoryManager.shared) @@ -109,35 +108,25 @@ struct MessageText: View { Logger.services.error("No valid components found in channel URL: \(url.absoluteString, privacy: .public)") return .discarded } - self.addChannels = Bool(url.query?.contains("add=true") ?? false) + addChannels = Bool(url.query?.contains("add=true") ?? false) guard let lastComponent = components.last else { Logger.services.error("Channel URL missing fragment component: \(url.absoluteString, privacy: .public)") - self.channelSettings = nil + self.saveChannelLink = nil return .discarded } - self.channelSettings = lastComponent.components(separatedBy: "?").first ?? "" - Logger.services.debug("Add Channel: \(self.addChannels, privacy: .public)") - self.saveChannels = true + let cs = lastComponent.components(separatedBy: "?").first ?? "" + self.saveChannelLink = SaveChannelLinkData(data: cs, add: addChannels) + Logger.services.debug("Add Channel: \(addChannels, privacy: .public)") Logger.mesh.debug("Opening Channel Settings URL: \(url.absoluteString, privacy: .public)") return .handled // Prevent default browser opening } return .systemAction // Open other URLs in browser }) // Display sheet for channel settings - .sheet(isPresented: Binding( - get: { - saveChannels && !(channelSettings == nil) - }, - set: { newValue in - saveChannels = newValue - if !newValue { - channelSettings = nil - } - } - )) { + .sheet(item: $saveChannelLink) { link in SaveChannelQRCode( - channelSetLink: channelSettings ?? "Empty Channel URL", - addChannels: addChannels, + channelSetLink: link.data, + addChannels: link.add, accessoryManager: accessoryManager ) .presentationDetents([.large]) diff --git a/Meshtastic/Views/Settings/SaveChannelQRCode.swift b/Meshtastic/Views/Settings/SaveChannelQRCode.swift index 71a032b4..cc4c6f84 100644 --- a/Meshtastic/Views/Settings/SaveChannelQRCode.swift +++ b/Meshtastic/Views/Settings/SaveChannelQRCode.swift @@ -9,6 +9,12 @@ import CoreData import OSLog import MeshtasticProtobufs +struct SaveChannelLinkData: Identifiable { + let id = UUID() + let data: String + let add: Bool +} + struct SaveChannelQRCode: View { @Environment(\.dismiss) private var dismiss @Environment(\.managedObjectContext) var context