diff --git a/Meshtastic/Views/Map/MapViewSwiftUI.swift b/Meshtastic/Views/Map/MapViewSwiftUI.swift index 11891735..14a59fae 100644 --- a/Meshtastic/Views/Map/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/MapViewSwiftUI.swift @@ -15,6 +15,15 @@ struct MapViewSwiftUI: UIViewRepresentable { let region: MKCoordinateRegion let mapViewType: MKMapType + + // Offline Maps + //make this view dependent on the UserDefault that is updated when importing a new map file + @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0 + @State private var loadedLastUpdatedLocalMapFile = 0 + var customMapOverlay: CustomMapOverlay? + @State private var presentCustomMapOverlayHash: CustomMapOverlay? + var overlays: [Overlay] = [] + func makeUIView(context: Context) -> MKMapView { mapView.mapType = mapViewType mapView.setRegion(region, animated: true) @@ -42,14 +51,15 @@ struct MapViewSwiftUI: UIViewRepresentable { final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate { var parent: MapViewSwiftUI - var gRecognizer = UITapGestureRecognizer() + var longPressRecognizer = UILongPressGestureRecognizer() init(_ parent: MapViewSwiftUI) { self.parent = parent super.init() - self.gRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapHandler)) - self.gRecognizer.delegate = self - self.parent.mapView.addGestureRecognizer(gRecognizer) + self.longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler)) + self.longPressRecognizer.minimumPressDuration = 1.0 + self.longPressRecognizer.delegate = self + self.parent.mapView.addGestureRecognizer(longPressRecognizer) } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { @@ -72,12 +82,143 @@ struct MapViewSwiftUI: UIViewRepresentable { } } - @objc func tapHandler(_ gesture: UITapGestureRecognizer) { - // Screen Position - CGPoint - let location = gRecognizer.location(in: self.parent.mapView) - // Map Coordinate - CLLocationCoordinate2D - let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView) - print(coordinate) + @objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) { + if gesture.state == .ended { + // Screen Position - CGPoint + let location = longPressRecognizer.location(in: self.parent.mapView) + // Map Coordinate - CLLocationCoordinate2D + let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView) + print(coordinate) + } + } + } + + /// is supposed to be located in the folder with the map name + public struct DefaultTile: Hashable { + let tileName: String + let tileType: String + + public init(tileName: String, tileType: String) { + self.tileName = tileName + self.tileType = tileType + } + } + + public struct CustomMapOverlay: Equatable, Hashable { + let mapName: String + let tileType: String + var canReplaceMapContent: Bool + var minimumZoomLevel: Int? + var maximumZoomLevel: Int? + let defaultTile: DefaultTile? + + public init( + mapName: String, + tileType: String, + canReplaceMapContent: Bool = true, // false for transparent tiles + minimumZoomLevel: Int? = nil, + maximumZoomLevel: Int? = nil, + defaultTile: DefaultTile? = nil + ) { + + self.mapName = mapName + self.tileType = tileType + self.canReplaceMapContent = canReplaceMapContent + self.minimumZoomLevel = minimumZoomLevel + self.maximumZoomLevel = maximumZoomLevel + self.defaultTile = defaultTile + } + + public init?( + mapName: String?, + tileType: String, + canReplaceMapContent: Bool = true, // false for transparent tiles + minimumZoomLevel: Int? = nil, + maximumZoomLevel: Int? = nil, + defaultTile: DefaultTile? = nil + ) { + if (mapName == nil || mapName! == "") { + return nil + } + self.mapName = mapName! + self.tileType = tileType + self.canReplaceMapContent = canReplaceMapContent + self.minimumZoomLevel = minimumZoomLevel + self.maximumZoomLevel = maximumZoomLevel + self.defaultTile = defaultTile + } + } + + public class CustomMapOverlaySource: MKTileOverlay { + + // requires folder: tiles/{mapName}/z/y/y,{tileType} + private var parent: MapView + private let mapName: String + private let tileType: String + private let defaultTile: DefaultTile? + + public init( + parent: MapView, + mapName: String, + tileType: String, + defaultTile: DefaultTile? + ) { + self.parent = parent + self.mapName = mapName + self.tileType = tileType + self.defaultTile = defaultTile + super.init(urlTemplate: "") + } + + public override func url(forTilePath path: MKTileOverlayPath) -> URL { + if let tileUrl = Bundle.main.url( + forResource: "\(path.y)", + withExtension: self.tileType, + subdirectory: "tiles/\(self.mapName)/\(path.z)/\(path.x)", + localization: nil + ) { + return tileUrl + } else if let defaultTile = self.defaultTile, let defaultTileUrl = Bundle.main.url( + forResource: defaultTile.tileName, + withExtension: defaultTile.tileType, + subdirectory: "tiles/\(self.mapName)", + localization: nil + ) { + return defaultTileUrl + } else { + let urlstring = self.mapName+"\(path.z)/\(path.x)/\(path.y).png" + return URL(string: urlstring)! + // Bundle.main.url(forResource: "surrounding", withExtension: "png", subdirectory: "tiles")! + } + + } + + } + + public struct Overlay { + + public static func == (lhs: MapViewSwiftUI.Overlay, rhs: MapViewSwiftUI.Overlay) -> Bool { + // maybe to use in the future for comparison of full array + lhs.shape.coordinate.latitude == rhs.shape.coordinate.latitude && + lhs.shape.coordinate.longitude == rhs.shape.coordinate.longitude && + lhs.fillColor == rhs.fillColor + } + + var shape: MKOverlay + var fillColor: UIColor? + var strokeColor: UIColor? + var lineWidth: CGFloat + + public init( + shape: MKOverlay, + fillColor: UIColor? = nil, + strokeColor: UIColor? = nil, + lineWidth: CGFloat = 0 + ) { + self.shape = shape + self.fillColor = fillColor + self.strokeColor = strokeColor + self.lineWidth = lineWidth } } } diff --git a/Meshtastic/Views/Map/WaypointFormView.swift b/Meshtastic/Views/Map/WaypointFormView.swift index 6ce94398..9c1f4b3e 100644 --- a/Meshtastic/Views/Map/WaypointFormView.swift +++ b/Meshtastic/Views/Map/WaypointFormView.swift @@ -10,23 +10,21 @@ import SwiftUI struct WaypointFormView: View { @Environment(\.dismiss) private var dismiss + @FocusState private var emojiIsFocused: Bool @State private var id: Int32? @State private var name: String = "" @State private var description: String = "" - @State private var emoji: String = "" - @FocusState private var emojiIsFocused: Bool + @State private var emoji: String = "📍" @State private var latitude: Double = 0.0 @State private var longitude: Double = 0.0 - @State private var expire: Date = Date.now.addingTimeInterval(60 * 60) + @State private var expire: Date = Date.now.addingTimeInterval(60 * 120) // 1 minute * 120 = 2 Hours @State private var locked: Bool = false - - var body: some View { - + Form { - - Section(header: Text("Waypoint")) { + Section(header: Text("Waypoint").font(.title3)) { + Text("Distance Away").foregroundColor(Color.gray) Text("Lat/Long ") + Text(" \(String(latitude) + "," + String(longitude))").foregroundColor(Color.gray) HStack { Text("Name") @@ -70,9 +68,9 @@ struct WaypointFormView: View { }) } HStack { - Text("Emoji") + Text("Icon") Spacer() - EmojiOnlyTextField(text: $emoji, placeholder: "emoji") + EmojiOnlyTextField(text: $emoji, placeholder: "Select an emoji") .font(.title) .focused($emojiIsFocused) .onChange(of: emoji) { value in @@ -111,7 +109,7 @@ struct WaypointFormView: View { .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) - .padding() + .padding(5) Button { dismiss() @@ -121,7 +119,7 @@ struct WaypointFormView: View { .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) - .padding() + .padding(5) } } } diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 0925335d..98756ae8 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -78,11 +78,11 @@ struct DeviceMetricsLog: View { ScrollView { let columns = [ - GridItem(), - GridItem(), - GridItem(), - GridItem(), - GridItem(.fixed(140)) + GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1), + GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1), + GridItem(.flexible(minimum: 30, maximum: 70), spacing: 0.1), + GridItem(.flexible(minimum: 30, maximum: 65), spacing: 0.1), + GridItem(spacing: 0) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { GridRow { diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index e044a08f..3c48b671 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -65,11 +65,11 @@ struct EnvironmentMetricsLog: View { } else { ScrollView { let columns = [ - GridItem(), - GridItem(), - GridItem(), - GridItem(), - GridItem(.fixed(140)) + GridItem(spacing: 0.1), + GridItem(spacing: 0.1), + GridItem(spacing: 0.1), + GridItem(spacing: 0.1), + GridItem(spacing: 0) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 7ed3b79a..c0fbd3e4 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -11,15 +11,12 @@ struct NodeDetail: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - - @State private var mapType: MKMapType = .standard - - @State private var showingDetailsPopover = false - @State var satsInView = 0 + @State private var mapType: MKMapType = .standard + @State private var showingDetailsPopover = false @State private var showingShutdownConfirm: Bool = false @State private var showingRebootConfirm: Bool = false - @State var presentingWaypointForm = false + @State private var presentingWaypointForm = true var node: NodeInfoEntity @@ -36,10 +33,7 @@ struct NodeDetail: View { ZStack { let annotations = node.positions?.array as! [PositionEntity] ZStack { - MapViewSwiftUI(positions: annotations, region: MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005) - //MKCoordinateSpan(latitudeDelta: 0.16405544070813249, longitudeDelta: 0.1232528799585566) - - ), mapViewType: mapType) + MapViewSwiftUI(positions: annotations, region: MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)), mapViewType: mapType) VStack { Spacer() Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ") @@ -58,7 +52,6 @@ struct NodeDetail: View { } } else { HStack { - } .padding([.top], 20) } @@ -73,7 +66,6 @@ struct NodeDetail: View { Divider() VStack { if node.user != nil { - Image(hwModelString) .resizable() .aspectRatio(contentMode: .fill) @@ -343,9 +335,7 @@ struct NodeDetail: View { } Button(action: { - showingRebootConfirm = true - }) { Label("reboot", systemImage: "arrow.triangle.2.circlepath") diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 2be8210c..1b98a7e5 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -31,10 +31,10 @@ struct PositionLog: View { Text(String(position.seqNo)) } TableColumn("Latitude") { position in - Text(String(format: "%.6f", position.latitude ?? 0)) + Text(String(format: "%.5f", position.latitude ?? 0)) } TableColumn("Longitude") { position in - Text(String(format: "%.6f", position.longitude ?? 0)) + Text(String(format: "%.5f", position.longitude ?? 0)) } TableColumn("Altitude") { position in Text(String(position.altitude)) @@ -61,11 +61,11 @@ struct PositionLog: View { ScrollView { // Use a grid on iOS as a table only shows a single column let columns = [ - GridItem(.fixed(90)), - GridItem(.fixed(95)), - GridItem(.fixed(45)), - GridItem(.fixed(40)), - GridItem(.fixed(140)) + GridItem(spacing: 0.1), + GridItem(spacing: 0.1), + GridItem(.flexible(minimum: 35, maximum: 40), spacing: 0.1), + GridItem(.flexible(minimum: 30, maximum: 35), spacing: 0.1), + GridItem(spacing: 0) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { @@ -89,9 +89,9 @@ struct PositionLog: View { } ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in GridRow { - Text(String(format: "%.6f", mappin.latitude ?? 0)) + Text(String(format: "%.5f", mappin.latitude ?? 0)) .font(.caption2) - Text(String(format: "%.6f", mappin.longitude ?? 0)) + Text(String(format: "%.5f", mappin.longitude ?? 0)) .font(.caption2) Text(String(mappin.satsInView)) .font(.caption2) @@ -102,19 +102,15 @@ struct PositionLog: View { } } } - .padding(.leading, 15) - .padding(.trailing, 5) } + .padding(.leading) } HStack { Button(role: .destructive) { - isPresentingClearLogConfirm = true - } label: { - Label("Clear Log", systemImage: "trash.fill") } .buttonStyle(.bordered)