mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #426 from meshtastic/2.2.12_Working_Changes
2.2.12 working changes
This commit is contained in:
commit
45df550882
8 changed files with 175 additions and 26 deletions
|
|
@ -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 = "";
|
||||
|
|
|
|||
28
Meshtastic/Extensions/Measurement.swift
Normal file
28
Meshtastic/Extensions/Measurement.swift
Normal 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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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@*/)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue