From 4d547e48db2a74e153d81d93ce75088c025c65d6 Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Wed, 29 May 2024 16:40:07 -0500 Subject: [PATCH] This change fixes several lint errors throughout the project, and moves the SwiftLint build phase to before compilation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After this change, a developer can now clone the project and run without the build failing due to lint errors! πŸ˜ƒ * I ran `swiftlint --fix` to resolve many auto-correctable issues (mostly whitespace) * Excluded the `Meshtastic/Protobufs` directory from lint, since that code is automatically generated. * Converted some single letter method parameters to lowercase. * Converted several instances `force_cast` to instead use `guard` or `if let` to unwrap optional values. During this change, some of the SwiftUI views became "too complex to be solved in a reasonable time", so I broke up the views into distinct sub-expressions. I was able to build and run the app on an iOS simulator. --- .swiftlint.yml | 5 + Meshtastic.xcodeproj/project.pbxproj | 3 +- Meshtastic/Enums/DeviceEnums.swift | 6 +- Meshtastic/Enums/LoraConfigEnums.swift | 2 +- Meshtastic/Enums/PositionConfigEnums.swift | 4 +- Meshtastic/Enums/RouteEnums.swift | 4 +- Meshtastic/Enums/TelemetryEnums.swift | 8 +- Meshtastic/Export/WriteCsvFile.swift | 1 - Meshtastic/Extensions/Bundle.swift | 20 +- .../Extensions/CLLocationCoordinate2D.swift | 14 +- .../CoreData/ChannelEntityExtension.swift | 8 +- .../CoreData/LocationEntityExtension.swift | 1 - .../CoreData/MyInfoEntityExtension.swift | 8 +- .../CoreData/NodeInfoEntityExtension.swift | 23 +- .../CoreData/PositionEntityExtension.swift | 16 +- .../CoreData/UserEntityExtension.swift | 10 +- .../CoreData/WaypointEntityExtension.swift | 8 +- Meshtastic/Extensions/Date.swift | 12 +- Meshtastic/Extensions/Measurement.swift | 2 +- Meshtastic/Extensions/UIColor.swift | 4 +- Meshtastic/Extensions/Url.swift | 4 +- Meshtastic/Extensions/UserDefaults.swift | 12 +- Meshtastic/Helpers/BLEManager.swift | 824 +++++++++--------- Meshtastic/Helpers/EmojiOnlyTextField.swift | 4 - .../Helpers/LocalNotificationManager.swift | 2 +- Meshtastic/Helpers/LocationHelper.swift | 8 +- Meshtastic/Helpers/LocationsHandler.swift | 24 +- Meshtastic/Helpers/MeshPackets.swift | 40 +- .../Helpers/Mqtt/MqttClientProxyManager.swift | 2 +- Meshtastic/MeshtasticApp.swift | 23 +- Meshtastic/MeshtasticAppDelegate.swift | 8 +- Meshtastic/Persistence/Persistence.swift | 2 +- Meshtastic/Persistence/QueryCoreData.swift | 2 +- Meshtastic/Persistence/UpdateCoreData.swift | 32 +- Meshtastic/Tips/MessagesTips.swift | 4 +- Meshtastic/Views/Bluetooth/Connect.swift | 10 +- Meshtastic/Views/ContentView.swift | 8 +- Meshtastic/Views/Helpers/BatteryGauge.swift | 6 +- .../Views/Helpers/BatteryLevelCompact.swift | 10 +- Meshtastic/Views/Helpers/CircleText.swift | 9 +- .../Views/Helpers/ConnectedDevice.swift | 8 +- Meshtastic/Views/Helpers/DateTimeText.swift | 2 +- Meshtastic/Views/Helpers/LastHeardText.swift | 4 +- .../Views/Helpers/LoRaSignalStrength.swift | 4 +- Meshtastic/Views/Helpers/MQTTIcon.swift | 4 +- .../Helpers/Weather/AirQualityIndex.swift | 16 +- .../Views/Helpers/Weather/IAQScale.swift | 2 +- .../Helpers/Weather/IndoorAirQuality.swift | 12 +- .../Views/MapKitMap/Custom/MapButtons.swift | 4 +- .../MapKitMap/Custom/MapViewSwiftUI.swift | 2 +- .../Views/MapKitMap/NodeMapMapkit.swift | 12 +- Meshtastic/Views/Messages/ChannelList.swift | 232 ++--- .../Views/Messages/ChannelMessageList.swift | 8 +- .../Messages/MessageContextMenuItems.swift | 4 +- Meshtastic/Views/Messages/MessageText.swift | 2 +- Meshtastic/Views/Messages/Messages.swift | 17 +- .../Views/Messages/TapbackResponses.swift | 4 +- .../TextMessageField/TextMessageField.swift | 12 +- .../TextMessageField/TextMessageSize.swift | 2 +- Meshtastic/Views/Messages/UserList.swift | 40 +- .../Views/Nodes/EnvironmentMetricsLog.swift | 12 +- .../Map/MapContent/MeshMapContent.swift | 98 ++- .../Map/MapContent/NodeMapContent.swift | 22 +- .../Nodes/Helpers/Map/MapSettingsForm.swift | 8 +- .../Nodes/Helpers/Map/NodeMapSwiftUI.swift | 10 +- .../Helpers/Map/PositionAltitudeChart.swift | 24 +- .../Nodes/Helpers/Map/PositionPopover.swift | 14 +- .../Nodes/Helpers/Map/WaypointForm.swift | 25 +- .../Views/Nodes/Helpers/NodeDetail.swift | 20 +- Meshtastic/Views/Nodes/Helpers/NodeInfo.swift | 4 +- .../Views/Nodes/Helpers/NodeInfoItem.swift | 4 +- .../Views/Nodes/Helpers/NodeListFilter.swift | 22 +- .../Views/Nodes/Helpers/NodeListItem.swift | 51 +- Meshtastic/Views/Nodes/MeshMap.swift | 21 +- Meshtastic/Views/Nodes/NodeList.swift | 61 +- Meshtastic/Views/Nodes/PaxCounterLog.swift | 6 +- Meshtastic/Views/Nodes/PositionLog.swift | 34 +- Meshtastic/Views/Nodes/TraceRouteLog.swift | 29 +- Meshtastic/Views/Settings/About.swift | 4 +- Meshtastic/Views/Settings/AppSettings.swift | 2 +- Meshtastic/Views/Settings/Channels.swift | 33 +- .../Views/Settings/Channels/ChannelForm.swift | 25 +- .../Views/Settings/Config/DeviceConfig.swift | 14 +- .../Views/Settings/Config/DisplayConfig.swift | 14 +- .../Views/Settings/Config/LoRaConfig.swift | 12 +- .../Config/Module/AmbientLightingConfig.swift | 6 +- .../Config/Module/CannedMessagesConfig.swift | 8 +- .../Config/Module/DetectionSensorConfig.swift | 12 +- .../Module/ExternalNotificationConfig.swift | 18 +- .../Settings/Config/Module/MQTTConfig.swift | 51 +- .../Config/Module/PaxCounterConfig.swift | 6 +- .../Config/Module/RangeTestConfig.swift | 6 +- .../Settings/Config/Module/RtttlConfig.swift | 2 +- .../Settings/Config/Module/SerialConfig.swift | 4 +- .../Config/Module/StoreForwardConfig.swift | 10 +- .../Config/Module/TelemetryConfig.swift | 5 +- .../Views/Settings/Config/NetworkConfig.swift | 10 +- .../Settings/Config/PositionConfig.swift | 17 +- .../Views/Settings/Config/PowerConfig.swift | 12 +- Meshtastic/Views/Settings/Firmware.swift | 24 +- Meshtastic/Views/Settings/FirmwareApi.swift | 14 +- Meshtastic/Views/Settings/GPSStatus.swift | 10 +- Meshtastic/Views/Settings/RouteRecorder.swift | 26 +- Meshtastic/Views/Settings/Routes.swift | 48 +- .../Views/Settings/SaveChannelQRCode.swift | 1 - Meshtastic/Views/Settings/Settings.swift | 11 +- Meshtastic/Views/Settings/ShareChannels.swift | 4 +- Meshtastic/Views/Settings/UserConfig.swift | 6 +- 108 files changed, 1231 insertions(+), 1233 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 9f3ff810..665e90cc 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,7 @@ +# Exclude automatically generated Swift files +excluded: + - Meshtastic/Protobufs + line_length: 400 type_name: @@ -46,3 +50,4 @@ disabled_rules: # rule identifiers to exclude from running nesting: type_level: warning: 3 + diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 159970c5..2f62a9ee 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1032,10 +1032,10 @@ isa = PBXNativeTarget; buildConfigurationList = DDC2E17E26CE248F0042C5E4 /* Build configuration list for PBXNativeTarget "Meshtastic" */; buildPhases = ( + BB450974275599CE00509624 /* ShellScript */, DDC2E15026CE248E0042C5E4 /* Sources */, DDC2E15126CE248E0042C5E4 /* Frameworks */, DDC2E15226CE248E0042C5E4 /* Resources */, - BB450974275599CE00509624 /* ShellScript */, DDDE5A0829AF163F00490C6C /* Embed Foundation Extensions */, ); buildRules = ( @@ -1177,6 +1177,7 @@ /* Begin PBXShellScriptBuildPhase section */ BB450974275599CE00509624 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); diff --git a/Meshtastic/Enums/DeviceEnums.swift b/Meshtastic/Enums/DeviceEnums.swift index dbbc6dc2..a17144fa 100644 --- a/Meshtastic/Enums/DeviceEnums.swift +++ b/Meshtastic/Enums/DeviceEnums.swift @@ -21,7 +21,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { case repeater = 4 case router = 2 case routerClient = 3 - + var id: Int { self.rawValue } var name: String { switch self { @@ -48,7 +48,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { case .lostAndFound: return "Lost and Found" } - + } var description: String { switch self { @@ -76,7 +76,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { return "device.role.lostandfound".localized } } - + var systemName: String { switch self { case .client: diff --git a/Meshtastic/Enums/LoraConfigEnums.swift b/Meshtastic/Enums/LoraConfigEnums.swift index 781661c6..6954dfac 100644 --- a/Meshtastic/Enums/LoraConfigEnums.swift +++ b/Meshtastic/Enums/LoraConfigEnums.swift @@ -28,7 +28,7 @@ enum RegionCodes: Int, CaseIterable, Identifiable { case my_919 = 17 case sg_923 = 18 case lora24 = 13 - var topic: String { + var topic: String { switch self { case .unset: "UNSET" diff --git a/Meshtastic/Enums/PositionConfigEnums.swift b/Meshtastic/Enums/PositionConfigEnums.swift index a9958c08..71b6727f 100644 --- a/Meshtastic/Enums/PositionConfigEnums.swift +++ b/Meshtastic/Enums/PositionConfigEnums.swift @@ -103,9 +103,9 @@ enum GpsMode: Int, CaseIterable, Equatable { case enabled = 1 case disabled = 0 case notPresent = 2 - + var id: Int { self.rawValue } - + var description: String { switch self { case .disabled: diff --git a/Meshtastic/Enums/RouteEnums.swift b/Meshtastic/Enums/RouteEnums.swift index ab2451cc..052d0407 100644 --- a/Meshtastic/Enums/RouteEnums.swift +++ b/Meshtastic/Enums/RouteEnums.swift @@ -15,7 +15,7 @@ enum ActivityType: Int, CaseIterable, Identifiable { case driving = 3 case overlanding = 4 case skiing = 5 - + var id: Int { self.rawValue } var description: String { switch self { @@ -33,7 +33,7 @@ enum ActivityType: Int, CaseIterable, Identifiable { return "routes.activitytype.skiing".localized } } - + var fileNameString: String { switch self { case .walking: diff --git a/Meshtastic/Enums/TelemetryEnums.swift b/Meshtastic/Enums/TelemetryEnums.swift index 1e9f54fd..187a8d74 100644 --- a/Meshtastic/Enums/TelemetryEnums.swift +++ b/Meshtastic/Enums/TelemetryEnums.swift @@ -15,7 +15,7 @@ enum Aqi: Int, CaseIterable, Identifiable { case unhealthy = 3 case veryUnhealthy = 4 case hazardous = 5 - + var id: Int { self.rawValue } var description: String { switch self { @@ -65,7 +65,7 @@ enum Aqi: Int, CaseIterable, Identifiable { return Range(301...500) } } - + static func getAqi(for value: Int) -> Aqi { let aqi: Aqi switch value { @@ -96,7 +96,7 @@ enum Iaq: Int, CaseIterable, Identifiable { case heavilyPolluted = 4 case severelyPolluted = 5 case extremelyPolluted = 6 - + var id: Int { self.rawValue } var description: String { switch self { @@ -134,7 +134,7 @@ enum Iaq: Int, CaseIterable, Identifiable { return .brown } } - + var range: Range { switch self { case .excellent: diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index 1048846c..335b8544 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -118,7 +118,6 @@ func positionToCsvFile(positions: [PositionEntity]) -> String { return csvString } - func routeToCsvFile(locations: [LocationEntity]) -> String { var csvString: String = "" let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) diff --git a/Meshtastic/Extensions/Bundle.swift b/Meshtastic/Extensions/Bundle.swift index b3bd85bf..84353a51 100644 --- a/Meshtastic/Extensions/Bundle.swift +++ b/Meshtastic/Extensions/Bundle.swift @@ -8,15 +8,15 @@ import Foundation extension Bundle { - public var appName: String { getInfo("CFBundleName") } - public var displayName: String { getInfo("CFBundleDisplayName") } - public var language: String { getInfo("CFBundleDevelopmentRegion") } - public var identifier: String { getInfo("CFBundleIdentifier") } - public var copyright: String { getInfo("NSHumanReadableCopyright").replacingOccurrences(of: "\\\\n", with: "\n") } - - public var appBuild: String { getInfo("CFBundleVersion") } - public var appVersionLong: String { getInfo("CFBundleShortVersionString") } - //public var appVersionShort: String { getInfo("CFBundleShortVersion") } - + public var appName: String { getInfo("CFBundleName") } + public var displayName: String { getInfo("CFBundleDisplayName") } + public var language: String { getInfo("CFBundleDevelopmentRegion") } + public var identifier: String { getInfo("CFBundleIdentifier") } + public var copyright: String { getInfo("NSHumanReadableCopyright").replacingOccurrences(of: "\\\\n", with: "\n") } + + public var appBuild: String { getInfo("CFBundleVersion") } + public var appVersionLong: String { getInfo("CFBundleShortVersionString") } + // public var appVersionShort: String { getInfo("CFBundleShortVersion") } + fileprivate func getInfo(_ str: String) -> String { infoDictionary?[str] as? String ?? "⚠️" } } diff --git a/Meshtastic/Extensions/CLLocationCoordinate2D.swift b/Meshtastic/Extensions/CLLocationCoordinate2D.swift index 58287add..2d681159 100644 --- a/Meshtastic/Extensions/CLLocationCoordinate2D.swift +++ b/Meshtastic/Extensions/CLLocationCoordinate2D.swift @@ -28,19 +28,19 @@ extension [CLLocationCoordinate2D] { /// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product. /// Returns a positive value, if OAB makes a counter-clockwise turn, /// negative for clockwise turn, and zero if the points are collinear. - func cross(P: CLLocationCoordinate2D, A: CLLocationCoordinate2D, B: CLLocationCoordinate2D) -> Double { - let part1 = (A.longitude - P.longitude) * (B.latitude - P.latitude) - let part2 = (A.latitude - P.latitude) * (B.longitude - P.longitude) - return part1 - part2; + func cross(p: CLLocationCoordinate2D, a: CLLocationCoordinate2D, b: CLLocationCoordinate2D) -> Double { + let part1 = (a.longitude - p.longitude) * (b.latitude - p.latitude) + let part2 = (a.latitude - p.latitude) * (b.longitude - p.longitude) + return part1 - part2 } // Sort points lexicographically - let points = self.sorted() { + let points = self.sorted { $0.longitude == $1.longitude ? $0.latitude < $1.latitude : $0.longitude < $1.longitude } // Build the lower hull var lower: [CLLocationCoordinate2D] = [] for p in points { - while lower.count >= 2 && cross(P: lower[lower.count - 2], A: lower[lower.count - 1], B: p) <= 0 { + while lower.count >= 2 && cross(p: lower[lower.count - 2], a: lower[lower.count - 1], b: p) <= 0 { lower.removeLast() } lower.append(p) @@ -48,7 +48,7 @@ extension [CLLocationCoordinate2D] { // Build upper hull var upper: [CLLocationCoordinate2D] = [] for p in points.reversed() { - while upper.count >= 2 && cross(P: upper[upper.count-2], A: upper[upper.count-1], B: p) <= 0 { + while upper.count >= 2 && cross(p: upper[upper.count-2], a: upper[upper.count-1], b: p) <= 0 { upper.removeLast() } upper.append(p) diff --git a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift index e5a4d95a..2568943a 100644 --- a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift @@ -12,13 +12,13 @@ extension ChannelEntity { self.value(forKey: "allPrivateMessages") as? [MessageEntity] ?? [MessageEntity]() } - + var unreadMessages: Int { - - let unreadMessages = allPrivateMessages.filter{ ($0 as AnyObject).read == false } + + let unreadMessages = allPrivateMessages.filter { ($0 as AnyObject).read == false } return unreadMessages.count } - + var protoBuf: Channel { var channel = Channel() channel.index = self.index diff --git a/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift b/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift index 310fe626..5b35f620 100644 --- a/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift @@ -39,4 +39,3 @@ extension LocationEntity { } } } - diff --git a/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift index 888df18e..80ba19c8 100644 --- a/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift @@ -8,17 +8,17 @@ import Foundation extension MyInfoEntity { - + var messageList: [MessageEntity] { self.value(forKey: "allMessages") as? [MessageEntity] ?? [MessageEntity]() } - + var unreadMessages: Int { - let unreadMessages = messageList.filter{ ($0 as AnyObject).read == false && ($0 as AnyObject).isEmoji == false } + let unreadMessages = messageList.filter { ($0 as AnyObject).read == false && ($0 as AnyObject).isEmoji == false } return unreadMessages.count } var hasAdmin: Bool { - let adminChannel = channels?.filter{ ($0 as AnyObject).name?.lowercased() == "admin" } + let adminChannel = channels?.filter { ($0 as AnyObject).name?.lowercased() == "admin" } return adminChannel?.count ?? 0 > 0 } } diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index 2f42ad06..9258b424 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -9,37 +9,36 @@ import Foundation import CoreData extension NodeInfoEntity { - + var hasPositions: Bool { return positions?.count ?? 0 > 0 } - + var hasDeviceMetrics: Bool { - let deviceMetrics = telemetries?.filter{ ($0 as AnyObject).metricsType == 0 } + let deviceMetrics = telemetries?.filter { ($0 as AnyObject).metricsType == 0 } return deviceMetrics?.count ?? 0 > 0 } - + var hasEnvironmentMetrics: Bool { - let environmentMetrics = telemetries?.filter{ ($0 as AnyObject).metricsType == 1 } + let environmentMetrics = telemetries?.filter { ($0 as AnyObject).metricsType == 1 } return environmentMetrics?.count ?? 0 > 0 } var hasDetectionSensorMetrics: Bool { return user?.sensorMessageList.count ?? 0 > 0 } - + var hasTraceRoutes: Bool { return traceRoutes?.count ?? 0 > 0 } - + var hasPax: Bool { return pax?.count ?? 0 > 0 } - - + var isStoreForwardRouter: Bool { return storeForwardConfig?.isRouter ?? false } - + var isOnline: Bool { let fifteenMinutesAgo = Calendar.current.date(byAdding: .minute, value: -15, to: Date()) if lastHeard?.compare(fifteenMinutesAgo!) == .orderedDescending { @@ -50,13 +49,13 @@ extension NodeInfoEntity { } public func createNodeInfo(num: Int64, context: NSManagedObjectContext) -> NodeInfoEntity { - + let newNode = NodeInfoEntity(context: context) newNode.id = Int64(num) newNode.num = Int64(num) let newUser = UserEntity(context: context) newUser.num = Int64(num) - let userId = String(format:"%2X", num) + let userId = String(format: "%2X", num) newUser.userId = "!\(userId)" let last4 = String(userId.suffix(4)) newUser.longName = "Meshtastic \(last4)" diff --git a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift index bac4f840..0c634779 100644 --- a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift @@ -11,7 +11,7 @@ import MapKit import SwiftUI extension PositionEntity { - + static func allPositionsFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = PositionEntity.fetchRequest() request.fetchLimit = 1000 @@ -20,20 +20,20 @@ extension PositionEntity { request.returnsDistinctResults = true request.sortDescriptors = [NSSortDescriptor(key: "time", ascending: false)] let positionPredicate = NSPredicate(format: "nodePosition != nil && (nodePosition.user.shortName != nil || nodePosition.user.shortName != '') && latest == true") - + let pointOfInterest = LocationHelper.currentLocation - + if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude { - let D: Double = UserDefaults.meshMapDistance * 1.1 - let R: Double = 6371009 + let d: Double = UserDefaults.meshMapDistance * 1.1 + let r: Double = 6371009 let meanLatitidue = pointOfInterest.latitude * .pi / 180 - let deltaLatitude = D / R * 180 / .pi - let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi + let deltaLatitude = d / r * 180 / .pi + let deltaLongitude = d / (r * cos(meanLatitidue)) * 180 / .pi let minLatitude: Double = pointOfInterest.latitude - deltaLatitude let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude let minLongitude: Double = pointOfInterest.longitude - deltaLongitude let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude - let distancePredicate = NSPredicate(format: "(%lf <= (longitudeI / 1e7)) AND ((longitudeI / 1e7) <= %lf) AND (%lf <= (latitudeI / 1e7)) AND ((latitudeI / 1e7) <= %lf)", minLongitude, maxLongitude,minLatitude, maxLatitude) + let distancePredicate = NSPredicate(format: "(%lf <= (longitudeI / 1e7)) AND ((longitudeI / 1e7) <= %lf) AND (%lf <= (latitudeI / 1e7)) AND ((latitudeI / 1e7) <= %lf)", minLongitude, maxLongitude, minLatitude, maxLatitude) request.predicate = NSCompoundPredicate(type: .and, subpredicates: [positionPredicate, distancePredicate]) } else { request.predicate = positionPredicate diff --git a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift index e4887c78..a8adeaaf 100644 --- a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift @@ -9,7 +9,6 @@ import Foundation import CoreData extension UserEntity { - var messageList: [MessageEntity] { self.value(forKey: "allMessages") as? [MessageEntity] ?? [MessageEntity]() @@ -18,22 +17,21 @@ extension UserEntity { var adminMessageList: [MessageEntity] { self.value(forKey: "adminMessages") as? [MessageEntity] ?? [MessageEntity]() } - + var sensorMessageList: [MessageEntity] { self.value(forKey: "detectionSensorMessages") as? [MessageEntity] ?? [MessageEntity]() } - + var unreadMessages: Int { - let unreadMessages = messageList.filter{ ($0 as AnyObject).read == false } + let unreadMessages = messageList.filter { ($0 as AnyObject).read == false } return unreadMessages.count } } - public func createUser(num: Int64, context: NSManagedObjectContext) -> UserEntity { let newUser = UserEntity(context: context) newUser.num = Int64(num) - let userId = String(format:"%2X", num) + let userId = String(format: "%2X", num) newUser.userId = "!\(userId)" let last4 = String(userId.suffix(4)) newUser.longName = "Meshtastic \(last4)" diff --git a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift index 0c3d6854..b17ccecf 100644 --- a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift @@ -10,13 +10,13 @@ import MapKit import SwiftUI extension WaypointEntity { - + static func allWaypointssFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = WaypointEntity.fetchRequest() request.fetchLimit = 50 - //request.fetchBatchSize = 1 - //request.returnsObjectsAsFaults = false - //request.includesSubentities = true + // request.fetchBatchSize = 1 + // request.returnsObjectsAsFaults = false + // request.includesSubentities = true request.returnsDistinctResults = true request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: false)] request.predicate = NSPredicate(format: "expire == nil || expire >= %@", Date() as NSDate) diff --git a/Meshtastic/Extensions/Date.swift b/Meshtastic/Extensions/Date.swift index 2cb46f53..7fc72b33 100644 --- a/Meshtastic/Extensions/Date.swift +++ b/Meshtastic/Extensions/Date.swift @@ -8,7 +8,7 @@ import Foundation extension Date { - + func formattedDate(format: String) -> String { let dateformat = DateFormatter() dateformat.dateFormat = format @@ -20,12 +20,12 @@ extension Date { } func relativeTimeOfDay() -> String { let hour = Calendar.current.component(.hour, from: self) - + switch hour { - case 6..<12 : return "relativetimeofday.morning".localized - case 12 : return "relativetimeofday.midday".localized - case 13..<17 : return "relativetimeofday.afternoon".localized - case 17..<22 : return "relativetimeofday.evening".localized + case 6..<12: return "relativetimeofday.morning".localized + case 12: return "relativetimeofday.midday".localized + case 13..<17: return "relativetimeofday.afternoon".localized + case 17..<22: return "relativetimeofday.evening".localized default: return "relativetimeofday.nighttime".localized } } diff --git a/Meshtastic/Extensions/Measurement.swift b/Meshtastic/Extensions/Measurement.swift index 9fb3eec0..ca867c09 100644 --- a/Meshtastic/Extensions/Measurement.swift +++ b/Meshtastic/Extensions/Measurement.swift @@ -16,7 +16,7 @@ extension PlottableMeasurement: Plottable where UnitType == UnitLength { var primitivePlottable: Double { self.measurement.converted(to: .meters).value } - + init?(primitivePlottable: Double) { self.init( measurement: Measurement( diff --git a/Meshtastic/Extensions/UIColor.swift b/Meshtastic/Extensions/UIColor.swift index d9160015..558631de 100644 --- a/Meshtastic/Extensions/UIColor.swift +++ b/Meshtastic/Extensions/UIColor.swift @@ -16,7 +16,7 @@ extension UIColor { var green: CGFloat = 0 var alpha: CGFloat = 0 - getRed(&red, green: &green, blue: &blue, alpha: &alpha) + getRed(&red, green: &green, blue: &blue, alpha: &alpha) return UIColor( red: add(componentDelta, toComponent: red), @@ -37,7 +37,7 @@ extension UIColor { private func add(_ value: CGFloat, toComponent: CGFloat) -> CGFloat { return max(0, min(1, toComponent + value)) } - + static var random: UIColor { return UIColor( red: .random(in: 0...1), diff --git a/Meshtastic/Extensions/Url.swift b/Meshtastic/Extensions/Url.swift index 20d3ca6e..6d594dd9 100644 --- a/Meshtastic/Extensions/Url.swift +++ b/Meshtastic/Extensions/Url.swift @@ -8,10 +8,10 @@ import Foundation extension URL { - + func regularFileAllocatedSize() throws -> UInt64 { let resourceValues = try self.resourceValues(forKeys: allocatedSizeResourceKeys) - + guard resourceValues.isRegularFile ?? false else { return 0 } diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index d4b7c098..b94a0776 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -29,7 +29,7 @@ struct UserDefault { return value } - + return UserDefaults.standard.object(forKey: key.rawValue) as? T ?? defaultValue } set { @@ -95,10 +95,10 @@ extension UserDefaults { @UserDefault(.meshMapDistance, defaultValue: 800000) static var meshMapDistance: Double - + @UserDefault(.enableMapWaypoints, defaultValue: false) static var enableMapWaypoints: Bool - + @UserDefault(.enableMapRecentering, defaultValue: false) static var enableMapRecentering: Bool @@ -146,13 +146,13 @@ extension UserDefaults { @UserDefault(.enableSmartPosition, defaultValue: false) static var enableSmartPosition: Bool - + @UserDefault(.channelMessageNotifications, defaultValue: true) static var channelMessageNotifications: Bool - + @UserDefault(.newNodeNotifications, defaultValue: true) static var newNodeNotifications: Bool - + @UserDefault(.lowBatteryNotifications, defaultValue: true) static var lowBatteryNotifications: Bool diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 729a6fa0..2ce38529 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -9,7 +9,7 @@ import CocoaMQTT // Meshtastic BLE Device Manager // --------------------------------------------------------------------------------------- class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate, ObservableObject { - + private static var documentsFolder: URL { do { return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) @@ -18,7 +18,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } var context: NSManagedObjectContext? - + static let shared = BLEManager() private var centralManager: CBCentralManager! @Published var peripherals: [Peripheral] = [] @@ -52,9 +52,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let FROMRADIO_UUID = CBUUID(string: "0x2C55E69E-4993-11ED-B878-0242AC120002") let EOL_FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5") let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453") - + let meshLog = documentsFolder.appendingPathComponent("meshlog.txt") - + // MARK: init BLEManager override init() { self.lastConnectionError = "" @@ -64,7 +64,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate mqttManager.delegate = self // centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: restoreKey]) } - + // MARK: Scanning for BLE Devices // Scan for nearby BLE devices using the Meshtastic BLE service ID func startScanning() { @@ -73,7 +73,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate print("βœ… Scanning Started") } } - + // Stop Scanning For BLE Devices func stopScanning() { if centralManager.isScanning { @@ -81,7 +81,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate print("πŸ›‘ Stopped Scanning") } } - + // MARK: BLE Connect functions /// The action after the timeout-timer has fired /// @@ -91,17 +91,17 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate @objc func timeoutTimerFired(timer: Timer) { guard let timerContext = timer.userInfo as? [String: String] else { return } let name: String = timerContext["name", default: "Unknown"] - + self.timeoutTimerCount += 1 self.lastConnectionError = "" - + if timeoutTimerCount == 10 { if connectedPeripheral != nil { self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) } connectedPeripheral = nil if self.timeoutTimer != nil { - + self.timeoutTimer!.invalidate() } self.isConnected = false @@ -114,7 +114,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate print("🚨 BLE Connecting 2 Second Timeout Timer Fired \(timeoutTimerCount) Time(s): \(name)") } } - + // Connect to a specific peripheral func connectTo(peripheral: CBPeripheral) { stopScanning() @@ -127,7 +127,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate print("ℹ️ BLE Disconnecting from: \(connectedPeripheral.name) to connect to \(peripheral.name ?? "Unknown")") disconnectPeripheral() } - + centralManager?.connect(peripheral) // Invalidate any existing timer if timeoutTimer != nil { @@ -140,10 +140,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate RunLoop.current.add(timeoutTimer!, forMode: .common) print("ℹ️ BLE Connecting: \(peripheral.name ?? "Unknown")") } - + // Disconnect Connected Peripheral func cancelPeripheralConnection() { - + if mqttProxyConnected { mqttManager.mqttClientProxy?.disconnect() } @@ -162,10 +162,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate stopScanning() startScanning() } - + // Disconnect Connected Peripheral func disconnectPeripheral(reconnect: Bool = true) { - + guard let connectedPeripheral = connectedPeripheral else { return } if mqttProxyConnected { mqttManager.mqttClientProxy?.disconnect() @@ -180,7 +180,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate stopScanning() startScanning() } - + // Called each time a peripheral is connected func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { isConnecting = false @@ -193,7 +193,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if timeoutTimer != nil { timeoutTimer!.invalidate() } - + // remove any connection errors self.lastConnectionError = "" // Map the peripheral to the connectedPeripheral ObservedObjects @@ -210,13 +210,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate peripheral.discoverServices([meshtasticServiceCBUUID]) print("βœ… BLE Connected: \(peripheral.name ?? "Unknown")") } - + // Called when a Peripheral fails to connect func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { cancelPeripheralConnection() print("🚫 BLE Failed to Connect: \(peripheral.name ?? "Unknown")") } - + // Disconnect Peripheral Event func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { self.connectedPeripheral = nil @@ -277,7 +277,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake self.startScanning() } - + // MARK: Peripheral Services functions func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let e = error { @@ -291,36 +291,36 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } } - + // MARK: Discover Characteristics Event func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { - + if let e = error { print("🚫 BLE Discover Characteristics error for \(peripheral.name ?? "Unknown") \(e) disconnecting device") // Try and stop crashes when this error occurs disconnectPeripheral() return } - + guard let characteristics = service.characteristics else { return } - + for characteristic in characteristics { switch characteristic.uuid { - + case TORADIO_UUID: print("βœ… BLE did discover TORADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") TORADIO_characteristic = characteristic - + case FROMRADIO_UUID: print("βœ… BLE did discover FROMRADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") FROMRADIO_characteristic = characteristic peripheral.readValue(for: FROMRADIO_characteristic) - + case FROMNUM_UUID: print("βœ… BLE did discover FROMNUM (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") FROMNUM_characteristic = characteristic peripheral.setNotifyValue(true, for: characteristic) - + default: break } @@ -332,22 +332,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate sendWantConfig() } } - - // MARK: MqttClientProxyManagerDelegate Methods + + // MARK: MqttClientProxyManagerDelegate Methods func onMqttConnected() { mqttProxyConnected = true mqttError = "" print("πŸ“² Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).") mqttManager.mqttClientProxy?.subscribe(mqttManager.topic) } - + func onMqttDisconnected() { mqttProxyConnected = false print("MQTT Disconnected") } - + func onMqttMessageReceived(message: CocoaMQTTMessage) { - + if message.topic.contains("/stat/") { return } @@ -355,28 +355,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate proxyMessage.topic = message.topic proxyMessage.data = Data(message.payload) proxyMessage.retained = message.retained - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.mqttClientProxyMessage = proxyMessage let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - + } } - + func onMqttError(message: String) { mqttProxyConnected = false mqttError = message print("πŸ“² Mqtt Client Proxy onMqttError: \(message)") } - + // MARK: Protobuf Methods func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32, context: NSManagedObjectContext) -> Int64 { - + guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return 0 } - + var adminPacket = AdminMessage() adminPacket.getDeviceMetadataRequest = true var meshPacket: MeshPacket = MeshPacket() @@ -397,12 +397,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return 0 } - + func sendTraceRouteRequest(destNum: Int64, wantResponse: Bool) -> Bool { - + var success = false guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return success } - + let fromNodeNum = connectedPeripheral.num let routePacket = RouteDiscovery() var meshPacket = MeshPacket() @@ -415,16 +415,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.tracerouteApp dataMessage.wantResponse = wantResponse meshPacket.decoded = dataMessage - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - + let traceRoute = TraceRouteEntity(context: context!) let nodes: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") nodes.predicate = NSPredicate(format: "num IN %@", [destNum, self.connectedPeripheral.num]) @@ -438,8 +438,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRoute.time = Date() traceRoute.node = receivingNode // Grab the most recent postion, within the last hour - if connectedNode?.positions?.count ?? 0 > 0 { - let mostRecent = connectedNode?.positions?.lastObject as! PositionEntity + if connectedNode?.positions?.count ?? 0 > 0, let mostRecent = connectedNode?.positions?.lastObject as? PositionEntity { if mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { traceRoute.altitude = mostRecent.altitude traceRoute.latitudeI = mostRecent.latitudeI @@ -455,26 +454,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let nsError = error as NSError print("πŸ’₯ Error Updating Core Data BluetoothConfigEntity: \(nsError)") } - + let logString = String.localizedStringWithFormat("mesh.log.traceroute.sent %@".localized, String(destNum)) MeshLogger.log("πŸͺ§ \(logString)") - + } catch { - + } } return success } - + func sendWantConfig() { guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return } - + if FROMRADIO_characteristic == nil { MeshLogger.log("🚨 \("firmware.version.unsupported".localized)") invalidVersion = true return } else { - + let nodeName = connectedPeripheral?.peripheral.name ?? "unknown".localized let logString = String.localizedStringWithFormat("mesh.log.wantconfig %@".localized, nodeName) MeshLogger.log("πŸ›ŽοΈ \(logString)") @@ -489,18 +488,18 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate connectedPeripheral!.peripheral.readValue(for: FROMRADIO_characteristic) } } - + func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { if let errorText = error?.localizedDescription { print("🚫 didUpdateNotificationStateFor error: \(errorText)") } } - + // MARK: Data Read / Update Characteristic Event func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { - + if let e = error { - + print("🚫 didUpdateValueFor Characteristic error \(e)") let errorCode = (e as NSError).code if errorCode == 5 || errorCode == 15 { @@ -513,46 +512,46 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return } - + switch characteristic.uuid { - + case FROMRADIO_UUID: - + if characteristic.value == nil || characteristic.value!.isEmpty { return } var decodedInfo = FromRadio() - + do { decodedInfo = try FromRadio(serializedData: characteristic.value!) - + } catch { print(characteristic.value!) } - + // Publish mqttClientProxyMessages received on the from radio if decodedInfo.payloadVariant == FromRadio.OneOf_PayloadVariant.mqttClientProxyMessage(decodedInfo.mqttClientProxyMessage) { - let message = CocoaMQTTMessage ( + let message = CocoaMQTTMessage( topic: decodedInfo.mqttClientProxyMessage.topic, - payload: [UInt8](decodedInfo.mqttClientProxyMessage.data), + payload: [UInt8](decodedInfo.mqttClientProxyMessage.data), retained: decodedInfo.mqttClientProxyMessage.retained ) mqttManager.mqttClientProxy?.publish(message) } - + switch decodedInfo.packet.decoded.portnum { - + // Handle Any local only packets we get over BLE case .unknownApp: var nowKnown = false guard let ctx = context else { return } - + // MyInfo from initial connection if decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 { let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, context: ctx) - + if myInfo != nil { UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0) connectedPeripheral.num = myInfo?.myNodeNum ?? 0 @@ -584,7 +583,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate localConfig(config: decodedInfo.config, context: ctx, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral.longName) } // Module Config - if decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0{ + if decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0 { nowKnown = true moduleConfig(config: decodedInfo.moduleConfig, context: ctx, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName) if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) { @@ -636,10 +635,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("πŸ•ΈοΈ 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!) case .ipTunnelApp: - //MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for IP Tunnel App UNHANDLED UNHANDLED") case .serialApp: - //MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Serial App UNHANDLED UNHANDLED") case .storeForwardApp: if wantStoreAndForwardPackets { @@ -650,29 +649,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .rangeTestApp: if wantRangeTestPackets { textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: true, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) - } - else { + } else { MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Range Test App Range testing is disabled.") } case .telemetryApp: if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } case .textMessageCompressedApp: - //MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Text Message Compressed App UNHANDLED") case .zpsApp: // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Zero Positioning System App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Zero Positioning System App UNHANDLED") case .privateApp: - //MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Private App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Private App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Private App UNHANDLED UNHANDLED") case .atakForwarder: - //MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for ATAK Forwarder App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for ATAK Forwarder App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for ATAK Forwarder App UNHANDLED UNHANDLED") case .simulatorApp: - //MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Simulator App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Simulator App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Simulator App UNHANDLED UNHANDLED") case .audioApp: - //MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Audio App UNHANDLED UNHANDLED") case .tracerouteApp: if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { @@ -682,7 +680,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if routingMessage.route.count == 0 { let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(decodedInfo.packet.from)) MeshLogger.log("πŸͺ§ \(logString)") - + } else { var routeString = "You --> " var hopNodes: [TraceRouteHopEntity] = [] @@ -695,8 +693,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRouteHop.time = Date() if hopNode?.hasPositions ?? false { traceRoute?.hasPositions = true - let mostRecent = hopNode?.positions?.lastObject as! PositionEntity - if mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { + if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { traceRouteHop.altitude = mostRecent.altitude traceRouteHop.latitudeI = mostRecent.latitudeI traceRouteHop.longitudeI = mostRecent.longitudeI @@ -746,9 +743,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate print("MAX PORT NUM OF 511") case .atakPlugin: MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for ATAK Plugin App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") - + } - + if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce { invalidVersion = false lastConnectionError = "" @@ -756,10 +753,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate print("🀜 Want Config Complete. ID:\(decodedInfo.configCompleteID)") peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) // Config conplete returns so we don't read the characteristic again - + /// MQTT Client Proxy and RangeTest and Store and Forward interest if connectedPeripheral.num > 0 { - + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(connectedPeripheral.num)) do { @@ -777,22 +774,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let appState = AppState.shared appState.unreadChannelMessages = fetchedNodeInfo[0].myInfo?.unreadMessages ?? 0 appState.unreadDirectMessages = fetchedNodeInfo[0].user?.unreadMessages ?? 0 - //appState.connectedNode = fetchedNodeInfo[0] + // appState.connectedNode = fetchedNodeInfo[0] UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages - + } if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].rangeTestConfig?.enabled == true { - wantRangeTestPackets = true; + wantRangeTestPackets = true } if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].storeForwardConfig?.enabled == true { - wantStoreAndForwardPackets = true; + wantStoreAndForwardPackets = true } - + } catch { print("Failed to find a node info for the connected node") } } - + // MARK: Share Location Position Update Timer // Use context to pass the radio name with the timer // Use a RunLoop to prevent the timer from running on the main UI thread @@ -805,7 +802,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return } - + case FROMNUM_UUID: print("πŸ—žοΈ BLE (Notify) characteristic, value will be read next") default: @@ -816,16 +813,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate peripheral.readValue(for: FROMRADIO_characteristic) } } - + public func sendMessage(message: String, toUserNum: Int64, channel: Int32, isEmoji: Bool, replyID: Int64) -> Bool { var success = false - + // Return false if we are not properly connected to a device, handle retry logic in the view for now if connectedPeripheral == nil || connectedPeripheral!.peripheral.state != CBPeripheralState.connected { - + self.disconnectPeripheral() self.startScanning() - + // Try and connect to the preferredPeripherial first let preferredPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == UserDefaults.preferredPeripheralId as String }).first if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil { @@ -834,32 +831,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let nodeName = connectedPeripheral?.peripheral.name ?? "unknown".localized let logString = String.localizedStringWithFormat("mesh.log.textmessage.send.failed %@".localized, nodeName) MeshLogger.log("🚫 \(logString)") - + success = false } else if message.count < 1 { - + // Don't send an empty message print("🚫 Don't Send an Empty Message") success = false - + } else { - + let fromUserNum: Int64 = self.connectedPeripheral.num - + let messageUsers: NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") messageUsers.predicate = NSPredicate(format: "num IN %@", [fromUserNum, Int64(toUserNum)]) - + do { - + guard let fetchedUsers = try context?.fetch(messageUsers) as? [UserEntity] else { return false } if fetchedUsers.isEmpty { - + print("🚫 Message Users Not Found, Fail") success = false } else if fetchedUsers.count >= 1 { - + let newMessage = MessageEntity(context: context!) newMessage.messageId = Int64(UInt32.random(in: UInt32(UInt8.max).. 0 { @@ -904,7 +901,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded.replyID = UInt32(replyID) } meshPacket.wantAck = true - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket @@ -912,13 +909,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) let logString = String.localizedStringWithFormat("mesh.log.textmessage.sent %@ %@ %@".localized, String(newMessage.messageId), String(fromUserNum), String(toUserNum)) - + MeshLogger.log("πŸ’¬ \(logString)") do { try context!.save() print("πŸ’Ύ Saved a new sent message from \(connectedPeripheral.num) to \(toUserNum)") success = true - + } catch { context!.rollback() let nsError = error as NSError @@ -926,14 +923,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } } - + } catch { - + } } return success } - + public func sendWaypoint(waypoint: Waypoint) -> Bool { if waypoint.latitudeI == 373346000 && waypoint.longitudeI == -1220090000 { return false @@ -947,12 +944,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var dataMessage = DataMessage() do { dataMessage.payload = try waypoint.serializedData() - } - catch { + } catch { // Could not serialiaze the payload return false } - + dataMessage.portnum = PortNum.waypointApp meshPacket.decoded = dataMessage var toRadio: ToRadio! @@ -997,11 +993,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return success } - + public func getPositionFromPhoneGPS(destNum: Int64) -> Position? { var positionPacket = Position() if #available(iOS 17.0, macOS 14.0, *) { - + guard let lastLocation = LocationsHandler.shared.locationsArray.last else { return nil } @@ -1014,7 +1010,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate positionPacket.satsInView = UInt32(LocationsHandler.satsInView) let currentSpeed = lastLocation.speed - if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { + if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { positionPacket.groundSpeed = UInt32(currentSpeed) } let currentHeading = lastLocation.course @@ -1023,7 +1019,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } else { - + positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) let timestamp = LocationHelper.shared.locationManager.location?.timestamp ?? Date() @@ -1032,7 +1028,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate positionPacket.altitude = Int32(LocationHelper.shared.locationManager.location?.altitude ?? 0) positionPacket.satsInView = UInt32(LocationHelper.satsInView) let currentSpeed = LocationHelper.shared.locationManager.location?.speed ?? 0 - if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { + if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { positionPacket.groundSpeed = UInt32(currentSpeed) } let currentHeading = LocationHelper.shared.locationManager.location?.course ?? 0 @@ -1042,7 +1038,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return positionPacket } - + public func setFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool { var adminPacket = AdminMessage() guard let positionPacket = getPositionFromPhoneGPS(destNum: fromUser.num) else { @@ -1066,7 +1062,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func removeFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.removeFixedPosition = true @@ -1087,14 +1083,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) -> Bool { var success = false let fromNodeNum = connectedPeripheral.num guard let positionPacket = getPositionFromPhoneGPS(destNum: destNum) else { return false } - + var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) meshPacket.channel = UInt32(channel) @@ -1104,7 +1100,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.positionApp dataMessage.wantResponse = wantResponse meshPacket.decoded = dataMessage - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket @@ -1122,11 +1118,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if connectedPeripheral != nil { // Send a position out to the mesh if "share location with the mesh" is enabled in settings if UserDefaults.provideLocation { - let _ = sendPosition(channel: 0, destNum: connectedPeripheral.num, wantResponse: false) + _ = sendPosition(channel: 0, destNum: connectedPeripheral.num, wantResponse: false) } } } - + public func sendShutdown(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.shutdownSeconds = 5 @@ -1147,7 +1143,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendReboot(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.rebootSeconds = 5 @@ -1168,7 +1164,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendRebootOta(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.rebootOtaSeconds = 5 @@ -1189,7 +1185,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendEnterDfuMode(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.enterDfuModeRequest = true @@ -1211,7 +1207,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func sendFactoryReset(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.factoryReset = 5 @@ -1225,14 +1221,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - + let messageDescription = "πŸš€ Sent Factory Reset Admin Message to: \(toUser.longName ?? "unknown".localized) from: \(fromUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func sendNodeDBReset(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.nodedbReset = 5 @@ -1245,7 +1241,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage let messageDescription = "πŸš€ Sent NodeDB Reset Admin Message to: \(toUser.longName ?? "unknown".localized) from: \(fromUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -1253,7 +1249,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func connectToPreferredPeripheral() -> Bool { var success = false // Return false if we are not properly connected to a device, handle retry logic in the view for now @@ -1271,9 +1267,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return success } - + public func getChannel(channel: Channel, fromUser: UserEntity, toUser: UserEntity) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.getChannelRequest = UInt32(channel.index + 1) var meshPacket: MeshPacket = MeshPacket() @@ -1286,7 +1282,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + let messageDescription = "πŸŽ›οΈ Requested Channel \(channel.index) for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) @@ -1294,7 +1290,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } public func saveChannel(channel: Channel, fromUser: UserEntity, toUser: UserEntity) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setChannel = channel var meshPacket: MeshPacket = MeshPacket() @@ -1307,24 +1303,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›Ÿ Saved Channel \(channel.index) for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) } return 0 } - + public func saveChannelSet(base64UrlString: String, addChannels: Bool = false) -> Bool { if isConnected { - + var i: Int32 = 0 var myInfo: MyInfoEntity // Before we get started delete the existing channels from the myNodeInfo if !addChannels { tryClearExistingChannels() } - + let decodedString = base64UrlString.base64urlToBase64() if let decodedData = Data(base64Encoded: decodedString) { do { @@ -1355,7 +1351,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate print("Failed to find a node MyInfo to save these channels to") } } - + var chan = Channel() if i == 0 { chan.role = Channel.Role.primary @@ -1412,12 +1408,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let logString = String.localizedStringWithFormat("mesh.log.lora.config.sent %@".localized, String(connectedPeripheral.num)) MeshLogger.log("πŸ“» \(logString)") } - + if self.connectedPeripheral != nil { self.sendWantConfig() return true } - + } catch { return false } @@ -1425,7 +1421,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func saveUser(config: User, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setOwner = config @@ -1446,7 +1442,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return 0 } - + public func removeNode(node: NodeInfoEntity, connectedNodeNum: Int64) -> Bool { var adminPacket = AdminMessage() adminPacket.removeByNodenum = UInt32(node.num) @@ -1463,8 +1459,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - - if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected{ + + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { do { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) context!.delete(node.user!) @@ -1479,7 +1475,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func setFavoriteNode(node: NodeInfoEntity, connectedNodeNum: Int64) -> Bool { var adminPacket = AdminMessage() adminPacket.setFavoriteNode = UInt32(node.num) @@ -1496,14 +1492,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - - if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected{ + + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) return true } return false } - + public func removeFavoriteNode(node: NodeInfoEntity, connectedNodeNum: Int64) -> Bool { var adminPacket = AdminMessage() adminPacket.removeFavoriteNode = UInt32(node.num) @@ -1520,14 +1516,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - - if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected{ + + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) return true } return false } - + public func saveLicensedUser(ham: HamParameters, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setHamMode = ham @@ -1563,20 +1559,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage let messageDescription = "πŸ›Ÿ Saved Bluetooth Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func saveDeviceConfig(config: Config.DeviceConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.device = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1595,7 +1591,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return 0 } - + public func saveDisplayConfig(config: Config.DisplayConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.display = config @@ -1619,9 +1615,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return 0 } - + public func saveLoRaConfig(config: Config.LoRaConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.lora = config var meshPacket: MeshPacket = MeshPacket() @@ -1636,20 +1632,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage let messageDescription = "πŸ›Ÿ Saved LoRa Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func savePositionConfig(config: Config.PositionConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.position = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1660,24 +1656,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›Ÿ Saved Position Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertPositionConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func savePowerConfig(config: Config.PowerConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.power = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1688,24 +1684,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›Ÿ Saved Power Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertPowerConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func saveNetworkConfig(config: Config.NetworkConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.network = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1716,24 +1712,24 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›Ÿ Saved Network Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertNetworkConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func saveAmbientLightingModuleConfig(config: ModuleConfig.AmbientLightingConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.ambientLighting = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1745,22 +1741,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›Ÿ Saved Ambient Lighting Module Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertAmbientLightingModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func saveCannedMessageModuleConfig(config: ModuleConfig.CannedMessageConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.cannedMessage = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1772,22 +1768,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›Ÿ Saved Canned Message Module Config for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertCannedMessagesModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func saveCannedMessageModuleMessages(messages: String, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setCannedMessageModuleMessages = messages - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1800,22 +1796,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›Ÿ Saved Canned Message Module Messages for \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { - + return Int64(meshPacket.id) } - + return 0 } - + public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.detectionSensor = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.externalNotification = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1849,12 +1845,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.paxcounter = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1875,26 +1871,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setRingtoneMessage = ringtone - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.mqtt = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.rangeTest = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.serial = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.storeForward = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.telemetry = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - + var adminPacket = AdminMessage() adminPacket.getChannelRequest = channelIndex - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - + var adminPacket = AdminMessage() adminPacket.getCannedMessageModuleMessagesRequest = true - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(destNum) meshPacket.from = UInt32(connectedPeripheral.num) @@ -2090,35 +2086,35 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.wantAck = true meshPacket.decoded.wantResponse = wantResponse - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = wantResponse - + meshPacket.decoded = dataMessage - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket - + let binaryData: Data = try! toRadio.serializedData() - + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) let logString = String.localizedStringWithFormat("mesh.log.cannedmessages.messages.get %@".localized, String(connectedPeripheral.num)) MeshLogger.log("πŸ₯« \(logString)") return true } - + return false } - + public func requestBluetoothConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.bluetoothConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2126,27 +2122,27 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Bluetooth Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestDeviceConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.deviceConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2154,27 +2150,27 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Device Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestDisplayConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.displayConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2182,27 +2178,27 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Display Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestLoRaConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.loraConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2210,29 +2206,29 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested LoRa Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { - + return true } - + return false } - + public func requestNetworkConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.networkConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2240,26 +2236,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Network Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestPositionConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.positionConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2267,26 +2263,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Position Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestPowerConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.powerConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2294,26 +2290,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Power Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestAmbientLightingConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.ambientlightingConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2321,26 +2317,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Ambient Lighting Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestCannedMessagesModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.cannedmsgConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2348,26 +2344,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Canned Messages Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestExternalNotificationModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.extnotifConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2375,26 +2371,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested External Notificaiton Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestPaxCounterModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.paxcounterConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2402,26 +2398,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested PAX Counter Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestRtttlConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getRingtoneRequest = true - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2429,26 +2425,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested RTTTL Ringtone Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestRangeTestModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.rangetestConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2456,26 +2452,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Range Test Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestMqttModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.mqttConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2483,26 +2479,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested MQTT Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestDetectionSensorModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.detectionsensorConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2510,27 +2506,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Detection Sensor Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - - + public func requestSerialModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.serialConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2538,26 +2533,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Serial Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestStoreAndForwardModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.storeforwardConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2565,26 +2560,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Store and Forward Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestTelemetryModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.telemetryConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2592,30 +2587,30 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "πŸ›ŽοΈ Requested Telemetry Module Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + // Send an admin message to a radio, save a message to core data for logging private func sendAdminMessageToRadio(meshPacket: MeshPacket, adminDescription: String, fromUser: UserEntity, toUser: UserEntity) -> Bool { - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - - if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected{ + + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { let newMessage = MessageEntity(context: context!) newMessage.messageId = Int64(meshPacket.id) newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) @@ -2624,8 +2619,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate newMessage.adminDescription = adminDescription newMessage.fromUser = fromUser newMessage.toUser = toUser - - + do { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) try context!.save() @@ -2639,9 +2633,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + public func requestStoreAndForwardClientHistory(fromUser: UserEntity, toUser: UserEntity) -> Bool { - + /// send a request for ClientHistory with a time period matching the heartbeat var sfPacket = StoreAndForward() sfPacket.rr = StoreAndForward.RequestResponse.clientHistory @@ -2658,7 +2652,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.storeForwardApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket @@ -2670,7 +2664,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return false } - + func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { // Handle each of the store and forward request / response messages @@ -2682,8 +2676,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .routerHeartbeat: /// When we get a router heartbeat we know there is a store and forward node on the network /// Check if it is the primary S&F Router and save the timestamp of the last heartbeat so that we can show the request message history menu item on node long press if the router has been seen recently - if (storeAndForwardMessage.heartbeat.secondary == 0) { - + if storeAndForwardMessage.heartbeat.secondary == 0 { + guard let routerNode = getNodeInfo(id: Int64(packet.from), context: context) else { return } @@ -2698,7 +2692,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate newConfig.lastHeartbeat = Date() routerNode.storeForwardConfig = newConfig } - + do { try context.save() } catch { @@ -2725,7 +2719,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate newConfig.lastRequest = Int32(storeAndForwardMessage.history.lastRequest) routerNode.storeForwardConfig = newConfig } - + do { try context.save() } catch { @@ -2758,12 +2752,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } } - + public func tryClearExistingChannels() { // Before we get started delete the existing channels from the myNodeInfo let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num)) - + do { let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as? [MyInfoEntity] ?? [] if fetchedMyInfo.count == 1 { @@ -2784,7 +2778,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // MARK: - CB Central Manager implmentation extension BLEManager: CBCentralManagerDelegate { - + // MARK: Bluetooth enabled/disabled func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == CBManagerState.poweredOn { @@ -2794,9 +2788,9 @@ extension BLEManager: CBCentralManagerDelegate { } else { isSwitchedOn = false } - + var status = "" - + switch central.state { case .poweredOff: status = "BLE is powered off" @@ -2815,10 +2809,10 @@ extension BLEManager: CBCentralManagerDelegate { } print("BLEManager status: \(status)") } - + // Called each time a peripheral is discovered func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { - + if self.automaticallyReconnect && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" { self.connectTo(peripheral: peripheral) print("ℹ️ BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")") @@ -2826,7 +2820,7 @@ extension BLEManager: CBCentralManagerDelegate { let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "?", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral) let index = peripherals.map { $0.peripheral }.firstIndex(of: peripheral) - + if let peripheralIndex = index { peripherals[peripheralIndex] = device } else { diff --git a/Meshtastic/Helpers/EmojiOnlyTextField.swift b/Meshtastic/Helpers/EmojiOnlyTextField.swift index 04736020..7041dbed 100644 --- a/Meshtastic/Helpers/EmojiOnlyTextField.swift +++ b/Meshtastic/Helpers/EmojiOnlyTextField.swift @@ -8,10 +8,6 @@ import SwiftUI class SwiftUIEmojiTextField: UITextField { - override func awakeFromNib() { - super.awakeFromNib() - } - func setEmoji() { _ = self.textInputMode } diff --git a/Meshtastic/Helpers/LocalNotificationManager.swift b/Meshtastic/Helpers/LocalNotificationManager.swift index 5ae3df98..21e09942 100644 --- a/Meshtastic/Helpers/LocalNotificationManager.swift +++ b/Meshtastic/Helpers/LocalNotificationManager.swift @@ -37,7 +37,7 @@ class LocalNotificationManager { content.body = notification.content content.sound = .default content.interruptionLevel = .timeSensitive - + if notification.target != nil { content.userInfo["target"] = notification.target } diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index 8d0100c0..774ed7f4 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -5,8 +5,8 @@ import MapKit class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { static let shared = LocationHelper() var locationManager = CLLocationManager() - - //@Published var region = MKCoordinateRegion() + + // @Published var region = MKCoordinateRegion() @Published var authorizationStatus: CLAuthorizationStatus? override init() { super.init() @@ -47,7 +47,7 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { } return sats } - + func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { switch manager.authorizationStatus { case .authorizedAlways: @@ -67,7 +67,7 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - + } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("Location manager error: \(error.localizedDescription)") diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index 4d80e99f..f9279884 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -16,7 +16,7 @@ import CoreLocation private let manager: CLLocationManager private var background: CLBackgroundActivitySession? var enableSmartPosition: Bool = UserDefaults.enableSmartPosition - + @Published var locationsArray: [CLLocation] @Published var isStationary = false @Published var count = 0 @@ -25,12 +25,12 @@ import CoreLocation @Published var recordingStarted: Date? @Published var distanceTraveled = 0.0 @Published var elevationGain = 0.0 - + @Published var updatesStarted: Bool = UserDefaults.standard.bool(forKey: "liveUpdatesStarted") { didSet { UserDefaults.standard.set(updatesStarted, forKey: "liveUpdatesStarted") } } - + @Published var backgroundActivity: Bool = UserDefaults.standard.bool(forKey: "BGActivitySessionStarted") { didSet { @@ -38,27 +38,27 @@ import CoreLocation UserDefaults.standard.set(backgroundActivity, forKey: "BGActivitySessionStarted") } } - + private init() { self.manager = CLLocationManager() // Creating a location manager instance is safe to call here in `MainActor`. self.manager.allowsBackgroundLocationUpdates = true locationsArray = [CLLocation]() } - + func startLocationUpdates() { if self.manager.authorizationStatus == .notDetermined { self.manager.requestWhenInUseAuthorization() } print("Starting location updates") - Task() { + Task { do { self.updatesStarted = true let updates = CLLocationUpdate.liveUpdates() for try await update in updates { - if !self.updatesStarted { break } + if !self.updatesStarted { break } if let loc = update.location { self.isStationary = update.isStationary - + var locationAdded: Bool locationAdded = addLocation(loc, smartPostion: enableSmartPosition) if !isRecording && locationAdded { @@ -74,12 +74,12 @@ import CoreLocation return } } - + func stopLocationUpdates() { print("Stopping location updates") self.updatesStarted = false } - + func addLocation(_ location: CLLocation, smartPostion: Bool) -> Bool { if smartPostion { let age = -location.timestamp.timeIntervalSinceNow @@ -111,9 +111,9 @@ import CoreLocation } return true } - + static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090) - + static var satsInView: Int { var sats = 0 if let newLocation = shared.locationsArray.last { diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 78121fb0..e02b7738 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -226,7 +226,7 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS if fetchedNode.count > 0 { fetchedNode[0].metadata = newMetadata } else { - + if fromNum > 0 { let newNode = createNodeInfo(num: Int64(fromNum), context: context) newNode.metadata = newMetadata @@ -284,7 +284,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) newNode.snr = nodeInfo.snr if nodeInfo.hasUser { - + let newUser = UserEntity(context: context) newUser.userId = nodeInfo.user.id newUser.num = Int64(nodeInfo.num) @@ -307,7 +307,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje position.longitudeI = nodeInfo.position.longitudeI position.altitude = nodeInfo.position.altitude position.satsInView = Int32(nodeInfo.position.satsInView) - position.speed = Int32(nodeInfo.position.groundSpeed) + position.speed = Int32(nodeInfo.position.groundSpeed) position.heading = Int32(nodeInfo.position.groundTrack) position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) var newPostions = [PositionEntity]() @@ -349,7 +349,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].hopsAway = Int32(nodeInfo.hopsAway) if nodeInfo.hasUser { - if (fetchedNode[0].user == nil) { + if fetchedNode[0].user == nil { fetchedNode[0].user = UserEntity(context: context) } fetchedNode[0].user!.userId = nodeInfo.user.id @@ -360,9 +360,9 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].user!.isLicensed = nodeInfo.user.isLicensed fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue) fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() - } else { - if (fetchedNode[0].user == nil && nodeInfo.num > Int16.max) { - + } else { + if fetchedNode[0].user == nil && nodeInfo.num > Int16.max { + let newUser = createUser(num: Int64(nodeInfo.num), context: context) fetchedNode[0].user = newUser } @@ -550,25 +550,25 @@ func adminResponseAck (packet: MeshPacket, context: NSManagedObjectContext) { } } func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) { - + let logString = String.localizedStringWithFormat("mesh.log.paxcounter %@".localized, String(packet.from)) MeshLogger.log("πŸ§‘β€πŸ€β€πŸ§‘ \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - + do { let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] - + if let paxMessage = try? Paxcount(serializedData: packet.decoded.payload) { - + let newPax = PaxCounterEntity(context: context) newPax.ble = Int32(truncatingIfNeeded: paxMessage.ble) newPax.wifi = Int32(truncatingIfNeeded: paxMessage.wifi) newPax.uptime = Int32(truncatingIfNeeded: paxMessage.uptime) newPax.time = Date() - - if (fetchedNode?.count ?? 0 > 0) { + + if fetchedNode?.count ?? 0 > 0 { guard let mutablePax = fetchedNode?[0].pax!.mutableCopy() as? NSMutableOrderedSet else { return } @@ -584,7 +584,7 @@ func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) { } } } catch { - + } } @@ -619,7 +619,7 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana } fetchedMessage![0].ackSNR = packet.rxSnr fetchedMessage![0].ackTimestamp = Int32(truncatingIfNeeded: packet.rxTime) - + if fetchedMessage![0].toUser != nil { fetchedMessage![0].toUser!.objectWillChange.send() } else { @@ -772,20 +772,20 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec } } let rangeTest = messageText?.contains(rangeTestRegex) ?? false && messageText?.starts(with: "seq ") ?? false - + if !wantRangeTestPackets && rangeTest { return } var storeForwardBroadcast = false if storeForward { if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { - messageText = String(bytes: storeAndForwardMessage.text, encoding: .utf8) + messageText = String(bytes: storeAndForwardMessage.text, encoding: .utf8) if storeAndForwardMessage.rr == .routerTextBroadcast { storeForwardBroadcast = true } } } - + if messageText?.count ?? 0 > 0 { MeshLogger.log("πŸ’¬ \("mesh.log.textmessage.received".localized)") @@ -837,7 +837,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec messageSaved = true if messageSaved { - + if packet.decoded.portnum == PortNum.detectionSensorApp && !UserDefaults.enableDetectionNotifications { return } @@ -876,7 +876,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec if !fetchedMyInfo.isEmpty { appState.unreadChannelMessages = fetchedMyInfo[0].unreadMessages UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages - + for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { if channel.index == newMessage.channel { context.refresh(channel, mergeChanges: true) diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index 4de5f67e..13d22f4b 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -39,7 +39,7 @@ class MqttClientProxyManager { let minimumVersion = "2.3.2" let currentVersion = UserDefaults.firmwareVersion let supportedVersion = minimumVersion.compare(currentVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(currentVersion, options: .numeric) == .orderedSame - + if let host = host { let port = defaultServerPort let username = node.mqttConfig?.username diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 2a8e37d1..fb5b79c9 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -9,7 +9,7 @@ import TipKit @available(iOS 17.0, *) @main struct MeshtasticAppleApp: App { - + @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate let persistenceController = PersistenceController.shared @ObservedObject private var bleManager: BLEManager = BLEManager.shared @@ -28,7 +28,7 @@ struct MeshtasticAppleApp: App { .environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(bleManager) .sheet(isPresented: $saveChannels) { - SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager) + SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager) .presentationDetents([.large]) .presentationDragIndicator(.visible) } @@ -71,13 +71,12 @@ struct MeshtasticAppleApp: App { } else if path.starts(with: "meshtastic://nodes") { AppState.shared.tabSelection = Tab.nodes } - - + } else { saveChannels = false print("User wants to import a MBTILES offline map file: \(self.incomingUrl?.absoluteString ?? "No Tiles link")") } - + /// Only do the map tiles stuff if it is enabled if UserDefaults.enableOfflineMapsMBTiles { /// we are expecting a .mbtiles map file that contains raster data @@ -87,30 +86,30 @@ struct MeshtasticAppleApp: App { let destination = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false) if !self.saveChannels { - + // tell the system we want the file please guard url.startAccessingSecurityScopedResource() else { return } - + // do we need to delete an old one? if fileManager.fileExists(atPath: destination.path) { print("ℹ️ Found an old map file. Deleting it") try? fileManager.removeItem(atPath: destination.path) } - + do { try fileManager.copyItem(at: url, to: destination) } catch { print("Copy MB Tile file failed. Error: \(error)") } - + if fileManager.fileExists(atPath: destination.path) { print("ℹ️ Saved the map file") - + // need to tell the map view that it needs to update and try loading the new overlay UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastUpdatedLocalMapFile") - + } else { print("πŸ’₯ Didn't save the map file") } @@ -168,6 +167,6 @@ class AppState: ObservableObject { @Published var unreadDirectMessages: Int = 0 @Published var unreadChannelMessages: Int = 0 @Published var firmwareVersion: String = "0.0.0" - //@Published var connectedNode: NodeInfoEntity? + // @Published var connectedNode: NodeInfoEntity? @Published var navigationPath: String? } diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index c44a7f7d..3a99f6b8 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -8,12 +8,12 @@ import SwiftUI class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { print("πŸš€ Meshtstic Apple App launched!") // Default User Default Values - UserDefaults.standard.register(defaults: ["meshMapRecentering" : true]) - UserDefaults.standard.register(defaults: ["meshMapShowNodeHistory" : true]) - UserDefaults.standard.register(defaults: ["meshMapShowRouteLines" : true]) + UserDefaults.standard.register(defaults: ["meshMapRecentering": true]) + UserDefaults.standard.register(defaults: ["meshMapShowNodeHistory": true]) + UserDefaults.standard.register(defaults: ["meshMapShowRouteLines": true]) UNUserNotificationCenter.current().delegate = self if #available(iOS 17.0, macOS 14.0, *) { let locationsHandler = LocationsHandler.shared diff --git a/Meshtastic/Persistence/Persistence.swift b/Meshtastic/Persistence/Persistence.swift index 997fdbea..13688f5c 100644 --- a/Meshtastic/Persistence/Persistence.swift +++ b/Meshtastic/Persistence/Persistence.swift @@ -60,7 +60,7 @@ class PersistenceController { do { try persistentStoreCoordinator.destroyPersistentStore(at: url, ofType: NSSQLiteStoreType, options: nil) print("πŸ’₯ CoreData database truncated. All app data has been erased.") - + do { try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil) } catch let error { diff --git a/Meshtastic/Persistence/QueryCoreData.swift b/Meshtastic/Persistence/QueryCoreData.swift index 502aa61a..b68c36d6 100644 --- a/Meshtastic/Persistence/QueryCoreData.swift +++ b/Meshtastic/Persistence/QueryCoreData.swift @@ -26,7 +26,7 @@ public func getNodeInfo(id: Int64, context: NSManagedObjectContext) -> NodeInfoE } public func getStoreAndForwardMessageIds(seconds: Int, context: NSManagedObjectContext) -> [UInt32] { - + let time = seconds * -1 let fetchMessagesRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MessageEntity") let timeRange = Calendar.current.date(byAdding: .minute, value: time, to: Date()) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 4e067903..844957d4 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -110,12 +110,12 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes let persistenceController = PersistenceController.shared.container for i in 0...persistenceController.managedObjectModel.entities.count-1 { - + let entity = persistenceController.managedObjectModel.entities[i] let query = NSFetchRequest(entityName: entity.name!) var deleteRequest = NSBatchDeleteRequest(fetchRequest: query) let entityName = entity.name ?? "UNK" - + if includeRoutes { deleteRequest = NSBatchDeleteRequest(fetchRequest: query) } else if !includeRoutes { @@ -153,7 +153,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newNode.snr = packet.rxSnr newNode.rssi = packet.rxRssi newNode.viaMqtt = packet.viaMqtt - + if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum { newNode.channel = Int32(packet.channel) } @@ -161,16 +161,16 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newNode.hopsAway = Int32(nodeInfoMessage.hopsAway) newNode.favorite = nodeInfoMessage.isFavorite } - + if let newUserMessage = try? User(serializedData: packet.decoded.payload) { - - if newUserMessage.id.isEmpty { + + if newUserMessage.id.isEmpty { if packet.from > Int16.max { let newUser = createUser(num: Int64(packet.from), context: context) newNode.user = newUser } } else { - + let newUser = UserEntity(context: context) newUser.userId = newUserMessage.id newUser.num = Int64(packet.from) @@ -179,9 +179,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newUser.role = Int32(newUserMessage.role.rawValue) newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() newNode.user = newUser - - - if (UserDefaults.newNodeNotifications){ + + if UserDefaults.newNodeNotifications { let manager = LocalNotificationManager() manager.notifications = [ Notification( @@ -202,7 +201,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].user = newUser } } - + if newNode.user == nil && packet.from > Int16.max { newNode.user = createUser(num: Int64(packet.from), context: context) } @@ -219,7 +218,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) print("πŸ’₯ Error Inserting New Core Data MyInfoEntity: \(nsError)") } newNode.myInfo = myInfoEntity - + } else { // Update an existing node fetchedNode[0].id = Int64(packet.from) @@ -260,7 +259,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) } else if packet.hopStart != 0 && packet.hopLimit <= packet.hopStart { fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit) } - if (fetchedNode[0].user == nil) { + if fetchedNode[0].user == nil { let newUser = createUser(num: Int64(packet.from), context: context) fetchedNode[0].user! = newUser } @@ -335,8 +334,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) } /// Don't save nearly the same position over and over. If the next position is less than 10 meters from the new position, delete the previous position and save the new one. if mutablePositions.count > 0 && (position.precisionBits == 32 || position.precisionBits == 0) { - let mostRecent = mutablePositions.lastObject as! PositionEntity - if mostRecent.coordinate.distance(from: position.coordinate) < 15.0 { + if let mostRecent = mutablePositions.lastObject as? PositionEntity, mostRecent.coordinate.distance(from: position.coordinate) < 15.0 { mutablePositions.remove(mostRecent) } } else if mutablePositions.count > 0 { @@ -798,7 +796,7 @@ func upsertAmbientLightingModuleConfigPacket(config: Meshtastic.ModuleConfig.Amb fetchedNode[0].ambientLightingConfig = newAmbientLightingConfig } else { - + if fetchedNode[0].ambientLightingConfig == nil { fetchedNode[0].ambientLightingConfig = AmbientLightingConfigEntity(context: context) } @@ -1041,7 +1039,7 @@ func upsertPaxCounterModuleConfigPacket(config: Meshtastic.ModuleConfig.Paxcount let newPaxCounterConfig = PaxCounterConfigEntity(context: context) newPaxCounterConfig.enabled = config.enabled newPaxCounterConfig.paxcounterUpdateInterval = Int32(config.paxcounterUpdateInterval) - + fetchedNode[0].paxCounterConfig = newPaxCounterConfig } else { diff --git a/Meshtastic/Tips/MessagesTips.swift b/Meshtastic/Tips/MessagesTips.swift index c50af1b6..d78daa0e 100644 --- a/Meshtastic/Tips/MessagesTips.swift +++ b/Meshtastic/Tips/MessagesTips.swift @@ -33,11 +33,11 @@ struct ContactsTip: Tip { return "tip.messages.contacts" } var title: Text { - //Text("tip.messages.contacts.title") + // Text("tip.messages.contacts.title") Text("Contacts") } var message: Text? { - //Text("tip.messages.contacts.message") + // Text("tip.messages.contacts.message") Text("Each node is an available contact. Contacts with recent messages or marked as favorites show up at the top of the list. Select a contact to send or view messages. Long press to favorite or mute the contact or delete the conversation.") } var image: Image? { diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index c8f422b7..438e3a00 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -71,7 +71,7 @@ struct Connect: View { Text("subscribed").font(.callout) .foregroundColor(.green) } else { - + HStack { if #available(iOS 17.0, macOS 14.0, *) { Image(systemName: "square.stack.3d.down.forward") @@ -125,7 +125,7 @@ struct Connect: View { if !bleManager.sendShutdown(fromUser: node!.user!, toUser: node!.user!, adminIndex: node!.myInfo!.adminIndex) { print("Shutdown Failed") } - + } label: { Label("Power Off", systemImage: "power") } @@ -233,7 +233,7 @@ struct Connect: View { bleManager.disconnectPeripheral() } clearCoreDataDatabase(context: context, includeRoutes: false) - + let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId }) if radio != nil { bleManager.connectTo(peripheral: radio!.peripheral) @@ -242,7 +242,7 @@ struct Connect: View { } .textCase(nil) } - + } else { Text("bluetooth.off") .foregroundColor(.red) @@ -269,7 +269,7 @@ struct Connect: View { if bleManager.isConnecting { Button(role: .destructive, action: { bleManager.cancelPeripheralConnection() - + }) { Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") } diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index da24c465..46f7f903 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -56,19 +56,19 @@ struct ContentView: View { } } } -//#Preview { +// #Preview { // if #available(iOS 17.0, *) { // // ContentView(deepLinkManager: .init()) // } else { // // Fallback on earlier versions // } -//} +// } -//struct ContentView_Previews: PreviewProvider { +// struct ContentView_Previews: PreviewProvider { // static var previews: some View { // ContentView() // } -//} +// } enum Tab: Hashable { case contacts diff --git a/Meshtastic/Views/Helpers/BatteryGauge.swift b/Meshtastic/Views/Helpers/BatteryGauge.swift index 431eee25..952c9768 100644 --- a/Meshtastic/Views/Helpers/BatteryGauge.swift +++ b/Meshtastic/Views/Helpers/BatteryGauge.swift @@ -9,17 +9,17 @@ import SwiftUI import Charts struct BatteryGauge: View { - + @ObservedObject var node: NodeInfoEntity private let minValue = 0.0 private let maxValue = 100.00 var body: some View { - + let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity let batteryLevel = Double(mostRecent?.batteryLevel ?? 0) - + VStack { if batteryLevel > 100.0 { // Plugged in diff --git a/Meshtastic/Views/Helpers/BatteryLevelCompact.swift b/Meshtastic/Views/Helpers/BatteryLevelCompact.swift index e3fc1100..7bb2542c 100644 --- a/Meshtastic/Views/Helpers/BatteryLevelCompact.swift +++ b/Meshtastic/Views/Helpers/BatteryLevelCompact.swift @@ -7,7 +7,7 @@ import SwiftUI struct BatteryLevelCompact: View { - + @ObservedObject var node: NodeInfoEntity var font: Font @@ -26,25 +26,25 @@ struct BatteryLevelCompact: View { .foregroundColor(color) .symbolRenderingMode(.hierarchical) } else if batteryLevel < 100 && batteryLevel > 74 { - + Image(systemName: "battery.75") .font(iconFont) .foregroundColor(color) .symbolRenderingMode(.hierarchical) } else if batteryLevel < 75 && batteryLevel > 49 { - + Image(systemName: "battery.50") .font(iconFont) .foregroundColor(color) .symbolRenderingMode(.hierarchical) } else if batteryLevel < 50 && batteryLevel > 14 { - + Image(systemName: "battery.25") .font(iconFont) .foregroundColor(color) .symbolRenderingMode(.hierarchical) } else if batteryLevel < 15 && batteryLevel > 0 { - + Image(systemName: "battery.0") .font(iconFont) .foregroundColor(color) diff --git a/Meshtastic/Views/Helpers/CircleText.swift b/Meshtastic/Views/Helpers/CircleText.swift index 47b4ebbf..2807968e 100644 --- a/Meshtastic/Views/Helpers/CircleText.swift +++ b/Meshtastic/Views/Helpers/CircleText.swift @@ -9,7 +9,7 @@ struct CircleText: View { var text: String var color: Color var circleSize: CGFloat = 45 - + var body: some View { ZStack { @@ -29,7 +29,7 @@ struct CircleText: View { .font(.system(size: 5000)) .minimumScaleFactor(0.001) #endif - + } .aspectRatio(1, contentMode: .fit) } @@ -59,8 +59,7 @@ struct CircleText_Previews: PreviewProvider { .previewLayout(.fixed(width: 300, height: 100)) } HStack { - - + CircleText(text: "CW-A", color: Color.secondary) .previewLayout(.fixed(width: 300, height: 100)) CircleText(text: "CW-A", color: Color.secondary, circleSize: 80) @@ -71,7 +70,7 @@ struct CircleText_Previews: PreviewProvider { .previewLayout(.fixed(width: 300, height: 100)) } HStack { - + CircleText(text: "πŸš—", color: Color.orange) .previewLayout(.fixed(width: 300, height: 100)) CircleText(text: "πŸ”‹", color: Color.indigo, circleSize: 80) diff --git a/Meshtastic/Views/Helpers/ConnectedDevice.swift b/Meshtastic/Views/Helpers/ConnectedDevice.swift index 2a96d526..c57a4c5d 100644 --- a/Meshtastic/Views/Helpers/ConnectedDevice.swift +++ b/Meshtastic/Views/Helpers/ConnectedDevice.swift @@ -5,7 +5,6 @@ A view draws the indicator used in the upper right corner for views using BLE import SwiftUI - struct ConnectedDevice: View { var bluetoothOn: Bool var deviceConnected: Bool @@ -22,7 +21,7 @@ struct ConnectedDevice: View { if (phoneOnly && UIDevice.current.userInterfaceIdiom == .phone) || !phoneOnly { if bluetoothOn { if deviceConnected { - if (mqttUplinkEnabled || mqttDownlinkEnabled) { + if mqttUplinkEnabled || mqttDownlinkEnabled { MQTTIcon(connected: mqttProxyConnected, uplink: mqttUplinkEnabled, downlink: mqttDownlinkEnabled, topic: mqttTopic) } Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") @@ -44,12 +43,9 @@ struct ConnectedDevice: View { } } - - - struct ConnectedDevice_Previews: PreviewProvider { static var previews: some View { - VStack (alignment: .trailing) { + VStack(alignment: .trailing) { ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true) ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: false, mqttUplinkEnabled: true, mqttDownlinkEnabled: true) ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true, mqttUplinkEnabled: true, mqttDownlinkEnabled: true, mqttTopic: "msh/US/2/e/#") diff --git a/Meshtastic/Views/Helpers/DateTimeText.swift b/Meshtastic/Views/Helpers/DateTimeText.swift index 6b61afd0..517772fe 100644 --- a/Meshtastic/Views/Helpers/DateTimeText.swift +++ b/Meshtastic/Views/Helpers/DateTimeText.swift @@ -17,7 +17,7 @@ struct DateTimeText: View { let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date()) let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current) - + var body: some View { let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss a") diff --git a/Meshtastic/Views/Helpers/LastHeardText.swift b/Meshtastic/Views/Helpers/LastHeardText.swift index 02c23f9f..4d5deba4 100644 --- a/Meshtastic/Views/Helpers/LastHeardText.swift +++ b/Meshtastic/Views/Helpers/LastHeardText.swift @@ -8,13 +8,13 @@ import SwiftUI struct LastHeardText: View { var lastHeard: Date? let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date()) - + static let formatter: RelativeDateTimeFormatter = { let formatter = RelativeDateTimeFormatter() formatter.unitsStyle = .full return formatter }() - + var body: some View { if lastHeard != nil && lastHeard! >= sixMonthsAgo! { Text(lastHeard?.formatted() ?? "unknown.age".localized) diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift index 60feeacf..829fd837 100644 --- a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift +++ b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift @@ -13,8 +13,8 @@ struct LoRaSignalStrengthMeter: View { var preset: ModemPresets var compact: Bool var body: some View { - - if (snr != 0.0 && rssi != 0) { + + if snr != 0.0 && rssi != 0 { let signalStrength = getLoRaSignalStrength(snr: snr, rssi: rssi, preset: preset) let gradient = Gradient(colors: [.red, .orange, .yellow, .green]) if !compact { diff --git a/Meshtastic/Views/Helpers/MQTTIcon.swift b/Meshtastic/Views/Helpers/MQTTIcon.swift index 171f743e..4cd615be 100644 --- a/Meshtastic/Views/Helpers/MQTTIcon.swift +++ b/Meshtastic/Views/Helpers/MQTTIcon.swift @@ -18,8 +18,8 @@ struct MQTTIcon: View { var body: some View { Button( action: { - if(topic.length > 0) {self.isPopoverOpen.toggle()} - } ) { + if topic.length > 0 {self.isPopoverOpen.toggle()} + }) { // the last one defaults to just showing up/down if it isn't specified b/c on the mqtt config screen, there's no information about uplink/downlink and no good alternative icon Image(systemName: uplink && downlink ? "arrow.up.arrow.down.circle.fill" : uplink ? "arrow.up.circle.fill" : downlink ? "arrow.down.circle.fill" : "arrow.up.arrow.down.circle.fill") .imageScale(.large) diff --git a/Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift b/Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift index 160caae5..66307a91 100644 --- a/Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift +++ b/Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift @@ -21,13 +21,13 @@ struct AirQualityIndex: View { var aqi: Int var displayMode: IaqDisplayMode = .pill let gradient = Gradient(colors: [.green, .yellow, .orange, .red, .purple, .magenta]) - + var body: some View { - + let aqiEnum = Aqi.getAqi(for: aqi) switch displayMode { case .pill: - ZStack (alignment: .leading) { + ZStack(alignment: .leading) { RoundedRectangle(cornerRadius: 10) .fill(aqiEnum.color) .frame(width: 125, height: 30) @@ -48,7 +48,7 @@ struct AirQualityIndex: View { .font(.caption) case .gauge: Gauge(value: Double(aqi), in: 0...500) { - + Text("IAQ") .foregroundColor(aqiEnum.color) } currentValueLabel: { @@ -115,19 +115,19 @@ struct AirQualityIndex_Previews: PreviewProvider { } Text(".gauge") .font(.title2) - HStack (alignment: .top) { + HStack(alignment: .top) { AirQualityIndex(aqi: 6, displayMode: .gauge) AirQualityIndex(aqi: 51, displayMode: .gauge) AirQualityIndex(aqi: 101, displayMode: .gauge) AirQualityIndex(aqi: 151, displayMode: .gauge) } - HStack (alignment: .top) { + HStack(alignment: .top) { AirQualityIndex(aqi: 201, displayMode: .gauge) AirQualityIndex(aqi: 251, displayMode: .gauge) AirQualityIndex(aqi: 301, displayMode: .gauge) AirQualityIndex(aqi: 351, displayMode: .gauge) } - HStack (alignment: .top) { + HStack(alignment: .top) { AirQualityIndex(aqi: 401, displayMode: .gauge) AirQualityIndex(aqi: 500, displayMode: .gauge) } @@ -140,7 +140,7 @@ struct AirQualityIndex_Previews: PreviewProvider { AirQualityIndex(aqi: 351, displayMode: .gradient) AirQualityIndex(aqi: 401, displayMode: .gradient) AirQualityIndex(aqi: 500, displayMode: .gradient) - + }.previewLayout(.fixed(width: 300, height: 800)) } } diff --git a/Meshtastic/Views/Helpers/Weather/IAQScale.swift b/Meshtastic/Views/Helpers/Weather/IAQScale.swift index 58de0bb8..e83d5e87 100644 --- a/Meshtastic/Views/Helpers/Weather/IAQScale.swift +++ b/Meshtastic/Views/Helpers/Weather/IAQScale.swift @@ -10,7 +10,7 @@ import SwiftUI struct IAQScale: View { var body: some View { - VStack(alignment:.leading) { + VStack(alignment: .leading) { ForEach(Iaq.allCases) { iaq in HStack { RoundedRectangle(cornerRadius: 5) diff --git a/Meshtastic/Views/Helpers/Weather/IndoorAirQuality.swift b/Meshtastic/Views/Helpers/Weather/IndoorAirQuality.swift index 5e12aa9a..8e3773b9 100644 --- a/Meshtastic/Views/Helpers/Weather/IndoorAirQuality.swift +++ b/Meshtastic/Views/Helpers/Weather/IndoorAirQuality.swift @@ -28,7 +28,7 @@ struct IndoorAirQuality: View { let iaqEnum = Iaq.getIaq(for: iaq) switch displayMode { case .pill: - ZStack (alignment: .leading) { + ZStack(alignment: .leading) { RoundedRectangle(cornerRadius: 10) .fill(iaqEnum.color) .frame(width: 125, height: 30) @@ -49,7 +49,7 @@ struct IndoorAirQuality: View { .font(.caption) case .gauge: Gauge(value: Double(iaq), in: 0...500) { - + Text("IAQ") .foregroundColor(iaqEnum.color) } currentValueLabel: { @@ -117,19 +117,19 @@ struct IndoorAirQuality_Previews: PreviewProvider { } Text(".gauge") .font(.title2) - HStack (alignment: .top) { + HStack(alignment: .top) { IndoorAirQuality(iaq: 6, displayMode: .gauge) IndoorAirQuality(iaq: 51, displayMode: .gauge) IndoorAirQuality(iaq: 101, displayMode: .gauge) IndoorAirQuality(iaq: 151, displayMode: .gauge) } - HStack (alignment: .top) { + HStack(alignment: .top) { IndoorAirQuality(iaq: 201, displayMode: .gauge) IndoorAirQuality(iaq: 251, displayMode: .gauge) IndoorAirQuality(iaq: 301, displayMode: .gauge) IndoorAirQuality(iaq: 351, displayMode: .gauge) } - HStack (alignment: .top) { + HStack(alignment: .top) { IndoorAirQuality(iaq: 401, displayMode: .gauge) IndoorAirQuality(iaq: 500, displayMode: .gauge) } @@ -142,7 +142,7 @@ struct IndoorAirQuality_Previews: PreviewProvider { IndoorAirQuality(iaq: 351, displayMode: .gradient) IndoorAirQuality(iaq: 401, displayMode: .gradient) IndoorAirQuality(iaq: 500, displayMode: .gradient) - + }.previewLayout(.fixed(width: 300, height: 800)) } } diff --git a/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift b/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift index bc1e12e7..2d45b4f4 100644 --- a/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift +++ b/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift @@ -49,7 +49,7 @@ struct MapButtons: View { } // MARK: Previews -//struct MapControl_Previews: PreviewProvider { +// struct MapControl_Previews: PreviewProvider { // @State static var tracking: UserTrackingModes = .none // @State static var isPresentingInfoSheet = false // static var previews: some View { @@ -61,4 +61,4 @@ struct MapButtons: View { // } // .previewLayout(.fixed(width: 60, height: 100)) // } -//} +// } diff --git a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift index 0a5edf70..20f62fbe 100644 --- a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift @@ -75,7 +75,7 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.showsBuildings = true mapView.showsScale = true mapView.showsTraffic = true - + mapView.showsCompass = false let compass = MKCompassButton(mapView: mapView) compass.translatesAutoresizingMaskIntoConstraints = false diff --git a/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift b/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift index eeba0318..b1ad3945 100644 --- a/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift +++ b/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift @@ -10,7 +10,7 @@ import MapKit import WeatherKit struct NodeMapMapkit: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager /// Weather @@ -21,7 +21,7 @@ struct NodeMapMapkit: View { @State private var symbolName: String = "cloud.fill" @State private var attributionLink: URL? @State private var attributionLogo: URL? - + @Environment(\.colorScheme) var colorScheme: ColorScheme @AppStorage("meshMapType") private var meshMapType = 0 @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false @@ -40,7 +40,7 @@ struct NodeMapMapkit: View { ), animation: .none) private var waypoints: FetchedResults @ObservedObject var node: NodeInfoEntity - + var body: some View { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) @@ -90,7 +90,7 @@ struct NodeMapMapkit: View { VStack { Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName) .font(.caption) - + Label("\(humidity ?? 0)%", systemImage: "humidity") .font(.caption2) @@ -103,12 +103,12 @@ struct NodeMapMapkit: View { .controlSize(.mini) } .frame(height: 10) - + Link("Other data sources", destination: attributionLink ?? URL(string: "https://weather-data.apple.com/legal-attribution.html")!) .font(.caption2) } .padding(5) - + } .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) .padding(5) diff --git a/Meshtastic/Views/Messages/ChannelList.swift b/Meshtastic/Views/Messages/ChannelList.swift index d8a4e96d..9cd2ed5d 100644 --- a/Meshtastic/Views/Messages/ChannelList.swift +++ b/Meshtastic/Views/Messages/ChannelList.swift @@ -9,7 +9,7 @@ import SwiftUI import CoreData struct ChannelList: View { - + @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -20,133 +20,139 @@ struct ChannelList: View { @State private var isPresentingDeleteChannelMessagesConfirm: Bool = false @State private var isPresentingTraceRouteSentAlert = false - + var restrictedChannels = ["gpio", "mqtt", "serial"] - - var body: some View { + + @ViewBuilder + private func makeNavigationLink( + myInfo: MyInfoEntity, + channel: ChannelEntity + ) -> some View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY") - + + NavigationLink(destination: ChannelMessageList(myInfo: myInfo, channel: channel)) { + let mostRecent = channel.allPrivateMessages.last(where: { $0.channel == channel.index }) + let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) + let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 + let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 + + ZStack { + Image(systemName: "circle.fill") + .opacity(channel.unreadMessages > 0 ? 1 : 0) + .font(.system(size: 10)) + .foregroundColor(.accentColor) + .brightness(0.2) + } + CircleText(text: String(channel.index), color: .accentColor) + .brightness(0.2) + + VStack(alignment: .leading) { + HStack { + if channel.name?.isEmpty ?? false { + if channel.role == 1 { + Text(String("PrimaryChannel").camelCaseToWords()) + .font(.headline) + } else { + Text(String("Channel \(channel.index)").camelCaseToWords()) + .font(.headline) + } + } else { + Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()) + .font(.headline) + } + + Spacer() + + if channel.allPrivateMessages.count > 0 { + + if lastMessageDay == currentDay { + Text(lastMessageTime, style: .time ) + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay == (currentDay - 1) { + Text("Yesterday") + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { + Text(lastMessageTime.formattedDate(format: dateFormatString)) + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay < (currentDay - 1800) { + Text(lastMessageTime.formattedDate(format: dateFormatString)) + .font(.footnote) + .foregroundColor(.secondary) + } + } + } + + if channel.allPrivateMessages.count > 0 { + HStack(alignment: .top) { + Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") + // .font(.system(size: 16)) + .font(.footnote) + .foregroundColor(.secondary) + } + } + } + } + } + + var body: some View { VStack { // Display Contacts for the rest of the non admin channels - if node != nil && node!.myInfo != nil && node!.myInfo!.channels != nil { - List(node!.myInfo!.channels!.array as! [ChannelEntity], id: \.self, selection: $channelSelection) { (channel: ChannelEntity) in + if let node, let myInfo = node.myInfo, let channels = myInfo.channels?.array as? [ChannelEntity] { + List(channels, id: \.self, selection: $channelSelection) { (channel: ChannelEntity) in if !restrictedChannels.contains(channel.name?.lowercased() ?? "") { - - NavigationLink(destination: ChannelMessageList(myInfo: node!.myInfo!, channel: channel)) { - - let mostRecent = channel.allPrivateMessages.last(where: { $0.channel == channel.index }) - let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) - let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 - let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 - - ZStack { - Image(systemName: "circle.fill") - .opacity(channel.unreadMessages > 0 ? 1 : 0) - .font(.system(size: 10)) - .foregroundColor(.accentColor) - .brightness(0.2) - } - CircleText(text: String(channel.index), color: .accentColor) - .brightness(0.2) - - VStack(alignment: .leading){ - HStack{ - if channel.name?.isEmpty ?? false { - if channel.role == 1 { - Text(String("PrimaryChannel").camelCaseToWords()) - .font(.headline) - } else { - Text(String("Channel \(channel.index)").camelCaseToWords()) - .font(.headline) - } - } else { - Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()) - .font(.headline) - } - - Spacer() - - if channel.allPrivateMessages.count > 0 { - - if lastMessageDay == currentDay { - Text(lastMessageTime, style: .time ) - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay == (currentDay - 1) { - Text("Yesterday") - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { - Text(lastMessageTime.formattedDate(format: dateFormatString)) - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay < (currentDay - 1800) { - Text(lastMessageTime.formattedDate(format: dateFormatString)) - .font(.footnote) - .foregroundColor(.secondary) - } - } - } - + makeNavigationLink(myInfo: myInfo, channel: channel) + .frame(height: 62) + .contextMenu { if channel.allPrivateMessages.count > 0 { - HStack(alignment: .top) { - Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") - //.font(.system(size: 16)) - .font(.footnote) - .foregroundColor(.secondary) + Button(role: .destructive) { + isPresentingDeleteChannelMessagesConfirm = true + channelSelection = channel + } label: { + Label("Delete Messages", systemImage: "trash") } } - } - } - .frame(height: 62) - .contextMenu { - if channel.allPrivateMessages.count > 0 { - Button(role: .destructive) { - isPresentingDeleteChannelMessagesConfirm = true - channelSelection = channel + Button { + channel.mute = !channel.mute + + do { + let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node.user!, toUser: node.user!) + if adminMessageId > 0 { + context.refresh(channel, mergeChanges: true) + } + + try context.save() + + } catch { + context.rollback() + print("πŸ’₯ Save Channel Mute Error") + } } label: { - Label("Delete Messages", systemImage: "trash") + Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash") } } - Button { - channel.mute = !channel.mute - - do { - let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node!.user!, toUser: node!.user!) - if adminMessageId > 0 { - context.refresh(channel, mergeChanges: true) - } - - try context.save() - - } catch { - context.rollback() - print("πŸ’₯ Save Channel Mute Error") + .confirmationDialog( + "This conversation will be deleted.", + isPresented: $isPresentingDeleteChannelMessagesConfirm, + titleVisibility: .visible + ) { + Button(role: .destructive) { + deleteChannelMessages(channel: channelSelection!, context: context) + context.refresh(myInfo, mergeChanges: true) + UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages + channelSelection = nil + } label: { + Text("delete") } - } label: { - Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash") } - } - .confirmationDialog( - "This conversation will be deleted.", - isPresented: $isPresentingDeleteChannelMessagesConfirm, - titleVisibility: .visible - ) { - Button(role: .destructive) { - deleteChannelMessages(channel: channelSelection!, context: context) - context.refresh(node!.myInfo!, mergeChanges: true) - UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages - channelSelection = nil - } label: { - Text("delete") + .onAppear { + if self.bleManager.context == nil { + self.bleManager.context = context + } } - } - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } } } .padding([.top, .bottom]) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 7d7a7962..70e88d78 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -60,7 +60,7 @@ struct ChannelMessageList: View { .foregroundColor(.gray) .offset(y: 8) } - + HStack { MessageText( message: message, @@ -75,13 +75,13 @@ struct ChannelMessageList: View { RetryButton(message: message, destination: .channel(channel)) } } - + TapbackResponses(message: message) { appState.unreadChannelMessages = myInfo.unreadMessages UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages context.refresh(myInfo, mergeChanges: true) } - + HStack { if currentUser && message.receivedACK { // Ack Received @@ -142,7 +142,7 @@ struct ChannelMessageList: View { } }) } - + TextMessageField( destination: .channel(channel), replyMessageId: $replyMessageId, diff --git a/Meshtastic/Views/Messages/MessageContextMenuItems.swift b/Meshtastic/Views/Messages/MessageContextMenuItems.swift index 4fa31421..ab363f59 100644 --- a/Meshtastic/Views/Messages/MessageContextMenuItems.swift +++ b/Meshtastic/Views/Messages/MessageContextMenuItems.swift @@ -4,7 +4,7 @@ import CoreData struct MessageContextMenuItems: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - + let message: MessageEntity let tapBackDestination: MessageDestination let isCurrentUser: Bool @@ -47,7 +47,7 @@ struct MessageContextMenuItems: View { Text("copy") Image(systemName: "doc.on.doc") } - + Menu("message.details") { VStack { let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp)) diff --git a/Meshtastic/Views/Messages/MessageText.swift b/Meshtastic/Views/Messages/MessageText.swift index 67500bc2..80d2cd05 100644 --- a/Meshtastic/Views/Messages/MessageText.swift +++ b/Meshtastic/Views/Messages/MessageText.swift @@ -10,7 +10,7 @@ struct MessageText: View { static let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss:a") @Environment(\.managedObjectContext) var context - + let message: MessageEntity let tapBackDestination: MessageDestination let isCurrentUser: Bool diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 482f538f..919426a9 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -12,7 +12,7 @@ import TipKit #endif struct Messages: View { - + @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -20,9 +20,9 @@ struct Messages: View { @State var node: NodeInfoEntity? @State private var userSelection: UserEntity? // Nothing selected by default. @State private var channelSelection: ChannelEntity? // Nothing selected by default. - + @State private var columnVisibility = NavigationSplitViewVisibility.all - + enum MessagesSidebar { case groupMessages case directMessages @@ -67,9 +67,9 @@ struct Messages: View { .navigationBarTitleDisplayMode(.large) .navigationBarItems(leading: MeshtasticLogo()) .onChange(of: (appState.navigationPath)) { newPath in - - if ((newPath?.hasPrefix("meshtastic://messages")) != nil) { - + + if (newPath?.hasPrefix("meshtastic://messages")) != nil { + if let urlComponent = URLComponents(string: newPath ?? "") { let queryItems = urlComponent.queryItems let messageId = queryItems?.first(where: { $0.name == "messageId" })?.value @@ -77,8 +77,7 @@ struct Messages: View { if channel == nil { print("Channel not found") - } - else { + } else { print("Channel \(channel)") // selectedNode = nodes.first(where: { $0.num == Int64(nodeNum ?? "-1") }) // AppState.shared.navigationPath = nil @@ -106,7 +105,7 @@ struct Messages: View { } } } - + } content: { } detail: { diff --git a/Meshtastic/Views/Messages/TapbackResponses.swift b/Meshtastic/Views/Messages/TapbackResponses.swift index a7685697..c5c21133 100644 --- a/Meshtastic/Views/Messages/TapbackResponses.swift +++ b/Meshtastic/Views/Messages/TapbackResponses.swift @@ -2,9 +2,9 @@ import SwiftUI struct TapbackResponses: View { @Environment(\.managedObjectContext) var context - + let message: MessageEntity - let onRead: () -> Void + let onRead: () -> Void @ViewBuilder var body: some View { diff --git a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift index 49580f02..4819e5be 100644 --- a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift +++ b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift @@ -3,12 +3,12 @@ import SwiftUI struct TextMessageField: View { static let maxbytes = 228 @EnvironmentObject var bleManager: BLEManager - + let destination: MessageDestination @Binding var replyMessageId: Int64 @FocusState.Binding var isFocused: Bool let onSubmit: () -> Void - + @State private var typingMessage: String = "" @State private var totalBytes = 0 @State private var sendPositionWithMessage = false @@ -25,7 +25,7 @@ struct TextMessageField: View { TextMessageSize(maxbytes: Self.maxbytes, totalBytes: totalBytes).padding(.trailing) } #endif - + HStack(alignment: .top) { ZStack { TextField("message", text: $typingMessage, axis: .vertical) @@ -80,13 +80,13 @@ struct TextMessageField: View { } .padding(.all, 15) } - + private func requestPosition() { let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown" sendPositionWithMessage = true typingMessage = "πŸ“ " + userLongName + " \(destination.positionShareMessage)." } - + private func sendMessage() { let messageSent = bleManager.sendMessage( message: typingMessage, @@ -121,7 +121,7 @@ private extension MessageDestination { case .channel: return "has shared their position with you" } } - + var positionDestNum: Int64 { switch self { case let .user(user): return user.num diff --git a/Meshtastic/Views/Messages/TextMessageField/TextMessageSize.swift b/Meshtastic/Views/Messages/TextMessageField/TextMessageSize.swift index d7428110..2b7b1e5e 100644 --- a/Meshtastic/Views/Messages/TextMessageField/TextMessageSize.swift +++ b/Meshtastic/Views/Messages/TextMessageField/TextMessageSize.swift @@ -3,7 +3,7 @@ import SwiftUI struct TextMessageSize: View { let maxbytes: Int let totalBytes: Int - + var body: some View { ProgressView("\("bytes".localized): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) .frame(width: 130) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 2d13d28f..ac3012c8 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -12,7 +12,7 @@ import TipKit #endif struct UserList: View { - + @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -26,7 +26,7 @@ struct UserList: View { @State private var hopsAway: Int = -1 @State private var deviceRole: Int = -1 @State var isEditingFilters = false - + @FetchRequest( sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), NSSortDescriptor(key: "userNode.favorite", ascending: false), @@ -38,7 +38,7 @@ struct UserList: View { @State var selectedUserNum: Int64? @State private var userSelection: UserEntity? // Nothing selected by default. @State private var isPresentingDeleteUserMessagesConfirm: Bool = false - + var body: some View { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY") @@ -61,15 +61,15 @@ struct UserList: View { .foregroundColor(.accentColor) .brightness(0.2) } - + CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num)))) - - VStack(alignment: .leading){ - HStack{ + + VStack(alignment: .leading) { + HStack { Text(user.longName ?? "unknown".localized) .font(.headline) Spacer() - if (user.userNode?.favorite ?? false) { + if user.userNode?.favorite ?? false { Image(systemName: "star.fill") .foregroundColor(.yellow) } @@ -93,7 +93,7 @@ struct UserList: View { } } } - + if user.messageList.count > 0 { HStack(alignment: .top) { Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") @@ -106,7 +106,7 @@ struct UserList: View { .frame(height: 62) .contextMenu { Button { - + if node != nil && !(user.userNode?.favorite ?? false) { let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num)) if success { @@ -238,9 +238,9 @@ struct UserList: View { .scrollDismissesKeyboard(.immediately) } } - + private func searchUserList() { - + /// Case Insensitive Search Text Predicates let searchPredicates = ["userId", "numString", "hwModel", "longName", "shortName"].map { property in return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText) @@ -269,7 +269,7 @@ struct UserList: View { let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway == %i", Int32(hopsAway)) predicates.append(hopsAwayPredicate) } - + /// Online if isOnline { let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate) @@ -283,22 +283,22 @@ struct UserList: View { /// Distance if distanceFilter { let pointOfInterest = LocationHelper.currentLocation - + if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude { - let D: Double = maxDistance * 1.1 - let R: Double = 6371009 + let d: Double = maxDistance * 1.1 + let r: Double = 6371009 let meanLatitidue = pointOfInterest.latitude * .pi / 180 - let deltaLatitude = D / R * 180 / .pi - let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi + let deltaLatitude = d / r * 180 / .pi + let deltaLongitude = d / (r * cos(meanLatitidue)) * 180 / .pi let minLatitude: Double = pointOfInterest.latitude - deltaLatitude let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude let minLongitude: Double = pointOfInterest.longitude - deltaLongitude let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude - let distancePredicate = NSPredicate(format: "(SUBQUERY(userNode.positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude,minLatitude, maxLatitude) + let distancePredicate = NSPredicate(format: "(SUBQUERY(userNode.positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude, minLatitude, maxLatitude) predicates.append(distancePredicate) } } - + if predicates.count > 0 || !searchText.isEmpty { if !searchText.isEmpty { let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates) diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 53dd8280..41d21db1 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -113,7 +113,7 @@ struct EnvironmentMetricsLog: View { GridItem(spacing: 0) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1, pinnedViews: [.sectionHeaders]) { - + GridRow { Text("Temp") .font(.caption) @@ -132,9 +132,9 @@ struct EnvironmentMetricsLog: View { .fontWeight(.bold) } ForEach(environmentMetrics, id: \.self) { em in - + GridRow { - + Text(em.temperature.formattedTemperature()) .font(.caption) Text("\(String(format: "%.0f", em.relativeHumidity))%") @@ -154,7 +154,7 @@ struct EnvironmentMetricsLog: View { } } HStack { - + Button(role: .destructive) { isPresentingClearLogConfirm = true } label: { @@ -188,7 +188,7 @@ struct EnvironmentMetricsLog: View { .padding(.bottom) .padding(.trailing) } - + } else { if #available (iOS 17, *) { ContentUnavailableView("No Environment Metrics", systemImage: "slash.circle") @@ -197,7 +197,7 @@ struct EnvironmentMetricsLog: View { } } } - + .navigationTitle("Environment Metrics Log") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index 4270343c..7c7d3356 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -10,7 +10,7 @@ import MapKit @available(iOS 17.0, macOS 14.0, *) struct MeshMapContent: MapContent { - + @StateObject var appState = AppState.shared /// Parameters @Binding var showUserLocation: Bool @@ -24,13 +24,13 @@ struct MeshMapContent: MapContent { @Binding var selectedPosition: PositionEntity? @AppStorage("enableMapWaypoints") private var showWaypoints = false @Binding var selectedWaypoint: WaypointEntity? - + @FetchRequest(fetchRequest: PositionEntity.allPositionsFetchRequest(), animation: .easeIn) var positions: FetchedResults - + @FetchRequest(fetchRequest: WaypointEntity.allWaypointssFetchRequest(), animation: .none) var waypoints: FetchedResults - + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)], predicate: NSPredicate(format: "enabled == true", ""), animation: .none) private var routes: FetchedResults @@ -39,26 +39,13 @@ struct MeshMapContent: MapContent { @State private var scale: CGFloat = 0.5 @MapContentBuilder - var meshMap: some MapContent { - let loraNodes = positions.filter { $0.nodePosition?.viaMqtt ?? true == false } - let loraCoords = Array(loraNodes).compactMap({(position) -> CLLocationCoordinate2D in - return position.nodeCoordinate ?? LocationsHandler.DefaultLocation - }) - /// Convex Hull - if showConvexHull { - if loraCoords.count > 0 { - let hull = loraCoords.getConvexHull() - MapPolygon(coordinates: hull) - .stroke(.blue, lineWidth: 3) - .foregroundStyle(.indigo.opacity(0.4)) - } - } - /// Position Annotations - ForEach(Array(positions), id: \.id) { position in + var positionAnnotations: some MapContent { + ForEach(positions, id: \.id) { position in /// Node color from node.num let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0)) + let positionName = position.nodePosition?.user?.longName ?? "?" /// Latest Position Anotations - Annotation(position.nodePosition?.user?.longName ?? "?", coordinate: position.coordinate) { + Annotation(positionName, coordinate: position.coordinate) { LazyVStack { ZStack { let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0)) @@ -89,16 +76,14 @@ struct MeshMapContent: MapContent { } } } - .onTapGesture { location in - selectedPosition = (selectedPosition == position ? nil : position) - } } - - + /// Node History and Route Lines for favorites - if position.nodePosition?.favorite ?? false { + if let nodePosition = position.nodePosition, + nodePosition.favorite, + let positions = nodePosition.positions, + let nodePositions = Array(positions) as? [PositionEntity] { if showRouteLines { - let nodePositions = Array(position.nodePosition!.positions!) as! [PositionEntity] let routeCoords = nodePositions.compactMap({(pos) -> CLLocationCoordinate2D in return pos.nodeCoordinate ?? LocationHelper.DefaultLocation }) @@ -114,7 +99,7 @@ struct MeshMapContent: MapContent { .stroke(gradient, style: dashed) } if showNodeHistory { - ForEach(Array(position.nodePosition!.positions!) as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in + ForEach(nodePositions, id: \.self) { (mappin: PositionEntity) in if mappin.latest == false && mappin.nodePosition?.favorite ?? false { let pf = PositionFlags(rawValue: Int(mappin.nodePosition?.metadata?.positionFlags ?? 771)) let headingDegrees = Angle.degrees(Double(mappin.heading)) @@ -129,11 +114,11 @@ struct MeshMapContent: MapContent { .clipShape(Circle()) .rotationEffect(headingDegrees) .frame(width: 16, height: 16) - + } else { Circle() .fill(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0)))) - .strokeBorder(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))).isLight() ? .black : .white ,lineWidth: 2) + .strokeBorder(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))).isLight() ? .black : .white, lineWidth: 2) .frame(width: 12, height: 12) } } @@ -147,19 +132,23 @@ struct MeshMapContent: MapContent { /// Reduced Precision Map Circles if 10...19 ~= position.precisionBits { let pp = PositionPrecision(rawValue: Int(position.precisionBits)) - let radius : CLLocationDistance = pp?.precisionMeters ?? 0 + let radius: CLLocationDistance = pp?.precisionMeters ?? 0 if radius > 0.0 { MapCircle(center: position.coordinate, radius: radius) .foregroundStyle(Color(nodeColor).opacity(0.25)) .stroke(.white, lineWidth: 2) } } - /// Routes - ForEach(Array(routes)) { route in - let routeLocations = Array(route.locations!) as! [LocationEntity] - let routeCoords = routeLocations.compactMap({(loc) -> CLLocationCoordinate2D in + } + } + + @MapContentBuilder + var routeAnnotations: some MapContent { + ForEach(routes) { route in + if let routeLocations = route.locations, let locations = Array(routeLocations) as? [LocationEntity] { + let routeCoords = locations.compactMap {(loc) -> CLLocationCoordinate2D in return loc.locationCoordinate ?? LocationHelper.DefaultLocation - }) + } Annotation("Start", coordinate: routeCoords.first ?? LocationHelper.DefaultLocation) { ZStack { Circle() @@ -184,18 +173,19 @@ struct MeshMapContent: MapContent { ) MapPolyline(coordinates: routeCoords) .stroke(Color(UIColor(hex: UInt32(route.color))), style: solid) - } } - - /// Waypoint Annotations - if waypoints.count > 0 && showWaypoints { - ForEach(Array(waypoints) as! [WaypointEntity], id: \.self) { waypoint in + } + + @MapContentBuilder + var waypointAnnotations: some MapContent { + if waypoints.count > 0, showWaypoints, let waypoints = Array(waypoints) as? [WaypointEntity] { + ForEach(waypoints, id: \.self) { waypoint in Annotation(waypoint.name ?? "?", coordinate: waypoint.coordinate) { LazyVStack { ZStack { CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "πŸ“"), color: Color.orange, circleSize: 40) - .onTapGesture(perform: { location in + .onTapGesture(perform: { _ in selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint) }) } @@ -204,7 +194,27 @@ struct MeshMapContent: MapContent { } } } - + + @MapContentBuilder + var meshMap: some MapContent { + let loraNodes = positions.filter { $0.nodePosition?.viaMqtt ?? true == false } + let loraCoords = Array(loraNodes).compactMap({(position) -> CLLocationCoordinate2D in + return position.nodeCoordinate ?? LocationsHandler.DefaultLocation + }) + /// Convex Hull + if showConvexHull { + if loraCoords.count > 0 { + let hull = loraCoords.getConvexHull() + MapPolygon(coordinates: hull) + .stroke(.blue, lineWidth: 3) + .foregroundStyle(.indigo.opacity(0.4)) + } + } + positionAnnotations + routeAnnotations + waypointAnnotations + } + @MapContentBuilder var body: some MapContent { meshMap diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift index 78d20407..a0c2f63b 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift @@ -10,7 +10,7 @@ import CoreData @available(iOS 17.0, macOS 14.0, *) struct NodeMapContent: MapContent { - + @ObservedObject var node: NodeInfoEntity @State var showUserLocation: Bool = false @State var positions: [PositionEntity] = [] @@ -22,7 +22,7 @@ struct NodeMapContent: MapContent { @AppStorage("enableMapTraffic") private var showTraffic: Bool = false @AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = false @AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid - + // Map Configuration @Namespace var mapScope @State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true) @@ -33,26 +33,26 @@ struct NodeMapContent: MapContent { @State var isEditingSettings = false @State var selectedPosition: PositionEntity? @State var isMeshMap = false - + @MapContentBuilder var nodeMap: some MapContent { let positionArray = node.positions?.array as? [PositionEntity] ?? [] let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in return position.nodeCoordinate ?? LocationsHandler.DefaultLocation }) - + /// Node Color from node.num let nodeColor = UIColor(hex: UInt32(node.num)) - + /// Node Annotations ForEach(node.positions?.array as? [PositionEntity] ?? [], id: \.id) { position in - + let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771)) let headingDegrees = Angle.degrees(Double(position.heading)) /// Reduced Precision Map Circle if position.latest && 10...19 ~= position.precisionBits { let pp = PositionPrecision(rawValue: Int(position.precisionBits)) - let radius : CLLocationDistance = pp?.precisionMeters ?? 0 + let radius: CLLocationDistance = pp?.precisionMeters ?? 0 if radius > 0.0 { MapCircle(center: position.coordinate, radius: radius) .foregroundStyle(Color(nodeColor).opacity(0.25)) @@ -73,7 +73,7 @@ struct NodeMapContent: MapContent { } } /// Route Lines - if showRouteLines { + if showRouteLines { let gradient = LinearGradient( colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)], startPoint: .leading, endPoint: .trailing @@ -153,11 +153,11 @@ struct NodeMapContent: MapContent { .clipShape(Circle()) .rotationEffect(headingDegrees) .frame(width: 16, height: 16) - + } else { Circle() .fill(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0)))) - .strokeBorder(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))).isLight() ? .black : .white ,lineWidth: 2) + .strokeBorder(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))).isLight() ? .black : .white, lineWidth: 2) .frame(width: 12, height: 12) } } @@ -168,7 +168,7 @@ struct NodeMapContent: MapContent { } } } - + @MapContentBuilder var body: some MapContent { if node.positions?.count ?? 0 > 0 { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift index 9a775911..2b703506 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift @@ -24,7 +24,7 @@ struct MapSettingsForm: View { @Binding var meshMap: Bool var body: some View { - + NavigationStack { Form { Section(header: Text("Map Options")) { @@ -63,7 +63,7 @@ struct MapSettingsForm: View { UserDefaults.enableMapWaypoints = !waypoints } } - + Toggle(isOn: $nodeHistory) { Label("Node History", systemImage: "building.columns.fill") } @@ -75,7 +75,7 @@ struct MapSettingsForm: View { Toggle(isOn: $routeLines) { Label("Route Lines", systemImage: "road.lanes") } - + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .onTapGesture { self.routeLines.toggle() @@ -123,6 +123,6 @@ Spacer() } .presentationDetents([.fraction(meshMap ? 0.55 : 0.45), .fraction(0.65)]) .presentationDragIndicator(.visible) - + } } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index bab5aa81..f8ee5165 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -32,21 +32,21 @@ struct NodeMapSwiftUI: View { @State var isShowingAltitude = false @State var isEditingSettings = false @State var isMeshMap = false - + @State private var mapRegion = MKCoordinateRegion.init() - + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], predicate: NSPredicate( format: "expire == nil || expire >= %@", Date() as NSDate ), animation: .none) private var waypoints: FetchedResults - + var body: some View { var mostRecent = node.positions?.lastObject as? PositionEntity - + if node.hasPositions { ZStack { - MapReader { reader in + MapReader { _ in Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { NodeMapContent(node: node) } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift index 4f69bd2a..2bbeaea1 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift @@ -21,14 +21,26 @@ struct PositionAltitudeChart: View { @Environment(\.dismiss) private var dismiss @ObservedObject var node: NodeInfoEntity @State private var lineWidth = 2.0 - - var body: some View { + + var data: [PositionAltitude] { let fiveYearsAgo = Calendar.current.date(byAdding: .year, value: -5, to: Date()) - let nodePositions = Array(node.positions!) as! [PositionEntity] - let filteredPositions = nodePositions.filter({$0.time != nil && ($0.time ?? fiveYearsAgo!) > fiveYearsAgo!}) - let data = filteredPositions.map { PositionAltitude(time: $0.time ?? Date(), altitude: Measurement(value: Double($0.altitude), unit: .meters) ) } + guard let nodePositions = node.positions, + let positions = Array(nodePositions) as? [PositionEntity] + else { + return [] + } + + let filteredPositions = positions.filter({$0.time != nil && ($0.time ?? fiveYearsAgo!) > fiveYearsAgo!}) + return filteredPositions.map { + PositionAltitude( + time: $0.time ?? Date(), + altitude: Measurement(value: Double($0.altitude), unit: .meters) + ) + } + } + + var body: some View { GroupBox(label: Label("Altitude", systemImage: "mountain.2")) { - Chart(data, id: \.time) { LineMark( x: .value("Time", $0.time), diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index daa43ac9..37e913bf 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -25,7 +25,7 @@ struct PositionPopover: View { VStack { HStack { ZStack { - + if position.nodePosition?.isOnline ?? false { Circle() .fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5))) @@ -42,13 +42,13 @@ struct PositionPopover: View { } CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 65) } - + Text(position.nodePosition?.user?.longName ?? "Unknown") .font(.largeTitle) } Divider() - HStack (alignment: .center) { - VStack (alignment: .leading) { + HStack(alignment: .center) { + VStack(alignment: .leading) { /// Time Label { Text("heard".localized + ":") @@ -131,7 +131,7 @@ struct PositionPopover: View { } .padding(.bottom, 5) if position.nodePosition?.viaMqtt ?? false { - + Label { let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees) Text("MQTT") @@ -146,7 +146,7 @@ struct PositionPopover: View { if let lastLocation = locationsHandler.locationsArray.last { /// Distance if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { - let metersAway = position.coordinate.distance(from:CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude)) + let metersAway = position.coordinate.distance(from: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude)) Label { Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))") .foregroundColor(.primary) @@ -160,7 +160,7 @@ struct PositionPopover: View { Spacer() } Spacer() - VStack (alignment: .center) { + VStack(alignment: .center) { if position.nodePosition != nil { if position.nodePosition?.favorite ?? false { Image(systemName: "star.fill") diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 0869ff2b..9a29e378 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -1,4 +1,3 @@ - // // WaypointForm.swift // Meshtastic @@ -11,7 +10,7 @@ import MapKit import CoreLocation struct WaypointForm: View { - + @EnvironmentObject var bleManager: BLEManager @Environment(\.dismiss) private var dismiss @State var waypoint: WaypointEntity @@ -27,7 +26,7 @@ struct WaypointForm: View { @State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours @State private var locked: Bool = false @State private var lockedTo: Int64 = 0 - + var body: some View { NavigationStack { if editMode { @@ -35,7 +34,7 @@ struct WaypointForm: View { .font(.largeTitle) Divider() Form { - let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: waypoint.coordinate.latitude , longitude: waypoint.coordinate.longitude )) + let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: waypoint.coordinate.latitude, longitude: waypoint.coordinate.longitude )) Section(header: Text("Coordinate") ) { HStack { Text("Location: \(String(format: "%.5f", waypoint.coordinate.latitude) + "," + String(format: "%.5f", waypoint.coordinate.longitude))") @@ -91,14 +90,14 @@ struct WaypointForm: View { .font(.title) .focused($iconIsFocused) .onChange(of: icon) { value in - + // If you have anything other than emojis in your string make it empty if !value.onlyEmojis() { icon = "" } // If a second emoji is entered delete the first one if value.count >= 1 { - + if value.count > 1 { let index = value.index(value.startIndex, offsetBy: 1) icon = String(value[index]) @@ -106,7 +105,7 @@ struct WaypointForm: View { iconIsFocused = false } } - + } Toggle(isOn: $expires) { Label("Expires", systemImage: "clock.badge.xmark") @@ -168,7 +167,7 @@ struct WaypointForm: View { .controlSize(.regular) .disabled(bleManager.connectedPeripheral == nil) .padding(.bottom) - + Button(role: .cancel) { dismiss() } label: { @@ -178,9 +177,9 @@ struct WaypointForm: View { .buttonBorderShape(.capsule) .controlSize(.regular) .padding(.bottom) - + if waypoint.id > 0 && bleManager.isConnected { - + Menu { Button("For me", action: { bleManager.context!.delete(waypoint) @@ -211,7 +210,7 @@ struct WaypointForm: View { } newWaypoint.expire = UInt32(1) if bleManager.sendWaypoint(waypoint: newWaypoint) { - + bleManager.context!.delete(waypoint) do { try bleManager.context!.save() @@ -237,7 +236,7 @@ struct WaypointForm: View { } } else { VStack { - HStack { + HStack { CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "πŸ“"), color: Color.orange, circleSize: 65) Spacer() Text(waypoint.name ?? "?") @@ -258,7 +257,7 @@ struct WaypointForm: View { } } Divider() - VStack (alignment: .leading) { + VStack(alignment: .leading) { // Description if (waypoint.longDescription ?? "").count > 0 { Label { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index d93a6ab2..1505638a 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -22,7 +22,7 @@ struct NodeDetail: View { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) NavigationStack { - GeometryReader { bounds in + GeometryReader { _ in VStack { ScrollView { NodeInfoItem(node: node) @@ -78,12 +78,12 @@ struct NodeDetail: View { Image(systemName: "flipphone") .symbolRenderingMode(.hierarchical) .font(.title) - + Text("Device Metrics Log") .font(.title3) } .disabled(!node.hasDeviceMetrics) - + Divider() NavigationLink { if #available (iOS 17, macOS 14, *) { @@ -91,12 +91,12 @@ struct NodeDetail: View { } else { NodeMapMapkit(node: node) } - + } label: { Image(systemName: "map") .symbolRenderingMode(.hierarchical) .font(.title) - + Text("Node Map") .font(.title3) } @@ -108,7 +108,7 @@ struct NodeDetail: View { Image(systemName: "mappin.and.ellipse") .symbolRenderingMode(.hierarchical) .font(.title) - + Text("Position Log") .font(.title3) } @@ -120,7 +120,7 @@ struct NodeDetail: View { Image(systemName: "cloud.sun.rain") .symbolRenderingMode(.hierarchical) .font(.title) - + Text("Environment Metrics Log") .font(.title3) } @@ -133,7 +133,7 @@ struct NodeDetail: View { Image(systemName: "signpost.right.and.left") .symbolRenderingMode(.hierarchical) .font(.title) - + Text("Trace Route Log") .font(.title3) } @@ -146,7 +146,7 @@ struct NodeDetail: View { Image(systemName: "sensor") .symbolRenderingMode(.hierarchical) .font(.title) - + Text("Detection Sensor Log") .font(.title3) } @@ -159,7 +159,7 @@ struct NodeDetail: View { Image(systemName: "figure.walk.motion") .symbolRenderingMode(.hierarchical) .font(.title) - + Text("paxcounter.log") .font(.title3) } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfo.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfo.swift index 3c230e27..920f4335 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfo.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfo.swift @@ -14,9 +14,9 @@ struct NodeInfoItem: View { @ObservedObject var node: NodeInfoEntity var body: some View { - + Divider() - + HStack { VStack(alignment: .center) { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index 318962c6..de066816 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -15,9 +15,9 @@ struct NodeInfoItem: View { var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast var body: some View { - + Divider() - + HStack { VStack(alignment: .center) { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift index 321054c1..2c49a7df 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift @@ -20,14 +20,14 @@ struct NodeListFilter: View { @Binding var maximumDistance: Double @Binding var hopsAway: Int @Binding var deviceRole: Int - + var body: some View { - + NavigationStack { Form { Section(header: Text(filterTitle)) { Toggle(isOn: $viaLora) { - + Label { Text("Via Lora") } icon: { @@ -37,7 +37,7 @@ struct NodeListFilter: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Toggle(isOn: $viaMqtt) { - + Label { Text("Via Mqtt") } icon: { @@ -46,9 +46,9 @@ struct NodeListFilter: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) - + Toggle(isOn: $isOnline) { - + Label { Text("Online") } icon: { @@ -59,13 +59,13 @@ struct NodeListFilter: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) - + Toggle(isOn: $isFavorite) { - + Label { Text("Favorites") } icon: { - + Image(systemName: "star.fill") .foregroundColor(.yellow) .symbolRenderingMode(.hierarchical) @@ -73,9 +73,9 @@ struct NodeListFilter: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) - + Toggle(isOn: $distanceFilter) { - + Label { Text("Distance") } icon: { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index e9c1ef65..172795e4 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -9,14 +9,14 @@ import SwiftUI import CoreLocation struct NodeListItem: View { - + @ObservedObject var node: NodeInfoEntity var connected: Bool var connectedNode: Int64 var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast - + var body: some View { - + NavigationLink(value: node) { LazyVStack(alignment: .leading) { HStack { @@ -69,7 +69,7 @@ struct NodeListItem: View { Text("Role: \(role?.name ?? "unknown".localized)") .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) .foregroundColor(.gray) - + } if node.isStoreForwardRouter { HStack { @@ -82,14 +82,29 @@ struct NodeListItem: View { .foregroundColor(.gray) } } - + if node.positions?.count ?? 0 > 0 && connectedNode != node.num { HStack { - let lastPostion = node.positions!.reversed()[0] as! PositionEntity - if #available(iOS 17.0, macOS 14.0, *) { - if let currentLocation = LocationsHandler.shared.locationsArray.last { - let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude) - if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude { + if let lastPostion = node.positions?.lastObject as? PositionEntity { + if #available(iOS 17.0, macOS 14.0, *) { + if let currentLocation = LocationsHandler.shared.locationsArray.last { + let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude) + if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude { + let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) + let metersAway = nodeCoord.distance(from: myCoord) + Image(systemName: "lines.measurement.horizontal") + .font(.callout) + .symbolRenderingMode(.hierarchical) + .frame(width: 30) + DistanceText(meters: metersAway) + .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + .foregroundColor(.gray) + } + } + } else { + + let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) + if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) let metersAway = nodeCoord.distance(from: myCoord) Image(systemName: "lines.measurement.horizontal") @@ -101,20 +116,6 @@ struct NodeListItem: View { .foregroundColor(.gray) } } - } else { - - let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) - if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { - let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) - let metersAway = nodeCoord.distance(from: myCoord) - Image(systemName: "lines.measurement.horizontal") - .font(.callout) - .symbolRenderingMode(.hierarchical) - .frame(width: 30) - DistanceText(meters: metersAway) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) - } } } } @@ -131,7 +132,7 @@ struct NodeListItem: View { .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) } } - + if node.viaMqtt && connectedNode != node.num { Image(systemName: "dot.radiowaves.up.forward") .symbolRenderingMode(.hierarchical) diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 35b67397..5787e3ea 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -13,11 +13,9 @@ import Foundation import MapKit #endif - - @available(iOS 17.0, macOS 14.0, *) struct MeshMap: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @StateObject var appState = AppState.shared @@ -29,7 +27,7 @@ struct MeshMap: View { @AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .standard // Map Configuration @Namespace var mapScope - @State var mapStyle: MapStyle = MapStyle.standard(elevation: .flat, emphasis: MapStyle.StandardEmphasis.muted ,pointsOfInterest: .excludingAll, showsTraffic: false) + @State var mapStyle: MapStyle = MapStyle.standard(elevation: .flat, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .excludingAll, showsTraffic: false) @State var position = MapCameraPosition.automatic @State var isEditingSettings = false @State var selectedPosition: PositionEntity? @@ -39,15 +37,14 @@ struct MeshMap: View { @State var newWaypointCoord: CLLocationCoordinate2D? @State var isMeshMap = true - var body: some View { - + NavigationStack { ZStack { MapReader { reader in Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { MeshMapContent(showUserLocation: $showUserLocation, showTraffic: $showTraffic, showPointsOfInterest: $showPointsOfInterest, selectedMapLayer: $selectedMapLayer, selectedPosition: $selectedPosition, selectedWaypoint: $selectedWaypoint) - + } .mapScope(mapScope) .mapStyle(mapStyle) @@ -60,7 +57,7 @@ struct MeshMap: View { .mapControlVisibility(.automatic) } .controlSize(.regular) - .onTapGesture(count: 1, perform: { position in + .onTapGesture(count: 1, perform: { position in newWaypointCoord = reader.convert(position, from: .local) ?? CLLocationCoordinate2D.init() }) .gesture( @@ -73,7 +70,7 @@ struct MeshMap: View { print("Unable to retreive tap location from gesture data.") return } - + guard let coordinate = reader.convert(point, from: .local) else { print("Unable to convert local point to coordinate on map.") return @@ -162,7 +159,7 @@ struct MeshMap: View { } .tint(Color(UIColor.secondarySystemBackground)) .foregroundColor(.accentColor) - .buttonStyle(.borderedProminent) + .buttonStyle(.borderedProminent) } .controlSize(.regular) .padding(5) @@ -176,9 +173,9 @@ struct MeshMap: View { if self.bleManager.context == nil { self.bleManager.context = context } - + // let wayPointEntity = getWaypoint(id: Int64(deepLinkManager.waypointId) ?? -1, context: context) - //if wayPointEntity.id > 0 { + // if wayPointEntity.id > 0 { // position = .camera(MapCamera(centerCoordinate: wayPointEntity.coordinate, distance: 1000, heading: 0, pitch: 60)) switch selectedMapLayer { case .standard: diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index a6e1368d..53cd2467 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -8,7 +8,7 @@ import SwiftUI import CoreLocation struct NodeList: View { - + @StateObject var appState = AppState.shared @State private var columnVisibility = NavigationSplitViewVisibility.all @State private var selectedNode: NodeInfoEntity? @@ -26,25 +26,25 @@ struct NodeList: View { @State private var maxDistance: Double = 800000 @State private var hopsAway: Int = -1 @State private var deviceRole: Int = -1 - + @State var isEditingFilters = false - + @SceneStorage("selectedDetailView") var selectedDetailView: String? @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @FetchRequest( - sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false), + sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false), NSSortDescriptor(key: "lastHeard", ascending: false), NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default) var nodes: FetchedResults - + var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { - + // HStack { // Button("Open Node") { // UIApplication @@ -52,19 +52,19 @@ struct NodeList: View { // .open(URL(string: "meshtastic://nodes?nodeNum=530606484")!) // } // } - + let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) List(nodes, id: \.self, selection: $selectedNode) { node in - - NodeListItem(node: node, + + NodeListItem(node: node, connected: bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num, connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1)) .contextMenu { - + Button { if !node.favorite { - + let success = bleManager.setFavoriteNode(node: node, connectedNodeNum: Int64(connectedNodeNum)) if success { node.favorite = !node.favorite @@ -89,7 +89,7 @@ struct NodeList: View { print("Favorited a node") } } - + } label: { Label(node.favorite ? "Un-Favorite" : "Favorite", systemImage: node.favorite ? "star.slash.fill" : "star.fill") } @@ -132,14 +132,14 @@ struct NodeList: View { isPresentingTraceRouteSentAlert = false } } - + } label: { Label("Trace Route", systemImage: "signpost.right.and.left") } if node.isStoreForwardRouter { Button { - let success = bleManager.requestStoreAndForwardClientHistory(fromUser: connectedNode!.user!, toUser: node.user!) + let success = bleManager.requestStoreAndForwardClientHistory(fromUser: connectedNode!.user!, toUser: node.user!) if success { isPresentingClientHistorySentAlert = true DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { @@ -152,7 +152,7 @@ struct NodeList: View { } } if bleManager.connectedPeripheral != nil { - Button (role: .destructive) { + Button(role: .destructive) { deleteNodeId = node.num isPresentingDeleteNodeAlert = true } label: { @@ -212,7 +212,7 @@ struct NodeList: View { .disableAutocorrection(true) .scrollDismissesKeyboard(.immediately) .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) - + .listStyle(.plain) .confirmationDialog( @@ -251,7 +251,7 @@ struct NodeList: View { .navigationBarItems( trailing: ZStack { - if (UIDevice.current.userInterfaceIdiom != .phone) { + if UIDevice.current.userInterfaceIdiom != .phone { Button { columnVisibility = .detailOnly } label: { @@ -264,7 +264,7 @@ struct NodeList: View { name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", phoneOnly: true) }) } - + } else { if #available (iOS 17, *) { ContentUnavailableView("select.node", systemImage: "flipphone") @@ -278,7 +278,7 @@ struct NodeList: View { } else { Text("Select something to view") } - + } .navigationSplitViewStyle(.balanced) .onChange(of: searchText) { _ in @@ -315,19 +315,18 @@ struct NodeList: View { searchNodeList() } .onChange(of: (appState.navigationPath)) { newPath in - + guard let deepLink = newPath else { return } if deepLink.hasPrefix("meshtastic://nodes") { - + if let urlComponent = URLComponents(string: deepLink) { let queryItems = urlComponent.queryItems let nodeNum = queryItems?.first(where: { $0.name == "nodenum" })?.value if nodeNum == nil { print("nodeNum not found") - } - else { + } else { selectedNode = nodes.first(where: { $0.num == Int64(nodeNum ?? "-1") }) AppState.shared.navigationPath = nil } @@ -371,7 +370,7 @@ struct NodeList: View { let hopsAwayPredicate = NSPredicate(format: "hopsAway == %i", Int32(hopsAway)) predicates.append(hopsAwayPredicate) } - + /// Online if isOnline { let isOnlinePredicate = NSPredicate(format: "lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate) @@ -385,22 +384,22 @@ struct NodeList: View { /// Distance if distanceFilter { let pointOfInterest = LocationHelper.currentLocation - + if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude { - let D: Double = maxDistance * 1.1 - let R: Double = 6371009 + let d: Double = maxDistance * 1.1 + let r: Double = 6371009 let meanLatitidue = pointOfInterest.latitude * .pi / 180 - let deltaLatitude = D / R * 180 / .pi - let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi + let deltaLatitude = d / r * 180 / .pi + let deltaLongitude = d / (r * cos(meanLatitidue)) * 180 / .pi let minLatitude: Double = pointOfInterest.latitude - deltaLatitude let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude let minLongitude: Double = pointOfInterest.longitude - deltaLongitude let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude - let distancePredicate = NSPredicate(format: "(SUBQUERY(positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude,minLatitude, maxLatitude) + let distancePredicate = NSPredicate(format: "(SUBQUERY(positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude, minLatitude, maxLatitude) predicates.append(distancePredicate) } } - + if predicates.count > 0 || !searchText.isEmpty { if !searchText.isEmpty { let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates) diff --git a/Meshtastic/Views/Nodes/PaxCounterLog.swift b/Meshtastic/Views/Nodes/PaxCounterLog.swift index 567b4d4c..f4e73c5a 100644 --- a/Meshtastic/Views/Nodes/PaxCounterLog.swift +++ b/Meshtastic/Views/Nodes/PaxCounterLog.swift @@ -25,13 +25,13 @@ struct PaxCounterLog: View { var body: some View { VStack { if node.hasPax { - + let oneWeekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date()) let pax = node.pax?.reversed() as? [PaxCounterEntity] ?? [] let chartData = pax .filter { $0.time != nil && $0.time! >= oneWeekAgo! } .sorted { $0.time! < $1.time! } - let maxValue = (chartData.map{ $0.wifi }.max() ?? 0) + (chartData.map{ $0.ble }.max() ?? 0) + 5 + let maxValue = (chartData.map { $0.wifi }.max() ?? 0) + (chartData.map { $0.ble }.max() ?? 0) + 5 if chartData.count > 0 { GroupBox(label: Label("\(pax.count) Readings Total", systemImage: "chart.xyaxis.line")) { @@ -47,7 +47,7 @@ struct PaxCounterLog: View { .accessibilityValue("X: \(point.time!), Y: \(point.wifi + point.ble)") .foregroundStyle(paxChartColor) .interpolationMethod(.cardinal) - + Plot { PointMark( x: .value("x", point.time!), diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index f4979cb7..f675e8dc 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -20,7 +20,7 @@ struct PositionLog: View { @ObservedObject var node: NodeInfoEntity @State private var isPresentingClearLogConfirm = false @State private var sortOrder = [KeyPathComparator(\PositionEntity.time)] - + var body: some View { VStack { if node.hasPositions { @@ -62,7 +62,7 @@ struct PositionLog: View { } .width(min: 180) } - + } else { ScrollView { // Use a grid on iOS as a table only shows a single column @@ -91,19 +91,21 @@ struct PositionLog: View { .font(.caption2) .fontWeight(.bold) } - ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in - let altitude = Measurement(value: Double(mappin.altitude), unit: UnitLength.meters) - GridRow { - Text(String(format: "%.5f", mappin.latitude ?? 0)) - .font(.caption2) - Text(String(format: "%.5f", mappin.longitude ?? 0)) - .font(.caption2) - Text(String(mappin.satsInView)) - .font(.caption2) - Text(altitude.formatted()) - .font(.caption2) - Text(mappin.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) - .font(.caption2) + if let positions = node.positions?.reversed() as? [PositionEntity] { + ForEach(positions, id: \.self) { (mappin: PositionEntity) in + let altitude = Measurement(value: Double(mappin.altitude), unit: UnitLength.meters) + GridRow { + Text(String(format: "%.5f", mappin.latitude ?? 0)) + .font(.caption2) + Text(String(format: "%.5f", mappin.longitude ?? 0)) + .font(.caption2) + Text(String(mappin.satsInView)) + .font(.caption2) + Text(altitude.formatted()) + .font(.caption2) + Text(mappin.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) + .font(.caption2) + } } } } @@ -160,7 +162,7 @@ struct PositionLog: View { } } ) - + } else { if #available (iOS 17, *) { ContentUnavailableView("No Positions", systemImage: "mappin.slash") diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index dde3db46..55dd6d20 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -15,7 +15,7 @@ struct TraceRouteLog: View { @ObservedObject var locationsHandler = LocationsHandler.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - + @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @State var exportString = "" @@ -23,16 +23,16 @@ struct TraceRouteLog: View { @State private var selectedRoute: TraceRouteEntity? // Map Configuration @Namespace var mapScope - @State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted ,pointsOfInterest: .all, showsTraffic: true) + @State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .all, showsTraffic: true) @State var position = MapCameraPosition.automatic let distanceFormatter = MKDistanceFormatter() - + var body: some View { - HStack (alignment: .top) { + HStack(alignment: .top) { VStack { VStack { List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in - + Label { Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "\(route.hops?.count ?? 0) \(route.hops?.count ?? 0 == 1 ? "Hop": "Hops")") : "No Response")") } icon: { @@ -63,12 +63,12 @@ struct TraceRouteLog: View { } .font(.title2) } - + let hopsArray = selectedRoute?.hops?.array as? [TraceRouteHopEntity] ?? [] let lineCoords = hopsArray.compactMap({(hop) -> CLLocationCoordinate2D in return hop.coordinate ?? LocationHelper.DefaultLocation }) - if selectedRoute?.response ?? false { + if selectedRoute?.response ?? false { if selectedRoute?.hasPositions ?? false { Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { Annotation("You", coordinate: selectedRoute?.coordinate ?? LocationHelper.DefaultLocation) { @@ -82,8 +82,7 @@ struct TraceRouteLog: View { .annotationTitles(.automatic) // Direct Trace Route if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 == 0 { - if selectedRoute?.node?.positions?.count ?? 0 > 0 { - let mostRecent = selectedRoute?.node?.positions?.lastObject as! PositionEntity + if selectedRoute?.node?.positions?.count ?? 0 > 0, let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { var traceRouteCoords: [CLLocationCoordinate2D] = [selectedRoute?.coordinate ?? LocationsHandler.DefaultLocation, mostRecent.coordinate] Annotation(selectedRoute?.node?.user?.shortName ?? "???", coordinate: mostRecent.nodeCoordinate ?? LocationHelper.DefaultLocation) { ZStack { @@ -101,19 +100,21 @@ struct TraceRouteLog: View { .stroke(.blue, style: dashed) } } else if selectedRoute?.hops?.count ?? 0 == 0 { - + } } .frame(maxWidth: .infinity, maxHeight: .infinity) } VStack { /// Distance - if selectedRoute?.node?.positions?.count ?? 0 > 0 && selectedRoute?.coordinate != nil { - let mostRecent = selectedRoute?.node?.positions?.lastObject as! PositionEntity + if selectedRoute?.node?.positions?.count ?? 0 > 0, + selectedRoute?.coordinate != nil, + let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { + let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude) - + if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { - let metersAway = selectedRoute?.coordinate?.distance(from:CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude)) + let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude)) Label { Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway ?? 0)))") .foregroundColor(.primary) diff --git a/Meshtastic/Views/Settings/About.swift b/Meshtastic/Views/Settings/About.swift index 0ab25c67..e7135408 100644 --- a/Meshtastic/Views/Settings/About.swift +++ b/Meshtastic/Views/Settings/About.swift @@ -22,7 +22,7 @@ struct AboutMeshtastic: View { } Section(header: Text("Apple Apps")) { - + if locale.region?.identifier ?? "US" == "US" { HStack { Image("SOLAR_NODE") @@ -48,7 +48,7 @@ struct AboutMeshtastic: View { } } .font(.title2) - + Text("Version: \(Bundle.main.appVersionLong) (\(Bundle.main.appBuild)) ") } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index ba5d46f9..5fbdc50a 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -16,7 +16,7 @@ struct AppSettings: View { VStack { Form { Section(header: Text("App Settings")) { - Button("Open Settings", systemImage: "gear") { + Button("Open Settings", systemImage: "gear") { // Get the settings URL and open it if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(url) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 797ee714..b3792ec3 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -44,10 +44,10 @@ struct Channels: View { @State var positionsEnabled = true @State var supportedVersion = true @State var selectedChannel: ChannelEntity? - + /// Minimum Version for granular position configuration @State var minimumVersion = "2.2.24" - + @FetchRequest( sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false), NSSortDescriptor(key: "lastHeard", ascending: false), @@ -90,7 +90,7 @@ struct Channels: View { positionPrecision = 32 preciseLocation = true positionsEnabled = true - + } else if !supportedVersion && channelRole == 2 { positionPrecision = 0 preciseLocation = false @@ -135,7 +135,7 @@ struct Channels: View { } } } - .sheet(item: $selectedChannel) { selection in + .sheet(item: $selectedChannel) { _ in #if targetEnvironment(macCatalyst) Text("channel") .font(.largeTitle) @@ -159,7 +159,7 @@ struct Channels: View { channel.settings.uplinkEnabled = uplink channel.settings.downlinkEnabled = downlink channel.settings.moduleSettings.positionPrecision = UInt32(positionPrecision) - + selectedChannel!.role = Int32(channelRole) selectedChannel!.index = channelIndex selectedChannel!.name = channelName @@ -187,20 +187,21 @@ struct Channels: View { print("πŸ’₯ Unresolved Core Data error in the channel editor. Error: \(nsError)") } } else { - guard let channelEntity = node?.myInfo?.channels?.first(where: { ($0 as! ChannelEntity).index == channelIndex }) else { + guard let channelEntities = node?.myInfo?.channels as? [ChannelEntity], + let channelEntity = channelEntities.first(where: { $0.index == channelIndex }) else { return } - - let objects = (channelEntity as! ChannelEntity).allPrivateMessages + + let objects = channelEntity.allPrivateMessages for object in objects { context.delete(object) } for node in nodes { - if node.channel == (channelEntity as AnyObject).index { + if node.channel == channelEntity.index { context.delete(node) } } - context.delete(channelEntity as! ChannelEntity) + context.delete(channelEntity) do { try context.save() print("πŸ’Ύ Deleted Channel: \(channel.settings.name)") @@ -259,7 +260,7 @@ struct Channels: View { uplink = false downlink = false hasChanges = true - + let newChannel = ChannelEntity(context: context) newChannel.id = channelIndex newChannel.index = channelIndex @@ -297,10 +298,8 @@ func firstMissingChannelIndex(_ indexes: [Int]) -> Int { var smallestIndex = 1 if indexes.isEmpty { return smallestIndex } if smallestIndex <= indexes.count { - for element in smallestIndex...indexes.count { - if !indexes.contains(element) { - return element - } + for element in smallestIndex...indexes.count where !indexes.contains(element) { + return element } } return indexes.count + 1 @@ -333,7 +332,7 @@ enum PositionPrecision: Int, CaseIterable, Identifiable { case twentyfour = 24 var id: Int { self.rawValue } - + var precisionMeters: Double { switch self { case .two: @@ -384,7 +383,7 @@ enum PositionPrecision: Int, CaseIterable, Identifiable { return 1.413763999910884 } } - + var description: String { let distanceFormatter = MKDistanceFormatter() return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters))) diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 560d1d70..476695bb 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -5,7 +5,6 @@ // Copyright(c) Garth Vander Houwen 3/17/24. // - import SwiftUI #if canImport(MapKit) import MapKit @@ -26,9 +25,9 @@ struct ChannelForm: View { @Binding var hasChanges: Bool @Binding var hasValidKey: Bool @Binding var supportedVersion: Bool - + var body: some View { - + NavigationStack { Form { Section(header: Text("channel details")) { @@ -98,15 +97,14 @@ struct ChannelForm: View { Color.clear : Color.red , lineWidth: 2.0) - + ) .onChange(of: channelKey, perform: { _ in - + let tempKey = Data(base64Encoded: channelKey) ?? Data() - if tempKey.count == channelKeySize || channelKeySize == -1{ + if tempKey.count == channelKeySize || channelKeySize == -1 { hasValidKey = true - } - else { + } else { hasValidKey = false } hasChanges = true @@ -131,9 +129,9 @@ struct ChannelForm: View { } } } - + Section(header: Text("position")) { - + VStack(alignment: .leading) { Toggle(isOn: $positionsEnabled) { Label(channelRole == 1 ? "Positions Enabled" : "Allow Position Requests", systemImage: positionsEnabled ? "mappin" : "mappin.slash") @@ -141,7 +139,7 @@ struct ChannelForm: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .disabled(!supportedVersion) } - + if positionsEnabled { VStack(alignment: .leading) { Toggle(isOn: $preciseLocation) { @@ -156,7 +154,7 @@ struct ChannelForm: View { } } } - + if !preciseLocation { VStack(alignment: .leading) { Label("Approximate Location", systemImage: "location.slash.circle.fill") @@ -236,8 +234,7 @@ struct ChannelForm: View { let tempKey = Data(base64Encoded: channelKey) ?? Data() if tempKey.count == channelKeySize || channelKeySize == -1 { hasValidKey = true - } - else { + } else { hasValidKey = false } } diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index c4256f58..6fe96327 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -46,7 +46,7 @@ struct DeviceConfig: View { .font(.callout) } .pickerStyle(DefaultPickerStyle()) - + VStack(alignment: .leading) { Picker("Rebroadcast Mode", selection: $rebroadcastMode ) { ForEach(RebroadcastModes.allCases) { rm in @@ -58,13 +58,13 @@ struct DeviceConfig: View { .font(.callout) } .pickerStyle(DefaultPickerStyle()) - + Toggle(isOn: $isManaged) { Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath") Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Picker("Node Info Broadcast Interval", selection: $nodeInfoBroadcastSecs ) { ForEach(UpdateIntervals.allCases) { ui in if ui.rawValue >= 3600 { @@ -75,13 +75,13 @@ struct DeviceConfig: View { .pickerStyle(DefaultPickerStyle()) } Section(header: Text("Hardware")) { - + Toggle(isOn: $doubleTapAsButtonPress) { Label("Double Tap as Button", systemImage: "hand.tap") Text("Treat double tap on supported accelerometers as a user button press.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $ledHeartbeatEnabled) { Label("LED Heartbeat", systemImage: "waveform.path.ecg") Text("Controls the blinking LED on the device. For most devices this will control one of the up to 4 LEDS, the charger and GPS LEDs are not controllable.") @@ -110,7 +110,7 @@ struct DeviceConfig: View { } }) .foregroundColor(.gray) - + } .keyboardType(.default) .disableAutocorrection(true) @@ -165,7 +165,7 @@ struct DeviceConfig: View { bleManager.disconnectPeripheral() clearCoreDataDatabase(context: context, includeRoutes: false) } - + } else { print("NodeDB Reset Failed") } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 829d644f..2b3531bc 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -37,7 +37,7 @@ struct DisplayConfig: View { Text(dm.description) } } - + Text("Override automatic OLED screen detection.") .foregroundColor(.gray) .font(.callout) @@ -54,13 +54,13 @@ struct DisplayConfig: View { Text("Requires that there be an accelerometer on your device.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $flipScreen) { Label("Flip Screen", systemImage: "pip.swap") Text("Flip screen vertically") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + VStack(alignment: .leading) { Picker("OLED Type", selection: $oledType ) { ForEach(OledTypes.allCases) { ot in @@ -85,20 +85,20 @@ struct DisplayConfig: View { .font(.callout) } .pickerStyle(DefaultPickerStyle()) - + VStack(alignment: .leading) { Picker("Carousel Interval", selection: $screenCarouselInterval ) { ForEach(ScreenCarouselIntervals.allCases) { sci in Text(sci.description) } } - + Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.") .foregroundColor(.gray) .font(.callout) } .pickerStyle(DefaultPickerStyle()) - + VStack(alignment: .leading) { Picker("GPS Format", selection: $gpsFormat ) { ForEach(GpsFormats.allCases) { lu in @@ -110,7 +110,7 @@ struct DisplayConfig: View { .font(.callout) } .pickerStyle(DefaultPickerStyle()) - + VStack(alignment: .leading) { Picker("Display Units", selection: $units ) { ForEach(Units.allCases) { un in diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 3bfe9a41..f17cca5d 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -69,7 +69,7 @@ struct LoRaConfig: View { .font(.callout) } .pickerStyle(DefaultPickerStyle()) - + Toggle(isOn: $usePreset) { Label("Use Preset", systemImage: "list.bullet.rectangle") } @@ -91,7 +91,7 @@ struct LoRaConfig: View { } } Section(header: Text("Advanced")) { - + Toggle(isOn: $ignoreMqtt) { Label("Ignore MQTT", systemImage: "server.rack") } @@ -140,7 +140,7 @@ struct LoRaConfig: View { .font(.callout) } .pickerStyle(DefaultPickerStyle()) - + VStack(alignment: .leading) { HStack { Text("Frequency Slot") @@ -163,12 +163,12 @@ struct LoRaConfig: View { .foregroundColor(.gray) .font(.callout) } - + Toggle(isOn: $rxBoostedGain) { Label("RX Boosted Gain", systemImage: "waveform.badge.plus") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + HStack { Label("Frequency Override", systemImage: "waveform.path.ecg") Spacer() @@ -177,7 +177,7 @@ struct LoRaConfig: View { .scrollDismissesKeyboard(.immediately) .focused($focusedField, equals: .frequencyOverride) } - + HStack { Image(systemName: "antenna.radiowaves.left.and.right") .foregroundColor(.accentColor) diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index 9bb450ca..a6ad64c1 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -28,15 +28,15 @@ struct AmbientLightingConfig: View { VStack { Form { ConfigHeader(title: "Ambient Lighting", config: \.ambientLightingConfig, node: node, onAppear: setAmbientLightingConfigValue) - + Section(header: Text("options")) { - + Toggle(isOn: $ledState) { Label("LED State", systemImage: ledState ? "lightbulb.led.fill" : "lightbulb.led") Text("The state of the LED (on/off)") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + HStack { Image(systemName: "eyedropper") .foregroundColor(.accentColor) diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index dd5df775..ead273fe 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -39,21 +39,21 @@ struct CannedMessagesConfig: View { VStack { Form { ConfigHeader(title: "Canned messages", config: \.cannedMessageConfig, node: node, onAppear: setCannedMessagesValues) - + Section(header: Text("options")) { - + Toggle(isOn: $enabled) { Label("enabled", systemImage: "list.bullet.rectangle.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $sendBell) { Label("Send Bell", systemImage: "bell") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Picker("Configuration Presets", selection: $configPreset ) { ForEach(ConfigPresets.allCases) { cp in Text(cp.description) diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index e78a9967..5eda232d 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -44,9 +44,9 @@ struct DetectionSensorConfig: View { VStack { Form { ConfigHeader(title: "Detection Sensor", config: \.detectionSensorConfig, node: node, onAppear: setDetectionSensorValues) - + Section(header: Text("options")) { - + Toggle(isOn: $enabled) { Label("enabled", systemImage: "dot.radiowaves.right") Text("Enables the detection sensor module, it needs to be enabled on both the node with the sensor, and any nodes that you want to receive detection sensor text messages or view the detection sensor log and chart.") @@ -90,7 +90,7 @@ struct DetectionSensorConfig: View { .autocapitalization(.none) .disableAutocorrection(true) .onChange(of: name, perform: { _ in - + let totalBytes = name.utf8.count // Only mess with the value if it is too big if totalBytes > 20 { @@ -102,7 +102,7 @@ struct DetectionSensorConfig: View { Text("Friendly name used to format message sent to mesh. Example: A name \"Motion\" would result in a message \"Motion detected\"") .font(.callout) .foregroundStyle(.gray) - + Picker("GPIO Pin to monitor", selection: $monitorPin) { ForEach(0..<49) { if $0 == 0 { @@ -113,13 +113,13 @@ struct DetectionSensorConfig: View { } } .pickerStyle(DefaultPickerStyle()) - + Toggle(isOn: $detectionTriggeredHigh) { Label("Detection trigger High", systemImage: "dial.high") Text("Whether or not the GPIO pin state detection is triggered on HIGH (1) or LOW (0)") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $usePullup) { Label("Uses pullup resistor", systemImage: "arrow.up.to.line") Text(" Whether or not use INPUT_PULLUP mode for GPIO pin. Only applicable if the board uses pull-up resistors on the pin") diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 02af0792..cf1cac54 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -38,28 +38,28 @@ struct ExternalNotificationConfig: View { ConfigHeader(title: "External notification", config: \.externalNotificationConfig, node: node, onAppear: setExternalNotificationValues) Section(header: Text("options")) { - + Toggle(isOn: $enabled) { Label("enabled", systemImage: "megaphone") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $alertBell) { Label("Alert when receiving a bell", systemImage: "bell") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $alertMessage) { Label("Alert when receiving a message", systemImage: "message") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $usePWM) { Label("Use PWM Buzzer", systemImage: "light.beacon.max.fill") Text("Use a PWM output (like the RAK Buzzer) for tunes instead of an on/off output. This will ignore the output, output duration and active settings and use the device config buzzer GPIO option instead.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $useI2SAsBuzzer) { Label("Use I2S As Buzzer", systemImage: "light.beacon.max.fill") Text("Enables devices with native I2S audio output to use the RTTTL over speaker like a buzzer. T-Watch S3 and T-Deck for example have this capability.") @@ -76,7 +76,7 @@ struct ExternalNotificationConfig: View { Text("If enabled, the 'output' Pin will be pulled active high, disabled means active low.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Picker("Output pin GPIO", selection: $output) { ForEach(0..<49) { if $0 == 0 { @@ -88,7 +88,7 @@ struct ExternalNotificationConfig: View { } .pickerStyle(DefaultPickerStyle()) .listRowSeparator(.visible) - + Picker("GPIO Output Duration", selection: $outputMilliseconds ) { ForEach(OutputIntervals.allCases) { oi in Text(oi.description) @@ -100,7 +100,7 @@ struct ExternalNotificationConfig: View { .foregroundColor(.gray) .font(.callout) .listRowSeparator(.visible) - + Picker("Nag timeout", selection: $nagTimeout ) { ForEach(OutputIntervals.allCases) { oi in Text(oi.description) @@ -112,7 +112,7 @@ struct ExternalNotificationConfig: View { .foregroundColor(.gray) .font(.callout) } - + Section(header: Text("Optional GPIO") .font(.caption) .foregroundColor(.gray) diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 58beac1c..6244debc 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -32,8 +32,7 @@ struct MQTTConfig: View { @State var mapPublishIntervalSecs = 3600 @State var preciseLocation: Bool = false @State var mapPositionPrecision: Double = 13.0 - - + let locale = Locale.current var body: some View { @@ -41,7 +40,7 @@ struct MQTTConfig: View { Form { if node != nil && node?.loRaConfig != nil { let rc = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0)) - if rc?.dutyCycle ?? 0 > 0 && rc?.dutyCycle ?? 0 < 100 { + if rc?.dutyCycle ?? 0 > 0 && rc?.dutyCycle ?? 0 < 100 { Text("Your region has a \(rc?.dutyCycle ?? 0)% duty cycle. MQTT is not advised when you are duty cycle restricted, the extra traffic will quickly overwhelm your LoRa mesh.") .font(.callout) .foregroundColor(.red) @@ -51,19 +50,19 @@ struct MQTTConfig: View { ConfigHeader(title: "MQTT", config: \.mqttConfig, node: node, onAppear: setMqttValues) Section(header: Text("options")) { - + Toggle(isOn: $enabled) { Label("enabled", systemImage: "dot.radiowaves.up.forward") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $proxyToClientEnabled) { - + Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right") Text("Utilizes the network connection on your phone to connect to MQTT.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + if enabled && proxyToClientEnabled && node!.mqttConfig!.proxyToClientEnabled == true { Toggle(isOn: $mqttConnected) { Label(mqttConnected ? "mqtt.disconnect".localized : "mqtt.connect".localized, systemImage: "server.rack") @@ -72,25 +71,25 @@ struct MQTTConfig: View { .fixedSize(horizontal: false, vertical: true) .foregroundColor(.red) } - + } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } - + Toggle(isOn: $encryptionEnabled) { Label("Encryption Enabled", systemImage: "lock.icloud") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + Toggle(isOn: $jsonEnabled) { Label("JSON Enabled", systemImage: "ellipsis.curlybraces") Text("JSON mode is a limited, unencrypted MQTT output for locally integrating with home assistant") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } - + Section(header: Text("Map Report")) { - + Toggle(isOn: $mapReportingEnabled) { Label("enabled", systemImage: "map") } @@ -104,7 +103,7 @@ struct MQTTConfig: View { } } .pickerStyle(DefaultPickerStyle()) - + VStack(alignment: .leading) { Toggle(isOn: $preciseLocation) { Label("Precise Location", systemImage: "scope") @@ -119,7 +118,7 @@ struct MQTTConfig: View { } } } - + if !preciseLocation { VStack(alignment: .leading) { Label("Approximate Location", systemImage: "location.slash.circle.fill") @@ -157,7 +156,7 @@ struct MQTTConfig: View { Text("The root topic to use for MQTT.") .foregroundColor(.gray) .font(.callout) - + if nearbyTopics.count > 0 { Picker("Nearby Topics", selection: $selectedTopic ) { ForEach(nearbyTopics, id: \.self) { nt in @@ -171,7 +170,7 @@ struct MQTTConfig: View { .font(.callout) } } - + Section(header: Text("Server")) { HStack { Label("Address", systemImage: "server.rack") @@ -190,7 +189,7 @@ struct MQTTConfig: View { .keyboardType(.default) } .autocorrectionDisabled() - + HStack { Label("mqtt.username", systemImage: "person.text.rectangle") TextField("mqtt.username", text: $username) @@ -198,9 +197,9 @@ struct MQTTConfig: View { .autocapitalization(.none) .disableAutocorrection(true) .onChange(of: username, perform: { _ in - + let totalBytes = username.utf8.count - + // Only mess with the value if it is too big if totalBytes > 62 { username = String(username.dropLast()) @@ -218,7 +217,7 @@ struct MQTTConfig: View { .autocapitalization(.none) .disableAutocorrection(true) .onChange(of: password, perform: { _ in - + let totalBytes = password.utf8.count // Only mess with the value if it is too big if totalBytes > 62 { @@ -371,20 +370,20 @@ struct MQTTConfig: View { } } func setMqttValues() { - + if #available(iOS 17.0, macOS 14.0, *) { - + nearbyTopics = [] let geocoder = CLGeocoder() if LocationsHandler.shared.locationsArray.count > 0 { let region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))?.topic defaultTopic = "msh/" + (region ?? "UNSET") - geocoder.reverseGeocodeLocation(LocationsHandler.shared.locationsArray.first!, completionHandler: {(placemarks, error) -> Void in + geocoder.reverseGeocodeLocation(LocationsHandler.shared.locationsArray.first!, completionHandler: {(placemarks, error) in if error != nil { print("Failed to reverse geocode location") return } - + if let placemarks = placemarks, let placemark = placemarks.first { let cc = locale.region?.identifier ?? "UNK" /// Country Topic unless you are US @@ -412,9 +411,7 @@ struct MQTTConfig: View { if !neightborhoodTopic.isEmpty { nearbyTopics.append(neightborhoodTopic) } - } - else - { + } else { print("No Location") } }) diff --git a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift index 30599af2..ba32cff9 100644 --- a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift @@ -60,7 +60,7 @@ struct PaxCounterConfig: View { if self.bleManager.context == nil { self.bleManager.context = context } - + setPaxValues() // Need to request a PAX Counter module config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.paxCounterConfig == nil { @@ -80,7 +80,7 @@ struct PaxCounterConfig: View { hasChanges = $0 != val } } - + SaveConfigButton(node: node, hasChanges: $hasChanges) { guard let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context), let fromUser = connectedNode.user, @@ -91,7 +91,7 @@ struct PaxCounterConfig: View { var config = ModuleConfig.PaxcounterConfig() config.enabled = enabled config.paxcounterUpdateInterval = UInt32(paxcounterUpdateInterval) - + let adminMessageId = bleManager.savePaxcounterModuleConfig( config: config, fromUser: fromUser, diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index b56ef643..425b41e4 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -24,7 +24,7 @@ struct RangeTestConfig: View { VStack { Form { ConfigHeader(title: "Range", config: \.rangeTestConfig, node: node, onAppear: setRangeTestValues) - + Section(header: Text("options")) { Toggle(isOn: $enabled) { Label("enabled", systemImage: "figure.walk") @@ -41,14 +41,14 @@ struct RangeTestConfig: View { Text("This device will send out range test messages on the selected interval.") .foregroundColor(.gray) .font(.callout) - + Toggle(isOn: $save) { Label("save", systemImage: "square.and.arrow.down.fill") Text("Saves a CSV with the range test message details, currently only available on ESP32 devices with a web server.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .disabled(!(node != nil && node?.metadata?.hasWifi ?? false)) - + } } .disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil) diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index dfd951df..6df3e504 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -22,7 +22,7 @@ struct RtttlConfig: View { VStack { Form { ConfigHeader(title: "ringtone", config: \.rtttlConfig, node: node, onAppear: setRtttLConfigValue) - + Section(header: Text("options")) { HStack { Label("ringtone", systemImage: "music.quarternote.3") diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index f4e88f45..19b58c6f 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -25,12 +25,12 @@ struct SerialConfig: View { @State var timeout = 0 @State var overrideConsoleSerialPort = false @State var mode = 0 - + var body: some View { VStack { Form { ConfigHeader(title: "Serial", config: \.serialConfig, node: node, onAppear: setSerialValues) - + Section(header: Text("options")) { Toggle(isOn: $enabled) { diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index e9b1c985..15cbabed 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -32,9 +32,9 @@ struct StoreForwardConfig: View { VStack { Form { ConfigHeader(title: "storeforward", config: \.storeForwardConfig, node: node, onAppear: setStoreAndForwardValues) - + Section(header: Text("options")) { - + Toggle(isOn: $enabled) { Label("enabled", systemImage: "envelope.arrow.triangle.branch") Text("Enables the store and forward module. Store and forward must be enabled on both client and router devices.") @@ -66,7 +66,7 @@ struct StoreForwardConfig: View { } } } - + if isRouter { Section(header: Text("Router Options")) { Toggle(isOn: $heartbeat) { @@ -119,7 +119,7 @@ struct StoreForwardConfig: View { print("Failed to save isRouter") } } - + var sfc = ModuleConfig.StoreForwardConfig() sfc.enabled = self.enabled sfc.heartbeat = self.heartbeat @@ -144,7 +144,7 @@ struct StoreForwardConfig: View { if self.bleManager.context == nil { self.bleManager.context = context } - + // Need to request a Detection Sensor Module Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.storeForwardConfig == nil { print("empty store and forward module config") diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 7483118b..f645fc51 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -24,13 +24,12 @@ struct TelemetryConfig: View { @State var powerMeasurementEnabled = false @State var powerUpdateInterval = 0 @State var powerScreenEnabled = false - var body: some View { VStack { Form { ConfigHeader(title: "Telemetry", config: \.telemetryConfig, node: node, onAppear: setTelemetryValues) - + Section(header: Text("update.interval")) { Picker("Device Metrics", selection: $deviceUpdateInterval ) { ForEach(UpdateIntervals.allCases) { ui in @@ -176,7 +175,7 @@ struct TelemetryConfig: View { if node != nil && node?.telemetryConfig != nil { if newPowerUpdateInterval != node!.telemetryConfig!.powerUpdateInterval { hasChanges = true } } - } + } .onChange(of: powerScreenEnabled) { newPowerScreenEnabled in if node != nil && node?.telemetryConfig != nil { if newPowerScreenEnabled != node!.telemetryConfig!.powerScreenEnabled { hasChanges = true } diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index ca134b7b..2527138b 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -28,16 +28,16 @@ struct NetworkConfig: View { VStack { Form { ConfigHeader(title: "Network", config: \.networkConfig, node: node, onAppear: setNetworkValues) - - if (node != nil && node?.metadata?.hasWifi ?? false) { + + if node != nil && node?.metadata?.hasWifi ?? false { Section(header: Text("WiFi Options")) { - + Toggle(isOn: $wifiEnabled) { Label("enabled", systemImage: "wifi") Text("Enabling WiFi will disable the bluetooth connection to the app.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + HStack { Label("ssid", systemImage: "network") TextField("ssid", text: $wifiSsid) @@ -74,7 +74,7 @@ struct NetworkConfig: View { .keyboardType(.default) } } - if (node != nil && node?.metadata?.hasEthernet ?? false) { + if node != nil && node?.metadata?.hasEthernet ?? false { Section(header: Text("Ethernet Options")) { Toggle(isOn: $ethEnabled) { Label("enabled", systemImage: "network") diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 575de199..e92088ff 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -76,8 +76,8 @@ struct PositionConfig: View { @State var minimumVersion = "2.3.3" @State private var supportedVersion = true @State private var showingSetFixedAlert = false - //@State private var showingRemoveFixedAlert = false - + // @State private var showingRemoveFixedAlert = false + var body: some View { VStack { Form { @@ -98,12 +98,12 @@ struct PositionConfig: View { .foregroundColor(.gray) .font(.callout) } - + Toggle(isOn: $smartPositionEnabled) { Label("Smart Position", systemImage: "brain") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + if smartPositionEnabled { VStack(alignment: .leading) { Picker("Minimum Interval", selection: $broadcastSmartMinimumIntervalSecs) { @@ -147,8 +147,7 @@ struct PositionConfig: View { .padding(.top, 5) .padding(.bottom, 5) if gpsMode == 1 { - - + Text("Positions will be provided by your device GPS, if you select disabled or not present you can set a fixed position.") .foregroundColor(.gray) .font(.callout) @@ -170,7 +169,7 @@ struct PositionConfig: View { if !(node?.positionConfig?.fixedPosition ?? false) { Text("Your current location will be set as the fixed position and broadcast over the mesh on the position interval.") } else { - + } } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -215,7 +214,7 @@ struct PositionConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } Section(header: Text("Advanced Position Flags")) { - + if includeAltitude { Toggle(isOn: $includeAltitudeMsl) { Label("Altitude is Mean Sea Level", systemImage: "arrow.up.to.line.compact") @@ -239,7 +238,7 @@ struct PositionConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } } - + if gpsMode == 1 { Section(header: Text("Advanced Device GPS")) { Picker("GPS Receive GPIO", selection: $rxGpio) { diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift index 11b8a71e..155d9a5f 100644 --- a/Meshtastic/Views/Settings/Config/PowerConfig.swift +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -17,9 +17,9 @@ struct PowerConfig: View { @State private var waitBluetoothSecs = 60 @State private var lsSecs = 300 @State private var minWakeSecs = 10 - + @State private var currentDevice: DeviceHardware? - + @State private var hasChanges: Bool = false @FocusState private var isFocused: Bool @@ -28,7 +28,7 @@ struct PowerConfig: View { ConfigHeader(title: "config.power.title", config: \.powerConfig, node: node, onAppear: setPowerValues) Section { - if (currentDevice?.architecture == .esp32 || currentDevice?.architecture == .esp32S3) || (currentDevice?.architecture == .nrf52840 && (node?.deviceConfig?.role ?? 0 == 5 || node?.deviceConfig?.role ?? 0 == 6)) { + if (currentDevice?.architecture == .esp32 || currentDevice?.architecture == .esp32S3) || (currentDevice?.architecture == .nrf52840 && (node?.deviceConfig?.role ?? 0 == 5 || node?.deviceConfig?.role ?? 0 == 6)) { Toggle(isOn: $isPowerSaving) { Label("config.power.saving", systemImage: "bolt") Text("config.power.saving.description") @@ -120,13 +120,13 @@ struct PowerConfig: View { if self.bleManager.context == nil { self.bleManager.context = context } - + Api().loadDeviceHardwareData { (hw) in - + for device in hw { let currentHardware = node?.user?.hwModel ?? "UNSET" let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "") - if deviceString == currentHardware { + if deviceString == currentHardware { currentDevice = device } } diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index f27b1967..2b77a9b1 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -17,14 +17,14 @@ struct Firmware: View { @State private var currentDevice: DeviceHardware? @State private var latestStable: FirmwareRelease? @State private var latestAlpha: FirmwareRelease? - + var body: some View { - + let supportedVersion = self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame ScrollView { VStack(alignment: .leading) { let deviceString = currentDevice?.hwModelSlug.replacingOccurrences(of: "_", with: "") - + HStack { VStack { Image(systemName: currentDevice?.activelySupported ?? false ? "checkmark.seal.fill" : "x.circle") @@ -45,7 +45,7 @@ struct Firmware: View { .frame(width: 300, height: 300) .cornerRadius(5) } - + if supportedVersion { Text("Your Firmware is up to date") .fixedSize(horizontal: false, vertical: true) @@ -72,7 +72,7 @@ struct Firmware: View { .fixedSize(horizontal: false, vertical: true) .font(.title2) .padding(.bottom) - + Text("Get the latest stable firmware") .fixedSize(horizontal: false, vertical: true) .font(.callout) @@ -81,7 +81,7 @@ struct Firmware: View { Link("Release Notes", destination: URL(string: "\(latestStable?.pageURL ?? "https://meshtastic.org")")!) .font(.caption) .padding(.bottom) - + Text("Get the latest alpha firmware") .fixedSize(horizontal: false, vertical: true) .font(.callout) @@ -90,10 +90,10 @@ struct Firmware: View { Link("Release Notes", destination: URL(string: "\(latestAlpha?.pageURL ?? "https://meshtastic.org")")!) .font(.caption) .padding(.bottom) - + if currentDevice?.architecture == Meshtastic.Architecture.nrf52840 { VStack(alignment: .leading) { - + Text("Drag & Drop is the recommended way to update firmware for NRF devices. If your iPhone or iPad is USB-C it will work with your regular USB-C charging cable, for lightning devices you need the Apple Lightning to USB camera adaptor.") .fixedSize(horizontal: false, vertical: true) .foregroundStyle(.gray) @@ -109,7 +109,7 @@ struct Firmware: View { Button { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) if connectedNode != nil { - + if bleManager.sendEnterDfuMode(fromUser: connectedNode!.user!, toUser: node!.user!) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { bleManager.disconnectPeripheral(reconnect: false) @@ -178,18 +178,18 @@ struct Firmware: View { .font(.title3) Text(node?.user?.hwModel ?? "UNSET") .font(.title3) - Text ( currentDevice?.architecture.rawValue ?? "UNKNOWN") + Text( currentDevice?.architecture.rawValue ?? "UNKNOWN") .font(.title3) } } .padding() .padding(.bottom, 5) - .onAppear() { + .onAppear { Api().loadDeviceHardwareData { (hw) in for device in hw { let currentHardware = node?.user?.hwModel ?? "UNSET" let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "") - if deviceString == currentHardware { + if deviceString == currentHardware { currentDevice = device } } diff --git a/Meshtastic/Views/Settings/FirmwareApi.swift b/Meshtastic/Views/Settings/FirmwareApi.swift index c894006e..e9670f2c 100644 --- a/Meshtastic/Views/Settings/FirmwareApi.swift +++ b/Meshtastic/Views/Settings/FirmwareApi.swift @@ -43,17 +43,17 @@ struct FirmwareRelease: Codable { } } -class Api : ObservableObject{ +class Api: ObservableObject { - func loadDeviceHardwareData(completion:@escaping ([DeviceHardware]) -> ()) { + func loadDeviceHardwareData(completion: @escaping ([DeviceHardware]) -> Void) { /// List from https://api.meshtastic.org/resource/deviceHardware guard let url = Bundle.main.url(forResource: "DeviceHardware.json", withExtension: nil) else { print("Couldn't find DeviceHardware.json in main bundle.") return } - - URLSession.shared.dataTask(with: url) { data, response, error in + + URLSession.shared.dataTask(with: url) { data, _, _ in if let data = data { do { let deviceHardware = try JSONDecoder().decode([DeviceHardware].self, from: data) @@ -67,13 +67,13 @@ class Api : ObservableObject{ } }.resume() } - - func loadFirmwareReleaseData(completion:@escaping (FirmwareReleases) -> ()) { + + func loadFirmwareReleaseData(completion: @escaping (FirmwareReleases) -> Void) { guard let url = URL(string: "https://api.meshtastic.org/github/firmware/list") else { print("Invalid url...") return } - URLSession.shared.dataTask(with: url) { data, response, error in + URLSession.shared.dataTask(with: url) { data, _, _ in if let data = data { do { let firmwareReleases = try JSONDecoder().decode(FirmwareReleases.self, from: data) diff --git a/Meshtastic/Views/Settings/GPSStatus.swift b/Meshtastic/Views/Settings/GPSStatus.swift index b9e18679..b1119694 100644 --- a/Meshtastic/Views/Settings/GPSStatus.swift +++ b/Meshtastic/Views/Settings/GPSStatus.swift @@ -10,20 +10,20 @@ import CoreLocation @available(iOS 17.0, macOS 14.0, *) struct GPSStatus: View { - + var largeFont: Font = .footnote var smallFont: Font = .caption2 - + @ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared var body: some View { - + if let newLocation = locationsHandler.locationsArray.last { let horizontalAccuracy = Measurement(value: newLocation.horizontalAccuracy, unit: UnitLength.meters) let verticalAccuracy = Measurement(value: newLocation.verticalAccuracy, unit: UnitLength.meters) let altitiude = Measurement(value: newLocation.altitude, unit: UnitLength.meters) let speed = Measurement(value: newLocation.speed, unit: UnitSpeed.kilometersPerHour) let speedAccuracy = Measurement(value: newLocation.speedAccuracy, unit: UnitSpeed.metersPerSecond) - let courseAccuracy = Measurement(value: newLocation.courseAccuracy, unit: UnitAngle.degrees) + let courseAccuracy = Measurement(value: newLocation.courseAccuracy, unit: UnitAngle.degrees) Label("Coordinate \(String(format: "%.5f", newLocation.coordinate.latitude)), \(String(format: "%.5f", newLocation.coordinate.longitude))", systemImage: "mappin") .font(largeFont) @@ -33,7 +33,7 @@ struct GPSStatus: View { .font(largeFont) Label("Sats Estimate \(LocationsHandler.satsInView)", systemImage: "sparkles") .font(largeFont) - + } HStack { if newLocation.verticalAccuracy > 0 { diff --git a/Meshtastic/Views/Settings/RouteRecorder.swift b/Meshtastic/Views/Settings/RouteRecorder.swift index 766b72d5..d05cc2e3 100644 --- a/Meshtastic/Views/Settings/RouteRecorder.swift +++ b/Meshtastic/Views/Settings/RouteRecorder.swift @@ -13,7 +13,7 @@ import CoreMotion @available(iOS 17.0, macOS 14.0, *) struct RouteRecorder: View { - + @ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared @Environment(\.managedObjectContext) var context @State private var position: MapCameraPosition = .userLocation(followsHeading: true, fallback: .automatic) @@ -24,7 +24,7 @@ struct RouteRecorder: View { @State var recording: RouteEntity? @State var color: Color = .blue @State var activity: Int = 1 - + var body: some View { VStack { ZStack { @@ -34,7 +34,7 @@ struct RouteRecorder: View { let lineCoords = locationsHandler.locationsArray.compactMap({(position) -> CLLocationCoordinate2D in return position.coordinate }) - + let gradient = LinearGradient( colors: [color], startPoint: .leading, endPoint: .trailing @@ -79,7 +79,7 @@ struct RouteRecorder: View { NavigationStack { VStack { if locationsHandler.isRecording { - HStack (alignment: .center) { + HStack(alignment: .center) { Image(systemName: "record.circle.fill") .symbolRenderingMode(.multicolor) .font(.title) @@ -93,8 +93,8 @@ struct RouteRecorder: View { } .padding() } else if locationsHandler.isRecordingPaused { - HStack (alignment: .center) { - + HStack(alignment: .center) { + Image(systemName: "playpause") .symbolRenderingMode(.multicolor) .font(.title3) @@ -104,7 +104,7 @@ struct RouteRecorder: View { } .padding(.top) } - + if locationsHandler.isRecording || locationsHandler.isRecordingPaused { Divider() HStack { @@ -197,7 +197,7 @@ struct RouteRecorder: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding(.bottom) - + } else if locationsHandler.isRecording { /// We are recording show pause button Button { @@ -223,16 +223,16 @@ struct RouteRecorder: View { .controlSize(.large) .padding(.bottom) } - + if locationsHandler.isRecording || locationsHandler.isRecordingPaused { /// We are recording or paused, show finish button Button { - + if let rec = recording { rec.enabled = true rec.distance = locationsHandler.distanceTraveled rec.elevationGain = locationsHandler.elevationGain - context.refresh(rec, mergeChanges:true) + context.refresh(rec, mergeChanges: true) } locationsHandler.isRecording = false locationsHandler.isRecordingPaused = false @@ -270,7 +270,7 @@ struct RouteRecorder: View { #endif Spacer() } - + } } } @@ -298,7 +298,7 @@ struct RouteRecorder: View { do { try context.save() print("πŸ’Ύ Saved a new route location") - //print("πŸ’Ύ Updated Canned Messages Messages For: \(fetchedNode[0].num)") + // print("πŸ’Ύ Updated Canned Messages Messages For: \(fetchedNode[0].num)") } catch { context.rollback() let nsError = error as NSError diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 6f47c9ea..dc112ae1 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -11,7 +11,7 @@ import MapKit @available(iOS 17.0, macOS 14.0, *) struct Routes: View { - + @State private var columnVisibility = NavigationSplitViewVisibility.doubleColumn @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -20,18 +20,18 @@ struct Routes: View { @State private var isShowingBadFileAlert = false @State var isExporting = false @State var exportString = "" - + @State var hasChanges = false @State var name = "" @State var notes = "" @State var enabled = true @State var color = Color(red: 51, green: 199, blue: 88) - + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "enabled", ascending: false), NSSortDescriptor(key: "name", ascending: true), NSSortDescriptor(key: "date", ascending: false)], animation: .default) - + var routes: FetchedResults var body: some View { - + VStack { if selectedRoute == nil { Button("Import Route") { @@ -41,7 +41,7 @@ struct Routes: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding() - + .alert(isPresented: $isShowingBadFileAlert) { Alert(title: Text("Not a valid route file"), message: Text("Your route file must have both Latitude and Longitude columns and headers."), dismissButton: .default(Text("OK"))) } @@ -55,7 +55,7 @@ struct Routes: View { guard selectedFile.startAccessingSecurityScopedResource() else { return } - + do { guard let fileContent = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return } let routeName = selectedFile.lastPathComponent.dropLast(4) @@ -101,11 +101,11 @@ struct Routes: View { } else { isShowingBadFileAlert = true } - + } catch { print("error: \(error)") // to do deal with errors } - + } catch { print("CSV Import Error") } @@ -113,16 +113,16 @@ struct Routes: View { List(routes, id: \.self, selection: $selectedRoute) { route in let routeColor = Color(UIColor(hex: route.color >= 0 ? UInt32(route.color) : 0)) Label { - VStack (alignment: .leading) { + VStack(alignment: .leading) { Text("\(route.name ?? "No Name Route")") .padding(.top) .foregroundStyle(.primary) - + Text("\(route.date?.formatted() ?? "Unknown Time")") .padding(.bottom) .font(.callout) .foregroundColor(.gray) - + if route.notes?.count ?? 0 > 0 { Text("\(route.notes ?? "")") .padding(.bottom) @@ -157,7 +157,7 @@ struct Routes: View { Label("delete", systemImage: "trash") } } - + } .listStyle(.plain) } else { @@ -177,20 +177,20 @@ struct Routes: View { .onChange(of: name, perform: { _ in let totalBytes = name.utf8.count // Only mess with the value if it is too big - + if totalBytes > 100 { name = String(name.dropLast()) } }) - + Toggle(isOn: $enabled) { Label("enabled", systemImage: "point.topleft.filled.down.to.point.bottomright.curvepath") Text("Show on the mesh map.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + ColorPicker("Color", selection: $color, supportsOpacity: false) - + TextField( "Notes", text: $notes, @@ -207,14 +207,14 @@ struct Routes: View { hasChanges = false } HStack { - + Button("cancel", role: .cancel) { selectedRoute = nil } .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) - + Button("save") { selectedRoute?.name = name selectedRoute?.notes = notes @@ -235,19 +235,19 @@ struct Routes: View { .controlSize(.large) .disabled(!hasChanges) } - .onChange(of: name) { newName in + .onChange(of: name) { _ in hasChanges = true } - .onChange(of: notes) { newNotes in + .onChange(of: notes) { _ in hasChanges = true } - .onChange(of: enabled) { newEnabled in + .onChange(of: enabled) { _ in hasChanges = true } - .onChange(of: color) { newColor in + .onChange(of: color) { _ in hasChanges = true } - Map() { + Map { Annotation("Start", coordinate: lineCoords.first ?? LocationHelper.DefaultLocation) { ZStack { Circle() diff --git a/Meshtastic/Views/Settings/SaveChannelQRCode.swift b/Meshtastic/Views/Settings/SaveChannelQRCode.swift index 7de80622..c00a3c59 100644 --- a/Meshtastic/Views/Settings/SaveChannelQRCode.swift +++ b/Meshtastic/Views/Settings/SaveChannelQRCode.swift @@ -63,7 +63,6 @@ struct SaveChannelQRCode: View { .padding() } - #if targetEnvironment(macCatalyst) Button { dismiss() diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index ec2c747f..187a4797 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -94,11 +94,10 @@ struct Settings: View { } .tag(SettingsSidebar.routeRecorder) } - + let node = nodes.first(where: { $0.num == preferredNodeNum }) let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 ? true : false - - + if !(node?.deviceConfig?.isManaged ?? false) { if bleManager.connectedPeripheral != nil { Section("Configure") { @@ -107,7 +106,7 @@ struct Settings: View { if selectedNode == 0 { Text("Connect to a Node").tag(0) } - + ForEach(nodes) { node in if node.num == bleManager.connectedPeripheral?.num ?? 0 { Label { @@ -161,8 +160,8 @@ struct Settings: View { Section("radio.configuration") { if node != nil && node?.loRaConfig != nil { let rc = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0)) - if rc?.dutyCycle ?? 0 > 0 && rc?.dutyCycle ?? 0 < 100 { - + if rc?.dutyCycle ?? 0 > 0 && rc?.dutyCycle ?? 0 < 100 { + Label { Text("Hourly Duty Cycle") } icon: { diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index b2e2f1c3..6246d7fe 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -52,7 +52,7 @@ struct ShareChannels: View { var qrCodeImage = QrCodeImage() var body: some View { - + if #available(iOS 17.0, macOS 14.0, *) { VStack { TipView(ShareChannelsTip(), arrowEdge: .bottom) @@ -202,7 +202,7 @@ struct ShareChannels: View { .controlSize(.large) .padding(.top) .padding(.bottom) - + ShareLink("Share QR Code & Link", item: Image(uiImage: qrImage), subject: Text("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you"), diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index a98c79ff..12f584d1 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -42,11 +42,11 @@ struct UserConfig: View { VStack { Form { Section(header: Text("User Details")) { - + VStack(alignment: .leading) { HStack { Label(isLicensed ? "Call Sign" : "Long Name", systemImage: "person.crop.rectangle.fill") - + TextField("Long Name", text: $longName) .onChange(of: longName, perform: { _ in let totalBytes = longName.utf8.count @@ -65,7 +65,7 @@ struct UserConfig: View { Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to \(isLicensed ? "8" : "36") bytes long.") .foregroundColor(.gray) .font(.callout) - + } VStack(alignment: .leading) { HStack {