diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 11d7c54b..e284ac42 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -11481,9 +11481,6 @@ }, "Map Tile Data" : { - }, - "Map Type" : { - }, "map.centering" : { "extractionState" : "manual", @@ -18948,9 +18945,6 @@ }, "Select a Trace Route" : { - }, - "Select something to view" : { - }, "select.contact" : { "extractionState" : "manual", @@ -19139,6 +19133,9 @@ }, "Send a message to a certain meshtastic channel" : { + }, + "Send a position on the primary channel when the user button is triple clicked." : { + }, "Send a shutdown to the node you are connected to" : { @@ -21895,6 +21892,9 @@ }, "Treat double tap on supported accelerometers as a user button press." : { + }, + "Triple Click Ad Hoc Ping" : { + }, "Try Again" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d9146f08..20a5586d 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1165,7 +1165,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1540; - LastUpgradeCheck = 1540; + LastUpgradeCheck = 1600; TargetAttributes = { 25F5D5C62C4375A8008036E3 = { CreatedOnToolsVersion = 15.4; @@ -1680,7 +1680,6 @@ DDC2E17F26CE248F0042C5E4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; @@ -1695,7 +1694,7 @@ INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 16.6; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1715,7 +1714,6 @@ DDC2E18026CE248F0042C5E4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; @@ -1730,7 +1728,7 @@ INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 16.6; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme b/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme index 9ef67c6d..19c6089f 100644 --- a/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme +++ b/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme @@ -1,6 +1,6 @@ some IntentResult { // Request user confirmation before performing the factory reset - try await requestConfirmation(result: .result(dialog: "Are you sure you want to factory reset the node?"),confirmationActionName: ConfirmationActionName + try await requestConfirmation(result: .result(dialog: "Are you sure you want to factory reset the node?"), confirmationActionName: ConfirmationActionName .custom(acceptLabel: "Factory Reset", acceptAlternatives: [], denyLabel: "Cancel", denyAlternatives: [], destructive: true)) // Ensure the node is connected @@ -27,7 +27,7 @@ struct FactoryResetNodeIntent: AppIntent { let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext), let fromUser = connectedNode.user, let toUser = connectedNode.user { - + // Attempt to send a factory reset command, throw an error if it fails if !BLEManager.shared.sendFactoryReset(fromUser: fromUser, toUser: toUser) { throw AppIntentErrors.AppIntentError.message("Failed to perform factory reset") diff --git a/Meshtastic/AppIntents/MessageChannelIntent.swift b/Meshtastic/AppIntents/MessageChannelIntent.swift index 6001d957..aa9ea47a 100644 --- a/Meshtastic/AppIntents/MessageChannelIntent.swift +++ b/Meshtastic/AppIntents/MessageChannelIntent.swift @@ -15,37 +15,36 @@ struct MessageChannelIntent: AppIntent { @Parameter(title: "Message") var messageContent: String - - @Parameter(title: "Channel",controlStyle: .stepper, inclusiveRange: (lowerBound: 0, upperBound: 7)) + + @Parameter(title: "Channel", controlStyle: .stepper, inclusiveRange: (lowerBound: 0, upperBound: 7)) var channelNumber: Int - - + static var parameterSummary: some ParameterSummary { Summary("Send \(\.$messageContent) to \(\.$channelNumber)") } func perform() async throws -> some IntentResult { - if (!BLEManager.shared.isConnected){ + if !BLEManager.shared.isConnected { throw AppIntentErrors.AppIntentError.notConnected } - + // Check if channel number is between 1 and 7 guard (0...7).contains(channelNumber) else { throw $channelNumber.needsValueError("Channel number must be between 0 and 7.") } - + // Convert messageContent to data and check its length guard let messageData = messageContent.data(using: .utf8) else { throw AppIntentErrors.AppIntentError.message("Failed to encode message content") } - + if messageData.count > 200 { throw $messageContent.needsValueError("Message content exceeds 200 bytes.") } - if(!BLEManager.shared.sendMessage(message: messageContent, toUserNum: 0, channel: Int32(channelNumber), isEmoji: false, replyID: 0)){ + if !BLEManager.shared.sendMessage(message: messageContent, toUserNum: 0, channel: Int32(channelNumber), isEmoji: false, replyID: 0) { throw AppIntentErrors.AppIntentError.message("Failed to send message") } - - return .result() + + return .result() } } diff --git a/Meshtastic/AppIntents/NodePositionIntent.swift b/Meshtastic/AppIntents/NodePositionIntent.swift index a173df0d..1e052eb9 100644 --- a/Meshtastic/AppIntents/NodePositionIntent.swift +++ b/Meshtastic/AppIntents/NodePositionIntent.swift @@ -14,44 +14,38 @@ struct NodePositionIntent: AppIntent { @Parameter(title: "Node Number") var nodeNum: Int - + static var title: LocalizedStringResource = "Get Node Position" static var description: IntentDescription = "Fetch the latest position of a cetain node" - - + func perform() async throws -> some IntentResult & ReturnsValue { - if (!BLEManager.shared.isConnected) { + if !BLEManager.shared.isConnected { throw AppIntentErrors.AppIntentError.notConnected } - let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest(entityName: "NodeInfoEntity") - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - do { - guard let fetchedNode = try PersistenceController.shared.container.viewContext.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity], fetchedNode.count == 1 else { - throw $nodeNum.needsValueError("Could not find node") - } - - let nodeInfo = fetchedNode[0] - nodeInfo.latestEnvironmentMetrics?.batteryLevel - if let latitude = nodeInfo.latestPosition?.coordinate.latitude, - let longitude = nodeInfo.latestPosition?.coordinate.longitude { - let nodeLocation = CLLocation(latitude: latitude, longitude: longitude) - - // Reverse geocode the CLLocation to get a CLPlacemark - let geocoder = CLGeocoder() - let placemarks = try await geocoder.reverseGeocodeLocation(nodeLocation) - - if let placemark = placemarks.first { - return .result(value: placemark) - } else { - throw AppIntentErrors.AppIntentError.message("Error Reverse Geocoding Location") - } - } else { - throw AppIntentErrors.AppIntentError.message("Node does not have positions") - } - } catch { - throw AppIntentErrors.AppIntentError.message("Fetch Failure") + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + do { + guard let fetchedNode = try PersistenceController.shared.container.viewContext.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity], fetchedNode.count == 1 else { + throw $nodeNum.needsValueError("Could not find node") } - } - -} + let nodeInfo = fetchedNode[0] + if let latitude = nodeInfo.latestPosition?.coordinate.latitude, + let longitude = nodeInfo.latestPosition?.coordinate.longitude { + let nodeLocation = CLLocation(latitude: latitude, longitude: longitude) + // Reverse geocode the CLLocation to get a CLPlacemark + let geocoder = CLGeocoder() + let placemarks = try await geocoder.reverseGeocodeLocation(nodeLocation) + if let placemark = placemarks.first { + return .result(value: placemark) + } else { + throw AppIntentErrors.AppIntentError.message("Error Reverse Geocoding Location") + } + } else { + throw AppIntentErrors.AppIntentError.message("Node does not have positions") + } + } catch { + throw AppIntentErrors.AppIntentError.message("Fetch Failure") + } + } +} diff --git a/Meshtastic/AppIntents/SendWaypointIntent.swift b/Meshtastic/AppIntents/SendWaypointIntent.swift index 392d232a..4352c548 100644 --- a/Meshtastic/AppIntents/SendWaypointIntent.swift +++ b/Meshtastic/AppIntents/SendWaypointIntent.swift @@ -16,10 +16,10 @@ struct SendWaypointIntent: AppIntent { @Parameter(title: "Name", default: "Dropped Pin") var nameParameter: String? - + @Parameter(title: "Description", default: "") var descriptionParameter: String? - + @Parameter(title: "Emoji", default: "📍") var emojiParameter: String? @@ -27,19 +27,19 @@ struct SendWaypointIntent: AppIntent { var locationParameter: CLPlacemark func perform() async throws -> some IntentResult { - if (!BLEManager.shared.isConnected){ + if !BLEManager.shared.isConnected { throw AppIntentErrors.AppIntentError.notConnected } // Provide default values if parameters are nil let name = nameParameter ?? "Dropped Pin" let description = descriptionParameter ?? "" let emoji = emojiParameter ?? "📍" - + // Validate name length if name.utf8.count > 30 { throw $nameParameter.needsValueError("Name must be less than 30 bytes") } - + // Validate description length if description.utf8.count > 100 { throw $descriptionParameter.needsValueError("Description must be less than 100 bytes") @@ -60,7 +60,6 @@ struct SendWaypointIntent: AppIntent { newWaypoint.longitudeI = Int32(longitude * 10_000_000) } - newWaypoint.id = UInt32.random(in: UInt32(UInt8.max).. Position? { var positionPacket = Position() - if #available(iOS 17.0, macOS 14.0, *) { - guard let lastLocation = LocationsHandler.shared.locationsArray.last else { - return nil - } + guard let lastLocation = LocationsHandler.shared.locationsArray.last else { + return nil + } - if lastLocation == CLLocation(latitude: 0, longitude: 0) { - return nil - } - - positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7) - positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7) - let timestamp = lastLocation.timestamp - positionPacket.time = UInt32(timestamp.timeIntervalSince1970) - positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) - positionPacket.altitude = Int32(lastLocation.altitude) - positionPacket.satsInView = UInt32(LocationsHandler.satsInView) - let currentSpeed = lastLocation.speed - if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { - positionPacket.groundSpeed = UInt32(currentSpeed) - } - let currentHeading = lastLocation.course - if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { - positionPacket.groundTrack = UInt32(currentHeading) - } - /// Set location source for time - if !fixedPosition { - /// From GPS treat time as good - positionPacket.locationSource = Position.LocSource.locExternal - } else { - /// From GPS, but time can be old and have drifted - positionPacket.locationSource = Position.LocSource.locManual - } + if lastLocation == CLLocation(latitude: 0, longitude: 0) { + return nil + } + positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7) + positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7) + let timestamp = lastLocation.timestamp + positionPacket.time = UInt32(timestamp.timeIntervalSince1970) + positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) + positionPacket.altitude = Int32(lastLocation.altitude) + positionPacket.satsInView = UInt32(LocationsHandler.satsInView) + let currentSpeed = lastLocation.speed + if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { + positionPacket.groundSpeed = UInt32(currentSpeed) + } + let currentHeading = lastLocation.course + if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { + positionPacket.groundTrack = UInt32(currentHeading) + } + /// Set location source for time + if !fixedPosition { + /// From GPS treat time as good + positionPacket.locationSource = Position.LocSource.locExternal } else { - - positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) - positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) - let timestamp = LocationHelper.shared.locationManager.location?.timestamp ?? Date() - positionPacket.time = UInt32(timestamp.timeIntervalSince1970) - positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) - 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) { - positionPacket.groundSpeed = UInt32(currentSpeed) - } - let currentHeading = LocationHelper.shared.locationManager.location?.course ?? 0 - if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { - positionPacket.groundTrack = UInt32(currentHeading) - } - /// Set location source for time - if !fixedPosition { - /// From GPS treat time as good - positionPacket.locationSource = Position.LocSource.locExternal - } else { - /// From GPS, but time can be old and have drifted - positionPacket.locationSource = Position.LocSource.locManual - } + /// From GPS, but time can be old and have drifted + positionPacket.locationSource = Position.LocSource.locManual } return positionPacket } @@ -1645,7 +1613,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let decodedString = base64UrlString.base64urlToBase64() if let decodedData = Data(base64Encoded: decodedString) { do { - let channelSet: ChannelSet = try ChannelSet(serializedData: decodedData) + let channelSet: ChannelSet = try ChannelSet(serializedBytes: decodedData) for cs in channelSet.settings { if addChannels { // We are trying to add a channel so lets get the last index @@ -2017,10 +1985,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved LoRa Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey,context: context) + upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context) return Int64(meshPacket.id) } - return 0 } @@ -3223,7 +3190,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { - if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { + if let storeAndForwardMessage = try? StoreAndForward(serializedBytes: packet.decoded.payload) { // Handle each of the store and forward request / response messages switch storeAndForwardMessage.rr { case .unset: @@ -3349,7 +3316,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 { @@ -3359,9 +3326,8 @@ extension BLEManager: CBCentralManagerDelegate { } else { isSwitchedOn = false } - + var status = "" - switch central.state { case .poweredOff: status = "BLE is powered off" @@ -3380,10 +3346,9 @@ extension BLEManager: CBCentralManagerDelegate { } Logger.services.info("📜 [BLE] Bluetooth 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) Logger.services.info("✅ [BLE] Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown", privacy: .public)") @@ -3391,7 +3356,6 @@ 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/LocalNotificationManager.swift b/Meshtastic/Helpers/LocalNotificationManager.swift index 1260f20c..47f64bce 100644 --- a/Meshtastic/Helpers/LocalNotificationManager.swift +++ b/Meshtastic/Helpers/LocalNotificationManager.swift @@ -5,16 +5,9 @@ import OSLog class LocalNotificationManager { var notifications = [Notification]() - - let thumbsUpAction = UNNotificationAction(identifier: "messageNotification.thumbsUpAction", title: - "👍 \(Tapbacks.thumbsUp.description)", options: []) - let thumbsDownAction = UNNotificationAction(identifier: "messageNotification.thumbsDownAction", title: - "👎 \(Tapbacks.thumbsDown.description)", options: []) - let replyInputAction = UNTextInputNotificationAction( - identifier: "messageNotification.replyInputAction", - title: "reply".localized, - options: []) - + let thumbsUpAction = UNNotificationAction(identifier: "messageNotification.thumbsUpAction", title: "👍 \(Tapbacks.thumbsUp.description)", options: []) + let thumbsDownAction = UNNotificationAction(identifier: "messageNotification.thumbsDownAction", title: "👎 \(Tapbacks.thumbsDown.description)", options: []) + let replyInputAction = UNTextInputNotificationAction(identifier: "messageNotification.replyInputAction", title: "reply".localized, options: []) // Step 1 Request Permissions for notifications private func requestAuthorization() { @@ -43,13 +36,13 @@ class LocalNotificationManager { private func scheduleNotifications() { let messageNotificationCategory = UNNotificationCategory( identifier: "messageNotificationCategory", - actions: [thumbsUpAction, thumbsDownAction,replyInputAction], + actions: [thumbsUpAction, thumbsDownAction, replyInputAction], intentIdentifiers: [], options: .customDismissAction ) UNUserNotificationCenter.current().setNotificationCategories([messageNotificationCategory]) - + for notification in notifications { let content = UNMutableNotificationContent() content.subtitle = notification.subtitle @@ -75,7 +68,6 @@ class LocalNotificationManager { content.userInfo["userNum"] = notification.userNum } - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger) diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index 20d7725f..a215667b 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -10,7 +10,6 @@ import CoreLocation import OSLog // Shared state that manages the `CLLocationManager` and `CLBackgroundActivitySession`. -@available(iOS 17.0, macOS 14.0, *) @MainActor class LocationsHandler: ObservableObject { static let shared = LocationsHandler() // Create a single, shared instance of the object. diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 7d0c6a6b..9c4ed69c 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -50,7 +50,6 @@ func generateMessageMarkdown (message: String) -> String { func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) { - let remote = nodeNum != UserDefaults.preferredPeripheralNum if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: nodeNum, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { @@ -454,11 +453,11 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { - if let adminMessage = try? AdminMessage(serializedData: packet.decoded.payload) { + if let adminMessage = try? AdminMessage(serializedBytes: packet.decoded.payload) { if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getCannedMessageModuleMessagesResponse(adminMessage.getCannedMessageModuleMessagesResponse) { - if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { + if let cmmc = try? CannedMessageModuleConfig(serializedBytes: packet.decoded.payload) { if !cmmc.messages.isEmpty { @@ -581,7 +580,7 @@ func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) { do { let fetchedNode = try context.fetch(fetchNodeInfoRequest) - if let paxMessage = try? Paxcount(serializedData: packet.decoded.payload) { + if let paxMessage = try? Paxcount(serializedBytes: packet.decoded.payload) { let newPax = PaxCounterEntity(context: context) newPax.ble = Int32(truncatingIfNeeded: paxMessage.ble) @@ -611,7 +610,7 @@ func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) { func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { - if let routingMessage = try? Routing(serializedData: packet.decoded.payload) { + if let routingMessage = try? Routing(serializedBytes: packet.decoded.payload) { let routingError = RoutingError(rawValue: routingMessage.errorReason.rawValue) @@ -833,7 +832,7 @@ func textMessageAppPacket( } var storeForwardBroadcast = false if storeForward { - if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { + if let storeAndForwardMessage = try? StoreAndForward(serializedBytes: packet.decoded.payload) { messageText = String(bytes: storeAndForwardMessage.text, encoding: .utf8) if storeAndForwardMessage.rr == .routerTextBroadcast { storeForwardBroadcast = true @@ -993,7 +992,6 @@ func textMessageAppPacket( } } - func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.waypoint.received %@".localized, String(packet.from)) @@ -1004,7 +1002,7 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { do { - if let waypointMessage = try? Waypoint(serializedData: packet.decoded.payload) { + if let waypointMessage = try? Waypoint(serializedBytes: packet.decoded.payload) { let fetchedWaypoint = try context.fetch(fetchWaypointRequest) if fetchedWaypoint.isEmpty { let waypoint = WaypointEntity(context: context) diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents index 2a5e4a56..687237cb 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents @@ -69,6 +69,7 @@ + diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index f926956c..b7b28f35 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -3,11 +3,8 @@ import SwiftUI import CoreData import OSLog -#if canImport(TipKit) import TipKit -#endif -@available(iOS 17.0, *) @main struct MeshtasticAppleApp: App { @@ -111,26 +108,24 @@ struct MeshtasticAppleApp: App { } }) .task { - if #available(iOS 17.0, macOS 14.0, *) { - #if DEBUG - /// Optionally, call `Tips.resetDatastore()` before `Tips.configure()` to reset the state of all tips. This will allow tips to re-appear even after they have been dismissed by the user. - /// This is for testing only, and should not be enabled in release builds. - try? Tips.resetDatastore() - #endif + #if DEBUG + /// Optionally, call `Tips.resetDatastore()` before `Tips.configure()` to reset the state of all tips. This will allow tips to re-appear even after they have been dismissed by the user. + /// This is for testing only, and should not be enabled in release builds. + try? Tips.resetDatastore() + #endif - try? Tips.configure( - [ - // Reset which tips have been shown and what parameters have been tracked, useful during testing and for this sample project - .datastoreLocation(.applicationDefault), - // When should the tips be presented? If you use .immediate, they'll all be presented whenever a screen with a tip appears. - // You can adjust this on per tip level as well - .displayFrequency(.immediate) - ] - ) - } + try? Tips.configure( + [ + // Reset which tips have been shown and what parameters have been tracked, useful during testing and for this sample project + .datastoreLocation(.applicationDefault), + // When should the tips be presented? If you use .immediate, they'll all be presented whenever a screen with a tip appears. + // You can adjust this on per tip level as well + .displayFrequency(.immediate) + ] + ) } } - .onChange(of: scenePhase) { (newScenePhase) in + .onChange(of: scenePhase) { (_, newScenePhase) in switch newScenePhase { case .background: Logger.services.info("🎬 [App] Scene is in the background") diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index d38557bc..801fa955 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -9,9 +9,9 @@ import SwiftUI import OSLog class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject { - + var router: Router? - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { Logger.services.info("🚀 [App] Meshtstic Apple App launched!") // Default User Default Values @@ -19,14 +19,11 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat 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 - locationsHandler.startLocationUpdates() - - // If a background activity session was previously active, reinstantiate it after the background launch. - if locationsHandler.backgroundActivity { - locationsHandler.backgroundActivity = true - } + let locationsHandler = LocationsHandler.shared + locationsHandler.startLocationUpdates() + // If a background activity session was previously active, reinstantiate it after the background launch. + if locationsHandler.backgroundActivity { + locationsHandler.backgroundActivity = true } return true } @@ -38,7 +35,7 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat ) { completionHandler([.list, .banner, .sound]) } - + // This method is called when a user clicks on the notification func userNotificationCenter( _ center: UNUserNotificationCenter, @@ -46,11 +43,10 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat withCompletionHandler completionHandler: @escaping () -> Void ) { let userInfo = response.notification.request.content.userInfo - + switch response.actionIdentifier { case UNNotificationDefaultActionIdentifier: break - case "messageNotification.thumbsUpAction": if let channel = userInfo["channel"] as? Int32, let replyID = userInfo["messageId"] as? Int64 { @@ -65,8 +61,6 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat } else { Logger.services.error("Failed to retrieve channel or messageId from userInfo") } - break - case "messageNotification.thumbsDownAction": if let channel = userInfo["channel"] as? Int32, let replyID = userInfo["messageId"] as? Int64 { @@ -81,8 +75,6 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat } else { Logger.services.error("Failed to retrieve channel or messageId from userInfo") } - break - case "messageNotification.replyInputAction": if let userInput = (response as? UNTextInputNotificationResponse)?.userText, let channel = userInfo["channel"] as? Int32, @@ -98,12 +90,10 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat } else { Logger.services.error("Failed to retrieve user input, channel, or messageId from userInfo") } - break - default: break } - + if let targetValue = userInfo["target"] as? String, let deepLink = userInfo["path"] as? String, let url = URL(string: deepLink) { @@ -112,7 +102,6 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat } else { Logger.services.error("Failed to handle notification response: \(userInfo)") } - completionHandler() } } diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index faa8e606..69757588 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -159,12 +159,12 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) if packet.to == Constants.maximumNodeNum || packet.to == UserDefaults.preferredPeripheralNum { newNode.channel = Int32(packet.channel) } - if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { + if let nodeInfoMessage = try? NodeInfo(serializedBytes: packet.decoded.payload) { newNode.hopsAway = Int32(nodeInfoMessage.hopsAway) newNode.favorite = nodeInfoMessage.isFavorite } - if let newUserMessage = try? User(serializedData: packet.decoded.payload) { + if let newUserMessage = try? User(serializedBytes: packet.decoded.payload) { if newUserMessage.id.isEmpty { if packet.from > Constants.minimumNodeNum { @@ -254,7 +254,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].channel = Int32(packet.channel) } - if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { + if let nodeInfoMessage = try? NodeInfo(serializedBytes: packet.decoded.payload) { fetchedNode[0].hopsAway = Int32(nodeInfoMessage.hopsAway) fetchedNode[0].favorite = nodeInfoMessage.isFavorite @@ -320,7 +320,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) do { - if let positionMessage = try? Position(serializedData: packet.decoded.payload) { + if let positionMessage = try? Position(serializedBytes: packet.decoded.payload) { /// Don't save empty position packets from null island or apple park if (positionMessage.longitudeI != 0 && positionMessage.latitudeI != 0) && (positionMessage.latitudeI != 373346000 && positionMessage.longitudeI != -1220090000) { @@ -462,24 +462,24 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessi if fetchedNode[0].deviceConfig == nil { let newDeviceConfig = DeviceConfigEntity(context: context) newDeviceConfig.role = Int32(config.role.rawValue) - newDeviceConfig.serialEnabled = config.serialEnabled newDeviceConfig.buttonGpio = Int32(config.buttonGpio) newDeviceConfig.buzzerGpio = Int32(config.buzzerGpio) newDeviceConfig.rebroadcastMode = Int32(config.rebroadcastMode.rawValue) newDeviceConfig.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber) newDeviceConfig.doubleTapAsButtonPress = config.doubleTapAsButtonPress + newDeviceConfig.tripleClickAsAdHocPing = !config.disableTripleClick newDeviceConfig.ledHeartbeatEnabled = !config.ledHeartbeatDisabled newDeviceConfig.isManaged = config.isManaged newDeviceConfig.tzdef = config.tzdef fetchedNode[0].deviceConfig = newDeviceConfig } else { fetchedNode[0].deviceConfig?.role = Int32(config.role.rawValue) - fetchedNode[0].deviceConfig?.serialEnabled = config.serialEnabled fetchedNode[0].deviceConfig?.buttonGpio = Int32(config.buttonGpio) fetchedNode[0].deviceConfig?.buzzerGpio = Int32(config.buzzerGpio) fetchedNode[0].deviceConfig?.rebroadcastMode = Int32(config.rebroadcastMode.rawValue) fetchedNode[0].deviceConfig?.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber) fetchedNode[0].deviceConfig?.doubleTapAsButtonPress = config.doubleTapAsButtonPress + fetchedNode[0].deviceConfig?.tripleClickAsAdHocPing = !config.disableTripleClick fetchedNode[0].deviceConfig?.ledHeartbeatEnabled = !config.ledHeartbeatDisabled fetchedNode[0].deviceConfig?.isManaged = config.isManaged fetchedNode[0].deviceConfig?.tzdef = config.tzdef diff --git a/Meshtastic/Tips/BluetoothTips.swift b/Meshtastic/Tips/BluetoothTips.swift index f7540b66..02d2af53 100644 --- a/Meshtastic/Tips/BluetoothTips.swift +++ b/Meshtastic/Tips/BluetoothTips.swift @@ -5,11 +5,8 @@ // Copyright(c) Garth Vander Houwen 8/31/23. // import SwiftUI -#if canImport(TipKit) import TipKit -#endif -@available(iOS 17.0, macOS 14.0, *) struct BluetoothConnectionTip: Tip { var id: String { diff --git a/Meshtastic/Tips/ChannelTips.swift b/Meshtastic/Tips/ChannelTips.swift index be241951..712a266e 100644 --- a/Meshtastic/Tips/ChannelTips.swift +++ b/Meshtastic/Tips/ChannelTips.swift @@ -5,11 +5,8 @@ // Copyright(c) Garth Vander Houwen 8/31/23. // import SwiftUI - #if canImport(TipKit) import TipKit - #endif - @available(iOS 17.0, macOS 14.0, *) struct ShareChannelsTip: Tip { var id: String { @@ -26,7 +23,6 @@ } } -@available(iOS 17.0, macOS 14.0, *) struct CreateChannelsTip: Tip { var id: String { @@ -43,7 +39,6 @@ struct CreateChannelsTip: Tip { } } -@available(iOS 17.0, macOS 14.0, *) struct AdminChannelTip: Tip { var id: String { diff --git a/Meshtastic/Tips/MessagesTips.swift b/Meshtastic/Tips/MessagesTips.swift index 4771e386..ddbe9feb 100644 --- a/Meshtastic/Tips/MessagesTips.swift +++ b/Meshtastic/Tips/MessagesTips.swift @@ -5,11 +5,8 @@ // Copyright(c) Garth Vander Houwen 9/15/23. // import SwiftUI -#if canImport(TipKit) import TipKit -#endif -@available(iOS 17.0, macOS 14.0, *) struct MessagesTip: Tip { var id: String { diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index f7aaa738..c26b7676 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -11,9 +11,7 @@ import CoreData import CoreLocation import CoreBluetooth import OSLog -#if canImport(TipKit) import TipKit -#endif #if canImport(ActivityKit) import ActivityKit #endif @@ -49,9 +47,7 @@ struct Connect: View { if bleManager.isSwitchedOn { Section(header: Text("connected.radio").font(.title)) { if let connectedPeripheral = bleManager.connectedPeripheral, connectedPeripheral.peripheral.state == .connected { - if #available(iOS 17.0, macOS 14.0, *) { - TipView(BluetoothConnectionTip(), arrowEdge: .bottom) - } + TipView(BluetoothConnectionTip(), arrowEdge: .bottom) VStack(alignment: .leading) { HStack { VStack(alignment: .center) { @@ -78,12 +74,10 @@ struct Connect: View { .foregroundColor(.green) } else { HStack { - if #available(iOS 17.0, macOS 14.0, *) { - Image(systemName: "square.stack.3d.down.forward") - .symbolRenderingMode(.multicolor) - .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) - .foregroundColor(.orange) - } + Image(systemName: "square.stack.3d.down.forward") + .symbolRenderingMode(.multicolor) + .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) + .foregroundColor(.orange) Text("communicating").font(.callout) .foregroundColor(.orange) } @@ -307,10 +301,10 @@ struct Connect: View { .presentationDetents([.large]) .presentationDragIndicator(.automatic) } - .onChange(of: (self.bleManager.invalidVersion)) { _ in + .onChange(of: self.bleManager.invalidVersion) { invalidFirmwareVersion = self.bleManager.invalidVersion } - .onChange(of: (self.bleManager.isSubscribed)) { sub in + .onChange(of: self.bleManager.isSubscribed) { _, sub in if UserDefaults.preferredPeripheralId.count > 0 && sub { diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 23b25f0c..b122b0aa 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -4,7 +4,6 @@ import SwiftUI -@available(iOS 17.0, *) struct ContentView: View { @ObservedObject var appState: AppState @@ -39,19 +38,11 @@ struct ContentView: View { } .tag(NavigationState.Tab.nodes) - if #available(iOS 17.0, macOS 14.0, *), !UserDefaults.mapUseLegacy { - MeshMap(router: appState.router) - .tabItem { - Label("map", systemImage: "map") - } - .tag(NavigationState.Tab.map) - } else { - NodeMap(router: appState.router) - .tabItem { - Label("map", systemImage: "map") - } - .tag(NavigationState.Tab.map) - } + MeshMap(router: appState.router) + .tabItem { + Label("map", systemImage: "map") + } + .tag(NavigationState.Tab.map) Settings( router: appState.router diff --git a/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift b/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift index 59c0e9cd..f9593585 100644 --- a/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift +++ b/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift @@ -1,164 +1,164 @@ +//// +//// NodeMapControl.swift +//// Meshtastic +//// +//// Created by Garth Vander Houwen on 9/9/23. +//// +//import SwiftUI +//import CoreLocation +//import MapKit +//import WeatherKit +//import OSLog // -// NodeMapControl.swift -// Meshtastic +//struct NodeMapMapkit: View { // -// Created by Garth Vander Houwen on 9/9/23. +// @Environment(\.managedObjectContext) var context +// @EnvironmentObject var bleManager: BLEManager +// /// Weather +// /// The current weather condition for the city. +// @State private var condition: WeatherCondition? +// @State private var temperature: Measurement? +// @State private var humidity: Int? +// @State private var symbolName: String = "cloud.fill" +// @State private var attributionLink: URL? +// @State private var attributionLogo: URL? // -import SwiftUI -import CoreLocation -import MapKit -import WeatherKit -import OSLog - -struct NodeMapMapkit: View { - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - /// Weather - /// The current weather condition for the city. - @State private var condition: WeatherCondition? - @State private var temperature: Measurement? - @State private var humidity: Int? - @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 - @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false - @State private var selectedMapLayer: MapLayer = .standard - @State var waypointCoordinate: WaypointCoordinate? - @State var editingWaypoint: Int = 0 - @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( - mapName: "offlinemap", - tileType: "png", - canReplaceMapContent: true - ) - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], - predicate: NSPredicate( - format: "expire == nil || expire >= %@", Date() as NSDate - ), animation: .none) - private var waypoints: FetchedResults - @ObservedObject var node: NodeInfoEntity - - var body: some View { - - NavigationStack { - GeometryReader { bounds in - VStack { - if node.hasPositions { - ZStack { - let positionArray = node.positions?.array as? [PositionEntity] ?? [] - let lastTenThousand = Array(positionArray.prefix(10000)) - // let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) } - ZStack { - MapViewSwiftUI(onLongPress: { coord in - waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0) - }, onWaypointEdit: { wpId in - if wpId > 0 { - waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) - } - }, - selectedMapLayer: selectedMapLayer, - positions: lastTenThousand, - waypoints: Array(waypoints), - userTrackingMode: MKUserTrackingMode.none, - showNodeHistory: meshMapShowNodeHistory, - showRouteLines: meshMapShowRouteLines, - customMapOverlay: self.customMapOverlay - ) - VStack(alignment: .leading) { - Spacer() - HStack(alignment: .bottom, spacing: 1) { - Picker("Map Type", selection: $selectedMapLayer) { - ForEach(MapLayer.allCases, id: \.self) { layer in - if layer == MapLayer.offline && UserDefaults.enableOfflineMaps { - Text(layer.localized) - } else if layer != MapLayer.offline { - Text(layer.localized) - } - } - } - .onChange(of: (selectedMapLayer)) { newMapLayer in - UserDefaults.mapLayer = newMapLayer - } - .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) - .pickerStyle(.menu) - .padding(5) - VStack { - VStack { - Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName) - .font(.caption) - - Label("\(humidity ?? 0)%", systemImage: "humidity") - .font(.caption2) - - AsyncImage(url: attributionLogo) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - ProgressView() - .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) - .task { - do { - if node.hasPositions { - let mostRecent = node.positions?.lastObject as? PositionEntity - let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)) - condition = weather.currentWeather.condition - temperature = weather.currentWeather.temperature - humidity = Int(weather.currentWeather.humidity * 100) - symbolName = weather.currentWeather.symbolName - let attribution = try await WeatherService.shared.attribution - attributionLink = attribution.legalPageURL - attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL - } - } catch { - Logger.services.error("Could not gather weather information: \(error.localizedDescription)") - condition = .clear - symbolName = "cloud.fill" - } - } - } - } - } - .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) - .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65) - } - } else { - HStack { - } - .padding([.top], 20) - } - } - .edgesIgnoringSafeArea([.leading, .trailing]) - .sheet(item: $waypointCoordinate, content: { wpc in - WaypointFormMapKit(coordinate: wpc) - .presentationDetents([.medium, .large]) - .presentationDragIndicator(.automatic) - }) - .navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline) - .navigationBarItems(trailing: - ZStack { - ConnectedDevice( - bluetoothOn: bleManager.isSwitchedOn, - deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) - } - .padding(.bottom, 2) - } - } -} +// @Environment(\.colorScheme) var colorScheme: ColorScheme +// @AppStorage("meshMapType") private var meshMapType = 0 +// @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false +// @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false +// @State private var selectedMapLayer: MapLayer = .standard +// @State var waypointCoordinate: WaypointCoordinate? +// @State var editingWaypoint: Int = 0 +// @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( +// mapName: "offlinemap", +// tileType: "png", +// canReplaceMapContent: true +// ) +// @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], +// predicate: NSPredicate( +// format: "expire == nil || expire >= %@", Date() as NSDate +// ), animation: .none) +// private var waypoints: FetchedResults +// @ObservedObject var node: NodeInfoEntity +// +// var body: some View { +// +// NavigationStack { +// GeometryReader { bounds in +// VStack { +// if node.hasPositions { +// ZStack { +// let positionArray = node.positions?.array as? [PositionEntity] ?? [] +// let lastTenThousand = Array(positionArray.prefix(10000)) +// // let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) } +// ZStack { +// MapViewSwiftUI(onLongPress: { coord in +// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0) +// }, onWaypointEdit: { wpId in +// if wpId > 0 { +// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) +// } +// }, +// selectedMapLayer: selectedMapLayer, +// positions: lastTenThousand, +// waypoints: Array(waypoints), +// userTrackingMode: MKUserTrackingMode.none, +// showNodeHistory: meshMapShowNodeHistory, +// showRouteLines: meshMapShowRouteLines, +// customMapOverlay: self.customMapOverlay +// ) +// VStack(alignment: .leading) { +// Spacer() +// HStack(alignment: .bottom, spacing: 1) { +// Picker("Map Type", selection: $selectedMapLayer) { +// ForEach(MapLayer.allCases, id: \.self) { layer in +// if layer == MapLayer.offline && UserDefaults.enableOfflineMaps { +// Text(layer.localized) +// } else if layer != MapLayer.offline { +// Text(layer.localized) +// } +// } +// } +// .onChange(of: (selectedMapLayer)) { newMapLayer in +// UserDefaults.mapLayer = newMapLayer +// } +// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) +// .pickerStyle(.menu) +// .padding(5) +// VStack { +// VStack { +// Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName) +// .font(.caption) +// +// Label("\(humidity ?? 0)%", systemImage: "humidity") +// .font(.caption2) +// +// AsyncImage(url: attributionLogo) { image in +// image +// .resizable() +// .scaledToFit() +// } placeholder: { +// ProgressView() +// .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) +// .task { +// do { +// if node.hasPositions { +// let mostRecent = node.positions?.lastObject as? PositionEntity +// let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)) +// condition = weather.currentWeather.condition +// temperature = weather.currentWeather.temperature +// humidity = Int(weather.currentWeather.humidity * 100) +// symbolName = weather.currentWeather.symbolName +// let attribution = try await WeatherService.shared.attribution +// attributionLink = attribution.legalPageURL +// attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL +// } +// } catch { +// Logger.services.error("Could not gather weather information: \(error.localizedDescription)") +// condition = .clear +// symbolName = "cloud.fill" +// } +// } +// } +// } +// } +// .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) +// .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65) +// } +// } else { +// HStack { +// } +// .padding([.top], 20) +// } +// } +// .edgesIgnoringSafeArea([.leading, .trailing]) +// .sheet(item: $waypointCoordinate, content: { wpc in +// WaypointFormMapKit(coordinate: wpc) +// .presentationDetents([.medium, .large]) +// .presentationDragIndicator(.automatic) +// }) +// .navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline) +// .navigationBarItems(trailing: +// ZStack { +// ConnectedDevice( +// bluetoothOn: bleManager.isSwitchedOn, +// deviceConnected: bleManager.connectedPeripheral != nil, +// name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") +// }) +// } +// .padding(.bottom, 2) +// } +// } +//} diff --git a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift index 5fc5e2f4..ed48e1a4 100644 --- a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift +++ b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift @@ -51,7 +51,7 @@ struct WaypointFormMapKit: View { axis: .vertical ) .foregroundColor(Color.gray) - .onChange(of: name, perform: { _ in + .onChange(of: name) { var totalBytes = name.utf8.count // Only mess with the value if it is too big while totalBytes > 30 { @@ -61,7 +61,7 @@ struct WaypointFormMapKit: View { if totalBytes > 30 { name = String(name.dropLast()) } - }) + } } HStack { Text("Description") @@ -72,14 +72,14 @@ struct WaypointFormMapKit: View { axis: .vertical ) .foregroundColor(Color.gray) - .onChange(of: description, perform: { _ in + .onChange(of: description) { var totalBytes = description.utf8.count // Only mess with the value if it is too big while totalBytes > 100 { description = String(description.dropLast()) totalBytes = description.utf8.count } - }) + } } HStack { Text("Icon") @@ -87,7 +87,7 @@ struct WaypointFormMapKit: View { EmojiOnlyTextField(text: $icon, placeholder: "Select an emoji") .font(.title) .focused($iconIsFocused) - .onChange(of: icon) { value in + .onChange(of: icon) { _, value in // If you have anything other than emojis in your string make it empty if !value.onlyEmojis() { diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 36cc5592..1b783427 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -134,11 +134,11 @@ struct ChannelMessageList: View { scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) } } - .onChange(of: channel.allPrivateMessages, perform: { _ in + .onChange(of: channel.allPrivateMessages) { withAnimation { scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) } - }) + } } TextMessageField( diff --git a/Meshtastic/Views/Messages/MessageText.swift b/Meshtastic/Views/Messages/MessageText.swift index 411511fd..df5b1f3d 100644 --- a/Meshtastic/Views/Messages/MessageText.swift +++ b/Meshtastic/Views/Messages/MessageText.swift @@ -47,23 +47,14 @@ struct MessageText: View { let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue) if tapBackDestination.overlaySensorMessage { VStack { - if #available(iOS 17.0, macOS 14.0, *) { - isDetectionSensorMessage ? Image(systemName: "sensor.fill") - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) - .foregroundStyle(Color.orange) - .symbolRenderingMode(.multicolor) - .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) - .offset(x: 20, y: -20) - : nil - } else { - isDetectionSensorMessage ? Image(systemName: "sensor.fill") - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) - .foregroundStyle(Color.orange) - .offset(x: 20, y: -20) - : nil - } + isDetectionSensorMessage ? Image(systemName: "sensor.fill") + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) + .foregroundStyle(Color.orange) + .symbolRenderingMode(.multicolor) + .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) + .offset(x: 20, y: -20) + : nil } } else { EmptyView() diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 09b8d1af..1267d3e2 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -8,9 +8,7 @@ import SwiftUI import CoreData import OSLog -#if canImport(TipKit) import TipKit -#endif struct Messages: View { @@ -65,9 +63,7 @@ struct Messages: View { } } - if #available(iOS 17.0, macOS 14.0, *) { - TipView(MessagesTip(), arrowEdge: .top) - } + TipView(MessagesTip(), arrowEdge: .top) } .navigationTitle("messages") .navigationBarTitleDisplayMode(.large) @@ -91,7 +87,7 @@ struct Messages: View { } else if case .directMessages = router.navigationState.messages { Text("Select a conversation") } - }.onChange(of: router.navigationState) { _ in + }.onChange(of: router.navigationState) { setupNavigationState() } } diff --git a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift index 7aded0f1..1eca5015 100644 --- a/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift +++ b/Meshtastic/Views/Messages/TextMessageField/TextMessageField.swift @@ -30,14 +30,14 @@ struct TextMessageField: View { HStack(alignment: .top) { ZStack { TextField("message", text: $typingMessage, axis: .vertical) - .onChange(of: typingMessage, perform: { value in + .onChange(of: typingMessage) { _, value in totalBytes = value.utf8.count // Only mess with the value if it is too big while totalBytes > Self.maxbytes { typingMessage = String(typingMessage.dropLast()) totalBytes = typingMessage.utf8.count } - }) + } .keyboardType(.default) .toolbar { ToolbarItemGroup(placement: .keyboard) { diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 4db57180..4a4028e2 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -8,9 +8,7 @@ import SwiftUI import CoreData import OSLog -#if canImport(TipKit) import TipKit -#endif struct UserList: View { @@ -201,12 +199,12 @@ struct UserList: View { .sheet(isPresented: $showingHelp) { DirectMessagesHelp() } - .onChange(of: searchText) { _ in + .onChange(of: searchText) { Task { await searchUserList() } } - .onChange(of: viaLora) { _ in + .onChange(of: viaLora) { if !viaLora && !viaMqtt { viaMqtt = true } @@ -214,7 +212,7 @@ struct UserList: View { await searchUserList() } } - .onChange(of: viaMqtt) { _ in + .onChange(of: viaMqtt) { if !viaLora && !viaMqtt { viaLora = true } @@ -222,27 +220,27 @@ struct UserList: View { await searchUserList() } } - .onChange(of: [deviceRoles]) { _ in + .onChange(of: [deviceRoles]) { Task { await searchUserList() } } - .onChange(of: hopsAway) { _ in + .onChange(of: hopsAway) { Task { await searchUserList() } } - .onChange(of: [boolFilters]) { _ in + .onChange(of: [boolFilters]) { Task { await searchUserList() } } - .onChange(of: maxDistance) { _ in + .onChange(of: maxDistance) { Task { await searchUserList() } } - .onChange(of: isPkiEncrypted) { _ in + .onChange(of: isPkiEncrypted) { Task { await searchUserList() } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index cb483745..7ce190fc 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -122,11 +122,11 @@ struct UserMessageList: View { scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) } } - .onChange(of: user.messageList, perform: { _ in + .onChange(of: user.messageList) { withAnimation { scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) } - }) + } } TextMessageField( diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 63e17424..0239e476 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -36,32 +36,31 @@ struct DeviceMetricsLog: View { .sorted { $0.time! < $1.time! } if chartData.count > 0 { GroupBox(label: Label("\(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { - if #available(iOS 17.0, macOS 14.0, *) { - Chart { - ForEach(chartData, id: \.self) { point in - Plot { - LineMark( - x: .value("x", point.time!), - y: .value("y", point.batteryLevel) - ) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") - .foregroundStyle(batteryChartColor) - .interpolationMethod(.linear) - Plot { - PointMark( - x: .value("x", point.time!), - y: .value("y", point.channelUtilization) - ) - .symbolSize(25) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") - .foregroundStyle(channelUtilizationChartColor) - if let chartSelection { - RuleMark(x: .value("Second", chartSelection, unit: .second)) - .foregroundStyle(.tertiary.opacity(0.5)) + Chart { + ForEach(chartData, id: \.self) { point in + Plot { + LineMark( + x: .value("x", point.time!), + y: .value("y", point.batteryLevel) + ) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") + .foregroundStyle(batteryChartColor) + .interpolationMethod(.linear) + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.channelUtilization) + ) + .symbolSize(25) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") + .foregroundStyle(channelUtilizationChartColor) + if let chartSelection { + RuleMark(x: .value("Second", chartSelection, unit: .second)) + .foregroundStyle(.tertiary.opacity(0.5)) // .annotation( // position: .automatic, // overflowResolution: .init(x: .fit, y: .disabled) @@ -75,91 +74,37 @@ struct DeviceMetricsLog: View { // .foregroundStyle(Color.accentColor.opacity(0.2)) // } // } - } - RuleMark(y: .value("Network Status Orange", 25)) - .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) - .foregroundStyle(.orange) - RuleMark(y: .value("Network Status Red", 50)) - .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) - .foregroundStyle(.red) - Plot { - PointMark( - x: .value("x", point.time!), - y: .value("y", point.airUtilTx) - ) - .symbolSize(25) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)") - .foregroundStyle(airtimeChartColor) } - } - .chartXAxis(content: { - AxisMarks(position: .top) - }) - .chartXAxis(.automatic) - .chartXSelection(value: $chartSelection) - .chartYScale(domain: 0...100) - .chartForegroundStyleScale([ - idiom == .phone ? "Battery" : "Battery Level": batteryChartColor, - "Channel Utilization": channelUtilizationChartColor, - "Airtime": airtimeChartColor - ]) - .chartLegend(position: .automatic, alignment: .bottom) - } else { - // Fallback on earlier versions - Chart { - ForEach(chartData, id: \.self) { point in - Plot { - LineMark( - x: .value("x", point.time!), - y: .value("y", point.batteryLevel) - ) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") - .foregroundStyle(batteryChartColor) - .interpolationMethod(.linear) - Plot { - PointMark( - x: .value("x", point.time!), - y: .value("y", point.channelUtilization) - ) - .symbolSize(25) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") - .foregroundStyle(channelUtilizationChartColor) - RuleMark(y: .value("Network Status Orange", 25)) - .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) - .foregroundStyle(.orange) - RuleMark(y: .value("Network Status Red", 50)) - .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) - .foregroundStyle(.red) - Plot { - PointMark( - x: .value("x", point.time!), - y: .value("y", point.airUtilTx) - ) - .symbolSize(25) - } - .accessibilityLabel("Line Series") - .accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)") - .foregroundStyle(airtimeChartColor) + RuleMark(y: .value("Network Status Orange", 25)) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + .foregroundStyle(.orange) + RuleMark(y: .value("Network Status Red", 50)) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + .foregroundStyle(.red) + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.airUtilTx) + ) + .symbolSize(25) } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)") + .foregroundStyle(airtimeChartColor) } - .chartXAxis(content: { - AxisMarks(position: .top) - }) - .chartXAxis(.automatic) - .chartYScale(domain: 0...100) - .chartForegroundStyleScale([ - idiom == .phone ? "Battery" : "Battery Level": batteryChartColor, - "Channel Utilization": channelUtilizationChartColor, - "Airtime": airtimeChartColor - ]) - .chartLegend(position: .automatic, alignment: .bottom) } + .chartXAxis(content: { + AxisMarks(position: .top) + }) + .chartXAxis(.automatic) + .chartXSelection(value: $chartSelection) + .chartYScale(domain: 0...100) + .chartForegroundStyleScale([ + idiom == .phone ? "Battery" : "Battery Level": batteryChartColor, + "Channel Utilization": channelUtilizationChartColor, + "Airtime": airtimeChartColor + ]) + .chartLegend(position: .automatic, alignment: .bottom) } .frame(minHeight: 240) } @@ -262,18 +207,14 @@ struct DeviceMetricsLog: View { .padding(.bottom) .padding(.trailing) } - .onChange(of: selection) { newSelection in + .onChange(of: selection) { _, newSelection in guard let metrics = deviceMetrics.first(where: { $0.id == newSelection }) else { return } chartSelection = metrics.time } } else { - if #available (iOS 17, *) { - ContentUnavailableView("No Device Metrics", systemImage: "slash.circle") - } else { - Text("No Device Metrics") - } + ContentUnavailableView("No Device Metrics", systemImage: "slash.circle") } } .navigationTitle("device.metrics.log") diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 0cd35248..56b0c82c 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -193,11 +193,7 @@ struct EnvironmentMetricsLog: View { } } else { - if #available (iOS 17, *) { - ContentUnavailableView("No Environment Metrics", systemImage: "slash.circle") - } else { - Text("No Environment Metrics") - } + ContentUnavailableView("No Environment Metrics", systemImage: "slash.circle") } } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index edfec3be..dd49484a 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -8,7 +8,6 @@ import SwiftUI import MapKit -@available(iOS 17.0, macOS 14.0, *) struct MeshMapContent: MapContent { /// Parameters diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift index a0c2f63b..c9fbf95a 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift @@ -8,7 +8,6 @@ import SwiftUI import MapKit import CoreData -@available(iOS 17.0, macOS 14.0, *) struct NodeMapContent: MapContent { @ObservedObject var node: NodeInfoEntity diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift index 38b96a5d..088787b9 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift @@ -6,11 +6,8 @@ // import SwiftUI -#if canImport(MapKit) import MapKit -#endif -@available(iOS 17.0, macOS 14.0, *) struct MapSettingsForm: View { @Environment(\.dismiss) private var dismiss @State private var currentDetent = PresentationDetent.medium @@ -39,7 +36,7 @@ struct MapSettingsForm: View { .pickerStyle(SegmentedPickerStyle()) .padding(.top, 5) .padding(.bottom, 5) - .onChange(of: mapLayer) { newMapLayer in + .onChange(of: mapLayer) { _, newMapLayer in UserDefaults.mapLayer = newMapLayer } if meshMap { @@ -53,7 +50,7 @@ struct MapSettingsForm: View { } .pickerStyle(DefaultPickerStyle()) } - .onChange(of: meshMapDistance) { newMeshMapDistance in + .onChange(of: meshMapDistance) { _, newMeshMapDistance in UserDefaults.meshMapDistance = newMeshMapDistance } Toggle(isOn: $waypoints) { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index 340117ac..1120e811 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -7,11 +7,8 @@ import SwiftUI import CoreLocation -#if canImport(MapKit) import MapKit -#endif -@available(iOS 17.0, macOS 14.0, *) struct NodeMapSwiftUI: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -83,7 +80,7 @@ struct NodeMapSwiftUI: View { } .sheet(isPresented: $isEditingSettings) { MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap) - .onChange(of: (selectedMapLayer)) { newMapLayer in + .onChange(of: (selectedMapLayer)) { _, newMapLayer in switch selectedMapLayer { case .standard: UserDefaults.mapLayer = newMapLayer diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift index 2bbeaea1..5bde5a14 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionAltitudeChart.swift @@ -7,16 +7,13 @@ import SwiftUI import Charts -#if canImport(MapKit) import MapKit -#endif struct PositionAltitude { let time: Date var altitude: Measurement } -@available(iOS 17.0, macOS 14.0, *) struct PositionAltitudeChart: View { @Environment(\.dismiss) private var dismiss @ObservedObject var node: NodeInfoEntity diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index 3f0a528b..fb019e0b 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -8,7 +8,6 @@ import SwiftUI import MapKit -@available(iOS 17.0, macOS 14.0, *) struct PositionPopover: View { @ObservedObject var locationsHandler = LocationsHandler.shared @@ -96,7 +95,6 @@ struct PositionPopover: View { } /// Altitude Label { - let formatter = MeasurementFormatter() let distanceInMeters = Measurement(value: Double(position.altitude), unit: UnitLength.meters) let distanceInFeet = distanceInMeters.converted(to: UnitLength.feet) if Locale.current.measurementSystem == .metric { @@ -215,20 +213,12 @@ struct PositionPopover: View { .padding(.bottom) } if position.nodePosition?.hasDetectionSensorMetrics ?? false { - if #available(iOS 17.0, macOS 14.0, *) { - Image(systemName: "sensor.fill") - .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) - .symbolRenderingMode(.hierarchical) - .foregroundColor(.accentColor) - .font(.largeTitle) - .padding(.bottom) - } else { - Image(systemName: "sensor.fill") - .symbolRenderingMode(.hierarchical) - .foregroundColor(.accentColor) - .font(.largeTitle) - .padding(.bottom) - } + Image(systemName: "sensor.fill") + .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) + .symbolRenderingMode(.hierarchical) + .foregroundColor(.accentColor) + .font(.largeTitle) + .padding(.bottom) } BatteryGauge(node: position.nodePosition!) } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 4261b0cc..71d62590 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -65,14 +65,14 @@ struct WaypointForm: View { axis: .vertical ) .foregroundColor(Color.gray) - .onChange(of: name, perform: { _ in + .onChange(of: name) { var totalBytes = name.utf8.count // Only mess with the value if it is too big while totalBytes > 30 { name = String(name.dropLast()) totalBytes = name.utf8.count } - }) + } } HStack { Text("Description") @@ -83,14 +83,14 @@ struct WaypointForm: View { axis: .vertical ) .foregroundColor(Color.gray) - .onChange(of: description, perform: { _ in + .onChange(of: description) { var totalBytes = description.utf8.count // Only mess with the value if it is too big while totalBytes > 100 { description = String(description.dropLast()) totalBytes = description.utf8.count } - }) + } } HStack { Text("Icon") @@ -98,7 +98,7 @@ struct WaypointForm: View { EmojiOnlyTextField(text: $icon, placeholder: "Select an emoji") .font(.title) .focused($iconIsFocused) - .onChange(of: icon) { value in + .onChange(of: icon) { _, value in // If you have anything other than emojis in your string make it empty if !value.onlyEmojis() { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 11defc79..a9f7bcfe 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -221,11 +221,7 @@ struct NodeDetail: View { .disabled(!node.hasDeviceMetrics) NavigationLink { - if #available (iOS 17, macOS 14, *) { - NodeMapSwiftUI(node: node, showUserLocation: connectedNode?.num ?? 0 == node.num) - } else { - NodeMapMapkit(node: node) - } + NodeMapSwiftUI(node: node, showUserLocation: connectedNode?.num ?? 0 == node.num) } label: { Label { Text("Node Map") @@ -260,19 +256,17 @@ struct NodeDetail: View { } .disabled(!node.hasEnvironmentMetrics) - if #available(iOS 17.0, macOS 14.0, *) { - NavigationLink { - TraceRouteLog(node: node) - } label: { - Label { - Text("Trace Route Log") - } icon: { - Image(systemName: "signpost.right.and.left") - .symbolRenderingMode(.multicolor) - } + NavigationLink { + TraceRouteLog(node: node) + } label: { + Label { + Text("Trace Route Log") + } icon: { + Image(systemName: "signpost.right.and.left") + .symbolRenderingMode(.multicolor) } - .disabled(node.traceRoutes?.count ?? 0 == 0) } + .disabled(node.traceRoutes?.count ?? 0 == 0) NavigationLink { DetectionSensorLog(node: node) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 481666a1..66f58a07 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -101,36 +101,9 @@ struct NodeListItem: View { if node.positions?.count ?? 0 > 0 && connectedNode != node.num { HStack { 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(.multicolor) - .frame(width: 30) - DistanceText(meters: metersAway) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) - let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord) - let headingDegrees = Angle.degrees(trueBearing) - Image(systemName: "location.north") - .font(.callout) - .symbolRenderingMode(.multicolor) - .clipShape(Circle()) - .rotationEffect(headingDegrees) - let heading = Measurement(value: trueBearing, unit: UnitAngle.degrees) - Text("\(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))") - .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 { + 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") @@ -139,7 +112,7 @@ struct NodeListItem: View { .frame(width: 30) DistanceText(meters: metersAway) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.secondary) + .foregroundColor(.gray) let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord) let headingDegrees = Angle.degrees(trueBearing) Image(systemName: "location.north") @@ -211,13 +184,11 @@ struct NodeListItem: View { .font(.callout) .frame(width: 30) } - if #available(iOS 17.0, macOS 14.0, *) { - if node.hasTraceRoutes { - Image(systemName: "signpost.right.and.left") - .symbolRenderingMode(.hierarchical) - .font(.callout) - .frame(width: 30) - } + if node.hasTraceRoutes { + Image(systemName: "signpost.right.and.left") + .symbolRenderingMode(.hierarchical) + .font(.callout) + .frame(width: 30) } } } diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 5dd6e19a..447f6fdd 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -10,11 +10,8 @@ import CoreData import CoreLocation import Foundation import OSLog -#if canImport(MapKit) import MapKit -#endif -@available(iOS 17.0, macOS 14.0, *) struct MeshMap: View { @Environment(\.managedObjectContext) var context @@ -142,7 +139,7 @@ struct MeshMap: View { guard case .map = router.navigationState.selectedTab else { return } // TODO: handle deep link for waypoints } - .onChange(of: selectedMapLayer) { newMapLayer in + .onChange(of: selectedMapLayer) { _, newMapLayer in switch selectedMapLayer { case .standard: UserDefaults.mapLayer = newMapLayer @@ -186,17 +183,6 @@ struct MeshMap: View { .tint(Color(UIColor.secondarySystemBackground)) .foregroundColor(.accentColor) .buttonStyle(.borderedProminent) -// Button(action: { -// withAnimation { -// editingFilters = !editingFilters -// } -// }) { -// Image(systemName: !editingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill") -// .padding(.vertical, 5) -// } -// .tint(Color(UIColor.secondarySystemBackground)) -// .foregroundColor(.accentColor) -// .buttonStyle(.borderedProminent) } .controlSize(.regular) .padding(5) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index f81a34cf..756f1179 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -274,26 +274,18 @@ struct NodeList: View { ) } } else { - if #available (iOS 17, *) { - ContentUnavailableView("select.node", systemImage: "flipphone") - } else { - Text("select.node") - } + ContentUnavailableView("select.node", systemImage: "flipphone") } } detail: { - if #available (iOS 17, *) { - ContentUnavailableView("", systemImage: "line.3.horizontal") - } else { - Text("Select something to view") - } + ContentUnavailableView("", systemImage: "line.3.horizontal") } .navigationSplitViewStyle(.balanced) - .onChange(of: searchText) { _ in + .onChange(of: searchText) { Task { await searchNodeList() } } - .onChange(of: viaLora) { _ in + .onChange(of: viaLora) { if !viaLora && !viaMqtt { viaMqtt = true } @@ -301,7 +293,7 @@ struct NodeList: View { await searchNodeList() } } - .onChange(of: viaMqtt) { _ in + .onChange(of: viaMqtt) { if !viaLora && !viaMqtt { viaLora = true } @@ -309,32 +301,32 @@ struct NodeList: View { await searchNodeList() } } - .onChange(of: [boolFilters]) { _ in + .onChange(of: [boolFilters]) { Task { await searchNodeList() } } - .onChange(of: [deviceRoles]) { _ in + .onChange(of: [deviceRoles]) { Task { await searchNodeList() } } - .onChange(of: hopsAway) { _ in + .onChange(of: hopsAway) { Task { await searchNodeList() } } - .onChange(of: maxDistance) { _ in + .onChange(of: maxDistance) { Task { await searchNodeList() } } - .onChange(of: distanceFilter) { _ in + .onChange(of: distanceFilter) { Task { await searchNodeList() } } - .onChange(of: router.navigationState) { _ in + .onChange(of: router.navigationState) { if let selected = router.navigationState.nodeListSelectedNodeNum { self.selectedNode = getNodeInfo(id: selected, context: context) } else { diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 53b0aae3..9988f237 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -26,8 +26,6 @@ struct NodeMap: View { @State var selectedOverlayServer: MapOverlayServer = UserDefaults.mapOverlayServer @State var mapTilesAboveLabels: Bool = UserDefaults.mapTilesAboveLabels let fromDate: NSDate = Calendar.current.date(byAdding: .month, value: -1, to: Date())! as NSDate -// @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], -// predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.date(byAdding: .day, value: -7, to: Date())! as NSDate), animation: .none) @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "nodePosition != nil", Calendar.current.date(byAdding: .day, value: -7, to: Date())! as NSDate), animation: .none) private var positions: FetchedResults @@ -97,7 +95,7 @@ struct NodeMap: View { } } .pickerStyle(SegmentedPickerStyle()) - .onChange(of: (selectedMapLayer)) { newMapLayer in + .onChange(of: selectedMapLayer) { _, newMapLayer in UserDefaults.mapLayer = newMapLayer } .padding(.top, 5) @@ -144,10 +142,10 @@ struct NodeMap: View { .font(.footnote) } } - .pickerStyle(DefaultPickerStyle()) - .onChange(of: (selectedOverlayServer)) { newSelectedOverlayServer in - UserDefaults.mapOverlayServer = newSelectedOverlayServer - } + .pickerStyle(DefaultPickerStyle()) + .onChange(of: (selectedOverlayServer)) { _, newSelectedOverlayServer in + UserDefaults.mapOverlayServer = newSelectedOverlayServer + } Text(LocalizedStringKey(selectedOverlayServer.attribution)) .font(.footnote) .foregroundColor(.gray) @@ -160,7 +158,7 @@ struct NodeMap: View { Text("Enable Offline Maps") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .onChange(of: enableOfflineMaps) { newEnableOfflineMaps in + .onChange(of: enableOfflineMaps) { _, newEnableOfflineMaps in UserDefaults.enableOfflineMaps = newEnableOfflineMaps if !enableOfflineMaps { if self.selectedMapLayer == .offline { @@ -176,10 +174,10 @@ struct NodeMap: View { Text(tsl.description) } } - .pickerStyle(DefaultPickerStyle()) - .onChange(of: (selectedTileServer)) { newSelectedTileServer in - UserDefaults.mapTileServer = newSelectedTileServer - } + .pickerStyle(DefaultPickerStyle()) + .onChange(of: (selectedTileServer)) { _, newSelectedTileServer in + UserDefaults.mapTileServer = newSelectedTileServer + } Text("Attribution:") .fontWeight(.semibold) .font(.footnote) diff --git a/Meshtastic/Views/Nodes/PaxCounterLog.swift b/Meshtastic/Views/Nodes/PaxCounterLog.swift index 6d764c63..bb03aa9b 100644 --- a/Meshtastic/Views/Nodes/PaxCounterLog.swift +++ b/Meshtastic/Views/Nodes/PaxCounterLog.swift @@ -196,11 +196,7 @@ struct PaxCounterLog: View { .padding(.trailing) } } else { - if #available (iOS 17, *) { - ContentUnavailableView("paxcounter.content.unavailable", systemImage: "slash.circle") - } else { - Text("paxcounter.content.unavailable") - } + ContentUnavailableView("paxcounter.content.unavailable", systemImage: "slash.circle") } } .navigationTitle("paxcounter.log") diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 43354830..3abcb791 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -166,11 +166,7 @@ struct PositionLog: View { ) } else { - if #available (iOS 17, *) { - ContentUnavailableView("No Positions", systemImage: "mappin.slash") - } else { - Text("No Positions") - } + ContentUnavailableView("No Positions", systemImage: "mappin.slash") } } .navigationTitle("Position Log \(node.positions?.count ?? 0) Points") diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 22d190d5..398a12b0 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -8,11 +8,8 @@ import SwiftUI import CoreData import OSLog -#if canImport(MapKit) import MapKit -#endif -@available(iOS 17.0, macOS 14.0, *) struct TraceRouteLog: View { private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @ObservedObject var locationsHandler = LocationsHandler.shared diff --git a/Meshtastic/Views/Settings/AppData.swift b/Meshtastic/Views/Settings/AppData.swift index bed0a948..3f1ebf0e 100644 --- a/Meshtastic/Views/Settings/AppData.swift +++ b/Meshtastic/Views/Settings/AppData.swift @@ -22,9 +22,7 @@ struct AppData: View { VStack { Section(header: Text("phone.gps")) { - if #available(iOS 17.0, macOS 14.0, *) { - GPSStatus() - } + GPSStatus() } Divider() Button(action: { @@ -69,8 +67,6 @@ struct AppData: View { let container = NSPersistentContainer(name: "Meshtastic") do { try container.restorePersistentStore(from: file.absoluteURL) - let request = MyInfoEntity.fetchRequest() - try context.fetch(request) UserDefaults.preferredPeripheralId = "" UserDefaults.preferredPeripheralNum = Int(file.pathComponents[(idiom == .phone || idiom == .pad) ? 9 : 10]) ?? 0 Logger.data.notice("🗂️ Restored a core data backup to backup/\(UserDefaults.preferredPeripheralNum, privacy: .public)") diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index fd3db810..b96ed806 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -8,8 +8,6 @@ import SwiftUI import OSLog -/// Needed for TableColumnForEach -@available(iOS 17.0, macOS 14.0, *) struct AppLog: View { @State private var logs: [OSLogEntryLog] = [] @@ -133,25 +131,25 @@ struct AppLog: View { logs.sort(using: sortOrder) } } - .onChange(of: searchText) { _ in + .onChange(of: searchText) { Task { await logs = searchAppLogs() logs.sort(using: sortOrder) } } - .onChange(of: [categories]) { _ in + .onChange(of: [categories]) { Task { await logs = searchAppLogs() logs.sort(using: sortOrder) } } - .onChange(of: [levels]) { _ in + .onChange(of: [levels]) { Task { await logs = searchAppLogs() logs.sort(using: sortOrder) } } - .onChange(of: selection) { newSelection in + .onChange(of: selection) { _, newSelection in presentingErrorDetails = true let log = logs.first { $0.id == newSelection @@ -216,7 +214,6 @@ struct AppLog: View { } } -@available(iOS 17.0, macOS 14.0, *) extension AppLog { @MainActor private func searchAppLogs() async -> [OSLogEntryLog] { diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index ca55b95d..1a28a6be 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -10,9 +10,7 @@ import MapKit import MeshtasticProtobufs import OSLog import SwiftUI -#if canImport(TipKit) import TipKit -#endif func generateChannelKey(size: Int) -> String { var keyData = Data(count: size) @@ -62,9 +60,7 @@ struct Channels: View { VStack { List { - if #available(iOS 17.0, macOS 14.0, *) { - TipView(CreateChannelsTip(), arrowEdge: .bottom) - } + TipView(CreateChannelsTip(), arrowEdge: .bottom) if node != nil && node?.myInfo != nil { ForEach(node?.myInfo?.channels?.array as? [ChannelEntity] ?? [], id: \.self) { (channel: ChannelEntity) in Button(action: { diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 72e17cef..72d6f58b 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -6,9 +6,7 @@ // import SwiftUI -#if canImport(MapKit) import MapKit -#endif struct ChannelForm: View { @@ -41,7 +39,7 @@ struct ChannelForm: View { .disableAutocorrection(true) .keyboardType(.alphabet) .foregroundColor(Color.gray) - .onChange(of: channelName, perform: { _ in + .onChange(of: channelName) { channelName = channelName.replacing(" ", with: "") var totalBytes = channelName.utf8.count // Only mess with the value if it is too big @@ -50,7 +48,7 @@ struct ChannelForm: View { totalBytes = channelName.utf8.count } hasChanges = true - }) + } } HStack { Picker("Key Size", selection: $channelKeySize) { @@ -99,7 +97,7 @@ struct ChannelForm: View { , lineWidth: 2.0) ) - .onChange(of: channelKey, perform: { _ in + .onChange(of: channelKey) { let tempKey = Data(base64Encoded: channelKey) ?? Data() if tempKey.count == channelKeySize || channelKeySize == -1 { @@ -108,7 +106,7 @@ struct ChannelForm: View { hasValidKey = false } hasChanges = true - }) + } .disabled(channelKeySize <= 0) } HStack { @@ -148,7 +146,7 @@ struct ChannelForm: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .disabled(!supportedVersion) .listRowSeparator(.visible) - .onChange(of: preciseLocation) { pl in + .onChange(of: preciseLocation) { _, pl in if pl == false { positionPrecision = 14 } @@ -186,10 +184,10 @@ struct ChannelForm: View { .listRowSeparator(.visible) } } - .onChange(of: channelName) { _ in + .onChange(of: channelName) { hasChanges = true } - .onChange(of: channelKeySize) { _ in + .onChange(of: channelKeySize) { if channelKeySize == -1 { channelKey = "AQ==" } else { @@ -198,10 +196,10 @@ struct ChannelForm: View { } hasChanges = true } - .onChange(of: channelKey) { _ in + .onChange(of: channelKey) { hasChanges = true } - .onChange(of: channelKeySize) { _ in + .onChange(of: channelKeySize) { if channelKeySize == -1 { if channelRole == 0 { preciseLocation = false @@ -209,10 +207,10 @@ struct ChannelForm: View { channelKey = "AQ==" } } - .onChange(of: channelRole) { _ in + .onChange(of: channelRole) { hasChanges = true } - .onChange(of: preciseLocation) { loc in + .onChange(of: preciseLocation) { _, loc in if loc == true { if channelKey == "AQ==" { preciseLocation = false @@ -225,10 +223,10 @@ struct ChannelForm: View { } hasChanges = true } - .onChange(of: positionPrecision) { _ in + .onChange(of: positionPrecision) { hasChanges = true } - .onChange(of: positionsEnabled) { pe in + .onChange(of: positionsEnabled) { _, pe in if pe { if positionPrecision == 0 { positionPrecision = 14 @@ -238,10 +236,10 @@ struct ChannelForm: View { } hasChanges = true } - .onChange(of: uplink) { _ in + .onChange(of: uplink) { hasChanges = true } - .onChange(of: downlink) { _ in + .onChange(of: downlink) { hasChanges = true } .onFirstAppear { diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 46211dac..37f989a5 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -45,7 +45,7 @@ struct BluetoothConfig: View { Label("bluetooth.mode.fixedpin", systemImage: "wallet.pass") TextField("bluetooth.mode.fixedpin", text: $fixedPin) .foregroundColor(.gray) - .onChange(of: fixedPin, perform: { _ in + .onChange(of: fixedPin) { // Don't let the first character be 0 because it will get stripped when saving a UInt32 if fixedPin.first == "0" { fixedPin = fixedPin.replacing("0", with: "") @@ -59,7 +59,7 @@ struct BluetoothConfig: View { } else if fixedPin.utf8.count < pinLength { shortPin = true } - }) + } .foregroundColor(.gray) } .keyboardType(.decimalPad) @@ -121,14 +121,14 @@ struct BluetoothConfig: View { } } } - .onChange(of: enabled) { - if $0 != node?.bluetoothConfig?.enabled { hasChanges = true } + .onChange(of: enabled) { oldEnabled, newEnabled in + if oldEnabled != newEnabled && newEnabled != node?.bluetoothConfig?.enabled { hasChanges = true } } - .onChange(of: mode) { - if $0 != node?.bluetoothConfig?.mode ?? -1 { hasChanges = true } + .onChange(of: mode) { oldNode, newNode in + if oldNode != newNode && newNode != node?.bluetoothConfig?.mode ?? -1 { hasChanges = true } } - .onChange(of: fixedPin) { newFixedPin in - if newFixedPin != String(node?.bluetoothConfig?.fixedPin ?? -1) { hasChanges = true } + .onChange(of: fixedPin) { oldFixedPin, newFixedPin in + if oldFixedPin != newFixedPin && newFixedPin != String(node?.bluetoothConfig?.fixedPin ?? -1) { hasChanges = true } } } func setBluetoothValues() { diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index f9efd066..f5fca283 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -23,11 +23,11 @@ struct DeviceConfig: View { @State var deviceRole = 0 @State var buzzerGPIO = 0 @State var buttonGPIO = 0 - @State var serialEnabled = true @State var rebroadcastMode = 0 @State var nodeInfoBroadcastSecs = 10800 @State var doubleTapAsButtonPress = false @State var ledHeartbeatEnabled = true + @State var tripleClickAsAdHocPing = true @State var tzdef = "" var body: some View { @@ -77,6 +77,12 @@ struct DeviceConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Toggle(isOn: $tripleClickAsAdHocPing) { + Label("Triple Click Ad Hoc Ping", systemImage: "map.pin") + Text("Send a position on the primary channel when the user button is triple clicked.") + } + .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.") @@ -84,23 +90,19 @@ struct DeviceConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } Section(header: Text("Debug")) { - Toggle(isOn: $serialEnabled) { - Label("Serial Console", systemImage: "terminal") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) VStack(alignment: .leading) { HStack { Label("Time Zone", systemImage: "clock.badge.exclamationmark") TextField("Time Zone", text: $tzdef, axis: .vertical) .foregroundColor(.gray) - .onChange(of: tzdef, perform: { _ in + .onChange(of: tzdef) { var totalBytes = tzdef.utf8.count // Only mess with the value if it is too big while totalBytes > 63 { tzdef = String(tzdef.dropLast()) totalBytes = tzdef.utf8.count } - }) + } .foregroundColor(.gray) } .keyboardType(.default) @@ -194,12 +196,12 @@ struct DeviceConfig: View { if connectedNode != nil { var dc = Config.DeviceConfig() dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue() - dc.serialEnabled = serialEnabled dc.buttonGpio = UInt32(buttonGPIO) dc.buzzerGpio = UInt32(buzzerGPIO) dc.rebroadcastMode = RebroadcastModes(rawValue: rebroadcastMode)?.protoEnumValue() ?? RebroadcastModes.all.protoEnumValue() dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs) dc.doubleTapAsButtonPress = doubleTapAsButtonPress + dc.disableTripleClick = !tripleClickAsAdHocPing dc.tzdef = tzdef dc.ledHeartbeatDisabled = !ledHeartbeatEnabled let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) @@ -247,39 +249,36 @@ struct DeviceConfig: View { } } } - .onChange(of: deviceRole) { - if $0 != node?.deviceConfig?.role ?? -1 { hasChanges = true } + .onChange(of: deviceRole) { oldRole, newRole in + if oldRole != newRole && newRole != node?.deviceConfig?.role ?? -1 { hasChanges = true } } - .onChange(of: serialEnabled) { - if $0 != node?.deviceConfig?.serialEnabled { hasChanges = true } + .onChange(of: buttonGPIO) { oldButtonGPIO, newButtonGPIO in + if oldButtonGPIO != newButtonGPIO && newButtonGPIO != node?.deviceConfig?.buttonGpio ?? -1 { hasChanges = true } } - .onChange(of: buttonGPIO) { newButtonGPIO in - if newButtonGPIO != node?.deviceConfig?.buttonGpio ?? -1 { hasChanges = true } + .onChange(of: buzzerGPIO) { oldBuzzerGPIO, newBuzzerGPIO in + if oldBuzzerGPIO != newBuzzerGPIO && newBuzzerGPIO != node?.deviceConfig?.buzzerGpio ?? -1 { hasChanges = true } } - .onChange(of: buzzerGPIO) { newBuzzerGPIO in - if newBuzzerGPIO != node?.deviceConfig?.buttonGpio ?? -1 { hasChanges = true } + .onChange(of: rebroadcastMode) { oldRebroadcastMode, newRebroadcastMode in + if oldRebroadcastMode != newRebroadcastMode && newRebroadcastMode != node?.deviceConfig?.rebroadcastMode ?? -1 { hasChanges = true } } - .onChange(of: rebroadcastMode) { newRebroadcastMode in - if newRebroadcastMode != node?.deviceConfig?.rebroadcastMode ?? -1 { hasChanges = true } + .onChange(of: nodeInfoBroadcastSecs) { oldNodeInfoBroadcastSecs, newNodeInfoBroadcastSecs in + if oldNodeInfoBroadcastSecs != newNodeInfoBroadcastSecs && newNodeInfoBroadcastSecs != node?.deviceConfig?.nodeInfoBroadcastSecs ?? -1 { hasChanges = true } } - .onChange(of: nodeInfoBroadcastSecs) { newNodeInfoBroadcastSecs in - if newNodeInfoBroadcastSecs != node?.deviceConfig?.nodeInfoBroadcastSecs ?? -1 { hasChanges = true } + .onChange(of: doubleTapAsButtonPress) { oldDoubleTapAsButtonPress, newDoubleTapAsButtonPress in + if oldDoubleTapAsButtonPress != newDoubleTapAsButtonPress && newDoubleTapAsButtonPress != node?.deviceConfig?.doubleTapAsButtonPress ?? false { hasChanges = true } } - .onChange(of: doubleTapAsButtonPress) { - if $0 != node?.deviceConfig?.doubleTapAsButtonPress { hasChanges = true } + .onChange(of: tripleClickAsAdHocPing) { oldTripleClickAsAdHocPing, newTripleClickAsAdHocPing in + if oldTripleClickAsAdHocPing != newTripleClickAsAdHocPing && newTripleClickAsAdHocPing != node?.deviceConfig?.tripleClickAsAdHocPing ?? false { hasChanges = true } } - .onChange(of: tzdef) { newTzdef in - if newTzdef != node?.deviceConfig?.tzdef { hasChanges = true } + .onChange(of: tzdef) { oldTzdef, newTzdef in + if oldTzdef != newTzdef && newTzdef != node?.deviceConfig?.tzdef { hasChanges = true } } - .onChange(of: ledHeartbeatEnabled) { newLedHeartbeatEnabled in - if node != nil && node?.deviceConfig != nil { - if newLedHeartbeatEnabled != node!.deviceConfig!.ledHeartbeatEnabled { hasChanges = true } - } + .onChange(of: ledHeartbeatEnabled) { oldLedHeartbeatEnabled, newLedHeartbeatEnabled in + if oldLedHeartbeatEnabled != newLedHeartbeatEnabled && newLedHeartbeatEnabled != node?.deviceConfig?.ledHeartbeatEnabled ?? false { hasChanges = true } } } func setDeviceValues() { self.deviceRole = Int(node?.deviceConfig?.role ?? 0) - self.serialEnabled = (node?.deviceConfig?.serialEnabled ?? true) self.buttonGPIO = Int(node?.deviceConfig?.buttonGpio ?? 0) self.buzzerGPIO = Int(node?.deviceConfig?.buzzerGpio ?? 0) self.rebroadcastMode = Int(node?.deviceConfig?.rebroadcastMode ?? 0) @@ -288,6 +287,7 @@ struct DeviceConfig: View { nodeInfoBroadcastSecs = 3600 } self.doubleTapAsButtonPress = node?.deviceConfig?.doubleTapAsButtonPress ?? false + self.tripleClickAsAdHocPing = node?.deviceConfig?.tripleClickAsAdHocPing ?? false self.ledHeartbeatEnabled = node?.deviceConfig?.ledHeartbeatEnabled ?? true self.tzdef = node?.deviceConfig?.tzdef ?? "" if self.tzdef.isEmpty { diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 1e9781a6..07975419 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -184,32 +184,32 @@ struct DisplayConfig: View { } } } - .onChange(of: screenOnSeconds) { newScreenSecs in - if newScreenSecs != node?.displayConfig?.screenOnSeconds ?? -1 { hasChanges = true } + .onChange(of: screenOnSeconds) { oldScreenSecs, newScreenSecs in + if oldScreenSecs != newScreenSecs && newScreenSecs != node?.displayConfig?.screenOnSeconds ?? -1 { hasChanges = true } } - .onChange(of: screenCarouselInterval) { newCarouselSecs in - if newCarouselSecs != node?.displayConfig?.screenCarouselInterval ?? -1 { hasChanges = true } + .onChange(of: screenCarouselInterval) { oldCarouselSecs, newCarouselSecs in + if oldCarouselSecs != newCarouselSecs && newCarouselSecs != node?.displayConfig?.screenCarouselInterval ?? -1 { hasChanges = true } } - .onChange(of: compassNorthTop) { - if $0 != node?.displayConfig?.compassNorthTop { hasChanges = true } + .onChange(of: compassNorthTop) { oldCompassNorthTop, newCompassNorthTop in + if oldCompassNorthTop != newCompassNorthTop && newCompassNorthTop != node?.displayConfig?.compassNorthTop { hasChanges = true } } - .onChange(of: wakeOnTapOrMotion) { - if $0 != node?.displayConfig?.wakeOnTapOrMotion { hasChanges = true } + .onChange(of: wakeOnTapOrMotion) { oldWakeOnTapOrMotion, newWakeOnTapOrMotion in + if oldWakeOnTapOrMotion != newWakeOnTapOrMotion && newWakeOnTapOrMotion != node?.displayConfig?.wakeOnTapOrMotion { hasChanges = true } } - .onChange(of: gpsFormat) { newGpsFormat in - if newGpsFormat != node?.displayConfig?.gpsFormat ?? -1 { hasChanges = true } + .onChange(of: gpsFormat) { oldGpsFormat, newGpsFormat in + if oldGpsFormat != newGpsFormat && newGpsFormat != node?.displayConfig?.gpsFormat ?? -1 { hasChanges = true } } - .onChange(of: flipScreen) { - if $0 != node?.displayConfig?.flipScreen { hasChanges = true } + .onChange(of: flipScreen) { oldFlipScreen, newFlipScreen in + if oldFlipScreen != newFlipScreen && newFlipScreen != node?.displayConfig?.flipScreen { hasChanges = true } } - .onChange(of: oledType) { newOledType in - if newOledType != node?.displayConfig?.oledType ?? -1 { hasChanges = true } + .onChange(of: oledType) { oldOledType, newOledType in + if oldOledType != newOledType && newOledType != node?.displayConfig?.oledType ?? -1 { hasChanges = true } } - .onChange(of: displayMode) { newDisplayMode in - if newDisplayMode != node?.displayConfig?.displayMode ?? -1 { hasChanges = true } + .onChange(of: displayMode) { oldDisplayMode, newDisplayMode in + if oldDisplayMode != newDisplayMode && newDisplayMode != node?.displayConfig?.displayMode ?? -1 { hasChanges = true } } - .onChange(of: units) { newUnits in - if newUnits != node?.displayConfig?.units ?? -1 { hasChanges = true } + .onChange(of: units) { oldUnits, newUnits in + if oldUnits != newUnits && newUnits != node?.displayConfig?.units ?? -1 { hasChanges = true } } } func setDisplayValues() { diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 13623ee3..4c12c598 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -259,47 +259,47 @@ struct LoRaConfig: View { } } } - .onChange(of: region) { newRegion in + .onChange(of: region) { _, newRegion in if newRegion != node?.loRaConfig?.regionCode ?? -1 { hasChanges = true } } - .onChange(of: usePreset) { - if $0 != node?.loRaConfig?.usePreset { hasChanges = true } + .onChange(of: usePreset) { _, newPreset in + if newPreset != node?.loRaConfig?.usePreset { hasChanges = true } } - .onChange(of: modemPreset) { newModemPreset in + .onChange(of: modemPreset) { _, newModemPreset in if newModemPreset != node?.loRaConfig?.modemPreset ?? -1 { hasChanges = true } } - .onChange(of: hopLimit) { newHopLimit in + .onChange(of: hopLimit) { _, newHopLimit in if newHopLimit != node?.loRaConfig?.hopLimit ?? -1 { hasChanges = true } } - .onChange(of: channelNum) { newChannelNum in + .onChange(of: channelNum) { _, newChannelNum in if newChannelNum != node?.loRaConfig?.channelNum ?? -1 { hasChanges = true } } - .onChange(of: bandwidth) { newBandwidth in + .onChange(of: bandwidth) { _, newBandwidth in if newBandwidth != node?.loRaConfig?.bandwidth ?? -1 { hasChanges = true } } - .onChange(of: codingRate) { newCodingRate in + .onChange(of: codingRate) { _, newCodingRate in if newCodingRate != node?.loRaConfig?.codingRate ?? -1 { hasChanges = true } } - .onChange(of: spreadFactor) { newSpreadFactor in + .onChange(of: spreadFactor) { _, newSpreadFactor in if newSpreadFactor != node?.loRaConfig?.spreadFactor ?? -1 { hasChanges = true } } - .onChange(of: rxBoostedGain) { - if $0 != node?.loRaConfig?.sx126xRxBoostedGain { hasChanges = true } + .onChange(of: rxBoostedGain) { _, newRxBoostedGain in + if newRxBoostedGain != node?.loRaConfig?.sx126xRxBoostedGain { hasChanges = true } } - .onChange(of: overrideFrequency) { newOverrideFrequency in + .onChange(of: overrideFrequency) { _, newOverrideFrequency in if newOverrideFrequency != node?.loRaConfig?.overrideFrequency { hasChanges = true } } - .onChange(of: txPower) { newTxPower in + .onChange(of: txPower) { _, newTxPower in if newTxPower != node?.loRaConfig?.txPower ?? -1 { hasChanges = true } } - .onChange(of: txEnabled) { - if $0 != node?.loRaConfig?.txEnabled { hasChanges = true } + .onChange(of: txEnabled) { _, newTxEnabled in + if newTxEnabled != node?.loRaConfig?.txEnabled { hasChanges = true } } - .onChange(of: ignoreMqtt) { - if $0 != node?.loRaConfig?.ignoreMqtt { hasChanges = true } + .onChange(of: ignoreMqtt) { _, newIgnoreMqtt in + if newIgnoreMqtt != node?.loRaConfig?.ignoreMqtt { hasChanges = true } } - .onChange(of: okToMqtt) { - if $0 != node?.loRaConfig?.okToMqtt { hasChanges = true } + .onChange(of: okToMqtt) { _, newOkToMqtt in + if newOkToMqtt != node?.loRaConfig?.okToMqtt { hasChanges = true } } } func setLoRaValues() { diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index fe3faa51..fc82a4ca 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -8,7 +8,6 @@ import MeshtasticProtobufs import SwiftUI import OSLog -@available(iOS 17.0, macOS 14.0, *) struct AmbientLightingConfig: View { @Environment(\.self) var environment @Environment(\.managedObjectContext) var context @@ -107,20 +106,14 @@ struct AmbientLightingConfig: View { } } } - .onChange(of: ledState) { - if let val = node?.ambientLightingConfig?.ledState { - hasChanges = $0 != val - } + .onChange(of: ledState) { _, newLedState in + if newLedState != node?.ambientLightingConfig?.ledState { hasChanges = true } } - .onChange(of: current) { - if let val = node?.ambientLightingConfig?.current { - hasChanges = $0 != val - } + .onChange(of: current) { _, newCurrent in + if newCurrent != node?.ambientLightingConfig?.current ?? 10 { hasChanges = true } } - .onChange(of: color) { c in - if color != c { - hasChanges = true - } + .onChange(of: color) { oldColor, newColor in + if oldColor != newColor { hasChanges = true } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 568be9da..4f580b2a 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -71,8 +71,7 @@ struct CannedMessagesConfig: View { .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) - .onChange(of: messages, perform: { _ in - + .onChange(of: messages) { var totalBytes = messages.utf8.count // Only mess with the value if it is too big while totalBytes > 198 { @@ -80,7 +79,7 @@ struct CannedMessagesConfig: View { totalBytes = messages.utf8.count } hasMessagesChanges = true - }) + } .foregroundColor(.gray) } .keyboardType(.default) @@ -255,7 +254,7 @@ struct CannedMessagesConfig: View { } } } - .onChange(of: configPreset) { newPreset in + .onChange(of: configPreset) { _, newPreset in if newPreset == 1 { @@ -284,55 +283,35 @@ struct CannedMessagesConfig: View { hasChanges = true } - .onChange(of: enabled) { - if let val = node?.cannedMessageConfig?.enabled { - hasChanges = $0 != val - } + .onChange(of: enabled) { _, newEnabled in + if newEnabled != node?.cannedMessageConfig?.enabled { hasChanges = true } } - .onChange(of: sendBell) { - if let val = node?.cannedMessageConfig?.sendBell { - hasChanges = $0 != val - } + .onChange(of: sendBell) { _, newSendBell in + if newSendBell != node?.cannedMessageConfig?.sendBell { hasChanges = true } } - .onChange(of: rotary1Enabled) { - if let val = node?.cannedMessageConfig?.rotary1Enabled { - hasChanges = $0 != val - } + .onChange(of: rotary1Enabled) { _, newRotary1Enabled in + if newRotary1Enabled != node?.cannedMessageConfig?.rotary1Enabled { hasChanges = true } } - .onChange(of: updown1Enabled) { - if let val = node?.cannedMessageConfig?.updown1Enabled { - hasChanges = $0 != val - } + .onChange(of: updown1Enabled) { _, newUpdown1Enabled in + if newUpdown1Enabled != node?.cannedMessageConfig?.updown1Enabled { hasChanges = true } } - .onChange(of: inputbrokerPinA) { newPinA in - if node != nil && node!.cannedMessageConfig != nil { - if newPinA != node!.cannedMessageConfig!.inputbrokerPinA { hasChanges = true } - } + .onChange(of: inputbrokerPinA) { _, newPinA in + if newPinA != node?.cannedMessageConfig?.inputbrokerPinA ?? -1 { hasChanges = true } } - .onChange(of: inputbrokerPinB) { newPinB in - if node != nil && node!.cannedMessageConfig != nil { - if newPinB != node!.cannedMessageConfig!.inputbrokerPinB { hasChanges = true } - } + .onChange(of: inputbrokerPinB) { _, newPinB in + if newPinB != node?.cannedMessageConfig?.inputbrokerPinB ?? -1 { hasChanges = true } } - .onChange(of: inputbrokerPinPress) { newPinPress in - if node != nil && node!.cannedMessageConfig != nil { - if newPinPress != node!.cannedMessageConfig!.inputbrokerPinPress { hasChanges = true } - } + .onChange(of: inputbrokerPinPress) { _, newPinPress in + if newPinPress != node?.cannedMessageConfig?.inputbrokerPinPress ?? -1 { hasChanges = true } } - .onChange(of: inputbrokerEventCw) { newKeyA in - if node != nil && node!.cannedMessageConfig != nil { - if newKeyA != node!.cannedMessageConfig!.inputbrokerEventCw { hasChanges = true } - } + .onChange(of: inputbrokerEventCw) { _, newKeyA in + if newKeyA != node?.cannedMessageConfig?.inputbrokerEventCw ?? -1 { hasChanges = true } } - .onChange(of: inputbrokerEventCcw) { newKeyB in - if node != nil && node!.cannedMessageConfig != nil { - if newKeyB != node!.cannedMessageConfig!.inputbrokerEventCcw { hasChanges = true } - } + .onChange(of: inputbrokerEventCcw) { _, newKeyB in + if newKeyB != node?.cannedMessageConfig?.inputbrokerEventCcw ?? -1 { hasChanges = true } } - .onChange(of: inputbrokerEventPress) { newKeyPress in - if node != nil && node!.cannedMessageConfig != nil { - if newKeyPress != node!.cannedMessageConfig!.inputbrokerEventPress { hasChanges = true } - } + .onChange(of: inputbrokerEventPress) { _, newKeyPress in + if newKeyPress != node?.cannedMessageConfig?.inputbrokerEventPress ?? -1 { hasChanges = true } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 6305dd98..eb50a80f 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -91,14 +91,14 @@ struct DetectionSensorConfig: View { .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) - .onChange(of: name, perform: { _ in + .onChange(of: name) { var totalBytes = name.utf8.count // Only mess with the value if it is too big while totalBytes > 20 { name = String(name.dropLast()) totalBytes = name.utf8.count } - }) + } } .listRowSeparator(.hidden) Text("Friendly name used to format message sent to mesh. Example: A name \"Motion\" would result in a message \"Motion detected\"") @@ -210,47 +210,31 @@ struct DetectionSensorConfig: View { } } } - .onChange(of: enabled) { - if let val = node?.detectionSensorConfig?.enabled { - hasChanges = $0 != val - } + .onChange(of: enabled) { _, newEnabled in + if newEnabled != node?.detectionSensorConfig?.enabled { hasChanges = true } } - .onChange(of: sendBell) { - if let val = node?.detectionSensorConfig?.sendBell { - hasChanges = $0 != val - } + .onChange(of: sendBell) { _, newSendBell in + if newSendBell != node?.detectionSensorConfig?.sendBell { hasChanges = true } } - .onChange(of: detectionTriggeredHigh) { newDetectionTriggeredHigh in - if node != nil && node?.detectionSensorConfig != nil { - if newDetectionTriggeredHigh != node!.detectionSensorConfig!.detectionTriggeredHigh { hasChanges = true } - } + .onChange(of: detectionTriggeredHigh) { _, newDetectionTriggeredHigh in + if newDetectionTriggeredHigh != node?.detectionSensorConfig?.detectionTriggeredHigh { hasChanges = true } } - .onChange(of: usePullup) { - if let val = node?.detectionSensorConfig?.usePullup { - hasChanges = $0 != val - } + .onChange(of: usePullup) { _, newUsePullup in + if newUsePullup != node?.detectionSensorConfig?.usePullup { hasChanges = true } } - .onChange(of: name) { newName in - if node != nil && node?.detectionSensorConfig != nil { - if newName != node!.detectionSensorConfig!.name { hasChanges = true } - } + .onChange(of: name) { _, newName in + if newName != node?.detectionSensorConfig?.name ?? "" { hasChanges = true } } - .onChange(of: monitorPin) { newMonitorPin in - if node != nil && node?.detectionSensorConfig != nil { - if newMonitorPin != node!.detectionSensorConfig!.monitorPin { hasChanges = true } - } + .onChange(of: monitorPin) { _, newMonitorPin in + if newMonitorPin != node?.detectionSensorConfig?.monitorPin ?? 0 { hasChanges = true } } - .onChange(of: minimumBroadcastSecs) { newMinimumBroadcastSecs in - if node != nil && node?.detectionSensorConfig != nil { - if newMinimumBroadcastSecs != node!.detectionSensorConfig!.minimumBroadcastSecs { hasChanges = true } - } + .onChange(of: minimumBroadcastSecs) { _, newMinimumBroadcastSecs in + if newMinimumBroadcastSecs != node?.detectionSensorConfig?.minimumBroadcastSecs ?? 0 { hasChanges = true } } - .onChange(of: stateBroadcastSecs) { newStateBroadcastSecs in - if node != nil && node?.detectionSensorConfig != nil { - if newStateBroadcastSecs != node!.detectionSensorConfig!.stateBroadcastSecs { hasChanges = true } - } + .onChange(of: stateBroadcastSecs) { _, newStateBroadcastSecs in + if newStateBroadcastSecs != node?.detectionSensorConfig?.stateBroadcastSecs ?? 0 { hasChanges = true } } - .onChange(of: detectionNotificationsEnabled) { newDetectionNotificationsEnabled in + .onChange(of: detectionNotificationsEnabled) { _, newDetectionNotificationsEnabled in UserDefaults.enableDetectionNotifications = newDetectionNotificationsEnabled } } diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 57c3a672..a0f14c7f 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -220,80 +220,50 @@ struct ExternalNotificationConfig: View { } } } - .onChange(of: enabled) { - if let val = node?.externalNotificationConfig?.enabled { - hasChanges = $0 != val - } + .onChange(of: enabled) { _, newEnabled in + if newEnabled != node?.externalNotificationConfig?.enabled { hasChanges = true } } - .onChange(of: alertBell) { - if let val = node?.externalNotificationConfig?.alertBell { - hasChanges = $0 != val - } + .onChange(of: alertBell) { _, newAlertBell in + if newAlertBell != node?.externalNotificationConfig?.alertBell { hasChanges = true } } - .onChange(of: alertBellBuzzer) { - if let val = node?.externalNotificationConfig?.alertBellBuzzer { - hasChanges = $0 != val - } + .onChange(of: alertBellBuzzer) { _, newAlertBellBuzzer in + if newAlertBellBuzzer != node?.externalNotificationConfig?.alertBellBuzzer { hasChanges = true } } - .onChange(of: alertBellVibra) { - if let val = node?.externalNotificationConfig?.alertBellVibra { - hasChanges = $0 != val - } + .onChange(of: alertBellVibra) { _, newAlertBellVibra in + if newAlertBellVibra != node?.externalNotificationConfig?.alertBellVibra { hasChanges = true } } - .onChange(of: alertMessage) { - if let val = node?.externalNotificationConfig?.alertMessage { - hasChanges = $0 != val - } + .onChange(of: alertMessage) { _, newAlertMessage in + if newAlertMessage != node?.externalNotificationConfig?.alertMessage { hasChanges = true } } - .onChange(of: alertMessageBuzzer) { - if let val = node?.externalNotificationConfig?.alertMessageBuzzer { - hasChanges = $0 != val - } + .onChange(of: alertMessageBuzzer) { _, newAlertMessageBuzzer in + if newAlertMessageBuzzer != node?.externalNotificationConfig?.alertMessageBuzzer { hasChanges = true } } - .onChange(of: alertMessageVibra) { - if let val = node?.externalNotificationConfig?.alertMessageVibra { - hasChanges = $0 != val - } + .onChange(of: alertMessageVibra) { _, newAlertMessageVibra in + if newAlertMessageVibra != node?.externalNotificationConfig?.alertMessageVibra { hasChanges = true } } - .onChange(of: active) { - if let val = node?.externalNotificationConfig?.active { - hasChanges = $0 != val - } + .onChange(of: active) { _, newActive in + if newActive != node?.externalNotificationConfig?.active { hasChanges = true } } - .onChange(of: output) { newOutput in - if node != nil && node!.externalNotificationConfig != nil { - if newOutput != node!.externalNotificationConfig!.output { hasChanges = true } - } + .onChange(of: output) { _, newOutput in + if newOutput != node?.externalNotificationConfig?.output ?? -1 { hasChanges = true } } - .onChange(of: output) { newOutputBuzzer in - if node != nil && node!.externalNotificationConfig != nil { - if newOutputBuzzer != node!.externalNotificationConfig!.outputBuzzer { hasChanges = true } - } + .onChange(of: output) { _, newOutputBuzzer in + if newOutputBuzzer != node?.externalNotificationConfig?.outputBuzzer ?? -1 { hasChanges = true } } - .onChange(of: output) { newOutputVibra in - if node != nil && node!.externalNotificationConfig != nil { - if newOutputVibra != node!.externalNotificationConfig!.outputVibra { hasChanges = true } - } + .onChange(of: output) { _, newOutputVibra in + if newOutputVibra != node?.externalNotificationConfig?.outputVibra ?? -1 { hasChanges = true } } - .onChange(of: outputMilliseconds) { newOutputMs in - if node != nil && node!.externalNotificationConfig != nil { - if newOutputMs != node!.externalNotificationConfig!.outputMilliseconds { hasChanges = true } - } + .onChange(of: outputMilliseconds) { _, newOutputMs in + if newOutputMs != node?.externalNotificationConfig?.outputMilliseconds ?? -1 { hasChanges = true } } - .onChange(of: usePWM) { - if let val = node?.externalNotificationConfig?.usePWM { - hasChanges = $0 != val - } + .onChange(of: usePWM) { _, newPWM in + if newPWM != node?.externalNotificationConfig?.usePWM { hasChanges = true } } - .onChange(of: nagTimeout) { newNagTimeout in - if node != nil && node!.externalNotificationConfig != nil { - if newNagTimeout != node!.externalNotificationConfig!.nagTimeout { hasChanges = true } - } + .onChange(of: nagTimeout) { _, newNagTimeout in + if newNagTimeout != node?.externalNotificationConfig?.nagTimeout ?? -1 { hasChanges = true } } - .onChange(of: useI2SAsBuzzer) { - if let val = node?.externalNotificationConfig?.useI2SAsBuzzer { - hasChanges = $0 != val - } + .onChange(of: useI2SAsBuzzer) { _, newUseI2SAsBuzzer in + if newUseI2SAsBuzzer != node?.externalNotificationConfig?.useI2SAsBuzzer { hasChanges = true } } } func setExternalNotificationValues() { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 6b39b649..224fd43c 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -123,14 +123,14 @@ struct MQTTConfig: View { Label("Root Topic", systemImage: "tree") TextField("Root Topic", text: $root) .foregroundColor(.gray) - .onChange(of: root, perform: { _ in + .onChange(of: root) { var totalBytes = root.utf8.count // Only mess with the value if it is too big while totalBytes > 30 { root = String(root.dropLast()) totalBytes = root.utf8.count } - }) + } .foregroundColor(.gray) } .keyboardType(.asciiCapable) @@ -162,7 +162,7 @@ struct MQTTConfig: View { .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) - .onChange(of: address, perform: { _ in + .onChange(of: address) { var totalBytes = address.utf8.count // Only mess with the value if it is too big while totalBytes > 62 { @@ -170,7 +170,7 @@ struct MQTTConfig: View { totalBytes = address.utf8.count } hasChanges = true - }) + } .keyboardType(.default) } .autocorrectionDisabled() @@ -181,7 +181,7 @@ struct MQTTConfig: View { .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) - .onChange(of: username, perform: { _ in + .onChange(of: username) { var totalBytes = username.utf8.count // Only mess with the value if it is too big while totalBytes > 62 { @@ -189,7 +189,7 @@ struct MQTTConfig: View { totalBytes = username.utf8.count } hasChanges = true - }) + } .foregroundColor(.gray) } .keyboardType(.default) @@ -200,7 +200,7 @@ struct MQTTConfig: View { .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) - .onChange(of: password, perform: { _ in + .onChange(of: password) { var totalBytes = password.utf8.count // Only mess with the value if it is too big while totalBytes > 62 { @@ -208,7 +208,7 @@ struct MQTTConfig: View { totalBytes = password.utf8.count } hasChanges = true - }) + } .foregroundColor(.gray) } .keyboardType(.default) @@ -262,55 +262,47 @@ struct MQTTConfig: View { ) } ) - .onChange(of: enabled) { - if $0 != node?.mqttConfig?.enabled { hasChanges = true } + .onChange(of: enabled) { _, newEnabled in + if newEnabled != node?.mqttConfig?.enabled { hasChanges = true } } - .onChange(of: proxyToClientEnabled) { newProxyToClientEnabled in + .onChange(of: proxyToClientEnabled) { _, newProxyToClientEnabled in if newProxyToClientEnabled { jsonEnabled = false } if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true } } - .onChange(of: address) { newAddress in - if node != nil && node?.mqttConfig != nil { - if newAddress != node!.mqttConfig!.address { hasChanges = true } - } + .onChange(of: address) { _, newAddress in + if newAddress != node?.mqttConfig?.address ?? "" { hasChanges = true } } .onChange(of: username) { newUsername in - if node != nil && node?.mqttConfig != nil { - if newUsername != node!.mqttConfig!.username { hasChanges = true } - } + if newUsername != node?.mqttConfig?.username ?? "" { hasChanges = true } } .onChange(of: password) { newPassword in - if node != nil && node?.mqttConfig != nil { - if newPassword != node!.mqttConfig!.password { hasChanges = true } - } + if newPassword != node?.mqttConfig?.password ?? "" { hasChanges = true } } .onChange(of: root) { newRoot in - if node != nil && node?.mqttConfig != nil { - if newRoot != node!.mqttConfig!.root { hasChanges = true } - } + if newRoot != node?.mqttConfig?.root ?? "" { hasChanges = true } } - .onChange(of: selectedTopic) { newSelectedTopic in + .onChange(of: selectedTopic) { _, newSelectedTopic in root = newSelectedTopic } - .onChange(of: encryptionEnabled) { - if $0 != node?.mqttConfig?.encryptionEnabled { hasChanges = true } + .onChange(of: encryptionEnabled) { _, newEncryptionEnabled in + if newEncryptionEnabled != node?.mqttConfig?.encryptionEnabled { hasChanges = true } } - .onChange(of: jsonEnabled) { newJsonEnabled in + .onChange(of: jsonEnabled) { _, newJsonEnabled in if newJsonEnabled { proxyToClientEnabled = false } if newJsonEnabled != node?.mqttConfig?.jsonEnabled { hasChanges = true } } - .onChange(of: tlsEnabled) { newTlsEnabled in + .onChange(of: tlsEnabled) { _, newTlsEnabled in if address.lowercased() == "mqtt.meshtastic.org" { tlsEnabled = false } else { if newTlsEnabled != node?.mqttConfig?.tlsEnabled { hasChanges = true } } } - .onChange(of: mqttConnected) { newMqttConnected in + .onChange(of: mqttConnected) { _, newMqttConnected in if newMqttConnected == false { if bleManager.mqttProxyConnected { bleManager.mqttManager.disconnect() @@ -321,13 +313,11 @@ struct MQTTConfig: View { } } } - .onChange(of: mapReportingEnabled) { - if $0 != node?.mqttConfig?.mapReportingEnabled { hasChanges = true } + .onChange(of: mapReportingEnabled) { _, newMapReportingEnabled in + if newMapReportingEnabled != node?.mqttConfig?.mapReportingEnabled { hasChanges = true } } - .onChange(of: mapPublishIntervalSecs) { newMapPublishIntervalSecs in - if node != nil && node?.mqttConfig != nil { - if newMapPublishIntervalSecs != node!.mqttConfig!.mapPublishIntervalSecs { hasChanges = true } - } + .onChange(of: mapPublishIntervalSecs) { _, newMapPublishIntervalSecs in + if newMapPublishIntervalSecs != node?.mqttConfig?.mapPublishIntervalSecs ?? -1 { hasChanges = true } } .onFirstAppear { // Need to request a MqttModuleConfig from the remote node before allowing changes @@ -353,52 +343,50 @@ 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) in + if let error { + Logger.services.error("Failed to reverse geocode location: \(error.localizedDescription)") + return + } - 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) in - if let error { - Logger.services.error("Failed to reverse geocode location: \(error.localizedDescription)") - return + if let placemarks = placemarks, let placemark = placemarks.first { + let cc = locale.region?.identifier ?? "UNK" + /// Country Topic unless you are US + if placemark.isoCountryCode ?? "unknown" != cc { + let countryTopic = defaultTopic + "/" + (placemark.isoCountryCode ?? "") + if !countryTopic.isEmpty { + nearbyTopics.append(countryTopic) + } } - - if let placemarks = placemarks, let placemark = placemarks.first { - let cc = locale.region?.identifier ?? "UNK" - /// Country Topic unless you are US - if placemark.isoCountryCode ?? "unknown" != cc { - let countryTopic = defaultTopic + "/" + (placemark.isoCountryCode ?? "") - if !countryTopic.isEmpty { - nearbyTopics.append(countryTopic) - } - } - let stateTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") - if !stateTopic.isEmpty { - nearbyTopics.append(stateTopic) - } - let countyTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subAdministrativeArea?.lowercased().replacingOccurrences(of: " ", with: "") ?? "") - if !countyTopic.isEmpty { - nearbyTopics.append(countyTopic) - } - let cityTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.locality?.lowercased().replacingOccurrences(of: " ", with: "") ?? "") - if !cityTopic.isEmpty { - nearbyTopics.append(cityTopic) - } - let neightborhoodTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subLocality?.lowercased() - .replacingOccurrences(of: " ", with: "") - .replacingOccurrences(of: "'", with: "") ?? "") - if !neightborhoodTopic.isEmpty { - nearbyTopics.append(neightborhoodTopic) - } - } else { - Logger.services.debug("No Location") + let stateTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + if !stateTopic.isEmpty { + nearbyTopics.append(stateTopic) } - }) - } + let countyTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subAdministrativeArea?.lowercased().replacingOccurrences(of: " ", with: "") ?? "") + if !countyTopic.isEmpty { + nearbyTopics.append(countyTopic) + } + let cityTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.locality?.lowercased().replacingOccurrences(of: " ", with: "") ?? "") + if !cityTopic.isEmpty { + nearbyTopics.append(cityTopic) + } + let neightborhoodTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subLocality?.lowercased() + .replacingOccurrences(of: " ", with: "") + .replacingOccurrences(of: "'", with: "") ?? "") + if !neightborhoodTopic.isEmpty { + nearbyTopics.append(neightborhoodTopic) + } + } else { + Logger.services.debug("No Location") + } + }) } + self.enabled = node?.mqttConfig?.enabled ?? false self.proxyToClientEnabled = node?.mqttConfig?.proxyToClientEnabled ?? false self.address = node?.mqttConfig?.address ?? "" diff --git a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift index 1141bc7d..2fd97646 100644 --- a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift @@ -79,11 +79,11 @@ struct PaxCounterConfig: View { } } } - .onChange(of: enabled) { - if $0 != node?.paxCounterConfig?.enabled { hasChanges = true } + .onChange(of: enabled) { oldEnabled, newEnabled in + if oldEnabled != newEnabled && newEnabled != node?.paxCounterConfig?.enabled { hasChanges = true } } - .onChange(of: paxcounterUpdateInterval) { - if $0 != node?.paxCounterConfig?.updateInterval ?? -1 { hasChanges = true } + .onChange(of: paxcounterUpdateInterval) { oldPaxcounterUpdateInterval, newPaxcounterUpdateInterval in + if oldPaxcounterUpdateInterval != newPaxcounterUpdateInterval && newPaxcounterUpdateInterval != node?.paxCounterConfig?.updateInterval ?? -1 { hasChanges = true } } SaveConfigButton(node: node, hasChanges: $hasChanges) { diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 872294d8..ae4797fc 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -102,14 +102,14 @@ struct RangeTestConfig: View { } } } - .onChange(of: enabled) { - if $0 != node?.rangeTestConfig?.enabled { hasChanges = true } + .onChange(of: enabled) { _, newEnabled in + if newEnabled != node?.rangeTestConfig?.enabled { hasChanges = true } } - .onChange(of: save) { - if $0 != node?.rangeTestConfig?.save { hasChanges = true } + .onChange(of: save) { _, newSave in + if newSave != node?.rangeTestConfig?.save { hasChanges = true } } - .onChange(of: sender) { - if $0 != node?.rangeTestConfig?.sender ?? -1 { hasChanges = true } + .onChange(of: sender) { _, newSender in + if newSender != node?.rangeTestConfig?.sender ?? -1 { hasChanges = true } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index 772b6b98..2e0931a7 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -31,14 +31,14 @@ struct RtttlConfig: View { .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) - .onChange(of: ringtone, perform: { _ in + .onChange(of: ringtone) { var totalBytes = ringtone.utf8.count // Only mess with the value if it is too big while totalBytes > 228 { ringtone = String(ringtone.dropLast()) totalBytes = ringtone.utf8.count } - }) + } .foregroundColor(.gray) } .keyboardType(.default) @@ -93,7 +93,7 @@ struct RtttlConfig: View { } } } - .onChange(of: ringtone) { newRingtone in + .onChange(of: ringtone) { _, newRingtone in if node != nil && node!.rtttlConfig != nil { if newRingtone != node!.rtttlConfig!.ringtone { hasChanges = true } } diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 893ddca1..a7fd5bdb 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -157,41 +157,29 @@ struct SerialConfig: View { } } } - .onChange(of: enabled) { - if $0 != node?.serialConfig?.enabled { hasChanges = true } + .onChange(of: enabled) { oldEnabled, newEnabled in + if oldEnabled != newEnabled && newEnabled != node?.serialConfig?.enabled ?? false { hasChanges = true } } - .onChange(of: echo) { - if $0 != node?.serialConfig?.echo { hasChanges = true } + .onChange(of: echo) { oldEcho, newEcho in + if oldEcho != newEcho && newEcho != node?.serialConfig?.echo ?? false { hasChanges = true } } - .onChange(of: rxd) { newRxd in - if node != nil && node!.serialConfig != nil { - if newRxd != node!.serialConfig!.rxd { hasChanges = true } - } + .onChange(of: rxd) { oldRxd, newRxd in + if oldRxd != newRxd && newRxd != node?.serialConfig?.rxd ?? -1 { hasChanges = true } } - .onChange(of: txd) { newTxd in - if node != nil && node!.serialConfig != nil { - if newTxd != node!.serialConfig!.txd { hasChanges = true } - } + .onChange(of: txd) { oldTxd, newTxd in + if oldTxd != newTxd && newTxd != node?.serialConfig?.txd ?? -1 { hasChanges = true } } - .onChange(of: baudRate) { newBaud in - if node != nil && node!.serialConfig != nil { - if newBaud != node!.serialConfig!.baudRate { hasChanges = true } - } + .onChange(of: baudRate) { oldBaud, newBaud in + if oldBaud != newBaud && newBaud != node?.serialConfig?.baudRate ?? -1 { hasChanges = true } } - .onChange(of: timeout) { newTimeout in - if node != nil && node!.serialConfig != nil { - if newTimeout != node!.serialConfig!.timeout { hasChanges = true } - } + .onChange(of: timeout) { oldTimeout, newTimeout in + if oldTimeout != newTimeout && newTimeout != node?.serialConfig?.timeout ?? -1 { hasChanges = true } } - .onChange(of: overrideConsoleSerialPort) { newOverrideConsoleSerialPort in - if node != nil && node!.serialConfig != nil { - if newOverrideConsoleSerialPort != node!.serialConfig!.overrideConsoleSerialPort { hasChanges = true } - } + .onChange(of: overrideConsoleSerialPort) { oldOverrideConsoleSerialPort, newOverrideConsoleSerialPort in + if oldOverrideConsoleSerialPort != newOverrideConsoleSerialPort && newOverrideConsoleSerialPort != node?.serialConfig?.overrideConsoleSerialPort ?? false { hasChanges = true } } - .onChange(of: mode) { newMode in - if node != nil && node!.serialConfig != nil { - if newMode != node!.serialConfig!.mode { hasChanges = true } - } + .onChange(of: mode) { oldMode, newMode in + if oldMode != newMode && newMode != node?.serialConfig?.mode ?? -1 { hasChanges = true } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index aeee9601..d90ea3fb 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -167,35 +167,23 @@ struct StoreForwardConfig: View { } } } - .onChange(of: enabled) { newEnabled in - if node != nil && node?.storeForwardConfig != nil { - if newEnabled != node!.storeForwardConfig!.enabled { hasChanges = true } - } + .onChange(of: enabled) { oldEnabled, newEnabled in + if oldEnabled != newEnabled && newEnabled != node!.storeForwardConfig!.enabled { hasChanges = true } } - .onChange(of: isRouter) { newIsRouter in - if node != nil && node?.storeForwardConfig != nil { - if newIsRouter != node!.storeForwardConfig!.isRouter { hasChanges = true } - } + .onChange(of: isRouter) { oldIsRouter, newIsRouter in + if oldIsRouter != newIsRouter && newIsRouter != node!.storeForwardConfig!.isRouter { hasChanges = true } } - .onChange(of: heartbeat) { newHeartbeat in - if node != nil && node?.storeForwardConfig != nil { - if newHeartbeat != node!.storeForwardConfig!.heartbeat { hasChanges = true } - } + .onChange(of: heartbeat) { oldHeartbeat, newHeartbeat in + if oldHeartbeat != newHeartbeat && newHeartbeat != node?.storeForwardConfig?.heartbeat ?? true { hasChanges = true } } - .onChange(of: records) { newRecords in - if node != nil && node?.storeForwardConfig != nil { - if newRecords != node!.storeForwardConfig!.records { hasChanges = true } - } + .onChange(of: records) { oldRecords, newRecords in + if oldRecords != newRecords && newRecords != node!.storeForwardConfig?.records ?? -1 { hasChanges = true } } - .onChange(of: historyReturnMax) { newHistoryReturnMax in - if node != nil && node?.storeForwardConfig != nil { - if newHistoryReturnMax != node!.storeForwardConfig!.historyReturnMax { hasChanges = true } - } + .onChange(of: historyReturnMax) { oldHistoryReturnMax, newHistoryReturnMax in + if oldHistoryReturnMax != newHistoryReturnMax && newHistoryReturnMax != node!.storeForwardConfig?.historyReturnMax ?? -1 { hasChanges = true } } - .onChange(of: historyReturnWindow) { newHistoryReturnWindow in - if node != nil && node?.storeForwardConfig != nil { - if newHistoryReturnWindow != node!.storeForwardConfig!.historyReturnWindow { hasChanges = true } - } + .onChange(of: historyReturnWindow) { oldHistoryReturnWindow, newHistoryReturnWindow in + if oldHistoryReturnWindow != newHistoryReturnWindow && newHistoryReturnWindow != node!.storeForwardConfig?.historyReturnWindow ?? -1 { hasChanges = true } } } func setStoreAndForwardValues() { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index afef5727..4ba39834 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -155,45 +155,29 @@ struct TelemetryConfig: View { } } } - .onChange(of: deviceUpdateInterval) { newDeviceInterval in - if node != nil && node?.telemetryConfig != nil { - if newDeviceInterval != node!.telemetryConfig!.deviceUpdateInterval { hasChanges = true } - } + .onChange(of: deviceUpdateInterval) { _, newDeviceInterval in + if newDeviceInterval != node?.telemetryConfig?.deviceUpdateInterval ?? -1 { hasChanges = true } } - .onChange(of: environmentUpdateInterval) { newEnvInterval in - if node != nil && node?.telemetryConfig != nil { - if newEnvInterval != node!.telemetryConfig!.environmentUpdateInterval { hasChanges = true } - } + .onChange(of: environmentUpdateInterval) { _, newEnvInterval in + if newEnvInterval != node?.telemetryConfig?.environmentUpdateInterval ?? -1 { hasChanges = true } } - .onChange(of: environmentMeasurementEnabled) { newEnvEnabled in - if node != nil && node?.telemetryConfig != nil { - if newEnvEnabled != node!.telemetryConfig!.environmentMeasurementEnabled { hasChanges = true } - } + .onChange(of: environmentMeasurementEnabled) { _, newEnvEnabled in + if newEnvEnabled != node?.telemetryConfig?.environmentMeasurementEnabled { hasChanges = true } } - .onChange(of: environmentScreenEnabled) { newEnvScreenEnabled in - if node!.telemetryConfig != nil { - if newEnvScreenEnabled != node!.telemetryConfig!.environmentScreenEnabled { hasChanges = true } - } + .onChange(of: environmentScreenEnabled) { _, newEnvScreenEnabled in + if newEnvScreenEnabled != node?.telemetryConfig?.environmentScreenEnabled { hasChanges = true } } - .onChange(of: environmentDisplayFahrenheit) { newEnvDisplayF in - if node != nil && node?.telemetryConfig != nil { - if newEnvDisplayF != node!.telemetryConfig!.environmentDisplayFahrenheit { hasChanges = true } - } + .onChange(of: environmentDisplayFahrenheit) { _, newEnvDisplayF in + if newEnvDisplayF != node?.telemetryConfig?.environmentDisplayFahrenheit { hasChanges = true } } - .onChange(of: powerMeasurementEnabled) { newPowerMeasurementEnabled in - if node != nil && node?.telemetryConfig != nil { - if newPowerMeasurementEnabled != node!.telemetryConfig!.powerMeasurementEnabled { hasChanges = true } - } + .onChange(of: powerMeasurementEnabled) { _, newPowerMeasurementEnabled in + if newPowerMeasurementEnabled != node?.telemetryConfig?.powerMeasurementEnabled { hasChanges = true } } - .onChange(of: powerUpdateInterval) { newPowerUpdateInterval in - if node != nil && node?.telemetryConfig != nil { - if newPowerUpdateInterval != node!.telemetryConfig!.powerUpdateInterval { hasChanges = true } - } + .onChange(of: powerUpdateInterval) { _, newPowerUpdateInterval in + if newPowerUpdateInterval != node?.telemetryConfig?.powerUpdateInterval ?? -1 { hasChanges = true } } - .onChange(of: powerScreenEnabled) { newPowerScreenEnabled in - if node != nil && node?.telemetryConfig != nil { - if newPowerScreenEnabled != node!.telemetryConfig!.powerScreenEnabled { hasChanges = true } - } + .onChange(of: powerScreenEnabled) { _, newPowerScreenEnabled in + if newPowerScreenEnabled != node?.telemetryConfig?.powerScreenEnabled { hasChanges = true } } } } diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 198ad1a0..8cebde85 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -45,7 +45,7 @@ struct NetworkConfig: View { .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) - .onChange(of: wifiSsid, perform: { _ in + .onChange(of: wifiSsid) { var totalBytes = wifiSsid.utf8.count // Only mess with the value if it is too big while totalBytes > 32 { @@ -53,7 +53,7 @@ struct NetworkConfig: View { totalBytes = wifiSsid.utf8.count } hasChanges = true - }) + } .foregroundColor(.gray) } .keyboardType(.default) @@ -63,7 +63,7 @@ struct NetworkConfig: View { .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) - .onChange(of: wifiPsk, perform: { _ in + .onChange(of: wifiPsk) { var totalBytes = wifiPsk.utf8.count // Only mess with the value if it is too big while totalBytes > 63 { @@ -71,7 +71,7 @@ struct NetworkConfig: View { totalBytes = wifiPsk.utf8.count } hasChanges = true - }) + } .foregroundColor(.gray) } .keyboardType(.default) @@ -151,20 +151,20 @@ struct NetworkConfig: View { } } } - .onChange(of: wifiEnabled) { - if $0 != node?.networkConfig?.wifiEnabled { hasChanges = true } + .onChange(of: wifiEnabled) { _, newEnabled in + if newEnabled != node?.networkConfig?.wifiEnabled { hasChanges = true } } - .onChange(of: wifiSsid) { newSSID in + .onChange(of: wifiSsid) { _, newSSID in if newSSID != node?.networkConfig?.wifiSsid { hasChanges = true } } - .onChange(of: wifiPsk) { newPsk in + .onChange(of: wifiPsk) { _, newPsk in if newPsk != node?.networkConfig?.wifiPsk { hasChanges = true } } - .onChange(of: wifiMode) { - if $0 != node?.networkConfig?.wifiMode ?? -1 { hasChanges = true } + .onChange(of: wifiMode) { _, newMode in + if newMode != node?.networkConfig?.wifiMode ?? -1 { hasChanges = true } } - .onChange(of: ethEnabled) { - if $0 != node?.networkConfig?.ethEnabled { hasChanges = true } + .onChange(of: ethEnabled) { _, newEthEnabled in + if newEthEnabled != node?.networkConfig?.ethEnabled { hasChanges = true } } } func setNetworkValues() { diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 4497c1f7..fe61d1fa 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -398,7 +398,7 @@ struct PositionConfig: View { } } } - .onChange(of: fixedPosition) { newFixed in + .onChange(of: fixedPosition) { _, newFixed in if supportedVersion { if let positionConfig = node?.positionConfig { /// Fixed Position is off to start @@ -411,37 +411,37 @@ struct PositionConfig: View { } } } - .onChange(of: gpsMode) { newGpsMode in + .onChange(of: gpsMode) { _, newGpsMode in if newGpsMode != node?.positionConfig?.gpsMode ?? 0 { hasChanges = true } } - .onChange(of: rxGpio) { newRxGpio in + .onChange(of: rxGpio) { _, newRxGpio in if newRxGpio != node?.positionConfig?.rxGpio ?? 0 { hasChanges = true } } - .onChange(of: txGpio) { newTxGpio in + .onChange(of: txGpio) { _, newTxGpio in if newTxGpio != node?.positionConfig?.txGpio ?? 0 { hasChanges = true } } - .onChange(of: gpsEnGpio) { newGpsEnGpio in + .onChange(of: gpsEnGpio) { _, newGpsEnGpio in if newGpsEnGpio != node?.positionConfig?.gpsEnGpio ?? 0 { hasChanges = true } } - .onChange(of: smartPositionEnabled) { newSmartPositionEnabled in + .onChange(of: smartPositionEnabled) { _, newSmartPositionEnabled in if newSmartPositionEnabled != node?.positionConfig?.smartPositionEnabled { hasChanges = true } } - .onChange(of: positionBroadcastSeconds) { newPositionBroadcastSeconds in + .onChange(of: positionBroadcastSeconds) { _, newPositionBroadcastSeconds in if newPositionBroadcastSeconds != node?.positionConfig?.positionBroadcastSeconds ?? 0 { hasChanges = true } } - .onChange(of: broadcastSmartMinimumIntervalSecs) { newBroadcastSmartMinimumIntervalSecs in + .onChange(of: broadcastSmartMinimumIntervalSecs) { _, newBroadcastSmartMinimumIntervalSecs in if newBroadcastSmartMinimumIntervalSecs != node?.positionConfig?.broadcastSmartMinimumIntervalSecs ?? 0 { hasChanges = true } } - .onChange(of: broadcastSmartMinimumDistance) { newBroadcastSmartMinimumDistance in + .onChange(of: broadcastSmartMinimumDistance) { _, newBroadcastSmartMinimumDistance in if newBroadcastSmartMinimumDistance != node?.positionConfig?.broadcastSmartMinimumDistance ?? 0 { hasChanges = true } } - .onChange(of: gpsUpdateInterval) { newGpsUpdateInterval in + .onChange(of: gpsUpdateInterval) { _, newGpsUpdateInterval in if newGpsUpdateInterval != node?.positionConfig?.gpsUpdateInterval ?? 0 { hasChanges = true } } } func handlePositionFlagtChanges() { - guard let positionConfig = node?.positionConfig else { return } + guard (node?.positionConfig) != nil else { return } let pf = PositionFlags(rawValue: self.positionFlags) hasChanges = pf.contains(.Altitude) || diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift index 9ba0fe0a..822fd1e0 100644 --- a/Meshtastic/Views/Settings/Config/PowerConfig.swift +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -148,31 +148,31 @@ struct PowerConfig: View { } } } - .onChange(of: isPowerSaving) { - if $0 != node?.powerConfig?.isPowerSaving { hasChanges = true } + .onChange(of: isPowerSaving) { oldIsPowerSaving, newIsPowerSaving in + if oldIsPowerSaving != newIsPowerSaving && newIsPowerSaving != node?.powerConfig?.isPowerSaving { hasChanges = true } } - .onChange(of: shutdownOnPowerLoss) { newShutdownOnPowerLoss in + .onChange(of: shutdownOnPowerLoss) { _, newShutdownOnPowerLoss in if newShutdownOnPowerLoss { hasChanges = true } } - .onChange(of: shutdownAfterSecs) { - if $0 != node?.powerConfig?.minWakeSecs ?? -1 { hasChanges = true } + .onChange(of: shutdownAfterSecs) { oldShutdownAfterSecs, newShutdownAfterSecs in + if oldShutdownAfterSecs != newShutdownAfterSecs && newShutdownAfterSecs != node?.powerConfig?.minWakeSecs ?? -1 { hasChanges = true } } - .onChange(of: adcOverride) { _ in + .onChange(of: adcOverride) { hasChanges = true } - .onChange(of: adcMultiplier) { newAdcMultiplier in - if newAdcMultiplier != node?.powerConfig?.adcMultiplierOverride ?? -1 { hasChanges = true } + .onChange(of: adcMultiplier) { _, newAdcMultiplier in + if newAdcMultiplier != node?.powerConfig?.adcMultiplierOverride ?? -1 { hasChanges = true } } - .onChange(of: waitBluetoothSecs) { - if $0 != node?.powerConfig?.waitBluetoothSecs ?? -1 { hasChanges = true } + .onChange(of: waitBluetoothSecs) { oldWaitBluetoothSecs, newWaitBluetoothSecs in + if oldWaitBluetoothSecs != newWaitBluetoothSecs && newWaitBluetoothSecs != node?.powerConfig?.waitBluetoothSecs ?? -1 { hasChanges = true } } - .onChange(of: lsSecs) { - if $0 != node?.powerConfig?.lsSecs ?? -1 { hasChanges = true } + .onChange(of: lsSecs) { _, newLsSecs in + if newLsSecs != node?.powerConfig?.lsSecs ?? -1 { hasChanges = true } } - .onChange(of: minWakeSecs) { - if $0 != node?.powerConfig?.minWakeSecs ?? -1 { hasChanges = true } + .onChange(of: minWakeSecs) { _, newMinWakeSecs in + if newMinWakeSecs != node?.powerConfig?.minWakeSecs ?? -1 { hasChanges = true } } SaveConfigButton(node: node, hasChanges: $hasChanges) { @@ -232,13 +232,13 @@ private struct FloatField: View { TextField(title.localized, value: $typingNumber, format: .number) .foregroundColor(.gray) .multilineTextAlignment(.trailing) - .onChange(of: typingNumber, perform: { _ in + .onChange(of: typingNumber) { if isValid(typingNumber) { number = typingNumber } else { typingNumber = number } - }) + } .keyboardType(.decimalPad) .onAppear { typingNumber = number diff --git a/Meshtastic/Views/Settings/Config/SecurityConfig.swift b/Meshtastic/Views/Settings/Config/SecurityConfig.swift index 95a07dfb..75b69463 100644 --- a/Meshtastic/Views/Settings/Config/SecurityConfig.swift +++ b/Meshtastic/Views/Settings/Config/SecurityConfig.swift @@ -106,19 +106,19 @@ struct SecurityConfig: View { name: "\(bleManager.connectedPeripheral?.shortName ?? "?")" ) }) - .onChange(of: isManaged) { - if $0 != node?.securityConfig?.isManaged { hasChanges = true } + .onChange(of: isManaged) { _, newIsManaged in + if newIsManaged != node?.securityConfig?.isManaged { hasChanges = true } } - .onChange(of: serialEnabled) { - if $0 != node?.securityConfig?.serialEnabled { hasChanges = true } + .onChange(of: serialEnabled) { _, newSerialEnabled in + if newSerialEnabled != node?.securityConfig?.serialEnabled { hasChanges = true } } - .onChange(of: debugLogApiEnabled) { - if $0 != node?.securityConfig?.debugLogApiEnabled { hasChanges = true } + .onChange(of: debugLogApiEnabled) { _, newDebugLogApiEnabled in + if newDebugLogApiEnabled != node?.securityConfig?.debugLogApiEnabled { hasChanges = true } } - .onChange(of: adminChannelEnabled) { - if $0 != node?.securityConfig?.adminChannelEnabled { hasChanges = true } + .onChange(of: adminChannelEnabled) { _, newAdminChannelEnabled in + if newAdminChannelEnabled != node?.securityConfig?.adminChannelEnabled { hasChanges = true } } - .onChange(of: publicKey) { _ in + .onChange(of: publicKey) { let tempKey = Data(base64Encoded: publicKey) ?? Data() if tempKey.count == 32 { hasValidPublicKey = true @@ -127,7 +127,7 @@ struct SecurityConfig: View { } hasChanges = true } - .onChange(of: privateKey) { _ in + .onChange(of: privateKey) { let tempKey = Data(base64Encoded: privateKey) ?? Data() if tempKey.count == 32 { hasValidPrivateKey = true @@ -136,7 +136,7 @@ struct SecurityConfig: View { } hasChanges = true } - .onChange(of: adminKey) { key in + .onChange(of: adminKey) { _, key in let tempKey = Data(base64Encoded: key) ?? Data() if key.isEmpty { hasValidAdminKey = true diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index 9a450775..9962e69d 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -13,7 +13,7 @@ struct Firmware: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager var node: NodeInfoEntity? - @State var minimumVersion = "2.4.2" + @State var minimumVersion = "2.5.4" @State var version = "" @State private var currentDevice: DeviceHardware? @State private var latestStable: FirmwareRelease? diff --git a/Meshtastic/Views/Settings/GPSStatus.swift b/Meshtastic/Views/Settings/GPSStatus.swift index b1119694..c92a647c 100644 --- a/Meshtastic/Views/Settings/GPSStatus.swift +++ b/Meshtastic/Views/Settings/GPSStatus.swift @@ -8,7 +8,6 @@ import SwiftUI import CoreLocation -@available(iOS 17.0, macOS 14.0, *) struct GPSStatus: View { var largeFont: Font = .footnote diff --git a/Meshtastic/Views/Settings/RouteRecorder.swift b/Meshtastic/Views/Settings/RouteRecorder.swift index c19dbd9f..82e7bb32 100644 --- a/Meshtastic/Views/Settings/RouteRecorder.swift +++ b/Meshtastic/Views/Settings/RouteRecorder.swift @@ -12,7 +12,6 @@ import CoreLocation import CoreMotion import OSLog -@available(iOS 17.0, macOS 14.0, *) struct RouteRecorder: View { @ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared @@ -284,7 +283,7 @@ struct RouteRecorder: View { .onDisappear(perform: { UIApplication.shared.isIdleTimerDisabled = false }) - .onChange(of: locationsHandler.locationsArray.last) { newLoc in + .onChange(of: locationsHandler.locationsArray.last) { _, newLoc in if locationsHandler.isRecording { if let loc = newLoc { if recording != nil { diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 314497e8..174221d2 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -10,7 +10,6 @@ import CoreData import MapKit import OSLog -@available(iOS 17.0, macOS 14.0, *) struct Routes: View { @State private var columnVisibility = NavigationSplitViewVisibility.doubleColumn @@ -58,7 +57,6 @@ struct Routes: View { } do { - guard let fileContent = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return } let routeName = selectedFile.lastPathComponent.dropLast(4) let lines = fileContent.components(separatedBy: "\n") @@ -176,14 +174,14 @@ struct Routes: View { axis: .vertical ) .foregroundColor(Color.gray) - .onChange(of: name, perform: { _ in + .onChange(of: name) { var totalBytes = name.utf8.count // Only mess with the value if it is too big while totalBytes > 100 { name = String(name.dropLast()) totalBytes = name.utf8.count } - }) + } Toggle(isOn: $enabled) { Label("enabled", systemImage: "point.topleft.filled.down.to.point.bottomright.curvepath") @@ -237,16 +235,16 @@ struct Routes: View { .controlSize(.large) .disabled(!hasChanges) } - .onChange(of: name) { _ in + .onChange(of: name) { hasChanges = true } - .onChange(of: notes) { _ in + .onChange(of: notes) { hasChanges = true } - .onChange(of: enabled) { _ in + .onChange(of: enabled) { hasChanges = true } - .onChange(of: color) { _ in + .onChange(of: color) { hasChanges = true } Map { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 481fb29d..ab8c97b3 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -7,9 +7,7 @@ import SwiftUI import OSLog -#if canImport(TipKit) import TipKit -#endif struct Settings: View { @Environment(\.managedObjectContext) var context @@ -156,13 +154,11 @@ struct Settings: View { var moduleConfigurationSection: some View { Section("module.configuration") { - if #available(iOS 17.0, macOS 14.0, *) { - NavigationLink(value: SettingsNavigationState.ambientLighting) { - Label { - Text("ambient.lighting") - } icon: { - Image(systemName: "light.max") - } + NavigationLink(value: SettingsNavigationState.ambientLighting) { + Label { + Text("ambient.lighting") + } icon: { + Image(systemName: "light.max") } } @@ -323,22 +319,20 @@ struct Settings: View { Image(systemName: "gearshape") } } - if #available(iOS 17.0, macOS 14.0, *) { - NavigationLink(value: SettingsNavigationState.routes) { - Label { - Text("routes") - } icon: { - Image(systemName: "road.lanes.curved.right") - } + NavigationLink(value: SettingsNavigationState.routes) { + Label { + Text("routes") + } icon: { + Image(systemName: "road.lanes.curved.right") } + } - NavigationLink(value: SettingsNavigationState.routeRecorder) { - Label { - Text("route.recorder") - } icon: { - Image(systemName: "record.circle") - .foregroundColor(.red) - } + NavigationLink(value: SettingsNavigationState.routeRecorder) { + Label { + Text("route.recorder") + } icon: { + Image(systemName: "record.circle") + .foregroundColor(.red) } } @@ -392,7 +386,7 @@ struct Settings: View { } } .pickerStyle(.navigationLink) - .onChange(of: selectedNode) { newValue in + .onChange(of: selectedNode) { _, newValue in if selectedNode > 0 { let node = nodes.first(where: { $0.num == newValue }) let connectedNode = nodes.first(where: { $0.num == preferredNodeNum }) @@ -405,9 +399,7 @@ struct Settings: View { } } } - if #available(iOS 17.0, macOS 14.0, *) { - TipView(AdminChannelTip(), arrowEdge: .top) - } + TipView(AdminChannelTip(), arrowEdge: .top) } else { if bleManager.connectedPeripheral != nil { Text("Connected Node \(node?.user?.longName ?? "unknown".localized)") @@ -418,9 +410,7 @@ struct Settings: View { radioConfigurationSection deviceConfigurationSection moduleConfigurationSection - if #available (iOS 17.0, *) { - loggingSection - } + loggingSection #if DEBUG developersSection #endif @@ -435,13 +425,9 @@ struct Settings: View { case .appSettings: AppSettings() case .routes: - if #available(iOS 17.0, *) { - Routes() - } + Routes() case .routeRecorder: - if #available(iOS 17.0, *) { - RouteRecorder() - } + RouteRecorder() case .lora: LoRaConfig(node: nodes.first(where: { $0.num == selectedNode })) case .channels: @@ -463,9 +449,7 @@ struct Settings: View { case .power: PowerConfig(node: nodes.first(where: { $0.num == selectedNode })) case .ambientLighting: - if #available(iOS 17.0, macOS 14.0, *) { - AmbientLightingConfig(node: node) - } + AmbientLightingConfig(node: node) case .cannedMessages: CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode })) case .detectionSensor: @@ -491,16 +475,14 @@ struct Settings: View { case .meshLog: MeshLog() case .debugLogs: - if #available(iOS 17.0, macOS 14.0, *) { - AppLog() - } + AppLog() case .appFiles: AppData() case .firmwareUpdates: Firmware(node: node) } } - .onChange(of: UserDefaults.preferredPeripheralNum ) { newConnectedNode in + .onChange(of: UserDefaults.preferredPeripheralNum ) { _, newConnectedNode in preferredNodeNum = newConnectedNode if nodes.count > 1 { if selectedNode == 0 { diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 136b9563..1e10c571 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -8,10 +8,7 @@ import SwiftUI import CoreData import CoreImage.CIFilterBuiltins import MeshtasticProtobufs - -#if canImport(TipKit) import TipKit -#endif struct QrCodeImage { let context = CIContext() @@ -55,10 +52,8 @@ struct ShareChannels: View { var body: some View { - if #available(iOS 17.0, macOS 14.0, *) { - VStack { - TipView(ShareChannelsTip(), arrowEdge: .bottom) - } + VStack { + TipView(ShareChannelsTip(), arrowEdge: .bottom) } GeometryReader { bounds in let smallest = min(bounds.size.width, bounds.size.height) @@ -240,15 +235,15 @@ struct ShareChannels: View { .onAppear { generateChannelSet() } - .onChange(of: includeChannel0) { _ in generateChannelSet() } - .onChange(of: includeChannel1) { _ in generateChannelSet() } - .onChange(of: includeChannel2) { _ in generateChannelSet() } - .onChange(of: includeChannel3) { _ in generateChannelSet() } - .onChange(of: includeChannel4) { _ in generateChannelSet() } - .onChange(of: includeChannel5) { _ in generateChannelSet() } - .onChange(of: includeChannel6) { _ in generateChannelSet() } - .onChange(of: includeChannel7) { _ in generateChannelSet() } - .onChange(of: replaceChannels) { _ in generateChannelSet() } + .onChange(of: includeChannel0) { generateChannelSet() } + .onChange(of: includeChannel1) { generateChannelSet() } + .onChange(of: includeChannel2) { generateChannelSet() } + .onChange(of: includeChannel3) { generateChannelSet() } + .onChange(of: includeChannel4) { generateChannelSet() } + .onChange(of: includeChannel5) { generateChannelSet() } + .onChange(of: includeChannel6) { generateChannelSet() } + .onChange(of: includeChannel7) { generateChannelSet() } + .onChange(of: replaceChannels) { generateChannelSet() } } } func generateChannelSet() { diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index 72084954..daacb849 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -49,14 +49,14 @@ struct UserConfig: View { Label(isLicensed ? "Call Sign" : "Long Name", systemImage: "person.crop.rectangle.fill") TextField("Long Name", text: $longName) - .onChange(of: longName, perform: { _ in + .onChange(of: longName) { var totalBytes = longName.utf8.count // Only mess with the value if it is too big while totalBytes > (isLicensed ? 6 : 36) { longName = String(longName.dropLast()) totalBytes = longName.utf8.count } - }) + } } .keyboardType(.default) .disableAutocorrection(true) @@ -74,14 +74,14 @@ struct UserConfig: View { Label("Short Name", systemImage: "circlebadge.fill") TextField("Short Name", text: $shortName) .foregroundColor(.gray) - .onChange(of: shortName, perform: { _ in + .onChange(of: shortName) { var totalBytes = shortName.utf8.count // Only mess with the value if it is too big if totalBytes > 4 { shortName = String(shortName.dropLast()) totalBytes = shortName.utf8.count } - }) + } .foregroundColor(.gray) } .keyboardType(.default) @@ -197,17 +197,17 @@ struct UserConfig: View { self.overrideFrequency = node?.loRaConfig?.overrideFrequency ?? 0.00 self.hasChanges = false } - .onChange(of: shortName) { newShort in + .onChange(of: shortName) { _, newShort in if node != nil && node!.user != nil { if newShort != node?.user!.shortName { hasChanges = true } } } - .onChange(of: longName) { newLong in + .onChange(of: longName) { _, newLong in if node != nil && node!.user != nil { if newLong != node?.user!.longName { hasChanges = true } } } - .onChange(of: isLicensed) { newIsLicensed in + .onChange(of: isLicensed) { _, newIsLicensed in if node != nil && node!.user != nil { if newIsLicensed != node?.user!.isLicensed { hasChanges = true @@ -219,10 +219,10 @@ struct UserConfig: View { } } } - .onChange(of: overrideFrequency) { _ in + .onChange(of: overrideFrequency) { hasChanges = true } - .onChange(of: txPower) { _ in + .onChange(of: txPower) { hasChanges = true } } diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 06099bd1..e16e3913 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -120,7 +120,6 @@ struct WidgetsLiveActivity: Widget { } } - struct WidgetsLiveActivity_Previews: PreviewProvider { static let attributes = MeshActivityAttributes(nodeNum: 123456789, name: "RAK Compact Rotary Handset Gray 8E6G") static let state = MeshActivityAttributes.ContentState(uptimeSeconds: 600, channelUtilization: 1.2, airtime: 3.5, sentPackets: 12587, receivedPackets: 12555, badReceivedPackets: 800, nodesOnline: 99, totalNodes: 100, timerRange: Date.now...Date(timeIntervalSinceNow: 300))