mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
376 lines
14 KiB
Swift
376 lines
14 KiB
Swift
//
|
|
// CannedMessagesConfig.swift
|
|
// Meshtastic Apple
|
|
//
|
|
// Copyright (c) Garth Vander Houwen 6/22/22.
|
|
//
|
|
import SwiftUI
|
|
|
|
struct CannedMessagesConfig: View {
|
|
@Environment(\.managedObjectContext) var context
|
|
@EnvironmentObject var bleManager: BLEManager
|
|
@Environment(\.dismiss) private var goBack
|
|
var node: NodeInfoEntity?
|
|
@State private var isPresentingSaveConfirm: Bool = false
|
|
@State var hasChanges = false
|
|
@State var hasMessagesChanges = false
|
|
@State var configPreset = 0
|
|
@State var enabled = false
|
|
/// CannedMessageModule will sends a bell character with the messages.
|
|
@State var sendBell: Bool = false
|
|
/// Enable the rotary encoder #1. This is a 'dumb' encoder sending pulses on both A and B pins while rotating.
|
|
@State var rotary1Enabled = false
|
|
/// Enable the Up/Down/Select input device. Can be RAK rotary encoder or 3 buttons. Uses the a/b/press definitions from inputbroker.
|
|
@State var updown1Enabled: Bool = false
|
|
/// GPIO pin for rotary encoder A port.
|
|
@State var inputbrokerPinA = 0
|
|
/// GPIO pin for rotary encoder B port.
|
|
@State var inputbrokerPinB = 0
|
|
/// GPIO pin for rotary encoder Press port.
|
|
@State var inputbrokerPinPress = 0
|
|
/// Generate input event on CW of this kind.
|
|
@State var inputbrokerEventCw = 0
|
|
/// Generate input event on CCW of this kind.
|
|
@State var inputbrokerEventCcw = 0
|
|
/// Generate input event on Press of this kind.
|
|
@State var inputbrokerEventPress = 0
|
|
@State var messages = ""
|
|
var body: some View {
|
|
VStack {
|
|
Form {
|
|
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
|
|
Text("There has been no response to a request for device metadata over the admin channel for this node.")
|
|
.font(.callout)
|
|
.foregroundColor(.orange)
|
|
|
|
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
|
|
// Let users know what is going on if they are using remote admin and don't have the config yet
|
|
if node?.cannedMessageConfig == nil {
|
|
Text("Canned messages config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
|
|
.font(.callout)
|
|
.foregroundColor(.orange)
|
|
} else {
|
|
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
|
|
.font(.title3)
|
|
.onAppear {
|
|
setCannedMessagesValues()
|
|
}
|
|
}
|
|
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
|
|
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
|
|
.font(.title3)
|
|
} else {
|
|
Text("Please connect to a radio to configure settings.")
|
|
.font(.callout)
|
|
.foregroundColor(.orange)
|
|
}
|
|
Section(header: Text("options")) {
|
|
Toggle(isOn: $enabled) {
|
|
|
|
Label("enabled", systemImage: "list.bullet.rectangle.fill")
|
|
}
|
|
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
|
Toggle(isOn: $sendBell) {
|
|
|
|
Label("Send Bell", systemImage: "bell")
|
|
}
|
|
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
|
Picker("Configuration Presets", selection: $configPreset ) {
|
|
ForEach(ConfigPresets.allCases) { cp in
|
|
Text(cp.description)
|
|
}
|
|
}
|
|
.pickerStyle(DefaultPickerStyle())
|
|
.padding(.top, 10)
|
|
.padding(.bottom, 10)
|
|
}
|
|
HStack {
|
|
Label("Messages", systemImage: "message.fill")
|
|
TextField("Messages separate with |", text: $messages, axis: .vertical)
|
|
.foregroundColor(.gray)
|
|
.autocapitalization(.none)
|
|
.disableAutocorrection(true)
|
|
.onChange(of: messages, perform: { _ in
|
|
|
|
let totalBytes = messages.utf8.count
|
|
// Only mess with the value if it is too big
|
|
if totalBytes > 198 {
|
|
|
|
let firstNBytes = Data(messages.utf8.prefix(198))
|
|
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
|
// Set the shortName back to the last place where it was the right size
|
|
messages = maxBytesString
|
|
}
|
|
}
|
|
hasMessagesChanges = true
|
|
})
|
|
.foregroundColor(.gray)
|
|
}
|
|
.keyboardType(.default)
|
|
Section(header: Text("Control Type")) {
|
|
Toggle(isOn: $rotary1Enabled) {
|
|
|
|
Label("Rotary 1", systemImage: "dial.min")
|
|
}
|
|
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
|
.disabled(updown1Enabled)
|
|
Toggle(isOn: $updown1Enabled) {
|
|
|
|
Label("Up Down 1", systemImage: "arrow.up.arrow.down")
|
|
}
|
|
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
|
.disabled(rotary1Enabled)
|
|
}
|
|
.disabled(configPreset > 0)
|
|
Section(header: Text("Inputs")) {
|
|
Picker("Pin A", selection: $inputbrokerPinA) {
|
|
ForEach(0..<48) {
|
|
if $0 == 0 {
|
|
Text("unset")
|
|
} else {
|
|
Text("Pin \($0)")
|
|
}
|
|
}
|
|
}
|
|
.pickerStyle(DefaultPickerStyle())
|
|
Text("GPIO pin for rotary encoder A port.")
|
|
.font(.caption)
|
|
Picker("Pin B", selection: $inputbrokerPinB) {
|
|
ForEach(0..<48) {
|
|
if $0 == 0 {
|
|
Text("unset")
|
|
} else {
|
|
Text("Pin \($0)")
|
|
}
|
|
}
|
|
}
|
|
.pickerStyle(DefaultPickerStyle())
|
|
Text("GPIO pin for rotary encoder B port.")
|
|
.font(.caption)
|
|
Picker("Press Pin", selection: $inputbrokerPinPress) {
|
|
ForEach(0..<48) {
|
|
if $0 == 0 {
|
|
Text("unset")
|
|
} else {
|
|
Text("Pin \($0)")
|
|
}
|
|
}
|
|
}
|
|
.pickerStyle(DefaultPickerStyle())
|
|
Text("GPIO pin for rotary encoder Press port.")
|
|
.font(.caption)
|
|
}
|
|
.disabled(configPreset > 0)
|
|
Section(header: Text("Key Mapping")) {
|
|
Picker("Clockwise Rotary Event", selection: $inputbrokerEventCw ) {
|
|
ForEach(InputEventChars.allCases) { iec in
|
|
Text(iec.description)
|
|
}
|
|
}
|
|
.pickerStyle(DefaultPickerStyle())
|
|
.padding(.top, 10)
|
|
.padding(.bottom, 10)
|
|
Picker("Counter Clockwise Rotary Event", selection: $inputbrokerEventCcw ) {
|
|
ForEach(InputEventChars.allCases) { iec in
|
|
Text(iec.description)
|
|
}
|
|
}
|
|
.pickerStyle(DefaultPickerStyle())
|
|
.padding(.top, 10)
|
|
.padding(.bottom, 10)
|
|
Picker("Encoder Press Event", selection: $inputbrokerEventPress ) {
|
|
ForEach(InputEventChars.allCases) { iec in
|
|
Text(iec.description)
|
|
}
|
|
}
|
|
.pickerStyle(DefaultPickerStyle())
|
|
.padding(.top, 10)
|
|
.padding(.bottom, 10)
|
|
}
|
|
.disabled(configPreset > 0)
|
|
}
|
|
.scrollDismissesKeyboard(.immediately)
|
|
.disabled(self.bleManager.connectedPeripheral == nil || node?.cannedMessageConfig == nil)
|
|
Button {
|
|
isPresentingSaveConfirm = true
|
|
} label: {
|
|
Label("save", systemImage: "square.and.arrow.down")
|
|
}
|
|
.disabled(bleManager.connectedPeripheral == nil || (!hasChanges && !hasMessagesChanges))
|
|
.buttonStyle(.bordered)
|
|
.buttonBorderShape(.capsule)
|
|
.controlSize(.large)
|
|
.padding()
|
|
.confirmationDialog(
|
|
"are.you.sure",
|
|
isPresented: $isPresentingSaveConfirm,
|
|
titleVisibility: .visible
|
|
) {
|
|
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "unknown".localized
|
|
let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName)
|
|
Button(buttonText) {
|
|
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
|
if hasChanges {
|
|
if connectedNode != nil {
|
|
var cmc = ModuleConfig.CannedMessageConfig()
|
|
cmc.enabled = enabled
|
|
cmc.sendBell = sendBell
|
|
cmc.rotary1Enabled = rotary1Enabled
|
|
cmc.updown1Enabled = updown1Enabled
|
|
if rotary1Enabled {
|
|
/// Input event origin accepted by the canned messages
|
|
/// Can be e.g. "rotEnc1", "upDownEnc1", "cardkb", or keyword "_any"
|
|
cmc.allowInputSource = "rotEnc1"
|
|
} else if updown1Enabled {
|
|
cmc.allowInputSource = "upDown1"
|
|
} else {
|
|
cmc.allowInputSource = "_any"
|
|
}
|
|
cmc.inputbrokerPinA = UInt32(inputbrokerPinA)
|
|
cmc.inputbrokerPinB = UInt32(inputbrokerPinB)
|
|
cmc.inputbrokerPinPress = UInt32(inputbrokerPinPress)
|
|
cmc.inputbrokerEventCw = InputEventChars(rawValue: inputbrokerEventCw)!.protoEnumValue()
|
|
cmc.inputbrokerEventCcw = InputEventChars(rawValue: inputbrokerEventCcw)!.protoEnumValue()
|
|
cmc.inputbrokerEventPress = InputEventChars(rawValue: inputbrokerEventPress)!.protoEnumValue()
|
|
let adminMessageId = bleManager.saveCannedMessageModuleConfig(config: cmc, fromUser: node!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
|
if adminMessageId > 0 {
|
|
// Should show a saved successfully alert once I know that to be true
|
|
// for now just disable the button after a successful save
|
|
hasChanges = false
|
|
goBack()
|
|
}
|
|
}
|
|
}
|
|
if hasMessagesChanges {
|
|
let adminMessageId = bleManager.saveCannedMessageModuleMessages(messages: messages, fromUser: node!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
|
if adminMessageId > 0 {
|
|
// Should show a saved successfully alert once I know that to be true
|
|
// for now just disable the button after a successful save
|
|
hasMessagesChanges = false
|
|
if !hasChanges {
|
|
bleManager.sendWantConfig()
|
|
goBack()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
message: {
|
|
Text("config.save.confirm")
|
|
}
|
|
.navigationTitle("canned.messages.config")
|
|
.navigationBarItems(trailing:
|
|
ZStack {
|
|
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
|
})
|
|
.onAppear {
|
|
if self.bleManager.context == nil {
|
|
self.bleManager.context = context
|
|
}
|
|
setCannedMessagesValues()
|
|
// Need to request a CannedMessagesModuleConfig from the remote node before allowing changes
|
|
if bleManager.connectedPeripheral != nil && node?.cannedMessageConfig == nil {
|
|
print("empty canned messages module config")
|
|
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
|
if node != nil && connectedNode != nil {
|
|
_ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
|
}
|
|
}
|
|
}
|
|
.onChange(of: configPreset) { newPreset in
|
|
|
|
if newPreset == 1 {
|
|
|
|
// RAK Rotary Encoder
|
|
updown1Enabled = true
|
|
rotary1Enabled = false
|
|
inputbrokerPinA = 4
|
|
inputbrokerPinB = 10
|
|
inputbrokerPinPress = 9
|
|
inputbrokerEventCw = InputEventChars.down.rawValue
|
|
inputbrokerEventCcw = InputEventChars.up.rawValue
|
|
inputbrokerEventPress = InputEventChars.select.rawValue
|
|
|
|
} else if newPreset == 2 {
|
|
|
|
// CardKB / RAK Keypad
|
|
updown1Enabled = false
|
|
rotary1Enabled = false
|
|
inputbrokerPinA = 0
|
|
inputbrokerPinB = 0
|
|
inputbrokerPinPress = 0
|
|
inputbrokerEventCw = InputEventChars.none.rawValue
|
|
inputbrokerEventCcw = InputEventChars.none.rawValue
|
|
inputbrokerEventPress = InputEventChars.none.rawValue
|
|
}
|
|
|
|
hasChanges = true
|
|
}
|
|
.onChange(of: enabled) { newEnabled in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newEnabled != node!.cannedMessageConfig!.enabled { hasChanges = true }
|
|
}
|
|
}
|
|
.onChange(of: sendBell) { newBell in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newBell != node!.cannedMessageConfig!.sendBell { hasChanges = true }
|
|
}
|
|
}
|
|
.onChange(of: rotary1Enabled) { newRot1 in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newRot1 != node!.cannedMessageConfig!.rotary1Enabled { hasChanges = true }
|
|
}
|
|
}
|
|
.onChange(of: updown1Enabled) { newUpDown in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newUpDown != node!.cannedMessageConfig!.updown1Enabled { hasChanges = true }
|
|
}
|
|
}
|
|
.onChange(of: inputbrokerPinA) { newPinA in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newPinA != node!.cannedMessageConfig!.inputbrokerPinA { hasChanges = true }
|
|
}
|
|
}
|
|
.onChange(of: inputbrokerPinB) { newPinB in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newPinB != node!.cannedMessageConfig!.inputbrokerPinB { hasChanges = true }
|
|
}
|
|
}
|
|
.onChange(of: inputbrokerPinPress) { newPinPress in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newPinPress != node!.cannedMessageConfig!.inputbrokerPinPress { hasChanges = true }
|
|
}
|
|
}
|
|
.onChange(of: inputbrokerEventCw) { newKeyA in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newKeyA != node!.cannedMessageConfig!.inputbrokerEventCw { hasChanges = true }
|
|
}
|
|
}
|
|
.onChange(of: inputbrokerEventCcw) { newKeyB in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newKeyB != node!.cannedMessageConfig!.inputbrokerEventCcw { hasChanges = true }
|
|
}
|
|
}
|
|
.onChange(of: inputbrokerEventPress) { newKeyPress in
|
|
if node != nil && node!.cannedMessageConfig != nil {
|
|
if newKeyPress != node!.cannedMessageConfig!.inputbrokerEventPress { hasChanges = true }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
func setCannedMessagesValues() {
|
|
self.enabled = node?.cannedMessageConfig?.enabled ?? false
|
|
self.sendBell = node?.cannedMessageConfig?.sendBell ?? false
|
|
self.rotary1Enabled = node?.cannedMessageConfig?.rotary1Enabled ?? false
|
|
self.updown1Enabled = node?.cannedMessageConfig?.updown1Enabled ?? false
|
|
self.inputbrokerPinA = Int(node?.cannedMessageConfig?.inputbrokerPinA ?? 0)
|
|
self.inputbrokerPinB = Int(node?.cannedMessageConfig?.inputbrokerPinB ?? 0)
|
|
self.inputbrokerPinPress = Int(node?.cannedMessageConfig?.inputbrokerPinPress ?? 0)
|
|
self.inputbrokerEventCw = Int(node?.cannedMessageConfig?.inputbrokerEventCw ?? 0)
|
|
self.inputbrokerEventCcw = Int(node?.cannedMessageConfig?.inputbrokerEventCcw ?? 0)
|
|
self.inputbrokerEventPress = Int(node?.cannedMessageConfig?.inputbrokerEventPress ?? 0)
|
|
self.messages = node?.cannedMessageConfig?.messages ?? ""
|
|
self.hasChanges = false
|
|
self.hasMessagesChanges = false
|
|
}
|
|
}
|