Meshtastic-Apple/Meshtastic/Views/Settings/Channels/ChannelForm.swift
Copilot 894e9382d8
Add missing SwiftUI #Preview blocks across 65 views (#1649)
* Add SwiftUI previews for simple helper views

Agent-Logs-Url: https://github.com/meshtastic/Meshtastic-Apple/sessions/a2a43e8c-24fd-443a-8a98-13b678770edd

Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>

* Add previews for action buttons, ChannelForm, MetricsColumnDetail, and DeviceOnboarding

Agent-Logs-Url: https://github.com/meshtastic/Meshtastic-Apple/sessions/a2a43e8c-24fd-443a-8a98-13b678770edd

Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>

* Add previews for config views, log views, AppLog, Firmware, AppData, and UserConfig

Agent-Logs-Url: https://github.com/meshtastic/Meshtastic-Apple/sessions/a2a43e8c-24fd-443a-8a98-13b678770edd

Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>

* Add preview for PositionConfig

Agent-Logs-Url: https://github.com/meshtastic/Meshtastic-Apple/sessions/a2a43e8c-24fd-443a-8a98-13b678770edd

Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>

* Fix formatting bugs in #Preview blocks: restore missing .environmentObject/.environment modifiers and add proper tab indentation

Agent-Logs-Url: https://github.com/meshtastic/Meshtastic-Apple/sessions/7eeb7a54-7928-466f-8e39-b00d0012a09d

Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>

* Linting fixes

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>
Co-authored-by: Garth Vander Houwen <garthvh@yahoo.com>
2026-04-04 18:02:32 -07:00

270 lines
6.8 KiB
Swift

//
// ChannelForm.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 3/17/24.
//
import SwiftUI
import MapKit
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
@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) {
channelName = channelName.replacing(" ", with: "")
var totalBytes = channelName.utf8.count
// Only mess with the value if it is too big
while totalBytes > 11 {
channelName = String(channelName.dropLast())
totalBytes = channelName.utf8.count
}
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("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) {
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 {
if (channelKey != "AQ==" && channelKeySize > 1) && channelRole > 0 {
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 = 15
}
}
}
}
if !preciseLocation {
VStack(alignment: .leading) {
Label("Approximate Location", systemImage: "location.slash.circle.fill")
Slider(value: $positionPrecision, in: 12...15, step: 1) {
} minimumValueLabel: {
Image(systemName: "plus")
} maximumValueLabel: {
Image(systemName: "minus")
}
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))
}
}
.onChange(of: channelName) {
hasChanges = true
}
.onChange(of: channelKeySize) {
if channelKeySize == -1 {
channelKey = "AQ=="
} else {
let key = generateChannelKey(size: channelKeySize)
channelKey = key
}
hasChanges = true
}
.onChange(of: channelKey) {
hasChanges = true
}
.onChange(of: channelKeySize) {
if channelKeySize == -1 {
if channelRole == 0 {
preciseLocation = false
}
channelKey = "AQ=="
}
}
.onChange(of: channelRole) {
hasChanges = true
}
.onChange(of: preciseLocation) { _, loc in
if loc == true {
if channelKey == "AQ==" || channelKeySize <= 1 {
preciseLocation = false
} else {
positionPrecision = 32
}
} else {
positionPrecision = 14
}
hasChanges = true
}
.onChange(of: positionPrecision) {
hasChanges = true
}
.onChange(of: positionsEnabled) { _, pe in
if pe {
if positionPrecision == 0 {
positionPrecision = 15
}
} else {
positionPrecision = 0
}
hasChanges = true
}
.onChange(of: uplink) {
hasChanges = true
}
.onChange(of: downlink) {
hasChanges = true
}
.onFirstAppear {
let tempKey = Data(base64Encoded: channelKey) ?? Data()
if tempKey.count == channelKeySize || channelKeySize == -1 {
hasValidKey = true
} else {
hasValidKey = false
}
}
}
}
}
#Preview {
ChannelForm(
channelIndex: .constant(0),
channelName: .constant("LongFast"),
channelKeySize: .constant(32),
channelKey: .constant("AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE="),
channelRole: .constant(1),
uplink: .constant(false),
downlink: .constant(false),
positionPrecision: .constant(14),
preciseLocation: .constant(false),
positionsEnabled: .constant(true),
hasChanges: .constant(false),
hasValidKey: .constant(true),
supportedVersion: .constant(true)
)
}