Merge pull request #1304 from meshtastic/2.6.11

2.6.11 Working Changes
This commit is contained in:
Garth Vander Houwen 2025-07-09 15:13:27 -07:00 committed by GitHub
commit 2a011acb1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 6786 additions and 193 deletions

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
102B5EAB2E172F41003D191E /* DatadogCore in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EAA2E172F41003D191E /* DatadogCore */; };
102B5EAD2E172F41003D191E /* DatadogCrashReporting in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EAC2E172F41003D191E /* DatadogCrashReporting */; };
102B5EAF2E172F41003D191E /* DatadogLogs in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EAE2E172F41003D191E /* DatadogLogs */; };
102B5EB12E172F41003D191E /* DatadogRUM in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EB02E172F41003D191E /* DatadogRUM */; };
108FFECB2DD3F43C00BFAA81 /* ShareContactQRDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 108FFECA2DD3F43C00BFAA81 /* ShareContactQRDialog.swift */; };
108FFECD2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 108FFECC2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift */; };
231B3F212D087A4C0069A07D /* MetricTableColumn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F202D087A4C0069A07D /* MetricTableColumn.swift */; };
@ -592,7 +596,11 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
102B5EAD2E172F41003D191E /* DatadogCrashReporting in Frameworks */,
25A978BA2C13F8ED0003AAE7 /* MeshtasticProtobufs in Frameworks */,
102B5EAB2E172F41003D191E /* DatadogCore in Frameworks */,
102B5EAF2E172F41003D191E /* DatadogLogs in Frameworks */,
102B5EB12E172F41003D191E /* DatadogRUM in Frameworks */,
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1236,6 +1244,10 @@
packageProductDependencies = (
DD0D3D212A55CEB10066DB71 /* CocoaMQTT */,
25A978B92C13F8ED0003AAE7 /* MeshtasticProtobufs */,
102B5EAA2E172F41003D191E /* DatadogCore */,
102B5EAC2E172F41003D191E /* DatadogCrashReporting */,
102B5EAE2E172F41003D191E /* DatadogLogs */,
102B5EB02E172F41003D191E /* DatadogRUM */,
);
productName = MeshtasticClient;
productReference = DDC2E15426CE248E0042C5E4 /* Meshtastic.app */;
@ -1299,12 +1311,14 @@
se,
sr,
it,
ja,
);
mainGroup = DDC2E14B26CE248E0042C5E4;
packageReferences = (
DD0D3D202A55CEB10066DB71 /* XCRemoteSwiftPackageReference "CocoaMQTT" */,
25A978B82C13F8ED0003AAE7 /* XCLocalSwiftPackageReference "MeshtasticProtobufs" */,
259792242C2F10B600AD1659 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */,
);
productRefGroup = DDC2E15526CE248E0042C5E4 /* Products */;
projectDirPath = "";
@ -1823,12 +1837,12 @@
INFOPLIST_FILE = Meshtastic/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Meshtastic;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 17.3;
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1856,12 +1870,12 @@
INFOPLIST_FILE = Meshtastic/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Meshtastic;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 17.3;
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1886,13 +1900,13 @@
INFOPLIST_FILE = Widgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.3;
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1918,13 +1932,13 @@
INFOPLIST_FILE = Widgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.3;
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.10;
MARKETING_VERSION = 2.6.11;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1985,6 +1999,14 @@
/* End XCLocalSwiftPackageReference section */
/* Begin XCRemoteSwiftPackageReference section */
102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/DataDog/dd-sdk-ios.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.29.0;
};
};
259792242C2F10B600AD1659 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-protobuf.git";
@ -2004,6 +2026,26 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
102B5EAA2E172F41003D191E /* DatadogCore */ = {
isa = XCSwiftPackageProductDependency;
package = 102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */;
productName = DatadogCore;
};
102B5EAC2E172F41003D191E /* DatadogCrashReporting */ = {
isa = XCSwiftPackageProductDependency;
package = 102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */;
productName = DatadogCrashReporting;
};
102B5EAE2E172F41003D191E /* DatadogLogs */ = {
isa = XCSwiftPackageProductDependency;
package = 102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */;
productName = DatadogLogs;
};
102B5EB02E172F41003D191E /* DatadogRUM */ = {
isa = XCSwiftPackageProductDependency;
package = 102B5EA92E172F41003D191E /* XCRemoteSwiftPackageReference "dd-sdk-ios" */;
productName = DatadogRUM;
};
25A978B92C13F8ED0003AAE7 /* MeshtasticProtobufs */ = {
isa = XCSwiftPackageProductDependency;
productName = MeshtasticProtobufs;

View file

@ -1,5 +1,5 @@
{
"originHash" : "a3033aea781828906c453276e3723177901ce64df5757de7ada28c854c9662eb",
"originHash" : "0dabe052e9e56f8514254d01df9aa7245e16b28a649d59bac6781d4ac9a79efa",
"pins" : [
{
"identity" : "cocoamqtt",
@ -10,6 +10,15 @@
"version" : "2.1.8"
}
},
{
"identity" : "dd-sdk-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/DataDog/dd-sdk-ios.git",
"state" : {
"revision" : "d0a42d8067665cb6ee86af51251ccc071f62bd54",
"version" : "2.29.0"
}
},
{
"identity" : "mqttcocoaasyncsocket",
"kind" : "remoteSourceControl",
@ -19,6 +28,24 @@
"version" : "1.0.8"
}
},
{
"identity" : "opentelemetry-swift-packages",
"kind" : "remoteSourceControl",
"location" : "https://github.com/DataDog/opentelemetry-swift-packages.git",
"state" : {
"revision" : "4a7295600d4ebb9525a23c11586c5fdb74ae8b7e",
"version" : "1.13.1"
}
},
{
"identity" : "plcrashreporter",
"kind" : "remoteSourceControl",
"location" : "https://github.com/microsoft/plcrashreporter.git",
"state" : {
"revision" : "8c61e5e38e9f737dd68512ed1ea5ab081244ad65",
"version" : "1.12.0"
}
},
{
"identity" : "starscream",
"kind" : "remoteSourceControl",

View file

@ -74,6 +74,7 @@ extension UserDefaults {
case environmentEnableWeatherKit
case enableAdministration
case mapReportingOptIn
case usageDataAndCrashReporting
case testIntEnum
}
@ -155,6 +156,9 @@ extension UserDefaults {
@UserDefault(.mapReportingOptIn, defaultValue: false)
static var mapReportingOptIn: Bool
@UserDefault(.usageDataAndCrashReporting, defaultValue: true)
static var usageDataAndCrashReporting: Bool
@UserDefault(.testIntEnum, defaultValue: .one)
static var testIntEnum: TestIntEnum

View file

@ -499,7 +499,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
let traceRoute = TraceRouteEntity(context: context)
let nodes = NodeInfoEntity.fetchRequest()
nodes.predicate = NSPredicate(format: "num IN %@", [destNum, self.connectedPeripheral.num])
if let connectedNum = self.connectedPeripheral?.num {
nodes.predicate = NSPredicate(format: "num IN %@", [destNum, connectedNum])
} else {
nodes.predicate = NSPredicate(format: "num == %@", destNum)
}
do {
let fetchedNodes = try context.fetch(nodes)
let receivingNode = fetchedNodes.first(where: { $0.num == destNum })
@ -801,18 +805,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
channelPacket(channel: decodedInfo.channel, fromNum: Int64(truncatingIfNeeded: connectedPeripheral.num), context: context)
}
// Config
if decodedInfo.config.isInitialized && !invalidVersion && connectedPeripheral != nil {
if decodedInfo.config.isInitialized && !invalidVersion && connectedPeripheral != nil && self.connectedPeripheral?.num != 0 {
nowKnown = true
localConfig(config: decodedInfo.config, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral.longName)
localConfig(config: decodedInfo.config, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral?.longName ?? "Unknown")
}
// Module Config
if decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0 {
onWantConfigResponseReceived()
nowKnown = true
moduleConfig(config: decodedInfo.moduleConfig, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName)
moduleConfig(config: decodedInfo.moduleConfig, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral?.longName ?? "Unknown")
if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) {
if decodedInfo.moduleConfig.cannedMessage.enabled {
_ = self.getCannedMessageModuleMessages(destNum: self.connectedPeripheral.num, wantResponse: true)
if let connectedNum = self.connectedPeripheral?.num, connectedNum > 0 {
_ = self.getCannedMessageModuleMessages(destNum: connectedNum, wantResponse: true)
}
}
}
}
@ -866,7 +872,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
case .nodeinfoApp:
if !invalidVersion { upsertNodeInfoPacket(packet: decodedInfo.packet, context: context) }
case .routingApp:
if !invalidVersion { routingPacket(packet: decodedInfo.packet, connectedNodeNum: self.connectedPeripheral.num, context: context) }
if !invalidVersion {
guard let peripheral = self.connectedPeripheral else {
Logger.mesh.error("🕸️ connectedPeripheral is nil. Unable to determine connectedNodeNum for routingPacket.")
return
}
routingPacket(packet: decodedInfo.packet, connectedNodeNum: peripheral.num, context: context)
}
case .adminApp:
adminAppPacket(packet: decodedInfo.packet, context: context)
case .replyApp:
@ -1174,7 +1186,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
success = false
} else {
let fromUserNum: Int64 = self.connectedPeripheral.num
guard let fromUserNum = self.connectedPeripheral?.num else {
Logger.mesh.error("🚫 Connected peripheral user number is nil, cannot send message.")
return false
}
let messageUsers = UserEntity.fetchRequest()
messageUsers.predicate = NSPredicate(format: "num IN %@", [fromUserNum, Int64(toUserNum)])
@ -1230,8 +1245,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
newMessage.toUser?.userNode?.favorite = true
do {
try context.save()
Logger.data.info("💾 Auto favorited node bases on sending a message \(self.connectedPeripheral.num.toHex(), privacy: .public) to \(toUserNum.toHex(), privacy: .public)")
_ = self.setFavoriteNode(node: (newMessage.toUser?.userNode)!, connectedNodeNum: fromUserNum)
if let connectedPeripheral = self.connectedPeripheral {
Logger.data.info("💾 Auto favorited node based on sending a message \(connectedPeripheral.num.toHex(), privacy: .public) to \(toUserNum.toHex(), privacy: .public)")
} else {
Logger.data.warning("⚠️ connectedPeripheral is nil while attempting to log auto-favoriting a node.")
}
guard let userNode = newMessage.toUser?.userNode else {
Logger.data.warning("⚠️ Unable to set favorite node: userNode is nil.")
return false
}
_ = self.setFavoriteNode(node: userNode, connectedNodeNum: fromUserNum)
} catch {
context.rollback()
let nsError = error as NSError
@ -1267,7 +1290,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
Logger.mesh.info("💬 \(logString, privacy: .public)")
do {
try context.save()
Logger.data.info("💾 Saved a new sent message from \(self.connectedPeripheral.num.toHex(), privacy: .public) to \(toUserNum.toHex(), privacy: .public)")
Logger.data.info("💾 Saved a new sent message from \(self.connectedPeripheral?.num.toHex() ?? "0", privacy: .public) to \(toUserNum.toHex(), privacy: .public)")
success = true
} catch {
@ -1278,7 +1301,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
}
}
} catch {
Logger.data.error("💥 Send message failure \(self.connectedPeripheral.num.toHex(), privacy: .public) to \(toUserNum.toHex(), privacy: .public)")
Logger.data.error("💥 Send message failure \(self.connectedPeripheral?.num.toHex() ?? "0", privacy: .public) to \(toUserNum.toHex(), privacy: .public)")
}
}
return success
@ -1495,6 +1518,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
}
public func sendTime() -> Bool {
if self.connectedPeripheral?.num ?? 0 <= 0 {
Logger.mesh.error("🚫 Unable to send time, connected node is disconnected or invalid")
return false
}
var adminPacket = AdminMessage()
adminPacket.setTimeOnly = UInt32(Date().timeIntervalSince1970)
var meshPacket: MeshPacket = MeshPacket()

View file

@ -15,7 +15,7 @@ import OSLog
import ActivityKit
#endif
// Simple extension to consicely pass values through a has_XXX boolean check
// Simple extension to concisely pass values through a has_XXX boolean check
fileprivate extension Bool {
func then<T>(_ value: T) -> T? {
self ? value : nil
@ -504,9 +504,6 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getCannedMessageModuleMessagesResponse(adminMessage.getCannedMessageModuleMessagesResponse) {
if let cmmc = try? CannedMessageModuleConfig(serializedBytes: packet.decoded.payload) {
if !cmmc.messages.isEmpty {
let logString = String.localizedStringWithFormat("Canned Messages Messages Received For: %@".localized, packet.from.toHex())
Logger.mesh.info("🥫 \(logString, privacy: .public)")
@ -520,10 +517,11 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
.replacingOccurrences(of: "11: ", with: "")
.replacingOccurrences(of: "\"", with: "")
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: "\n").first ?? ""
fetchedNode[0].cannedMessageConfig?.messages = messages
do {
try context.save()
Logger.data.info("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num.toHex(), privacy: .public)")
Logger.data.info("💾 Updated Canned Messages Messages For: \(fetchedNode.first?.num.toHex() ?? "Unknown".localized, privacy: .public)")
} catch {
context.rollback()
let nsError = error as NSError
@ -533,7 +531,6 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
} catch {
Logger.data.error("💥 Error Deserializing ADMIN_APP packet.")
}
}
}
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) {
channelPacket(channel: adminMessage.getChannelResponse, fromNum: Int64(packet.from), context: context)

View file

@ -5,6 +5,9 @@ import CoreData
import OSLog
import TipKit
import MeshtasticProtobufs
import DatadogCore
import DatadogCrashReporting
import DatadogRUM
@main
struct MeshtasticAppleApp: App {
@ -26,6 +29,28 @@ struct MeshtasticAppleApp: App {
let appState = AppState(
router: Router()
)
// Initialize Datadog
// RUM Client Tokens are NOT secret
let appID = "79fe92a9-74c9-4c8f-ba63-6308384ecfa9"
let clientToken = "pub4427bea20dbdb08a6af68034de22cd3b"
let environment = "testflight"
Datadog.initialize(
with: Datadog.Configuration(
clientToken: clientToken,
env: environment,
site: .us5
),
trackingConsent: UserDefaults.usageDataAndCrashReporting ? .granted : .notGranted
)
RUM.enable(
with: RUM.Configuration(
applicationID: appID,
uiKitViewsPredicate: DefaultUIKitRUMViewsPredicate(),
uiKitActionsPredicate: DefaultUIKitRUMActionsPredicate()
)
)
self._appState = ObservedObject(wrappedValue: appState)
// Initialize the BLEManager singleton with the necessary dependencies
BLEManager.setup(appState: appState, context: persistenceController.container.viewContext)

View file

@ -337,7 +337,10 @@ struct FilteredUserList<Content: View>: View {
}
}
// Always apply unmessagable and connected node filters
let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO")
// Show unmessagable nodes only if they have messages, otherwise hide them
let unmessagablePredicate = NSPredicate(format: "unmessagable == NO")
let hasMessagesPredicate = NSPredicate(format: "receivedMessages.@count > 0 OR sentMessages.@count > 0")
let isUnmessagablePredicate = NSCompoundPredicate(type: .or, subpredicates: [unmessagablePredicate, hasMessagesPredicate])
predicates.append(isUnmessagablePredicate)
let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == NO")
predicates.append(isIgnoredPredicate)

View file

@ -14,6 +14,7 @@ import SwiftUI
struct WaypointForm: View {
@EnvironmentObject var bleManager: BLEManager
@Environment(\.managedObjectContext) var context
@Environment(\.dismiss) private var dismiss
@State var waypoint: WaypointEntity
let distanceFormatter = MKDistanceFormatter()
@ -210,11 +211,11 @@ struct WaypointForm: View {
Menu {
Button("For me", action: {
bleManager.context.delete(waypoint)
context.delete(waypoint)
do {
try bleManager.context.save()
try context.save()
} catch {
bleManager.context.rollback()
context.rollback()
}
dismiss() })
Button("For everyone", action: {
@ -239,11 +240,11 @@ struct WaypointForm: View {
newWaypoint.expire = UInt32(1)
if bleManager.sendWaypoint(waypoint: newWaypoint) {
bleManager.context.delete(waypoint)
context.delete(waypoint)
do {
try bleManager.context.save()
try context.save()
} catch {
bleManager.context.rollback()
context.rollback()
}
dismiss()
} else {
@ -384,11 +385,11 @@ struct WaypointForm: View {
}
.alert("Waypoint Failed to Send", isPresented: $waypointFailedAlert) {
Button("OK", role: .cancel) {
bleManager.context.delete(waypoint)
context.delete(waypoint)
do {
try bleManager.context.save()
try context.save()
} catch {
bleManager.context.rollback()
context.rollback()
}
dismiss()
}
@ -396,18 +397,18 @@ struct WaypointForm: View {
.onDisappear {
if waypoint.id == 0 {
// New, unsent waypoint created by the user: delete it
bleManager.context.delete(waypoint)
context.delete(waypoint)
do {
try bleManager.context.save()
try context.save()
} catch {
bleManager.context.rollback()
context.rollback()
Logger.mesh.error("Failed to save context on waypoint deletion: \(error)")
}
}
}
.onAppear {
if waypoint.id > 0 {
let waypoint = getWaypoint(id: Int64(waypoint.id), context: bleManager.context)
let waypoint = getWaypoint(id: Int64(waypoint.id), context: context)
name = waypoint.name ?? "Dropped Pin"
description = waypoint.longDescription ?? ""
icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")

View file

@ -16,6 +16,7 @@ struct AppSettings: View {
@AppStorage("purgeStaleNodeDays") private var purgeStaleNodeDays: Double = 0
@AppStorage("environmentEnableWeatherKit") private var environmentEnableWeatherKit: Bool = true
@AppStorage("enableAdministration") private var enableAdministration: Bool = false
@AppStorage("usageDataAndCrashReporting") private var usageDataAndCrashReporting: Bool = true
var body: some View {
VStack {
Form {
@ -33,6 +34,13 @@ struct AppSettings: View {
Text("PKI based node administration, requires firmware version 2.5+")
.foregroundStyle(.secondary)
.font(.caption)
Toggle(isOn: $usageDataAndCrashReporting) {
Label("Usage and Crash Data", systemImage: "pencil.and.list.clipboard")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("Provide anonymous usage statistics and crash reports.")
.foregroundStyle(.secondary)
.font(.caption)
}
Section(header: Text("environment")) {
VStack(alignment: .leading) {

View file

@ -11,7 +11,7 @@ struct ConfigHeader<T>: View {
var body: some View {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
Text("There has been no response to a request for device metadata via PKC admin for this node.")
.font(.callout)
.foregroundColor(.orange)
@ -19,7 +19,7 @@ struct ConfigHeader<T>: View {
// Let users know what is going on if they are using remote admin and don't have the config yet
let expiration = node?.sessionExpiration ?? Date()
if node?[keyPath: config] == nil || expiration < node?.sessionExpiration ?? Date() {
Text("\(title) config data was requested over the admin channel but no response has been returned from the remote node.")
Text("\(title) config data was requested via PKC admin but no response has been returned from the remote node.")
.font(.callout)
.foregroundColor(.orange)
} else {

View file

@ -79,6 +79,7 @@ struct CannedMessagesConfig: View {
totalBytes = messages.utf8.count
}
hasMessagesChanges = true
hasChanges = true
}
.foregroundColor(.gray)
}

View file

@ -0,0 +1,15 @@
{
"originHash" : "a2385deee281bd55bce80722a1f2b020f7b745c02005befa8ccbf58a39ef4002",
"pins" : [
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
"version" : "1.29.0"
}
}
],
"version" : 3
}

@ -1 +1 @@
Subproject commit 27fac39141d99fe727a0a1824c5397409b1aea75
Subproject commit 816595c8bbdfc3b4388e11348ccd043294d58705