From 10568b9df941e2502b7550f36ae0ddb84c033f18 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 21 Dec 2022 23:18:26 -0800 Subject: [PATCH] Re-enable channel editor, bump version --- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Views/Settings/Channels.swift | 564 +++++++++++------------ Meshtastic/Views/Settings/Settings.swift | 20 +- 3 files changed, 294 insertions(+), 294 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 0d6c3898..c5f1370d 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1000,7 +1000,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.8; + MARKETING_VERSION = 2.0.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1033,7 +1033,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.8; + MARKETING_VERSION = 2.0.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 7f73dc12..427b1dd3 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -4,285 +4,285 @@ // // Copyright(c) Garth Vander Houwen 4/8/22. // -//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 { -// -// @Environment(\.managedObjectContext) var context -// @EnvironmentObject var bleManager: BLEManager -// @Environment(\.dismiss) private var goBack -// @Environment(\.sizeCategory) var sizeCategory -// -// -// var node: NodeInfoEntity? -// -// @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 -// -// var body: some View { -// -// NavigationStack { -// List { -// if node != nil && node?.myInfo != nil { -// ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in -// Button(action: { -// 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 { -// -// Button { -// let key = generateChannelKey(size: 32) -// channelName = "" -// channelIndex = Int32(node!.myInfo!.channels!.array.count) -// channelRole = 2 -// channelKey = key -// uplink = false -// downlink = false -// hasChanges = false -// isPresentingEditView = true -// -// } label: { -// Label("Add Channel", systemImage: "plus.square") -// } -// .buttonStyle(.bordered) -// .buttonBorderShape(.capsule) -// .controlSize(.large) -// .padding() -// .sheet(isPresented: $isPresentingEditView) { -// -// #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) -// .disabled(channelRole == 1 && channelName.count > 0) -// .onChange(of: channelName, perform: { value 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 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) -// } -// HStack (alignment: .top) { -// Text("Key") -// Spacer() -// TextField ( -// "", -// text: $channelKey, -// axis: .vertical -// ) -// .foregroundColor(Color.gray) -// .disabled(true) -// -// } -// .textSelection(.enabled) -// Picker("Channel Role", selection: $channelRole) { -// if channelRole == 1 { -// Text("Primary").tag(1) -// } else{ -// 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)) -// } -// .onSubmit { -// //validate(name: channelName) -// } -// .onChange(of: channelName) { newName in -// hasChanges = true -// } -// .onChange(of: channelKeySize) { newKeySize in -// if channelKeySize == -1 { -// channelKey = "AQ==" -// } else { -// let key = generateChannelKey(size: channelKeySize) -// channelKey = key -// } -// hasChanges = true -// } -// .onChange(of: channelKey) { newKey in -// hasChanges = true -// } -// .onChange(of: channelRole) { newRole in -// hasChanges = true -// } -// .onChange(of: uplink) { newUplink in -// hasChanges = true -// } -// .onChange(of: downlink) { newDownlink in -// hasChanges = true -// } -// HStack { -// Button { -// isPresentingSaveConfirm = true -// } label: { -// Label("save", systemImage: "square.and.arrow.down") -// } -// .disabled(bleManager.connectedPeripheral == nil || !hasChanges) -// .buttonStyle(.bordered) -// .buttonBorderShape(.capsule) -// .controlSize(.large) -// .padding(.bottom) -// .confirmationDialog( -// "are.you.sure", -// isPresented: $isPresentingSaveConfirm, -// titleVisibility: .visible -// ) { -// Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { -// -// var channel = Channel() -// channel.index = channelIndex -// channel.settings.id = UInt32(channelIndex) -// channel.settings.name = channelName -// channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() -// channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary -// channel.settings.uplinkEnabled = uplink -// channel.settings.downlinkEnabled = downlink -// -// let adminMessageId = bleManager.saveChannel(channel: channel, 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 -// channelName = "" -// hasChanges = false -// isPresentingEditView = false -// bleManager.disconnectPeripheral() -// } -// } -// } -// #if targetEnvironment(macCatalyst) -// Button { -// isPresentingEditView = false -// } label: { -// Label("Close", systemImage: "xmark") -// } -// .buttonStyle(.bordered) -// .buttonBorderShape(.capsule) -// .controlSize(.large) -// .padding(.bottom) -// #endif -// } -// .presentationDetents([.medium, .large]) -// } -// } -// } -// .navigationTitle("channels") -// .navigationSplitViewStyle(.automatic) -// .navigationBarItems(trailing: -// ZStack { -// ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") -// }) -// .onAppear { -// bleManager.context = context -// } -// } -//} +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 { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @Environment(\.dismiss) private var goBack + @Environment(\.sizeCategory) var sizeCategory + + + var node: NodeInfoEntity? + + @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 + + var body: some View { + + NavigationStack { + List { + if node != nil && node?.myInfo != nil { + ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in + Button(action: { + 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 { + + Button { + let key = generateChannelKey(size: 32) + channelName = "" + channelIndex = Int32(node!.myInfo!.channels!.array.count) + channelRole = 2 + channelKey = key + uplink = false + downlink = false + hasChanges = false + isPresentingEditView = true + + } label: { + Label("Add Channel", systemImage: "plus.square") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .sheet(isPresented: $isPresentingEditView) { + + #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) + .disabled(channelRole == 1 && channelName.count > 0) + .onChange(of: channelName, perform: { value 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 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) + } + HStack (alignment: .top) { + Text("Key") + Spacer() + TextField ( + "", + text: $channelKey, + axis: .vertical + ) + .foregroundColor(Color.gray) + .disabled(true) + + } + .textSelection(.enabled) + Picker("Channel Role", selection: $channelRole) { + if channelRole == 1 { + Text("Primary").tag(1) + } else{ + 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)) + } + .onSubmit { + //validate(name: channelName) + } + .onChange(of: channelName) { newName in + hasChanges = true + } + .onChange(of: channelKeySize) { newKeySize in + if channelKeySize == -1 { + channelKey = "AQ==" + } else { + let key = generateChannelKey(size: channelKeySize) + channelKey = key + } + hasChanges = true + } + .onChange(of: channelKey) { newKey in + hasChanges = true + } + .onChange(of: channelRole) { newRole in + hasChanges = true + } + .onChange(of: uplink) { newUplink in + hasChanges = true + } + .onChange(of: downlink) { newDownlink in + hasChanges = true + } + HStack { + Button { + isPresentingSaveConfirm = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible + ) { + Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var channel = Channel() + channel.index = channelIndex + channel.settings.id = UInt32(channelIndex) + channel.settings.name = channelName + channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() + channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary + channel.settings.uplinkEnabled = uplink + channel.settings.downlinkEnabled = downlink + + let adminMessageId = bleManager.saveChannel(channel: channel, 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 + channelName = "" + hasChanges = false + isPresentingEditView = false + bleManager.disconnectPeripheral() + } + } + } + #if targetEnvironment(macCatalyst) + Button { + isPresentingEditView = false + } label: { + Label("Close", systemImage: "xmark") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) + #endif + } + .presentationDetents([.medium, .large]) + } + } + } + .navigationTitle("channels") + .navigationSplitViewStyle(.automatic) + .navigationBarItems(trailing: + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + bleManager.context = context + } + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 9bb805f7..1ec48400 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -59,16 +59,16 @@ struct Settings: View { Text("lora") } -// NavigationLink() { -// -// Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) -// } label: { -// -// Image(systemName: "fibrechannel") -// .symbolRenderingMode(.hierarchical) -// -// Text("channels") -// } + NavigationLink() { + + Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) + } label: { + + Image(systemName: "fibrechannel") + .symbolRenderingMode(.hierarchical) + + Text("channels") + } NavigationLink() {