diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 514705a8..3a822d45 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -63,12 +63,13 @@ DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */; }; DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; }; DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; }; - DDC2E16F26CE248F0042C5E4 /* MeshtasticAppleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E16E26CE248F0042C5E4 /* MeshtasticAppleTests.swift */; }; - DDC2E17A26CE248F0042C5E4 /* MeshtasticAppleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E17926CE248F0042C5E4 /* MeshtasticAppleUITests.swift */; }; + DDC2E16F26CE248F0042C5E4 /* MeshtasticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E16E26CE248F0042C5E4 /* MeshtasticTests.swift */; }; + DDC2E17A26CE248F0042C5E4 /* MeshtasticUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E17926CE248F0042C5E4 /* MeshtasticUITests.swift */; }; DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */; }; DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; }; DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; }; DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; }; + DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */; }; DDCFF601285453A7005FA625 /* localonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCFF600285453A7005FA625 /* localonly.pb.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; }; @@ -163,6 +164,7 @@ DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = ""; }; DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = ""; }; DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; + DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = ""; }; DDCFF600285453A7005FA625 /* localonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = localonly.pb.swift; sourceTree = ""; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = ""; }; @@ -242,6 +244,7 @@ DD8169FE272476C700F4AB02 /* LogDocument.swift */, DD6B85A728009258000ACD6B /* ShareChannel.swift */, DD61937A2863876A00E59241 /* Config */, + DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, ); path = Settings; sourceTree = ""; @@ -648,6 +651,7 @@ DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */, DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, + DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */, DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */, DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */, @@ -668,7 +672,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DDC2E16F26CE248F0042C5E4 /* MeshtasticAppleTests.swift in Sources */, + DDC2E16F26CE248F0042C5E4 /* MeshtasticTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 176514b4..bf283cf8 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -825,6 +825,41 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } + public func saveUser(config: User, destNum: Int64, wantResponse: Bool) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.setOwner = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var deviceConfig = Config.DeviceConfig() diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 06562980..17c1d11e 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -13,8 +13,6 @@ struct Settings: View { @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings - - @FetchRequest( sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], animation: .default) @@ -52,6 +50,29 @@ struct Settings: View { .listRowSeparator(.visible) .fixedSize(horizontal: false, vertical: true) + NavigationLink { + UserConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + } label: { + + Image(systemName: "person.crop.rectangle.fill") + .symbolRenderingMode(.hierarchical) + + Text("User") + } + .disabled(bleManager.connectedPeripheral == nil) + + NavigationLink() { + + LoRaConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + } label: { + + Image(systemName: "dot.radiowaves.left.and.right") + .symbolRenderingMode(.hierarchical) + + Text("LoRa") + } + .disabled(bleManager.connectedPeripheral == nil) + NavigationLink { DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { @@ -71,18 +92,6 @@ struct Settings: View { Text("Display (Device Screen)") } .disabled(bleManager.connectedPeripheral == nil) - - NavigationLink() { - - LoRaConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) - } label: { - - Image(systemName: "dot.radiowaves.left.and.right") - .symbolRenderingMode(.hierarchical) - - Text("LoRa") - } - .disabled(bleManager.connectedPeripheral == nil) NavigationLink { PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift new file mode 100644 index 00000000..a580da78 --- /dev/null +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -0,0 +1,163 @@ +// +// User.swift +// Meshtastic Apple +// +// Copyright (c) Garth Vander Houwen 6/27/22. +// +import SwiftUI + +struct UserConfig: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + var node: NodeInfoEntity + + @State private var isPresentingFactoryResetConfirm: Bool = false + @State private var isPresentingSaveConfirm: Bool = false + @State var initialLoad: Bool = true + @State var hasChanges = false + + @State var shortName = "" + @State var longName = "" + + var body: some View { + + VStack { + + Form { + + Section(header: Text("USER DETAILS")) { + + HStack { + Label("Long Name", systemImage: "person.crop.rectangle.fill") + TextField("Long Name", text: $longName) + .onChange(of: longName, perform: { value in + + let totalBytes = longName.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 40 { + + let firstNBytes = Data(longName.utf8.prefix(40)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the longName back to the last place where it was the right size + longName = maxBytesString + } + } + }) + .onChange(of: shortName, perform: { value in + + let totalBytes = shortName.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 5 { + + let firstNBytes = Data(shortName.utf8.prefix(5)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + shortName = maxBytesString + } + } + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + .disableAutocorrection(true) + .listRowSeparator(.visible) + + HStack { + Label("Short Name", systemImage: "circlebadge.fill") + TextField("Long Name", text: $shortName) + .foregroundColor(.gray) + } + .keyboardType(.default) + .disableAutocorrection(true) + .listRowSeparator(.visible) + + } + + } + .disabled(bleManager.connectedPeripheral == nil) + + HStack { + + 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 User Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var u = User() + u.shortName = shortName + u.longName = longName + + if bleManager.saveUser(config: u, destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + + } else { + + } + } + } + } + Spacer() + } + + .navigationTitle("User 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.shortName = node.user!.shortName ?? "" + self.longName = node.user!.longName ?? "" + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: shortName) { newShort in + + if newShort != node.user!.shortName { + + hasChanges = true + } + } + .onChange(of: longName) { newLong in + + if newLong != node.user!.longName { + + hasChanges = true + } + } + .navigationViewStyle(StackNavigationViewStyle()) + } +}