mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Signal strength indicator updates
This commit is contained in:
parent
377eeea177
commit
8382ad962a
10 changed files with 265 additions and 79 deletions
|
|
@ -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 = "<group>"; };
|
||||
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrengthIndicator.swift; sourceTree = "<group>"; };
|
||||
DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV13.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrength.swift; sourceTree = "<group>"; };
|
||||
DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV8.xcdatamodel; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
|
|
@ -694,6 +696,7 @@
|
|||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */,
|
||||
DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */,
|
||||
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */,
|
||||
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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/)."
|
||||
}
|
||||
|
|
|
|||
72
Meshtastic/Views/Helpers/LoRaSignalStrength.swift
Normal file
72
Meshtastic/Views/Helpers/LoRaSignalStrength.swift
Normal file
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue