From 8d2b7050684cc0d184ec13579fe547123c8fcc81 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 17 Mar 2024 10:08:03 -0700 Subject: [PATCH] Split channel form out into its own view --- Meshtastic.xcodeproj/project.pbxproj | 28 +- Meshtastic/Views/Settings/Channels.swift | 234 +--------------- .../Views/Settings/Channels/ChannelForm.swift | 254 ++++++++++++++++++ 3 files changed, 276 insertions(+), 240 deletions(-) create mode 100644 Meshtastic/Views/Settings/Channels/ChannelForm.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6ae7e268..9a07d126 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -111,6 +111,7 @@ DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; }; DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */; }; + DD93800E2BA74D0C008BEC06 /* ChannelForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */; }; DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */; }; DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */; }; DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */; }; @@ -362,6 +363,7 @@ DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = ""; }; DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapContent.swift; sourceTree = ""; }; + DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelForm.swift; sourceTree = ""; }; DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSettingsForm.swift; sourceTree = ""; }; DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = ""; }; DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormMapKit.swift; sourceTree = ""; }; @@ -560,21 +562,6 @@ path = CoreData; sourceTree = ""; }; - DD2100802B0E676E00F2F116 /* Routes */ = { - isa = PBXGroup; - children = ( - DD2100832B0E67AD00F2F116 /* RouteMap */, - ); - path = Routes; - sourceTree = ""; - }; - DD2100832B0E67AD00F2F116 /* RouteMap */ = { - isa = PBXGroup; - children = ( - ); - path = RouteMap; - sourceTree = ""; - }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( @@ -604,7 +591,7 @@ DD4A911C2708C57100501B7E /* Settings */ = { isa = PBXGroup; children = ( - DD2100802B0E676E00F2F116 /* Routes */, + DD93800C2BA74CE3008BEC06 /* Channels */, DD97E96728EFE9A00056DDA4 /* About.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, @@ -747,6 +734,14 @@ name = Frameworks; sourceTree = ""; }; + DD93800C2BA74CE3008BEC06 /* Channels */ = { + isa = PBXGroup; + children = ( + DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */, + ); + path = Channels; + sourceTree = ""; + }; DDAD49EB2AFAE82500B4425D /* Map */ = { isa = PBXGroup; children = ( @@ -1229,6 +1224,7 @@ DDB75A0F2A05920E006ED576 /* FileManager.swift in Sources */, DD1933782B084F4200771CD5 /* Measurement.swift in Sources */, DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */, + DD93800E2BA74D0C008BEC06 /* ChannelForm.swift in Sources */, DD41A61529AB0035003C5A37 /* NodeWeatherForecast.swift in Sources */, DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, DD4640202AFF10F4002A5ECB /* WaypointForm.swift in Sources */, diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index db9c9a03..a7cfebf0 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -40,18 +40,15 @@ struct Channels: View { @State private var channelRole = 0 @State private var uplink = false @State private var downlink = false - @State private var positionPrecision = 32.0 @State private var preciseLocation = true @State private var positionsEnabled = true + @State private var supportedVersion = true /// Minimum Version for granular position configuration @State var minimumVersion = "2.2.24" - var body: some View { - - let supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame VStack { List { @@ -80,7 +77,10 @@ struct Channels: View { channelName = channel.name ?? "" uplink = channel.uplinkEnabled downlink = channel.downlinkEnabled - hasChanges = false + + print("Position Precision \(channel.positionPrecision)") + //self.positionPrecision = State(initialValue: Double(self.channel.positionPrecision)) + positionPrecision = Double(channel.positionPrecision) if !supportedVersion && channelRole == 1 { positionPrecision = 32 preciseLocation = true @@ -91,21 +91,22 @@ struct Channels: View { preciseLocation = false positionsEnabled = false } else { - positionPrecision = Double(channel.positionPrecision) if positionPrecision == 32 { preciseLocation = true positionsEnabled = true } else { preciseLocation = false } - if positionPrecision == 0 { positionsEnabled = false } else { positionsEnabled = true } } + hasChanges = false isPresentingEditView = true + + }) { VStack(alignment: .leading) { HStack { @@ -138,224 +139,9 @@ struct Channels: View { .font(.largeTitle) .padding() #endif - Form { - Section(header: Text("channel details")) { - HStack { - Text("name") - Spacer() - TextField( - "Channel Name", - text: $channelName - ) - .disableAutocorrection(true) - .keyboardType(.alphabet) - .foregroundColor(Color.gray) - .onChange(of: channelName, perform: { _ in - channelName = channelName.replacing(" ", with: "") - let totalBytes = channelName.utf8.count - // Only mess with the value if it is too big - if totalBytes > 11 { - let firstNBytes = Data(channelName.utf8.prefix(11)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the channelName back to the last place where it was the right size - channelName = maxBytesString - } - } - hasChanges = true - }) - } - HStack { - Picker("Key Size", selection: $channelKeySize) { - Text("Empty").tag(0) - Text("Default").tag(-1) - Text("1 byte").tag(1) - Text("128 bit").tag(16) - Text("192 bit").tag(24) - Text("256 bit").tag(32) - } - .pickerStyle(DefaultPickerStyle()) - Spacer() - Button { - if channelKeySize == -1 { - channelKey = "AQ==" - } else { - let key = generateChannelKey(size: channelKeySize) - channelKey = key - } - } label: { - Image(systemName: "lock.rotation") - .font(.title) - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.small) - } - HStack(alignment: .center) { - Text("Key") - Spacer() - TextField( - "Key", - text: $channelKey, - axis: .vertical - ) - .padding(6) - .disableAutocorrection(true) - .keyboardType(.alphabet) - .foregroundColor(Color.gray) - .textSelection(.enabled) - .background( - RoundedRectangle(cornerRadius: 10.0) - .stroke( - hasValidKey ? - Color.clear : - Color.red - , lineWidth: 2.0) - - ) - .onChange(of: channelKey, perform: { _ in - let tempKey = Data(base64Encoded: channelKey) ?? Data() - if tempKey.count == channelKeySize || channelKeySize == -1{ - hasValidKey = true - } - else { - hasValidKey = false - } - hasChanges = true - }) - .disabled(channelKeySize <= 0) - } - HStack { - if channelRole == 1 { - Picker("Channel Role", selection: $channelRole) { - Text("Primary").tag(1) - } - .pickerStyle(.automatic) - .disabled(true) - } else { - Text("Channel Role") - Spacer() - Picker("Channel Role", selection: $channelRole) { - Text("Disabled").tag(0) - Text("Secondary").tag(2) - } - .pickerStyle(.segmented) - } - } - } - - Section(header: Text("position")) { - - VStack(alignment: .leading) { - Toggle(isOn: $positionsEnabled) { - Label(channelRole == 1 ? "Positions Enabled" : "Allow Position Requests", systemImage: positionsEnabled ? "mappin" : "mappin.slash") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .disabled(!supportedVersion) - } - - if positionsEnabled { - VStack(alignment: .leading) { - Toggle(isOn: $preciseLocation) { - Label("Precise Location", systemImage: "scope") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .disabled(!supportedVersion) - .listRowSeparator(.visible) - .onChange(of: preciseLocation) { pl in - if pl == false { - positionPrecision = 13 - } - } - } - - if !preciseLocation { - VStack(alignment: .leading) { - Label("Approximate Location", systemImage: "location.slash.circle.fill") - Slider( - value: $positionPrecision, - in: 11...16, - step: 1 - ) - { - } minimumValueLabel: { - Image(systemName: "minus") - } maximumValueLabel: { - Image(systemName: "plus") - } - Text(PositionPrecision(rawValue: Int(positionPrecision))?.description ?? "") - .foregroundColor(.gray) - .font(.callout) - } - } - } - } - Section(header: Text("mqtt")) { - Toggle(isOn: $uplink) { - Label("Uplink Enabled", systemImage: "arrowshape.up") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .listRowSeparator(.visible) - - Toggle(isOn: $downlink) { - Label("Downlink Enabled", systemImage: "arrowshape.down") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .listRowSeparator(.visible) - } - } + ChannelForm(channelIndex: $channelIndex, channelName: $channelName, channelKeySize: $channelKeySize, channelKey: $channelKey, channelRole: $channelRole, uplink: $uplink, downlink: $downlink, positionPrecision: $positionPrecision, preciseLocation: $preciseLocation, positionsEnabled: $positionsEnabled, hasChanges: $hasChanges, hasValidKey: $hasValidKey, supportedVersion: $supportedVersion) .onAppear { - let tempKey = Data(base64Encoded: channelKey) ?? Data() - if tempKey.count == channelKeySize || channelKeySize == -1 { - hasValidKey = true - } - else { - hasValidKey = false - } - } - .onChange(of: channelName) { _ in - hasChanges = true - } - .onChange(of: channelKeySize) { _ in - if channelKeySize == -1 { - channelKey = "AQ==" - } else { - let key = generateChannelKey(size: channelKeySize) - channelKey = key - } - hasChanges = true - } - .onChange(of: channelKey) { _ in - hasChanges = true - } - .onChange(of: channelRole) { _ in - hasChanges = true - } - .onChange(of: preciseLocation) { loc in - if loc { - positionPrecision = 32 - } else { - positionPrecision = 14 - } - hasChanges = true - } - .onChange(of: positionPrecision) { _ in - hasChanges = true - } - .onChange(of: positionsEnabled) { pe in - if pe { - if positionPrecision == 0 { - positionPrecision = 32 - } - } else { - positionPrecision = 0 - } - hasChanges = true - } - .onChange(of: uplink) { _ in - hasChanges = true - } - .onChange(of: downlink) { _ in - hasChanges = true + supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame } HStack { Button { diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift new file mode 100644 index 00000000..c191d345 --- /dev/null +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -0,0 +1,254 @@ +// +// ChannelForm.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 3/17/24. +// + + +import SwiftUI +#if canImport(MapKit) +import MapKit +#endif + +struct ChannelForm: View { + + @Binding var channelIndex: Int32 + @Binding var channelName: String + @Binding var channelKeySize: Int + @Binding var channelKey: String + @Binding var channelRole: Int + @Binding var uplink: Bool + @Binding var downlink: Bool + @Binding var positionPrecision: Double + @Binding var preciseLocation: Bool + @Binding var positionsEnabled: Bool + + @Binding var hasChanges: Bool + @Binding var hasValidKey: Bool + + /// Minimum Version for granular position configuration + @Binding var supportedVersion: Bool + + var body: some View { + + NavigationStack { + Form { + Section(header: Text("channel details")) { + HStack { + Text("name") + Spacer() + TextField( + "Channel Name", + text: $channelName + ) + .disableAutocorrection(true) + .keyboardType(.alphabet) + .foregroundColor(Color.gray) + .onChange(of: channelName, perform: { _ in + channelName = channelName.replacing(" ", with: "") + let totalBytes = channelName.utf8.count + // Only mess with the value if it is too big + if totalBytes > 11 { + let firstNBytes = Data(channelName.utf8.prefix(11)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the channelName back to the last place where it was the right size + channelName = maxBytesString + } + } + hasChanges = true + }) + } + HStack { + Picker("Key Size", selection: $channelKeySize) { + Text("Empty").tag(0) + Text("Default").tag(-1) + Text("1 byte").tag(1) + Text("128 bit").tag(16) + Text("192 bit").tag(24) + Text("256 bit").tag(32) + } + .pickerStyle(DefaultPickerStyle()) + Spacer() + Button { + if channelKeySize == -1 { + channelKey = "AQ==" + } else { + let key = generateChannelKey(size: channelKeySize) + channelKey = key + } + } label: { + Image(systemName: "lock.rotation") + .font(.title) + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.small) + } + HStack(alignment: .center) { + Text("Key") + Spacer() + TextField( + "Key", + text: $channelKey, + axis: .vertical + ) + .padding(6) + .disableAutocorrection(true) + .keyboardType(.alphabet) + .foregroundColor(Color.gray) + .textSelection(.enabled) + .background( + RoundedRectangle(cornerRadius: 10.0) + .stroke( + hasValidKey ? + Color.clear : + Color.red + , lineWidth: 2.0) + + ) + .onChange(of: channelKey, perform: { _ in + let tempKey = Data(base64Encoded: channelKey) ?? Data() + if tempKey.count == channelKeySize || channelKeySize == -1{ + hasValidKey = true + } + else { + hasValidKey = false + } + hasChanges = true + }) + .disabled(channelKeySize <= 0) + } + HStack { + if channelRole == 1 { + Picker("Channel Role", selection: $channelRole) { + Text("Primary").tag(1) + } + .pickerStyle(.automatic) + .disabled(true) + } else { + Text("Channel Role") + Spacer() + Picker("Channel Role", selection: $channelRole) { + Text("Disabled").tag(0) + Text("Secondary").tag(2) + } + .pickerStyle(.segmented) + } + } + } + + Section(header: Text("position")) { + + VStack(alignment: .leading) { + Toggle(isOn: $positionsEnabled) { + Label(channelRole == 1 ? "Positions Enabled" : "Allow Position Requests", systemImage: positionsEnabled ? "mappin" : "mappin.slash") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .disabled(!supportedVersion) + } + + if positionsEnabled { + VStack(alignment: .leading) { + Toggle(isOn: $preciseLocation) { + Label("Precise Location", systemImage: "scope") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .disabled(!supportedVersion) + .listRowSeparator(.visible) + .onChange(of: preciseLocation) { pl in + if pl == false { + positionPrecision = 13 + } + } + } + + if !preciseLocation { + VStack(alignment: .leading) { + Label("Approximate Location", systemImage: "location.slash.circle.fill") + Slider(value: $positionPrecision, in: 11...16, step: 1) { + } minimumValueLabel: { + Image(systemName: "minus") + } maximumValueLabel: { + Image(systemName: "plus") + } + Text(PositionPrecision(rawValue: Int(positionPrecision))?.description ?? "") + .foregroundColor(.gray) + .font(.callout) + } + } + } + } + Section(header: Text("mqtt")) { + Toggle(isOn: $uplink) { + Label("Uplink Enabled", systemImage: "arrowshape.up") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .listRowSeparator(.visible) + + Toggle(isOn: $downlink) { + Label("Downlink Enabled", systemImage: "arrowshape.down") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .listRowSeparator(.visible) + } + } + .onChange(of: channelName) { _ in + hasChanges = true + } + .onChange(of: channelKeySize) { _ in + if channelKeySize == -1 { + channelKey = "AQ==" + } else { + let key = generateChannelKey(size: channelKeySize) + channelKey = key + } + hasChanges = true + } + .onChange(of: channelKey) { _ in + hasChanges = true + } + .onChange(of: channelRole) { _ in + hasChanges = true + } + .onChange(of: preciseLocation) { loc in + if loc == true { + positionPrecision = 32 + } else { + positionPrecision = 14 + } + hasChanges = true + } + .onChange(of: positionPrecision) { _ in + hasChanges = true + } + .onChange(of: positionsEnabled) { pe in + if pe { + if positionPrecision == 0 { + positionPrecision = 32 + } + } else { + positionPrecision = 0 + } + hasChanges = true + } + .onChange(of: uplink) { _ in + hasChanges = true + } + .onChange(of: downlink) { _ in + hasChanges = true + } + .onAppear { + let tempKey = Data(base64Encoded: channelKey) ?? Data() + if tempKey.count == channelKeySize || channelKeySize == -1 { + hasValidKey = true + } + else { + hasValidKey = false + } + } + } + .presentationDetents([.fraction(0.45), .fraction(0.65)]) + .presentationDragIndicator(.visible) + } +}