From e7a5aa40f0ecd7c2d1e7a9e4f8b7dcb9ef6a58c6 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Nov 2023 13:20:55 -0800 Subject: [PATCH 01/16] Group box for Position Altitude Chart on the node map Save Prefered node id so messages show up when not connected via ble --- Meshtastic/Extensions/UserDefaults.swift | 9 +++++++++ Meshtastic/Helpers/BLEManager.swift | 1 + Meshtastic/Views/Messages/ChannelMessageList.swift | 3 ++- Meshtastic/Views/Messages/Messages.swift | 2 +- .../Views/Nodes/Helpers/Map/PositionAltitudeChart.swift | 4 ++-- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index 07c230ad..1c9e21fd 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -12,6 +12,7 @@ extension UserDefaults { case enableRangeTest case meshtasticUsername case preferredPeripheralId + case preferredPeripheralNum case provideLocation case provideLocationInterval case mapLayer @@ -53,6 +54,14 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "preferredPeripheralId") } } + static var preferredPeripheralNum: Int { + get { + UserDefaults.standard.integer(forKey: "preferredPeripheralNum") + } + set { + UserDefaults.standard.set(newValue, forKey: "preferredPeripheralNum") + } + } static var provideLocation: Bool { get { UserDefaults.standard.bool(forKey: "provideLocation") diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index aa88a530..07ef857a 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -486,6 +486,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, context: context!) if myInfo != nil { + UserDefaults.preferredPeripheralNum = Int(myInfo!.myNodeNum) connectedPeripheral.num = myInfo!.myNodeNum connectedPeripheral.name = myInfo?.bleName ?? "unknown".localized connectedPeripheral.longName = myInfo?.bleName ?? "unknown".localized diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 49281d65..aa1b1a20 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -30,6 +30,7 @@ struct ChannelMessageList: View { @State private var deleteMessageId: Int64 = 0 @State private var replyMessageId: Int64 = 0 @State private var sendPositionWithMessage: Bool = false + @AppStorage("preferredPeripheralNum") private var preferredPeripheralNum = -1 var body: some View { VStack { @@ -39,7 +40,7 @@ struct ChannelMessageList: View { ScrollView { LazyVStack { ForEach( channel.allPrivateMessages ) { (message: MessageEntity) in - let currentUser: Bool = (bleManager.connectedPeripheral?.num ?? -1 == message.fromUser?.num ? true : false) + let currentUser: Bool = (Int64(preferredPeripheralNum) == message.fromUser?.num ? true : false) if message.replyID > 0 { let messageReply = channel.allPrivateMessages.first(where: { $0.messageId == message.replyID }) HStack { diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 6c261dfd..879a361c 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -72,7 +72,7 @@ struct Messages: View { } if UserDefaults.preferredPeripheralId.count > 0 { let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(bleManager.connectedPeripheral?.num ?? -1)) + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(UserDefaults.preferredPeripheralNum)) do { guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { return diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift index de10f6a5..eba287e7 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift @@ -25,7 +25,8 @@ struct PositionAltitudeChart: View { var body: some View { let nodePositions = Array(node.positions!) as! [PositionEntity] let data = nodePositions.map { PositionAltitude(time: $0.time ?? Date(), altitude: Measurement(value: Double($0.altitude), unit: .meters) ) } - HStack { + GroupBox(label: Label("Altitude", systemImage: "mountain.2")) { + Chart(data, id: \.time) { LineMark( x: .value("Time", $0.time), @@ -56,7 +57,6 @@ struct PositionAltitudeChart: View { } .chartXAxis(.visible) } - .padding() .background(Color(UIColor.secondarySystemBackground)) .opacity(/*@START_MENU_TOKEN@*/0.8/*@END_MENU_TOKEN@*/) } From e120598f5ff99f6b85b9135ee3f3eea1ddcc2e13 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Nov 2023 13:40:20 -0800 Subject: [PATCH 02/16] Bump version --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- Meshtastic/Views/Settings/Settings.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 9581b975..742bf295 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1435,7 +1435,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.12; + MARKETING_VERSION = 2.2.13; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1469,7 +1469,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.12; + MARKETING_VERSION = 2.2.13; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1591,7 +1591,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.12; + MARKETING_VERSION = 2.2.13; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1624,7 +1624,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.12; + MARKETING_VERSION = 2.2.13; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index a4972a90..b693ce20 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -283,7 +283,7 @@ struct Settings: View { if self.bleManager.context == nil { self.bleManager.context = context } - self.connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) + self.connectedNodeNum = UserDefaults.preferredPeripheralNum// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) selectedNode = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) } .listStyle(GroupedListStyle()) From 99fbc6c9a67f3f4e873f1f0b8347cbfb4698d07b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Nov 2023 16:08:46 -0800 Subject: [PATCH 03/16] Remove 30 day filter from mesh map --- Meshtastic/Views/Nodes/MeshMap.swift | 3 ++- .../Views/Settings/Config/DeviceConfig.swift | 7 ++++-- Meshtastic/Views/Settings/Settings.swift | 24 +++++++++---------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 520fbbab..3e090f35 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -44,8 +44,9 @@ struct MeshMap: View { var delay: Double = 0 @State private var scale: CGFloat = 0.5 + /// "time >= %@ && nodePosition != nil && latest == true" @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], - predicate: NSPredicate(format: "time >= %@ && nodePosition != nil && latest == true", Calendar.current.date(byAdding: .day, value: -30, to: Date())! as NSDate), animation: .none) + predicate: NSPredicate(format: "nodePosition != nil && latest == true", Calendar.current.date(byAdding: .day, value: -30, to: Date())! as NSDate), animation: .none) private var positions: FetchedResults @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index f7d97eb5..ddd596a8 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -143,8 +143,11 @@ struct DeviceConfig: View { ) { Button("Erase all device and app data?", role: .destructive) { if bleManager.sendNodeDBReset(fromUser: node!.user!, toUser: node!.user!) { - bleManager.disconnectPeripheral() - clearCoreDataDatabase(context: context) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + bleManager.disconnectPeripheral() + clearCoreDataDatabase(context: context) + } + } else { print("NodeDB Reset Failed") } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index b693ce20..9bc1792c 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -13,7 +13,7 @@ struct Settings: View { @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default) private var nodes: FetchedResults @State private var selectedNode: Int = 0 - @State private var connectedNodeNum: Int = 0 + @State private var preferredNodeNum: Int = 0 @State private var selection: SettingsSidebar = .about enum SettingsSidebar { case appSettings @@ -57,7 +57,7 @@ struct Settings: View { Text("app.settings") } .tag(SettingsSidebar.appSettings) - let node = nodes.first(where: { $0.num == connectedNodeNum }) + let node = nodes.first(where: { $0.num == preferredNodeNum }) let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 ? true : false if !(node?.deviceConfig?.isManaged ?? false) { Section("Configure") { @@ -84,8 +84,8 @@ struct Settings: View { .onChange(of: selectedNode) { newValue in if selectedNode > 0 { let node = nodes.first(where: { $0.num == newValue }) - let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) - connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) + let connectedNode = nodes.first(where: { $0.num == preferredNodeNum }) + preferredNodeNum = Int(connectedNode?.num ?? 0)// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil { let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) if adminMessageId > 0 { @@ -100,14 +100,14 @@ struct Settings: View { } Section("radio.configuration") { NavigationLink { - ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum })) + ShareChannels(node: nodes.first(where: { $0.num == preferredNodeNum })) } label: { Image(systemName: "qrcode") .symbolRenderingMode(.hierarchical) Text("share.channels") } .tag(SettingsSidebar.shareChannels) - .disabled(selectedNode > 0 && selectedNode != connectedNodeNum) + .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) NavigationLink { UserConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -125,14 +125,14 @@ struct Settings: View { } .tag(SettingsSidebar.loraConfig) NavigationLink { - Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) + Channels(node: nodes.first(where: { $0.num == preferredNodeNum })) } label: { Image(systemName: "fibrechannel") .symbolRenderingMode(.hierarchical) Text("channels") } .tag(SettingsSidebar.channelConfig) - .disabled(selectedNode > 0 && selectedNode != connectedNodeNum) + .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) NavigationLink { BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -257,7 +257,7 @@ struct Settings: View { } .tag(SettingsSidebar.meshLog) NavigationLink { - let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) + let connectedNode = nodes.first(where: { $0.num == preferredNodeNum }) AdminMessageList(user: connectedNode?.user) } label: { Image(systemName: "building.columns") @@ -268,14 +268,14 @@ struct Settings: View { } Section(header: Text("Firmware")) { NavigationLink { - Firmware(node: nodes.first(where: { $0.num == connectedNodeNum })) + Firmware(node: nodes.first(where: { $0.num == preferredNodeNum })) } label: { Image(systemName: "arrow.up.arrow.down.square") .symbolRenderingMode(.hierarchical) Text("Firmware Updates") } .tag(SettingsSidebar.about) - .disabled(selectedNode > 0 && selectedNode != connectedNodeNum) + .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) } } } @@ -283,7 +283,7 @@ struct Settings: View { if self.bleManager.context == nil { self.bleManager.context = context } - self.connectedNodeNum = UserDefaults.preferredPeripheralNum// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) + self.preferredNodeNum = UserDefaults.preferredPeripheralNum// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) selectedNode = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) } .listStyle(GroupedListStyle()) From 415661d47722953f125b710fd9cd4383f0c00b29 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Nov 2023 18:15:14 -0800 Subject: [PATCH 04/16] Make favorite icon yellow Add node list icon row --- Meshtastic/Views/Messages/UserList.swift | 2 +- .../Views/Messages/UserMessageList.swift | 2 +- .../Nodes/Helpers/Map/NodeMapSwiftUI.swift | 2 ++ .../Nodes/Helpers/Map/PositionPopover.swift | 4 ++-- .../Views/Nodes/Helpers/NodeDetail.swift | 2 +- .../Views/Nodes/Helpers/NodeListItem.swift | 24 +++++++++++++++++-- 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 43cc4b72..d35f6592 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -67,7 +67,7 @@ struct UserList: View { Spacer() if user.vip { Image(systemName: "star.fill") - .foregroundColor(.secondary) + .foregroundColor(.yellow) } if user.messageList.count > 0 { if lastMessageDay == currentDay { diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index c2458d71..39207c6b 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -38,7 +38,7 @@ struct UserMessageList: View { LazyVStack { ForEach( user.messageList ) { (message: MessageEntity) in if user.num != bleManager.connectedPeripheral?.num ?? -1 { - let currentUser: Bool = (bleManager.connectedPeripheral?.num ?? 0 == message.fromUser?.num ?? -1 ? true : false) + let currentUser: Bool = (Int64(UserDefaults.preferredPeripheralNum) == message.fromUser?.num ?? -1 ? true : false) if message.replyID > 0 { let messageReply = user.messageList.first(where: { $0.messageId == message.replyID }) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index 0e07c8fd..95d32fe7 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -225,6 +225,8 @@ struct NodeMapSwiftUI: View { } } .onChange(of: node) { + isLookingAround = false + isShowingAltitude = false mostRecent = node.positions?.lastObject as? PositionEntity if node.positions?.count ?? 0 > 1 { position = .automatic diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index 461da979..995f89e0 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -129,7 +129,7 @@ struct PositionPopover: View { if position.nodePosition != nil { if position.nodePosition?.user?.vip ?? false { Image(systemName: "star.fill") - .foregroundColor(.accentColor) + .foregroundColor(.yellow) .symbolRenderingMode(.hierarchical) .font(.largeTitle) .padding(.bottom, 5) @@ -137,7 +137,7 @@ struct PositionPopover: View { if position.nodePosition?.hasEnvironmentMetrics ?? false { Image(systemName: "cloud.sun.rain") .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) + .symbolRenderingMode(.multicolor) .font(.largeTitle) .padding(.bottom) } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 58096955..b644f2b6 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -72,7 +72,7 @@ struct NodeDetail: View { NavigationLink { EnvironmentMetricsLog(node: node) } label: { - Image(systemName: "chart.xyaxis.line") + Image(systemName: "cloud.sun.rain") .symbolRenderingMode(.hierarchical) .font(.title) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 8ba12835..8f4ae4e2 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -23,7 +23,6 @@ struct NodeListItem: View { VStack(alignment: .leading) { CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65) .padding(.trailing, 5) - BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor) } VStack(alignment: .leading) { HStack { @@ -33,7 +32,7 @@ struct NodeListItem: View { if node.user?.vip ?? false { Spacer() Image(systemName: "star.fill") - .foregroundColor(.secondary) + .foregroundColor(.yellow) } } if connected { @@ -83,6 +82,27 @@ struct NodeListItem: View { LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) } } + HStack { + BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor) + + if node.hasPositions { + Image(systemName: "mappin.and.ellipse") + .symbolRenderingMode(.hierarchical) + .font(.callout) + + } + if node.hasEnvironmentMetrics { + Image(systemName: "cloud.sun.rain") + .symbolRenderingMode(.hierarchical) + .font(.callout) + } + if node.hasDetectionSensorMetrics { + Image(systemName: "sensor") + .symbolRenderingMode(.hierarchical) + .font(.callout) + } + } + .padding(.top, 3) } .frame(maxWidth: .infinity, alignment: .leading) } From c8a8c5a617a4b00b803f8ae1f2756b9a1c6064a5 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Nov 2023 19:20:54 -0800 Subject: [PATCH 05/16] Add delay for factory reset --- Meshtastic/Views/Settings/Config/DeviceConfig.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index ddd596a8..682581f4 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -168,8 +168,10 @@ struct DeviceConfig: View { ) { Button("Factory reset your device and app? ", role: .destructive) { if bleManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!) { - bleManager.disconnectPeripheral() - clearCoreDataDatabase(context: context) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + bleManager.disconnectPeripheral() + clearCoreDataDatabase(context: context) + } } else { print("Factory Reset Failed") } From 27880fa990dc8a1a44fd85c7261276903e1f79ce Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Nov 2023 08:22:33 -0800 Subject: [PATCH 06/16] is online nimation for postion popup --- .../Nodes/Helpers/Map/PositionPopover.swift | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index 995f89e0..d92d4b35 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -15,11 +15,32 @@ struct PositionPopover: View { var position: PositionEntity var popover: Bool = true let distanceFormatter = MKDistanceFormatter() + var delay: Double = 0 + @State private var scale: CGFloat = 0.5 var body: some View { + // Node Color from node.num + let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0)) VStack { HStack { - CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(position.nodePosition?.user?.num ?? 0))), circleSize: 65) - .padding(.trailing, 5) + ZStack { + + if position.nodePosition?.isOnline ?? false { + Circle() + .fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5))) + .foregroundStyle(Color(nodeColor.lighter()).opacity(0.3)) + .scaleEffect(scale) + .animation( + Animation.easeInOut(duration: 0.6) + .repeatForever().delay(delay), value: scale + ) + .onAppear { + self.scale = 1 + } + .frame(width: 90, height: 90) + } + CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 65) + } + Text(position.nodePosition?.user?.longName ?? "Unknown") .font(.largeTitle) } From f6cb21d6d82fdaed555f358947b74e5e11aa3723 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Nov 2023 22:39:42 -0800 Subject: [PATCH 07/16] Routes --- Meshtastic.xcodeproj/project.pbxproj | 12 +- .../CoreData/LocationEntityExtension.swift | 42 ++ .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 4 +- .../contents | 376 ++++++++++++++++++ .../Nodes/Helpers/Map/MapSettingsForm.swift | 2 + .../Nodes/Helpers/Map/NodeMapSwiftUI.swift | 22 +- .../Nodes/Helpers/Map/WaypointForm.swift | 10 +- Meshtastic/Views/Settings/Routes.swift | 126 ++++++ Meshtastic/Views/Settings/Settings.swift | 12 + 10 files changed, 587 insertions(+), 21 deletions(-) create mode 100644 Meshtastic/Extensions/CoreData/LocationEntityExtension.swift create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents create mode 100644 Meshtastic/Views/Settings/Routes.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 742bf295..3da89de8 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -100,6 +100,8 @@ DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0B6B1294CDC55001356EC /* Channels.swift */; }; DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; }; DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; }; + DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580C2B0DAA9E00147258 /* Routes.swift */; }; + DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */; }; DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */; }; DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5226EB1DF10058C060 /* BLEManager.swift */; }; DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */; }; @@ -314,6 +316,9 @@ DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = ""; }; + DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV20.xcdatamodel; sourceTree = ""; }; + DDAB580C2B0DAA9E00147258 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routes.swift; sourceTree = ""; }; + DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEntityExtension.swift; sourceTree = ""; }; DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshMap.swift; sourceTree = ""; }; DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = ""; }; DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothConfig.swift; sourceTree = ""; }; @@ -472,6 +477,7 @@ DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */, DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */, DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */, + DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */, ); path = CoreData; sourceTree = ""; @@ -506,6 +512,7 @@ DD97E96728EFE9A00056DDA4 /* About.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, + DDAB580C2B0DAA9E00147258 /* Routes.swift */, DDA0B6B1294CDC55001356EC /* Channels.swift */, DDD6EEAE29BC024700383354 /* Firmware.swift */, DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, @@ -1208,6 +1215,7 @@ DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */, DD5E523A298EFA5300D21B61 /* TelemetryWeather.swift in Sources */, + DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */, C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, DD58C5F22919AD3C00D5BEFB /* ChannelEntityExtension.swift in Sources */, DDDB444E29F8AB0E00EE2349 /* Int.swift in Sources */, @@ -1223,6 +1231,7 @@ DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */, DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */, + DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */, DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */, DD5E5204298EE33B00D21B61 /* xmodem.pb.swift in Sources */, DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */, @@ -1735,6 +1744,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */, DD2CC2E52ABFE04E00EDFDA7 /* MeshtasticDataModelV19.xcdatamodel */, DDDB26492AAD743E003AFCB7 /* MeshtasticDataModelV18.xcdatamodel */, DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */, @@ -1755,7 +1765,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD2CC2E52ABFE04E00EDFDA7 /* MeshtasticDataModelV19.xcdatamodel */; + currentVersion = DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift b/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift new file mode 100644 index 00000000..310fe626 --- /dev/null +++ b/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift @@ -0,0 +1,42 @@ +// +// LocationEntityExtension.swift +// Meshtastic +// +// Copyright (c) Garth Vander Houwen 11/21/23. +// + +import CoreData +import CoreLocation +import MapKit +import SwiftUI + +extension LocationEntity { + + var latitude: Double? { + + let d = Double(latitudeI) + if d == 0 { + return 0 + } + return d / 1e7 + } + + var longitude: Double? { + + let d = Double(longitudeI) + if d == 0 { + return 0 + } + return d / 1e7 + } + + var locationCoordinate: CLLocationCoordinate2D? { + if latitudeI != 0 && longitudeI != 0 { + let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!) + return coord + } else { + return nil + } + } +} + diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 074f016d..fbcba258 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV19.xcdatamodel + MeshtasticDataModelV20.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV19.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV19.xcdatamodel/contents index 0dc699d5..08699813 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV19.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV19.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -290,7 +290,7 @@ - + diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents new file mode 100644 index 00000000..ec6e67db --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents @@ -0,0 +1,376 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift index fdf4cc4a..c9c351ba 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift @@ -80,6 +80,7 @@ struct MapSettingsForm: View { } } } + #if targetEnvironment(macCatalyst) Spacer() Button { @@ -95,5 +96,6 @@ Spacer() } .presentationDetents([.fraction(0.45), .fraction(0.65)]) .presentationDragIndicator(.visible) + } } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index 95d32fe7..703ef890 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -61,18 +61,16 @@ struct NodeMapSwiftUI: View { let nodeColor = UIColor(hex: UInt32(node.num)) /// Route Lines if showRouteLines { - if showRouteLines { - let gradient = LinearGradient( - colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)], - startPoint: .leading, endPoint: .trailing - ) - let dashed = StrokeStyle( - lineWidth: 3, - lineCap: .round, lineJoin: .round, dash: [10, 10] - ) - MapPolyline(coordinates: lineCoords) - .stroke(gradient, style: dashed) - } + let gradient = LinearGradient( + colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)], + startPoint: .leading, endPoint: .trailing + ) + let dashed = StrokeStyle( + lineWidth: 3, + lineCap: .round, lineJoin: .round, dash: [10, 10] + ) + MapPolyline(coordinates: lineCoords) + .stroke(gradient, style: dashed) } /// Convex Hull if showConvexHull { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index f68ed469..36bf1555 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -11,7 +11,7 @@ import MapKit import CoreLocation struct WaypointForm: View { - + @EnvironmentObject var bleManager: BLEManager @Environment(\.dismiss) private var dismiss @State var waypoint: WaypointEntity @@ -27,7 +27,7 @@ struct WaypointForm: View { @State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours @State private var locked: Bool = false @State private var lockedTo: Int64 = 0 - + var body: some View { VStack { if editMode { @@ -341,7 +341,7 @@ struct WaypointForm: View { } } .padding(.top) - #if targetEnvironment(macCatalyst) +#if targetEnvironment(macCatalyst) Spacer() Button { dismiss() @@ -352,7 +352,7 @@ struct WaypointForm: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding() - #endif +#endif } } } @@ -381,7 +381,7 @@ struct WaypointForm: View { expires = false expire = Date.now.addingTimeInterval(60 * 480) icon = "📍" - latitude = waypoint.coordinate.latitude + latitude = waypoint.coordinate.latitude longitude = waypoint.coordinate.longitude } } diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift new file mode 100644 index 00000000..43dfa543 --- /dev/null +++ b/Meshtastic/Views/Settings/Routes.swift @@ -0,0 +1,126 @@ +// +// Routes.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 11/21/23. +// + +import SwiftUI +import CoreData +import MapKit + +@available(iOS 17.0, macOS 14.0, *) +struct Routes: View { + + @State private var columnVisibility = NavigationSplitViewVisibility.doubleColumn + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @State private var selectedRoute: RouteEntity? + @State private var importing = false + + @FetchRequest(sortDescriptors: [], animation: .default) + + var routes: FetchedResults + var body: some View { + NavigationSplitView(columnVisibility: $columnVisibility) { + Button("Import Route") { + importing = true + } + .fileImporter( + isPresented: $importing, + allowedContentTypes: [.commaSeparatedText], + allowsMultipleSelection: false + ) { result in + do { + guard let selectedFile: URL = try result.get().first else { return } + + guard selectedFile.startAccessingSecurityScopedResource() else { // Notice this line right here + return + } + + do { + guard let fileContent = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return } + let routeName = selectedFile.lastPathComponent.dropLast(4) + let lines = fileContent.components(separatedBy: "\n") + let headers = lines.first?.components(separatedBy: ",") + var latIndex = -1 + var longIndex = -1 + for index in headers!.indices { + print("\(index): \( headers![index])") + if headers![index].trimmingCharacters(in: .whitespaces) == "Latitude" { + latIndex = index + } else if headers![index].trimmingCharacters(in: .whitespaces) == "Longitude" { + longIndex = index + } + } + if latIndex >= 0 && longIndex >= 0 { + let newRoute = RouteEntity(context: context) + newRoute.name = ("\(String(routeName)) - \(Date().formatted())") + newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max) + newRoute.color = 12 + newRoute.date = Date() + var newLocations = [LocationEntity]() + lines.dropFirst().forEach { line in + let data = line.components(separatedBy: ",") + if data.count > 1 { + let latitude = latIndex >= 0 ? data[latIndex].trimmingCharacters(in: .whitespaces) : "0" + let longitude = longIndex >= 0 ? data[longIndex].trimmingCharacters(in: .whitespaces) : "0" + let loc = LocationEntity(context: context) + loc.latitudeI = Int32((Double(latitude) ?? 0) * 1e7) + loc.longitudeI = Int32((Double(longitude) ?? 0) * 1e7) + newLocations.append(loc) + print("Longitude: \(longitude) Latitude: \(latitude)") + } + } + newRoute.locations? = NSOrderedSet(array: newLocations) + do { + try context.save() + } catch let error as NSError { + print("Error: \(error.localizedDescription)") + } + } + + } catch { + print("error: \(error)") // to do deal with errors + } + + } catch { + print("CSV Import Error") + } + } + + VStack { + List(routes, id: \.self, selection: $selectedRoute) { route in + Text(route.name ?? "No Name Route") + .font(.title) + } + .listStyle(.plain) + } + .navigationTitle("Route List") + } detail: { + VStack { + if selectedRoute != nil { + let locationArray = selectedRoute?.locations?.array as? [LocationEntity] ?? [] + let lineCoords = locationArray.compactMap({(location) -> CLLocationCoordinate2D in + return location.locationCoordinate ?? LocationHelper.DefaultLocation + }) + + Map () { + + let gradient = LinearGradient( + colors: [.cyan, .blue, .secondary],//[Color(nodeColor.lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)], + startPoint: .leading, endPoint: .trailing + ) + let dashed = StrokeStyle( + lineWidth: 3, + lineCap: .round, lineJoin: .round, dash: [10, 10] + ) + MapPolyline(coordinates: lineCoords) + .stroke(gradient, style: dashed) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + }.navigationTitle(" \(selectedRoute?.name ?? "Unknown Route") Map") + } + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 9bc1792c..1694105e 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -17,6 +17,7 @@ struct Settings: View { @State private var selection: SettingsSidebar = .about enum SettingsSidebar { case appSettings + case routes case shareChannels case userConfig case loraConfig @@ -57,6 +58,17 @@ struct Settings: View { Text("app.settings") } .tag(SettingsSidebar.appSettings) + if #available(iOS 17.0, macOS 14.0, *) { + NavigationLink { + Routes() + } label: { + Image(systemName: "gearshape") + .symbolRenderingMode(.hierarchical) + Text("routes") + } + .tag(SettingsSidebar.routes) + } + let node = nodes.first(where: { $0.num == preferredNodeNum }) let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 ? true : false if !(node?.deviceConfig?.isManaged ?? false) { From 926ea1ea673e43a680c2ace4496968e29bb86aff Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Nov 2023 23:26:35 -0800 Subject: [PATCH 08/16] Route start and finish --- .../Nodes/Helpers/Map/NodeMapSwiftUI.swift | 22 +++++------ Meshtastic/Views/Settings/Routes.swift | 38 ++++++++++++------- Meshtastic/Views/Settings/Settings.swift | 2 +- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index 703ef890..48ff4ba1 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -123,7 +123,7 @@ struct NodeMapSwiftUI: View { .opacity(0.8) .presentationCompactAdaptation(.popover) } - + } else { Image(systemName: "flipphone") .symbolEffect(.pulse.byLayer) @@ -140,7 +140,7 @@ struct NodeMapSwiftUI: View { .opacity(0.8) .presentationCompactAdaptation(.popover) } - + } } else { if showNodeHistory { @@ -153,7 +153,7 @@ struct NodeMapSwiftUI: View { .clipShape(Circle()) .rotationEffect(headingDegrees) .frame(width: 16, height: 16) - + } else { Circle() .fill(Color(UIColor(hex: UInt32(node.num)))) @@ -282,8 +282,8 @@ struct NodeMapSwiftUI: View { showWaypoints = !showWaypoints } }) { - Image(systemName: showWaypoints ? "signpost.right.and.left.fill" : "signpost.right.and.left") - .padding(.vertical, 5) + Image(systemName: showWaypoints ? "signpost.right.and.left.fill" : "signpost.right.and.left") + .padding(.vertical, 5) } .tint(Color(UIColor.secondarySystemBackground)) .foregroundColor(.accentColor) @@ -319,13 +319,13 @@ struct NodeMapSwiftUI: View { .foregroundColor(.accentColor) .buttonStyle(.borderedProminent) } - #if targetEnvironment(macCatalyst) +#if targetEnvironment(macCatalyst) /// Hide non fuctional catalyst controls -// MapZoomStepper(scope: mapScope) -// .mapControlVisibility(.visible) -// MapPitchSlider(scope: mapScope) -// .mapControlVisibility(.visible) - #endif + // MapZoomStepper(scope: mapScope) + // .mapControlVisibility(.visible) + // MapPitchSlider(scope: mapScope) + // .mapControlVisibility(.visible) +#endif } .controlSize(.regular) .padding(5) diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 43dfa543..2d64f42f 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -33,9 +33,8 @@ struct Routes: View { ) { result in do { guard let selectedFile: URL = try result.get().first else { return } - - guard selectedFile.startAccessingSecurityScopedResource() else { // Notice this line right here - return + guard selectedFile.startAccessingSecurityScopedResource() else { + return } do { @@ -79,7 +78,7 @@ struct Routes: View { print("Error: \(error.localizedDescription)") } } - + } catch { print("error: \(error)") // to do deal with errors } @@ -97,7 +96,7 @@ struct Routes: View { .listStyle(.plain) } .navigationTitle("Route List") - } detail: { + } detail: { VStack { if selectedRoute != nil { let locationArray = selectedRoute?.locations?.array as? [LocationEntity] ?? [] @@ -105,18 +104,31 @@ struct Routes: View { return location.locationCoordinate ?? LocationHelper.DefaultLocation }) - Map () { - - let gradient = LinearGradient( - colors: [.cyan, .blue, .secondary],//[Color(nodeColor.lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)], - startPoint: .leading, endPoint: .trailing - ) + Map() { + Annotation("Start", coordinate: lineCoords.first ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.green)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) + } + } + .annotationTitles(.automatic) + Annotation("Finish", coordinate: locationArray.last?.locationCoordinate ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.black)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) + } + } + .annotationTitles(.automatic) let dashed = StrokeStyle( lineWidth: 3, - lineCap: .round, lineJoin: .round, dash: [10, 10] + lineCap: .round, lineJoin: .round, dash: [7, 10] ) MapPolyline(coordinates: lineCoords) - .stroke(gradient, style: dashed) + .stroke(.green, style: dashed) } .frame(maxWidth: .infinity, maxHeight: .infinity) } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 1694105e..2d21c447 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -62,7 +62,7 @@ struct Settings: View { NavigationLink { Routes() } label: { - Image(systemName: "gearshape") + Image(systemName: "road.lanes.curved.right") .symbolRenderingMode(.hierarchical) Text("routes") } From 787b7907a6d06ea9ebc859f956ab73b249f4145c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Nov 2023 00:36:05 -0800 Subject: [PATCH 09/16] Route color --- Meshtastic/Extensions/UIColor.swift | 10 ++++++- .../contents | 3 +- Meshtastic/Views/Settings/Routes.swift | 30 +++++++++++++++---- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Meshtastic/Extensions/UIColor.swift b/Meshtastic/Extensions/UIColor.swift index 6a8909b9..d9160015 100644 --- a/Meshtastic/Extensions/UIColor.swift +++ b/Meshtastic/Extensions/UIColor.swift @@ -37,5 +37,13 @@ extension UIColor { private func add(_ value: CGFloat, toComponent: CGFloat) -> CGFloat { return max(0, min(1, toComponent + value)) } - + + static var random: UIColor { + return UIColor( + red: .random(in: 0...1), + green: .random(in: 0...1), + blue: .random(in: 0...1), + alpha: 1.0 + ) + } } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents index ec6e67db..b042f51f 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents @@ -285,8 +285,9 @@ - + + diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 2d64f42f..648d90e7 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -26,6 +26,10 @@ struct Routes: View { Button("Import Route") { importing = true } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() .fileImporter( isPresented: $importing, allowedContentTypes: [.commaSeparatedText], @@ -54,9 +58,9 @@ struct Routes: View { } if latIndex >= 0 && longIndex >= 0 { let newRoute = RouteEntity(context: context) - newRoute.name = ("\(String(routeName)) - \(Date().formatted())") + newRoute.name = String(routeName) newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max) - newRoute.color = 12 + newRoute.color = Int64(UIColor.random.hex) newRoute.date = Date() var newLocations = [LocationEntity]() lines.dropFirst().forEach { line in @@ -90,8 +94,22 @@ struct Routes: View { VStack { List(routes, id: \.self, selection: $selectedRoute) { route in - Text(route.name ?? "No Name Route") - .font(.title) + Label { + VStack (alignment: .leading) { + Text("\(route.name ?? "No Name Route")") + .padding(.top) + .foregroundStyle(.primary) + + Text("\(route.date?.formatted() ?? "Unknown Time")") + .padding(.bottom) + .font(.callout) + .foregroundColor(.gray) + } + } icon: { + RoundedRectangle(cornerRadius: 10) + .fill(Color(UIColor(hex: route.color >= 0 ? UInt32(route.color) : 0))) + .frame(width: 20, height: 20) + } } .listStyle(.plain) } @@ -128,11 +146,11 @@ struct Routes: View { lineCap: .round, lineJoin: .round, dash: [7, 10] ) MapPolyline(coordinates: lineCoords) - .stroke(.green, style: dashed) + .stroke(Color(UIColor(hex: UInt32(selectedRoute?.color ?? 0))), style: dashed) } .frame(maxWidth: .infinity, maxHeight: .infinity) } - }.navigationTitle(" \(selectedRoute?.name ?? "Unknown Route") Map") + }.navigationTitle(" \(selectedRoute?.name ?? "Unknown Route") \(selectedRoute?.locations?.count ?? 0) points") } } } From 1c190bb41302d8d2de37e4f8f3619080c1233dce Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Nov 2023 00:48:04 -0800 Subject: [PATCH 10/16] Swipe to delete routes --- Meshtastic/Views/Settings/Routes.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 648d90e7..e9d614ce 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -110,6 +110,18 @@ struct Routes: View { .fill(Color(UIColor(hex: route.color >= 0 ? UInt32(route.color) : 0))) .frame(width: 20, height: 20) } + .swipeActions { + Button(role: .destructive) { + context.delete(route) + do { + try context.save() + } catch let error as NSError { + print("Error: \(error.localizedDescription)") + } + } label: { + Label("delete", systemImage: "trash") + } + } } .listStyle(.plain) } From 44859fb98529b75b10e6718f39edd1b1c04685b1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Nov 2023 01:45:08 -0800 Subject: [PATCH 11/16] Alert for bad csv files --- Meshtastic/Views/Settings/Routes.swift | 31 +++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index e9d614ce..4c78a446 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -17,6 +17,7 @@ struct Routes: View { @EnvironmentObject var bleManager: BLEManager @State private var selectedRoute: RouteEntity? @State private var importing = false + @State private var isShowingBadFileAlert = false @FetchRequest(sortDescriptors: [], animation: .default) @@ -30,6 +31,10 @@ struct Routes: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding() + + .alert(isPresented: $isShowingBadFileAlert) { + Alert(title: Text("Not a valid route file"), message: Text("Your route file must have both Latitude and Longitude."), dismissButton: .default(Text("OK"))) + } .fileImporter( isPresented: $importing, allowedContentTypes: [.commaSeparatedText], @@ -62,6 +67,7 @@ struct Routes: View { newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max) newRoute.color = Int64(UIColor.random.hex) newRoute.date = Date() + newRoute.enabled = true var newLocations = [LocationEntity]() lines.dropFirst().forEach { line in let data = line.components(separatedBy: ",") @@ -80,7 +86,10 @@ struct Routes: View { try context.save() } catch let error as NSError { print("Error: \(error.localizedDescription)") + isShowingBadFileAlert = true } + } else { + isShowingBadFileAlert = true } } catch { @@ -94,6 +103,7 @@ struct Routes: View { VStack { List(routes, id: \.self, selection: $selectedRoute) { route in + let routeColor = Color(UIColor(hex: route.color >= 0 ? UInt32(route.color) : 0)) Label { VStack (alignment: .leading) { Text("\(route.name ?? "No Name Route")") @@ -104,11 +114,26 @@ struct Routes: View { .padding(.bottom) .font(.callout) .foregroundColor(.gray) + + if route.notes?.count ?? 0 > 0 { + Text("\(route.notes ?? "")") + .padding(.bottom) + .font(.callout) + .foregroundColor(.gray) + } } } icon: { - RoundedRectangle(cornerRadius: 10) - .fill(Color(UIColor(hex: route.color >= 0 ? UInt32(route.color) : 0))) - .frame(width: 20, height: 20) + ZStack { + Circle() + .fill(routeColor) + .frame(width: 40, height: 40) + .padding(.top) + if route.enabled { + Image(systemName: "checkmark.circle.fill") + .padding(.top) + .foregroundColor(routeColor.isLight() ? .black : .white) + } + } } .swipeActions { Button(role: .destructive) { From 24868b4a2825f4de1cad52fb8cf33dda5bab6b67 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 26 Nov 2023 10:45:03 -0800 Subject: [PATCH 12/16] Assorted updates --- Meshtastic.xcodeproj/project.pbxproj | 20 ++ Meshtastic/Helpers/BLEManager.swift | 4 +- .../Nodes/Helpers/Map/MapSettingsForm.swift | 2 +- .../Views/Nodes/Helpers/NodeListItem.swift | 1 + Meshtastic/Views/Nodes/MeshMap.swift | 68 +++++-- Meshtastic/Views/Settings/Channels.swift | 2 +- Meshtastic/Views/Settings/Routes.swift | 6 +- Meshtastic/Views/Settings/Settings.swift | 22 +-- Meshtastic/Views/Settings/Settings2.swift | 171 ++++++++++++++++++ 9 files changed, 264 insertions(+), 32 deletions(-) create mode 100644 Meshtastic/Views/Settings/Settings2.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 3da89de8..cbf12dd5 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */; }; DD1933782B084F4200771CD5 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933772B084F4200771CD5 /* Measurement.swift */; }; DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; + DD21007F2B0E571300F2F116 /* Settings2.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD21007E2B0E571300F2F116 /* Settings2.swift */; }; DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; }; DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; }; @@ -231,6 +232,7 @@ DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAltitudeChart.swift; sourceTree = ""; }; DD1933772B084F4200771CD5 /* Measurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; + DD21007E2B0E571300F2F116 /* Settings2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings2.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; @@ -482,6 +484,21 @@ path = CoreData; sourceTree = ""; }; + DD2100802B0E676E00F2F116 /* Routes */ = { + isa = PBXGroup; + children = ( + DD2100832B0E67AD00F2F116 /* RouteMap */, + ); + path = Routes; + sourceTree = ""; + }; + DD2100832B0E67AD00F2F116 /* RouteMap */ = { + isa = PBXGroup; + children = ( + ); + path = RouteMap; + sourceTree = ""; + }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( @@ -509,6 +526,7 @@ DD4A911C2708C57100501B7E /* Settings */ = { isa = PBXGroup; children = ( + DD2100802B0E676E00F2F116 /* Routes */, DD97E96728EFE9A00056DDA4 /* About.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, @@ -518,6 +536,7 @@ DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DD3501882852FC3B000FC853 /* Settings.swift */, + DD21007E2B0E571300F2F116 /* Settings2.swift */, DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD61937A2863876A00E59241 /* Config */, @@ -1091,6 +1110,7 @@ DD457188293C7E63000C49FB /* BLESignalStrengthIndicator.swift in Sources */, DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */, DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */, + DD21007F2B0E571300F2F116 /* Settings2.swift in Sources */, DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */, DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 07ef857a..f49dcc78 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -612,7 +612,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } case .neighborinfoApp: - MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + if let neighborInfo = try? NeighborInfo(serializedData: decodedInfo.packet.decoded.payload) { + MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App App UNHANDLED \(neighborInfo)") + } case .UNRECOGNIZED: MeshLogger.log("🕸️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .max: diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift index c9c351ba..f094d5e8 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift @@ -22,7 +22,7 @@ struct MapSettingsForm: View { var body: some View { - VStack { + NavigationStack { Form { Section(header: Text("Map Options")) { Picker(selection: $mapLayer, label: Text("")) { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 8f4ae4e2..a5b5a3db 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -80,6 +80,7 @@ struct NodeListItem: View { HStack { let preset = ModemPresets(rawValue: Int(modemPreset)) LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) + .padding(.top, 2) } } HStack { diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 3e090f35..621e9f08 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -54,6 +54,10 @@ struct MeshMap: View { format: "expire == nil || expire >= %@", Date() as NSDate ), animation: .none) private var waypoints: FetchedResults + + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)], + predicate: NSPredicate(format: "enabled == true", ""), animation: .none) + private var routes: FetchedResults var body: some View { @@ -123,7 +127,39 @@ struct MeshMap: View { selectedPosition = (selectedPosition == position ? nil : position) } } - /// Route Lines + /// Routes + ForEach(Array(routes), id: \.id) { route in + let routeLocations = Array(route.locations!) as! [LocationEntity] + let routeCoords = routeLocations.compactMap({(loc) -> CLLocationCoordinate2D in + return loc.locationCoordinate ?? LocationHelper.DefaultLocation + }) + Annotation("Start", coordinate: routeCoords.first ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.green)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) + } + } + .annotationTitles(.automatic) + Annotation("Finish", coordinate: routeCoords.last ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.black)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) + } + } + .annotationTitles(.automatic) + let dashed = StrokeStyle( + lineWidth: 3, + lineCap: .round, lineJoin: .round, dash: [7, 10] + ) + MapPolyline(coordinates: routeCoords) + .stroke(Color(UIColor(hex: UInt32(route.color))), style: dashed) + + } + /// Node Route Lines if showRouteLines { let nodePositions = Array(position.nodePosition!.positions!) as! [PositionEntity] let routeCoords = nodePositions.compactMap({(pos) -> CLLocationCoordinate2D in @@ -173,6 +209,19 @@ struct MeshMap: View { } } } + .mapScope(mapScope) + .mapStyle(mapStyle) + .mapControls { + MapScaleView(scope: mapScope) + .mapControlVisibility(.automatic) + MapUserLocationButton(scope: mapScope) + .mapControlVisibility(showUserLocation ? .visible : .hidden) + MapPitchToggle(scope: mapScope) + .mapControlVisibility(.automatic) + MapCompass(scope: mapScope) + .mapControlVisibility(.automatic) + } + .controlSize(.regular) .onTapGesture(count: 1, perform: { location in newWaypointCoord = reader.convert(location , from: .local) }) @@ -185,23 +234,10 @@ struct MeshMap: View { editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480) editingWaypoint!.id = 0 } + } } - .mapScope(mapScope) - .mapStyle(mapStyle) - .mapControls { - MapScaleView(scope: mapScope) - .mapControlVisibility(.visible) - if showUserLocation { - MapUserLocationButton(scope: mapScope) - .mapControlVisibility(.visible) - } - MapPitchToggle(scope: mapScope) - .mapControlVisibility(.visible) - MapCompass(scope: mapScope) - .mapControlVisibility(.visible) - } - .controlSize(.regular) + .sheet(item: $selectedPosition) { selection in PositionPopover(position: selection, popover: false) .padding() diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 03f48f56..737a90d3 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -142,7 +142,7 @@ struct Channels: View { Picker("Key Size", selection: $channelKeySize) { Text("Empty").tag(0) Text("Default").tag(-1) - Text("1 bit").tag(1) + Text("1 byte").tag(1) Text("128 bit").tag(16) Text("192 bit").tag(24) Text("256 bit").tag(32) diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 4c78a446..11b2e723 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -19,11 +19,12 @@ struct Routes: View { @State private var importing = false @State private var isShowingBadFileAlert = false - @FetchRequest(sortDescriptors: [], animation: .default) + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "enabled", ascending: false), NSSortDescriptor(key: "name", ascending: true), NSSortDescriptor(key: "date", ascending: false)], animation: .default) var routes: FetchedResults var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { + //VStack { Button("Import Route") { importing = true } @@ -152,6 +153,7 @@ struct Routes: View { } .navigationTitle("Route List") } detail: { + VStack { if selectedRoute != nil { let locationArray = selectedRoute?.locations?.array as? [LocationEntity] ?? [] @@ -169,7 +171,7 @@ struct Routes: View { } } .annotationTitles(.automatic) - Annotation("Finish", coordinate: locationArray.last?.locationCoordinate ?? LocationHelper.DefaultLocation) { + Annotation("Finish", coordinate: lineCoords.last ?? LocationHelper.DefaultLocation) { ZStack { Circle() .fill(Color(.black)) diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 2d21c447..7fb37da8 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -278,17 +278,17 @@ struct Settings: View { } .tag(SettingsSidebar.adminMessageLog) } - Section(header: Text("Firmware")) { - NavigationLink { - Firmware(node: nodes.first(where: { $0.num == preferredNodeNum })) - } label: { - Image(systemName: "arrow.up.arrow.down.square") - .symbolRenderingMode(.hierarchical) - Text("Firmware Updates") - } - .tag(SettingsSidebar.about) - .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) - } +// Section(header: Text("Firmware")) { +// NavigationLink { +// Firmware(node: nodes.first(where: { $0.num == preferredNodeNum })) +// } label: { +// Image(systemName: "arrow.up.arrow.down.square") +// .symbolRenderingMode(.hierarchical) +// Text("Firmware Updates") +// } +// .tag(SettingsSidebar.about) +// .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) +// } } } .onAppear { diff --git a/Meshtastic/Views/Settings/Settings2.swift b/Meshtastic/Views/Settings/Settings2.swift new file mode 100644 index 00000000..22fcb6c5 --- /dev/null +++ b/Meshtastic/Views/Settings/Settings2.swift @@ -0,0 +1,171 @@ +// +// Settings.swift +// MeshtasticApple +// +// Copyright (c) Garth Vander Houwen 6/9/22. +// + +import SwiftUI + +enum SettingsSidebar: CaseIterable { + case about + case appSettings + case routes + case radioConfig + case moduleConfig + case meshLog + case adminMessageLog + var name: String { + switch self { + case .about: + return "about.meshtastic".localized + case .appSettings: + return "app.settings".localized + case .routes: + return "routes".localized + case .radioConfig: + return "radio.configuration".localized + case .moduleConfig: + return "module.configuration".localized + case .meshLog: + return "mesh.log".localized + case .adminMessageLog: + return "admin.log".localized + } + } + var icon: String { + switch self { + case .about: + return "questionmark.app" + case .appSettings: + return "gearshape".localized + case .routes: + return "routes".localized + case .radioConfig: + return "flipphone".localized + case .moduleConfig: + return "module.configuration".localized + case .meshLog: + return "mesh.log".localized + case .adminMessageLog: + return "admin.log".localized + } + } +} +extension SettingsSidebar: Identifiable { + var id: Self { self } +} + +@available(iOS 17.0, macOS 14.0, *) +struct Settings2: View { + @State private var compactColumn = NavigationSplitViewColumn.detail + @State private var columnVisibility = NavigationSplitViewVisibility.automatic + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default) + private var nodes: FetchedResults + @State private var selectedNode: Int = 0 + @State private var preferredNodeNum: Int = 0 + @State private var selection: SettingsSidebar = .about + + enum SettingsContent { + case appSettings + case routes + case shareChannels + case userConfig + case loraConfig + case channelConfig + case bluetoothConfig + case deviceConfig + case displayConfig + case networkConfig + case positionConfig + case cannedMessagesConfig + case detectionSensorConfig + case externalNotificationConfig + case mqttConfig + case rangeTestConfig + case ringtoneConfig + case serialConfig + case telemetryConfig + case meshLog + case adminMessageLog + case about + } + var body: some View { + NavigationSplitView(columnVisibility: $columnVisibility, preferredCompactColumn: $compactColumn) { + + List(SettingsSidebar.allCases) { item in + switch(item) { + case .about: + NavigationLink { AboutMeshtastic() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .appSettings: + NavigationLink { AppSettings() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .routes: + NavigationLink { Routes() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .radioConfig: + NavigationLink { Routes() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .moduleConfig: + NavigationLink { Routes() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .meshLog: + NavigationLink { MeshLog() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + case .adminMessageLog: + NavigationLink { AdminMessageList() } label: { + Image(systemName: item.icon) + .symbolRenderingMode(.hierarchical) + Text(item.name.localized) + } + .tag(item) + } + } + .listStyle(GroupedListStyle()) + .navigationTitle("settings") + .navigationBarItems(leading: MeshtasticLogo()) + } content: { + List { + if selection == .routes { + Text("Routes Bitechs") + } + } + } + detail: { + Text("Detail") + ContentUnavailableView("select.menu.item", systemImage: "gear") + } + .onChange(of: selection) { value in + columnVisibility = .doubleColumn + compactColumn = .sidebar + + } + } +} From cdaecdd3a8f1ef64facddd49c5bb8add86833a90 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 26 Nov 2023 11:17:36 -0800 Subject: [PATCH 13/16] Delete unused page --- Meshtastic.xcodeproj/project.pbxproj | 4 - Meshtastic/Views/Settings/Routes.swift | 6 +- Meshtastic/Views/Settings/Settings2.swift | 171 ---------------------- 3 files changed, 3 insertions(+), 178 deletions(-) delete mode 100644 Meshtastic/Views/Settings/Settings2.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index cbf12dd5..b01e0ee0 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -22,7 +22,6 @@ DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */; }; DD1933782B084F4200771CD5 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933772B084F4200771CD5 /* Measurement.swift */; }; DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; - DD21007F2B0E571300F2F116 /* Settings2.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD21007E2B0E571300F2F116 /* Settings2.swift */; }; DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; }; DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; }; @@ -232,7 +231,6 @@ DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAltitudeChart.swift; sourceTree = ""; }; DD1933772B084F4200771CD5 /* Measurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; - DD21007E2B0E571300F2F116 /* Settings2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings2.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; @@ -536,7 +534,6 @@ DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DD3501882852FC3B000FC853 /* Settings.swift */, - DD21007E2B0E571300F2F116 /* Settings2.swift */, DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD61937A2863876A00E59241 /* Config */, @@ -1110,7 +1107,6 @@ DD457188293C7E63000C49FB /* BLESignalStrengthIndicator.swift in Sources */, DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */, DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */, - DD21007F2B0E571300F2F116 /* Settings2.swift in Sources */, DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */, DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 11b2e723..1c3d52c7 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -23,8 +23,8 @@ struct Routes: View { var routes: FetchedResults var body: some View { - NavigationSplitView(columnVisibility: $columnVisibility) { - //VStack { + //NavigationSplitView(columnVisibility: $columnVisibility) { + NavigationStack { Button("Import Route") { importing = true } @@ -152,7 +152,7 @@ struct Routes: View { .listStyle(.plain) } .navigationTitle("Route List") - } detail: { +// } detail: { VStack { if selectedRoute != nil { diff --git a/Meshtastic/Views/Settings/Settings2.swift b/Meshtastic/Views/Settings/Settings2.swift deleted file mode 100644 index 22fcb6c5..00000000 --- a/Meshtastic/Views/Settings/Settings2.swift +++ /dev/null @@ -1,171 +0,0 @@ -// -// Settings.swift -// MeshtasticApple -// -// Copyright (c) Garth Vander Houwen 6/9/22. -// - -import SwiftUI - -enum SettingsSidebar: CaseIterable { - case about - case appSettings - case routes - case radioConfig - case moduleConfig - case meshLog - case adminMessageLog - var name: String { - switch self { - case .about: - return "about.meshtastic".localized - case .appSettings: - return "app.settings".localized - case .routes: - return "routes".localized - case .radioConfig: - return "radio.configuration".localized - case .moduleConfig: - return "module.configuration".localized - case .meshLog: - return "mesh.log".localized - case .adminMessageLog: - return "admin.log".localized - } - } - var icon: String { - switch self { - case .about: - return "questionmark.app" - case .appSettings: - return "gearshape".localized - case .routes: - return "routes".localized - case .radioConfig: - return "flipphone".localized - case .moduleConfig: - return "module.configuration".localized - case .meshLog: - return "mesh.log".localized - case .adminMessageLog: - return "admin.log".localized - } - } -} -extension SettingsSidebar: Identifiable { - var id: Self { self } -} - -@available(iOS 17.0, macOS 14.0, *) -struct Settings2: View { - @State private var compactColumn = NavigationSplitViewColumn.detail - @State private var columnVisibility = NavigationSplitViewVisibility.automatic - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default) - private var nodes: FetchedResults - @State private var selectedNode: Int = 0 - @State private var preferredNodeNum: Int = 0 - @State private var selection: SettingsSidebar = .about - - enum SettingsContent { - case appSettings - case routes - case shareChannels - case userConfig - case loraConfig - case channelConfig - case bluetoothConfig - case deviceConfig - case displayConfig - case networkConfig - case positionConfig - case cannedMessagesConfig - case detectionSensorConfig - case externalNotificationConfig - case mqttConfig - case rangeTestConfig - case ringtoneConfig - case serialConfig - case telemetryConfig - case meshLog - case adminMessageLog - case about - } - var body: some View { - NavigationSplitView(columnVisibility: $columnVisibility, preferredCompactColumn: $compactColumn) { - - List(SettingsSidebar.allCases) { item in - switch(item) { - case .about: - NavigationLink { AboutMeshtastic() } label: { - Image(systemName: item.icon) - .symbolRenderingMode(.hierarchical) - Text(item.name.localized) - } - .tag(item) - case .appSettings: - NavigationLink { AppSettings() } label: { - Image(systemName: item.icon) - .symbolRenderingMode(.hierarchical) - Text(item.name.localized) - } - .tag(item) - case .routes: - NavigationLink { Routes() } label: { - Image(systemName: item.icon) - .symbolRenderingMode(.hierarchical) - Text(item.name.localized) - } - .tag(item) - case .radioConfig: - NavigationLink { Routes() } label: { - Image(systemName: item.icon) - .symbolRenderingMode(.hierarchical) - Text(item.name.localized) - } - .tag(item) - case .moduleConfig: - NavigationLink { Routes() } label: { - Image(systemName: item.icon) - .symbolRenderingMode(.hierarchical) - Text(item.name.localized) - } - .tag(item) - case .meshLog: - NavigationLink { MeshLog() } label: { - Image(systemName: item.icon) - .symbolRenderingMode(.hierarchical) - Text(item.name.localized) - } - .tag(item) - case .adminMessageLog: - NavigationLink { AdminMessageList() } label: { - Image(systemName: item.icon) - .symbolRenderingMode(.hierarchical) - Text(item.name.localized) - } - .tag(item) - } - } - .listStyle(GroupedListStyle()) - .navigationTitle("settings") - .navigationBarItems(leading: MeshtasticLogo()) - } content: { - List { - if selection == .routes { - Text("Routes Bitechs") - } - } - } - detail: { - Text("Detail") - ContentUnavailableView("select.menu.item", systemImage: "gear") - } - .onChange(of: selection) { value in - columnVisibility = .doubleColumn - compactColumn = .sidebar - - } - } -} From 93d3cb33c8c8a5aea0c4f138b8a0792fbc34f8f3 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 26 Nov 2023 12:19:32 -0800 Subject: [PATCH 14/16] Localize the routes, add some detents --- Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift | 4 +++- de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 1 + pl.lproj/Localizable.strings | 1 + zh-Hans.lproj/Localizable.strings | 1 + 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 36bf1555..54aa5b29 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -29,7 +29,7 @@ struct WaypointForm: View { @State private var lockedTo: Int64 = 0 var body: some View { - VStack { + NavigationStack { if editMode { Text((waypoint.id > 0) ? "Editing Waypoint" : "Create Waypoint") .font(.largeTitle) @@ -385,5 +385,7 @@ struct WaypointForm: View { longitude = waypoint.coordinate.longitude } } + .presentationDetents([.fraction(0.75)]) + .presentationDragIndicator(.visible) } } diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index e58c38b6..60d9ff6b 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -225,6 +225,7 @@ "received.ack.real"="Recipient Ack"; "ringtone"="Ringtone"; "ringtone.config"="Ringtone Config"; +"routes"="Routes"; "routing.acknowledged"="Bestätigt"; "routing.noroute"="Keine Route"; "routing.gotnak"="Negative Empfangsbestätigung empfangen"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index ae374695..23c19d6a 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -228,6 +228,7 @@ "received.ack.real"="Recipient Ack"; "ringtone"="Ringtone"; "ringtone.config"="Ringtone Config"; +"routes"="Routes"; "routing.acknowledged"="Acknowledged"; "routing.noroute"="No Route"; "routing.gotnak"="Received a negative acknowledgment"; diff --git a/pl.lproj/Localizable.strings b/pl.lproj/Localizable.strings index ad7bc540..489db06a 100644 --- a/pl.lproj/Localizable.strings +++ b/pl.lproj/Localizable.strings @@ -226,6 +226,7 @@ "received.ack.real"="Odbiorca potwierdzenia"; "ringtone"="Dzwonek"; "ringtone.config"="Konfiguracja dzwonka"; +"routes"="Routes"; "routing.acknowledged"="Potwierdzono"; "routing.noroute"="Brak trasy"; "routing.gotnak"="Otrzymano negatywne potwierdzenie"; diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index d1b7dcc8..cd4a522b 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -225,6 +225,7 @@ "received.ack.real"="收件人确认"; "ringtone"="铃声"; "ringtone.config"="铃声设置"; +"routes"="Routes"; "routing.acknowledged"="确认"; "routing.noroute"="找不到目标"; "routing.gotnak"="收到否认"; From f12eed755dde714b6ca6f35769f9ad553ebf0b1d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 26 Nov 2023 12:54:45 -0800 Subject: [PATCH 15/16] Config cleanup --- Meshtastic/Enums/DisplayEnums.swift | 26 +++++++++++++++++++ .../contents | 1 + Meshtastic/Persistence/UpdateCoreData.swift | 2 ++ .../Views/Settings/Config/DisplayConfig.swift | 17 ++++++++++++ .../Views/Settings/Config/LoRaConfig.swift | 12 +++++++++ 5 files changed, 58 insertions(+) diff --git a/Meshtastic/Enums/DisplayEnums.swift b/Meshtastic/Enums/DisplayEnums.swift index 42eb6339..e227fdc2 100644 --- a/Meshtastic/Enums/DisplayEnums.swift +++ b/Meshtastic/Enums/DisplayEnums.swift @@ -168,3 +168,29 @@ enum DisplayModes: Int, CaseIterable, Identifiable { } } } + +// Default of 0 is metric +enum Units: Int, CaseIterable, Identifiable { + + case metric = 0 + case imperial = 1 + + var id: Int { self.rawValue } + var description: String { + switch self { + case .metric: + return "Metric" + case .imperial: + return "Imperial" + } + } + func protoEnumValue() -> Config.DisplayConfig.DisplayUnits { + + switch self { + case .metric: + return Config.DisplayConfig.DisplayUnits.metric + case .imperial: + return Config.DisplayConfig.DisplayUnits.imperial + } + } +} diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents index b042f51f..fc942300 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV20.xcdatamodel/contents @@ -92,6 +92,7 @@ + diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 8588483c..d210cb0d 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -413,6 +413,7 @@ func upsertDisplayConfigPacket(config: Meshtastic.Config.DisplayConfig, nodeNum: newDisplayConfig.flipScreen = config.flipScreen newDisplayConfig.oledType = Int32(config.oled.rawValue) newDisplayConfig.displayMode = Int32(config.displaymode.rawValue) + newDisplayConfig.units = Int32(config.units.rawValue) newDisplayConfig.headingBold = config.headingBold fetchedNode[0].displayConfig = newDisplayConfig @@ -425,6 +426,7 @@ func upsertDisplayConfigPacket(config: Meshtastic.Config.DisplayConfig, nodeNum: fetchedNode[0].displayConfig?.flipScreen = config.flipScreen fetchedNode[0].displayConfig?.oledType = Int32(config.oled.rawValue) fetchedNode[0].displayConfig?.displayMode = Int32(config.displaymode.rawValue) + fetchedNode[0].displayConfig?.units = Int32(config.units.rawValue) fetchedNode[0].displayConfig?.headingBold = config.headingBold } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index d3d95b7d..f600dda3 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -26,6 +26,7 @@ struct DisplayConfig: View { @State var flipScreen = false @State var oledType = 0 @State var displayMode = 0 + @State var units = 0 var body: some View { @@ -125,6 +126,15 @@ struct DisplayConfig: View { Text("The format used to display GPS coordinates on the device screen.") .font(.caption) .listRowSeparator(.visible) + + Picker("Display Units", selection: $units ) { + ForEach(Units.allCases) { un in + Text(un.description) + } + } + .pickerStyle(DefaultPickerStyle()) + Text("Units displayed on the device screen") + .font(.caption) } } .disabled(self.bleManager.connectedPeripheral == nil || node?.displayConfig == nil) @@ -160,6 +170,7 @@ struct DisplayConfig: View { dc.flipScreen = flipScreen dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue() dc.displaymode = DisplayModes(rawValue: displayMode)!.protoEnumValue() + dc.units = Units(rawValue: units)!.protoEnumValue() let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { @@ -233,6 +244,11 @@ struct DisplayConfig: View { if newDisplayMode != node!.displayConfig!.displayMode { hasChanges = true } } } + .onChange(of: units) { newUnits in + if node != nil && node!.displayConfig != nil { + if newUnits != node!.displayConfig!.units { hasChanges = true } + } + } } func setDisplayValues() { self.gpsFormat = Int(node?.displayConfig?.gpsFormat ?? 0) @@ -243,6 +259,7 @@ struct DisplayConfig: View { self.flipScreen = node?.displayConfig?.flipScreen ?? false self.oledType = Int(node?.displayConfig?.oledType ?? 0) self.displayMode = Int(node?.displayConfig?.displayMode ?? 0) + self.units = Int(node?.displayConfig?.units ?? 0) self.hasChanges = false } } diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index db176fb5..dccef435 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -184,6 +184,12 @@ struct LoRaConfig: View { .scrollDismissesKeyboard(.immediately) .focused($focusedField, equals: .frequencyOverride) } + HStack { + Image(systemName: "antenna.radiowaves.left.and.right") + .foregroundColor(.accentColor) + Stepper("\(txPower)db Transmit Power", value: $txPower, in: 1...30, step: 1) + .padding(5) + } } } .disabled(self.bleManager.connectedPeripheral == nil || node?.loRaConfig == nil) @@ -214,6 +220,7 @@ struct LoRaConfig: View { lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() lc.usePreset = usePreset lc.txEnabled = txEnabled + lc.txPower = Int32(txPower) lc.channelNum = UInt32(channelNum) lc.bandwidth = UInt32(bandwidth) lc.codingRate = UInt32(codingRate) @@ -302,6 +309,11 @@ struct LoRaConfig: View { if newOverrideFrequency != node!.loRaConfig!.overrideFrequency { hasChanges = true } } } + .onChange(of: txPower) { newTxPower in + if node != nil && node!.loRaConfig != nil { + if newTxPower != node!.loRaConfig!.txPower { hasChanges = true } + } + } } func setLoRaValues() { self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 3) From 906d22c350b44f0b3d6b85b06e42dcd56b439e03 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 26 Nov 2023 17:50:10 -0800 Subject: [PATCH 16/16] Update some intervals, throw out the last postion log if the latest one is < 10 meters away. --- Meshtastic/Enums/AppSettingsEnums.swift | 3 --- Meshtastic/Enums/DisplayEnums.swift | 3 +++ Meshtastic/Persistence/UpdateCoreData.swift | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index d2d13979..dd0a94ed 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -85,7 +85,6 @@ enum UserTrackingModes: Int, CaseIterable, Identifiable { } enum LocationUpdateInterval: Int, CaseIterable, Identifiable { - case fiveSeconds = 5 case tenSeconds = 10 case fifteenSeconds = 15 case thirtySeconds = 30 @@ -97,8 +96,6 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable { var id: Int { self.rawValue } var description: String { switch self { - case .fiveSeconds: - return "interval.five.seconds".localized case .tenSeconds: return "interval.ten.seconds".localized case .fifteenSeconds: diff --git a/Meshtastic/Enums/DisplayEnums.swift b/Meshtastic/Enums/DisplayEnums.swift index e227fdc2..afecb6ec 100644 --- a/Meshtastic/Enums/DisplayEnums.swift +++ b/Meshtastic/Enums/DisplayEnums.swift @@ -73,6 +73,7 @@ enum ScreenOnIntervals: Int, CaseIterable, Identifiable { enum ScreenCarouselIntervals: Int, CaseIterable, Identifiable { case off = 0 + case fifteenSeconds = 15 case thirtySeconds = 30 case oneMinute = 60 case fiveMinutes = 300 @@ -84,6 +85,8 @@ enum ScreenCarouselIntervals: Int, CaseIterable, Identifiable { switch self { case .off: return "off".localized + case .fifteenSeconds: + return "interval.fifteen.seconds".localized case .thirtySeconds: return "interval.thirty.seconds".localized case .oneMinute: diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index d210cb0d..a02837c3 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -256,10 +256,10 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else { return } - /// Don't save the same position over and over. + /// Don't save nearly the same position over and over. If the next position is less than 10 meters from the new position, delete the previous position and save the new one. if mutablePositions.count > 0 { let mostRecent = mutablePositions.lastObject as! PositionEntity - if mostRecent.latitudeI == position.latitudeI && mostRecent.longitudeI == position.longitudeI { + if mostRecent.coordinate.distance(from: position.coordinate) < 10 { mutablePositions.remove(mostRecent) } }