diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 0bc052fb..37750d4d 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -7179,6 +7179,12 @@ }, "Factory reset your device and app? " : { + }, + "Failed to get a valid position to exchange" : { + + }, + "Failed to get a valid position to exchange." : { + }, "Favorite" : { @@ -16423,6 +16429,12 @@ } } } + }, + "Position Exchange Failed" : { + + }, + "Position Exchange Requested" : { + }, "Position Flags" : { @@ -20928,6 +20940,9 @@ }, "This conversation will be deleted." : { + }, + "This could take a while, response will appear in the trace route log for the node it was sent to." : { + }, "This could take a while. The response will appear in the trace route log for the node it was sent to." : { @@ -22489,7 +22504,7 @@ "Your MQTT Server must support TLS." : { }, - "Your position has been sent with a request for a response with their position." : { + "Your position has been sent with a request for a response with their position. You will receive a notification when a position is returned." : { }, "Your region has a %lld%% duty cycle. MQTT is not advised when you are duty cycle restricted, the extra traffic will quickly overwhelm your LoRa mesh." : { diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/ExchangePositionsButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/ExchangePositionsButton.swift index ed5fce47..f98bc999 100644 --- a/Meshtastic/Views/Nodes/Helpers/Actions/ExchangePositionsButton.swift +++ b/Meshtastic/Views/Nodes/Helpers/Actions/ExchangePositionsButton.swift @@ -6,16 +6,28 @@ struct ExchangePositionsButton: View { var node: NodeInfoEntity - @State - private var isPresentingPositionSentAlert: Bool = false + @State private var isPresentingPositionSentAlert: Bool = false + @State private var isPresentingPositionFailedAlert: Bool = false var body: some View { Button { - isPresentingPositionSentAlert = bleManager.sendPosition( + let positionSent = bleManager.sendPosition( channel: node.channel, destNum: node.num, wantResponse: true ) + if positionSent { + isPresentingPositionSentAlert = true + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + isPresentingPositionSentAlert = false + } + } else { + isPresentingPositionFailedAlert = true + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + isPresentingPositionFailedAlert = false + } + } + } label: { Label { Text("Exchange Positions") @@ -29,7 +41,14 @@ struct ExchangePositionsButton: View { ) { Button("OK") { }.keyboardShortcut(.defaultAction) } message: { - Text("Your position has been sent with a request for a response with their position.") + Text("Your position has been sent with a request for a response with their position. You will receive a notification when a position is returned.") + }.alert( + "Position Exchange Failed", + isPresented: $isPresentingPositionFailedAlert + ) { + Button("OK") { }.keyboardShortcut(.defaultAction) + } message: { + Text("Failed to get a valid position to exchange.") } } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index fc9b44c1..9bc5781a 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -281,12 +281,6 @@ struct NodeDetail: View { } Section("Actions") { - FavoriteNodeButton( - bleManager: bleManager, - context: context, - node: node - ) - if let user = node.user { NodeAlertsButton( context: context, @@ -295,19 +289,21 @@ struct NodeDetail: View { ) } - if let connectedPeripheral = bleManager.connectedPeripheral, - node.num != connectedPeripheral.num { - ExchangePositionsButton( + if let connectedNode { + FavoriteNodeButton( bleManager: bleManager, + context: context, node: node ) - - TraceRouteButton( - bleManager: bleManager, - node: node - ) - - if let connectedNode { + if connectedNode.num != node.num { + ExchangePositionsButton( + bleManager: bleManager, + node: node + ) + TraceRouteButton( + bleManager: bleManager, + node: node + ) if node.isStoreForwardRouter { ClientHistoryButton( bleManager: bleManager, @@ -315,7 +311,6 @@ struct NodeDetail: View { node: node ) } - DeleteNodeButton( bleManager: bleManager, context: context, diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index ac9aa65e..3773593b 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -31,6 +31,12 @@ struct NodeList: View { @State private var hopsAway: Double = -1.0 @State private var roleFilter = false @State private var deviceRoles: Set = [] + + @State private var isPresentingTraceRouteSentAlert = false + @State private var isPresentingPositionSentAlert = false + @State private var isPresentingPositionFailedAlert = false + @State private var isPresentingDeleteNodeAlert = false + @State private var deleteNodeId: Int64 = 0 var boolFilters: [Bool] {[ isOnline, @@ -66,12 +72,7 @@ struct NodeList: View { node: NodeInfoEntity, connectedNode: NodeInfoEntity? ) -> some View { - FavoriteNodeButton( - bleManager: bleManager, - context: context, - node: node - ) - + /// Allow users to mute notifications for a node even if they are not connected if let user = node.user { NodeAlertsButton( context: context, @@ -79,22 +80,57 @@ struct NodeList: View { user: user ) } - if let connectedNode { - ExchangePositionsButton( + /// Favoriting a node requires being connected + FavoriteNodeButton( bleManager: bleManager, + context: context, node: node ) - TraceRouteButton( - bleManager: bleManager, - node: node - ) -// DeleteNodeButton( -// bleManager: bleManager, -// context: context, -// connectedNode: connectedNode, -// node: node -// ) + /// Don't show trace route, position exchange or delete context menu items for the connected node + if connectedNode.num != node.num { + Button { + let traceRouteSent = bleManager.sendTraceRouteRequest( + destNum: node.num, + wantResponse: true + ) + if traceRouteSent { + isPresentingTraceRouteSentAlert = true + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + isPresentingTraceRouteSentAlert = false + } + } + + } label: { + Label("Trace Route", systemImage: "signpost.right.and.left") + } + Button { + let positionSent = bleManager.sendPosition( + channel: node.channel, + destNum: node.num, + wantResponse: true + ) + if positionSent { + isPresentingPositionSentAlert = true + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + isPresentingPositionSentAlert = false + } + } else { + isPresentingPositionFailedAlert = true + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + isPresentingPositionFailedAlert = false + } + } + } label: { + Label("Exchange Positions", systemImage: "arrow.triangle.2.circlepath") + } + Button(role: .destructive) { + deleteNodeId = node.num + isPresentingDeleteNodeAlert = true + } label: { + Label("Delete Node", systemImage: "trash") + } + } } } @@ -150,6 +186,44 @@ struct NodeList: View { .scrollDismissesKeyboard(.immediately) .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) .listStyle(.plain) + .alert( + "Position Exchange Requested", + isPresented: $isPresentingPositionSentAlert) { + Button("OK") { }.keyboardShortcut(.defaultAction) + } message: { + Text("Your position has been sent with a request for a response with their position. You will receive a notification when a position is returned.") + } + .alert( + "Position Exchange Failed", + isPresented: $isPresentingPositionFailedAlert) { + Button("OK") { }.keyboardShortcut(.defaultAction) + } message: { + Text("Failed to get a valid position to exchange") + } + .alert( + "Trace Route Sent", + isPresented: $isPresentingTraceRouteSentAlert) { + Button("OK") { }.keyboardShortcut(.defaultAction) + } message: { + Text("This could take a while, response will appear in the trace route log for the node it was sent to.") + } + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingDeleteNodeAlert, + titleVisibility: .visible + ) { + Button("Delete Node") { + let deleteNode = getNodeInfo(id: deleteNodeId, context: context) + if connectedNode != nil { + if deleteNode != nil { + let success = bleManager.removeNode(node: deleteNode!, connectedNodeNum: Int64(bleManager.connectedPeripheral?.num ?? -1)) + if !success { + Logger.data.error("Failed to delete node \(deleteNode?.user?.longName ?? "unknown".localized)") + } + } + } + } + } .navigationSplitViewColumnWidth(min: 100, ideal: 250, max: 500) .navigationBarItems( leading: MeshtasticLogo(),