Meshtastic-Apple/Meshtastic/Views/Settings/Channels.swift

291 lines
8.5 KiB
Swift
Raw Normal View History

2022-12-17 19:14:53 -08:00
//
2022-12-29 16:27:06 -08:00
// ShareChannel.swift
// MeshtasticApple
2022-12-17 19:14:53 -08:00
//
2022-12-29 16:27:06 -08:00
// Copyright(c) Garth Vander Houwen 4/8/22.
2022-12-17 19:14:53 -08:00
//
2022-12-29 16:27:06 -08:00
import SwiftUI
import CoreData
func generateChannelKey(size: Int) -> String {
var keyData = Data(count: size)
_ = keyData.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, size, $0.baseAddress!)
}
return keyData.base64EncodedString()
}
struct Channels: View {
2023-03-06 10:33:18 -08:00
2022-12-29 16:27:06 -08:00
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
@Environment(\.sizeCategory) var sizeCategory
var node: NodeInfoEntity?
2023-03-06 10:33:18 -08:00
2022-12-29 16:27:06 -08:00
@State var hasChanges = false
@State private var isPresentingEditView = false
@State private var isPresentingSaveConfirm: Bool = false
@State private var channelIndex: Int32 = 0
@State private var channelName = ""
@State private var channelKeySize = 32
@State private var channelKey = "AQ=="
@State private var channelRole = 0
@State private var uplink = false
@State private var downlink = false
2023-03-06 10:33:18 -08:00
2022-12-29 16:27:06 -08:00
var body: some View {
2023-03-06 10:33:18 -08:00
2022-12-29 16:27:06 -08:00
NavigationStack {
List {
if node != nil && node?.myInfo != nil {
2023-03-14 13:01:35 -07:00
ForEach(node?.myInfo?.channels?.array as? [ChannelEntity] ?? [], id: \.self) { (channel: ChannelEntity) in
2023-03-06 10:33:18 -08:00
Button(action: {
2022-12-29 16:27:06 -08:00
channelIndex = channel.index
channelRole = Int(channel.role)
channelKey = channel.psk?.base64EncodedString() ?? ""
if channelKey.count == 0 {
channelKeySize = 0
} else if channelKey == "AQ==" {
channelKeySize = -1
} else if channelKey.count == 24 {
channelKeySize = 16
} else if channelKey.count == 32 {
channelKeySize = 24
} else if channelKey.count == 44 {
channelKeySize = 32
}
channelName = channel.name ?? ""
uplink = channel.uplinkEnabled
downlink = channel.downlinkEnabled
isPresentingEditView = true
hasChanges = false
}) {
VStack(alignment: .leading) {
HStack {
CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 36, brightness: 0.1)
.padding(.trailing, 5)
VStack {
HStack {
if channel.name?.isEmpty ?? false {
if channel.role == 1 {
Text(String("PrimaryChannel").camelCaseToWords()).font(.headline)
} else {
Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline)
}
} else {
Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline)
}
}
}
}
}
}
}
}
}
if node?.myInfo?.channels?.array.count ?? 0 < 8 && node != nil {
2023-03-06 10:33:18 -08:00
2022-12-29 16:27:06 -08:00
Button {
let key = generateChannelKey(size: 32)
channelName = ""
2023-01-03 23:24:35 -08:00
channelIndex = Int32(node!.myInfo!.channels!.array.count)
2022-12-29 16:27:06 -08:00
channelRole = 2
channelKey = key
uplink = false
downlink = false
hasChanges = false
isPresentingEditView = true
2023-03-06 10:33:18 -08:00
2022-12-29 16:27:06 -08:00
} label: {
Label("Add Channel", systemImage: "plus.square")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
.sheet(isPresented: $isPresentingEditView) {
2023-03-06 10:33:18 -08:00
2022-12-29 16:27:06 -08:00
#if targetEnvironment(macCatalyst)
Text("channel")
.font(.largeTitle)
.padding()
#endif
Form {
HStack {
Text("name")
Spacer()
TextField(
"Channel Name",
text: $channelName
)
.disableAutocorrection(true)
.keyboardType(.alphabet)
.foregroundColor(Color.gray)
2023-03-06 10:33:18 -08:00
.onChange(of: channelName, perform: { _ in
2022-12-29 16:27:06 -08:00
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 bit").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)
}
2023-03-06 10:33:18 -08:00
HStack(alignment: .top) {
2022-12-29 16:27:06 -08:00
Text("Key")
Spacer()
2023-03-06 10:33:18 -08:00
TextField(
2022-12-29 16:27:06 -08:00
"",
text: $channelKey,
axis: .vertical
)
.foregroundColor(Color.gray)
.disabled(true)
2023-03-06 10:33:18 -08:00
2022-12-29 16:27:06 -08:00
}
.textSelection(.enabled)
Picker("Channel Role", selection: $channelRole) {
if channelRole == 1 {
Text("Primary").tag(1)
2023-03-06 10:33:18 -08:00
} else {
2022-12-29 16:27:06 -08:00
Text("Disabled").tag(0)
Text("Secondary").tag(2)
}
}
.pickerStyle(DefaultPickerStyle())
.disabled(channelRole == 1)
Toggle("Uplink Enabled", isOn: $uplink)
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle("Downlink Enabled", isOn: $downlink)
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
2023-03-06 10:33:18 -08:00
// .onSubmit {
// validate(name: channelName)
// }
.onChange(of: channelName) { _ in
2022-12-29 16:27:06 -08:00
hasChanges = true
}
2023-03-06 10:33:18 -08:00
.onChange(of: channelKeySize) { _ in
2022-12-29 16:27:06 -08:00
if channelKeySize == -1 {
channelKey = "AQ=="
} else {
let key = generateChannelKey(size: channelKeySize)
channelKey = key
}
hasChanges = true
}
2023-03-06 10:33:18 -08:00
.onChange(of: channelKey) { _ in
2022-12-29 16:27:06 -08:00
hasChanges = true
}
2023-03-06 10:33:18 -08:00
.onChange(of: channelRole) { _ in
2022-12-29 16:27:06 -08:00
hasChanges = true
}
2023-03-06 10:33:18 -08:00
.onChange(of: uplink) { _ in
2022-12-29 16:27:06 -08:00
hasChanges = true
}
2023-03-06 10:33:18 -08:00
.onChange(of: downlink) { _ in
2022-12-29 16:27:06 -08:00
hasChanges = true
}
HStack {
Button {
var channel = Channel()
channel.index = channelIndex
channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary
if channel.role != Channel.Role.disabled {
channel.settings.id = UInt32(channelIndex)
channel.settings.name = channelName
channel.settings.psk = Data(base64Encoded: channelKey) ?? Data()
channel.settings.uplinkEnabled = uplink
channel.settings.downlinkEnabled = downlink
} else {
if channelIndex <= node!.myInfo!.channels?.count ?? 0 {
2023-03-06 15:30:10 -08:00
guard let channelEntity = node!.myInfo!.channels?[Int(channelIndex)] as? ChannelEntity else {
return
}
context.delete(channelEntity)
do {
try context.save()
print("💾 Deleted Channel: \(channel.settings.name)")
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)")
}
}
}
2023-03-06 10:33:18 -08:00
let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
2023-03-06 10:33:18 -08:00
if adminMessageId > 0 {
self.isPresentingEditView = false
channelName = ""
hasChanges = false
2023-03-06 13:26:04 -08:00
_ = bleManager.getChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
}
2022-12-29 16:27:06 -08:00
} label: {
Label("save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding(.bottom)
#if targetEnvironment(macCatalyst)
Button {
isPresentingEditView = false
} label: {
2022-12-29 16:36:43 -08:00
Label("close", systemImage: "xmark")
2022-12-29 16:27:06 -08:00
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding(.bottom)
#endif
}
.presentationDetents([.medium, .large])
}
}
}
.navigationTitle("channels")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
})
.onAppear {
bleManager.context = context
}
}
}