Merge pull request #426 from meshtastic/2.2.12_Working_Changes

2.2.12 working changes
This commit is contained in:
Garth Vander Houwen 2023-11-18 08:40:37 -08:00 committed by GitHub
commit 45df550882
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 175 additions and 26 deletions

View file

@ -19,6 +19,8 @@
DD13AA492AB73BF400BA0C98 /* PositionPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD13AA482AB73BF400BA0C98 /* PositionPopover.swift */; };
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */; };
DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B828CDA93900720036 /* SerialConfigEnums.swift */; };
DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */; };
DD1933782B084F4200771CD5 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933772B084F4200771CD5 /* Measurement.swift */; };
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; };
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; };
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; };
@ -224,6 +226,8 @@
DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV15.xcdatamodel; sourceTree = "<group>"; };
DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfigEnums.swift; sourceTree = "<group>"; };
DD1925B828CDA93900720036 /* SerialConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfigEnums.swift; sourceTree = "<group>"; };
DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAltitudeChart.swift; sourceTree = "<group>"; };
DD1933772B084F4200771CD5 /* Measurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = "<group>"; };
DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = "<group>"; };
DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = "<group>"; };
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = "<group>"; };
@ -636,6 +640,7 @@
DDB6CCFA2AAF805100945AF6 /* NodeMapSwiftUI.swift */,
DD13AA482AB73BF400BA0C98 /* PositionPopover.swift */,
DD46401F2AFF10F4002A5ECB /* WaypointForm.swift */,
DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */,
);
path = Map;
sourceTree = "<group>";
@ -856,6 +861,7 @@
DDDB443F29F79AB000EE2349 /* UserDefaults.swift */,
DDB75A0E2A05920E006ED576 /* FileManager.swift */,
DDB75A102A059258006ED576 /* Url.swift */,
DD1933772B084F4200771CD5 /* Measurement.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -1093,6 +1099,7 @@
DDDB444C29F8AAA600EE2349 /* Color.swift in Sources */,
DDB8F4122A9EE5DD00230ECE /* UserList.swift in Sources */,
DDB75A0F2A05920E006ED576 /* FileManager.swift in Sources */,
DD1933782B084F4200771CD5 /* Measurement.swift in Sources */,
DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */,
DD41A61529AB0035003C5A37 /* NodeWeatherForecast.swift in Sources */,
DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */,
@ -1192,6 +1199,7 @@
DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */,
DD5E520E298EE33B00D21B61 /* mqtt.pb.swift in Sources */,
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */,
DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */,
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
DDDB443D29F6592F00EE2349 /* NetworkManager.swift in Sources */,
DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */,
@ -1427,7 +1435,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.2.11;
MARKETING_VERSION = 2.2.12;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1461,7 +1469,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.2.11;
MARKETING_VERSION = 2.2.12;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1583,7 +1591,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.11;
MARKETING_VERSION = 2.2.12;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1616,7 +1624,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.11;
MARKETING_VERSION = 2.2.12;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View file

@ -0,0 +1,28 @@
//
// Measurement.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 11/17/23.
//
import Foundation
import Charts
struct PlottableMeasurement<UnitType: Unit> {
var measurement: Measurement<UnitType>
}
extension PlottableMeasurement: Plottable where UnitType == UnitLength {
var primitivePlottable: Double {
self.measurement.converted(to: .meters).value
}
init?(primitivePlottable: Double) {
self.init(
measurement: Measurement(
value: primitivePlottable,
unit: .meters
)
)
}
}

View file

@ -666,7 +666,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
let interval = UserDefaults.provideLocationInterval > 0 ? UserDefaults.provideLocationInterval : 30
if positionTimer != nil {
}
positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((UserDefaults.provideLocationInterval)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true)
positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval(interval), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true)
if positionTimer != nil {
RunLoop.current.add(positionTimer!, forMode: .common)
}

View file

@ -256,6 +256,13 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else {
return
}
/// Don't save the same position over and over.
if mutablePositions.count > 0 {
let mostRecent = mutablePositions.lastObject as! PositionEntity
if mostRecent.latitudeI == position.latitudeI && mostRecent.longitudeI == position.longitudeI {
mutablePositions.remove(mostRecent)
}
}
mutablePositions.add(position)
fetchedNode[0].id = Int64(packet.from)
fetchedNode[0].num = Int64(packet.from)

View file

@ -33,6 +33,7 @@ struct NodeMapSwiftUI: View {
@State var position = MapCameraPosition.automatic
@State var scene: MKLookAroundScene?
@State var isLookingAround = false
@State var isShowingAltitude = false
@State var isEditingSettings = false
@State var selectedPosition: PositionEntity?
@State var showWaypoints = false
@ -47,7 +48,7 @@ struct NodeMapSwiftUI: View {
var body: some View {
let positionArray = node.positions?.array as? [PositionEntity] ?? []
let mostRecent = node.positions?.lastObject as? PositionEntity
var mostRecent = node.positions?.lastObject as? PositionEntity
let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in
return position.nodeCoordinate ?? LocationHelper.DefaultLocation
})
@ -75,10 +76,12 @@ struct NodeMapSwiftUI: View {
}
/// Convex Hull
if showConvexHull {
let hull = lineCoords.getConvexHull()
MapPolygon(coordinates: hull)
.stroke(Color(nodeColor.darker()), lineWidth: 3)
.foregroundStyle(Color(nodeColor).opacity(0.4))
if lineCoords.count > 0 {
let hull = lineCoords.getConvexHull()
MapPolygon(coordinates: hull)
.stroke(Color(nodeColor.darker()), lineWidth: 3)
.foregroundStyle(Color(nodeColor).opacity(0.4))
}
}
/// Waypoint Annotations
@ -88,8 +91,6 @@ struct NodeMapSwiftUI: View {
ZStack {
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 35)
.onTapGesture(coordinateSpace: .named("nodemap")) { location in
print("Tapped at \(location)")
let pinLocation = reader.convert(location, from: .local)
selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint)
}
}
@ -193,6 +194,14 @@ struct NodeMapSwiftUI: View {
.padding(.horizontal, 20)
}
}
.overlay(alignment: .bottom) {
if !isLookingAround && isShowingAltitude {
PositionAltitudeChart(node: node)
.frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 250 : 400)
.clipShape(RoundedRectangle(cornerRadius: 12))
.padding(.horizontal, 20)
}
}
.sheet(item: $selectedWaypoint) { selection in
WaypointForm(waypoint: selection)
.padding()
@ -216,8 +225,12 @@ struct NodeMapSwiftUI: View {
}
}
.onChange(of: node) {
let mostRecent = node.positions?.lastObject as? PositionEntity
position = MapCameraPosition.automatic//.camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 1500, heading: 0, pitch: 0))
mostRecent = node.positions?.lastObject as? PositionEntity
if node.positions?.count ?? 0 > 1 {
position = .automatic
} else {
position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 150, heading: 0, pitch: 60))
}
if let mostRecent {
Task {
scene = try? await fetchScene(for: mostRecent.coordinate)
@ -236,6 +249,12 @@ struct NodeMapSwiftUI: View {
case .offline:
mapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic)
}
mostRecent = node.positions?.lastObject as? PositionEntity
if node.positions?.count ?? 0 > 1 {
position = .automatic
} else {
position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 150, heading: 0, pitch: 60))
}
if self.scene == nil {
Task {
scene = try? await fetchScene(for: mostRecent!.coordinate)
@ -273,9 +292,10 @@ struct NodeMapSwiftUI: View {
/// Look Around Button
if self.scene != nil {
Button(action: {
withAnimation {
isLookingAround = !isLookingAround
if isShowingAltitude {
isShowingAltitude = false
}
isLookingAround = !isLookingAround
}) {
Image(systemName: isLookingAround ? "binoculars.fill" : "binoculars")
.padding(.vertical, 5)
@ -284,6 +304,21 @@ struct NodeMapSwiftUI: View {
.foregroundColor(.accentColor)
.buttonStyle(.borderedProminent)
}
/// Altitude Button
if node.positions?.count ?? 0 > 1 {
Button(action: {
if isLookingAround {
isLookingAround = false
}
isShowingAltitude = !isShowingAltitude
}) {
Image(systemName: isShowingAltitude ? "mountain.2.fill" : "mountain.2")
.padding(.vertical, 5)
}
.tint(Color(UIColor.secondarySystemBackground))
.foregroundColor(.accentColor)
.buttonStyle(.borderedProminent)
}
#if targetEnvironment(macCatalyst)
/// Hide non fuctional catalyst controls
// MapZoomStepper(scope: mapScope)

View file

@ -0,0 +1,63 @@
//
// PositionAltitudeChart.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 11/17/23.
//
import SwiftUI
import Charts
#if canImport(MapKit)
import MapKit
#endif
struct PositionAltitude {
let time: Date
var altitude: Measurement<UnitLength>
}
@available(iOS 17.0, macOS 14.0, *)
struct PositionAltitudeChart: View {
@Environment(\.dismiss) private var dismiss
@ObservedObject var node: NodeInfoEntity
@State private var lineWidth = 2.0
var body: some View {
let nodePositions = Array(node.positions!) as! [PositionEntity]
let data = nodePositions.map { PositionAltitude(time: $0.time ?? Date(), altitude: Measurement(value: Double($0.altitude), unit: .meters) ) }
HStack {
Chart(data, id: \.time) {
LineMark(
x: .value("Time", $0.time),
y: .value("Altitude", PlottableMeasurement(measurement: $0.altitude))
)
.accessibilityLabel($0.time.formatted(date: .abbreviated, time: .shortened))
.accessibilityValue("\($0.altitude)")
.lineStyle(StrokeStyle(lineWidth: lineWidth))
.interpolationMethod(.linear)
.symbol(Circle().strokeBorder(lineWidth: lineWidth))
.symbolSize(60)
}
.chartYAxis {
AxisMarks { value in
AxisGridLine()
AxisValueLabel("""
\(value.as(PlottableMeasurement.self)!
.measurement
.converted(to: .meters),
format: .measurement(
width: .wide,
numberFormatStyle: .number.precision(
.fractionLength(0))
)
)
""")
}
}
.chartXAxis(.visible)
}
.padding()
.background(Color(UIColor.secondarySystemBackground))
.opacity(/*@START_MENU_TOKEN@*/0.8/*@END_MENU_TOKEN@*/)
}
}

View file

@ -23,11 +23,7 @@ struct NodeListItem: View {
VStack(alignment: .leading) {
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65)
.padding(.trailing, 5)
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
if deviceMetrics?.count ?? 0 >= 1 {
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor)
}
BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor)
}
VStack(alignment: .leading) {
HStack {

View file

@ -78,10 +78,12 @@ struct MeshMap: View {
}
/// Convex Hull
if showConvexHull {
let hull = lineCoords.getConvexHull()
MapPolygon(coordinates: hull)
.stroke(.blue, lineWidth: 3)
.foregroundStyle(.indigo.opacity(0.4))
if lineCoords.count > 0 {
let hull = lineCoords.getConvexHull()
MapPolygon(coordinates: hull)
.stroke(.blue, lineWidth: 3)
.foregroundStyle(.indigo.opacity(0.4))
}
}
/// Position Annotations
ForEach(Array(positions), id: \.id) { position in
@ -104,7 +106,17 @@ struct MeshMap: View {
}
.frame(width: 60, height: 60)
}
CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 40)
if position.nodePosition?.hasDetectionSensorMetrics ?? false {
Image(systemName: "sensor.fill")
.symbolRenderingMode(.palette)
.symbolEffect(.variableColor)
.padding()
.foregroundStyle(.white)
.background(Color(nodeColor))
.clipShape(Circle())
} else {
CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 40)
}
}
.onTapGesture { location in
selectedPosition = (selectedPosition == position ? nil : position)