From 4288415cf1be7055cc0841ffa4de35309096ffa7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 23 Feb 2024 16:08:01 -0800 Subject: [PATCH] Sliding into channels --- Meshtastic.xcodeproj/project.pbxproj | 4 +- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 427 ++++++++++++++++++ Meshtastic/Views/Settings/Channels.swift | 306 +++++++++---- en.lproj/Localizable.strings | 1 + 5 files changed, 640 insertions(+), 100 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 0a0e9522..56190b9c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -263,6 +263,7 @@ DD0E20F92B87090400F2D100 /* atak.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = atak.pb.swift; sourceTree = ""; }; DD0E20FA2B87090400F2D100 /* clientonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = clientonly.pb.swift; sourceTree = ""; }; DD0E20FB2B87090400F2D100 /* paxcount.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = paxcount.pb.swift; sourceTree = ""; }; + DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 28.xcdatamodel"; sourceTree = ""; }; DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV14.xcdatamodel; sourceTree = ""; }; DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminMessageList.swift; sourceTree = ""; }; DD13AA482AB73BF400BA0C98 /* PositionPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionPopover.swift; sourceTree = ""; }; @@ -1870,6 +1871,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */, D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */, DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */, DD23D9AB2B7133F6003F5CBE /* MeshtasticDataModelV 25.xcdatamodel */, @@ -1898,7 +1900,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */; + currentVersion = DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index d1c01592..37d79244 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 27.xcdatamodel + MeshtasticDataModelV 28.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents new file mode 100644 index 00000000..cb1c1d4e --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 4d5b47df..5c8aa7e3 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -7,6 +7,7 @@ import SwiftUI import CoreData +import MapKit #if canImport(TipKit) import TipKit #endif @@ -39,6 +40,10 @@ 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 var body: some View { @@ -102,111 +107,166 @@ struct Channels: View { .padding() #endif Form { - 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") + Section(header: Text("channel details")) { + HStack { + Text("name") Spacer() - Picker("Channel Role", selection: $channelRole) { + 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) } - .pickerStyle(.segmented) } } - Toggle("Uplink Enabled", isOn: $uplink) + + Section(header: Text("position")) { + VStack(alignment: .leading) { + Toggle(isOn: $positionsEnabled) { + Label("Positions Enabled", systemImage: positionsEnabled ? "mappin" : "mappin.slash") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + + if positionsEnabled { + VStack(alignment: .leading) { + Toggle(isOn: $preciseLocation) { + Label("Precise Location", systemImage: "location.circle") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .listRowSeparator(.visible) + .onChange(of: preciseLocation) { pl in + if pl == false { + positionPrecision = 16 + } + } + } + + if !preciseLocation { + VStack(alignment: .leading) { + Label("Position Precision", systemImage: "scope") + 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)) - Toggle("Downlink Enabled", isOn: $downlink) + .listRowSeparator(.visible) + + Toggle(isOn: $downlink) { + Label("Downlink Enabled", systemImage: "arrowshape.down") + } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .listRowSeparator(.visible) + } } .onAppear { let tempKey = Data(base64Encoded: channelKey) ?? Data() @@ -325,7 +385,7 @@ struct Channels: View { .padding(.bottom) #endif } - .presentationDetents([.fraction(0.45), .fraction(0.55), .fraction(0.65)]) + .presentationDetents([.fraction(0.85), .large]) .presentationDragIndicator(.visible) } if node?.myInfo?.channels?.array.count ?? 0 < 8 && node != nil { @@ -376,3 +436,53 @@ func firstMissingChannelIndex(_ indexes: [Int]) -> Int { } return indexes.count + 1 } + + +enum PositionPrecision: Int, CaseIterable, Identifiable { + + case eleven = 11 + case twelve = 12 + case thirteen = 13 + case fourteen = 14 + case fifteen = 15 + case sixteen = 16 + + var id: Int { self.rawValue } + + var precisionMeters: Double { + switch self { + + case .eleven: + return 11672.736900000944 + case .twelve: + return 5836.362884000802 + case .thirteen: + return 2918.1758760007315 + case .fourteen: + return 1459.0823719999053 + case .fifteen: + return 729.5356200010741 + case .sixteen: + return 364.7622440000765 + } + } + + var description: String { + let distanceFormatter = MKDistanceFormatter() + switch self { + + case .eleven: + return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters))) + case .twelve: + return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters))) + case .thirteen: + return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters))) + case .fourteen: + return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters))) + case .fifteen: + return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters))) + case .sixteen: + return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters))) + } + } +} diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index a90d8797..fed9f0e3 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -233,6 +233,7 @@ "phone.gps.interval.description"="How frequently your phone will send your location to the device, location updates to the mesh are managed by the device."; "position"="Position"; "position.config"="Position Config"; +"position.precision %@"="Within %@"; "power"="Power"; "power.adc.override"="ADC Override"; "power.adc.multiplier"="Multiplier";