Merge pull request #1190 from meshtastic/2.6.0

2.6.0 Working Changes
This commit is contained in:
Garth Vander Houwen 2025-04-27 15:19:00 -07:00 committed by GitHub
commit e3c0040f26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 627 additions and 1767 deletions

File diff suppressed because it is too large Load diff

View file

@ -56,7 +56,6 @@
B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B399E8A32B6F486400E4488E /* RetryButton.swift */; };
B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E905B02B71F7F300654D07 /* TextMessageField.swift */; };
BC47C2EF2CE0017D008245CA /* MessageNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */; };
BC5EBA3C2D002A2000C442FF /* MessageNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5EBA3B2D002A2000C442FF /* MessageNodeIntent.swift */; };
BC6B45FF2CB2F98900723CEB /* SaveChannelSettingsIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6B45FE2CB2F98900723CEB /* SaveChannelSettingsIntent.swift */; };
BCB613812C67290800485544 /* SendWaypointIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613802C67290800485544 /* SendWaypointIntent.swift */; };
BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613822C672A2600485544 /* MessageChannelIntent.swift */; };
@ -146,7 +145,6 @@
DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8EBF42285058FA00426DCA /* DisplayConfig.swift */; };
DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */; };
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */; };
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; };
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; };
DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */; };
DD93800E2BA74D0C008BEC06 /* ChannelForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */; };
@ -437,7 +435,6 @@
DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConfig.swift; sourceTree = "<group>"; };
DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingError.swift; sourceTree = "<group>"; };
DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = "<group>"; };
DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = "<group>"; };
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = "<group>"; };
DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapContent.swift; sourceTree = "<group>"; };
DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelForm.swift; sourceTree = "<group>"; };
@ -731,7 +728,6 @@
DDDB263E2AABEE20003AFCB7 /* NodeList.swift */,
DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */,
DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */,
DD90860D26F69BAE00DC5189 /* NodeMap.swift */,
DD73FD1028750779000852D6 /* PositionLog.swift */,
DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */,
6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */,
@ -1240,7 +1236,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1540;
LastUpgradeCheck = 1600;
LastUpgradeCheck = 1630;
TargetAttributes = {
25F5D5C62C4375A8008036E3 = {
CreatedOnToolsVersion = 15.4;
@ -1404,7 +1400,6 @@
DDC4D568275499A500A4208E /* Persistence.swift in Sources */,
DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */,
DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */,
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */,
D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */,
DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */,
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */,
@ -1486,7 +1481,6 @@
251926872C3BAE2200249DF5 /* NodeAlertsButton.swift in Sources */,
DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */,
DDD5BB092C285DDC007E03CA /* AppLog.swift in Sources */,
BC5EBA3C2D002A2000C442FF /* MessageNodeIntent.swift in Sources */,
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */,
233E99C52D84A0B600CC3A77 /* CompactWidget.swift in Sources */,
DDC1B81A2AB5377B00C71E39 /* MessagesTips.swift in Sources */,
@ -1608,7 +1602,6 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = GCH7VS5Y9R;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@ -1632,7 +1625,6 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = GCH7VS5Y9R;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
@ -1682,6 +1674,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = GCH7VS5Y9R;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@ -1746,6 +1739,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = GCH7VS5Y9R;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
@ -1782,7 +1776,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\"";
DEVELOPMENT_TEAM = GCH7VS5Y9R;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Meshtastic/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Meshtastic;
@ -1792,7 +1785,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.5.23;
MARKETING_VERSION = 2.6.0;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1816,7 +1809,6 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\"";
DEVELOPMENT_TEAM = GCH7VS5Y9R;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Meshtastic/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Meshtastic;
@ -1826,7 +1818,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.5.23;
MARKETING_VERSION = 2.6.0;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1847,7 +1839,6 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = GCH7VS5Y9R;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Widgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
@ -1858,7 +1849,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.5.23;
MARKETING_VERSION = 2.6.0;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1880,7 +1871,6 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = GCH7VS5Y9R;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Widgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
@ -1891,7 +1881,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.5.23;
MARKETING_VERSION = 2.6.0;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
LastUpgradeVersion = "1630"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
LastUpgradeVersion = "1630"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -16,7 +16,7 @@ class AppIntentErrors {
var localizedStringResource: LocalizedStringResource {
switch self {
case let .message(message):
Logger.services.error("App Intent: \(message,privacy: .public)")
Logger.services.error("App Intent: \(message, privacy: .public)")
return "Error: \(message)"
case .notConnected:
Logger.services.error("App Intent: No Connected Node")

View file

@ -13,38 +13,38 @@ 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.")

View file

@ -29,12 +29,9 @@ struct SaveChannelSettingsIntent: AppIntent {
if channelUrl.absoluteString.lowercased().contains("meshtastic.org/e/#") {
// Split the URL to get the portion after "#"
let components = channelUrl.absoluteString.components(separatedBy: "#")
// Add channels flag based on the URL query parameter (if present)
let addChannels = Bool(channelUrl["add"] ?? "false") ?? false
var channelSettings: String?
// Extract the Base64 encoded channel settings (after "#")
if let lastComponent = components.last {
channelSettings = lastComponent.components(separatedBy: "?").first // Ignore any query parameters
@ -44,7 +41,6 @@ struct SaveChannelSettingsIntent: AppIntent {
if let channelSettings = channelSettings {
// Call the BLEManager to save the channel settings
let saveResult = BLEManager.shared.saveChannelSet(base64UrlString: channelSettings, addChannels: addChannels)
if !saveResult {
throw AppIntentErrors.AppIntentError.message("Failed to save the channel settings.")
}

View file

@ -19,11 +19,11 @@ enum ConfigPresets: Int, CaseIterable, Identifiable {
switch self {
case .unset:
return "canned.messages.preset.manual".localized
return "Manual Configuration".localized
case .rakRotaryEncoder:
return "canned.messages.preset.rakrotary".localized
return "RAK Rotary Encoder".localized
case .cardKB:
return "canned.messages.preset.cardkb".localized
return "M5 Stack Card KB / RAK Keypad".localized
}
}
}

View file

@ -19,11 +19,11 @@ enum ChannelRoles: Int, CaseIterable, Identifiable {
switch self {
case .disabled:
return "channel.role.disabled".localized
return "Disabled".localized
case .primary:
return "channel.role.primary".localized
return "Primary".localized
case .secondary:
return "channel.role.secondary".localized
return "Secondary".localized
}
}
func protoEnumValue() -> Channel.Role {

View file

@ -110,11 +110,11 @@ enum GpsMode: Int, CaseIterable, Equatable {
var description: String {
switch self {
case .disabled:
return "gpsmode.disabled".localized
return "Disabled".localized
case .enabled:
return "gpsmode.enabled".localized
return "Enabled".localized
case .notPresent:
return "gpsmode.notPresent".localized
return "Not Present".localized
}
}
func protoEnumValue() -> Config.PositionConfig.GpsMode {

View file

@ -31,7 +31,7 @@ enum SerialBaudRates: Int, CaseIterable, Identifiable {
switch self {
case .baudDefault:
return "default".localized
return "Default".localized
case .baud110:
return "110 Baud"
case .baud300:
@ -118,7 +118,7 @@ enum SerialModeTypes: Int, CaseIterable, Identifiable {
var description: String {
switch self {
case .default:
return "serial.mode.default".localized
return "Default".localized
case .simple:
return "serial.mode.simple".localized
case .proto:

View file

@ -20,13 +20,10 @@ struct CsvDocument: FileDocument {
}
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
csvData = String(decoding: data, as: UTF8.self)
csvData = String(data: data, encoding: .utf8) ?? ""
} else {
throw CocoaError(.fileReadCorruptFile)
}
}

View file

@ -14,7 +14,7 @@ func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
if metricsType == 0 {
// Create Device Metrics Header
csvString = "\("battery.level".localized), \("Voltage".localized), \("channel.utilization".localized), \("airtime".localized), \("uptime".localized), \("Timestamp".localized)"
csvString = "\("battery.level".localized), \("Voltage".localized), \("Channel Utilization".localized), \("airtime".localized), \("uptime".localized), \("Timestamp".localized)"
for dm in telemetry where dm.metricsType == 0 {
csvString += "\n"
csvString += dm.batteryLevel?.formatted(.number.grouping(.never)) ?? ""

View file

@ -18,7 +18,7 @@ extension OSLogEntryLog.Level {
case .notice: "⚠️ Notice"
case .error: "🚨 Error"
case .fault: "💥 Fault"
@unknown default: "default"
@unknown default: "Default".localized
}
}
var color: Color {

View file

@ -579,7 +579,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let error {
Logger.services.error("🚫 [BLE] didUpdateValueFor Characteristic error \(error.localizedDescription, privacy: .public)")
let errorCode = (error as NSError).code
if errorCode == 5 || errorCode == 15 {
@ -633,14 +632,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
} catch {
Logger.services.error("💥 \(error.localizedDescription, privacy: .public) \(characteristic.value!, privacy: .public)")
}
// Publish mqttClientProxyMessages received on the from radio
if decodedInfo.payloadVariant == FromRadio.OneOf_PayloadVariant.mqttClientProxyMessage(decodedInfo.mqttClientProxyMessage) {
let message = CocoaMQTTMessage(
topic: decodedInfo.mqttClientProxyMessage.topic,
payload: [UInt8](decodedInfo.mqttClientProxyMessage.data),
retained: decodedInfo.mqttClientProxyMessage.retained
)
let message = CocoaMQTTMessage(topic: decodedInfo.mqttClientProxyMessage.topic, payload: [UInt8](decodedInfo.mqttClientProxyMessage.data), retained: decodedInfo.mqttClientProxyMessage.retained)
mqttManager.mqttClientProxy?.publish(message)
} else if decodedInfo.payloadVariant == FromRadio.OneOf_PayloadVariant.clientNotification(decodedInfo.clientNotification) {
if decodedInfo.clientNotification.hasReplyID {
@ -782,13 +776,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
adminAppPacket(packet: decodedInfo.packet, context: context)
case .replyApp:
Logger.mesh.info("🕸️ MESH PACKET received for Reply App handling as a text message")
textMessageAppPacket(
packet: decodedInfo.packet,
wantRangeTestPackets: wantRangeTestPackets,
connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0),
context: context,
appState: appState
)
textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context, appState: appState)
case .ipTunnelApp:
Logger.mesh.info("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED UNHANDLED")
case .serialApp:
@ -3448,7 +3436,7 @@ extension BLEManager: CBCentralManagerDelegate {
case .unsupported:
status = "BLE is unsupported"
default:
status = "default"
status = "Default".localized
}
Logger.services.info("📜 [BLE] Bluetooth status: \(status, privacy: .public)")
}

View file

@ -686,20 +686,15 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) {
if let telemetryMessage = try? Telemetry(serializedBytes: packet.decoded.payload) {
let logString = String.localizedStringWithFormat("mesh.log.telemetry.received %@".localized, String(packet.from))
Logger.mesh.info("📈 \(logString, privacy: .public)")
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
}
let telemetry = TelemetryEntity(context: context)
let fetchNodeTelemetryRequest = NodeInfoEntity.fetchRequest()
fetchNodeTelemetryRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
do {
let fetchedNode = try context.fetch(fetchNodeTelemetryRequest)
if fetchedNode.count == 1 {
@ -756,7 +751,6 @@ 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)")
telemetry.powerCh1Voltage = telemetryMessage.powerMetrics.hasCh1Voltage.then(telemetryMessage.powerMetrics.ch1Voltage)
telemetry.powerCh1Current = telemetryMessage.powerMetrics.hasCh1Current.then(telemetryMessage.powerMetrics.ch1Current)
telemetry.powerCh2Voltage = telemetryMessage.powerMetrics.hasCh2Voltage.then(telemetryMessage.powerMetrics.ch2Voltage)
@ -764,7 +758,6 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
telemetry.powerCh3Voltage = telemetryMessage.powerMetrics.hasCh3Voltage.then(telemetryMessage.powerMetrics.ch3Voltage)
telemetry.powerCh3Current = telemetryMessage.powerMetrics.hasCh3Current.then(telemetryMessage.powerMetrics.ch3Current)
telemetry.metricsType = 2
}
telemetry.snr = packet.rxSnr
telemetry.rssi = packet.rxRssi
@ -781,7 +774,6 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet
}
try context.save()
Logger.data.info("💾 [TelemetryEntity] of type \(MetricsTypes(rawValue: Int(telemetry.metricsType))?.name ?? "Unknown Metrics Type", privacy: .public) Saved for Node: \(packet.from.toHex(), privacy: .public)")
if telemetry.metricsType == 0 {
// Connected Device Metrics

View file

@ -43,18 +43,13 @@ class MqttClientProxyManager {
let port = defaultServerPort
let username = node.mqttConfig?.username
let password = node.mqttConfig?.password
// 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" + "/#"
let qos = CocoaMQTTQoS(rawValue: UInt8(1))!
connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic, qos: qos, cleanSession: true)
connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic)
}
}
func connect(host: String, port: Int, useSsl: Bool, username: String?, password: String?, topic: String?, qos: CocoaMQTTQoS, cleanSession: Bool) {
func connect(host: String, port: Int, useSsl: Bool, username: String?, password: String?, topic: String?) {
guard !host.isEmpty else {
delegate?.onMqttDisconnected()
return
@ -67,7 +62,7 @@ class MqttClientProxyManager {
mqttClient.username = username
mqttClient.password = password
mqttClient.keepAlive = 60
mqttClient.cleanSession = cleanSession
mqttClient.cleanSession = true
if debugLog {
mqttClient.logLevel = .debug
}

View file

@ -41,7 +41,6 @@ class MetricsChartSeries: ObservableObject {
// Used for scaling the Y-axis
let initialYAxisRange: ClosedRange<Float>?
let minumumYAxisSpan: Float?
// Main initializer
init<Value, ChartBody: ChartContent, ForegroundStyle: ShapeStyle>(
id: String,

View file

@ -67,12 +67,12 @@ class MetricsSeriesList: ObservableObject, RandomAccessCollection, RangeReplacea
for aSeries in self.visible {
var seriesUpper = range[aSeries]?.upperBound ?? -.infinity
var seriesLower = range[aSeries]?.lowerBound ?? .infinity
if let value = aSeries.valueFor(te) {
// Update the global bounds
if value > globalUpper {globalUpper = value}
if value < globalLower {globalLower = value}
// Update the series bounds if necessary
if value > seriesUpper || value < seriesLower {
if value > seriesUpper {

View file

@ -13,7 +13,7 @@ struct MessagesTip: Tip {
return "tip.messages"
}
var title: Text {
Text("tip.messages.title")
Text("Messages")
}
var message: Text? {
Text("tip.messages.message")

View file

@ -67,7 +67,7 @@ struct Connect: View {
Text("BLE Name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name?.addingVariationSelectors ?? "unknown".localized)")
.font(.callout).foregroundColor(Color.gray)
if node != nil {
Text("firmware.version").font(.callout)+Text(": \(node?.metadata?.firmwareVersion ?? "unknown".localized)")
Text("Firmware Version").font(.callout)+Text(": \(node?.metadata?.firmwareVersion ?? "unknown".localized)")
.font(.callout).foregroundColor(Color.gray)
}
if bleManager.isSubscribed {

View file

@ -49,7 +49,7 @@ struct InvalidVersion: View {
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)

View file

@ -54,7 +54,7 @@ struct ContentView: View {
router: appState.router
)
.tabItem {
Label("settings", systemImage: "gear")
Label("Settings", systemImage: "gear")
.font(.title)
}
.tag(NavigationState.Tab.settings)

View file

@ -10,11 +10,11 @@ struct CircleText: View {
var text: String
var color: Color
var circleSize: CGFloat = 45
var node: NodeInfoEntity? = nil
var node: NodeInfoEntity?
var body: some View {
if let node = node {
NavigationStack{
NavigationStack {
NavigationLink(destination: NodeDetail(node: node)) {
circleContent
}

View file

@ -31,7 +31,7 @@ import SwiftUI
WeightCompactWidget(weight: "123", unit: "kg")
SoilTemperatureCompactWidget(temperature: "23", unit: "°C")
SoilMoistureCompactWidget(moisture: "23", unit: "%")
let rain: Float = 10.1
let locale = NSLocale.current as NSLocale
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)

View file

@ -16,7 +16,7 @@ struct RadiationCompactWidget: View {
HStack(alignment: .firstTextBaseline) {
Text(verbatim: "")
.font(.system(size: 30, design: .monospaced))
.foregroundColor(.accentColor)
.tint(.accentColor)
Text("Radiation")
.textCase(.uppercase)
.font(.callout)

View file

@ -39,4 +39,3 @@ struct WeatherConditionsCompactWidget: View {
}
}
}

View file

@ -50,7 +50,7 @@ struct DirectMessagesHelp: View {
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -29,7 +29,7 @@ struct MQTTIcon: View {
VStack(spacing: 0.5) {
Text("Topic: \(topic)".localized)
.padding(20)
Button("close", action: { self.isPopoverOpen = false }).padding([.bottom], 20)
Button("Close", action: { self.isPopoverOpen = false }).padding([.bottom], 20)
}
.presentationCompactAdaptation(.popover)
})

View file

@ -69,8 +69,8 @@ struct PowerMetrics: View {
}
enum PowerMetricType: String {
case current = "current"
case voltage = "voltage"
case current = "Current"
case voltage = "Voltage"
}
struct PowerMetricCompactWidget: View {

View file

@ -154,6 +154,6 @@ struct ChannelList: View {
.listStyle(.plain)
}
}
.navigationTitle("channels")
.navigationTitle("Channels")
}
}

View file

@ -14,19 +14,16 @@ struct ChannelMessageList: View {
@EnvironmentObject var appState: AppState
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
// Keyboard State
@FocusState var messageFieldFocused: Bool
@ObservedObject var myInfo: MyInfoEntity
@ObservedObject var channel: ChannelEntity
@State private var replyMessageId: Int64 = 0
@AppStorage("preferredPeripheralNum") private var preferredPeripheralNum = -1
// Scroll state
@State private var showScrollToBottomButton = false
@State private var hasReachedBottom = false
@State private var gotFirstUnreadMessage: Bool = false
@State private var showScrollToBottomButton = false
@State private var hasReachedBottom = false
@State private var gotFirstUnreadMessage: Bool = false
var body: some View {
VStack {
@ -118,7 +115,7 @@ struct ChannelMessageList: View {
.frame(maxWidth: .infinity)
.id(message.messageId)
.onAppear {
if gotFirstUnreadMessage{
if gotFirstUnreadMessage {
if !message.read {
message.read = true
do {
@ -184,7 +181,6 @@ struct ChannelMessageList: View {
showScrollToBottomButton = true
}
}
// Scroll to bottom button
if showScrollToBottomButton {
Button {

View file

@ -73,7 +73,6 @@ struct MessageText: View {
} else {
EmptyView()
}
}
.contextMenu {
MessageContextMenuItems(

View file

@ -35,7 +35,7 @@ struct Messages: View {
List(selection: $router.navigationState.messages) {
NavigationLink(value: MessagesNavigationState.channels()) {
Label {
Text("channels")
Text("Channels")
.badge(unreadChannelMessages)
.font(.title2)
.padding()

View file

@ -20,7 +20,6 @@ struct UserMessageList: View {
// View State Items
@ObservedObject var user: UserEntity
@State private var replyMessageId: Int64 = 0
// Scroll state
@State private var showScrollToBottomButton = false
@State private var hasReachedBottom = false
@ -171,7 +170,6 @@ struct UserMessageList: View {
showScrollToBottomButton = true
}
}
// Scroll to bottom button
if showScrollToBottomButton {
Button {

View file

@ -165,7 +165,7 @@ struct DeviceMetricsLog: View {
// dm.voltage.map { Text("\(String(format: "%.2f", $0))") } ?? Text("--")
Text("\(dm.voltage?.formatted(.number.precision(.fractionLength(2))) ?? Constants.nilValueIndicator)")
}
TableColumn("channel.utilization") { dm in
TableColumn("Channel Utilization") { dm in
dm.channelUtilization.map { channelUtilization in
// Text("\(String(format: "%.2f", channelUtilization))%")
Text("\(channelUtilization.formatted(.number.precision(.fractionLength(2))))%")

View file

@ -14,45 +14,43 @@ 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")
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, privacy: .public)")
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, privacy: .public) not found in Core Data")
return
}
Logger.services.info("Fetching NodeInfoEntity for userNum: \(userNum, privacy: .public)")
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, privacy: .public) 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")
}
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.warning("NavigateToAction: Node \(userNum, privacy: .public) has invalid or missing coordinates")
Logger.services.error("Failed to create URL for navigation")
}
} catch {
Logger.services.error("NavigateToAction: Failed to fetch node with userNum \(userNum, privacy: .public): \(error.localizedDescription, privacy: .public)")
}
} label: {
Label {
Text("Navigate to node")
} icon: {
Image(systemName: "map")
.symbolRenderingMode(.hierarchical)
} else {
Logger.services.warning("NavigateToAction: Node \(userNum, privacy: .public) has invalid or missing coordinates")
}
} catch {
Logger.services.error("NavigateToAction: Failed to fetch node with userNum \(userNum, privacy: .public): \(error.localizedDescription, privacy: .public)")
}
} label: {
Label {
Text("Navigate to node")
} icon: {
Image(systemName: "map")
.symbolRenderingMode(.hierarchical)
}
}
}
}

View file

@ -111,7 +111,7 @@ Spacer()
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -23,7 +23,7 @@ struct PositionPopover: View {
var body: some View {
// Node Color from node.num
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
NavigationStack{
NavigationStack {
VStack {
HStack {
ZStack {
@ -105,7 +105,6 @@ struct PositionPopover: View {
.foregroundColor(.primary)
.font(idiom == .phone ? .callout : .body)
}
} icon: {
Image(systemName: "mountain.2.fill")
.symbolRenderingMode(.hierarchical)
@ -180,7 +179,6 @@ struct PositionPopover: View {
}
.padding(.bottom, 5)
if position.nodePosition?.viaMqtt ?? false {
Label {
Text("MQTT")
.font(idiom == .phone ? .callout : .body)
@ -234,7 +232,7 @@ struct PositionPopover: View {
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -338,7 +338,7 @@ struct WaypointForm: View {
if LocationsHandler.currentLocation.distance(from: LocationsHandler.DefaultLocation) > 0.0 {
let metersAway = waypoint.coordinate.distance(from: LocationsHandler.currentLocation)
Label {
Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
Text("Distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
.foregroundColor(.primary)
} icon: {
Image(systemName: "lines.measurement.horizontal")
@ -354,7 +354,7 @@ struct WaypointForm: View {
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -220,7 +220,6 @@ extension MetricsColumnList {
)
} ?? Text(Constants.nilValueIndicator)
}),
// Rainfall 24-hour
MetricsTableColumn(
id: "rainfall24H",

View file

@ -80,7 +80,7 @@ extension MetricsSeriesList {
.alignsMarkStylesWithPlotArea()
}
}),
// Barometric Pressure Series Configuration
MetricsChartSeries(
id: "barometricPressure",
@ -106,7 +106,7 @@ extension MetricsSeriesList {
.alignsMarkStylesWithPlotArea()
}
}),
// Indoor Air Quality Series Configuration
MetricsChartSeries(
id: "iaq",
@ -134,7 +134,7 @@ extension MetricsSeriesList {
.alignsMarkStylesWithPlotArea()
}
}),
// Lux
MetricsChartSeries(
id: "lux",
@ -460,7 +460,7 @@ extension MetricsSeriesList {
.lineStyle(StrokeStyle(lineWidth: 4))
.alignsMarkStylesWithPlotArea()
}
}),
})
])
}
}

View file

@ -63,7 +63,7 @@ struct MetricsColumnDetail: View {
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -120,7 +120,7 @@ struct NodeDetail: View {
if let metadata = node.metadata {
HStack {
Label {
Text("firmware.version")
Text("Firmware Version")
} icon: {
Image(systemName: "memorychip")
.symbolRenderingMode(.multicolor)
@ -214,7 +214,7 @@ struct NodeDetail: View {
// to use with WeatherKit, or has actual data in the most recent EnvironmentMetrics entity
// that will be rendered in this section.
if node.hasPositions && UserDefaults.environmentEnableWeatherKit
|| node.hasDataForLatestEnvironmentMetrics(attributes: ["iaq", "temperature", "relativeHumidity", "barometricPressure", "windSpeed", "radiation", "weight", "distance", "soilTemperature", "soilMoisture"]) {
|| node.hasDataForLatestEnvironmentMetrics(attributes: ["iaq", "temperature", "relativeHumidity", "barometricPressure", "windSpeed", "radiation", "weight", "Distance", "soilTemperature", "soilMoisture"]) {
Section("Environment") {
if !node.hasEnvironmentMetrics {
LocalWeatherConditions(location: node.latestPosition?.nodeLocation)

View file

@ -192,7 +192,7 @@ struct NodeListFilter: View {
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -77,7 +77,7 @@ struct NodeListItem: View {
imageColor: .green,
text: "connected".localized)
}
if node.lastHeard?.timeIntervalSince1970 ?? 0 > 0 && node.lastHeard! < Calendar.current.date(byAdding: .year, value: 1, to: Date())!{
if node.lastHeard?.timeIntervalSince1970 ?? 0 > 0 && node.lastHeard! < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
IconAndText(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill",
imageColor: node.isOnline ? .green : .orange,
text: node.lastHeard?.formatted() ?? "unknown.age".localized)

View file

@ -65,10 +65,7 @@ struct NodeList: View {
var nodes: FetchedResults<NodeInfoEntity>
var connectedNode: NodeInfoEntity? {
getNodeInfo(
id: bleManager.connectedPeripheral?.num ?? 0,
context: context
)
getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
}
@ViewBuilder
@ -78,19 +75,11 @@ struct NodeList: View {
) -> some View {
/// Allow users to mute notifications for a node even if they are not connected
if let user = node.user {
NodeAlertsButton(
context: context,
node: node,
user: user
)
NodeAlertsButton(context: context, node: node, user: user)
}
if let connectedNode {
/// Favoriting a node requires being connected
FavoriteNodeButton(
bleManager: bleManager,
context: context,
node: node
)
FavoriteNodeButton(bleManager: bleManager, context: context, node: node)
/// Don't show message, trace route, position exchange or delete context menu items for the connected node
if connectedNode.num != node.num {
if !node.viaMqtt || node.viaMqtt && node.hopsAway == 0 {

View file

@ -1,233 +0,0 @@
////
//// NodeMap.swift
//// MeshtasticApple
////
//// Created by Garth Vander Houwen on 8/7/21.
////
//
//import SwiftUI
//import MapKit
//import CoreLocation
//import CoreData
//
//struct NodeMap: View {
// @Environment(\.managedObjectContext) var context
// @EnvironmentObject var bleManager: BLEManager
//
// @ObservedObject
// var router: Router
// @State var selectedMapLayer: MapLayer = UserDefaults.mapLayer
// @State var enableMapRecentering: Bool = UserDefaults.enableMapRecentering
// @State var enableMapRouteLines: Bool = UserDefaults.enableMapRouteLines
// @State var enableMapNodeHistoryPins: Bool = UserDefaults.enableMapNodeHistoryPins
// @State var enableOfflineMaps: Bool = UserDefaults.enableOfflineMaps
// @State var selectedTileServer: MapTileServer = UserDefaults.mapTileServer
// @State var enableOverlayServer: Bool = UserDefaults.enableOverlayServer
// @State var selectedOverlayServer: MapOverlayServer = UserDefaults.mapOverlayServer
// @State var mapTilesAboveLabels: Bool = UserDefaults.mapTilesAboveLabels
// let fromDate: NSDate = Calendar.current.date(byAdding: .month, value: -1, to: Date())! as NSDate
// @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)],
// predicate: NSPredicate(format: "nodePosition != nil", Calendar.current.date(byAdding: .day, value: -7, to: Date())! as NSDate), animation: .none)
// private var positions: FetchedResults<PositionEntity>
// @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
// predicate: NSPredicate(
// format: "expire == nil || expire >= %@", Date() as NSDate
// ), animation: .none)
// private var waypoints: FetchedResults<WaypointEntity>
// @State var waypointCoordinate: WaypointCoordinate?
// @State var selectedTracking: UserTrackingModes = .none
// @State var isPresentingInfoSheet: Bool = false
// @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay(
// mapName: "offlinemap",
// tileType: "png",
// canReplaceMapContent: true
// )
// var body: some View {
// NavigationStack {
// ZStack {
// MapViewSwiftUI(
// onLongPress: { coord in
// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0)
// }, onWaypointEdit: { wpId in
// if wpId > 0 {
// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
// }
// },
// selectedMapLayer: selectedMapLayer,
// positions: Array(positions),
// waypoints: Array(waypoints),
// userTrackingMode: selectedTracking.MKUserTrackingModeValue(),
// showNodeHistory: enableMapNodeHistoryPins,
// showRouteLines: enableMapRouteLines,
// customMapOverlay: self.customMapOverlay
// )
// VStack(alignment: .trailing) {
// HStack(alignment: .top) {
// Spacer()
// MapButtons(tracking: $selectedTracking, isPresentingInfoSheet: $isPresentingInfoSheet)
// .padding(.trailing, 8)
// .padding(.top, 16)
// }
// Spacer()
// TileDownloadStatus()
// .padding(.trailing, 16)
// .padding(.bottom, 20)
// }
// }
// .ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
// .frame(maxHeight: .infinity)
// .sheet(item: $waypointCoordinate, content: { wpc in
// WaypointFormMapKit(coordinate: wpc)
// .presentationDetents([.medium, .large])
// .presentationDragIndicator(.automatic)
// })
// .sheet(isPresented: $isPresentingInfoSheet) {
// VStack {
// Form {
// Section(header: Text("Map Options")) {
// Picker(selection: $selectedMapLayer, label: Text("")) {
// ForEach(MapLayer.allCases, id: \.self) { layer in
// if layer == MapLayer.offline && enableOfflineMaps {
// Text(layer.localized)
// } else if layer != MapLayer.offline {
// Text(layer.localized)
// }
// }
// }
// .pickerStyle(SegmentedPickerStyle())
// .onChange(of: selectedMapLayer) { _, newMapLayer in
// UserDefaults.mapLayer = newMapLayer
// }
// .padding(.top, 5)
// .padding(.bottom, 5)
// Toggle(isOn: $enableMapRecentering) {
// Label("map.recentering", systemImage: "camera.metering.center.weighted")
// }
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
// .onTapGesture {
// self.enableMapRecentering.toggle()
// UserDefaults.enableMapRecentering = self.enableMapRecentering
// }
// Toggle(isOn: $enableMapNodeHistoryPins) {
// Label("Show Node History", systemImage: "building.columns.fill")
// }
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
// .onTapGesture {
// self.enableMapNodeHistoryPins.toggle()
// UserDefaults.enableMapNodeHistoryPins = self.enableMapNodeHistoryPins
// }
// Toggle(isOn: $enableMapRouteLines) {
// Label("Show Route Lines", systemImage: "road.lanes")
// }
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
// .onTapGesture {
// self.enableMapRouteLines.toggle()
// UserDefaults.enableMapRouteLines = self.enableMapRouteLines
// }
// let locale = Locale.current
// if locale.region?.identifier ?? "no locale" == "US" {
// Toggle(isOn: $enableOverlayServer) {
// Label("Show Weather", systemImage: "cloud.fill")
// }
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
// .onTapGesture {
// self.enableOverlayServer.toggle()
// UserDefaults.enableOverlayServer = self.enableOverlayServer
// }
// if enableOverlayServer {
// Picker(selection: $selectedOverlayServer,
// label: Text("Radar")) {
// ForEach(MapOverlayServer.allCases, id: \.self) { mos in
// Text(mos.description)
// .font(.footnote)
// }
// }
// .pickerStyle(DefaultPickerStyle())
// .onChange(of: (selectedOverlayServer)) { _, newSelectedOverlayServer in
// UserDefaults.mapOverlayServer = newSelectedOverlayServer
// }
// Text(LocalizedStringKey(selectedOverlayServer.attribution))
// .font(.footnote)
// .foregroundColor(.gray)
// .padding(0)
// }
// }
// }
// Section(header: Text("Offline Maps")) {
// Toggle(isOn: $enableOfflineMaps) {
// Text("Enable Offline Maps")
// }
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
// .onChange(of: enableOfflineMaps) { _, newEnableOfflineMaps in
// UserDefaults.enableOfflineMaps = newEnableOfflineMaps
// if !enableOfflineMaps {
// if self.selectedMapLayer == .offline {
// self.selectedMapLayer = .standard
// }
// }
// }
// if enableOfflineMaps {
// VStack(alignment: .leading) {
// Picker(selection: $selectedTileServer,
// label: Text("Tile Server")) {
// ForEach(MapTileServer.allCases, id: \.self) { tsl in
// Text(tsl.description)
// }
// }
// .pickerStyle(DefaultPickerStyle())
// .onChange(of: (selectedTileServer)) { _, newSelectedTileServer in
// UserDefaults.mapTileServer = newSelectedTileServer
// }
// Text("Attribution:")
// .fontWeight(.semibold)
// .font(.footnote)
// Text(LocalizedStringKey(selectedTileServer.attribution))
// .font(.footnote)
// .foregroundColor(.gray)
// .padding(0)
// Divider()
// Toggle(isOn: $mapTilesAboveLabels) {
// Text("Tiles above Labels")
// }
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
// .onTapGesture {
// self.mapTilesAboveLabels.toggle()
// UserDefaults.mapTilesAboveLabels = self.mapTilesAboveLabels
// }
// }
// }
// }
// }
// #if targetEnvironment(macCatalyst)
// Button {
// isPresentingInfoSheet = false
// } label: {
// Label("close", systemImage: "xmark")
// }
// .buttonStyle(.bordered)
// .buttonBorderShape(.capsule)
// .controlSize(.large)
// .padding(.bottom)
// #endif
// }
// .presentationDetents([enableOfflineMaps || enableOverlayServer ? .large : .medium])
// .presentationDragIndicator(.visible)
// }
// }
// .navigationBarItems(leading:
// MeshtasticLogo(), trailing:
// ZStack {
// ConnectedDevice(
// bluetoothOn: bleManager.isSwitchedOn,
// deviceConnected: bleManager.connectedPeripheral != nil,
// name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName :
// "?")
// })
// .onAppear(perform: {
// UIApplication.shared.isIdleTimerDisabled = true
// })
// .onDisappear(perform: {
// UIApplication.shared.isIdleTimerDisabled = false
// })
// }
//}

View file

@ -65,8 +65,6 @@ struct PositionLog: View {
.width(min: 180)
}
.textSelection(.enabled)
} else {
ScrollView {
// Use a grid on iOS as a table only shows a single column

View file

@ -14,7 +14,6 @@ struct AboutMeshtastic: View {
var body: some View {
VStack {
List {
Section(header: Text("What is Meshtastic?")) {
Text("An open source, off-grid, decentralized, mesh network that runs on affordable, low-power radios.")
@ -44,7 +43,7 @@ struct AboutMeshtastic: View {
Button("Review the app") {
if let scene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
SKStoreReviewController.requestReview(in: scene)
AppStore.requestReview(in: scene)
}
}
.font(.title2)

View file

@ -270,4 +270,4 @@ extension AppLog {
}
}
extension OSLogEntry: Identifiable { }
extension OSLogEntry: @retroactive Identifiable { }

View file

@ -43,7 +43,7 @@ struct AppSettings: View {
Button {
isPresentingCoreDataResetConfirm = true
} label: {
Label("clear.app.data", systemImage: "trash")
Label("Clear App Data", systemImage: "trash")
.foregroundColor(.red)
}
.confirmationDialog(

View file

@ -230,7 +230,7 @@ struct Channels: View {
Button {
goBack()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
@ -279,7 +279,7 @@ struct Channels: View {
.padding()
}
}
.navigationTitle("channels")
.navigationTitle("Channels")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")

View file

@ -30,7 +30,7 @@ struct ChannelForm: View {
Form {
Section(header: Text("channel details")) {
HStack {
Text("name")
Text("Name")
Spacer()
TextField(
"Channel Name",
@ -170,7 +170,7 @@ struct ChannelForm: View {
}
}
}
Section(header: Text("mqtt")) {
Section(header: Text("MQTT")) {
Toggle(isOn: $uplink) {
Label("Uplink Enabled", systemImage: "arrowshape.up")
}

View file

@ -30,11 +30,9 @@ struct DeviceConfig: View {
@State var ledHeartbeatEnabled = true
@State var tripleClickAsAdHocPing = true
@State var tzdef = ""
@State private var showRouterWarning = false
@State private var confirmWarning = false
var body: some View {
VStack {
Form {

View file

@ -388,7 +388,6 @@ struct MQTTConfig: View {
}
if let placemarks = placemarks, let placemark = placemarks.first {
let cc = locale.region?.identifier ?? "UNK"
/// Country Topic unless your region is a country
if !(region?.isCountry ?? false) {
let countryTopic = defaultTopic + "/" + (placemark.isoCountryCode ?? "")

View file

@ -53,7 +53,7 @@ struct SerialConfig: View {
}
.pickerStyle(DefaultPickerStyle())
.listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/)
Picker("timeout", selection: $timeout ) {
Picker("Timeout", selection: $timeout ) {
ForEach(SerialTimeoutIntervals.allCases) { sti in
Text(sti.description)
}

View file

@ -139,7 +139,6 @@ struct PositionConfig: View {
ForEach(GpsMode.allCases, id: \.self) { at in
Text(at.description)
.tag(at.id)
}
}
.pickerStyle(SegmentedPickerStyle())
@ -210,7 +209,7 @@ struct PositionConfig: View {
}
Toggle(isOn: $includeTimestamp) { // 128
Label("timestamp", systemImage: "clock")
Label("Timestamp", systemImage: "clock")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.onChange(of: includeTimestamp) { _, newIncludeTimestamp in
@ -314,7 +313,6 @@ struct PositionConfig: View {
.font(.caption)
}
}
var saveButton: some View {
SaveConfigButton(node: node, hasChanges: $hasChanges) {
if fixedPosition && !supportedVersion {
@ -399,11 +397,7 @@ struct PositionConfig: View {
.navigationTitle("position.config")
.navigationBarItems(
trailing: ZStack {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: bleManager.connectedPeripheral?.shortName ?? "?"
)
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: bleManager.connectedPeripheral?.shortName ?? "?")
}
)
.onFirstAppear {

View file

@ -136,7 +136,7 @@ struct AppLogFilter: View {
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -151,7 +151,7 @@ struct LogDetail: View {
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -250,7 +250,7 @@ struct RouteRecorder: View {
}
isShowingDetails = false
} label: {
Label("finish", systemImage: "flag.checkered")
Label("Finish", systemImage: "flag.checkered")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
@ -261,7 +261,7 @@ struct RouteRecorder: View {
Button(role: .cancel) {
isShowingDetails = false
} label: {
Label("close", systemImage: "xmark")
Label("Close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -78,7 +78,7 @@ struct Settings: View {
NavigationLink(value: SettingsNavigationState.channels) {
Label {
Text("channels")
Text("Channels")
} icon: {
Image(systemName: "fibrechannel")
}
@ -209,7 +209,7 @@ struct Settings: View {
if isModuleSupported(.mqttConfig) {
NavigationLink(value: SettingsNavigationState.mqtt) {
Label {
Text("mqtt")
Text("MQTT")
} icon: {
Image(systemName: "dot.radiowaves.up.forward")
}
@ -542,7 +542,7 @@ struct Settings: View {
}
}
}
.navigationTitle("settings")
.navigationTitle("Settings")
.navigationBarItems(
leading: MeshtasticLogo().onLongPressGesture(minimumDuration: 1.0) {
self.moduleOverride.toggle()

View file

@ -62,7 +62,7 @@ struct ShareChannels: View {
Grid {
GridRow {
Spacer()
Text("include")
Text("Include")
.font(.caption)
.fontWeight(.bold)
.padding(.trailing)
@ -70,7 +70,7 @@ struct ShareChannels: View {
.font(.caption)
.fontWeight(.bold)
.padding(.trailing)
Text("encrypted")
Text("Encrypted")
.font(.caption)
.fontWeight(.bold)
}

View file

@ -224,7 +224,7 @@ struct NodeInfoView: View {
.foregroundStyle(.secondary)
.opacity(isLuminanceReduced ? 0.8 : 1.0)
.fixedSize()
let now = Date()
Text("Last Heard: \(now.formatted())")
.font(.caption)