mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
commit
e3c0040f26
64 changed files with 627 additions and 1767 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -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 = "";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1600"
|
||||
LastUpgradeVersion = "1630"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1600"
|
||||
LastUpgradeVersion = "1630"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) ?? ""
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ struct InvalidVersion: View {
|
|||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
Label("Close", systemImage: "xmark")
|
||||
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -39,4 +39,3 @@ struct WeatherConditionsCompactWidget: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ struct DirectMessagesHelp: View {
|
|||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
Label("Close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -154,6 +154,6 @@ struct ChannelList: View {
|
|||
.listStyle(.plain)
|
||||
}
|
||||
}
|
||||
.navigationTitle("channels")
|
||||
.navigationTitle("Channels")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ struct MessageText: View {
|
|||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
|
||||
}
|
||||
.contextMenu {
|
||||
MessageContextMenuItems(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))))%")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ Spacer()
|
|||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
Label("Close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -220,7 +220,6 @@ extension MetricsColumnList {
|
|||
)
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Rainfall 24-hour
|
||||
MetricsTableColumn(
|
||||
id: "rainfall24H",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}),
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ struct MetricsColumnDetail: View {
|
|||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
Label("Close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ struct NodeListFilter: View {
|
|||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
Label("Close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -270,4 +270,4 @@ extension AppLog {
|
|||
}
|
||||
}
|
||||
|
||||
extension OSLogEntry: Identifiable { }
|
||||
extension OSLogEntry: @retroactive Identifiable { }
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 : "?")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 ?? "")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ struct AppLogFilter: View {
|
|||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
Label("Close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ struct LogDetail: View {
|
|||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
Label("Close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue