diff --git a/Meshtastic/Enums/PositionConfigEnums.swift b/Meshtastic/Enums/PositionConfigEnums.swift index 71c67cda..7350cfa3 100644 --- a/Meshtastic/Enums/PositionConfigEnums.swift +++ b/Meshtastic/Enums/PositionConfigEnums.swift @@ -109,6 +109,7 @@ enum GpsUpdateIntervals: Int, CaseIterable, Identifiable { case fifteenSeconds = 15 case thirtySeconds = 30 case oneMinute = 60 + case twoMinutes = 120 case fiveMinutes = 300 case tenMinutes = 600 case fifteenMinutes = 900 @@ -134,6 +135,8 @@ enum GpsUpdateIntervals: Int, CaseIterable, Identifiable { return "Thirty Seconds" case .oneMinute: return "One Minute" + case .twoMinutes: + return "Two Minutes" case .fiveMinutes: return "Five Minutes" case .tenMinutes: diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 815b5502..badb2498 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -962,7 +962,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) + positionPacket.timestamp = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) positionPacket.altitude = Int32(LocationHelper.currentAltitude) + positionPacket.satsInView = UInt32(LocationHelper.satsInView) // Get Errors without some speed if LocationHelper.currentSpeed >= 5 { diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index 547b0f62..c40b4d2a 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -52,7 +52,31 @@ class LocationHelper: NSObject, ObservableObject { return timestamp } - + static var satsInView: Int { + + // If we have a position we have a sat + var sats = 1 + + if shared.locationManager.location?.verticalAccuracy ?? 0 > 0 { + sats = 4 + + if 0...15 ~= shared.locationManager.location?.horizontalAccuracy ?? 0{ + sats = 12 + } else if 16...30 ~= shared.locationManager.location?.horizontalAccuracy ?? 0{ + sats = 10 + } else if 31...45 ~= shared.locationManager.location?.horizontalAccuracy ?? 0{ + sats = 8 + } else if 46...60 ~= shared.locationManager.location?.horizontalAccuracy ?? 0{ + sats = 6 + } + + } else if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 && 60...300 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 { + sats = 3 + } else if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 && shared.locationManager.location?.horizontalAccuracy ?? 0 > 300 { + sats = 2 + } + return sats + } private let locationManager = CLLocationManager() diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index fd3e1c17..aa2dca85 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -36,9 +36,9 @@ struct NodeDetail: View { let mostRecent = node.positions?.lastObject as! PositionEntity if mostRecent.coordinate != nil { - + let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!) - + let regionBinding = Binding( get: { MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) @@ -54,28 +54,30 @@ struct NodeDetail: View { interactionModes: [.all], showsUserLocation: true, userTrackingMode: .constant(.follow), - annotationItems: annotations) - { location in + annotationItems: annotations) { location in return MapAnnotation( - coordinate: location.coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0), - content: { - - NodeAnnotation(time: location.time!) - } + coordinate: location.coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0), + content: { + + NodeAnnotation(time: location.time!) + } ) - } + } .ignoresSafeArea(.all, edges: [.leading, .trailing]) .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.70) + } + Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ") + .offset( y:-40) } - Text("Sats: \(mostRecent.satsInView)").offset( y:-40) + } else { HStack { } - .padding([.top], 40) + .padding([.top], 60) } ScrollView { @@ -89,12 +91,8 @@ struct NodeDetail: View { VStack(alignment: .center) { - Text("AKA").font(.largeTitle) - .foregroundColor(.gray).fixedSize() - .offset(y:5) CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 75, fontSize: 26) } - .padding() Divider() @@ -105,15 +103,14 @@ struct NodeDetail: View { Image(hwModelString) .resizable() .aspectRatio(contentMode: .fill) - .frame(width: 200, height: 200) + .frame(width: 75, height: 75) .cornerRadius(5) Text(String(hwModelString)) .foregroundColor(.gray) - .font(.largeTitle).fixedSize() + .font(.title).fixedSize() } } - .padding() if node.snr > 0 { @@ -432,7 +429,6 @@ struct NodeDetail: View { } } .offset( y:-40) - .padding(.bottom, -40) } .edgesIgnoringSafeArea([.leading, .trailing]) .navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown") diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 25072e80..95f5bf9b 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -21,57 +21,87 @@ struct PositionLog: View { var body: some View { NavigationStack { - - ScrollView { - - Grid(alignment: .topLeading, horizontalSpacing: 2) { - - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - //Add a table for mac and ipad - } - - GridRow { - Text("Lat / Long") - .font(.caption2) - .fontWeight(.bold) - Text("Sat") - .font(.caption2) - .fontWeight(.bold) - Text("Alt") - .font(.caption2) - .fontWeight(.bold) - Text("Spd") - .font(.caption2) - .fontWeight(.bold) - Text("Hd") - .font(.caption2) - .fontWeight(.bold) - Text("Timestamp") - .font(.caption2) - .fontWeight(.bold) - } - Divider() - ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in - GridRow { - Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") - .font(.caption2) - Text(String(mappin.satsInView)) - .font(.caption2) - Text(String(mappin.altitude)) - .font(.caption2) - Text(String(mappin.speed)) - .font(.caption2) - Text(String(mappin.heading)) - .font(.caption2) - Text(mappin.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") - .font(.caption2) + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + //Add a table for mac and ipad + VStack { + Table(node.positions!.reversed() as! [PositionEntity]) { + TableColumn("SeqNo") { position in + Text(String(position.seqNo)) + } + .width(75) + TableColumn("Latitude") { position in + Text(String(format: "%.6f", position.latitude ?? 0)) + } + TableColumn("Longitude") { position in + Text(String(format: "%.6f", position.longitude ?? 0)) + } + TableColumn("Altitude") { position in + Text(String(position.altitude)) + } + .width(75) + TableColumn("Sats") { position in + Text(String(position.satsInView)) + } + .width(75) + TableColumn("Speed") { position in + Text(String(position.speed)) + } + .width(75) + TableColumn("Heading") { position in + Text(String(position.heading)) + } + TableColumn("Time Stamp") { position in + Text(position.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") } } } - .padding(.leading, 15) - .padding(.trailing, 5) + + } else { + + ScrollView { + // Use a grid on iOS as a table only shows a single column + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + GridRow { + + Text("Latitude") + .font(.caption2) + .fontWeight(.bold) + Text("Longitude") + .font(.caption2) + .fontWeight(.bold) + Text("Sats") + .font(.caption2) + .fontWeight(.bold) + Text("Alt") + .font(.caption2) + .fontWeight(.bold) + Text("Timestamp") + .font(.caption2) + .fontWeight(.bold) + } + Divider() + ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in + GridRow { + Text(String(mappin.latitude ?? 0)) + .font(.caption2) + Text(String(mappin.longitude ?? 0)) + .font(.caption2) + Text(String(mappin.satsInView)) + .font(.caption2) + Text(String(mappin.altitude)) + .font(.caption2) + Text(mappin.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.caption2) + } + } + } + .padding(.leading, 15) + .padding(.trailing, 5) + } } + HStack { Button(role: .destructive) { @@ -95,11 +125,10 @@ struct PositionLog: View { if clearPositions(destNum: node.num, context: context) { - print("Clear Position Log Failed") + print("Successfully Cleared Position Log") } else { - - + print("Clear Position Log Failed") } } } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 622dd08b..ea8560dc 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -224,7 +224,8 @@ struct CannedMessagesConfig: View { .confirmationDialog( "Are you sure?", - isPresented: $isPresentingSaveConfirm + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible ) { Button("Save Canned Messages Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 7e2ad96b..7e85192c 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -145,9 +145,9 @@ struct ExternalNotificationConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", - isPresented: $isPresentingSaveConfirm + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible ) { Button("Save External Notification Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 8d103493..b4bc8c96 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -161,7 +161,8 @@ struct MQTTConfig: View { .confirmationDialog( "Are you sure?", - isPresented: $isPresentingSaveConfirm + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible ) { Button("Save MQTT Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 8bb25733..311fec72 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -109,9 +109,9 @@ struct RangeTestConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", - isPresented: $isPresentingSaveConfirm + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible ) { Button("Save Range Test Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 2971b599..fd4e5d8e 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -123,7 +123,8 @@ struct SerialConfig: View { .confirmationDialog( "Are you sure?", - isPresented: $isPresentingSaveConfirm + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible ) { Button("Save Serial Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index a3cce9f8..1ccecd4b 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -152,7 +152,8 @@ struct TelemetryConfig: View { .confirmationDialog( "Are you sure?", - isPresented: $isPresentingSaveConfirm + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible ) { Button("Save Telemetry Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 2c335e4f..797f0359 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -17,7 +17,7 @@ struct PositionFlags: OptionSet static let Dop = PositionFlags(rawValue: 8) static let Hvdop = PositionFlags(rawValue: 16) static let Satsinview = PositionFlags(rawValue: 32) - static let SeqNos = PositionFlags(rawValue: 64) + static let SeqNo = PositionFlags(rawValue: 64) static let Timestamp = PositionFlags(rawValue: 128) static let Speed = PositionFlags(rawValue: 256) static let Heading = PositionFlags(rawValue: 512) @@ -33,6 +33,7 @@ struct PositionConfig: View { @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true @State var hasChanges = false + @State var hasFlagChanges = false @State var smartPositionEnabled = true @State var deviceGpsEnabled = true @@ -246,7 +247,7 @@ struct PositionConfig: View { if includeDop { pf.insert(.Dop) } if includeHvdop { pf.insert(.Hvdop) } if includeSatsinview { pf.insert(.Satsinview) } - if includeSeqNo { pf.insert(.SeqNos) } + if includeSeqNo { pf.insert(.SeqNo) } if includeTimestamp { pf.insert(.Timestamp) } if includeSpeed { pf.insert(.Speed) } if includeHeading { pf.insert(.Heading) } @@ -300,14 +301,13 @@ struct PositionConfig: View { if pf.contains(.Dop) { self.includeDop = true } else { self.includeDop = false } if pf.contains(.Hvdop) { self.includeHvdop = true } else { self.includeHvdop = false } if pf.contains(.Satsinview) { self.includeSatsinview = true } else { self.includeSatsinview = false } - if pf.contains(.SeqNos) { self.includeSeqNo = true } else { self.includeSeqNo = false } + if pf.contains(.SeqNo) { self.includeSeqNo = true } else { self.includeSeqNo = false } if pf.contains(.Timestamp) { self.includeTimestamp = true } else { self.includeTimestamp = false } if pf.contains(.Speed) { self.includeSpeed = true } else { self.includeSpeed = false } if pf.contains(.Heading) { self.includeHeading = true } else { self.includeHeading = false } self.hasChanges = false self.initialLoad = false - } } .onChange(of: deviceGpsEnabled) { newDeviceGps in @@ -352,8 +352,60 @@ struct PositionConfig: View { if newPositionBroadcastSeconds != node!.positionConfig!.positionBroadcastSeconds { hasChanges = true } } } - .onChange(of: includeAltitude || includeAltitudeMsl || includeGeoidalSeparation || includeDop || includeHvdop || includeSatsinview || includeSeqNo || includeTimestamp || includeSpeed || includeHeading) { newFlags in - // hasChanges = true + .onChange(of: includeAltitude) { altFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.Altitude) + if existingValue != altFlag { hasChanges = true } + } + .onChange(of: includeAltitudeMsl) { altMslFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.AltitudeMsl) + if existingValue != altMslFlag { hasChanges = true } + } + .onChange(of: includeSatsinview) { satsFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.Satsinview) + if existingValue != satsFlag { hasChanges = true } + } + .onChange(of: includeSeqNo) { seqFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.SeqNo) + if existingValue != seqFlag { hasChanges = true } + } + .onChange(of: includeTimestamp) { timestampFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.Timestamp) + if existingValue != timestampFlag { hasChanges = true } + } + .onChange(of: includeTimestamp) { timestampFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.Timestamp) + if existingValue != timestampFlag { hasChanges = true } + } + .onChange(of: includeSpeed) { speedFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.Speed) + if existingValue != speedFlag { hasChanges = true } + } + .onChange(of: includeHeading) { headingFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.Heading) + if existingValue != headingFlag { hasChanges = true } + } + .onChange(of: includeGeoidalSeparation) { geoSepFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.GeoidalSeparation) + if existingValue != geoSepFlag { hasChanges = true } + } + .onChange(of: includeDop) { dopFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.Dop) + if existingValue != dopFlag { hasChanges = true } + } + .onChange(of: includeHvdop) { hvdopFlag in + let pf = PositionFlags(rawValue: self.positionFlags) + let existingValue = pf.contains(.Hvdop) + if existingValue != hvdopFlag { hasChanges = true } } .navigationViewStyle(StackNavigationViewStyle()) }