diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 49095803..aec6aa6a 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; + DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLESignalStrengthIndicator.swift; sourceTree = ""; }; DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV5.xcdatamodel; sourceTree = ""; }; DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = ""; }; DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = ""; }; @@ -293,6 +294,7 @@ DDB75A152A0594AD006ED576 /* TileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileOverlay.swift; sourceTree = ""; }; DDB75A192A05EB67006ED576 /* alpha.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = alpha.png; sourceTree = ""; }; DDB75A1B2A076DFA006ED576 /* TilesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TilesView.swift; sourceTree = ""; }; + DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrengthIndicator.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 = ""; }; @@ -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 = ""; @@ -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 */, diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index e882fd08..d805bdfd 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -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) } } } diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index d8ee5e28..94764f97 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -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") diff --git a/Meshtastic/Helpers/Map/OfflineTileManager.swift b/Meshtastic/Helpers/Map/OfflineTileManager.swift index 7a5e7e4b..4c8e5dba 100644 --- a/Meshtastic/Helpers/Map/OfflineTileManager.swift +++ b/Meshtastic/Helpers/Map/OfflineTileManager.swift @@ -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) } diff --git a/Meshtastic/Model/PeripheralModel.swift b/Meshtastic/Model/PeripheralModel.swift index 1b71e9e7..61cbfcca 100644 --- a/Meshtastic/Model/PeripheralModel.swift +++ b/Meshtastic/Model/PeripheralModel.swift @@ -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 } } } diff --git a/Meshtastic/Views/Helpers/SignalStrengthIndicator.swift b/Meshtastic/Views/Helpers/BLESignalStrengthIndicator.swift similarity index 95% rename from Meshtastic/Views/Helpers/SignalStrengthIndicator.swift rename to Meshtastic/Views/Helpers/BLESignalStrengthIndicator.swift index afb84f43..c5d17f16 100644 --- a/Meshtastic/Views/Helpers/SignalStrengthIndicator.swift +++ b/Meshtastic/Views/Helpers/BLESignalStrengthIndicator.swift @@ -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 diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift b/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift new file mode 100644 index 00000000..56357168 --- /dev/null +++ b/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift @@ -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 + } +} diff --git a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift index 48cc8515..255d6788 100644 --- a/Meshtastic/Views/Helpers/Node/NodeInfoView.swift +++ b/Meshtastic/Views/Helpers/Node/NodeInfoView.swift @@ -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 { diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 8a365612..1bfaa379 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -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) } diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index caf7cced..72cfc3de 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -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 { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 511136d2..45ffb00f 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -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) diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 160063c0..f271c24d 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -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) } }