diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 7f433614..82e9e383 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */; }; DD17E5DE277D49D400010EC2 /* storeforward.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17E5DC277D49D400010EC2 /* storeforward.pb.swift */; }; DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; + DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; }; DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; }; DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; }; @@ -121,6 +122,8 @@ DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminMessageList.swift; sourceTree = ""; }; DD17E5DC277D49D400010EC2 /* storeforward.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = storeforward.pb.swift; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; + DD2160AD28C5536B00C17253 /* MeshtasticDataModel v 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 10.xcdatamodel"; sourceTree = ""; }; + DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = ""; }; @@ -260,6 +263,13 @@ path = Custom; sourceTree = ""; }; + DD2160AC28C5019400C17253 /* Messages */ = { + isa = PBXGroup; + children = ( + ); + path = Messages; + sourceTree = ""; + }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( @@ -318,6 +328,7 @@ DD41582928585C32009B0E59 /* RangeTestConfig.swift */, DD6193782863875F00E59241 /* SerialConfig.swift */, DD415827285859C4009B0E59 /* TelemetryConfig.swift */, + DD2160AE28C5552500C17253 /* MQTTConfig.swift */, ); path = Module; sourceTree = ""; @@ -488,6 +499,7 @@ DDC2E18D26CE25CB0042C5E4 /* Helpers */ = { isa = PBXGroup; children = ( + DD2160AC28C5019400C17253 /* Messages */, DD47E3D526F17ED900029299 /* CircleText.swift */, DD47E3D826F3093800029299 /* MessageBubble.swift */, DD90860B26F684AF00DC5189 /* BatteryIcon.swift */, @@ -705,6 +717,7 @@ DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */, DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */, DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */, + DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */, DDAF8C6226ED0A230058C060 /* mqtt.pb.swift in Sources */, DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */, DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */, @@ -936,7 +949,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.39; + MARKETING_VERSION = 1.3.40; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -968,7 +981,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.39; + MARKETING_VERSION = 1.3.40; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1141,6 +1154,7 @@ DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD2160AD28C5536B00C17253 /* MeshtasticDataModel v 10.xcdatamodel */, DD5929A528C0F292003DB21D /* MeshtasticDataModel v 9.xcdatamodel */, DD4033C328B405A60096A444 /* MeshtasticDataModel v 8.xcdatamodel */, DDB6ABD728AE8F5D00384BA1 /* MeshtasticDataModel v 7.xcdatamodel */, @@ -1151,7 +1165,7 @@ DD45C77427BD4EF80011784F /* MeshtasticDataModel v2.xcdatamodel */, DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */, ); - currentVersion = DD5929A528C0F292003DB21D /* MeshtasticDataModel v 9.xcdatamodel */; + currentVersion = DD2160AD28C5536B00C17253 /* MeshtasticDataModel v 10.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 6cd93f9b..dc3b786c 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1418,6 +1418,35 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return 0 } + public func saveMQTTConfig(config: ModuleConfig.MQTTConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setModuleConfig.mqtt = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index c87a2f90..ea185016 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -497,7 +497,6 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont newWiFiConfig.password = config.wifi.psk newWiFiConfig.mode = Int32(config.wifi.mode.rawValue) } - newWiFiConfig.num = fetchedNode[0].num fetchedNode[0].wiFiConfig = newWiFiConfig } else { @@ -596,8 +595,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj newCannedMessageConfig.inputbrokerEventCcw = Int32(config.cannedMessage.inputbrokerEventCcw.rawValue) newCannedMessageConfig.inputbrokerEventPress = Int32(config.cannedMessage.inputbrokerEventPress.rawValue) } - - newCannedMessageConfig.num = nodeNum fetchedNode[0].cannedMessageConfig = newCannedMessageConfig } else { @@ -628,7 +625,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj fetchedNode[0].cannedMessageConfig?.inputbrokerEventCcw = Int32(config.cannedMessage.inputbrokerEventCcw.rawValue) fetchedNode[0].cannedMessageConfig?.inputbrokerEventPress = Int32(config.cannedMessage.inputbrokerEventPress.rawValue) } - fetchedNode[0].cannedMessageConfig?.num = nodeNum } do { @@ -703,8 +699,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj newExternalNotificationConfig.output = Int32(config.externalNotification.output) newExternalNotificationConfig.outputMilliseconds = Int32(config.externalNotification.outputMs) } - - newExternalNotificationConfig.num = nodeNum fetchedNode[0].externalNotificationConfig = newExternalNotificationConfig } else { @@ -727,7 +721,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj fetchedNode[0].externalNotificationConfig?.output = Int32(config.externalNotification.output) fetchedNode[0].externalNotificationConfig?.outputMilliseconds = Int32(config.externalNotification.outputMs) } - fetchedNode[0].externalNotificationConfig?.num = nodeNum } do { @@ -789,7 +782,6 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj newRangeTestConfig.enabled = config.rangeTest.enabled newRangeTestConfig.save = config.rangeTest.save } - newRangeTestConfig.num = nodeNum fetchedNode[0].rangeTestConfig = newRangeTestConfig } else { diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 59abb4ee..8b0ce3b2 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModel v 9.xcdatamodel + MeshtasticDataModel v 10.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 10.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 10.xcdatamodel/contents new file mode 100644 index 00000000..fa053770 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 10.xcdatamodel/contents @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Protobufs/config.pb.swift b/Meshtastic/Protobufs/config.pb.swift index 8ee51d30..a0d820a0 100644 --- a/Meshtastic/Protobufs/config.pb.swift +++ b/Meshtastic/Protobufs/config.pb.swift @@ -732,8 +732,8 @@ struct Config { var region: Config.LoRaConfig.RegionCode = .unset /// - /// Overrides HOPS_RELIABLE and sets the maximum number of hops. This can't be greater than 7. - /// 0 for default of 3 + /// Maximum number of hops. This can't be greater than 7. + /// Default of 3 var hopLimit: UInt32 = 0 /// diff --git a/Meshtastic/Protobufs/module_config.pb.swift b/Meshtastic/Protobufs/module_config.pb.swift index 0d6337a8..44f61043 100644 --- a/Meshtastic/Protobufs/module_config.pb.swift +++ b/Meshtastic/Protobufs/module_config.pb.swift @@ -205,6 +205,10 @@ struct ModuleConfig { /// Decrypted packets may be useful for external systems that want to consume meshtastic packets var encryptionEnabled: Bool = false + /// + /// Whether to send / consume json packets on MQTT + var jsonEnabled: Bool = false + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -850,6 +854,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message 3: .same(proto: "username"), 4: .same(proto: "password"), 5: .standard(proto: "encryption_enabled"), + 6: .standard(proto: "json_enabled"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -863,6 +868,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message case 3: try { try decoder.decodeSingularStringField(value: &self.username) }() case 4: try { try decoder.decodeSingularStringField(value: &self.password) }() case 5: try { try decoder.decodeSingularBoolField(value: &self.encryptionEnabled) }() + case 6: try { try decoder.decodeSingularBoolField(value: &self.jsonEnabled) }() default: break } } @@ -884,6 +890,9 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message if self.encryptionEnabled != false { try visitor.visitSingularBoolField(value: self.encryptionEnabled, fieldNumber: 5) } + if self.jsonEnabled != false { + try visitor.visitSingularBoolField(value: self.jsonEnabled, fieldNumber: 6) + } try unknownFields.traverse(visitor: &visitor) } @@ -893,6 +902,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message if lhs.username != rhs.username {return false} if lhs.password != rhs.password {return false} if lhs.encryptionEnabled != rhs.encryptionEnabled {return false} + if lhs.jsonEnabled != rhs.jsonEnabled {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 21a46f62..21f96bcf 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -20,7 +20,7 @@ struct Connect: View { @State var isPreferredRadio: Bool = false @State var firmwareVersion = "0.0.0" - @State var minimumVersion = "1.3.39" + @State var minimumVersion = "1.3.40" @State var invalidVersion = false var body: some View { diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index c27485a3..76160113 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -23,6 +23,11 @@ struct Contacts: View { var body: some View { NavigationView { + + // Display Contact for Primary Channel + // Display Contacts for DM's on the Primary Channel + // Display Contacts for the rest of the non admin channels + List(users) { (user: UserEntity) in diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 23d53eca..80b9ccbb 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -64,17 +64,21 @@ struct NodeDetail: View { ) } .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.75) + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.6) } } Text("Sats: \(mostRecent.satsInView)").offset( y:-40) } else { - Image(node.user?.hwModel ?? "UNSET") - .resizable() - .aspectRatio(contentMode: .fit) - .cornerRadius(10) - .frame(width: bounds.size.width, height: bounds.size.height / 2) + HStack { + Image(node.user?.hwModel ?? "UNSET") + .resizable() + .aspectRatio(contentMode: .fit) + .cornerRadius(10) + .frame(width: bounds.size.width, height: bounds.size.height / 2.3) + .padding([.top], 40) + } + .offset( y:-40) } ScrollView { @@ -168,6 +172,7 @@ struct NodeDetail: View { Image(hwModelString) .resizable() + .aspectRatio(contentMode: .fit) .frame(width: 90, height: 90) .cornerRadius(5) @@ -429,16 +434,16 @@ struct NodeDetail: View { } } .listStyle(GroupedListStyle()) - .frame(minHeight:170) + .frame(minHeight: 170) .padding(0) } - .offset( y: (node.myInfo!.hasGps ? 0 : -40)) + .offset( y: (node.myInfo?.hasGps ?? false ? 0 : -40)) } .edgesIgnoringSafeArea([.leading, .trailing]) } } .navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown") - .navigationBarTitleDisplayMode(.automatic) + .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 46c4e152..80239cf4 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -123,6 +123,7 @@ struct NodeList: View { } .padding([.leading, .top, .bottom]) } + .isDetailLink(false) } } } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index eba68209..2f7e8908 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -128,7 +128,7 @@ struct CannedMessagesConfig: View { /// Generate input event on Press of this kind. @State var inputbrokerEventPress = 0 - @State var messagesPart1 = "" + @State var messages = "" var body: some View { @@ -160,10 +160,34 @@ struct CannedMessagesConfig: View { .padding(.bottom, 10) } - Section(header: Text("Messages")) { - - TextEditor(text: $messagesPart1) + + HStack { + Label("Messages", systemImage: "message.fill") + TextField("Messages seperate with |", text: $messages) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: messages, perform: { value in + + let totalBytes = messages.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 198 { + + let firstNBytes = Data(messages.utf8.prefix(198)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + messages = maxBytesString + } + } + hasMessagesChanges = true + }) + .foregroundColor(.gray) } + .keyboardType(.default) + Section(header: Text("Control Type")) { @@ -331,7 +355,7 @@ struct CannedMessagesConfig: View { if hasMessagesChanges { - let adminMessageId = bleManager.saveCannedMessageModuleMessages(messages: messagesPart1, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) + let adminMessageId = bleManager.saveCannedMessageModuleMessages(messages: messages, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true @@ -465,13 +489,6 @@ struct CannedMessagesConfig: View { if newKeyPress != node!.cannedMessageConfig!.inputbrokerEventPress { hasChanges = true } } } - .onChange(of: messagesPart1) { newMessagesChanges in - - if node != nil && node!.cannedMessageConfig != nil { - - hasMessagesChanges = true - } - } .navigationViewStyle(StackNavigationViewStyle()) } } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift new file mode 100644 index 00000000..b21624a7 --- /dev/null +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -0,0 +1,233 @@ +// +// MQTT.swift +// Meshtastic +// +// Copyright (c) Garth Vander Houwen 9/4/22. +// +import SwiftUI + +struct MQTTConfig: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + var node: NodeInfoEntity? + + @State private var isPresentingSaveConfirm: Bool = false + @State var initialLoad: Bool = true + @State var hasChanges: Bool = false + + @State var enabled = false + @State var address = "" + @State var username = "" + @State var password = "" + @State var encryptionEnabled = false + @State var jsonEnabled = false + + var body: some View { + + VStack { + + Form { + + Text("WiFi must also be enabled for MQTT to work. You can set uplink and downlink for each channel.") + .font(.title3) + + Section(header: Text("Options")) { + + Toggle(isOn: $enabled) { + + Label("Enabled", systemImage: "dot.radiowaves.right") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Toggle(isOn: $encryptionEnabled) { + + Label("Encryption Enabled", systemImage: "lock.icloud") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Toggle(isOn: $jsonEnabled) { + + Label("JSON Enabled", systemImage: "ellipsis.curlybraces") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + } + Section(header: Text("Custom Server")) { + + HStack { + Label("Address", systemImage: "server.rack") + TextField("Server Address", text: $username) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: address, perform: { value in + + let totalBytes = address.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 30 { + + let firstNBytes = Data(username.utf8.prefix(30)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + address = maxBytesString + } + } + hasChanges = true + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + + HStack { + Label("Username", systemImage: "person.text.rectangle") + TextField("Server Username", text: $username) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: username, perform: { value in + + let totalBytes = username.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 30 { + + let firstNBytes = Data(username.utf8.prefix(30)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + username = maxBytesString + } + } + hasChanges = true + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + + + HStack { + Label("Password", systemImage: "wallet.pass") + TextField("Server Password", text: $password) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: password, perform: { value in + + let totalBytes = password.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 30 { + + let firstNBytes = Data(password.utf8.prefix(30)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + password = maxBytesString + } + } + hasChanges = true + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + } + } + .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) + + 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 + ) { + Button("Save WiFI Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var mqtt = ModuleConfig.MQTTConfig() + mqtt.enabled = self.enabled + mqtt.address = self.address + mqtt.username = self.username + mqtt.password = self.password + mqtt.encryptionEnabled = self.encryptionEnabled + mqtt.jsonEnabled = self.jsonEnabled + + let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: node!.user!, toUser: node!.user!) + + 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 + self.hasChanges = false + + } else { + + } + } + } + } + .navigationTitle("MQTT Config") + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + if self.initialLoad{ + + self.bleManager.context = context + + self.enabled = (node!.mqttConfig?.enabled ?? false) + self.address = node!.mqttConfig?.address ?? "" + self.username = node!.mqttConfig?.username ?? "" + self.password = node!.mqttConfig?.password ?? "" + self.encryptionEnabled = (node!.mqttConfig?.encryptionEnabled ?? false) + self.jsonEnabled = (node!.mqttConfig?.jsonEnabled ?? false) + + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: enabled) { newEnabled in + + if node != nil && node!.wiFiConfig != nil { + + if newEnabled != node!.wiFiConfig!.enabled { hasChanges = true } + } + } + .onChange(of: encryptionEnabled) { newEncryptionEnabled in + + if node != nil && node!.wiFiConfig != nil { + + if newEncryptionEnabled != node!.mqttConfig!.encryptionEnabled { hasChanges = true } + } + } + .onChange(of: jsonEnabled) { newJsonEnabled in + + if node != nil && node!.wiFiConfig != nil { + + if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true } + } + } + .navigationViewStyle(StackNavigationViewStyle()) + } +} diff --git a/Meshtastic/Views/Settings/Config/WiFiConfig.swift b/Meshtastic/Views/Settings/Config/WiFiConfig.swift index 1378be55..b3b48db7 100644 --- a/Meshtastic/Views/Settings/Config/WiFiConfig.swift +++ b/Meshtastic/Views/Settings/Config/WiFiConfig.swift @@ -72,6 +72,7 @@ struct WiFiConfig: View { ssid = maxBytesString } } + hasChanges = true }) .foregroundColor(.gray) } @@ -99,6 +100,7 @@ struct WiFiConfig: View { password = maxBytesString } } + hasChanges = true }) .foregroundColor(.gray) } @@ -176,20 +178,6 @@ struct WiFiConfig: View { if newEnabled != node!.wiFiConfig!.enabled { hasChanges = true } } } - .onChange(of: ssid) { newSsid in - - if node != nil && node!.wiFiConfig != nil { - - if newSsid != node!.wiFiConfig!.ssid { hasChanges = true } - } - } - .onChange(of: password) { newPassword in - - if node != nil && node!.wiFiConfig != nil { - - if newPassword != node!.wiFiConfig!.password { hasChanges = true } - } - } .onChange(of: mode) { newMode in if node != nil && node!.wiFiConfig != nil { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index db8d7702..2aa04051 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -151,6 +151,17 @@ struct Settings: View { } .disabled(bleManager.connectedPeripheral == nil) + NavigationLink { + MQTTConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) + } label: { + + Image(systemName: "dot.radiowaves.right") + .symbolRenderingMode(.hierarchical) + + Text("MQTT (ESP32 Only)") + } + .disabled(bleManager.connectedPeripheral == nil) + NavigationLink { RangeTestConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { diff --git a/Meshtastic/Views/Settings/ShareChannel.swift b/Meshtastic/Views/Settings/ShareChannel.swift index 36a85f2e..1d73e7cf 100644 --- a/Meshtastic/Views/Settings/ShareChannel.swift +++ b/Meshtastic/Views/Settings/ShareChannel.swift @@ -62,16 +62,25 @@ struct ShareChannel: View { .resizable() .scaledToFit() .frame( - minWidth: smallest * 0.9, - maxWidth: smallest * 0.9, - minHeight: smallest * 0.9, - maxHeight: smallest * 0.9, + minWidth: smallest * 0.8, + maxWidth: smallest * 0.8, + minHeight: smallest * 0.8, + maxHeight: smallest * 0.8, alignment: .center ) - Spacer() - Text("Channel Name (Long/Slow)").font(.title) - Text(String(node!.myInfo!.maxChannels)) - Spacer() + + if node?.loRaConfig != nil { + + HStack { + + let preset = ModemPresets(rawValue: Int(node!.loRaConfig!.modemPreset)) + Text("Modem Preset \(preset!.description)").font(.title3) + } + } + HStack { + + Text("Number of Channels: \(node!.myInfo!.maxChannels)").font(.title2) + } } .frame(width: bounds.size.width, height: bounds.size.height) }