CLIENT_BASE add-favorite/role-change confirmation dialog (#1493)

* FavoriteNodeButton: refactor task out

* AccessoryManager.connectedDeviceRole helper

* FavoriteNodeButton: show confirmation dialog when a CLIENT_BASE is trying to add a favorite

* addContactFromURL: add comment referencing upcoming change in https://github.com/meshtastic/firmware/pull/8495

* DeviceConfig: role picker: show a warning when selecting CLIENT_BASE, similar to warning shown for ROUTER

* Adjust device configuration Client Base warning text
This commit is contained in:
Mike Robbins 2025-12-11 00:27:44 -05:00 committed by GitHub
parent c19c810749
commit 865e5e950b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 89 additions and 34 deletions

View file

@ -7662,6 +7662,10 @@
}
}
},
"Client Base should only favorite other nodes you control. Improper use will hurt your local mesh." : {
"comment" : "A message displayed in a confirmation dialog when trying to favorite a node as a CLIENT_BASE.",
"isCommentAutoGenerated" : true
},
"Client Hidden" : {
"localizations" : {
"de" : {
@ -41885,6 +41889,10 @@
}
}
},
"Yes, I control this node" : {
"comment" : "A button label that appears in a confirmation sheet when favoriting a node as a CLIENT_BASE.",
"isCommentAutoGenerated" : true
},
"Yesterday" : {
"localizations" : {
"de" : {

View file

@ -165,6 +165,7 @@ extension AccessoryManager {
nodeMeshPacket.decoded = dataNodeMessage
// Update local database with the new node info
// FUTURE: after https://github.com/meshtastic/firmware/pull/8495 is merged, `favorite: true` becomes `favorite: (connectedDeviceRole != DeviceRoles.clientBase)`
upsertNodeInfoPacket(packet: nodeMeshPacket, favorite: true, context: context)
}
} catch {

View file

@ -706,6 +706,13 @@ extension AccessoryManager {
return activeConnection?.device.firmwareVersion
}
var connectedDeviceRole: DeviceRoles? {
guard let connectedNodeNum = activeDeviceNum else { return nil }
guard let connectedNode = getNodeInfo(id: connectedNodeNum, context: context) else { return nil }
guard let connectedNodeUser = connectedNode.user else { return nil }
return DeviceRoles(rawValue: Int(connectedNodeUser.role))
}
func checkIsVersionSupported(forVersion: String) -> Bool {
let myVersion = connectedVersion ?? "0.0.0"
let supportedVersion = UserDefaults.firmwareVersion == "0.0.0" ||

View file

@ -8,39 +8,20 @@ struct FavoriteNodeButton: View {
@Environment(\.managedObjectContext) var context
@ObservedObject var node: NodeInfoEntity
@State var isShowingClientBaseConfirmation = false
var body: some View {
let connectedRoleIsClientBase = accessoryManager.connectedDeviceRole == DeviceRoles.clientBase
Button {
// Special case for CLIENT_BASE: show confirmation when attempting to favorite a node
if connectedRoleIsClientBase && !node.favorite {
isShowingClientBaseConfirmation = true
return
}
// Normal case: perform action immediately
guard let connectedNodeNum = accessoryManager.activeDeviceNum else { return }
Task {
do {
if node.favorite {
try await accessoryManager.removeFavoriteNode(
node: node,
connectedNodeNum: Int64(connectedNodeNum)
)
} else {
try await accessoryManager.setFavoriteNode(
node: node,
connectedNodeNum: Int64(connectedNodeNum)
)
}
Task { @MainActor in
// Update CoreData
node.favorite = !node.favorite
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save Node Favorite Error")
}
Logger.data.debug("Favorited a node")
}
} catch {
}
await assignFavorite(node: node, setToFavorite: !node.favorite, connectedNodeNum: Int64(connectedNodeNum))
}
} label: {
Label {
@ -50,5 +31,51 @@ struct FavoriteNodeButton: View {
.symbolRenderingMode(.multicolor)
}
}
.confirmationDialog(
"Are you sure?",
isPresented: $isShowingClientBaseConfirmation,
titleVisibility: .visible
) {
Button("Yes, I control this node") {
guard let connectedNodeNum = accessoryManager.activeDeviceNum else { return }
Task {
await assignFavorite(node: node, setToFavorite: true, connectedNodeNum: Int64(connectedNodeNum))
}
}
Button("Cancel", role: .cancel) { }
} message: {
Text("Client Base should only favorite other nodes you control. Improper use will hurt your local mesh.")
}
}
private func assignFavorite (node: NodeInfoEntity, setToFavorite: Bool, connectedNodeNum: Int64) async {
do {
if setToFavorite {
try await accessoryManager.setFavoriteNode(
node: node,
connectedNodeNum: Int64(connectedNodeNum)
)
} else {
try await accessoryManager.removeFavoriteNode(
node: node,
connectedNodeNum: Int64(connectedNodeNum)
)
}
Task { @MainActor in
// Update CoreData
node.favorite = setToFavorite
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save Node Favorite Error")
}
Logger.data.debug("Favorited a node")
}
} catch {
}
}
}

View file

@ -29,8 +29,9 @@ struct DeviceConfig: View {
@State var ledHeartbeatEnabled = true
@State var tripleClickAsAdHocPing = true
@State var tzdef = ""
@State private var showRouterWarning = false
@State private var showSpecialRoleWarning = false
@State private var showSpecialRoleWarningForRole: Int = 0
var body: some View {
Form {
ConfigHeader(title: "Device", config: \.deviceConfig, node: node, onAppear: setDeviceValues)
@ -43,13 +44,14 @@ struct DeviceConfig: View {
}
}
.onChange(of: deviceRole) { _, newRole in
if hasChanges && [DeviceRoles.router.rawValue, DeviceRoles.routerLate.rawValue].contains(newRole) {
showRouterWarning = true
if hasChanges && [DeviceRoles.router.rawValue, DeviceRoles.routerLate.rawValue, DeviceRoles.clientBase.rawValue].contains(newRole) {
showSpecialRoleWarningForRole = newRole
showSpecialRoleWarning = true
}
}
.confirmationDialog(
"Are you sure?",
isPresented: $showRouterWarning,
isPresented: $showSpecialRoleWarning,
titleVisibility: .visible
) {
@ -60,7 +62,7 @@ struct DeviceConfig: View {
setDeviceValues()
}
} message: {
Text("The Router roles are only for high vantage locations like mountaintops and towers with few nearby nodes, not for use in urban areas. Improper use will hurt your local mesh.")
Text(specialRoleWarningMessage(newRole: showSpecialRoleWarningForRole))
}
Text(DeviceRoles(rawValue: deviceRole)?.description ?? "")
.foregroundColor(.gray)
@ -332,4 +334,14 @@ struct DeviceConfig: View {
self.tzdef = node?.deviceConfig?.tzdef ?? ""
hasChanges = false
}
private func specialRoleWarningMessage(newRole: Int) -> String {
if [DeviceRoles.router.rawValue, DeviceRoles.routerLate.rawValue].contains(newRole) {
return "The Router roles are only for high vantage locations like mountaintops and towers with few nearby nodes, not for use in urban areas. Improper use will hurt your local mesh."
} else if newRole == DeviceRoles.clientBase.rawValue {
return "Switching to Client Base will clear this node's favorites. Client Base should only favorite other nodes you control. Improper use will hurt your local mesh."
} else {
return ""
}
}
}