Merge pull request #1166 from RCGV1/DisconnectSwipe

Add animation to demo swipe to disconnect
This commit is contained in:
Benjamin Faershtein 2025-04-23 12:37:06 -07:00 committed by GitHub
commit 1164a8e8e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 120 additions and 14 deletions

View file

@ -8691,6 +8691,12 @@
}
}
}
},
"Disconnect Node" : {
},
"Disconnect the currently connected node" : {
},
"Dismiss" : {
"localizations" : {
@ -23168,6 +23174,7 @@
}
},
"Power Metrics Log}" : {
"extractionState" : "stale",
"localizations" : {
"sr" : {
"stringUnit" : {

View file

@ -62,6 +62,7 @@
BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613822C672A2600485544 /* MessageChannelIntent.swift */; };
BCB613852C68703800485544 /* NodePositionIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613842C68703800485544 /* NodePositionIntent.swift */; };
BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613862C69A0FB00485544 /* AppIntentErrors.swift */; };
BCD93CBA2D9E11A2006C9214 /* DisconnectNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCD93CB92D9E11A2006C9214 /* DisconnectNodeIntent.swift */; };
BCE2D3C32C7ADF42008E6199 /* ShutDownNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */; };
BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */; };
BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */; };
@ -326,6 +327,7 @@
BCB613822C672A2600485544 /* MessageChannelIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageChannelIntent.swift; sourceTree = "<group>"; };
BCB613842C68703800485544 /* NodePositionIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodePositionIntent.swift; sourceTree = "<group>"; };
BCB613862C69A0FB00485544 /* AppIntentErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentErrors.swift; sourceTree = "<group>"; };
BCD93CB92D9E11A2006C9214 /* DisconnectNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisconnectNodeIntent.swift; sourceTree = "<group>"; };
BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShutDownNodeIntent.swift; sourceTree = "<group>"; };
BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartNodeIntent.swift; sourceTree = "<group>"; };
BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsProvider.swift; sourceTree = "<group>"; };
@ -686,6 +688,7 @@
BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */,
BC6B45FE2CB2F98900723CEB /* SaveChannelSettingsIntent.swift */,
BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */,
BCD93CB92D9E11A2006C9214 /* DisconnectNodeIntent.swift */,
);
path = AppIntents;
sourceTree = "<group>";
@ -1421,6 +1424,7 @@
DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */,
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */,
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
BCD93CBA2D9E11A2006C9214 /* DisconnectNodeIntent.swift in Sources */,
DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */,
231B3F252D087C3C0069A07D /* EnvironmentDefaultColumns.swift in Sources */,
25F5D5BE2C3F6D87008036E3 /* NavigationState.swift in Sources */,

View file

@ -0,0 +1,31 @@
//
// DisconnectNodeIntent.swift
// Meshtastic
//
// Created by Benjamin Faershtein on 4/2/25.
//
import Foundation
import AppIntents
struct DisconnectNodeIntent: AppIntent {
static var title: LocalizedStringResource = "Disconnect Node"
static var description: IntentDescription = "Disconnect the currently connected node"
func perform() async throws -> some IntentResult {
if !BLEManager.shared.isConnected {
throw AppIntentErrors.AppIntentError.notConnected
}
if let connectedPeripheral = BLEManager.shared.connectedPeripheral,
connectedPeripheral.peripheral.state == .connected {
BLEManager.shared.disconnectPeripheral(reconnect: false)
} else {
throw AppIntentErrors.AppIntentError.message("Error disconnecting node")
}
return .result()
}
}

View file

@ -32,5 +32,13 @@ struct ShortcutsProvider: AppShortcutsProvider {
"Send a \(.applicationName) group message"],
shortTitle: "Group Message",
systemImageName: "message")
AppShortcut(intent: DisconnectNodeIntent(),
phrases: ["Disconnect \(.applicationName) node",
"Disconnect my \(.applicationName) node",
"Disconnect from \(.applicationName)",
"Disconnect \(.applicationName)"],
shortTitle: "Disconnect",
systemImageName: "antenna.radiowaves.left.and.right.slash")
}
}

View file

@ -177,20 +177,25 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
// Disconnect Connected Peripheral
func disconnectPeripheral(reconnect: Bool = true) {
guard let connectedPeripheral = connectedPeripheral else { return }
if mqttProxyConnected {
mqttManager.mqttClientProxy?.disconnect()
// Ensure all operations run on the main thread
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
guard let connectedPeripheral = self.connectedPeripheral else { return }
if self.mqttProxyConnected {
self.mqttManager.mqttClientProxy?.disconnect()
}
self.automaticallyReconnect = reconnect
self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
self.FROMRADIO_characteristic = nil
self.isConnected = false
self.isSubscribed = false
self.invalidVersion = false
self.connectedVersion = "0.0.0"
self.stopScanning()
self.startScanning()
}
automaticallyReconnect = reconnect
centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
FROMRADIO_characteristic = nil
isConnected = false
isSubscribed = false
invalidVersion = false
connectedVersion = "0.0.0"
stopScanning()
startScanning()
}
// Called each time a peripheral is connected

View file

@ -26,6 +26,10 @@ struct Connect: View {
@State var liveActivityStarted = false
@State var presentingSwitchPreferredPeripheral = false
@State var selectedPeripherialId = ""
@State private var showSwipeDemo = false
@State private var swipeDemoOffset: CGFloat = 0
@State private var showDeleteButton: Bool = false
@AppStorage("hasSeenSwipeDemo") private var hasSeenSwipeDemo = false
init () {
let notificationCenter = UNUserNotificationCenter.current()
@ -89,6 +93,28 @@ struct Connect: View {
.font(.caption)
.foregroundColor(Color.gray)
.padding([.top])
.offset(x: swipeDemoOffset)
.overlay(
GeometryReader { geometry in
ZStack {
Rectangle()
.foregroundColor(.red)
.frame(width: 80)
.offset(x: geometry.size.width - 80)
VStack {
Image(systemName: "antenna.radiowaves.left.and.right.slash")
.foregroundColor(.white)
.font(.title3)
Text("Disconnect")
.foregroundColor(.white)
.font(.caption)
}
}
.offset(x: geometry.size.width - 50)
.opacity(showDeleteButton ? 1 : 0)
}
)
.swipeActions {
Button(role: .destructive) {
if let connectedPeripheral = bleManager.connectedPeripheral,
@ -123,7 +149,15 @@ struct Connect: View {
Text("Short Name: \(node?.user?.shortName ?? "?")")
Text("Long Name: \(node?.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
Text("BLE RSSI: \(connectedPeripheral.rssi)")
Button(role: .destructive) {
if let connectedPeripheral = bleManager.connectedPeripheral,
connectedPeripheral.peripheral.state == .connected {
bleManager.disconnectPeripheral(reconnect: false)
}
} label: {
Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash")
}
Button {
if !bleManager.sendShutdown(fromUser: node!.user!, toUser: node!.user!, adminIndex: node!.myInfo!.adminIndex) {
Logger.mesh.error("Shutdown Failed")
@ -323,6 +357,23 @@ struct Connect: View {
} else {
isUnsetRegion = false
}
// Show swipe demo if user hasn't seen it before and we're connected
if !hasSeenSwipeDemo && bleManager.isSubscribed && node != nil {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
withAnimation(.easeInOut(duration: 0.6)) {
swipeDemoOffset = -80
showDeleteButton = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
withAnimation(.easeInOut(duration: 0.6)) {
swipeDemoOffset = 0
showDeleteButton = false
}
// Mark as seen so it doesn't appear again
hasSeenSwipeDemo = true
}
}
}
} catch {
Logger.data.error("💥 Error fetching node info: \(error.localizedDescription, privacy: .public)")
}