mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Fix up grid layout for position and metrics logs
This commit is contained in:
parent
893b54e876
commit
3f220269a0
6 changed files with 185 additions and 60 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue