diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 35f9264f..56266691 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -104,6 +104,7 @@ DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A152A0594AD006ED576 /* TileOverlay.swift */; }; DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */ = {isa = PBXBuildFile; fileRef = DDB75A192A05EB67006ED576 /* alpha.png */; }; DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */; }; + DDB75A212A12B954006ED576 /* LoRaSignalStrength.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */; }; DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */; }; DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; }; DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; }; @@ -294,6 +295,7 @@ DDB75A192A05EB67006ED576 /* alpha.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = alpha.png; sourceTree = ""; }; DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrengthIndicator.swift; sourceTree = ""; }; DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV13.xcdatamodel; sourceTree = ""; }; + DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrength.swift; sourceTree = ""; }; DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV8.xcdatamodel; sourceTree = ""; }; DDC2E15426CE248E0042C5E4 /* Meshtastic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meshtastic.app; sourceTree = BUILT_PRODUCTS_DIR; }; DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticApp.swift; sourceTree = ""; }; @@ -694,6 +696,7 @@ DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */, DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */, DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */, + DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */, ); path = Helpers; sourceTree = ""; @@ -1060,6 +1063,7 @@ DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */, DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */, + DDB75A212A12B954006ED576 /* LoRaSignalStrength.swift in Sources */, DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */, DDB6ABE428B13FFF00384BA1 /* DisplayEnums.swift in Sources */, DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */, @@ -1451,7 +1455,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.10; + MARKETING_VERSION = 2.1.11; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1482,7 +1486,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.1.10; + MARKETING_VERSION = 2.1.11; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index 98e2b3b9..42f1c445 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -269,6 +269,12 @@ enum MapTileServer: String, CaseIterable, Identifiable { } } +enum OverlayType: String, CaseIterable, Equatable { + case tileServer + case geoJson + var localized: String { self.rawValue.localized } +} + enum MapOverlayServer: String, CaseIterable, Identifiable { case baseReReflectivityCurrent @@ -282,6 +288,29 @@ enum MapOverlayServer: String, CaseIterable, Identifiable { case mrmsHybridScanReflectivityComposite var id: String { self.rawValue } + var overlayType: OverlayType { + switch self { + + case .baseReReflectivityCurrent: + return .tileServer + case .baseReReflectivityOneHourAgo: + return .tileServer + case .echoTopsEetCurrent: + return .tileServer + case .echoTopsEetOneHourAgo: + return .tileServer + case .q2OneHourPrecipitation: + return .tileServer + case .q2TwentyFourHourPrecipitation: + return .tileServer + case .q2FortyEightHourPrecipitation: + return .tileServer + case .q2SeventyTwoHourPrecipitation: + return .tileServer + case .mrmsHybridScanReflectivityComposite: + return .tileServer + } + } var attribution: String { return "NEXRAD Weather tiles from Iowa State University Environmental Mesonet [OGC Web Services](https://mesonet.agron.iastate.edu/ogc/)." } diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift new file mode 100644 index 00000000..ee897c97 --- /dev/null +++ b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift @@ -0,0 +1,72 @@ +// +// LoRaSignalStrength.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 5/15/23. +// +import Foundation +import SwiftUI + +struct LoRaSignalStrengthMeter: View { + + var snr: Float + var rssi: Int32 + var preset: ModemPresets + var compact: Bool + + var body: some View { + + let signalStrength = getLoRaSignalStrength(snr: snr, rssi: rssi, preset: preset) + let gradient = Gradient(colors: [.red, .orange, .yellow, .green]) + + if !compact { + VStack { + LoRaSignalStrengthIndicator(signalStrength: signalStrength) + Text("Signal \(signalStrength.description)").font(.footnote) + Text("SNR \(String(format: "%.2f", snr))dB") + .foregroundColor(getSnrColor(snr: snr, preset: ModemPresets.longFast)) + .font(.caption2) + Text("RSSI \(rssi)dB") + .foregroundColor(getRssiColor(rssi: rssi)) + .font(.caption2) + } + } else { + Gauge(value: Double(signalStrength.rawValue), in: 0...3) { + } currentValueLabel: { + Image(systemName: "dot.radiowaves.left.and.right") + .font(.caption) + Text("Signal \(signalStrength.description)") + .font(.caption) + } + .gaugeStyle(.accessoryLinear) + .tint(gradient) + .font(.caption) + } + } +} + +struct LoRaSignalStrengthMeter_Previews: PreviewProvider { + static var previews: some View { + VStack { + LoRaSignalStrengthMeter(snr: -10, rssi: -100, preset: ModemPresets.longFast, compact: false) + LoRaSignalStrengthMeter(snr: -17.5, rssi: -100, preset: ModemPresets.longFast, compact: false) + LoRaSignalStrengthMeter(snr: -12.75, rssi: -139, preset: ModemPresets.longFast, compact: false) + LoRaSignalStrengthMeter(snr: -20.25, rssi: -128, preset: ModemPresets.longFast, compact: false) + LoRaSignalStrengthMeter(snr: -30, rssi: -128, preset: ModemPresets.longFast, compact: false) + } + VStack { + LoRaSignalStrengthMeter(snr: -10, rssi: -100, preset: ModemPresets.longFast, compact: true) + .padding(.bottom) + LoRaSignalStrengthMeter(snr: -17.5, rssi: -100, preset: ModemPresets.longFast, compact: true) + .padding(.bottom) + LoRaSignalStrengthMeter(snr: -12.75, rssi: -139, preset: ModemPresets.longFast, compact: true) + .padding(.bottom) + LoRaSignalStrengthMeter(snr: -20.25, rssi: -128, preset: ModemPresets.longFast, compact: true) + .padding(.bottom) + LoRaSignalStrengthMeter(snr: -30, rssi: -128, preset: ModemPresets.longFast, compact: true) + } + .padding() + } +} + + diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift b/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift index 5f88d6d5..3df05659 100644 --- a/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift +++ b/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift @@ -5,8 +5,6 @@ // Copyright Garth Vander Houwen 5/9/23. // -import Foundation - import Foundation import SwiftUI @@ -18,22 +16,25 @@ struct LoRaSignalStrengthIndicator: View { ForEach(0..<3) { bar in RoundedRectangle(cornerRadius: 3) .divided(amount: (CGFloat(bar) + 1) / CGFloat(3)) - .fill(getColor().opacity(bar <= signalStrength.rawValue ? 1 : 0.3)) + .fill(getColor(signalStrength: signalStrength).opacity(bar <= signalStrength.rawValue ? 1 : 0.3)) .frame(width: 8, height: 40) } } } +} - private func getColor() -> Color { - switch signalStrength { - case .none: - return Color.red - case .bad: - return Color.orange - case .fair: - return Color.yellow - case .good: - return Color.green +struct LoRaSignalStrengthIndicator_Previews: PreviewProvider { + static var previews: some View { + VStack { + let signalStrength = getLoRaSignalStrength(snr: -12.75, rssi: -139, preset: ModemPresets.longFast) + LoRaSignalStrengthIndicator(signalStrength: signalStrength) + Text("Signal \(signalStrength.description)").font(.footnote) + Text("SNR \(String(format: "%.2f", -12.75))dB") + .foregroundColor(getSnrColor(snr: -12.75, preset: ModemPresets.longFast)) + .font(.caption2) + Text("RSSI \(-139)dB") + .foregroundColor(getRssiColor(rssi: -139)) + .font(.caption2) } } } @@ -57,13 +58,26 @@ enum LoRaSignalStrength: Int { } } -func getLoRaSignalStrength(snr: Float, rssi: Int32) -> LoRaSignalStrength { +private func getColor(signalStrength: LoRaSignalStrength) -> Color { + switch signalStrength { + case .none: + return Color.red + case .bad: + return Color.orange + case .fair: + return Color.yellow + case .good: + return Color.green + } +} + +func getLoRaSignalStrength(snr: Float, rssi: Int32, preset: ModemPresets) -> LoRaSignalStrength { - if rssi > -115 && snr > -7 { + if rssi > -115 && snr > (preset.snrLimit()) { return .good - } else if rssi < -126 && snr < -15 { + } else if rssi < -126 && snr < (preset.snrLimit() - 7.5) { return .none - } else if rssi <= -120 || snr <= -13 { + } else if rssi <= -120 || snr <= (preset.snrLimit() - 5.5) { return .bad } else { return .fair @@ -86,14 +100,14 @@ func getRssiColor(rssi: Int32) -> Color { } } -func getSnrColor(snr: Float) -> Color { - if snr > -7 { +func getSnrColor(snr: Float, preset: ModemPresets) -> Color { + if snr > preset.snrLimit() { /// Good return .green - } else if snr < -7 && snr > -13 { + } else if snr < preset.snrLimit() && snr > (preset.snrLimit() - 5.5) { /// Fair return .yellow - } else if snr >= -14 { + } else if snr >= (preset.snrLimit() - 7.5) { /// Bad return .orange } else { diff --git a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift index 0c3b95d4..701adb38 100644 --- a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift +++ b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift @@ -47,11 +47,11 @@ struct NodeInfoView: View { Divider() if node.snr != 0 { VStack(alignment: .center) { - let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi) + let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate) LoRaSignalStrengthIndicator(signalStrength: signalStrength) Text("Signal \(signalStrength.description)").font(.title) Text("SNR \(String(format: "%.2f", node.snr))dB") - .foregroundColor(getSnrColor(snr: node.snr)) + .foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate)) .font(.title3) Text("RSSI \(node.rssi)dB") .foregroundColor(getRssiColor(rssi: node.rssi)) @@ -156,11 +156,11 @@ struct NodeInfoView: View { if node.snr != 0 { Divider() VStack(alignment: .center) { - let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi) + let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate) LoRaSignalStrengthIndicator(signalStrength: signalStrength) Text("Signal \(signalStrength.description)").font(.footnote) Text("SNR \(String(format: "%.2f", node.snr))dB") - .foregroundColor(getSnrColor(snr: node.snr)) + .foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate)) .font(.caption2) Text("RSSI \(node.rssi)dB") .foregroundColor(getRssiColor(rssi: node.rssi)) diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 4a242f88..e36b62e5 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -4,9 +4,24 @@ // // Copyright(c) Josh Pirihi & Garth Vander Houwen 1/16/22. +import Foundation import SwiftUI import MapKit +struct PolygonInfo: Codable { + let stroke: String? + let strokeWidth, strokeOpacity: Int? + let fill: String? + let fillOpacity: Double? + let title, subtitle: String? +} + +struct PolylineInfo: Codable { + let stroke: String? + let strokeWidth, strokeOpacity: Int? + let title, subtitle: String? +} + func degreesToRadians(_ number: Double) -> Double { return number * .pi / 180 } @@ -29,7 +44,7 @@ struct MapViewSwiftUI: UIViewRepresentable { let showRouteLines: Bool let mapViewType: MKMapType = MKMapType.standard - + // Offline Map Tiles @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0 @State private var loadedLastUpdatedLocalMapFile = 0 @@ -65,6 +80,7 @@ struct MapViewSwiftUI: UIViewRepresentable { } // Other MKMapView Settings mapView.preferredConfiguration.elevationStyle = .realistic// .flat + mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll mapView.isPitchEnabled = true mapView.isRotateEnabled = true mapView.isScrollEnabled = true @@ -134,14 +150,7 @@ struct MapViewSwiftUI: UIViewRepresentable { } } - func makeUIView(context: Context) -> MKMapView { - currentMapLayer = nil - mapView.delegate = context.coordinator - self.configureMap(mapView: mapView) - return mapView - } - - func updateUIView(_ mapView: MKMapView, context: Context) { + private func setMbtilesOverlay(mapView: MKMapView) { // MBTiles Offline if UserDefaults.enableOfflineMaps && UserDefaults.enableOfflineMapsMBTiles { @@ -149,7 +158,7 @@ struct MapViewSwiftUI: UIViewRepresentable { if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile { mapView.removeOverlays(mapView.overlays) if self.customMapOverlay != nil { - + let fileManager = FileManager.default let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path @@ -169,15 +178,69 @@ struct MapViewSwiftUI: UIViewRepresentable { } } } + } + + private func setGeoJsonOverlay(mapView: MKMapView) { + + guard let geoJsonFileUrl = URL(string: "https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json"), // Bundle.main.url(forResource: "location", withExtension: "geojson"), + //guard let geoJsonFileUrl = URL(string: "https://hrbrmstr.github.io/noaa-alerts-sp-to-geojson/current-all.geojson"), + let geoJsonData = try? Data.init(contentsOf: geoJsonFileUrl) else { + fatalError("Failure to fetch the file.") + } + guard let objs = try? MKGeoJSONDecoder().decode(geoJsonData) as? [MKGeoJSONFeature] else { + fatalError("Wrong format") + } + // Parse the objects + objs.forEach { (feature) in + guard let geometry = feature.geometry.first, + let propData = feature.properties else { + return; + } + // Check if it is MKPolygon + if let polygon = geometry as? MKPolygon { + let polygonInfo = try? JSONDecoder.init().decode(PolygonInfo.self, from: propData) + mapView.addOverlay(polygon) + //self.view?.render(overlay: polygon, info: polygonInfo) + } + // Check if it is MKPolyline + if let polyline = geometry as? MKPolyline { + mapView.addOverlay(polyline, level: .aboveLabels) + //let polylineInfo = try? JSONDecoder.init().decode(PolylineInfo.self, from: propData) + //self.view?.render(overlay: polyline, info: polylineInfo) + } + + // Check if it is MKPointAnnotation + // if let annotation = geometry as? MKPointAnnotation { + // let info = try? JSONDecoder.init().decode(Info.self, from: propData) + // let storeAnnotation = StoreAnnotation.init(title: info?.name, + // subtitle: info?.subTitle, + // website: info?.website, + // coordinate: annotation.coordinate) + // self.view?.setAnnotations(annotations: [storeAnnotation]) + // } + } + } + + func makeUIView(context: Context) -> MKMapView { + currentMapLayer = nil + mapView.delegate = context.coordinator + self.configureMap(mapView: mapView) + return mapView + } + + func updateUIView(_ mapView: MKMapView, context: Context) { + + // Set MBTiles overlay layer + setMbtilesOverlay(mapView: mapView) // Set selected map base layer setMapBaseLayer(mapView: mapView) - // Set map overlay layer + // Set map tile server and weather overlay layers setMapOverlays(mapView: mapView) let latest = positions .filter { $0.latest == true } .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } - + // Node Route Lines if showRouteLines { // Remove all existing PolyLine Overlays @@ -437,7 +500,14 @@ struct MapViewSwiftUI: UIViewRepresentable { renderer.lineWidth = 8 return renderer } - return MKOverlayRenderer() + if let polygon = overlay as? MKPolygon { + let renderer = MKPolygonRenderer(polygon: polygon) + renderer.fillColor = UIColor.purple.withAlphaComponent(0.2) + renderer.strokeColor = .purple.withAlphaComponent(0.7) + + return renderer + } + return MKOverlayRenderer(overlay: overlay) } } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 917cac0a..8e3ad3c5 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -54,7 +54,7 @@ struct NodeDetail: View { var body: some View { let hwModelString = node.user?.hwModel ?? "UNSET" - + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) NavigationStack { GeometryReader { bounds in VStack { @@ -84,19 +84,21 @@ struct NodeDetail: View { VStack(alignment: .leading) { Spacer() HStack(alignment: .bottom, spacing: 1) { - -// Picker("Map Type", selection: $mapType) { -// ForEach(MeshMapTypes.allCases) { map in -// Text(map.description) -// .tag(map.MKMapTypeValue()) -// } -// } -// .onChange(of: (mapType)) { newMapType in -// UserDefaults.mapType = Int(newMapType.rawValue) -// } -// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) -// .pickerStyle(.menu) -// .padding(5) + Picker("Map Type", selection: $selectedMapLayer) { + ForEach(MapLayer.allCases, id: \.self) { layer in + if layer == MapLayer.offline && UserDefaults.enableOfflineMaps { + Text(layer.localized) + } else if layer != MapLayer.offline { + Text(layer.localized) + } + } + } + .onChange(of: (selectedMapLayer)) { newMapLayer in + UserDefaults.mapLayer = newMapLayer + } + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) + .pickerStyle(.menu) + .padding(5) VStack { Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName) .font(.caption) @@ -151,7 +153,7 @@ struct NodeDetail: View { if self.bleManager.connectedPeripheral != nil && node.metadata != nil { HStack { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + if node.metadata?.canShutdown ?? false { Button(action: { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index f04291e2..d4a8aa87 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -27,6 +27,8 @@ struct NodeList: View { var body: some View { NavigationSplitView { + let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) + let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) List(nodes, id: \.self, selection: $selection) { node in if nodes.count == 0 { Text("no.nodes").font(.title) @@ -42,7 +44,7 @@ struct NodeList: View { .fontWeight(.medium) .font(.callout) if connected { - HStack { + HStack(alignment: .bottom) { Image(systemName: "repeat.circle.fill") .font(.callout) .symbolRenderingMode(.hierarchical) @@ -80,6 +82,11 @@ struct NodeList: View { LastHeardText(lastHeard: node.lastHeard) .font(.caption) } + if !connected { + HStack(alignment: .bottom) { let preset = ModemPresets(rawValue: Int(connectedNode?.loRaConfig?.modemPreset ?? 0)) + LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) + } + } } .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 9ebe55ee..95a2aecd 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -178,14 +178,14 @@ struct NodeMap: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .onChange(of: (enableOfflineMaps)) { newEnableOfflineMaps in - UserDefaults.enableOfflineMaps = newEnableOfflineMaps - if !newEnableOfflineMaps { + UserDefaults.enableOfflineMaps = enableOfflineMaps + if !enableOfflineMaps { if self.selectedMapLayer == .offline { self.selectedMapLayer = .standard } } } - if UserDefaults.enableOfflineMaps { + if enableOfflineMaps { VStack (alignment: .leading) { if !enableOfflineMapsMBTiles { diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 0dc8e05d..55b6b423 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -262,54 +262,42 @@ struct DeviceConfig: View { } } .onChange(of: deviceRole) { newRole in - - if node != nil && node!.deviceConfig != nil { - + if node != nil && node?.deviceConfig != nil { if newRole != node!.deviceConfig!.role { hasChanges = true } } } .onChange(of: serialEnabled) { newSerial in - - if node != nil && node!.deviceConfig != nil { - + if node != nil && node?.deviceConfig != nil { if newSerial != node!.deviceConfig!.serialEnabled { hasChanges = true } } } .onChange(of: debugLogEnabled) { newDebugLog in - - if node != nil && node!.deviceConfig != nil { - + if node != nil && node?.deviceConfig != nil { if newDebugLog != node!.deviceConfig!.debugLogEnabled { hasChanges = true } } } .onChange(of: buttonGPIO) { newButtonGPIO in - - if node != nil && node!.deviceConfig != nil { - + if node != nil && node?.deviceConfig != nil { if newButtonGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true } } } .onChange(of: buzzerGPIO) { newBuzzerGPIO in - - if node != nil && node!.deviceConfig != nil { - + if node != nil && node?.deviceConfig != nil { if newBuzzerGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true } } } .onChange(of: rebroadcastMode) { newRebroadcastMode in - - if node != nil && node!.deviceConfig != nil { - + if node != nil && node?.deviceConfig != nil { if newRebroadcastMode != node!.deviceConfig!.rebroadcastMode { hasChanges = true } } } .onChange(of: doubleTapAsButtonPress) { newDoubleTapAsButtonPress in - if node != nil && node!.deviceConfig != nil { + if node != nil && node?.deviceConfig != nil { if newDoubleTapAsButtonPress != node!.deviceConfig!.doubleTapAsButtonPress { hasChanges = true } } } .onChange(of: isManaged) { newIsManaged in - if node != nil && node!.deviceConfig != nil { + if node != nil && node?.deviceConfig != nil { if newIsManaged != node!.deviceConfig!.isManaged { hasChanges = true } } }