Skip to content

Commit 45f7aed

Browse files
authored
Merge pull request #1191 from meshtastic/DisconnectSwipe
Disconnect swipe
2 parents 32c463d + a1fcb30 commit 45f7aed

File tree

6 files changed

+121
-16
lines changed

6 files changed

+121
-16
lines changed

Localizable.xcstrings

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9665,6 +9665,12 @@
96659665
}
96669666
}
96679667
}
9668+
},
9669+
"Disconnect Node" : {
9670+
9671+
},
9672+
"Disconnect the currently connected node" : {
9673+
96689674
},
96699675
"Dismiss" : {
96709676
"localizations" : {

Meshtastic.xcodeproj/project.pbxproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613822C672A2600485544 /* MessageChannelIntent.swift */; };
6666
BCB613852C68703800485544 /* NodePositionIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613842C68703800485544 /* NodePositionIntent.swift */; };
6767
BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613862C69A0FB00485544 /* AppIntentErrors.swift */; };
68-
BCDDFA9A2DBB180D0065189C /* ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDDFA992DBB180D0065189C /* ScrollToBottomButton.swift */; };
68+
BCD93CBA2D9E11A2006C9214 /* DisconnectNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCD93CB92D9E11A2006C9214 /* DisconnectNodeIntent.swift */; };
69+
BCDDFA9A2DBB180D0065189C /* ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDDFA992DBB180D0065189C /* ScrollToBottomButton.swift */; };
6970
BCE2D3C32C7ADF42008E6199 /* ShutDownNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */; };
7071
BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */; };
7172
BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */; };
@@ -331,7 +332,8 @@
331332
BCB613822C672A2600485544 /* MessageChannelIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageChannelIntent.swift; sourceTree = "<group>"; };
332333
BCB613842C68703800485544 /* NodePositionIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodePositionIntent.swift; sourceTree = "<group>"; };
333334
BCB613862C69A0FB00485544 /* AppIntentErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentErrors.swift; sourceTree = "<group>"; };
334-
BCDDFA992DBB180D0065189C /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = "<group>"; };
335+
BCD93CB92D9E11A2006C9214 /* DisconnectNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisconnectNodeIntent.swift; sourceTree = "<group>"; };
336+
BCDDFA992DBB180D0065189C /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = "<group>"; };
335337
BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShutDownNodeIntent.swift; sourceTree = "<group>"; };
336338
BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartNodeIntent.swift; sourceTree = "<group>"; };
337339
BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsProvider.swift; sourceTree = "<group>"; };
@@ -691,6 +693,7 @@
691693
BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */,
692694
BC6B45FE2CB2F98900723CEB /* SaveChannelSettingsIntent.swift */,
693695
BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */,
696+
BCD93CB92D9E11A2006C9214 /* DisconnectNodeIntent.swift */,
694697
);
695698
path = AppIntents;
696699
sourceTree = "<group>";
@@ -1418,6 +1421,7 @@
14181421
DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */,
14191422
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */,
14201423
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
1424+
BCD93CBA2D9E11A2006C9214 /* DisconnectNodeIntent.swift in Sources */,
14211425
DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */,
14221426
231B3F252D087C3C0069A07D /* EnvironmentDefaultColumns.swift in Sources */,
14231427
25F5D5BE2C3F6D87008036E3 /* NavigationState.swift in Sources */,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// DisconnectNodeIntent.swift
3+
// Meshtastic
4+
//
5+
// Created by Benjamin Faershtein on 4/2/25.
6+
//
7+
8+
import Foundation
9+
import AppIntents
10+
11+
struct DisconnectNodeIntent: AppIntent {
12+
static var title: LocalizedStringResource = "Disconnect Node"
13+
14+
static var description: IntentDescription = "Disconnect the currently connected node"
15+
16+
17+
func perform() async throws -> some IntentResult {
18+
if !BLEManager.shared.isConnected {
19+
throw AppIntentErrors.AppIntentError.notConnected
20+
}
21+
22+
if let connectedPeripheral = BLEManager.shared.connectedPeripheral,
23+
connectedPeripheral.peripheral.state == .connected {
24+
BLEManager.shared.disconnectPeripheral(reconnect: false)
25+
} else {
26+
throw AppIntentErrors.AppIntentError.message("Error disconnecting node")
27+
}
28+
29+
return .result()
30+
}
31+
}

Meshtastic/AppIntents/ShortcutsProvider.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,13 @@ struct ShortcutsProvider: AppShortcutsProvider {
3232
"Send a \(.applicationName) group message"],
3333
shortTitle: "Group Message",
3434
systemImageName: "message")
35+
36+
AppShortcut(intent: DisconnectNodeIntent(),
37+
phrases: ["Disconnect \(.applicationName) node",
38+
"Disconnect my \(.applicationName) node",
39+
"Disconnect from \(.applicationName)",
40+
"Disconnect \(.applicationName)"],
41+
shortTitle: "Disconnect",
42+
systemImageName: "antenna.radiowaves.left.and.right.slash")
3543
}
3644
}

Meshtastic/Helpers/BLEManager.swift

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -177,20 +177,25 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
177177

178178
// Disconnect Connected Peripheral
179179
func disconnectPeripheral(reconnect: Bool = true) {
180-
181-
guard let connectedPeripheral = connectedPeripheral else { return }
182-
if mqttProxyConnected {
183-
mqttManager.mqttClientProxy?.disconnect()
180+
// Ensure all operations run on the main thread
181+
DispatchQueue.main.async { [weak self] in
182+
guard let self = self else { return }
183+
guard let connectedPeripheral = self.connectedPeripheral else { return }
184+
185+
if self.mqttProxyConnected {
186+
self.mqttManager.mqttClientProxy?.disconnect()
187+
}
188+
189+
self.automaticallyReconnect = reconnect
190+
self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
191+
self.FROMRADIO_characteristic = nil
192+
self.isConnected = false
193+
self.isSubscribed = false
194+
self.invalidVersion = false
195+
self.connectedVersion = "0.0.0"
196+
self.stopScanning()
197+
self.startScanning()
184198
}
185-
automaticallyReconnect = reconnect
186-
centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
187-
FROMRADIO_characteristic = nil
188-
isConnected = false
189-
isSubscribed = false
190-
invalidVersion = false
191-
connectedVersion = "0.0.0"
192-
stopScanning()
193-
startScanning()
194199
}
195200

196201
// Called each time a peripheral is connected

Meshtastic/Views/Bluetooth/Connect.swift

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ struct Connect: View {
2626
@State var liveActivityStarted = false
2727
@State var presentingSwitchPreferredPeripheral = false
2828
@State var selectedPeripherialId = ""
29+
@State private var showSwipeDemo = false
30+
@State private var swipeDemoOffset: CGFloat = 0
31+
@State private var showDeleteButton: Bool = false
32+
@AppStorage("hasSeenSwipeDemo") private var hasSeenSwipeDemo = false
2933

3034
init () {
3135
let notificationCenter = UNUserNotificationCenter.current()
@@ -89,6 +93,28 @@ struct Connect: View {
8993
.font(.caption)
9094
.foregroundColor(Color.gray)
9195
.padding([.top])
96+
.offset(x: swipeDemoOffset)
97+
.overlay(
98+
GeometryReader { geometry in
99+
ZStack {
100+
Rectangle()
101+
.foregroundColor(.red)
102+
.frame(width: 80)
103+
.offset(x: geometry.size.width - 80)
104+
VStack {
105+
Image(systemName: "antenna.radiowaves.left.and.right.slash")
106+
.foregroundColor(.white)
107+
.font(.title3)
108+
Text("Disconnect")
109+
.foregroundColor(.white)
110+
.font(.caption)
111+
}
112+
}
113+
.offset(x: geometry.size.width - 50)
114+
115+
.opacity(showDeleteButton ? 1 : 0)
116+
}
117+
)
92118
.swipeActions {
93119
Button(role: .destructive) {
94120
if let connectedPeripheral = bleManager.connectedPeripheral,
@@ -123,7 +149,15 @@ struct Connect: View {
123149
Text("Short Name: \(node?.user?.shortName ?? "?")")
124150
Text("Long Name: \(node?.user?.longName?.addingVariationSelectors ?? "Unknown".localized)")
125151
Text("BLE RSSI: \(connectedPeripheral.rssi)")
126-
152+
153+
Button(role: .destructive) {
154+
if let connectedPeripheral = bleManager.connectedPeripheral,
155+
connectedPeripheral.peripheral.state == .connected {
156+
bleManager.disconnectPeripheral(reconnect: false)
157+
}
158+
} label: {
159+
Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash")
160+
}
127161
Button {
128162
if !bleManager.sendShutdown(fromUser: node!.user!, toUser: node!.user!, adminIndex: node!.myInfo!.adminIndex) {
129163
Logger.mesh.error("Shutdown Failed")
@@ -323,6 +357,23 @@ struct Connect: View {
323357
} else {
324358
isUnsetRegion = false
325359
}
360+
// Show swipe demo if user hasn't seen it before and we're connected
361+
if !hasSeenSwipeDemo && bleManager.isSubscribed && node != nil {
362+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
363+
withAnimation(.easeInOut(duration: 0.6)) {
364+
swipeDemoOffset = -80
365+
showDeleteButton = true
366+
}
367+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
368+
withAnimation(.easeInOut(duration: 0.6)) {
369+
swipeDemoOffset = 0
370+
showDeleteButton = false
371+
}
372+
// Mark as seen so it doesn't appear again
373+
hasSeenSwipeDemo = true
374+
}
375+
}
376+
}
326377
} catch {
327378
Logger.data.error("💥 Error fetching node info: \(error.localizedDescription, privacy: .public)")
328379
}

0 commit comments

Comments
 (0)