mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
More tile sources
tile server attribution Signal strength indicator
This commit is contained in:
parent
a6ca91d640
commit
c425486658
12 changed files with 209 additions and 94 deletions
|
|
@ -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 */,
|
||||
|
|
|
|||
|
|
@ -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. © [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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
71
Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift
Normal file
71
Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue