From e1bf4b021283711cdd1fa1de836690e6439cb716 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 6 May 2023 16:15:12 -0700 Subject: [PATCH] Somewhat working offline maps --- Meshtastic.xcodeproj/project.pbxproj | 8 +- .../alpha.imageset/Contents.json | 21 --- Meshtastic/Enums/AppSettingsEnums.swift | 33 ++-- Meshtastic/Extensions/UserDefaults.swift | 21 ++- .../Helpers/Map/OfflineTileManager.swift | 10 +- .../Helpers/Map/TilesDownloadView.swift | 87 ---------- .../alpha.imageset => Resources}/alpha.png | Bin .../Views/Map/Custom/MapViewSwiftUI.swift | 152 +++++++++++------- Meshtastic/Views/Nodes/NodeDetail.swift | 34 ++-- Meshtastic/Views/Nodes/NodeList.swift | 4 +- Meshtastic/Views/Nodes/NodeMap.swift | 40 ++--- de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 1 + zh-Hans.lproj/Localizable.strings | 1 + 14 files changed, 183 insertions(+), 230 deletions(-) delete mode 100644 Meshtastic/Assets.xcassets/alpha.imageset/Contents.json delete mode 100644 Meshtastic/Helpers/Map/TilesDownloadView.swift rename Meshtastic/{Assets.xcassets/alpha.imageset => Resources}/alpha.png (100%) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6de0398b..0d8f2191 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -102,7 +102,7 @@ DDB75A112A059258006ED576 /* Url.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A102A059258006ED576 /* Url.swift */; }; DDB75A142A0593E2006ED576 /* OfflineTileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A132A0593E2006ED576 /* OfflineTileManager.swift */; }; DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A152A0594AD006ED576 /* TileOverlay.swift */; }; - DDB75A182A05975A006ED576 /* TilesDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A172A05975A006ED576 /* TilesDownloadView.swift */; }; + DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */ = {isa = PBXBuildFile; fileRef = DDB75A192A05EB67006ED576 /* alpha.png */; }; 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 */; }; @@ -290,7 +290,7 @@ DDB75A102A059258006ED576 /* Url.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Url.swift; sourceTree = ""; }; DDB75A132A0593E2006ED576 /* OfflineTileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineTileManager.swift; sourceTree = ""; }; DDB75A152A0594AD006ED576 /* TileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileOverlay.swift; sourceTree = ""; }; - DDB75A172A05975A006ED576 /* TilesDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TilesDownloadView.swift; sourceTree = ""; }; + DDB75A192A05EB67006ED576 /* alpha.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = alpha.png; 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 = ""; }; @@ -559,7 +559,6 @@ children = ( DDB75A132A0593E2006ED576 /* OfflineTileManager.swift */, DDB75A152A0594AD006ED576 /* TileOverlay.swift */, - DDB75A172A05975A006ED576 /* TilesDownloadView.swift */, ); path = Map; sourceTree = ""; @@ -661,6 +660,7 @@ DDC2E18926CE24F70042C5E4 /* Resources */ = { isa = PBXGroup; children = ( + DDB75A192A05EB67006ED576 /* alpha.png */, DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */, ); path = Resources; @@ -908,6 +908,7 @@ DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */, DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */, DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */, + DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */, DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -971,7 +972,6 @@ DD3501892852FC3B000FC853 /* Settings.swift in Sources */, DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */, DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */, - DDB75A182A05975A006ED576 /* TilesDownloadView.swift in Sources */, DD5E5203298EE33B00D21B61 /* config.pb.swift in Sources */, DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */, DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */, diff --git a/Meshtastic/Assets.xcassets/alpha.imageset/Contents.json b/Meshtastic/Assets.xcassets/alpha.imageset/Contents.json deleted file mode 100644 index eb9d7ea9..00000000 --- a/Meshtastic/Assets.xcassets/alpha.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "alpha.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index 9ed76137..e176cee1 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -128,6 +128,27 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable { } } } + +enum MapLayer: String, CaseIterable, Equatable { + case standard + case hybrid + case satellite + case offline + var localized: String { self.rawValue.localized } + var zoomRange: [Int] { + switch self { + case .standard: + return [Int](0...24) + case .hybrid: + return [Int](0...24) + case .satellite: + return [Int](0...24) + case .offline: + return [Int](0...17) + } + } +} + enum MapTileServerLinks: Int, CaseIterable, Identifiable { case none = 0 @@ -159,16 +180,4 @@ enum MapTileServerLinks: Int, CaseIterable, Identifiable { return "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}" } } - var zoomRange: [Int] { - switch self { - case .none: - return [Int](0...1) - case .wikimedia: - return [Int](0...24) - case .openStreetMaps: - return [Int](0...24) - case .nationalMap: - return [Int](0...24) - } - } } diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index fdfdb575..179d76c8 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -15,12 +15,12 @@ extension UserDefaults { case preferredPeripheralId case provideLocation case provideLocationInterval - case meshMapType - case meshMapCenteringMode + //case meshMapType case meshMapRecentering - case meshMapCustomTileServer case meshMapShowNodeHistory case meshMapShowRouteLines + case enableOfflineMaps + case mapTileServer } func reset() { @@ -74,12 +74,21 @@ extension UserDefaults { } } - static var mapType: Int { +// static var mapType: Int { +// get { +// UserDefaults.standard.integer(forKey: "meshMapType") +// } +// set { +// UserDefaults.standard.set(newValue, forKey: "meshMapType") +// } +// } + + static var mapLayer: MapLayer { get { - UserDefaults.standard.integer(forKey: "meshMapType") + MapLayer(rawValue: UserDefaults.standard.string(forKey: "mapLayer") ?? MapLayer.standard.rawValue) ?? MapLayer.standard } set { - UserDefaults.standard.set(newValue, forKey: "meshMapType") + UserDefaults.standard.set(newValue.rawValue, forKey: "mapLayer") } } diff --git a/Meshtastic/Helpers/Map/OfflineTileManager.swift b/Meshtastic/Helpers/Map/OfflineTileManager.swift index c6084e02..4dcbe5ca 100644 --- a/Meshtastic/Helpers/Map/OfflineTileManager.swift +++ b/Meshtastic/Helpers/Map/OfflineTileManager.swift @@ -22,15 +22,15 @@ class OfflineTileManager: ObservableObject { } // MARK: - Private properties - private var overlay: MKTileOverlay { MKTileOverlay(urlTemplate: UserDefaults.mapTileServer) } + private var overlay: MKTileOverlay { MKTileOverlay(urlTemplate: UserDefaults.mapTileServer.count > 1 ? UserDefaults.mapTileServer : "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png") } private var documentsDirectory: URL { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! } private let fileManager = FileManager.default // MARK: - Public property - @Published var progress: Float = 0 - @Published var status: DownloadStatus = .download + var progress: Float = 0 + var status: DownloadStatus = .download // MARK: - Public methods func getAllDownloadedSize() -> String { @@ -100,7 +100,7 @@ class OfflineTileManager: ObservableObject { if fileManager.fileExists(atPath: tilesUrl.path){ return tilesUrl } else { - if !UserDefaults.enableOfflineMaps { // Get and persist newTile + if UserDefaults.enableOfflineMaps { // Get and persist newTile return persistLocally(path: path) } else { // Else display empty tile (transparent over Maps tiles) return Bundle.main.url(forResource: "alpha", withExtension: "png")! @@ -140,7 +140,7 @@ class OfflineTileManager: ObservableObject { let data = try Data(contentsOf: url) try data.write(to: filename) } catch { - print("❤️ PersistLocallyError = \(error)") + print("💀 Save Tile Error = \(error)") } return url } diff --git a/Meshtastic/Helpers/Map/TilesDownloadView.swift b/Meshtastic/Helpers/Map/TilesDownloadView.swift deleted file mode 100644 index 2fc438d0..00000000 --- a/Meshtastic/Helpers/Map/TilesDownloadView.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// TilesDownloadView.swift -// Meshtastic -// -// Copyright © Garth Vander Houwen 5/5/23. -// - -import SwiftUI -import MapKit - -struct TilesDownloadView: View { - - @ObservedObject var tileManager = OfflineTileManager.shared - @State private var showAlert = false - @State var otherDownloadInProgress = false - - var boundingBox: MKMapRect - var name: String - - - var body: some View { - - Button(action: { - if self.tileManager.status == .download { - //Feedback.selected() - self.tileManager.download(boundingBox: self.boundingBox, name: self.name) - } else if self.tileManager.status == .downloaded { - //Feedback.selected() - self.showAlert = true - } - }) { - HStack() { - if tileManager.status == .downloaded { - Image(systemName: "trash") - .accentColor(.red) - } else { - Image(systemName: "map") - } - - VStack(alignment: .leading) { - if tileManager.status == .download { - Text("\("map.tiles.download".localized) (\(tileManager.getEstimatedDownloadSize(for: boundingBox).toBytes))") - } else if tileManager.status == .downloading { - Text("\("map.tiles.downloading".localized) (\(tileManager.getEstimatedDownloadSize(for: boundingBox).toBytes) \("Left".localized))") - } else { - Text("\("map.tiles.delete".localized) (\(tileManager.getDownloadedSize(for: boundingBox).toBytes))") - .accentColor(.red) - } - if tileManager.status == .downloading { - ProgressView(value: tileManager.progress) - .frame(height: 10) - } - } - Spacer() - } - //.isHidden(otherDownloadInProgress, remove: true) - } - .onAppear { - guard self.tileManager.status != .downloading else { - self.otherDownloadInProgress = true - return - } - self.tileManager.status = self.tileManager.hasBeenDownloaded(for: self.boundingBox) ? .downloaded : .download - } - .actionSheet(isPresented: $showAlert) { - ActionSheet( - title: Text("\("Delete".localized) (\(self.tileManager.getDownloadedSize(for: boundingBox).toBytes))"), - message: Text("DeleteTiles".localized), - buttons: [ - .destructive(Text("Delete".localized), action: { self.tileManager.remove(for: self.boundingBox) }), - .cancel(Text("Cancel".localized)) - ] - ) - } - } -} - -// MARK: Previews -struct TilesRow_Previews: PreviewProvider { - - static var previews: some View { - - TilesDownloadView(boundingBox: MKMapRect(), name: "test") - .previewLayout(.fixed(width: 300, height: 80)) - .environment(\.colorScheme, .light) - } -} diff --git a/Meshtastic/Assets.xcassets/alpha.imageset/alpha.png b/Meshtastic/Resources/alpha.png similarity index 100% rename from Meshtastic/Assets.xcassets/alpha.imageset/alpha.png rename to Meshtastic/Resources/alpha.png diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 373883c5..61ed0753 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -10,29 +10,30 @@ import MapKit func degreesToRadians(_ number: Double) -> Double { return number * .pi / 180 } +var currentMapLayer: MapLayer? struct MapViewSwiftUI: UIViewRepresentable { var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void var onWaypointEdit: (_ waypointId: Int ) -> Void - @Binding var visibleMapRect: MKMapRect + let mapView = MKMapView() // Parameters + var selectedMapLayer: MapLayer let positions: [PositionEntity] let waypoints: [WaypointEntity] - let mapViewType: MKMapType + let userTrackingMode: MKUserTrackingMode let showNodeHistory: Bool let showRouteLines: Bool + + let mapViewType: MKMapType = MKMapType.standard // Offline Map Tiles @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0 @State private var loadedLastUpdatedLocalMapFile = 0 var customMapOverlay: CustomMapOverlay? @State private var presentCustomMapOverlayHash: CustomMapOverlay? - // Custom Tile Server - var tileRenderer: MKTileOverlayRenderer? - let tileServer: MapTileServerLinks = .openStreetMaps // MARK: Private methods @@ -89,7 +90,65 @@ struct MapViewSwiftUI: UIViewRepresentable { #endif } + private func setOverlays(mapView: MKMapView) { + // Avoid refreshing UI if selectedLayer has not changed + guard currentMapLayer != selectedMapLayer else { return } + currentMapLayer = selectedMapLayer + for overlay in mapView.overlays { + if overlay is TileOverlay { + mapView.removeOverlay(overlay) + } + } + switch selectedMapLayer { + case .offline: + let overlay = TileOverlay() + overlay.canReplaceMapContent = false + mapView.mapType = .standard + mapView.addOverlay(overlay, level: .aboveRoads) + case .satellite: + mapView.mapType = .satellite + case .hybrid: + mapView.mapType = .hybrid + default: + mapView.mapType = .standard + } + } + + private func setUserTracking(mapView: MKMapView, headingView: UIImageView?) { +// switch selectedTracking { +// case .bounding: +// guard let firstBoundingBox = trails.first?.polyline.boundingMapRect else { +// self.selectedTracking = .enabled +// return +// } +// let boundingBox = trails +// .map { $0.polyline.boundingMapRect } +// .reduce(firstBoundingBox) { (boundingBox, nextResult) -> MKMapRect in +// let minX = nextResult.minX < boundingBox.minX ? nextResult.minX : boundingBox.minX +// let maxX = nextResult.maxX > boundingBox.maxX ? nextResult.maxX : boundingBox.maxX +// let minY = nextResult.minY < boundingBox.minY ? nextResult.minY : boundingBox.minY +// let maxY = nextResult.maxY > boundingBox.maxY ? nextResult.maxY : boundingBox.maxY +// return MKMapRect(origin: MKMapPoint(x: minX, y: minY), size: MKMapSize(width: maxX-minX, height: maxY-minY)) +// } +// var region = MKCoordinateRegion(boundingBox) +// region.span.latitudeDelta += 0.01 +// region.span.longitudeDelta += 0.01 +// mapView.setRegion(region, animated: false) +// case .disabled: +// mapView.setUserTrackingMode(.none, animated: true) +// locationManager.updateHeading = false +// headingView?.isHidden = true +// case .enabled: +// mapView.setUserTrackingMode(.follow, animated: true) +// locationManager.updateHeading = true +// case .heading: +// mapView.setUserTrackingMode(.followWithHeading, animated: true) +// headingView?.isHidden = true +// } + } + func makeUIView(context: Context) -> MKMapView { + currentMapLayer = nil mapView.delegate = context.coordinator self.configureMap(mapView: mapView) return mapView @@ -97,65 +156,39 @@ struct MapViewSwiftUI: UIViewRepresentable { func updateUIView(_ mapView: MKMapView, context: Context) { - mapView.mapType = mapViewType - //visibleMapRect = mapView.visibleMapRect - // Offline maps and tile server settings - if false {// UserDefaults.enableOfflineMaps { + // MBTiles Offline maps and tile server settings +// if UserDefaults.enableOfflineMaps && UserDefaults.mapTileServer == "" { - visibleMapRect = mapView.visibleMapRect - - if UserDefaults.mapTileServer.count > 0 { - tileRenderer?.alpha = 0.0 - let overlays = mapView.overlays - if mapView.mapType == .standard { - let overlay = MKTileOverlay(urlTemplate: UserDefaults.mapTileServer) - if overlays.contains(where: {$0 is MKPolyline}) { - mapView.addOverlay(overlay, level: .aboveLabels) - if let poly_overlay = overlays.filter({$0 is MKPolyline}).first { - mapView.addOverlay(poly_overlay, level: .aboveLabels) - } - } else { - mapView.addOverlay(overlay, level: .aboveLabels) - - } - } else { - for overlay in overlays { - if let ove = overlay as? MKTileOverlay { - mapView.removeOverlay(ove) - } - } - } - } else 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 - if fileManager.fileExists(atPath: tilePath) { - print("Loading local map file") - if let overlay = LocalMBTileOverlay(mbTilePath: tilePath) { - overlay.canReplaceMapContent = false// customMapOverlay.canReplaceMapContent - mapView.addOverlay(overlay) - } - } else { - print("Couldn't find a local map file to load") - } - } - DispatchQueue.main.async { - self.presentCustomMapOverlayHash = self.customMapOverlay - self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile - } - } - } +// 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 +// if fileManager.fileExists(atPath: tilePath) { +// print("Loading local map file") +// if let overlay = LocalMBTileOverlay(mbTilePath: tilePath) { +// overlay.canReplaceMapContent = true// customMapOverlay.canReplaceMapContent +// mapView.addOverlay(overlay) +// } +// } else { +// print("Couldn't find a local map file to load") +// } +// } +// DispatchQueue.main.async { +// self.presentCustomMapOverlayHash = self.customMapOverlay +// self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile +// } +// } +// } let latest = positions .filter { $0.latest == true } .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } - // Node Route Lines - if showRouteLines { + if true {//showRouteLines { // Remove all existing PolyLine Overlays for overlay in mapView.overlays { if overlay is MKPolyline { @@ -188,6 +221,9 @@ struct MapViewSwiftUI: UIViewRepresentable { } } + /// Set selected map layer + // setOverlays(mapView: mapView) + let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count) if annotationCount != mapView.annotations.count { print("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)") @@ -240,7 +276,7 @@ struct MapViewSwiftUI: UIViewRepresentable { func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { - print (mapView.visibleMapRect) + //print (mapView.visibleMapRect) } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 4e2b96c3..917cac0a 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -16,7 +16,8 @@ struct NodeDetail: View { @AppStorage("meshMapType") private var meshMapType = 0 @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false - @State private var mapType: MKMapType = .standard + //@State private var mapType: MKMapType = .standard + @State private var selectedMapLayer: MapLayer = .standard @State var mapRect: MKMapRect = MKMapRect() @State var waypointCoordinate: WaypointCoordinate? @State var editingWaypoint: Int = 0 @@ -70,10 +71,11 @@ struct NodeDetail: View { waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) } }, - visibleMapRect: $mapRect, + //visibleMapRect: $mapRect, + selectedMapLayer: selectedMapLayer, positions: lastTenThousand, waypoints: Array(waypoints), - mapViewType: mapType, + //mapViewType: mapType, userTrackingMode: MKUserTrackingMode.none, showNodeHistory: meshMapShowNodeHistory, showRouteLines: meshMapShowRouteLines, @@ -83,18 +85,18 @@ struct NodeDetail: View { 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: $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) VStack { Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName) .font(.caption) @@ -229,7 +231,7 @@ struct NodeDetail: View { }) .onAppear { self.bleManager.context = context - mapType = MeshMapTypes(rawValue: meshMapType)?.MKMapTypeValue() ?? .standard + //mapType = .standard// MeshMapTypes(rawValue: meshMapType)?.MKMapTypeValue() ?? .standard } .task(id: node.num) { if !loadedWeather { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 2a30dad9..9b56f1dc 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -35,7 +35,9 @@ 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: 20, brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white) + let characters = Array(node.user?.shortName ?? "??") + + CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: characters.count == 1 ? 40 : 20, 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 05a7aeb1..6511ee35 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -15,13 +15,12 @@ struct NodeMap: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @State var meshMapType: Int = UserDefaults.mapType + @State var selectedMapLayer: MapLayer = UserDefaults.mapLayer @State var enableMapRecentering: Bool = UserDefaults.enableMapRecentering @State var enableMapRouteLines: Bool = UserDefaults.enableMapRouteLines @State var enableMapNodeHistoryPins: Bool = UserDefaults.enableMapNodeHistoryPins @State var enableOfflineMaps: Bool = UserDefaults.enableOfflineMaps @State var mapTileServer: String = UserDefaults.mapTileServer - @State var mapRect: MKMapRect = MKMapRect() @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none) @@ -32,14 +31,12 @@ struct NodeMap: View { format: "expire == nil || expire >= %@", Date() as NSDate ), animation: .none) private var waypoints: FetchedResults + @State var waypointCoordinate: WaypointCoordinate? - - @State var mapType: MKMapType = .standard @State var selectedTracking: UserTrackingModes = .none @State var selectedTileServer: MapTileServerLinks = .wikimedia @State var isPresentingInfoSheet: Bool = false - @State var waypointCoordinate: WaypointCoordinate? @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( mapName: "offlinemap", tileType: "png", @@ -59,10 +56,9 @@ struct NodeMap: View { waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) } }, - visibleMapRect: $mapRect, + selectedMapLayer: selectedMapLayer, positions: Array(positions), waypoints: Array(waypoints), - mapViewType: mapType, userTrackingMode: selectedTracking.MKUserTrackingModeValue(), showNodeHistory: enableMapNodeHistoryPins, showRouteLines: enableMapRouteLines, @@ -92,15 +88,21 @@ struct NodeMap: View { VStack { Form { Section(header: Text("Map Options")) { - Picker("Map Type", selection: $mapType) { - ForEach(MeshMapTypes.allCases) { map in - Text(map.description).tag(map.MKMapTypeValue()) + Picker(selection: $selectedMapLayer, label: Text("")) { + ForEach(MapLayer.allCases, id: \.self) { layer in + if layer == MapLayer.offline && UserDefaults.enableOfflineMaps { + Text(layer.localized) + } else if layer != MapLayer.offline { + Text(layer.localized) + } } } - .pickerStyle(DefaultPickerStyle()) - .onChange(of: (mapType)) { newMapType in - UserDefaults.mapType = Int(newMapType.rawValue) + .pickerStyle(SegmentedPickerStyle()) + .onChange(of: (selectedMapLayer)) { newMapLayer in + UserDefaults.mapLayer = newMapLayer } + .padding(.top, 5) + .padding(.bottom, 5) Toggle(isOn: $enableMapRecentering) { @@ -141,16 +143,16 @@ struct NodeMap: View { self.enableOfflineMaps.toggle() UserDefaults.enableOfflineMaps = self.enableOfflineMaps } - Text("If you have shared a MBTiles file with meshtastic it will be loaded.") - .font(.caption) - .foregroundColor(.gray) +// Text("If you have shared a MBTiles file with meshtastic it will be loaded.") +// .font(.caption) +// .foregroundColor(.gray) if UserDefaults.enableOfflineMaps { VStack { // Picker("Tile Servers", selection: $selectedTileServer) { // ForEach(MapTileServerLinks.allCases) { ts in // Text(ts.description) -// .tag(ts.id) +// // .tag(ts.id) // } // } // .pickerStyle(.menu) @@ -159,7 +161,7 @@ struct NodeMap: View { // mapTileServer = selectedTileServer.tileUrl // } - TilesDownloadView(boundingBox: mapRect, name: "All tiles") +// TilesDownloadView(boundingBox: mapRect, name: "All tiles") HStack { Label("Tile Server", systemImage: "square.grid.3x2") TextField( @@ -207,8 +209,6 @@ struct NodeMap: View { .onAppear(perform: { UIApplication.shared.isIdleTimerDisabled = true self.bleManager.context = context - mapType = MeshMapTypes(rawValue: meshMapType)?.MKMapTypeValue() ?? .standard - }) .onDisappear(perform: { UIApplication.shared.isIdleTimerDisabled = false diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 0bda4f53..243b8015 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -200,6 +200,7 @@ "not.connected"="Kein Gerät verbunden"; "numbers.punctuation"="Ziffern und Interpunktion"; "off"="Aus"; +"offline"="Offline"; "on.boot"="Nur beim Starten"; "options"="Optionen"; "password"="Passwort"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index a38802dc..87ab586b 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -200,6 +200,7 @@ "not.connected"="No device connected"; "numbers.punctuation"="Numbers and Punctuation"; "off"="Off"; +"offline"="Offline"; "on.boot"="On Boot Only"; "options"="Options"; "password"="Password"; diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index 3ed5fd3b..974693bc 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -200,6 +200,7 @@ "not.connected"="未连接到电台"; "numbers.punctuation"="数字和标点符号"; "off"="关闭"; +"offline"="Offline"; "on.boot"="仅在启动时"; "options"="选项"; "password"="密码";