diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c7601ad9..ded23388 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -18,6 +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 */; }; @@ -243,6 +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 = ""; }; @@ -636,12 +643,15 @@ DD61937A2863876A00E59241 /* Config */ = { 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; @@ -1175,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 */, @@ -1254,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 */, @@ -1268,6 +1280,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 */, @@ -1845,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 */, @@ -1872,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/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 81533001..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 @@ -26,31 +25,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") @@ -94,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/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..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 @@ -32,30 +30,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 @@ -189,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 c2e2523f..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 @@ -29,33 +27,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 @@ -138,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 e1875294..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 @@ -52,34 +51,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) + ConfigHeader(title: "LoRa", config: \.loRaConfig, node: node, onAppear: setLoRaValues) - } 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) - } Section(header: Text("Options")) { Picker("Region", selection: $region ) { @@ -200,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 0a449682..136d4237 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") @@ -80,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 02fb0539..5ece017e 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) { @@ -190,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 64688edc..97f34179 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) { @@ -191,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 cf218762..83c278ea 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") @@ -170,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 f319f894..b898b840 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) { @@ -214,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 ac80a840..3574acb4 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) { @@ -72,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 0d43b4f5..81ad17d0 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") @@ -74,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 8d27dddf..7a0bf363 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) { @@ -124,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 4edc42b0..490ec9c3 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) { @@ -127,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 9f153808..d71c4e62 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 @@ -86,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 96ac19b4..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 = "" @@ -26,34 +25,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) { @@ -119,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 c5bdd94b..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 @@ -74,34 +73,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")) { @@ -275,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/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/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") + } + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 46ba5549..c05335cd 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 @@ -222,6 +223,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 5dca3b5c..f6c422fe 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -186,6 +186,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: %@"; @@ -232,6 +233,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";