mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge branch '2.5.19' into cleanup-nodelist-view
This commit is contained in:
commit
2e7e0da729
19 changed files with 4904 additions and 119 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
SupportingFiles/
|
||||
|
||||
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
|
||||
*.xcscmblueprint
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -486,6 +486,7 @@
|
|||
DDCDC69A29467643004C1DDA /* MeshtasticDataModelV3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV3.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = "<group>"; };
|
||||
DDD28D372C0CD2670063CFA3 /* MeshtasticDataModelV 37.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 37.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DDD3A2B22D5127CF0045EB48 /* ci_pre_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_pre_xcodebuild.sh; sourceTree = "<group>"; };
|
||||
DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttClientProxyManager.swift; sourceTree = "<group>"; };
|
||||
DDD5BB082C285DDC007E03CA /* AppLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLog.swift; sourceTree = "<group>"; };
|
||||
DDD5BB0A2C285E45007E03CA /* LogDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDetail.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -917,6 +918,7 @@
|
|||
DDC2E14B26CE248E0042C5E4 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDD3A2B12D5127B40045EB48 /* ci_scripts */,
|
||||
DDDBC87A2BC62E4E001E8DF7 /* Settings.bundle */,
|
||||
25AECD4E2C2F723200862C8E /* Localizable.xcstrings */,
|
||||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */,
|
||||
|
|
@ -1073,6 +1075,14 @@
|
|||
path = Persistence;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDD3A2B12D5127B40045EB48 /* ci_scripts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDD3A2B22D5127CF0045EB48 /* ci_pre_xcodebuild.sh */,
|
||||
);
|
||||
path = ci_scripts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDD43FE12A78C86B0083A3E9 /* Mqtt */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1776,7 +1786,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.18;
|
||||
MARKETING_VERSION = 2.5.19;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1810,7 +1820,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.18;
|
||||
MARKETING_VERSION = 2.5.19;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1842,7 +1852,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.18;
|
||||
MARKETING_VERSION = 2.5.19;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1875,7 +1885,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.18;
|
||||
MARKETING_VERSION = 2.5.19;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
|
|||
|
|
@ -78,10 +78,15 @@
|
|||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "publicMqttUsername"
|
||||
key = "PUBLIC_MQTT_USERNAME"
|
||||
value = "meshdev"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "PUBLIC_MQTT_PASSWORD"
|
||||
value = "large4cats"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
|
|
|
|||
61
Meshtastic/AppIntents/NavigateToNodeIntent.swift
Normal file
61
Meshtastic/AppIntents/NavigateToNodeIntent.swift
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// NavigateToNodeIntent.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Benjamin Faershtein on 2/8/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppIntents
|
||||
import CoreLocation
|
||||
import CoreData
|
||||
import UIKit
|
||||
|
||||
@available(iOS 16.4, *)
|
||||
struct NavigateToNodeIntent: ForegroundContinuableIntent {
|
||||
|
||||
static var title: LocalizedStringResource = "Navigate to Node Position"
|
||||
static var openAppWhenRun: Bool = false
|
||||
|
||||
@Parameter(title: "Node Number")
|
||||
var nodeNum: Int
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult & ProvidesDialog {
|
||||
if !BLEManager.shared.isConnected {
|
||||
throw AppIntentErrors.AppIntentError.notConnected
|
||||
}
|
||||
|
||||
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "NodeInfoEntity")
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
||||
do {
|
||||
guard let fetchedNode = try PersistenceController.shared.container.viewContext.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity],
|
||||
fetchedNode.count == 1 else {
|
||||
throw $nodeNum.needsValueError("Could not find node")
|
||||
}
|
||||
|
||||
let nodeInfo = fetchedNode[0]
|
||||
if let latitude = nodeInfo.latestPosition?.coordinate.latitude,
|
||||
let longitude = nodeInfo.latestPosition?.coordinate.longitude {
|
||||
|
||||
let url = URL(string: "maps://?saddr=&daddr=\(latitude),\(longitude)")
|
||||
|
||||
if let mapURL = url, UIApplication.shared.canOpenURL(mapURL) {
|
||||
// Request to continue in foreground before opening the app
|
||||
try await requestToContinueInForeground()
|
||||
|
||||
// Open Apple Maps for navigation
|
||||
UIApplication.shared.open(mapURL, options: [:], completionHandler: nil)
|
||||
return .result(dialog: "Navigating to node location.")
|
||||
} else {
|
||||
throw AppIntentErrors.AppIntentError.message("Unable to open Apple Maps.")
|
||||
}
|
||||
} else {
|
||||
throw AppIntentErrors.AppIntentError.message("Node does not have a recorded position.")
|
||||
}
|
||||
} catch {
|
||||
throw AppIntentErrors.AppIntentError.message("Failed to fetch node data.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,14 +8,6 @@
|
|||
import Foundation
|
||||
import Charts
|
||||
|
||||
extension Measurement where UnitType == UnitAngle {
|
||||
func reciprocal() -> Measurement {
|
||||
var recip = self.converted(to: .degrees)
|
||||
recip.value = (recip.value + 180).truncatingRemainder(dividingBy: 360)
|
||||
return recip.converted(to: self.unit)
|
||||
}
|
||||
}
|
||||
|
||||
struct PlottableMeasurement<UnitType: Unit> {
|
||||
var measurement: Measurement<UnitType>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,6 +145,5 @@ import OSLog
|
|||
}
|
||||
return sats
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -682,7 +682,6 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
let logString = String.localizedStringWithFormat("mesh.log.telemetry.received %@".localized, String(packet.from))
|
||||
MeshLogger.log("📈 \(logString)")
|
||||
|
||||
|
||||
if telemetryMessage.variant != Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.localStats(telemetryMessage.localStats) && telemetryMessage.variant != Telemetry.OneOf_Variant.powerMetrics(telemetryMessage.powerMetrics) {
|
||||
/// Other unhandled telemetry packets
|
||||
return
|
||||
|
|
@ -739,7 +738,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
Logger.statistics.info("📈 [Mesh Statistics] Channel Utilization: \(telemetryMessage.localStats.channelUtilization, privacy: .public) Airtime: \(telemetryMessage.localStats.airUtilTx, privacy: .public) Packets Sent: \(telemetryMessage.localStats.numPacketsTx, privacy: .public) Packets Received: \(telemetryMessage.localStats.numPacketsRx, privacy: .public) Bad Packets Received: \(telemetryMessage.localStats.numPacketsRxBad, privacy: .public) Nodes Online: \(telemetryMessage.localStats.numOnlineNodes, privacy: .public) of \(telemetryMessage.localStats.numTotalNodes, privacy: .public) nodes for Node: \(packet.from.toHex(), privacy: .public)")
|
||||
} else if telemetryMessage.variant == Telemetry.OneOf_Variant.powerMetrics(telemetryMessage.powerMetrics) {
|
||||
Logger.data.info("📈 [Power Metrics] Received for Node: \(packet.from.toHex(), privacy: .public)")
|
||||
|
||||
|
||||
if telemetryMessage.powerMetrics.hasCh1Voltage {
|
||||
telemetry.powerCh1Voltage = telemetryMessage.powerMetrics.ch1Voltage
|
||||
telemetry.metricsType = 2
|
||||
|
|
|
|||
|
|
@ -37,17 +37,15 @@ class MqttClientProxyManager {
|
|||
defaultServerPort = Int(fullHost.components(separatedBy: ":")[1]) ?? (useSsl ? 8883 : 1883)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let host = host {
|
||||
let port = defaultServerPort
|
||||
var username = node.mqttConfig?.username
|
||||
var password = node.mqttConfig?.password
|
||||
if host == defaultServerAddress {
|
||||
|
||||
// username = ProcessInfo.processInfo.environment["publicMqttUsername"]
|
||||
// password = ProcessInfo.processInfo.environment["publicMqttPsk"]
|
||||
useSsl = false
|
||||
}
|
||||
// if host == defaultServerAddress {
|
||||
//username = ProcessInfo.processInfo.environment["PUBLIC_MQTT_USERNAME"]
|
||||
//password = ProcessInfo.processInfo.environment["PUBLIC_MQTT_PASSWORD"]
|
||||
// }
|
||||
let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh"
|
||||
let prefix = root!
|
||||
topic = prefix + "/2/e" + "/#"
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ struct PowerMetrics: View {
|
|||
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
|
||||
|
||||
if(metric.powerCh1Voltage != nil) {
|
||||
if metric.powerCh1Voltage != nil {
|
||||
PowerMetricCompactWidget(
|
||||
type: .voltage,
|
||||
value: metric.powerCh1Voltage,
|
||||
|
|
@ -26,7 +25,7 @@ struct PowerMetrics: View {
|
|||
)
|
||||
}
|
||||
|
||||
if(metric.powerCh1Current != nil) {
|
||||
if metric.powerCh1Current != nil {
|
||||
PowerMetricCompactWidget(
|
||||
type: .current,
|
||||
value: metric.powerCh1Current,
|
||||
|
|
@ -34,7 +33,7 @@ struct PowerMetrics: View {
|
|||
)
|
||||
}
|
||||
|
||||
if(metric.powerCh2Voltage != nil) {
|
||||
if metric.powerCh2Voltage != nil {
|
||||
PowerMetricCompactWidget(
|
||||
type: .voltage,
|
||||
value: metric.powerCh2Voltage,
|
||||
|
|
@ -42,7 +41,7 @@ struct PowerMetrics: View {
|
|||
)
|
||||
}
|
||||
|
||||
if(metric.powerCh2Current != nil) {
|
||||
if metric.powerCh2Current != nil {
|
||||
PowerMetricCompactWidget(
|
||||
type: .current,
|
||||
value: metric.powerCh2Current,
|
||||
|
|
@ -50,7 +49,7 @@ struct PowerMetrics: View {
|
|||
)
|
||||
}
|
||||
|
||||
if(metric.powerCh3Voltage != nil) {
|
||||
if metric.powerCh3Voltage != nil {
|
||||
PowerMetricCompactWidget(
|
||||
type: .voltage,
|
||||
value: metric.powerCh3Voltage,
|
||||
|
|
@ -58,7 +57,7 @@ struct PowerMetrics: View {
|
|||
)
|
||||
}
|
||||
|
||||
if(metric.powerCh3Current != nil) {
|
||||
if metric.powerCh3Current != nil {
|
||||
PowerMetricCompactWidget(
|
||||
type: .current,
|
||||
value: metric.powerCh3Current,
|
||||
|
|
|
|||
|
|
@ -127,13 +127,17 @@ struct ChannelMessageList: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.padding([.top])
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.onFirstAppear {
|
||||
withAnimation {
|
||||
scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in
|
||||
withAnimation {
|
||||
scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
.onChange(of: channel.allPrivateMessages) {
|
||||
withAnimation {
|
||||
scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom)
|
||||
|
|
|
|||
|
|
@ -115,13 +115,17 @@ struct UserMessageList: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.padding([.top])
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.onFirstAppear {
|
||||
withAnimation {
|
||||
scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in
|
||||
withAnimation {
|
||||
scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
.onChange(of: user.messageList) {
|
||||
withAnimation {
|
||||
scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// NavigateToButton.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Benjamin Faershtein on 2/8/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreLocation
|
||||
import CoreData
|
||||
import OSLog
|
||||
|
||||
struct NavigateToButton: View {
|
||||
var node: NodeInfoEntity
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
guard let userNum = node.user?.num else {
|
||||
Logger.services.error("NavigateToAction: Selected node does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.services.info("Fetching NodeInfoEntity for userNum: \(userNum)")
|
||||
|
||||
let fetchRequest: NSFetchRequest<NodeInfoEntity> = NSFetchRequest(entityName: "NodeInfoEntity")
|
||||
fetchRequest.predicate = NSPredicate(format: "num == %lld", Int64(userNum))
|
||||
|
||||
do {
|
||||
let fetchedNodes = try PersistenceController.shared.container.viewContext.fetch(fetchRequest)
|
||||
|
||||
guard let nodeInfo = fetchedNodes.first else {
|
||||
Logger.services.error("NavigateToAction: Node with userNum \(userNum) not found in Core Data")
|
||||
return
|
||||
}
|
||||
|
||||
if let latitude = nodeInfo.latestPosition?.latitude,
|
||||
let longitude = nodeInfo.latestPosition?.longitude {
|
||||
if let url = URL(string: "maps://?saddr=&daddr=\(latitude),\(longitude)") {
|
||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||
} else {
|
||||
Logger.services.error("Failed to create URL for navigation")
|
||||
}
|
||||
} else {
|
||||
Logger.services.warning("NavigateToAction: Node \(userNum) has invalid or missing coordinates")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("NavigateToAction: Failed to fetch node with userNum \(userNum): \(error.localizedDescription)")
|
||||
}
|
||||
} label: {
|
||||
Label {
|
||||
Text("Navigate to node")
|
||||
} icon: {
|
||||
Image(systemName: "map")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ struct PositionPopover: View {
|
|||
/// Heading
|
||||
let degrees = Angle.degrees(Double(position.heading))
|
||||
Label {
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees).reciprocal()
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
Text("Heading: \(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
} icon: {
|
||||
Image(systemName: "location.north")
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ struct NodeDetail: View {
|
|||
}
|
||||
.disabled(!node.hasDeviceMetrics)
|
||||
|
||||
NavigationLink{
|
||||
NavigationLink {
|
||||
PowerMetricsLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
|
|
@ -378,6 +378,9 @@ struct NodeDetail: View {
|
|||
node: node
|
||||
)
|
||||
}
|
||||
if node.hasPositions {
|
||||
NavigateToButton(node: node)
|
||||
}
|
||||
IgnoreNodeButton(
|
||||
bleManager: bleManager,
|
||||
context: context,
|
||||
|
|
|
|||
|
|
@ -52,8 +52,9 @@ struct PositionLog: View {
|
|||
}
|
||||
TableColumn("Heading") { position in
|
||||
let degrees = Angle.degrees(Double(position.heading))
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees).reciprocal()
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
Text(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
TableColumn("SNR") { position in
|
||||
Text("\(String(format: "%.2f", position.snr)) dB")
|
||||
|
|
@ -63,6 +64,8 @@ struct PositionLog: View {
|
|||
}
|
||||
.width(min: 180)
|
||||
}
|
||||
.textSelection(.enabled)
|
||||
|
||||
|
||||
} else {
|
||||
ScrollView {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct MQTTConfig: View {
|
|||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
if enabled && proxyToClientEnabled && node!.mqttConfig!.proxyToClientEnabled == true {
|
||||
if enabled && proxyToClientEnabled && node?.mqttConfig?.proxyToClientEnabled ?? false == true {
|
||||
Toggle(isOn: $mqttConnected) {
|
||||
Label(mqttConnected ? "mqtt.disconnect".localized : "mqtt.connect".localized, systemImage: "server.rack")
|
||||
if bleManager.mqttError.count > 0 {
|
||||
|
|
@ -194,6 +194,7 @@ struct MQTTConfig: View {
|
|||
}
|
||||
.keyboardType(.default)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
|
||||
HStack {
|
||||
Label("password", systemImage: "wallet.pass")
|
||||
TextField("password", text: $password)
|
||||
|
|
@ -214,11 +215,13 @@ struct MQTTConfig: View {
|
|||
.keyboardType(.default)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/)
|
||||
Toggle(isOn: $tlsEnabled) {
|
||||
Label("TLS Enabled", systemImage: "checkmark.shield.fill")
|
||||
Text("Your MQTT Server must support TLS.")
|
||||
if !proxyToClientEnabled {
|
||||
Toggle(isOn: $tlsEnabled) {
|
||||
Label("TLS Enabled", systemImage: "checkmark.shield.fill")
|
||||
Text("Your MQTT Server must support TLS.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
Text("For all Mqtt functionality other than the map report you must also set uplink and downlink for each channel you want to bridge over Mqtt.")
|
||||
|
|
@ -269,6 +272,7 @@ struct MQTTConfig: View {
|
|||
.onChange(of: proxyToClientEnabled) { _, newProxyToClientEnabled in
|
||||
if newProxyToClientEnabled {
|
||||
jsonEnabled = false
|
||||
tlsEnabled = false
|
||||
}
|
||||
if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ struct GPSStatus: View {
|
|||
let altitiude = Measurement(value: newLocation.altitude, unit: UnitLength.meters)
|
||||
let speed = Measurement(value: newLocation.speed, unit: UnitSpeed.kilometersPerHour)
|
||||
let speedAccuracy = Measurement(value: newLocation.speedAccuracy, unit: UnitSpeed.metersPerSecond)
|
||||
let courseAccuracy = Measurement(value: newLocation.courseAccuracy, unit: UnitAngle.degrees).reciprocal()
|
||||
let courseAccuracy = Measurement(value: newLocation.courseAccuracy, unit: UnitAngle.degrees)
|
||||
|
||||
Label("Coordinate \(String(format: "%.5f", newLocation.coordinate.latitude)), \(String(format: "%.5f", newLocation.coordinate.longitude))", systemImage: "mappin")
|
||||
.font(largeFont)
|
||||
|
|
@ -45,7 +45,7 @@ struct GPSStatus: View {
|
|||
HStack {
|
||||
let degrees = Angle.degrees(newLocation.course)
|
||||
Label {
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees).reciprocal()
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
Text("Heading: \(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
} icon: {
|
||||
Image(systemName: "location.north")
|
||||
|
|
|
|||
16
ci_scripts/ci_pre_xcodebuild.sh
Executable file
16
ci_scripts/ci_pre_xcodebuild.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Stage: PRE-Xcode Build is activated .... "
|
||||
|
||||
# Move to the place where the scripts are located.
|
||||
# This is important because the position of the subsequently mentioned files depend of this origin.
|
||||
cd $CI_PRIMARY_REPOSITORY_PATH/ci_scripts || exit 1
|
||||
|
||||
# Write a JSON File containing all the environment variables and secrets.
|
||||
printf "{\"PUBLIC_MQTT_USERNAME\":\"%s\",\"PUBLIC_MQTT_PASSWORD\":\"%s\"}" "$PUBLIC_MQTT_USERNAME" "$PUBLIC_MQTT_PASSWORD" >> .\ $CI_PRIMARY_REPOSITORY_PATH/SupportingFiles/secrets.json
|
||||
|
||||
echo "Wrote Secrets.json file."
|
||||
|
||||
echo "Stage: PRE-Xcode Build is DONE .... "
|
||||
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue