From c3cede2d76fac5398fc513fa02e0a4ccd4d35219 Mon Sep 17 00:00:00 2001 From: Austin Payne Date: Sat, 17 Feb 2024 22:39:22 -0700 Subject: [PATCH 1/3] refactor: add ConfigHeader view for settings --- Meshtastic.xcodeproj/project.pbxproj | 4 ++ .../Settings/Config/BluetoothConfig.swift | 25 +------------ .../Views/Settings/Config/ConfigHeader.swift | 37 +++++++++++++++++++ .../Views/Settings/Config/DeviceConfig.swift | 26 +------------ .../Views/Settings/Config/DisplayConfig.swift | 26 +------------ .../Views/Settings/Config/LoRaConfig.swift | 28 +------------- .../Config/Module/AmbientLightingConfig.swift | 27 +------------- .../Config/Module/CannedMessagesConfig.swift | 27 +------------- .../Config/Module/DetectionSensorConfig.swift | 27 +------------- .../Module/ExternalNotificationConfig.swift | 27 +------------- .../Settings/Config/Module/MQTTConfig.swift | 29 ++------------- .../Config/Module/RangeTestConfig.swift | 27 +------------- .../Settings/Config/Module/RtttlConfig.swift | 27 +------------- .../Settings/Config/Module/SerialConfig.swift | 27 +------------- .../Config/Module/StoreForwardConfig.swift | 27 +------------- .../Config/Module/TelemetryConfig.swift | 27 +------------- .../Views/Settings/Config/NetworkConfig.swift | 28 +------------- .../Settings/Config/PositionConfig.swift | 27 +------------- 18 files changed, 71 insertions(+), 402 deletions(-) create mode 100644 Meshtastic/Views/Settings/Config/ConfigHeader.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c7601ad9..c03728a0 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D42B812B700066FBC8 /* MessageDestination.swift */; }; D93068D72B8146690066FBC8 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D62B8146690066FBC8 /* MessageText.swift */; }; D93068D92B81509C0066FBC8 /* TapbackResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D82B81509C0066FBC8 /* TapbackResponses.swift */; }; + D93068DD2B81CA820066FBC8 /* ConfigHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */; }; D9BC22DB2B7DE8E2006A37D5 /* TileDownloadStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */; }; D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */; }; D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */; }; @@ -243,6 +244,7 @@ D93068D42B812B700066FBC8 /* MessageDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDestination.swift; sourceTree = ""; }; D93068D62B8146690066FBC8 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; D93068D82B81509C0066FBC8 /* TapbackResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapbackResponses.swift; sourceTree = ""; }; + D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigHeader.swift; sourceTree = ""; }; D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileDownloadStatus.swift; sourceTree = ""; }; D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSize.swift; sourceTree = ""; }; D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = ""; }; @@ -636,6 +638,7 @@ DD61937A2863876A00E59241 /* Config */ = { isa = PBXGroup; children = ( + D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */, DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */, DD41582528582E9B009B0E59 /* DeviceConfig.swift */, DD8EBF42285058FA00426DCA /* DisplayConfig.swift */, @@ -1268,6 +1271,7 @@ DDDB444029F79AB000EE2349 /* UserDefaults.swift in Sources */, DDB6ABE028B13AC700384BA1 /* DeviceEnums.swift in Sources */, DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */, + D93068DD2B81CA820066FBC8 /* ConfigHeader.swift in Sources */, DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */, D9BC22DB2B7DE8E2006A37D5 /* TileDownloadStatus.swift in Sources */, DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */, diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 81533001..02ec930e 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -26,31 +26,8 @@ struct BluetoothConfig: View { }() var body: some View { 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) + ConfigHeader(title: "Bluetooth", config: \.bluetoothConfig, node: node, onAppear: setBluetoothValues) - } 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?.bluetoothConfig == nil { - Text("Bluetooth 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 { - setBluetoothValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? -1 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } Section(header: Text("options")) { Toggle(isOn: $enabled) { Label("enabled", systemImage: "antenna.radiowaves.left.and.right") diff --git a/Meshtastic/Views/Settings/Config/ConfigHeader.swift b/Meshtastic/Views/Settings/Config/ConfigHeader.swift new file mode 100644 index 00000000..3ff815f8 --- /dev/null +++ b/Meshtastic/Views/Settings/Config/ConfigHeader.swift @@ -0,0 +1,37 @@ +import SwiftUI +import CoreData + +struct ConfigHeader: View { + @EnvironmentObject var bleManager: BLEManager + + let title: String + let config: KeyPath + let node: NodeInfoEntity? + let onAppear: () -> Void + + var body: some View { + 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?[keyPath: config] == nil { + Text("\(title) 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(perform: onAppear) + } + } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? -1 { + Text("Configuration for: \(node?.user?.longName ?? "Unknown")") + } else { + Text("Please connect to a radio to configure settings.") + .font(.callout) + .foregroundColor(.orange) + } + } +} diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 54d4d912..af81b886 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -32,30 +32,8 @@ struct DeviceConfig: View { 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?.deviceConfig == nil { - Text("Device 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 { - setDeviceValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Device", config: \.deviceConfig, node: node, onAppear: setDeviceValues) + Section(header: Text("options")) { Picker("Device Role", selection: $deviceRole ) { ForEach(DeviceRoles.allCases) { dr in diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index c2e2523f..f9229dda 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -29,33 +29,9 @@ struct DisplayConfig: View { @State var units = 0 var body: some View { - 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) + ConfigHeader(title: "Display", config: \.displayConfig, node: node, onAppear: setDisplayValues) - } 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?.displayConfig == nil { - Text("Display 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 { - setDisplayValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } Section(header: Text("Device Screen")) { Picker("Display Mode", selection: $displayMode ) { ForEach(DisplayModes.allCases) { dm in diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index e1875294..b6f9d18e 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -52,34 +52,10 @@ struct LoRaConfig: View { }() 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?.loRaConfig == nil { - Text("LoRa 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 { - setLoRaValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "LoRa", config: \.loRaConfig, node: node, onAppear: setLoRaValues) + Section(header: Text("Options")) { Picker("Region", selection: $region ) { diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index 0a449682..e3ba2089 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -27,31 +27,8 @@ struct AmbientLightingConfig: View { 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?.rtttlConfig == nil { - Text("Ambient Lighting 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 { - setAmbientLightingConfigValue() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Ambient Lighting", config: \.ambientLightingConfig, node: node, onAppear: setAmbientLightingConfigValue) + Section(header: Text("options")) { Toggle(isOn: $ledState) { Label("LED State", systemImage: ledState ? "lightbulb.led.fill" : "lightbulb.led") diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 02fb0539..34609d04 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -38,31 +38,8 @@ struct CannedMessagesConfig: View { 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")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Canned messages", config: \.cannedMessageConfig, node: node, onAppear: setCannedMessagesValues) + Section(header: Text("options")) { Toggle(isOn: $enabled) { diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 64688edc..7170ad5a 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -43,31 +43,8 @@ struct DetectionSensorConfig: View { 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?.detectionSensorConfig == nil { - Text("Detection Sensor 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 { - setDetectionSensorValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Detection Sensor", config: \.detectionSensorConfig, node: node, onAppear: setDetectionSensorValues) + Section(header: Text("options")) { Toggle(isOn: $enabled) { diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index cf218762..edc02f9e 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -35,31 +35,8 @@ struct ExternalNotificationConfig: View { 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?.externalNotificationConfig == nil { - Text("External notification 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 { - setExternalNotificationValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "External notification", config: \.externalNotificationConfig, node: node, onAppear: setExternalNotificationValues) + Section(header: Text("options")) { Toggle(isOn: $enabled) { Label("enabled", systemImage: "megaphone") diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index b89792ed..d00931c1 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -38,32 +38,9 @@ struct MQTTConfig: View { .foregroundColor(.red) } } - - 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?.mqttConfig == nil { - Text("MQTT 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 { - setMqttValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + + ConfigHeader(title: "MQTT", config: \.mqttConfig, node: node, onAppear: setMqttValues) + Section(header: Text("options")) { Toggle(isOn: $enabled) { diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index ac80a840..128a91ce 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -23,31 +23,8 @@ struct RangeTestConfig: View { 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?.rangeTestConfig == nil { - Text("Range test 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 { - setRangeTestValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Range", config: \.rangeTestConfig, node: node, onAppear: setRangeTestValues) + Section(header: Text("options")) { Toggle(isOn: $enabled) { diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index 0d43b4f5..ebbc928c 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -21,31 +21,8 @@ struct RtttlConfig: View { 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?.rtttlConfig == nil { - Text("RTTTL Ringtone 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 { - setRtttLConfigValue() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "RTTTL Ringtone", config: \.rtttlConfig, node: node, onAppear: setRtttLConfigValue) + Section(header: Text("options")) { HStack { Label("ringtone", systemImage: "music.quarternote.3") diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 8d27dddf..dee9cf6e 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -31,31 +31,8 @@ struct SerialConfig: View { 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?.serialConfig == nil { - Text("Serial 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 { - setSerialValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Serial", config: \.serialConfig, node: node, onAppear: setSerialValues) + Section(header: Text("options")) { Toggle(isOn: $enabled) { diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index 4edc42b0..e8ead95b 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -31,31 +31,8 @@ struct StoreForwardConfig: View { 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?.storeForwardConfig == nil { - Text("Store and forward 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 { - setStoreAndForwardValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Store and forward", config: \.storeForwardConfig, node: node, onAppear: setStoreAndForwardValues) + Section(header: Text("options")) { Toggle(isOn: $enabled) { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 9f153808..c2a50db3 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -25,31 +25,8 @@ struct TelemetryConfig: View { 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?.telemetryConfig == nil { - Text("Telemetry 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 { - setTelemetryValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Telemetry", config: \.telemetryConfig, node: node, onAppear: setTelemetryValues) + Section(header: Text("update.interval")) { Picker("Device Metrics", selection: $deviceUpdateInterval ) { ForEach(UpdateIntervals.allCases) { ui in diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 96ac19b4..659126ea 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -26,34 +26,10 @@ struct NetworkConfig: View { @State var ethMode = 0 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?.networkConfig == nil { - Text("Network 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 { - setNetworkValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Network", config: \.networkConfig, node: node, onAppear: setNetworkValues) + if (node != nil && node?.metadata?.hasWifi ?? false) { Section(header: Text("WiFi Options")) { Toggle(isOn: $wifiEnabled) { diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index c5bdd94b..d577c571 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -74,34 +74,9 @@ struct PositionConfig: View { @State var includeHeading = false 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?.positionConfig == nil { - Text("Position 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 { - setPositionValues() - } - } - } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { - Text("Configuration for: \(node?.user?.longName ?? "Unknown")") - } else { - Text("Please connect to a radio to configure settings.") - .font(.callout) - .foregroundColor(.orange) - } + ConfigHeader(title: "Position", config: \.positionConfig, node: node, onAppear: setPositionValues) Section(header: Text("Position Packet")) { From 0daf9185e714778fe3d9fdad3d54f493f7fd2a7b Mon Sep 17 00:00:00 2001 From: Austin Payne Date: Sun, 18 Feb 2024 00:03:34 -0700 Subject: [PATCH 2/3] refactor: add SaveConfigButton view for settings --- .../Settings/Config/BluetoothConfig.swift | 49 +++------- .../Views/Settings/Config/DeviceConfig.swift | 61 ++++-------- .../Views/Settings/Config/DisplayConfig.swift | 64 ++++-------- .../Views/Settings/Config/LoRaConfig.swift | 69 +++++-------- .../Config/Module/AmbientLightingConfig.swift | 53 +++------- .../Config/Module/CannedMessagesConfig.swift | 98 ++++++++----------- .../Config/Module/DetectionSensorConfig.swift | 53 +++------- .../Module/ExternalNotificationConfig.swift | 68 +++++-------- .../Settings/Config/Module/MQTTConfig.swift | 56 ++++------- .../Config/Module/RangeTestConfig.swift | 45 +++------ .../Settings/Config/Module/RtttlConfig.swift | 37 ++----- .../Settings/Config/Module/SerialConfig.swift | 61 ++++-------- .../Config/Module/StoreForwardConfig.swift | 69 +++++-------- .../Config/Module/TelemetryConfig.swift | 48 +++------ .../Views/Settings/Config/NetworkConfig.swift | 52 +++------- .../Settings/Config/PositionConfig.swift | 93 +++++++----------- .../Settings/Config/SaveConfigButton.swift | 36 +++++++ 17 files changed, 354 insertions(+), 658 deletions(-) create mode 100644 Meshtastic/Views/Settings/Config/SaveConfigButton.swift diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 02ec930e..6c1167a3 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -12,7 +12,6 @@ struct BluetoothConfig: View { @EnvironmentObject var bleManager: BLEManager @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? - @State private var isPresentingSaveConfirm: Bool = false @State var hasChanges = false @State var enabled = true @State var mode = 0 @@ -71,42 +70,24 @@ struct BluetoothConfig: View { } } .disabled(self.bleManager.connectedPeripheral == nil || node?.bluetoothConfig == nil) - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || shortPin) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if connectedNode != nil { - var bc = Config.BluetoothConfig() - bc.enabled = enabled - bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin - bc.fixedPin = UInt32(fixedPin) ?? 123456 - let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: connectedNode!.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() - } + + SaveConfigButton(node: node, hasChanges: $hasChanges) { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + if connectedNode != nil { + var bc = Config.BluetoothConfig() + bc.enabled = enabled + bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin + bc.fixedPin = UInt32(fixedPin) ?? 123456 + let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: connectedNode!.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() } } - } message: { - Text("config.save.confirm") } + .navigationTitle("bluetooth.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index af81b886..d6971ba1 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -16,9 +16,7 @@ struct DeviceConfig: View { @State private var isPresentingNodeDBResetConfirm = false @State private var isPresentingFactoryResetConfirm = false - @State private var isPresentingSaveConfirm = false @State var hasChanges = false - @State var deviceRole = 0 @State var buzzerGPIO = 0 @State var buttonGPIO = 0 @@ -167,49 +165,28 @@ struct DeviceConfig: View { } } HStack { - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if connectedNode != nil { - var dc = Config.DeviceConfig() - dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue() - dc.serialEnabled = serialEnabled - dc.debugLogEnabled = debugLogEnabled - dc.buttonGpio = UInt32(buttonGPIO) - dc.buzzerGpio = UInt32(buzzerGPIO) - dc.rebroadcastMode = RebroadcastModes(rawValue: rebroadcastMode)?.protoEnumValue() ?? RebroadcastModes.all.protoEnumValue() - dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs) - dc.doubleTapAsButtonPress = doubleTapAsButtonPress - dc.isManaged = isManaged - let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.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() - } + SaveConfigButton(node: node, hasChanges: $hasChanges) { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + if connectedNode != nil { + var dc = Config.DeviceConfig() + dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue() + dc.serialEnabled = serialEnabled + dc.debugLogEnabled = debugLogEnabled + dc.buttonGpio = UInt32(buttonGPIO) + dc.buzzerGpio = UInt32(buzzerGPIO) + dc.rebroadcastMode = RebroadcastModes(rawValue: rebroadcastMode)?.protoEnumValue() ?? RebroadcastModes.all.protoEnumValue() + dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs) + dc.doubleTapAsButtonPress = doubleTapAsButtonPress + dc.isManaged = isManaged + let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.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() } } } - message: { - Text("config.save.confirm") - } } Spacer() } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index f9229dda..ebdfa9b7 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -15,9 +15,7 @@ struct DisplayConfig: View { var node: NodeInfoEntity? - @State private var isPresentingSaveConfirm: Bool = false @State var hasChanges = false - @State var screenOnSeconds = 0 @State var screenCarouselInterval = 0 @State var gpsFormat = 0 @@ -114,53 +112,31 @@ struct DisplayConfig: View { } .disabled(self.bleManager.connectedPeripheral == nil || node?.displayConfig == nil) - Button { + SaveConfigButton(node: node, hasChanges: $hasChanges) { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + if connectedNode != nil { + var dc = Config.DisplayConfig() + dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue() + dc.screenOnSecs = UInt32(screenOnSeconds) + dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval) + dc.compassNorthTop = compassNorthTop + dc.wakeOnTapOrMotion = wakeOnTapOrMotion + dc.flipScreen = flipScreen + dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue() + dc.displaymode = DisplayModes(rawValue: displayMode)!.protoEnumValue() + dc.units = Units(rawValue: units)!.protoEnumValue() - isPresentingSaveConfirm = true + let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if adminMessageId > 0 { - } label: { - - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if connectedNode != nil { - var dc = Config.DisplayConfig() - dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue() - dc.screenOnSecs = UInt32(screenOnSeconds) - dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval) - dc.compassNorthTop = compassNorthTop - dc.wakeOnTapOrMotion = wakeOnTapOrMotion - dc.flipScreen = flipScreen - dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue() - dc.displaymode = DisplayModes(rawValue: displayMode)!.protoEnumValue() - dc.units = Units(rawValue: units)!.protoEnumValue() - - let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: connectedNode!.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() - } + // 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() } } } - message: { - Text("config.save.confirm") - } + .navigationTitle("display.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index b6f9d18e..e51e3a99 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -29,7 +29,6 @@ struct LoRaConfig: View { var node: NodeInfoEntity? - @State var isPresentingSaveConfirm = false @State var hasChanges = false @State var region: Int = 0 @State var modemPreset = 0 @@ -55,7 +54,7 @@ struct LoRaConfig: View { VStack { Form { ConfigHeader(title: "LoRa", config: \.loRaConfig, node: node, onAppear: setLoRaValues) - + Section(header: Text("Options")) { Picker("Region", selection: $region ) { @@ -176,51 +175,31 @@ struct LoRaConfig: View { } .disabled(self.bleManager.connectedPeripheral == nil || node?.loRaConfig == nil) - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) - if connectedNode != nil { - var lc = Config.LoRaConfig() - lc.hopLimit = UInt32(hopLimit) - lc.region = RegionCodes(rawValue: region)!.protoEnumValue() - lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() - lc.usePreset = usePreset - lc.txEnabled = txEnabled - lc.txPower = Int32(txPower) - lc.channelNum = UInt32(channelNum) - lc.bandwidth = UInt32(bandwidth) - lc.codingRate = UInt32(codingRate) - lc.spreadFactor = UInt32(spreadFactor) - lc.sx126XRxBoostedGain = rxBoostedGain - lc.overrideFrequency = overrideFrequency - lc.ignoreMqtt = ignoreMqtt - let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: connectedNode!.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() - } + SaveConfigButton(node: node, hasChanges: $hasChanges) { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + if connectedNode != nil { + var lc = Config.LoRaConfig() + lc.hopLimit = UInt32(hopLimit) + lc.region = RegionCodes(rawValue: region)!.protoEnumValue() + lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() + lc.usePreset = usePreset + lc.txEnabled = txEnabled + lc.txPower = Int32(txPower) + lc.channelNum = UInt32(channelNum) + lc.bandwidth = UInt32(bandwidth) + lc.codingRate = UInt32(codingRate) + lc.spreadFactor = UInt32(spreadFactor) + lc.sx126XRxBoostedGain = rxBoostedGain + lc.overrideFrequency = overrideFrequency + lc.ignoreMqtt = ignoreMqtt + let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: connectedNode!.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() } } - } message: { - Text("config.save.confirm") } } .navigationTitle("lora.config") diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index e3ba2089..136d4237 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -57,49 +57,28 @@ struct AmbientLightingConfig: View { } } .disabled(self.bleManager.connectedPeripheral == nil || node?.ambientLightingConfig == nil) - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(self.bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { + SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if connectedNode != nil { - var al = ModuleConfig.AmbientLightingConfig() - al.ledState = ledState - al.current = UInt32(current) - if let components { - al.red = UInt32(components.red * 255) - al.green = UInt32(components.green * 255) - al.blue = UInt32(components.blue * 255) - } + if connectedNode != nil { + var al = ModuleConfig.AmbientLightingConfig() + al.ledState = ledState + al.current = UInt32(current) + if let components { + al.red = UInt32(components.red * 255) + al.green = UInt32(components.green * 255) + al.blue = UInt32(components.blue * 255) + } - let adminMessageId = bleManager.saveAmbientLightingModuleConfig(config: al, fromUser: connectedNode!.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() - } + let adminMessageId = bleManager.saveAmbientLightingModuleConfig(config: al, fromUser: connectedNode!.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() } } } - message: { - Text("config.save.confirm") - } .navigationTitle("ambient.lighting.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 34609d04..5ece017e 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -167,72 +167,52 @@ struct CannedMessagesConfig: View { } .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() - } + + SaveConfigButton(node: node, hasChanges: $hasChanges) { + 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" } - } - if hasMessagesChanges { - let adminMessageId = bleManager.saveCannedMessageModuleMessages(messages: messages, fromUser: node!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + 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 - hasMessagesChanges = false - if !hasChanges { - bleManager.sendWantConfig() - goBack() - } + 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: diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 7170ad5a..97f34179 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -168,48 +168,27 @@ struct DetectionSensorConfig: View { .scrollDismissesKeyboard(.interactively) .disabled(self.bleManager.connectedPeripheral == nil || node?.detectionSensorConfig == nil) - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { + SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) if connectedNode != nil { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - var dsc = ModuleConfig.DetectionSensorConfig() - dsc.enabled = self.enabled - dsc.sendBell = self.sendBell - dsc.name = self.name - dsc.monitorPin = UInt32(self.monitorPin) - dsc.detectionTriggeredHigh = self.detectionTriggeredHigh - dsc.usePullup = self.usePullup - dsc.minimumBroadcastSecs = UInt32(self.minimumBroadcastSecs) - dsc.stateBroadcastSecs = UInt32(self.stateBroadcastSecs) - let adminMessageId = bleManager.saveDetectionSensorModuleConfig(config: dsc, fromUser: connectedNode!.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() - } + var dsc = ModuleConfig.DetectionSensorConfig() + dsc.enabled = self.enabled + dsc.sendBell = self.sendBell + dsc.name = self.name + dsc.monitorPin = UInt32(self.monitorPin) + dsc.detectionTriggeredHigh = self.detectionTriggeredHigh + dsc.usePullup = self.usePullup + dsc.minimumBroadcastSecs = UInt32(self.minimumBroadcastSecs) + dsc.stateBroadcastSecs = UInt32(self.stateBroadcastSecs) + let adminMessageId = bleManager.saveDetectionSensorModuleConfig(config: dsc, fromUser: connectedNode!.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() } } } - message: { - Text("config.save.confirm") - } .navigationTitle("detection.sensor.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index edc02f9e..83c278ea 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -147,55 +147,35 @@ struct ExternalNotificationConfig: View { } .disabled(self.bleManager.connectedPeripheral == nil || node?.externalNotificationConfig == nil) } - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { + + SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) if connectedNode != nil { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - var enc = ModuleConfig.ExternalNotificationConfig() - enc.enabled = enabled - enc.alertBell = alertBell - enc.alertBellBuzzer = alertBellBuzzer - enc.alertBellVibra = alertBellVibra - enc.alertMessage = alertMessage - enc.alertMessageBuzzer = alertMessageBuzzer - enc.alertMessageVibra = alertMessageVibra - enc.active = active - enc.output = UInt32(output) - enc.nagTimeout = UInt32(nagTimeout) - enc.outputBuzzer = UInt32(outputBuzzer) - enc.outputVibra = UInt32(outputVibra) - enc.outputMs = UInt32(outputMilliseconds) - enc.usePwm = usePWM - enc.useI2SAsBuzzer = useI2SAsBuzzer - let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: connectedNode!.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() - } + var enc = ModuleConfig.ExternalNotificationConfig() + enc.enabled = enabled + enc.alertBell = alertBell + enc.alertBellBuzzer = alertBellBuzzer + enc.alertBellVibra = alertBellVibra + enc.alertMessage = alertMessage + enc.alertMessageBuzzer = alertMessageBuzzer + enc.alertMessageVibra = alertMessageVibra + enc.active = active + enc.output = UInt32(output) + enc.nagTimeout = UInt32(nagTimeout) + enc.outputBuzzer = UInt32(outputBuzzer) + enc.outputVibra = UInt32(outputVibra) + enc.outputMs = UInt32(outputMilliseconds) + enc.usePwm = usePWM + enc.useI2SAsBuzzer = useI2SAsBuzzer + let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: connectedNode!.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() } } } - message: { - Text("config.save.confirm") - } .navigationTitle("external.notification.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index d00931c1..932ddda1 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -191,49 +191,29 @@ struct MQTTConfig: View { .scrollDismissesKeyboard(.interactively) .disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == nil) } - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { + + SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) if connectedNode != nil { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - var mqtt = ModuleConfig.MQTTConfig() - mqtt.enabled = self.enabled - mqtt.proxyToClientEnabled = self.proxyToClientEnabled - mqtt.address = self.address - mqtt.username = self.username - mqtt.password = self.password - mqtt.root = self.root - mqtt.encryptionEnabled = self.encryptionEnabled - mqtt.jsonEnabled = self.jsonEnabled - mqtt.tlsEnabled = self.tlsEnabled - let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.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() - } + var mqtt = ModuleConfig.MQTTConfig() + mqtt.enabled = self.enabled + mqtt.proxyToClientEnabled = self.proxyToClientEnabled + mqtt.address = self.address + mqtt.username = self.username + mqtt.password = self.password + mqtt.root = self.root + mqtt.encryptionEnabled = self.encryptionEnabled + mqtt.jsonEnabled = self.jsonEnabled + mqtt.tlsEnabled = self.tlsEnabled + let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.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() } } } - message: { - Text("config.save.confirm") - } .navigationTitle("mqtt.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 128a91ce..3574acb4 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -49,44 +49,23 @@ struct RangeTestConfig: View { } } .disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil) - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { + SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if connectedNode != nil { - var rtc = ModuleConfig.RangeTestConfig() - rtc.enabled = enabled - rtc.save = save - rtc.sender = UInt32(sender) - let adminMessageId = bleManager.saveRangeTestModuleConfig(config: rtc, fromUser: connectedNode!.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 connectedNode != nil { + var rtc = ModuleConfig.RangeTestConfig() + rtc.enabled = enabled + rtc.save = save + rtc.sender = UInt32(sender) + let adminMessageId = bleManager.saveRangeTestModuleConfig(config: rtc, fromUser: connectedNode!.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() } } } - message: { - Text("config.save.confirm") - } .navigationTitle("range.test.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index ebbc928c..81ad17d0 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -51,40 +51,19 @@ struct RtttlConfig: View { } } .disabled(self.bleManager.connectedPeripheral == nil || node?.rtttlConfig == nil) - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { + SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if connectedNode != nil { - let adminMessageId = bleManager.saveRtttlConfig(ringtone: ringtone.trimmingCharacters(in: .whitespacesAndNewlines), fromUser: connectedNode!.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 connectedNode != nil { + let adminMessageId = bleManager.saveRtttlConfig(ringtone: ringtone.trimmingCharacters(in: .whitespacesAndNewlines), fromUser: connectedNode!.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() } } } - message: { - Text("config.save.confirm") - } .navigationTitle("ringtone.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index dee9cf6e..7a0bf363 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -101,54 +101,29 @@ struct SerialConfig: View { } .disabled(self.bleManager.connectedPeripheral == nil || node?.serialConfig == nil) - Button { + SaveConfigButton(node: node, hasChanges: $hasChanges) { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + if connectedNode != nil { + var sc = ModuleConfig.SerialConfig() + sc.enabled = enabled + sc.echo = echo + sc.rxd = UInt32(rxd) + sc.txd = UInt32(txd) + sc.baud = SerialBaudRates(rawValue: baudRate)!.protoEnumValue() + sc.timeout = UInt32(timeout) + sc.overrideConsoleSerialPort = overrideConsoleSerialPort + sc.mode = SerialModeTypes(rawValue: mode)!.protoEnumValue() - isPresentingSaveConfirm = true + let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) - } label: { - - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if connectedNode != nil { - var sc = ModuleConfig.SerialConfig() - sc.enabled = enabled - sc.echo = echo - sc.rxd = UInt32(rxd) - sc.txd = UInt32(txd) - sc.baud = SerialBaudRates(rawValue: baudRate)!.protoEnumValue() - sc.timeout = UInt32(timeout) - sc.overrideConsoleSerialPort = overrideConsoleSerialPort - sc.mode = SerialModeTypes(rawValue: mode)!.protoEnumValue() - - let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: connectedNode!.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 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() } } } - message: { - Text("config.save.confirm") - } .navigationTitle("serial.config") .navigationBarItems(trailing: diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index e8ead95b..490ec9c3 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -104,57 +104,36 @@ struct StoreForwardConfig: View { .scrollDismissesKeyboard(.interactively) .disabled(self.bleManager.connectedPeripheral == nil || node?.storeForwardConfig == nil) } - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { + + SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) if connectedNode != nil { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - - /// Let the user set isRouter for the connected node, for nodes on the mesh set isRouter based - /// on receipt of a primary heartbeat - if connectedNode?.num ?? 0 == node?.num ?? -1 { - connectedNode?.storeForwardConfig?.isRouter = isRouter - do { - try context.save() - } catch { - print("Failed to save isRouter") - } - } - - var sfc = ModuleConfig.StoreForwardConfig() - sfc.enabled = self.enabled - sfc.heartbeat = self.heartbeat - sfc.records = UInt32(self.records) - sfc.historyReturnMax = UInt32(self.historyReturnMax) - sfc.historyReturnWindow = UInt32(self.historyReturnWindow) - let adminMessageId = bleManager.saveStoreForwardModuleConfig(config: sfc, fromUser: connectedNode!.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() + /// Let the user set isRouter for the connected node, for nodes on the mesh set isRouter based + /// on receipt of a primary heartbeat + if connectedNode?.num ?? 0 == node?.num ?? -1 { + connectedNode?.storeForwardConfig?.isRouter = isRouter + do { + try context.save() + } catch { + print("Failed to save isRouter") } } + + var sfc = ModuleConfig.StoreForwardConfig() + sfc.enabled = self.enabled + sfc.heartbeat = self.heartbeat + sfc.records = UInt32(self.records) + sfc.historyReturnMax = UInt32(self.historyReturnMax) + sfc.historyReturnWindow = UInt32(self.historyReturnWindow) + let adminMessageId = bleManager.saveStoreForwardModuleConfig(config: sfc, fromUser: connectedNode!.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() + } } } - message: { - Text("config.save.confirm") - } .navigationTitle("storeforward.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index c2a50db3..d71c4e62 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -63,45 +63,25 @@ struct TelemetryConfig: View { } } .disabled(self.bleManager.connectedPeripheral == nil || node?.telemetryConfig == nil) - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || node!.telemetryConfig == nil) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { + + SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) if connectedNode != nil { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - var tc = ModuleConfig.TelemetryConfig() - tc.deviceUpdateInterval = UInt32(deviceUpdateInterval) - tc.environmentUpdateInterval = UInt32(environmentUpdateInterval) - tc.environmentMeasurementEnabled = environmentMeasurementEnabled - tc.environmentScreenEnabled = environmentScreenEnabled - tc.environmentDisplayFahrenheit = environmentDisplayFahrenheit - let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: connectedNode!.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() - } + var tc = ModuleConfig.TelemetryConfig() + tc.deviceUpdateInterval = UInt32(deviceUpdateInterval) + tc.environmentUpdateInterval = UInt32(environmentUpdateInterval) + tc.environmentMeasurementEnabled = environmentMeasurementEnabled + tc.environmentScreenEnabled = environmentScreenEnabled + tc.environmentDisplayFahrenheit = environmentDisplayFahrenheit + let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: connectedNode!.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() } } } - message: { - Text("config.save.confirm") - } .navigationTitle("telemetry.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 659126ea..2d4d9457 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -15,7 +15,6 @@ struct NetworkConfig: View { var node: NodeInfoEntity? - @State private var isPresentingSaveConfirm: Bool = false @State var hasChanges: Bool = false @State var wifiEnabled = false @State var wifiSsid = "" @@ -95,44 +94,25 @@ struct NetworkConfig: View { } .scrollDismissesKeyboard(.interactively) .disabled(self.bleManager.connectedPeripheral == nil || node?.networkConfig == nil) - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if connectedNode != nil { - var network = Config.NetworkConfig() - network.wifiEnabled = self.wifiEnabled - network.wifiSsid = self.wifiSsid - network.wifiPsk = self.wifiPsk - network.ethEnabled = self.ethEnabled - // network.addressMode = Config.NetworkConfig.AddressMode.dhcp - let adminMessageId = bleManager.saveNetworkConfig(config: network, fromUser: connectedNode!.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() - } + SaveConfigButton(node: node, hasChanges: $hasChanges) { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + if connectedNode != nil { + var network = Config.NetworkConfig() + network.wifiEnabled = self.wifiEnabled + network.wifiSsid = self.wifiSsid + network.wifiPsk = self.wifiPsk + network.ethEnabled = self.ethEnabled + // network.addressMode = Config.NetworkConfig.AddressMode.dhcp + + let adminMessageId = bleManager.saveNetworkConfig(config: network, fromUser: connectedNode!.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() } } - } message: { - Text("config.save.confirm") } } .navigationTitle("network.config") diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index d577c571..710ad601 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -30,7 +30,6 @@ struct PositionConfig: View { var node: NodeInfoEntity? - @State private var isPresentingSaveConfirm: Bool = false @State var hasChanges = false @State var hasFlagChanges = false @@ -250,67 +249,45 @@ struct PositionConfig: View { } .disabled(self.bleManager.connectedPeripheral == nil || node?.positionConfig == nil) - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - let nodeName = node?.user?.longName ?? "unknown".localized - let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) - Button(buttonText) { + SaveConfigButton(node: node, hasChanges: $hasChanges) { + if fixedPosition { + _ = bleManager.sendPosition(channel: 0, destNum: node?.num ?? 0, wantResponse: true) + } + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if fixedPosition { - _ = bleManager.sendPosition(channel: 0, destNum: node?.num ?? 0, wantResponse: true) - } - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - - if connectedNode != nil { - var pc = Config.PositionConfig() - pc.positionBroadcastSmartEnabled = smartPositionEnabled - pc.gpsEnabled = gpsMode == 1 - pc.gpsMode = Config.PositionConfig.GpsMode(rawValue: gpsMode) ?? Config.PositionConfig.GpsMode.notPresent - pc.fixedPosition = fixedPosition - pc.gpsUpdateInterval = UInt32(gpsUpdateInterval) - pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds) - pc.broadcastSmartMinimumIntervalSecs = UInt32(broadcastSmartMinimumIntervalSecs) - pc.broadcastSmartMinimumDistance = UInt32(broadcastSmartMinimumDistance) - pc.rxGpio = UInt32(rxGpio) - pc.txGpio = UInt32(txGpio) - pc.gpsEnGpio = UInt32(gpsEnGpio) - var pf: PositionFlags = [] - if includeAltitude { pf.insert(.Altitude) } - if includeAltitudeMsl { pf.insert(.AltitudeMsl) } - if includeGeoidalSeparation { pf.insert(.GeoidalSeparation) } - if includeDop { pf.insert(.Dop) } - if includeHvdop { pf.insert(.Hvdop) } - if includeSatsinview { pf.insert(.Satsinview) } - if includeSeqNo { pf.insert(.SeqNo) } - if includeTimestamp { pf.insert(.Timestamp) } - if includeSpeed { pf.insert(.Speed) } - if includeHeading { pf.insert(.Heading) } - pc.positionFlags = UInt32(pf.rawValue) - let adminMessageId = bleManager.savePositionConfig(config: pc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) - if adminMessageId > 0 { - // Disable the button after a successful save - hasChanges = false - goBack() - } + if connectedNode != nil { + var pc = Config.PositionConfig() + pc.positionBroadcastSmartEnabled = smartPositionEnabled + pc.gpsEnabled = gpsMode == 1 + pc.gpsMode = Config.PositionConfig.GpsMode(rawValue: gpsMode) ?? Config.PositionConfig.GpsMode.notPresent + pc.fixedPosition = fixedPosition + pc.gpsUpdateInterval = UInt32(gpsUpdateInterval) + pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds) + pc.broadcastSmartMinimumIntervalSecs = UInt32(broadcastSmartMinimumIntervalSecs) + pc.broadcastSmartMinimumDistance = UInt32(broadcastSmartMinimumDistance) + pc.rxGpio = UInt32(rxGpio) + pc.txGpio = UInt32(txGpio) + pc.gpsEnGpio = UInt32(gpsEnGpio) + var pf: PositionFlags = [] + if includeAltitude { pf.insert(.Altitude) } + if includeAltitudeMsl { pf.insert(.AltitudeMsl) } + if includeGeoidalSeparation { pf.insert(.GeoidalSeparation) } + if includeDop { pf.insert(.Dop) } + if includeHvdop { pf.insert(.Hvdop) } + if includeSatsinview { pf.insert(.Satsinview) } + if includeSeqNo { pf.insert(.SeqNo) } + if includeTimestamp { pf.insert(.Timestamp) } + if includeSpeed { pf.insert(.Speed) } + if includeHeading { pf.insert(.Heading) } + pc.positionFlags = UInt32(pf.rawValue) + let adminMessageId = bleManager.savePositionConfig(config: pc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if adminMessageId > 0 { + // Disable the button after a successful save + hasChanges = false + goBack() } } } - message: { - Text("config.save.confirm") - } } .navigationTitle("position.config") .navigationBarItems(trailing: diff --git a/Meshtastic/Views/Settings/Config/SaveConfigButton.swift b/Meshtastic/Views/Settings/Config/SaveConfigButton.swift new file mode 100644 index 00000000..6d200b2d --- /dev/null +++ b/Meshtastic/Views/Settings/Config/SaveConfigButton.swift @@ -0,0 +1,36 @@ +import SwiftUI + +struct SaveConfigButton: View { + @EnvironmentObject var bleManager: BLEManager + + @State private var isPresentingSaveConfirm = false + let node: NodeInfoEntity? + @Binding var hasChanges: Bool + let onConfirmation: () -> Void + + var body: some View { + Button { + isPresentingSaveConfirm = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible + ) { + let nodeName = node?.user?.longName ?? "unknown".localized + let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) + Button(buttonText) { + onConfirmation() + } + } message: { + Text("config.save.confirm") + } + } +} From a87d4fd3a6a36814ed28961afc7f19b2a17f75a3 Mon Sep 17 00:00:00 2001 From: Austin Payne Date: Mon, 19 Feb 2024 21:30:19 -0700 Subject: [PATCH 3/3] feature: add power configuration --- Meshtastic.xcodeproj/project.pbxproj | 12 +- Meshtastic/Enums/IntervalEnums.swift | 2 + Meshtastic/Helpers/BLEManager.swift | 59 ++- Meshtastic/Helpers/MeshPackets.swift | 4 + .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 426 ++++++++++++++++++ Meshtastic/Persistence/UpdateCoreData.swift | 49 ++ .../Views/Settings/Config/PowerConfig.swift | 237 ++++++++++ Meshtastic/Views/Settings/Settings.swift | 10 + en.lproj/Localizable.strings | 11 + 10 files changed, 808 insertions(+), 4 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 27.xcdatamodel/contents create mode 100644 Meshtastic/Views/Settings/Config/PowerConfig.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c03728a0..ded23388 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -18,7 +18,9 @@ D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D42B812B700066FBC8 /* MessageDestination.swift */; }; D93068D72B8146690066FBC8 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D62B8146690066FBC8 /* MessageText.swift */; }; D93068D92B81509C0066FBC8 /* TapbackResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D82B81509C0066FBC8 /* TapbackResponses.swift */; }; + D93068DB2B81C85E0066FBC8 /* PowerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */; }; D93068DD2B81CA820066FBC8 /* ConfigHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */; }; + D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93069072B81DF040066FBC8 /* SaveConfigButton.swift */; }; D9BC22DB2B7DE8E2006A37D5 /* TileDownloadStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */; }; D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */; }; D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */; }; @@ -244,7 +246,10 @@ D93068D42B812B700066FBC8 /* MessageDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDestination.swift; sourceTree = ""; }; D93068D62B8146690066FBC8 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; D93068D82B81509C0066FBC8 /* TapbackResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapbackResponses.swift; sourceTree = ""; }; + D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerConfig.swift; sourceTree = ""; }; D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigHeader.swift; sourceTree = ""; }; + D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 27.xcdatamodel"; sourceTree = ""; }; + D93069072B81DF040066FBC8 /* SaveConfigButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveConfigButton.swift; sourceTree = ""; }; D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileDownloadStatus.swift; sourceTree = ""; }; D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSize.swift; sourceTree = ""; }; D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = ""; }; @@ -639,12 +644,14 @@ isa = PBXGroup; children = ( D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */, + D93069072B81DF040066FBC8 /* SaveConfigButton.swift */, DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */, DD41582528582E9B009B0E59 /* DeviceConfig.swift */, DD8EBF42285058FA00426DCA /* DisplayConfig.swift */, DD2553562855B02500E55709 /* LoRaConfig.swift */, DD2553582855B52700E55709 /* PositionConfig.swift */, DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */, + D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */, DD61937B2863877A00E59241 /* Module */, ); path = Config; @@ -1178,6 +1185,7 @@ DD457188293C7E63000C49FB /* BLESignalStrengthIndicator.swift in Sources */, DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */, DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */, + D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */, DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */, DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, @@ -1257,6 +1265,7 @@ DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */, DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */, 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */, + D93068DB2B81C85E0066FBC8 /* PowerConfig.swift in Sources */, D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */, DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */, DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */, @@ -1849,6 +1858,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */, DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */, DD23D9AB2B7133F6003F5CBE /* MeshtasticDataModelV 25.xcdatamodel */, DDB234392B5CA9B000DA6FB1 /* MeshtasticDataModelV 24.xcdatamodel */, @@ -1876,7 +1886,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */; + currentVersion = D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Enums/IntervalEnums.swift b/Meshtastic/Enums/IntervalEnums.swift index 68bdf882..5b8e2a0f 100644 --- a/Meshtastic/Enums/IntervalEnums.swift +++ b/Meshtastic/Enums/IntervalEnums.swift @@ -165,3 +165,5 @@ enum UpdateIntervals: Int, CaseIterable, Identifiable { } } } + +typealias PowerIntervals = UpdateIntervals diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 5049c74e..d80dbd15 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1513,7 +1513,35 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } - + + public func savePowerConfig(config: Config.PowerConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setConfig.power = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) + meshPacket.channel = UInt32(adminIndex) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() @@ -2067,7 +2095,34 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + + public func requestPowerConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.getConfigRequest = AdminMessage.ConfigType.powerConfig + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index f8cee778..ef5ba3eb 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -51,6 +51,8 @@ func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int6 upsertNetworkConfigPacket(config: config.network, nodeNum: nodeNum, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { upsertPositionConfigPacket(config: config.position, nodeNum: nodeNum, context: context) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) { + upsertPowerConfigPacket(config: config.power, nodeNum: nodeNum, context: context) } } @@ -478,6 +480,8 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { upsertNetworkConfigPacket(config: config.network, nodeNum: Int64(packet.from), context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { upsertPositionConfigPacket(config: config.position, nodeNum: Int64(packet.from), context: context) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) { + upsertPowerConfigPacket(config: config.power, nodeNum: Int64(packet.from), context: context) } } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getModuleConfigResponse(adminMessage.getModuleConfigResponse) { let moduleConfig = adminMessage.getModuleConfigResponse diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 8afd0320..d1c01592 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 26.xcdatamodel + MeshtasticDataModelV 27.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 27.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 27.xcdatamodel/contents new file mode 100644 index 00000000..1987093a --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 27.xcdatamodel/contents @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index b2822b05..b38a38ba 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -645,6 +645,55 @@ func upsertPositionConfigPacket(config: Meshtastic.Config.PositionConfig, nodeNu } } +func upsertPowerConfigPacket(config: Meshtastic.Config.PowerConfig, nodeNum: Int64, context: NSManagedObjectContext) { + let logString = String.localizedStringWithFormat("mesh.log.power.config %@".localized, String(nodeNum)) + MeshLogger.log("πŸ—ΊοΈ \(logString)") + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { + return + } + // Found a node, save Power Config + if !fetchedNode.isEmpty { + if fetchedNode[0].powerConfig == nil { + let newPowerConfig = PowerConfigEntity(context: context) + newPowerConfig.adcMultiplierOverride = config.adcMultiplierOverride + newPowerConfig.deviceBatteryInaAddress = Int32(config.deviceBatteryInaAddress) + newPowerConfig.isPowerSaving = config.isPowerSaving + newPowerConfig.lsSecs = Int32(config.lsSecs) + newPowerConfig.minWakeSecs = Int32(config.minWakeSecs) + newPowerConfig.onBatteryShutdownAfterSecs = Int32(config.onBatteryShutdownAfterSecs) + newPowerConfig.waitBluetoothSecs = Int32(config.waitBluetoothSecs) + fetchedNode[0].powerConfig = newPowerConfig + } else { + fetchedNode[0].powerConfig?.adcMultiplierOverride = config.adcMultiplierOverride + fetchedNode[0].powerConfig?.deviceBatteryInaAddress = Int32(config.deviceBatteryInaAddress) + fetchedNode[0].powerConfig?.isPowerSaving = config.isPowerSaving + fetchedNode[0].powerConfig?.lsSecs = Int32(config.lsSecs) + fetchedNode[0].powerConfig?.minWakeSecs = Int32(config.minWakeSecs) + fetchedNode[0].powerConfig?.onBatteryShutdownAfterSecs = Int32(config.onBatteryShutdownAfterSecs) + fetchedNode[0].powerConfig?.waitBluetoothSecs = Int32(config.waitBluetoothSecs) + } + do { + try context.save() + print("πŸ’Ύ Updated Power Config for node number: \(String(nodeNum))") + } catch { + context.rollback() + let nsError = error as NSError + print("πŸ’₯ Error Updating Core Data PowerConfigEntity: \(nsError)") + } + } else { + print("πŸ’₯ No Nodes found in local database matching node number \(nodeNum) unable to save Power Config") + } + } catch { + let nsError = error as NSError + print("πŸ’₯ Fetching node for core data PowerConfigEntity failed: \(nsError)") + } +} + func upsertAmbientLightingModuleConfigPacket(config: Meshtastic.ModuleConfig.AmbientLightingConfig, nodeNum: Int64, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.ambientlighting.config %@".localized, String(nodeNum)) diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift new file mode 100644 index 00000000..08ca742a --- /dev/null +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -0,0 +1,237 @@ +import SwiftUI + +struct PowerConfig: View { + @Environment(\.managedObjectContext) private var context + @EnvironmentObject private var bleManager: BLEManager + @Environment(\.dismiss) private var goBack + + let node: NodeInfoEntity? + + @State private var isPowerSaving = false + + @State private var shutdownOnPowerLoss = false + @State private var shutdownAfterSecs = 0 + @State private var adcOverride = false + @State private var adcMultiplier: Float = 0.0 + + @State private var waitBluetoothSecs = 60 + @State private var lsSecs = 300 + @State private var minWakeSecs = 10 + + @State private var hasChanges: Bool = false + @FocusState private var isFocused: Bool + + var body: some View { + Form { + ConfigHeader(title: "Power", config: \.powerConfig, node: node, onAppear: setPowerValues) + + Section(header: Text("power")) { + Toggle(isOn: $isPowerSaving) { + Text("power.save") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + + Section { + Toggle(isOn: $shutdownOnPowerLoss) { + Text("power.shutdown.on.power.loss") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + if shutdownOnPowerLoss { + Picker("power.shutdown.after.secs", selection: $shutdownAfterSecs) { + ForEach(PowerIntervals.allCases) { at in + Text(at.description) + } + } + .pickerStyle(DefaultPickerStyle()) + } + } header: { + Text("Shutdown") + } + + Section { + Toggle(isOn: $adcOverride) { + Text("power.adc.override") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + if adcOverride { + HStack { + Text("power.adc.multiplier") + Spacer() + FloatField(title: "power.adc.multiplier", number: $adcMultiplier) { + (2.0 ... 6.0).contains($0) + } + .focused($isFocused) + Spacer() + } + } + } header: { + Text("Battery") + } + + Section { + Picker("power.wait.bluetooth.secs", selection: $waitBluetoothSecs) { + ForEach(PowerIntervals.allCases) { + Text($0.description) + } + } + .pickerStyle(DefaultPickerStyle()) + + Picker("power.ls.secs", selection: $lsSecs) { + ForEach(PowerIntervals.allCases) { + Text($0.description) + } + } + .pickerStyle(DefaultPickerStyle()) + + Picker("power.min.wake.secs", selection: $minWakeSecs) { + ForEach(PowerIntervals.allCases) { + Text($0.description) + } + } + .pickerStyle(DefaultPickerStyle()) + } header: { + Text("Sleep") + } + } + .disabled(self.bleManager.connectedPeripheral == nil || node?.powerConfig == nil) + .navigationTitle("power.config") + .navigationBarItems(trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: "\(bleManager.connectedPeripheral?.shortName ?? "?")" + ) + }) + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Spacer() + Button("dismiss.keyboard") { + isFocused = false + } + .font(.subheadline) + } + } + .onAppear { + if self.bleManager.context == nil { + self.bleManager.context = context + } + setPowerValues() + + // Need to request a Power config from the remote node before allowing changes + if bleManager.connectedPeripheral != nil && node?.powerConfig == nil { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + if node != nil && connectedNode != nil { + _ = bleManager.requestPowerConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + } + } + } + .onChange(of: isPowerSaving) { + if let val = node?.powerConfig?.isPowerSaving { + hasChanges = $0 != val + } + } + .onChange(of: shutdownOnPowerLoss) { _ in + hasChanges = true + } + .onChange(of: shutdownAfterSecs) { + if let val = node?.powerConfig?.onBatteryShutdownAfterSecs { + hasChanges = $0 != val + } + } + .onChange(of: adcOverride) { _ in + hasChanges = true + } + .onChange(of: adcMultiplier) { + if let val = node?.powerConfig?.adcMultiplierOverride { + hasChanges = $0 != val + } + } + .onChange(of: waitBluetoothSecs) { + if let val = node?.powerConfig?.waitBluetoothSecs { + hasChanges = $0 != val + } + } + .onChange(of: lsSecs) { + if let val = node?.powerConfig?.lsSecs { + hasChanges = $0 != val + } + } + .onChange(of: minWakeSecs) { + if let val = node?.powerConfig?.minWakeSecs { + hasChanges = $0 != val + } + } + + SaveConfigButton(node: node, hasChanges: $hasChanges) { + guard let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context), + let fromUser = connectedNode.user, + let toUser = node?.user else { + return + } + + var config = Config.PowerConfig() + config.isPowerSaving = isPowerSaving + config.onBatteryShutdownAfterSecs = shutdownOnPowerLoss ? UInt32(shutdownAfterSecs) : 0 + config.adcMultiplierOverride = adcOverride ? adcMultiplier : 0 + config.waitBluetoothSecs = UInt32(waitBluetoothSecs) + config.lsSecs = UInt32(lsSecs) + config.minWakeSecs = UInt32(minWakeSecs) + + let adminMessageId = bleManager.savePowerConfig( + config: config, + fromUser: fromUser, + toUser: toUser, + 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() + } + } + } + + private func setPowerValues() { + isPowerSaving = node?.powerConfig?.isPowerSaving ?? isPowerSaving + + shutdownAfterSecs = Int(node?.powerConfig?.onBatteryShutdownAfterSecs ?? Int32(shutdownAfterSecs)) + shutdownOnPowerLoss = shutdownAfterSecs != 0 + + adcMultiplier = node?.powerConfig?.adcMultiplierOverride ?? adcMultiplier + adcOverride = adcMultiplier != 0 + + waitBluetoothSecs = Int(node?.powerConfig?.waitBluetoothSecs ?? Int32(waitBluetoothSecs)) + lsSecs = Int(node?.powerConfig?.lsSecs ?? Int32(lsSecs)) + minWakeSecs = Int(node?.powerConfig?.minWakeSecs ?? Int32(minWakeSecs)) + } +} + +/// Helper view for isolating user float input that can be validated before being applied. +private struct FloatField: View { + let title: String + @Binding var number: Float + var isValid: (Float) -> Bool = { _ in true } + + @State private var typingNumber: Float = 0.0 + + var body: some View { + TextField(title.localized, value: $typingNumber, format: .number) + .foregroundColor(.gray) + .multilineTextAlignment(.trailing) + .onChange(of: typingNumber, perform: { _ in + if isValid(typingNumber) { + number = typingNumber + } else { + typingNumber = number + } + }) + .keyboardType(.decimalPad) + .onAppear { + typingNumber = number + } + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index a5bb46f9..4207b430 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -28,6 +28,7 @@ struct Settings: View { case displayConfig case networkConfig case positionConfig + case powerConfig case ambientLightingConfig case cannedMessagesConfig case detectionSensorConfig @@ -223,6 +224,15 @@ struct Settings: View { Text("position") } .tag(SettingsSidebar.positionConfig) + + NavigationLink { + PowerConfig(node: nodes.first(where: { $0.num == selectedNode })) + } label: { + Image(systemName: "bolt.fill") + .symbolRenderingMode(.hierarchical) + Text("power") + } + .tag(SettingsSidebar.powerConfig) } Section("module.configuration") { if #available(iOS 17.0, macOS 14.0, *) { diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 79a0fdd4..05dd2c37 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -185,6 +185,7 @@ "mesh.log.nodeinfo.received %@"="Node info received for: %@"; "mesh.log.position.config %@"="Positon config received: %@"; "mesh.log.position.received %@"="Position Packet received from node: %@"; +"mesh.log.power.config %@"="Power config received: %@"; "mesh.log.rangetest.config %@"="Range Test module config received: %@"; "mesh.log.ringtone.config %@"="RTTTL Ringtone config received: %@"; "mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@"; @@ -231,6 +232,16 @@ "phone.gps.interval.description"="How frequently your phone will send your location to the device, location updates to the mesh are managed by the device."; "position"="Position"; "position.config"="Position Config"; +"power"="Power"; +"power.adc.override"="ADC Override"; +"power.adc.multiplier"="Multiplier"; +"power.config"="Power Config"; +"power.ls.secs"="Light Sleep Interval"; +"power.min.wake.secs"="Minimum Wake Interval"; +"power.save"="Power Save"; +"power.shutdown.on.power.loss"="Shutdown on Power Loss"; +"power.shutdown.after.secs"="After"; +"power.wait.bluetooth.secs"="Bluetooth Off After"; "preferred.radio"="Preferred Radio"; "radio.configuration"="Radio Configuration"; "range.test"="Range Test";