diff --git a/Meshtastic/Extensions/String.swift b/Meshtastic/Extensions/String.swift index 93050df0..da401a00 100644 --- a/Meshtastic/Extensions/String.swift +++ b/Meshtastic/Extensions/String.swift @@ -30,6 +30,18 @@ extension String { var localized: String { NSLocalizedString(self, comment: self) } + + + func isEmoji() -> Bool { + // Emoji are no more than 4 bytes + if self.count > 4 { + return false + } else { + let characters = Array(self) + return characters[0].isEmoji + } + } + func onlyEmojis() -> Bool { return count > 0 && !contains { !$0.isEmoji } } diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index 6150915e..c0751635 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -104,6 +104,14 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "enableOfflineMaps") } } + static var enableOfflineMapsMBTiles: Bool { + get { + UserDefaults.standard.bool(forKey: "enableOfflineMapsMBTiles") + } + set { + UserDefaults.standard.set(newValue, forKey: "enableOfflineMapsMBTiles") + } + } static var mapTileServer: String { get { diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 447d01b0..d2927f70 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -90,21 +90,23 @@ struct MapViewSwiftUI: UIViewRepresentable { #endif } - private func setOverlays(mapView: MKMapView) { + private func setMapLayer(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 { + if overlay is MKTileOverlay { mapView.removeOverlay(overlay) } } switch selectedMapLayer { case .offline: - let overlay = TileOverlay() - overlay.canReplaceMapContent = false mapView.mapType = .standard - mapView.addOverlay(overlay, level: .aboveRoads) + if !UserDefaults.enableOfflineMapsMBTiles { + let overlay = TileOverlay() + overlay.canReplaceMapContent = false + mapView.addOverlay(overlay, level: .aboveLabels) + } case .satellite: mapView.mapType = .satellite case .hybrid: @@ -114,39 +116,6 @@ struct MapViewSwiftUI: UIViewRepresentable { } } - 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 @@ -156,39 +125,41 @@ struct MapViewSwiftUI: UIViewRepresentable { func updateUIView(_ mapView: MKMapView, context: Context) { - - // MBTiles Offline maps and tile server settings -// if UserDefaults.enableOfflineMaps && UserDefaults.mapTileServer == "" { + // MBTiles Offline + if UserDefaults.enableOfflineMaps && UserDefaults.enableOfflineMapsMBTiles { -// 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 -// } -// } -// } + 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 + } + } + } + // Set selected map layer + setMapLayer(mapView: mapView) let latest = positions .filter { $0.latest == true } .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } + // Node Route Lines - if true {//showRouteLines { + if showRouteLines { // Remove all existing PolyLine Overlays for overlay in mapView.overlays { if overlay is MKPolyline { @@ -221,9 +192,6 @@ 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)") @@ -255,7 +223,6 @@ struct MapViewSwiftUI: UIViewRepresentable { } func makeCoordinator() -> MapCoordinator { - //return Coordinator(self) return Coordinator(self) } @@ -443,18 +410,19 @@ struct MapViewSwiftUI: UIViewRepresentable { } public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { - - switch overlay { - case let overlay as MKTileOverlay: - return MKTileOverlayRenderer(tileOverlay: overlay) - case let polyline as MKPolyline: - let polylineRenderer = MKPolylineRenderer(overlay: overlay) - let titleString = polyline.title ?? "0" - let renderer = MKPolylineRenderer(polyline: polyline) - renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0) - renderer.lineWidth = 8 - return polylineRenderer - default: return MKOverlayRenderer() + + if let tileOverlay = overlay as? MKTileOverlay { + return MKTileOverlayRenderer(tileOverlay: tileOverlay) + } else { + if let routePolyline = overlay as? MKPolyline { + + let titleString = routePolyline.title ?? "0" + let renderer = MKPolylineRenderer(polyline: routePolyline) + renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0) + renderer.lineWidth = 8 + return renderer + } + return MKOverlayRenderer() } } } diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 6511ee35..5e054d14 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -21,6 +21,7 @@ struct NodeMap: View { @State var enableMapNodeHistoryPins: Bool = UserDefaults.enableMapNodeHistoryPins @State var enableOfflineMaps: Bool = UserDefaults.enableOfflineMaps @State var mapTileServer: String = UserDefaults.mapTileServer + @State var enableOfflineMapsMBTiles: Bool = UserDefaults.enableOfflineMapsMBTiles @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none) @@ -149,35 +150,34 @@ struct NodeMap: View { if UserDefaults.enableOfflineMaps { VStack { -// Picker("Tile Servers", selection: $selectedTileServer) { -// ForEach(MapTileServerLinks.allCases) { ts in -// Text(ts.description) -// // .tag(ts.id) -// } -// } -// .pickerStyle(.menu) -// .onChange(of: (selectedTileServer)) { newTileServer in -// -// mapTileServer = selectedTileServer.tileUrl -// } -// TilesDownloadView(boundingBox: mapRect, name: "All tiles") - HStack { - Label("Tile Server", systemImage: "square.grid.3x2") - TextField( - "Tile Server", - text: $mapTileServer, - axis: .vertical - ) - .foregroundColor(.gray) - .font(.caption) - .onChange(of: (mapTileServer)) { newMapTileServer in - UserDefaults.mapTileServer = newMapTileServer + if !enableOfflineMapsMBTiles { + + HStack { + Label("Tile Server", systemImage: "square.grid.3x2") + TextField( + "Tile Server", + text: $mapTileServer, + axis: .vertical + ) + .keyboardType(.asciiCapable) + .disableAutocorrection(true) + .foregroundColor(.gray) + .font(.caption) + .onChange(of: (mapTileServer)) { newMapTileServer in + UserDefaults.mapTileServer = newMapTileServer + } } } + Toggle(isOn: $enableOfflineMapsMBTiles) { + Text("Enable MB Tiles") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onTapGesture { + self.enableOfflineMapsMBTiles.toggle() + UserDefaults.enableOfflineMapsMBTiles = self.enableOfflineMapsMBTiles + } } - .keyboardType(.asciiCapable) - .disableAutocorrection(true) } } }