diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 7c131f75..1353fce8 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1188,7 +1188,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.2; + MARKETING_VERSION = 2.1.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1222,7 +1222,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.2; + MARKETING_VERSION = 2.1.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index c9274bf0..e0772c35 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -33,22 +33,6 @@ enum KeyboardType: Int, CaseIterable, Identifiable { } } -enum CenteringMode: Int, CaseIterable, Identifiable { - - case allAnnotations = 0 - case allPositions = 1 - - var id: Int { self.rawValue } - var description: String { - switch self { - case .allAnnotations: - return "All Annotations"// NSLocalizedString("default", comment: "Default Keyboard") - case .allPositions: - return "All Node Postions"// NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard") - } - } -} - enum MeshMapType: String, CaseIterable, Identifiable { case standard diff --git a/Meshtastic/Enums/DeviceEnums.swift b/Meshtastic/Enums/DeviceEnums.swift index a53a9893..186502ab 100644 --- a/Meshtastic/Enums/DeviceEnums.swift +++ b/Meshtastic/Enums/DeviceEnums.swift @@ -101,7 +101,7 @@ enum RebroadcastModes: Int, CaseIterable, Identifiable { case .allSkipDecoding: return "Same as behavior as ALL but skips packet decoding and simply rebroadcasts them. Only available in Repeater role. Setting this on any other roles will result in ALL behavior." case .localOnly: - return "Inverted top bar for 2 Color display" + return "Ignores observed messages from foreign meshes that are open or those which it cannot decrypt. Only rebroadcasts message on the nodes local primary / secondary channels." } } func protoEnumValue() -> Config.DeviceConfig.RebroadcastMode { diff --git a/Meshtastic/Model/UserSettings.swift b/Meshtastic/Model/UserSettings.swift index bb8de9ee..585559ee 100644 --- a/Meshtastic/Model/UserSettings.swift +++ b/Meshtastic/Model/UserSettings.swift @@ -62,6 +62,18 @@ class UserSettings: ObservableObject { UserDefaults.standard.synchronize() } } + @Published var meshMapShowNodeHistory: Bool { + didSet { + UserDefaults.standard.set(meshMapShowNodeHistory, forKey: "meshMapShowNodeHistory") + UserDefaults.standard.synchronize() + } + } + @Published var meshMapShowRouteLines: Bool { + didSet { + UserDefaults.standard.set(meshMapShowRouteLines, forKey: "meshMapShowRouteLines") + UserDefaults.standard.synchronize() + } + } init() { @@ -75,5 +87,7 @@ class UserSettings: ObservableObject { self.meshMapRecentering = UserDefaults.standard.object(forKey: "meshMapRecentering") as? Bool ?? false self.meshMapCustomTileServer = UserDefaults.standard.string(forKey: "meshMapCustomTileServer") ?? "" self.meshMapUserTrackingMode = UserDefaults.standard.object(forKey: "meshMapUserTrackingMode") as? Int ?? 0 + self.meshMapShowNodeHistory = UserDefaults.standard.object(forKey: "meshMapShowNodeHistory") as? Bool ?? true + self.meshMapShowRouteLines = UserDefaults.standard.object(forKey: "meshMapShowRouteLines") as? Bool ?? false } } diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index 04f17cdb..7be86a36 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -681,6 +681,10 @@ struct Config { /// Print first line in pseudo-bold? FALSE is original style, TRUE is bold var headingBold: Bool = false + /// + /// Should we wake the screen up on accelerometer detected motion or tap + var wakeOnTapOrMotion: Bool = false + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -1970,6 +1974,7 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp 7: .same(proto: "oled"), 8: .same(proto: "displaymode"), 9: .standard(proto: "heading_bold"), + 10: .standard(proto: "wake_on_tap_or_motion"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1987,6 +1992,7 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp case 7: try { try decoder.decodeSingularEnumField(value: &self.oled) }() case 8: try { try decoder.decodeSingularEnumField(value: &self.displaymode) }() case 9: try { try decoder.decodeSingularBoolField(value: &self.headingBold) }() + case 10: try { try decoder.decodeSingularBoolField(value: &self.wakeOnTapOrMotion) }() default: break } } @@ -2020,6 +2026,9 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if self.headingBold != false { try visitor.visitSingularBoolField(value: self.headingBold, fieldNumber: 9) } + if self.wakeOnTapOrMotion != false { + try visitor.visitSingularBoolField(value: self.wakeOnTapOrMotion, fieldNumber: 10) + } try unknownFields.traverse(visitor: &visitor) } @@ -2033,6 +2042,7 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if lhs.oled != rhs.oled {return false} if lhs.displaymode != rhs.displaymode {return false} if lhs.headingBold != rhs.headingBold {return false} + if lhs.wakeOnTapOrMotion != rhs.wakeOnTapOrMotion {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 32b25c29..58711e4e 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -295,8 +295,9 @@ struct Connect: View { if #available(iOS 16.2, *) { liveActivityStarted = true let timerSeconds = 60 - - let mostRecent = node?.telemetries?.lastObject as? TelemetryEntity + + let deviceMetrics = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) + let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown") diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 44fc5c5c..2fb8b422 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -20,21 +20,19 @@ struct MapViewSwiftUI: UIViewRepresentable { let waypoints: [WaypointEntity] let mapViewType: MKMapType let userTrackingMode: MKUserTrackingMode - let centeringMode: CenteringMode - let showRouteLines: Bool let showNodeHistory: Bool - let centerOnPositionsOnly: Bool - @AppStorage("meshMapRecentering") private var recenter: Bool = false + let showRouteLines: Bool + let colors: [UIColor] = [UIColor.systemIndigo, UIColor.orange, UIColor.green, UIColor.brown, UIColor.purple, UIColor.systemMint, UIColor.cyan, UIColor.magenta, UIColor.systemPink, UIColor.blue] + @AppStorage("meshMapRecentering") private var recenter: Bool = false // Offline Maps // make this view dependent on the UserDefault that is updated when importing a new map file @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0 @State private var loadedLastUpdatedLocalMapFile = 0 var customMapOverlay: CustomMapOverlay? @State private var presentCustomMapOverlayHash: CustomMapOverlay? - var overlays: [Overlay] = [] - let dynamicRegion: Bool = true + func makeUIView(context: Context) -> MKMapView { // Map View Parameters mapView.mapType = mapViewType @@ -49,24 +47,10 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.setUserTrackingMode(userTrackingMode, animated: true) if userTrackingMode == MKUserTrackingMode.none { mapView.showsUserLocation = false - switch centeringMode { - case .allAnnotations: - mapView.addAnnotations(showNodeHistory ? positions : latest) - if userTrackingMode == MKUserTrackingMode.none { - mapView.fitAllAnnotations() - } - case .allPositions: - if userTrackingMode == MKUserTrackingMode.none { - mapView.addAnnotations(showNodeHistory ? positions : latest) - mapView.fit(annotations: positions, andShow: false) - } else { - mapView.addAnnotations(showNodeHistory ? positions : latest) - } - } } else { - mapView.addAnnotations(showNodeHistory ? positions : latest) mapView.showsUserLocation = true } + mapView.addAnnotations(showNodeHistory ? positions : latest) // Other MKMapView Settings mapView.preferredConfiguration.elevationStyle = .realistic// .flat mapView.isPitchEnabled = true @@ -123,46 +107,59 @@ struct MapViewSwiftUI: UIViewRepresentable { DispatchQueue.main.async { self.presentCustomMapOverlayHash = self.customMapOverlay self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile - - if showRouteLines { - let nodePositions = positions.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) } + } + } + + DispatchQueue.main.async { + let latest = positions + .filter { $0.latest == true } + .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } + + if showRouteLines { + // Remove all existing PolyLine Overlays + for overlay in mapView.overlays { + if overlay is MKPolyline { + mapView.removeOverlay(overlay) + } + } + var lineIndex = 0 + for position in latest { + + let nodePositions = positions.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 } let lineCoords = nodePositions.map ({ (position) -> CLLocationCoordinate2D in return position.nodeCoordinate! }) let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count) + polyline.title = "\(String(position.nodePosition?.num ?? 0))-\(String(lineIndex))" mapView.addOverlay(polyline) + lineIndex += 1 + // There are 10 colors for lines, start over if we are at index 10 + if lineIndex > 9 { + lineIndex = 0 + } } } - } - - DispatchQueue.main.async { let annotationCount = waypoints.count + positions.count if annotationCount != mapView.annotations.count { mapView.removeAnnotations(mapView.annotations) - let latest = positions.filter { $0.latest == true } mapView.addAnnotations(waypoints) mapView.setUserTrackingMode(userTrackingMode, animated: true) if userTrackingMode == MKUserTrackingMode.none { mapView.showsUserLocation = false - switch centeringMode { - case .allAnnotations: - mapView.addAnnotations(showNodeHistory ? positions : latest) - if recenter && userTrackingMode == MKUserTrackingMode.none { - mapView.fitAllAnnotations() - } - case .allPositions: - if recenter && userTrackingMode == MKUserTrackingMode.none { - mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true) + mapView.addAnnotations(showNodeHistory ? positions : latest) + if recenter { + if showRouteLines || showNodeHistory { + mapView.fit(annotations: showNodeHistory ? positions : positions, andShow: false) } else { - mapView.addAnnotations(showNodeHistory ? positions : latest) + mapView.fitAllAnnotations() } } } else { // Centering Done by tracking mode - mapView.addAnnotations(latest) + mapView.addAnnotations(showNodeHistory ? positions : latest) mapView.showsUserLocation = true } } @@ -177,7 +174,6 @@ struct MapViewSwiftUI: UIViewRepresentable { var parent: MapViewSwiftUI var longPressRecognizer = UILongPressGestureRecognizer() - var overlays: [Overlay] = [] init(_ parent: MapViewSwiftUI) { self.parent = parent @@ -187,7 +183,6 @@ struct MapViewSwiftUI: UIViewRepresentable { self.longPressRecognizer.cancelsTouchesInView = true self.longPressRecognizer.delegate = self self.parent.mapView.addGestureRecognizer(longPressRecognizer) - self.overlays = [] } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { @@ -356,8 +351,10 @@ struct MapViewSwiftUI: UIViewRepresentable { } else { if let routePolyline = overlay as? MKPolyline { + let titleString = routePolyline.title ?? "None-0" + let index = Int(titleString.components(separatedBy: "-").last ?? "0") let renderer = MKPolylineRenderer(polyline: routePolyline) - renderer.strokeColor = UIColor.systemIndigo + renderer.strokeColor = parent.colors[index ?? 0] renderer.lineWidth = 5 return renderer } @@ -464,30 +461,30 @@ struct MapViewSwiftUI: UIViewRepresentable { } } - public struct Overlay { - - public static func == (lhs: MapViewSwiftUI.Overlay, rhs: MapViewSwiftUI.Overlay) -> Bool { - // maybe to use in the future for comparison of full array - lhs.shape.coordinate.latitude == rhs.shape.coordinate.latitude && - lhs.shape.coordinate.longitude == rhs.shape.coordinate.longitude && - lhs.fillColor == rhs.fillColor - } - - var shape: MKOverlay - var fillColor: UIColor? - var strokeColor: UIColor? - var lineWidth: CGFloat - - public init( - shape: MKOverlay, - fillColor: UIColor? = nil, - strokeColor: UIColor? = nil, - lineWidth: CGFloat = 0 - ) { - self.shape = shape - self.fillColor = fillColor - self.strokeColor = strokeColor - self.lineWidth = lineWidth - } - } +// public struct Overlay { +// +// public static func == (lhs: MapViewSwiftUI.Overlay, rhs: MapViewSwiftUI.Overlay) -> Bool { +// // maybe to use in the future for comparison of full array +// lhs.shape.coordinate.latitude == rhs.shape.coordinate.latitude && +// lhs.shape.coordinate.longitude == rhs.shape.coordinate.longitude && +// lhs.fillColor == rhs.fillColor +// } +// +// var shape: MKOverlay +// var fillColor: UIColor? +// var strokeColor: UIColor? +// var lineWidth: CGFloat +// +// public init( +// shape: MKOverlay, +// fillColor: UIColor? = nil, +// strokeColor: UIColor? = nil, +// lineWidth: CGFloat = 0 +// ) { +// self.shape = shape +// self.fillColor = fillColor +// self.strokeColor = strokeColor +// self.lineWidth = lineWidth +// } +// } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 3f3f518a..38544cde 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -14,6 +14,8 @@ struct NodeDetail: View { @EnvironmentObject var bleManager: BLEManager @Environment(\.colorScheme) var colorScheme: ColorScheme @AppStorage("meshMapType") private var meshMapType = "standard" + @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false + @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false @State private var mapType: MKMapType = .standard @State var waypointCoordinate: CLLocationCoordinate2D? @State var editingWaypoint: Int = 0 @@ -24,7 +26,6 @@ struct NodeDetail: View { @State private var showingRebootConfirm: Bool = false @State private var presentingWaypointForm = false @State private var showOverlays: Bool = true - @State private var overlays: [MapViewSwiftUI.Overlay] = [] @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( mapName: "offlinemap", tileType: "png", @@ -47,7 +48,8 @@ struct NodeDetail: View { @State private var attributionLink: URL? @State private var attributionLogo: URL? - + + var body: some View { let hwModelString = node.user?.hwModel ?? "UNSET" @@ -57,7 +59,8 @@ struct NodeDetail: View { VStack { if node.positions?.count ?? 0 > 0 { ZStack { - let annotations = node.positions?.array as? [PositionEntity] ?? [] + let positionArray = node.positions?.array as? [PositionEntity] ?? [] + let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) } ZStack { MapViewSwiftUI(onLongPress: { coord in waypointCoordinate = coord @@ -68,15 +71,12 @@ struct NodeDetail: View { editingWaypoint = wpId presentingWaypointForm = true } - }, positions: annotations, waypoints: Array(waypoints), + }, positions: todaysPositions, waypoints: Array(waypoints), mapViewType: mapType, userTrackingMode: MKUserTrackingMode.none, - centeringMode: .allPositions, - showRouteLines: false, - showNodeHistory: true, - centerOnPositionsOnly: true, - customMapOverlay: self.customMapOverlay, - overlays: self.overlays + showNodeHistory: meshMapShowNodeHistory, + showRouteLines: meshMapShowRouteLines, + customMapOverlay: self.customMapOverlay ) VStack(alignment: .leading) { Spacer() @@ -177,14 +177,14 @@ struct NodeDetail: View { .fixedSize() } } - - if node.telemetries?.count ?? 0 >= 1 { - - let mostRecent = node.telemetries?.lastObject as? TelemetryEntity + let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) + if deviceMetrics?.count ?? 0 >= 1 { + + let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity Divider() VStack(alignment: .center) { BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) - if mostRecent?.voltage ?? 0 > 0 { + if mostRecent?.voltage ?? 0 > 0.0 { Text(String(format: "%.2f", mostRecent?.voltage ?? 0.0) + " V") .font(.title) @@ -288,8 +288,10 @@ struct NodeDetail: View { } } - if node.telemetries?.count ?? 0 >= 1 { - let mostRecent = node.telemetries?.lastObject as? TelemetryEntity + let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) + if deviceMetrics?.count ?? 0 >= 1 { + + let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity Divider() VStack(alignment: .center) { BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0)) diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 297d8136..4cba5089 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -31,9 +31,9 @@ struct NodeMap: View { } @AppStorage("meshMapType") private var meshMapType = "hybridFlyover" @AppStorage("meshMapUserTrackingMode") private var meshMapUserTrackingMode = 0 - @AppStorage("meshMapCenteringMode") private var meshMapCenteringMode = 0 + @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false + @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false - // && nodePosition != nil @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none) private var positions: FetchedResults @@ -46,7 +46,6 @@ struct NodeMap: View { @State private var mapType: MKMapType = .standard @State private var userTrackingMode: MKUserTrackingMode = .none - @State private var mapCenteringMode: CenteringMode = .allAnnotations @State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation @State var editingWaypoint: Int = 0 @State private var presentingWaypointForm = false @@ -55,11 +54,6 @@ struct NodeMap: View { tileType: "png", canReplaceMapContent: true ) - @State private var overlays: [MapViewSwiftUI.Overlay] = [] - -// init() { -// _positions = FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none) -// } var body: some View { @@ -83,12 +77,9 @@ struct NodeMap: View { waypoints: Array(waypoints), mapViewType: mapType, userTrackingMode: userTrackingMode, - centeringMode: mapCenteringMode, - showRouteLines: false, - showNodeHistory: true, - centerOnPositionsOnly: false, - customMapOverlay: self.customMapOverlay, - overlays: self.overlays + showNodeHistory: meshMapShowNodeHistory, + showRouteLines: meshMapShowRouteLines, + customMapOverlay: self.customMapOverlay ) VStack { Spacer() @@ -124,7 +115,6 @@ struct NodeMap: View { UIApplication.shared.isIdleTimerDisabled = true self.bleManager.context = context self.bleManager.userSettings = userSettings - mapCenteringMode = CenteringMode(rawValue: meshMapCenteringMode) ?? CenteringMode.allAnnotations userTrackingMode = UserTrackingModes(rawValue: meshMapUserTrackingMode)?.MKUserTrackingModeValue() ?? MKUserTrackingMode.none switch meshMapType { case "standard": diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 70b525cc..ec83fae9 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -80,19 +80,22 @@ struct AppSettings: View { if userSettings.meshMapUserTrackingMode == 0 { - Picker("map.centering", selection: $userSettings.meshMapCenteringMode) { - ForEach(CenteringMode.allCases) { cm in - Text(cm.description) - } - } - .pickerStyle(DefaultPickerStyle()) - Toggle(isOn: $userSettings.meshMapRecentering) { Label("map.recentering", systemImage: "camera.metering.center.weighted") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } + Toggle(isOn: $userSettings.meshMapShowNodeHistory) { + + Label("Show Node History", systemImage: "building.columns.fill") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Toggle(isOn: $userSettings.meshMapShowRouteLines) { + + Label("Show Route Lines", systemImage: "road.lanes") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } } HStack { diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 44d9cbfc..5ca297dd 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -75,7 +75,6 @@ struct ShareChannels: View { Toggle("Channel 0 Included", isOn: $includeChannel0) .toggleStyle(.switch) .labelsHidden() - .disabled(channel.role == 1) Text(((channel.name!.isEmpty ? "Primary" : channel.name) ?? "Primary").camelCaseToWords()) if channel.psk?.hexDescription.count ?? 0 < 3 { Image(systemName: "lock.slash") @@ -264,6 +263,7 @@ struct ShareChannels: View { bleManager.context = context generateChannelSet() } + .onChange(of: includeChannel0) { _ in generateChannelSet() } .onChange(of: includeChannel1) { _ in generateChannelSet() } .onChange(of: includeChannel2) { _ in generateChannelSet() } .onChange(of: includeChannel3) { _ in generateChannelSet() }