More tile sources

tile server attribution
Signal strength indicator
This commit is contained in:
Garth Vander Houwen 2023-05-09 19:31:25 -07:00
parent a6ca91d640
commit c425486658
12 changed files with 209 additions and 94 deletions

View file

@ -30,7 +30,7 @@
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; };
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582928585C32009B0E59 /* RangeTestConfig.swift */; };
DD41A61529AB0035003C5A37 /* NodeWeatherForecast.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */; };
DD457188293C7E63000C49FB /* SignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */; };
DD457188293C7E63000C49FB /* BLESignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */; };
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeList.swift */; };
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D526F17ED900029299 /* CircleText.swift */; };
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911D2708C65400501B7E /* AppSettings.swift */; };
@ -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 */; };
DDB75A1C2A076DFA006ED576 /* TilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A1B2A076DFA006ED576 /* TilesView.swift */; };
DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.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 */; };
@ -214,7 +215,7 @@
DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeWeatherForecast.swift; sourceTree = "<group>"; };
DD41A61C29AE7E8E003C5A37 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
DD41A61E29AE7E8F003C5A37 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalStrengthIndicator.swift; sourceTree = "<group>"; };
DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLESignalStrengthIndicator.swift; sourceTree = "<group>"; };
DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV5.xcdatamodel; sourceTree = "<group>"; };
DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = "<group>"; };
DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = "<group>"; };
@ -293,6 +294,7 @@
DDB75A152A0594AD006ED576 /* TileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileOverlay.swift; sourceTree = "<group>"; };
DDB75A192A05EB67006ED576 /* alpha.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = alpha.png; sourceTree = "<group>"; };
DDB75A1B2A076DFA006ED576 /* TilesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TilesView.swift; sourceTree = "<group>"; };
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrengthIndicator.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>"; };
@ -692,7 +694,8 @@
DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */,
DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */,
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */,
DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */,
DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */,
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -967,7 +970,7 @@
files = (
DDDB444829F8A9C900EE2349 /* String.swift in Sources */,
DD5E520C298EE33B00D21B61 /* portnums.pb.swift in Sources */,
DD457188293C7E63000C49FB /* SignalStrengthIndicator.swift in Sources */,
DD457188293C7E63000C49FB /* BLESignalStrengthIndicator.swift in Sources */,
DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */,
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */,
DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */,
@ -1057,6 +1060,7 @@
DDDB445029F8AC9C00EE2349 /* UIImage.swift in Sources */,
DD86D40F2881BE4C00BAEB7A /* CsvDocument.swift in Sources */,
DDB75A142A0593E2006ED576 /* OfflineTileManager.swift in Sources */,
DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */,
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */,
DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */,
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */,

View file

@ -139,51 +139,94 @@ enum MapLayer: String, CaseIterable, Equatable {
enum MapTileServerLinks: String, CaseIterable, Identifiable {
case openStreetMaps
case openStreetMap
case openStreetMapDE
case openStreetMapFR
case openCycleMap
case openStreetMapHot
case openTopoMap
case usgsTopo
case usgsImageryTopo
case usgsImageryOnly
case toner
case watercolor
var id: String { self.rawValue }
var attribution: String {
switch self {
case .openStreetMaps:
return "OpenStreetMap is a map of the world, created by people like you and free to use under an open license. &copy; [OpenStreetMap](http://osm.org/copyright) contributors"
case .openStreetMap:
return "Map and data © [OpenStreetMap](http://www.openstreetmap.org) and contributors, [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.0/)"
case .openStreetMapDE:
return "[OpenStreetMap DE](https://openstreetmap.de) map and data © [OpenStreetMap](http://www.openstreetmap.org) and contributors, [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.0/)"
case .openStreetMapFR:
return "[OpenStreetMap FR](https://www.openstreetmap.fr) map and data © [OpenStreetMap](http://www.openstreetmap.org) and contributors, [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.0/)"
case .openCycleMap:
return "[OpenCycleMap](https://www.cyclosm.org) map and data © [OpenStreetMap](http://www.openstreetmap.org) and contributors, [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.0/)"
case .openTopoMap:
return "[OpenTopoMap](https://opentopomap.org) map and data © [OpenStreetMap](http://www.openstreetmap.org) and contributors, [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.0/)"
case .openStreetMapHot:
return "[OpenStreetMap FR](https://www.openstreetmap.fr) map and data © [OpenStreetMap](http://www.openstreetmap.org) and contributors, [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.0/)"
case .usgsTopo:
return "[USGS Topo](https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer) is a tile cache base map service that combines the most current data in The National Map (TNM), and other public-domain data, into a multi-scale topographic reference map."
return "[USGS](https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer) [National Map](http://nationalmap.gov/) topographic overlay."
case .usgsImageryTopo:
return "[USGS Imagery Topo](https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer) is a tile cache base map of orthoimagery in The National Map and US Topo vector data."
return "[USGS](https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer) [National Map](http://nationalmap.gov/) imagery and topographic overlay."
case .usgsImageryOnly:
return "[USGS Imagery Only](https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer) is a tile cache base map service of orthoimagery in The National Map."
return "[USGS](https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer) [National Map](http://nationalmap.gov/) imagery only overlay."
case .toner:
return "[Stamen Design's](https://github.com/stamen/toner-carto) black and white map tiles."
case .watercolor:
return "Cooper Hewitt, Smithsonian Design Museum's [Watercolor Maptiles](https://watercolormaps.collection.cooperhewitt.org/) is a open-source mapping tool created by Stamen Design and built on OpenStreetMap data."
return "Cooper Hewitt, Smithsonian Design Museum's [Watercolor Maptiles](https://watercolormaps.collection.cooperhewitt.org/) is a open-source mapping tool created by Stamen Design and built on [OpenStreetMap](http://www.openstreetmap.org) data."
}
}
var description: String {
switch self {
case .openStreetMaps:
return "Open Street Maps"
case .openStreetMap:
return "Open Street Map"
case .openStreetMapDE:
return "Open Street Map DE"
case .openStreetMapFR:
return "Open Street Map FR"
case .openCycleMap:
return "Open Cycle Map"
case .openStreetMapHot:
return "Humanitarian (OSM)"
case.openTopoMap:
return "Open Topo Map"
case .usgsTopo:
return "USGS Topographic"
case .usgsImageryTopo:
return "USGS Topo Imagery"
case .usgsImageryOnly:
return "USGS Imagery Only"
case .toner:
return "Toner"
case .watercolor:
return "Watercolor Maptiles"
}
}
var tileUrl: String {
switch self {
case .openStreetMaps:
case .openStreetMap:
return "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
case .openStreetMapDE:
return "https://tile.openstreetmap.de/{z}/{x}/{y}.png"
case .openStreetMapFR:
return "https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"
case .openCycleMap:
return "https://c.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png"
case .openStreetMapHot:
return "https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"
case .openTopoMap:
return "https://a.tile.opentopomap.org/{z}/{x}/{y}.png"
case .usgsTopo:
return "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}"
case .usgsImageryTopo:
return "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer/tile/{z}/{y}/{x}"
case .usgsImageryOnly:
return "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}"
case .toner:
return "https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png"
case .watercolor:
return "https://watercolormaps.collection.cooperhewitt.org/tile/watercolor/{z}/{x}/{y}.jpg"
@ -191,16 +234,28 @@ enum MapTileServerLinks: String, CaseIterable, Identifiable {
}
var zoomRange: [Int] {
switch self {
case .openStreetMaps:
return [Int](0...17)
case .openStreetMap:
return [Int](0...18)
case .openStreetMapDE:
return [Int](0...18)
case .openStreetMapFR:
return [Int](0...18)
case .openCycleMap:
return [Int](0...18)
case .openTopoMap:
return [Int](0...18)
case .openStreetMapHot:
return [Int](0...18)
case .usgsTopo:
return [Int](0...17)
return [Int](6...15)
case .usgsImageryTopo:
return [Int](0...17)
return [Int](6...15)
case .usgsImageryOnly:
return [Int](0...17)
return [Int](6...15)
case .toner:
return [Int](0...18)
case .watercolor:
return [Int](0...17)
return [Int](0...18)
}
}
}

View file

@ -118,7 +118,7 @@ extension UserDefaults {
static var mapTileServer: MapTileServerLinks {
get {
MapTileServerLinks(rawValue: UserDefaults.standard.string(forKey: "mapTileServer") ?? MapTileServerLinks.openStreetMaps.rawValue) ?? MapTileServerLinks.openStreetMaps
MapTileServerLinks(rawValue: UserDefaults.standard.string(forKey: "mapTileServer") ?? MapTileServerLinks.openStreetMap.rawValue) ?? MapTileServerLinks.openStreetMap
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: "mapTileServer")

View file

@ -22,7 +22,7 @@ class OfflineTileManager: ObservableObject {
}
// MARK: - Private properties
private var overlay: MKTileOverlay { MKTileOverlay(urlTemplate: UserDefaults.mapTileServer.tileUrl.count > 1 ? UserDefaults.mapTileServer.tileUrl : MapTileServerLinks.openStreetMaps.tileUrl) }
private var overlay: MKTileOverlay { MKTileOverlay(urlTemplate: UserDefaults.mapTileServer.tileUrl.count > 1 ? UserDefaults.mapTileServer.tileUrl : MapTileServerLinks.openStreetMap.tileUrl) }
private var documentsDirectory: URL { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! }
@ -52,7 +52,7 @@ class OfflineTileManager: ObservableObject {
let paths = self.computeTileOverlayPaths(boundingBox: boundingBox)
var accumulatedSize: UInt64 = 0
for path in paths {
let file = "tiles/z\(path.z)x\(path.x)y\(path.y).png"
let file = "tiles/\(UserDefaults.mapTileServer.id)-z\(path.z)x\(path.x)y\(path.y).png"
let url = documentsDirectory.appendingPathComponent(file)
accumulatedSize += (try? url.regularFileAllocatedSize()) ?? 0
}
@ -67,7 +67,7 @@ class OfflineTileManager: ObservableObject {
func remove(for boundingBox: MKMapRect) {
let paths = self.computeTileOverlayPaths(boundingBox: boundingBox)
for path in paths {
let file = "tiles/z\(path.z)x\(path.x)y\(path.y).png"
let file = "tiles/\(UserDefaults.mapTileServer.id)-z\(path.z)x\(path.x)y\(path.y).png"
let url = documentsDirectory.appendingPathComponent(file)
try? fileManager.removeItem(at: url)
}
@ -94,7 +94,7 @@ class OfflineTileManager: ObservableObject {
}
func getTileOverlay(for path: MKTileOverlayPath) -> URL {
let file = "z\(path.z)x\(path.x)y\(path.y).png"
let file = "\(UserDefaults.mapTileServer.id)-z\(path.z)x\(path.x)y\(path.y).png"
// Check is tile is already available
let tilesUrl = documentsDirectory.appendingPathComponent("tiles").appendingPathComponent(file)
if fileManager.fileExists(atPath: tilesUrl.path){
@ -134,7 +134,7 @@ class OfflineTileManager: ObservableObject {
@discardableResult private func persistLocally(path: MKTileOverlayPath) -> URL {
let url = overlay.url(forTilePath: path)
let file = "tiles/z\(path.z)x\(path.x)y\(path.y).png"
let file = "tiles/\(UserDefaults.mapTileServer.id)-z\(path.z)x\(path.x)y\(path.y).png"
let filename = documentsDirectory.appendingPathComponent(file)
do {
let data = try Data(contentsOf: url)
@ -147,7 +147,7 @@ class OfflineTileManager: ObservableObject {
private func filterTilesAlreadyExisting(paths: [MKTileOverlayPath]) -> [MKTileOverlayPath] {
paths.filter {
let file = "z\($0.z)x\($0.x)y\($0.y).png"
let file = "\(UserDefaults.mapTileServer.id)-z\($0.z)x\($0.x)y\($0.y).png"
let tilesPath = documentsDirectory.appendingPathComponent("tiles").appendingPathComponent(file).path
return !fileManager.fileExists(atPath: tilesPath)
}

View file

@ -24,13 +24,13 @@ struct Peripheral: Identifiable {
self.peripheral = peripheral
}
func getSignalStrength() -> SignalStrength {
func getSignalStrength() -> BLESignalStrength {
if NSNumber(value: rssi).compare(NSNumber(-65)) == ComparisonResult.orderedDescending {
return SignalStrength.strong
return BLESignalStrength.strong
} else if NSNumber(value: rssi).compare(NSNumber(-85)) == ComparisonResult.orderedDescending {
return SignalStrength.normal
return BLESignalStrength.normal
} else {
return SignalStrength.weak
return BLESignalStrength.weak
}
}
}

View file

@ -32,7 +32,7 @@ import Foundation
import SwiftUI
struct SignalStrengthIndicator: View {
let signalStrength: SignalStrength
let signalStrength: BLESignalStrength
var body: some View {
HStack {
@ -40,7 +40,7 @@ struct SignalStrengthIndicator: View {
RoundedRectangle(cornerRadius: 3)
.divided(amount: (CGFloat(bar) + 1) / CGFloat(3))
.fill(getColor().opacity(bar <= signalStrength.rawValue ? 1 : 0.3))
.frame(width: 8, height: 30)
.frame(width: 8, height: 40)
}
}
}
@ -71,7 +71,7 @@ extension Shape {
}
}
enum SignalStrength: Int {
enum BLESignalStrength: Int {
case weak = 0
case normal = 1
case strong = 2

View file

@ -0,0 +1,71 @@
//
// LoRaSignalStrengthIndicator.swift
// Meshtastic
//
// Copyright Garth Vander Houwen 5/9/23.
//
import Foundation
import Foundation
import SwiftUI
struct LoRaSignalStrengthIndicator: View {
let signalStrength: LoRaSignalStrength
var body: some View {
HStack {
ForEach(0..<3) { bar in
RoundedRectangle(cornerRadius: 3)
.divided(amount: (CGFloat(bar) + 1) / CGFloat(3))
.fill(getColor().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
}
}
}
enum LoRaSignalStrength: Int {
case none = 0
case bad = 1
case fair = 2
case good = 3
var description: String {
switch self {
case .none:
return "None"
case .bad:
return "Bad"
case .fair:
return "Fair"
case .good:
return "Good"
}
}
}
func getLoRaSignalStrength(snr: Float, rssi: Int32) -> LoRaSignalStrength {
if rssi > -115 && snr > -7 {
return .good
} else if rssi < -126 && snr < -15 {
return .none
} else if rssi <= -120 || snr <= -13 {
return .bad
} else {
return .fair
}
}

View file

@ -28,7 +28,7 @@ struct NodeInfoView: View {
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
HStack {
VStack(alignment: .center) {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 75, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 48 : 24, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white )
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 150, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 105 : 55, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white )
}
Divider()
VStack {
@ -44,40 +44,25 @@ struct NodeInfoView: View {
.font(.title).fixedSize()
}
}
if node.snr > 0 {
Divider()
Divider()
if node.snr != 0 {
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
.padding(.bottom, 10)
Text("SNR").font(.largeTitle).fixedSize()
Text("\(String(format: "%.2f", node.snr)) dB")
.font(.largeTitle)
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi)
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
Text("Signal \(signalStrength.description)").font(.title)
Text("SNR \(String(format: "%.2f", node.snr))dB")
.foregroundColor(.gray)
.fixedSize()
if (node.rssi > -115) && (node.snr <= -13) {
Image(systemName: "waveform.slash")
.font(.title)
.foregroundColor(.orange)
.symbolRenderingMode(.hierarchical)
Text("Noisy")
.font(.title2)
.foregroundColor(.orange)
.multilineTextAlignment(.center)
}
.font(.title3)
Text("RSSI \(node.rssi)dB")
.foregroundColor(.gray)
.font(.title3)
}
Divider()
}
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
if deviceMetrics?.count ?? 0 >= 1 {
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
Divider()
VStack(alignment: .center) {
BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0))
if mostRecent?.voltage ?? 0 > 0.0 {
@ -154,7 +139,7 @@ struct NodeInfoView: View {
HStack {
VStack(alignment: .center) {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 40 : 24, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white )
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 42 : 20, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white )
}
Divider()
VStack {
@ -167,39 +152,25 @@ struct NodeInfoView: View {
.font(.caption).fixedSize()
}
}
if node.snr > 0 {
Divider()
Divider()
if node.snr != 0 {
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("SNR").font(.title2).fixedSize()
Text("\(String(format: "%.2f", node.snr)) dB")
.font(.title2)
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi)
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
Text("Signal \(signalStrength.description)").font(.footnote)
Text("SNR \(String(format: "%.2f", node.snr))dB")
.foregroundColor(.gray)
.fixedSize()
if (node.rssi > -115) && (node.snr <= -13) {
Image(systemName: "waveform.slash")
.font(.callout)
.foregroundColor(.orange)
.symbolRenderingMode(.hierarchical)
Text("Noisy")
.font(.caption)
.multilineTextAlignment(.center)
.foregroundColor(.orange)
}
.font(.caption2)
Text("RSSI \(node.rssi)dB")
.foregroundColor(.gray)
.font(.caption2)
}
Divider()
}
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
if deviceMetrics?.count ?? 0 >= 1 {
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
Divider()
VStack(alignment: .center) {
BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0))
if mostRecent?.voltage ?? 0 > 0 {

View file

@ -203,10 +203,20 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
if userTrackingMode == MKUserTrackingMode.none {
mapView.showsUserLocation = false
if UserDefaults.enableMapRecentering {
if latest.count == 1 {
mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true)
} else {
mapView.fitAllAnnotations()
}
}
} else {
mapView.showsUserLocation = true
}
mapView.fitAllAnnotations()
mapView.setUserTrackingMode(userTrackingMode, animated: true)
}

View file

@ -148,7 +148,7 @@ struct Contacts: View {
HStack {
VStack {
HStack {
CircleText(text: user.shortName ?? "???", color: Color(UIColor(hex: UInt32(user.num))), circleSize: 60, fontSize: (user.shortName ?? "???").isEmoji() ? 36 : 18, textColor: UIColor(hex: UInt32(user.num)).isLight() ? .black : .white)
CircleText(text: user.shortName ?? "???", color: Color(UIColor(hex: UInt32(user.num))), circleSize: 60, fontSize: (user.shortName ?? "???").isEmoji() ? 42 : 20, textColor: UIColor(hex: UInt32(user.num)).isLight() ? .black : .white)
.padding(.trailing, 5)
VStack {
HStack {

View file

@ -35,7 +35,7 @@ struct NodeList: View {
let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num)
VStack(alignment: .leading) {
HStack {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 40 : 20, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white)
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 44 : 22, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white)
.padding(.trailing, 5)
VStack(alignment: .leading) {
Text(node.user?.longName ?? "unknown".localized).font(.headline)

View file

@ -153,7 +153,7 @@ struct NodeMap: View {
}
}
if UserDefaults.enableOfflineMaps {
VStack {
VStack (alignment: .leading) {
if !enableOfflineMapsMBTiles {
@ -166,12 +166,16 @@ struct NodeMap: View {
.pickerStyle(DefaultPickerStyle())
.onChange(of: (selectedTileServer)) { newSelectedTileServer in
UserDefaults.mapTileServer = newSelectedTileServer
tileManager.removeAll()
//tileManager.removeAll()
selectedMapLayer = .standard
}
Text("Attribution:")
.fontWeight(.semibold)
.font(.footnote)
Text(LocalizedStringKey(selectedTileServer.attribution))
.font(.caption)
.font(.footnote)
.foregroundColor(.gray)
.padding(0)
Divider()
Toggle(isOn: $mapTilesAboveLabels) {
Text("Tiles above Labels")
@ -193,7 +197,7 @@ struct NodeMap: View {
UserDefaults.enableOfflineMapsMBTiles = self.enableOfflineMapsMBTiles
}
Text("The latest MBTiles file shared with meshtastic will be loaded into the map.")
.font(.caption)
.font(.footnote)
.foregroundColor(.gray)
}
}