Merge pull request #654 from 72A12F4E/main

Address several SwiftLint issues
This commit is contained in:
Garth Vander Houwen 2024-06-01 07:31:30 -07:00 committed by GitHub
commit 79e23d561e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
108 changed files with 1230 additions and 1232 deletions

View file

@ -1,3 +1,7 @@
# Exclude automatically generated Swift files
excluded:
- Meshtastic/Protobufs
line_length: 400
type_name:
@ -46,3 +50,4 @@ disabled_rules: # rule identifiers to exclude from running
nesting:
type_level:
warning: 3

View file

@ -1032,10 +1032,10 @@
isa = PBXNativeTarget;
buildConfigurationList = DDC2E17E26CE248F0042C5E4 /* Build configuration list for PBXNativeTarget "Meshtastic" */;
buildPhases = (
BB450974275599CE00509624 /* ShellScript */,
DDC2E15026CE248E0042C5E4 /* Sources */,
DDC2E15126CE248E0042C5E4 /* Frameworks */,
DDC2E15226CE248E0042C5E4 /* Resources */,
BB450974275599CE00509624 /* ShellScript */,
DDDE5A0829AF163F00490C6C /* Embed Foundation Extensions */,
);
buildRules = (
@ -1177,6 +1177,7 @@
/* Begin PBXShellScriptBuildPhase section */
BB450974275599CE00509624 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);

View file

@ -21,7 +21,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
case repeater = 4
case router = 2
case routerClient = 3
var id: Int { self.rawValue }
var name: String {
switch self {
@ -48,7 +48,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
case .lostAndFound:
return "Lost and Found"
}
}
var description: String {
switch self {
@ -76,7 +76,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
return "device.role.lostandfound".localized
}
}
var systemName: String {
switch self {
case .client:

View file

@ -28,7 +28,7 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
case my_919 = 17
case sg_923 = 18
case lora24 = 13
var topic: String {
var topic: String {
switch self {
case .unset:
"UNSET"

View file

@ -103,9 +103,9 @@ enum GpsMode: Int, CaseIterable, Equatable {
case enabled = 1
case disabled = 0
case notPresent = 2
var id: Int { self.rawValue }
var description: String {
switch self {
case .disabled:

View file

@ -15,7 +15,7 @@ enum ActivityType: Int, CaseIterable, Identifiable {
case driving = 3
case overlanding = 4
case skiing = 5
var id: Int { self.rawValue }
var description: String {
switch self {
@ -33,7 +33,7 @@ enum ActivityType: Int, CaseIterable, Identifiable {
return "routes.activitytype.skiing".localized
}
}
var fileNameString: String {
switch self {
case .walking:

View file

@ -15,7 +15,7 @@ enum Aqi: Int, CaseIterable, Identifiable {
case unhealthy = 3
case veryUnhealthy = 4
case hazardous = 5
var id: Int { self.rawValue }
var description: String {
switch self {
@ -65,7 +65,7 @@ enum Aqi: Int, CaseIterable, Identifiable {
return Range(301...500)
}
}
static func getAqi(for value: Int) -> Aqi {
let aqi: Aqi
switch value {
@ -96,7 +96,7 @@ enum Iaq: Int, CaseIterable, Identifiable {
case heavilyPolluted = 4
case severelyPolluted = 5
case extremelyPolluted = 6
var id: Int { self.rawValue }
var description: String {
switch self {
@ -134,7 +134,7 @@ enum Iaq: Int, CaseIterable, Identifiable {
return .brown
}
}
var range: Range<Int> {
switch self {
case .excellent:

View file

@ -118,7 +118,6 @@ func positionToCsvFile(positions: [PositionEntity]) -> String {
return csvString
}
func routeToCsvFile(locations: [LocationEntity]) -> String {
var csvString: String = ""
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)

View file

@ -8,15 +8,15 @@
import Foundation
extension Bundle {
public var appName: String { getInfo("CFBundleName") }
public var displayName: String { getInfo("CFBundleDisplayName") }
public var language: String { getInfo("CFBundleDevelopmentRegion") }
public var identifier: String { getInfo("CFBundleIdentifier") }
public var copyright: String { getInfo("NSHumanReadableCopyright").replacingOccurrences(of: "\\\\n", with: "\n") }
public var appBuild: String { getInfo("CFBundleVersion") }
public var appVersionLong: String { getInfo("CFBundleShortVersionString") }
//public var appVersionShort: String { getInfo("CFBundleShortVersion") }
public var appName: String { getInfo("CFBundleName") }
public var displayName: String { getInfo("CFBundleDisplayName") }
public var language: String { getInfo("CFBundleDevelopmentRegion") }
public var identifier: String { getInfo("CFBundleIdentifier") }
public var copyright: String { getInfo("NSHumanReadableCopyright").replacingOccurrences(of: "\\\\n", with: "\n") }
public var appBuild: String { getInfo("CFBundleVersion") }
public var appVersionLong: String { getInfo("CFBundleShortVersionString") }
// public var appVersionShort: String { getInfo("CFBundleShortVersion") }
fileprivate func getInfo(_ str: String) -> String { infoDictionary?[str] as? String ?? "⚠️" }
}

View file

@ -28,19 +28,19 @@ extension [CLLocationCoordinate2D] {
/// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
/// Returns a positive value, if OAB makes a counter-clockwise turn,
/// negative for clockwise turn, and zero if the points are collinear.
func cross(P: CLLocationCoordinate2D, A: CLLocationCoordinate2D, B: CLLocationCoordinate2D) -> Double {
let part1 = (A.longitude - P.longitude) * (B.latitude - P.latitude)
let part2 = (A.latitude - P.latitude) * (B.longitude - P.longitude)
return part1 - part2;
func cross(p: CLLocationCoordinate2D, a: CLLocationCoordinate2D, b: CLLocationCoordinate2D) -> Double {
let part1 = (a.longitude - p.longitude) * (b.latitude - p.latitude)
let part2 = (a.latitude - p.latitude) * (b.longitude - p.longitude)
return part1 - part2
}
// Sort points lexicographically
let points = self.sorted() {
let points = self.sorted {
$0.longitude == $1.longitude ? $0.latitude < $1.latitude : $0.longitude < $1.longitude
}
// Build the lower hull
var lower: [CLLocationCoordinate2D] = []
for p in points {
while lower.count >= 2 && cross(P: lower[lower.count - 2], A: lower[lower.count - 1], B: p) <= 0 {
while lower.count >= 2 && cross(p: lower[lower.count - 2], a: lower[lower.count - 1], b: p) <= 0 {
lower.removeLast()
}
lower.append(p)
@ -48,7 +48,7 @@ extension [CLLocationCoordinate2D] {
// Build upper hull
var upper: [CLLocationCoordinate2D] = []
for p in points.reversed() {
while upper.count >= 2 && cross(P: upper[upper.count-2], A: upper[upper.count-1], B: p) <= 0 {
while upper.count >= 2 && cross(p: upper[upper.count-2], a: upper[upper.count-1], b: p) <= 0 {
upper.removeLast()
}
upper.append(p)

View file

@ -12,13 +12,13 @@ extension ChannelEntity {
self.value(forKey: "allPrivateMessages") as? [MessageEntity] ?? [MessageEntity]()
}
var unreadMessages: Int {
let unreadMessages = allPrivateMessages.filter{ ($0 as AnyObject).read == false }
let unreadMessages = allPrivateMessages.filter { ($0 as AnyObject).read == false }
return unreadMessages.count
}
var protoBuf: Channel {
var channel = Channel()
channel.index = self.index

View file

@ -39,4 +39,3 @@ extension LocationEntity {
}
}
}

View file

@ -8,17 +8,17 @@
import Foundation
extension MyInfoEntity {
var messageList: [MessageEntity] {
self.value(forKey: "allMessages") as? [MessageEntity] ?? [MessageEntity]()
}
var unreadMessages: Int {
let unreadMessages = messageList.filter{ ($0 as AnyObject).read == false && ($0 as AnyObject).isEmoji == false }
let unreadMessages = messageList.filter { ($0 as AnyObject).read == false && ($0 as AnyObject).isEmoji == false }
return unreadMessages.count
}
var hasAdmin: Bool {
let adminChannel = channels?.filter{ ($0 as AnyObject).name?.lowercased() == "admin" }
let adminChannel = channels?.filter { ($0 as AnyObject).name?.lowercased() == "admin" }
return adminChannel?.count ?? 0 > 0
}
}

View file

@ -9,37 +9,36 @@ import Foundation
import CoreData
extension NodeInfoEntity {
var hasPositions: Bool {
return positions?.count ?? 0 > 0
}
var hasDeviceMetrics: Bool {
let deviceMetrics = telemetries?.filter{ ($0 as AnyObject).metricsType == 0 }
let deviceMetrics = telemetries?.filter { ($0 as AnyObject).metricsType == 0 }
return deviceMetrics?.count ?? 0 > 0
}
var hasEnvironmentMetrics: Bool {
let environmentMetrics = telemetries?.filter{ ($0 as AnyObject).metricsType == 1 }
let environmentMetrics = telemetries?.filter { ($0 as AnyObject).metricsType == 1 }
return environmentMetrics?.count ?? 0 > 0
}
var hasDetectionSensorMetrics: Bool {
return user?.sensorMessageList.count ?? 0 > 0
}
var hasTraceRoutes: Bool {
return traceRoutes?.count ?? 0 > 0
}
var hasPax: Bool {
return pax?.count ?? 0 > 0
}
var isStoreForwardRouter: Bool {
return storeForwardConfig?.isRouter ?? false
}
var isOnline: Bool {
let fifteenMinutesAgo = Calendar.current.date(byAdding: .minute, value: -15, to: Date())
if lastHeard?.compare(fifteenMinutesAgo!) == .orderedDescending {
@ -50,13 +49,13 @@ extension NodeInfoEntity {
}
public func createNodeInfo(num: Int64, context: NSManagedObjectContext) -> NodeInfoEntity {
let newNode = NodeInfoEntity(context: context)
newNode.id = Int64(num)
newNode.num = Int64(num)
let newUser = UserEntity(context: context)
newUser.num = Int64(num)
let userId = String(format:"%2X", num)
let userId = String(format: "%2X", num)
newUser.userId = "!\(userId)"
let last4 = String(userId.suffix(4))
newUser.longName = "Meshtastic \(last4)"

View file

@ -11,7 +11,7 @@ import MapKit
import SwiftUI
extension PositionEntity {
static func allPositionsFetchRequest() -> NSFetchRequest<PositionEntity> {
let request: NSFetchRequest<PositionEntity> = PositionEntity.fetchRequest()
request.fetchLimit = 1000
@ -20,20 +20,20 @@ extension PositionEntity {
request.returnsDistinctResults = true
request.sortDescriptors = [NSSortDescriptor(key: "time", ascending: false)]
let positionPredicate = NSPredicate(format: "nodePosition != nil && (nodePosition.user.shortName != nil || nodePosition.user.shortName != '') && latest == true")
let pointOfInterest = LocationHelper.currentLocation
if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude {
let D: Double = UserDefaults.meshMapDistance * 1.1
let R: Double = 6371009
let d: Double = UserDefaults.meshMapDistance * 1.1
let r: Double = 6371009
let meanLatitidue = pointOfInterest.latitude * .pi / 180
let deltaLatitude = D / R * 180 / .pi
let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi
let deltaLatitude = d / r * 180 / .pi
let deltaLongitude = d / (r * cos(meanLatitidue)) * 180 / .pi
let minLatitude: Double = pointOfInterest.latitude - deltaLatitude
let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude
let minLongitude: Double = pointOfInterest.longitude - deltaLongitude
let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude
let distancePredicate = NSPredicate(format: "(%lf <= (longitudeI / 1e7)) AND ((longitudeI / 1e7) <= %lf) AND (%lf <= (latitudeI / 1e7)) AND ((latitudeI / 1e7) <= %lf)", minLongitude, maxLongitude,minLatitude, maxLatitude)
let distancePredicate = NSPredicate(format: "(%lf <= (longitudeI / 1e7)) AND ((longitudeI / 1e7) <= %lf) AND (%lf <= (latitudeI / 1e7)) AND ((latitudeI / 1e7) <= %lf)", minLongitude, maxLongitude, minLatitude, maxLatitude)
request.predicate = NSCompoundPredicate(type: .and, subpredicates: [positionPredicate, distancePredicate])
} else {
request.predicate = positionPredicate

View file

@ -9,7 +9,6 @@ import Foundation
import CoreData
extension UserEntity {
var messageList: [MessageEntity] {
self.value(forKey: "allMessages") as? [MessageEntity] ?? [MessageEntity]()
@ -18,22 +17,21 @@ extension UserEntity {
var adminMessageList: [MessageEntity] {
self.value(forKey: "adminMessages") as? [MessageEntity] ?? [MessageEntity]()
}
var sensorMessageList: [MessageEntity] {
self.value(forKey: "detectionSensorMessages") as? [MessageEntity] ?? [MessageEntity]()
}
var unreadMessages: Int {
let unreadMessages = messageList.filter{ ($0 as AnyObject).read == false }
let unreadMessages = messageList.filter { ($0 as AnyObject).read == false }
return unreadMessages.count
}
}
public func createUser(num: Int64, context: NSManagedObjectContext) -> UserEntity {
let newUser = UserEntity(context: context)
newUser.num = Int64(num)
let userId = String(format:"%2X", num)
let userId = String(format: "%2X", num)
newUser.userId = "!\(userId)"
let last4 = String(userId.suffix(4))
newUser.longName = "Meshtastic \(last4)"

View file

@ -10,13 +10,13 @@ import MapKit
import SwiftUI
extension WaypointEntity {
static func allWaypointssFetchRequest() -> NSFetchRequest<WaypointEntity> {
let request: NSFetchRequest<WaypointEntity> = WaypointEntity.fetchRequest()
request.fetchLimit = 50
//request.fetchBatchSize = 1
//request.returnsObjectsAsFaults = false
//request.includesSubentities = true
// request.fetchBatchSize = 1
// request.returnsObjectsAsFaults = false
// request.includesSubentities = true
request.returnsDistinctResults = true
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: false)]
request.predicate = NSPredicate(format: "expire == nil || expire >= %@", Date() as NSDate)

View file

@ -8,7 +8,7 @@
import Foundation
extension Date {
func formattedDate(format: String) -> String {
let dateformat = DateFormatter()
dateformat.dateFormat = format
@ -20,12 +20,12 @@ extension Date {
}
func relativeTimeOfDay() -> String {
let hour = Calendar.current.component(.hour, from: self)
switch hour {
case 6..<12 : return "relativetimeofday.morning".localized
case 12 : return "relativetimeofday.midday".localized
case 13..<17 : return "relativetimeofday.afternoon".localized
case 17..<22 : return "relativetimeofday.evening".localized
case 6..<12: return "relativetimeofday.morning".localized
case 12: return "relativetimeofday.midday".localized
case 13..<17: return "relativetimeofday.afternoon".localized
case 17..<22: return "relativetimeofday.evening".localized
default: return "relativetimeofday.nighttime".localized
}
}

View file

@ -16,7 +16,7 @@ extension PlottableMeasurement: Plottable where UnitType == UnitLength {
var primitivePlottable: Double {
self.measurement.converted(to: .meters).value
}
init?(primitivePlottable: Double) {
self.init(
measurement: Measurement(

View file

@ -16,7 +16,7 @@ extension UIColor {
var green: CGFloat = 0
var alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return UIColor(
red: add(componentDelta, toComponent: red),
@ -37,7 +37,7 @@ extension UIColor {
private func add(_ value: CGFloat, toComponent: CGFloat) -> CGFloat {
return max(0, min(1, toComponent + value))
}
static var random: UIColor {
return UIColor(
red: .random(in: 0...1),

View file

@ -8,10 +8,10 @@
import Foundation
extension URL {
func regularFileAllocatedSize() throws -> UInt64 {
let resourceValues = try self.resourceValues(forKeys: allocatedSizeResourceKeys)
guard resourceValues.isRegularFile ?? false else {
return 0
}

View file

@ -29,7 +29,7 @@ struct UserDefault<T: Decodable> {
return value
}
return UserDefaults.standard.object(forKey: key.rawValue) as? T ?? defaultValue
}
set {
@ -95,10 +95,10 @@ extension UserDefaults {
@UserDefault(.meshMapDistance, defaultValue: 800000)
static var meshMapDistance: Double
@UserDefault(.enableMapWaypoints, defaultValue: false)
static var enableMapWaypoints: Bool
@UserDefault(.enableMapRecentering, defaultValue: false)
static var enableMapRecentering: Bool
@ -146,13 +146,13 @@ extension UserDefaults {
@UserDefault(.enableSmartPosition, defaultValue: false)
static var enableSmartPosition: Bool
@UserDefault(.channelMessageNotifications, defaultValue: true)
static var channelMessageNotifications: Bool
@UserDefault(.newNodeNotifications, defaultValue: true)
static var newNodeNotifications: Bool
@UserDefault(.lowBatteryNotifications, defaultValue: true)
static var lowBatteryNotifications: Bool

File diff suppressed because it is too large Load diff

View file

@ -8,10 +8,6 @@ import SwiftUI
class SwiftUIEmojiTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
}
func setEmoji() {
_ = self.textInputMode
}

View file

@ -37,7 +37,7 @@ class LocalNotificationManager {
content.body = notification.content
content.sound = .default
content.interruptionLevel = .timeSensitive
if notification.target != nil {
content.userInfo["target"] = notification.target
}

View file

@ -5,8 +5,8 @@ import MapKit
class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
static let shared = LocationHelper()
var locationManager = CLLocationManager()
//@Published var region = MKCoordinateRegion()
// @Published var region = MKCoordinateRegion()
@Published var authorizationStatus: CLAuthorizationStatus?
override init() {
super.init()
@ -47,7 +47,7 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
}
return sats
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedAlways:
@ -67,7 +67,7 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location manager error: \(error.localizedDescription)")

View file

@ -16,7 +16,7 @@ import CoreLocation
private let manager: CLLocationManager
private var background: CLBackgroundActivitySession?
var enableSmartPosition: Bool = UserDefaults.enableSmartPosition
@Published var locationsArray: [CLLocation]
@Published var isStationary = false
@Published var count = 0
@ -25,12 +25,12 @@ import CoreLocation
@Published var recordingStarted: Date?
@Published var distanceTraveled = 0.0
@Published var elevationGain = 0.0
@Published
var updatesStarted: Bool = UserDefaults.standard.bool(forKey: "liveUpdatesStarted") {
didSet { UserDefaults.standard.set(updatesStarted, forKey: "liveUpdatesStarted") }
}
@Published
var backgroundActivity: Bool = UserDefaults.standard.bool(forKey: "BGActivitySessionStarted") {
didSet {
@ -38,27 +38,27 @@ import CoreLocation
UserDefaults.standard.set(backgroundActivity, forKey: "BGActivitySessionStarted")
}
}
private init() {
self.manager = CLLocationManager() // Creating a location manager instance is safe to call here in `MainActor`.
self.manager.allowsBackgroundLocationUpdates = true
locationsArray = [CLLocation]()
}
func startLocationUpdates() {
if self.manager.authorizationStatus == .notDetermined {
self.manager.requestWhenInUseAuthorization()
}
print("Starting location updates")
Task() {
Task {
do {
self.updatesStarted = true
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
if !self.updatesStarted { break }
if !self.updatesStarted { break }
if let loc = update.location {
self.isStationary = update.isStationary
var locationAdded: Bool
locationAdded = addLocation(loc, smartPostion: enableSmartPosition)
if !isRecording && locationAdded {
@ -74,12 +74,12 @@ import CoreLocation
return
}
}
func stopLocationUpdates() {
print("Stopping location updates")
self.updatesStarted = false
}
func addLocation(_ location: CLLocation, smartPostion: Bool) -> Bool {
if smartPostion {
let age = -location.timestamp.timeIntervalSinceNow
@ -111,9 +111,9 @@ import CoreLocation
}
return true
}
static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090)
static var satsInView: Int {
var sats = 0
if let newLocation = shared.locationsArray.last {

View file

@ -226,7 +226,7 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS
if fetchedNode.count > 0 {
fetchedNode[0].metadata = newMetadata
} else {
if fromNum > 0 {
let newNode = createNodeInfo(num: Int64(fromNum), context: context)
newNode.metadata = newMetadata
@ -284,7 +284,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard)))
newNode.snr = nodeInfo.snr
if nodeInfo.hasUser {
let newUser = UserEntity(context: context)
newUser.userId = nodeInfo.user.id
newUser.num = Int64(nodeInfo.num)
@ -307,7 +307,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
position.longitudeI = nodeInfo.position.longitudeI
position.altitude = nodeInfo.position.altitude
position.satsInView = Int32(nodeInfo.position.satsInView)
position.speed = Int32(nodeInfo.position.groundSpeed)
position.speed = Int32(nodeInfo.position.groundSpeed)
position.heading = Int32(nodeInfo.position.groundTrack)
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time)))
var newPostions = [PositionEntity]()
@ -349,7 +349,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
fetchedNode[0].hopsAway = Int32(nodeInfo.hopsAway)
if nodeInfo.hasUser {
if (fetchedNode[0].user == nil) {
if fetchedNode[0].user == nil {
fetchedNode[0].user = UserEntity(context: context)
}
fetchedNode[0].user!.userId = nodeInfo.user.id
@ -360,9 +360,9 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
fetchedNode[0].user!.isLicensed = nodeInfo.user.isLicensed
fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue)
fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
} else {
if (fetchedNode[0].user == nil && nodeInfo.num > Int16.max) {
} else {
if fetchedNode[0].user == nil && nodeInfo.num > Int16.max {
let newUser = createUser(num: Int64(nodeInfo.num), context: context)
fetchedNode[0].user = newUser
}
@ -550,25 +550,25 @@ func adminResponseAck (packet: MeshPacket, context: NSManagedObjectContext) {
}
}
func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat("mesh.log.paxcounter %@".localized, String(packet.from))
MeshLogger.log("🧑‍🤝‍🧑 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity]
if let paxMessage = try? Paxcount(serializedData: packet.decoded.payload) {
let newPax = PaxCounterEntity(context: context)
newPax.ble = Int32(truncatingIfNeeded: paxMessage.ble)
newPax.wifi = Int32(truncatingIfNeeded: paxMessage.wifi)
newPax.uptime = Int32(truncatingIfNeeded: paxMessage.uptime)
newPax.time = Date()
if (fetchedNode?.count ?? 0 > 0) {
if fetchedNode?.count ?? 0 > 0 {
guard let mutablePax = fetchedNode?[0].pax!.mutableCopy() as? NSMutableOrderedSet else {
return
}
@ -584,7 +584,7 @@ func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) {
}
}
} catch {
}
}
@ -619,7 +619,7 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
}
fetchedMessage![0].ackSNR = packet.rxSnr
fetchedMessage![0].ackTimestamp = Int32(truncatingIfNeeded: packet.rxTime)
if fetchedMessage![0].toUser != nil {
fetchedMessage![0].toUser!.objectWillChange.send()
} else {
@ -772,20 +772,20 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec
}
}
let rangeTest = messageText?.contains(rangeTestRegex) ?? false && messageText?.starts(with: "seq ") ?? false
if !wantRangeTestPackets && rangeTest {
return
}
var storeForwardBroadcast = false
if storeForward {
if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) {
messageText = String(bytes: storeAndForwardMessage.text, encoding: .utf8)
messageText = String(bytes: storeAndForwardMessage.text, encoding: .utf8)
if storeAndForwardMessage.rr == .routerTextBroadcast {
storeForwardBroadcast = true
}
}
}
if messageText?.count ?? 0 > 0 {
MeshLogger.log("💬 \("mesh.log.textmessage.received".localized)")
@ -837,7 +837,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec
messageSaved = true
if messageSaved {
if packet.decoded.portnum == PortNum.detectionSensorApp && !UserDefaults.enableDetectionNotifications {
return
}
@ -876,7 +876,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec
if !fetchedMyInfo.isEmpty {
appState.unreadChannelMessages = fetchedMyInfo[0].unreadMessages
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] {
if channel.index == newMessage.channel {
context.refresh(channel, mergeChanges: true)

View file

@ -39,7 +39,7 @@ class MqttClientProxyManager {
let minimumVersion = "2.3.2"
let currentVersion = UserDefaults.firmwareVersion
let supportedVersion = minimumVersion.compare(currentVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(currentVersion, options: .numeric) == .orderedSame
if let host = host {
let port = defaultServerPort
let username = node.mqttConfig?.username

View file

@ -9,7 +9,7 @@ import TipKit
@available(iOS 17.0, *)
@main
struct MeshtasticAppleApp: App {
@UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate
let persistenceController = PersistenceController.shared
@ObservedObject private var bleManager: BLEManager = BLEManager.shared
@ -28,7 +28,7 @@ struct MeshtasticAppleApp: App {
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(bleManager)
.sheet(isPresented: $saveChannels) {
SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager)
SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager)
.presentationDetents([.large])
.presentationDragIndicator(.visible)
}
@ -71,13 +71,12 @@ struct MeshtasticAppleApp: App {
} else if path.starts(with: "meshtastic://nodes") {
AppState.shared.tabSelection = Tab.nodes
}
} else {
saveChannels = false
print("User wants to import a MBTILES offline map file: \(self.incomingUrl?.absoluteString ?? "No Tiles link")")
}
/// Only do the map tiles stuff if it is enabled
if UserDefaults.enableOfflineMapsMBTiles {
/// we are expecting a .mbtiles map file that contains raster data
@ -87,30 +86,30 @@ struct MeshtasticAppleApp: App {
let destination = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false)
if !self.saveChannels {
// tell the system we want the file please
guard url.startAccessingSecurityScopedResource() else {
return
}
// do we need to delete an old one?
if fileManager.fileExists(atPath: destination.path) {
print(" Found an old map file. Deleting it")
try? fileManager.removeItem(atPath: destination.path)
}
do {
try fileManager.copyItem(at: url, to: destination)
} catch {
print("Copy MB Tile file failed. Error: \(error)")
}
if fileManager.fileExists(atPath: destination.path) {
print(" Saved the map file")
// need to tell the map view that it needs to update and try loading the new overlay
UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastUpdatedLocalMapFile")
} else {
print("💥 Didn't save the map file")
}
@ -168,6 +167,6 @@ class AppState: ObservableObject {
@Published var unreadDirectMessages: Int = 0
@Published var unreadChannelMessages: Int = 0
@Published var firmwareVersion: String = "0.0.0"
//@Published var connectedNode: NodeInfoEntity?
// @Published var connectedNode: NodeInfoEntity?
@Published var navigationPath: String?
}

View file

@ -8,12 +8,12 @@
import SwiftUI
class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
print("🚀 Meshtstic Apple App launched!")
// Default User Default Values
UserDefaults.standard.register(defaults: ["meshMapRecentering" : true])
UserDefaults.standard.register(defaults: ["meshMapShowNodeHistory" : true])
UserDefaults.standard.register(defaults: ["meshMapShowRouteLines" : true])
UserDefaults.standard.register(defaults: ["meshMapRecentering": true])
UserDefaults.standard.register(defaults: ["meshMapShowNodeHistory": true])
UserDefaults.standard.register(defaults: ["meshMapShowRouteLines": true])
UNUserNotificationCenter.current().delegate = self
if #available(iOS 17.0, macOS 14.0, *) {
let locationsHandler = LocationsHandler.shared

View file

@ -60,7 +60,7 @@ class PersistenceController {
do {
try persistentStoreCoordinator.destroyPersistentStore(at: url, ofType: NSSQLiteStoreType, options: nil)
print("💥 CoreData database truncated. All app data has been erased.")
do {
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
} catch let error {

View file

@ -26,7 +26,7 @@ public func getNodeInfo(id: Int64, context: NSManagedObjectContext) -> NodeInfoE
}
public func getStoreAndForwardMessageIds(seconds: Int, context: NSManagedObjectContext) -> [UInt32] {
let time = seconds * -1
let fetchMessagesRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MessageEntity")
let timeRange = Calendar.current.date(byAdding: .minute, value: time, to: Date())

View file

@ -110,12 +110,12 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes
let persistenceController = PersistenceController.shared.container
for i in 0...persistenceController.managedObjectModel.entities.count-1 {
let entity = persistenceController.managedObjectModel.entities[i]
let query = NSFetchRequest<NSFetchRequestResult>(entityName: entity.name!)
var deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
let entityName = entity.name ?? "UNK"
if includeRoutes {
deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
} else if !includeRoutes {
@ -153,7 +153,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
newNode.snr = packet.rxSnr
newNode.rssi = packet.rxRssi
newNode.viaMqtt = packet.viaMqtt
if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum {
newNode.channel = Int32(packet.channel)
}
@ -161,16 +161,16 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
newNode.hopsAway = Int32(nodeInfoMessage.hopsAway)
newNode.favorite = nodeInfoMessage.isFavorite
}
if let newUserMessage = try? User(serializedData: packet.decoded.payload) {
if newUserMessage.id.isEmpty {
if newUserMessage.id.isEmpty {
if packet.from > Int16.max {
let newUser = createUser(num: Int64(packet.from), context: context)
newNode.user = newUser
}
} else {
let newUser = UserEntity(context: context)
newUser.userId = newUserMessage.id
newUser.num = Int64(packet.from)
@ -179,9 +179,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
newUser.role = Int32(newUserMessage.role.rawValue)
newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased()
newNode.user = newUser
if (UserDefaults.newNodeNotifications){
if UserDefaults.newNodeNotifications {
let manager = LocalNotificationManager()
manager.notifications = [
Notification(
@ -202,7 +201,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
fetchedNode[0].user = newUser
}
}
if newNode.user == nil && packet.from > Int16.max {
newNode.user = createUser(num: Int64(packet.from), context: context)
}
@ -219,7 +218,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)")
}
newNode.myInfo = myInfoEntity
} else {
// Update an existing node
fetchedNode[0].id = Int64(packet.from)
@ -260,7 +259,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
} else if packet.hopStart != 0 && packet.hopLimit <= packet.hopStart {
fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit)
}
if (fetchedNode[0].user == nil) {
if fetchedNode[0].user == nil {
let newUser = createUser(num: Int64(packet.from), context: context)
fetchedNode[0].user! = newUser
}
@ -335,8 +334,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
}
/// Don't save nearly the same position over and over. If the next position is less than 10 meters from the new position, delete the previous position and save the new one.
if mutablePositions.count > 0 && (position.precisionBits == 32 || position.precisionBits == 0) {
let mostRecent = mutablePositions.lastObject as! PositionEntity
if mostRecent.coordinate.distance(from: position.coordinate) < 15.0 {
if let mostRecent = mutablePositions.lastObject as? PositionEntity, mostRecent.coordinate.distance(from: position.coordinate) < 15.0 {
mutablePositions.remove(mostRecent)
}
} else if mutablePositions.count > 0 {
@ -798,7 +796,7 @@ func upsertAmbientLightingModuleConfigPacket(config: Meshtastic.ModuleConfig.Amb
fetchedNode[0].ambientLightingConfig = newAmbientLightingConfig
} else {
if fetchedNode[0].ambientLightingConfig == nil {
fetchedNode[0].ambientLightingConfig = AmbientLightingConfigEntity(context: context)
}
@ -1041,7 +1039,7 @@ func upsertPaxCounterModuleConfigPacket(config: Meshtastic.ModuleConfig.Paxcount
let newPaxCounterConfig = PaxCounterConfigEntity(context: context)
newPaxCounterConfig.enabled = config.enabled
newPaxCounterConfig.paxcounterUpdateInterval = Int32(config.paxcounterUpdateInterval)
fetchedNode[0].paxCounterConfig = newPaxCounterConfig
} else {

View file

@ -33,11 +33,11 @@ struct ContactsTip: Tip {
return "tip.messages.contacts"
}
var title: Text {
//Text("tip.messages.contacts.title")
// Text("tip.messages.contacts.title")
Text("Contacts")
}
var message: Text? {
//Text("tip.messages.contacts.message")
// Text("tip.messages.contacts.message")
Text("Each node is an available contact. Contacts with recent messages or marked as favorites show up at the top of the list. Select a contact to send or view messages. Long press to favorite or mute the contact or delete the conversation.")
}
var image: Image? {

View file

@ -71,7 +71,7 @@ struct Connect: View {
Text("subscribed").font(.callout)
.foregroundColor(.green)
} else {
HStack {
if #available(iOS 17.0, macOS 14.0, *) {
Image(systemName: "square.stack.3d.down.forward")
@ -125,7 +125,7 @@ struct Connect: View {
if !bleManager.sendShutdown(fromUser: node!.user!, toUser: node!.user!, adminIndex: node!.myInfo!.adminIndex) {
print("Shutdown Failed")
}
} label: {
Label("Power Off", systemImage: "power")
}
@ -233,7 +233,7 @@ struct Connect: View {
bleManager.disconnectPeripheral()
}
clearCoreDataDatabase(context: context, includeRoutes: false)
let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId })
if radio != nil {
bleManager.connectTo(peripheral: radio!.peripheral)
@ -242,7 +242,7 @@ struct Connect: View {
}
.textCase(nil)
}
} else {
Text("bluetooth.off")
.foregroundColor(.red)
@ -269,7 +269,7 @@ struct Connect: View {
if bleManager.isConnecting {
Button(role: .destructive, action: {
bleManager.cancelPeripheralConnection()
}) {
Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash")
}

View file

@ -56,19 +56,19 @@ struct ContentView: View {
}
}
}
//#Preview {
// #Preview {
// if #available(iOS 17.0, *) {
// // ContentView(deepLinkManager: .init())
// } else {
// // Fallback on earlier versions
// }
//}
// }
//struct ContentView_Previews: PreviewProvider {
// struct ContentView_Previews: PreviewProvider {
// static var previews: some View {
// ContentView()
// }
//}
// }
enum Tab: Hashable {
case contacts

View file

@ -9,17 +9,17 @@ import SwiftUI
import Charts
struct BatteryGauge: View {
@ObservedObject var node: NodeInfoEntity
private let minValue = 0.0
private let maxValue = 100.00
var body: some View {
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
let batteryLevel = Double(mostRecent?.batteryLevel ?? 0)
VStack {
if batteryLevel > 100.0 {
// Plugged in

View file

@ -7,7 +7,7 @@
import SwiftUI
struct BatteryLevelCompact: View {
@ObservedObject var node: NodeInfoEntity
var font: Font
@ -26,25 +26,25 @@ struct BatteryLevelCompact: View {
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel < 100 && batteryLevel > 74 {
Image(systemName: "battery.75")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel < 75 && batteryLevel > 49 {
Image(systemName: "battery.50")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel < 50 && batteryLevel > 14 {
Image(systemName: "battery.25")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.hierarchical)
} else if batteryLevel < 15 && batteryLevel > 0 {
Image(systemName: "battery.0")
.font(iconFont)
.foregroundColor(color)

View file

@ -9,7 +9,7 @@ struct CircleText: View {
var text: String
var color: Color
var circleSize: CGFloat = 45
var body: some View {
ZStack {
@ -49,8 +49,7 @@ struct CircleText_Previews: PreviewProvider {
.previewLayout(.fixed(width: 300, height: 100))
}
HStack {
CircleText(text: "CW-A", color: Color.secondary)
.previewLayout(.fixed(width: 300, height: 100))
CircleText(text: "CW-A", color: Color.secondary, circleSize: 80)
@ -61,7 +60,7 @@ struct CircleText_Previews: PreviewProvider {
.previewLayout(.fixed(width: 300, height: 100))
}
HStack {
CircleText(text: "🚗", color: Color.orange)
.previewLayout(.fixed(width: 300, height: 100))
CircleText(text: "🔋", color: Color.indigo, circleSize: 80)

View file

@ -5,7 +5,6 @@ A view draws the indicator used in the upper right corner for views using BLE
import SwiftUI
struct ConnectedDevice: View {
var bluetoothOn: Bool
var deviceConnected: Bool
@ -22,7 +21,7 @@ struct ConnectedDevice: View {
if (phoneOnly && UIDevice.current.userInterfaceIdiom == .phone) || !phoneOnly {
if bluetoothOn {
if deviceConnected {
if (mqttUplinkEnabled || mqttDownlinkEnabled) {
if mqttUplinkEnabled || mqttDownlinkEnabled {
MQTTIcon(connected: mqttProxyConnected, uplink: mqttUplinkEnabled, downlink: mqttDownlinkEnabled, topic: mqttTopic)
}
Image(systemName: "antenna.radiowaves.left.and.right.circle.fill")
@ -44,12 +43,9 @@ struct ConnectedDevice: View {
}
}
struct ConnectedDevice_Previews: PreviewProvider {
static var previews: some View {
VStack (alignment: .trailing) {
VStack(alignment: .trailing) {
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true)
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: false, mqttUplinkEnabled: true, mqttDownlinkEnabled: true)
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true, mqttUplinkEnabled: true, mqttDownlinkEnabled: true, mqttTopic: "msh/US/2/e/#")

View file

@ -17,7 +17,7 @@ struct DateTimeText: View {
let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current)
var body: some View {
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss a")

View file

@ -8,13 +8,13 @@ import SwiftUI
struct LastHeardText: View {
var lastHeard: Date?
let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())
static let formatter: RelativeDateTimeFormatter = {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter
}()
var body: some View {
if lastHeard != nil && lastHeard! >= sixMonthsAgo! {
Text(lastHeard?.formatted() ?? "unknown.age".localized)

View file

@ -13,8 +13,8 @@ struct LoRaSignalStrengthMeter: View {
var preset: ModemPresets
var compact: Bool
var body: some View {
if (snr != 0.0 && rssi != 0) {
if snr != 0.0 && rssi != 0 {
let signalStrength = getLoRaSignalStrength(snr: snr, rssi: rssi, preset: preset)
let gradient = Gradient(colors: [.red, .orange, .yellow, .green])
if !compact {

View file

@ -18,8 +18,8 @@ struct MQTTIcon: View {
var body: some View {
Button( action: {
if(topic.length > 0) {self.isPopoverOpen.toggle()}
} ) {
if topic.length > 0 {self.isPopoverOpen.toggle()}
}) {
// the last one defaults to just showing up/down if it isn't specified b/c on the mqtt config screen, there's no information about uplink/downlink and no good alternative icon
Image(systemName: uplink && downlink ? "arrow.up.arrow.down.circle.fill" : uplink ? "arrow.up.circle.fill" : downlink ? "arrow.down.circle.fill" : "arrow.up.arrow.down.circle.fill")
.imageScale(.large)

View file

@ -21,13 +21,13 @@ struct AirQualityIndex: View {
var aqi: Int
var displayMode: IaqDisplayMode = .pill
let gradient = Gradient(colors: [.green, .yellow, .orange, .red, .purple, .magenta])
var body: some View {
let aqiEnum = Aqi.getAqi(for: aqi)
switch displayMode {
case .pill:
ZStack (alignment: .leading) {
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 10)
.fill(aqiEnum.color)
.frame(width: 125, height: 30)
@ -48,7 +48,7 @@ struct AirQualityIndex: View {
.font(.caption)
case .gauge:
Gauge(value: Double(aqi), in: 0...500) {
Text("IAQ")
.foregroundColor(aqiEnum.color)
} currentValueLabel: {
@ -115,19 +115,19 @@ struct AirQualityIndex_Previews: PreviewProvider {
}
Text(".gauge")
.font(.title2)
HStack (alignment: .top) {
HStack(alignment: .top) {
AirQualityIndex(aqi: 6, displayMode: .gauge)
AirQualityIndex(aqi: 51, displayMode: .gauge)
AirQualityIndex(aqi: 101, displayMode: .gauge)
AirQualityIndex(aqi: 151, displayMode: .gauge)
}
HStack (alignment: .top) {
HStack(alignment: .top) {
AirQualityIndex(aqi: 201, displayMode: .gauge)
AirQualityIndex(aqi: 251, displayMode: .gauge)
AirQualityIndex(aqi: 301, displayMode: .gauge)
AirQualityIndex(aqi: 351, displayMode: .gauge)
}
HStack (alignment: .top) {
HStack(alignment: .top) {
AirQualityIndex(aqi: 401, displayMode: .gauge)
AirQualityIndex(aqi: 500, displayMode: .gauge)
}
@ -140,7 +140,7 @@ struct AirQualityIndex_Previews: PreviewProvider {
AirQualityIndex(aqi: 351, displayMode: .gradient)
AirQualityIndex(aqi: 401, displayMode: .gradient)
AirQualityIndex(aqi: 500, displayMode: .gradient)
}.previewLayout(.fixed(width: 300, height: 800))
}
}

View file

@ -10,7 +10,7 @@ import SwiftUI
struct IAQScale: View {
var body: some View {
VStack(alignment:.leading) {
VStack(alignment: .leading) {
ForEach(Iaq.allCases) { iaq in
HStack {
RoundedRectangle(cornerRadius: 5)

View file

@ -28,7 +28,7 @@ struct IndoorAirQuality: View {
let iaqEnum = Iaq.getIaq(for: iaq)
switch displayMode {
case .pill:
ZStack (alignment: .leading) {
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 10)
.fill(iaqEnum.color)
.frame(width: 125, height: 30)
@ -49,7 +49,7 @@ struct IndoorAirQuality: View {
.font(.caption)
case .gauge:
Gauge(value: Double(iaq), in: 0...500) {
Text("IAQ")
.foregroundColor(iaqEnum.color)
} currentValueLabel: {
@ -117,19 +117,19 @@ struct IndoorAirQuality_Previews: PreviewProvider {
}
Text(".gauge")
.font(.title2)
HStack (alignment: .top) {
HStack(alignment: .top) {
IndoorAirQuality(iaq: 6, displayMode: .gauge)
IndoorAirQuality(iaq: 51, displayMode: .gauge)
IndoorAirQuality(iaq: 101, displayMode: .gauge)
IndoorAirQuality(iaq: 151, displayMode: .gauge)
}
HStack (alignment: .top) {
HStack(alignment: .top) {
IndoorAirQuality(iaq: 201, displayMode: .gauge)
IndoorAirQuality(iaq: 251, displayMode: .gauge)
IndoorAirQuality(iaq: 301, displayMode: .gauge)
IndoorAirQuality(iaq: 351, displayMode: .gauge)
}
HStack (alignment: .top) {
HStack(alignment: .top) {
IndoorAirQuality(iaq: 401, displayMode: .gauge)
IndoorAirQuality(iaq: 500, displayMode: .gauge)
}
@ -142,7 +142,7 @@ struct IndoorAirQuality_Previews: PreviewProvider {
IndoorAirQuality(iaq: 351, displayMode: .gradient)
IndoorAirQuality(iaq: 401, displayMode: .gradient)
IndoorAirQuality(iaq: 500, displayMode: .gradient)
}.previewLayout(.fixed(width: 300, height: 800))
}
}

View file

@ -49,7 +49,7 @@ struct MapButtons: View {
}
// MARK: Previews
//struct MapControl_Previews: PreviewProvider {
// struct MapControl_Previews: PreviewProvider {
// @State static var tracking: UserTrackingModes = .none
// @State static var isPresentingInfoSheet = false
// static var previews: some View {
@ -61,4 +61,4 @@ struct MapButtons: View {
// }
// .previewLayout(.fixed(width: 60, height: 100))
// }
//}
// }

View file

@ -75,7 +75,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
mapView.showsBuildings = true
mapView.showsScale = true
mapView.showsTraffic = true
mapView.showsCompass = false
let compass = MKCompassButton(mapView: mapView)
compass.translatesAutoresizingMaskIntoConstraints = false

View file

@ -10,7 +10,7 @@ import MapKit
import WeatherKit
struct NodeMapMapkit: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
/// Weather
@ -21,7 +21,7 @@ struct NodeMapMapkit: View {
@State private var symbolName: String = "cloud.fill"
@State private var attributionLink: URL?
@State private var attributionLogo: URL?
@Environment(\.colorScheme) var colorScheme: ColorScheme
@AppStorage("meshMapType") private var meshMapType = 0
@AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
@ -40,7 +40,7 @@ struct NodeMapMapkit: View {
), animation: .none)
private var waypoints: FetchedResults<WaypointEntity>
@ObservedObject var node: NodeInfoEntity
var body: some View {
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
@ -90,7 +90,7 @@ struct NodeMapMapkit: View {
VStack {
Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
.font(.caption)
Label("\(humidity ?? 0)%", systemImage: "humidity")
.font(.caption2)
@ -103,12 +103,12 @@ struct NodeMapMapkit: View {
.controlSize(.mini)
}
.frame(height: 10)
Link("Other data sources", destination: attributionLink ?? URL(string: "https://weather-data.apple.com/legal-attribution.html")!)
.font(.caption2)
}
.padding(5)
}
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
.padding(5)

View file

@ -9,7 +9,7 @@ import SwiftUI
import CoreData
struct ChannelList: View {
@StateObject var appState = AppState.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@ -20,133 +20,139 @@ struct ChannelList: View {
@State private var isPresentingDeleteChannelMessagesConfirm: Bool = false
@State private var isPresentingTraceRouteSentAlert = false
var restrictedChannels = ["gpio", "mqtt", "serial"]
var body: some View {
@ViewBuilder
private func makeNavigationLink(
myInfo: MyInfoEntity,
channel: ChannelEntity
) -> some View {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)
let dateFormatString = (localeDateFormat ?? "MM/dd/YY")
NavigationLink(destination: ChannelMessageList(myInfo: myInfo, channel: channel)) {
let mostRecent = channel.allPrivateMessages.last(where: { $0.channel == channel.index })
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
ZStack {
Image(systemName: "circle.fill")
.opacity(channel.unreadMessages > 0 ? 1 : 0)
.font(.system(size: 10))
.foregroundColor(.accentColor)
.brightness(0.2)
}
CircleText(text: String(channel.index), color: .accentColor)
.brightness(0.2)
VStack(alignment: .leading) {
HStack {
if channel.name?.isEmpty ?? false {
if channel.role == 1 {
Text(String("PrimaryChannel").camelCaseToWords())
.font(.headline)
} else {
Text(String("Channel \(channel.index)").camelCaseToWords())
.font(.headline)
}
} else {
Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords())
.font(.headline)
}
Spacer()
if channel.allPrivateMessages.count > 0 {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay == (currentDay - 1) {
Text("Yesterday")
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1800) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
if channel.allPrivateMessages.count > 0 {
HStack(alignment: .top) {
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
// .font(.system(size: 16))
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
}
}
var body: some View {
VStack {
// Display Contacts for the rest of the non admin channels
if node != nil && node!.myInfo != nil && node!.myInfo!.channels != nil {
List(node!.myInfo!.channels!.array as! [ChannelEntity], id: \.self, selection: $channelSelection) { (channel: ChannelEntity) in
if let node, let myInfo = node.myInfo, let channels = myInfo.channels?.array as? [ChannelEntity] {
List(channels, id: \.self, selection: $channelSelection) { (channel: ChannelEntity) in
if !restrictedChannels.contains(channel.name?.lowercased() ?? "") {
NavigationLink(destination: ChannelMessageList(myInfo: node!.myInfo!, channel: channel)) {
let mostRecent = channel.allPrivateMessages.last(where: { $0.channel == channel.index })
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
ZStack {
Image(systemName: "circle.fill")
.opacity(channel.unreadMessages > 0 ? 1 : 0)
.font(.system(size: 10))
.foregroundColor(.accentColor)
.brightness(0.2)
}
CircleText(text: String(channel.index), color: .accentColor)
.brightness(0.2)
VStack(alignment: .leading){
HStack{
if channel.name?.isEmpty ?? false {
if channel.role == 1 {
Text(String("PrimaryChannel").camelCaseToWords())
.font(.headline)
} else {
Text(String("Channel \(channel.index)").camelCaseToWords())
.font(.headline)
}
} else {
Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords())
.font(.headline)
}
Spacer()
if channel.allPrivateMessages.count > 0 {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay == (currentDay - 1) {
Text("Yesterday")
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1800) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
makeNavigationLink(myInfo: myInfo, channel: channel)
.frame(height: 62)
.contextMenu {
if channel.allPrivateMessages.count > 0 {
HStack(alignment: .top) {
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
//.font(.system(size: 16))
.font(.footnote)
.foregroundColor(.secondary)
Button(role: .destructive) {
isPresentingDeleteChannelMessagesConfirm = true
channelSelection = channel
} label: {
Label("Delete Messages", systemImage: "trash")
}
}
}
}
.frame(height: 62)
.contextMenu {
if channel.allPrivateMessages.count > 0 {
Button(role: .destructive) {
isPresentingDeleteChannelMessagesConfirm = true
channelSelection = channel
Button {
channel.mute = !channel.mute
do {
let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node.user!, toUser: node.user!)
if adminMessageId > 0 {
context.refresh(channel, mergeChanges: true)
}
try context.save()
} catch {
context.rollback()
print("💥 Save Channel Mute Error")
}
} label: {
Label("Delete Messages", systemImage: "trash")
Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash")
}
}
Button {
channel.mute = !channel.mute
do {
let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node!.user!, toUser: node!.user!)
if adminMessageId > 0 {
context.refresh(channel, mergeChanges: true)
}
try context.save()
} catch {
context.rollback()
print("💥 Save Channel Mute Error")
.confirmationDialog(
"This conversation will be deleted.",
isPresented: $isPresentingDeleteChannelMessagesConfirm,
titleVisibility: .visible
) {
Button(role: .destructive) {
deleteChannelMessages(channel: channelSelection!, context: context)
context.refresh(myInfo, mergeChanges: true)
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
channelSelection = nil
} label: {
Text("delete")
}
} label: {
Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash")
}
}
.confirmationDialog(
"This conversation will be deleted.",
isPresented: $isPresentingDeleteChannelMessagesConfirm,
titleVisibility: .visible
) {
Button(role: .destructive) {
deleteChannelMessages(channel: channelSelection!, context: context)
context.refresh(node!.myInfo!, mergeChanges: true)
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
channelSelection = nil
} label: {
Text("delete")
.onAppear {
if self.bleManager.context == nil {
self.bleManager.context = context
}
}
}
.onAppear {
if self.bleManager.context == nil {
self.bleManager.context = context
}
}
}
}
.padding([.top, .bottom])

View file

@ -60,7 +60,7 @@ struct ChannelMessageList: View {
.foregroundColor(.gray)
.offset(y: 8)
}
HStack {
MessageText(
message: message,
@ -75,13 +75,13 @@ struct ChannelMessageList: View {
RetryButton(message: message, destination: .channel(channel))
}
}
TapbackResponses(message: message) {
appState.unreadChannelMessages = myInfo.unreadMessages
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
context.refresh(myInfo, mergeChanges: true)
}
HStack {
if currentUser && message.receivedACK {
// Ack Received
@ -142,7 +142,7 @@ struct ChannelMessageList: View {
}
})
}
TextMessageField(
destination: .channel(channel),
replyMessageId: $replyMessageId,

View file

@ -4,7 +4,7 @@ import CoreData
struct MessageContextMenuItems: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
let message: MessageEntity
let tapBackDestination: MessageDestination
let isCurrentUser: Bool
@ -47,7 +47,7 @@ struct MessageContextMenuItems: View {
Text("copy")
Image(systemName: "doc.on.doc")
}
Menu("message.details") {
VStack {
let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp))

View file

@ -10,7 +10,7 @@ struct MessageText: View {
static let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss:a")
@Environment(\.managedObjectContext) var context
let message: MessageEntity
let tapBackDestination: MessageDestination
let isCurrentUser: Bool

View file

@ -12,7 +12,7 @@ import TipKit
#endif
struct Messages: View {
@StateObject var appState = AppState.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@ -20,9 +20,9 @@ struct Messages: View {
@State var node: NodeInfoEntity?
@State private var userSelection: UserEntity? // Nothing selected by default.
@State private var channelSelection: ChannelEntity? // Nothing selected by default.
@State private var columnVisibility = NavigationSplitViewVisibility.all
enum MessagesSidebar {
case groupMessages
case directMessages
@ -67,9 +67,9 @@ struct Messages: View {
.navigationBarTitleDisplayMode(.large)
.navigationBarItems(leading: MeshtasticLogo())
.onChange(of: (appState.navigationPath)) { newPath in
if ((newPath?.hasPrefix("meshtastic://messages")) != nil) {
if (newPath?.hasPrefix("meshtastic://messages")) != nil {
if let urlComponent = URLComponents(string: newPath ?? "") {
let queryItems = urlComponent.queryItems
let messageId = queryItems?.first(where: { $0.name == "messageId" })?.value
@ -77,8 +77,7 @@ struct Messages: View {
if channel == nil {
print("Channel not found")
}
else {
} else {
print("Channel \(channel)")
// selectedNode = nodes.first(where: { $0.num == Int64(nodeNum ?? "-1") })
// AppState.shared.navigationPath = nil
@ -106,7 +105,7 @@ struct Messages: View {
}
}
}
} content: {
} detail: {

View file

@ -2,9 +2,9 @@ import SwiftUI
struct TapbackResponses: View {
@Environment(\.managedObjectContext) var context
let message: MessageEntity
let onRead: () -> Void
let onRead: () -> Void
@ViewBuilder
var body: some View {

View file

@ -3,12 +3,12 @@ import SwiftUI
struct TextMessageField: View {
static let maxbytes = 228
@EnvironmentObject var bleManager: BLEManager
let destination: MessageDestination
@Binding var replyMessageId: Int64
@FocusState.Binding var isFocused: Bool
let onSubmit: () -> Void
@State private var typingMessage: String = ""
@State private var totalBytes = 0
@State private var sendPositionWithMessage = false
@ -25,7 +25,7 @@ struct TextMessageField: View {
TextMessageSize(maxbytes: Self.maxbytes, totalBytes: totalBytes).padding(.trailing)
}
#endif
HStack(alignment: .top) {
ZStack {
TextField("message", text: $typingMessage, axis: .vertical)
@ -80,13 +80,13 @@ struct TextMessageField: View {
}
.padding(.all, 15)
}
private func requestPosition() {
let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown"
sendPositionWithMessage = true
typingMessage = "📍 " + userLongName + " \(destination.positionShareMessage)."
}
private func sendMessage() {
let messageSent = bleManager.sendMessage(
message: typingMessage,
@ -121,7 +121,7 @@ private extension MessageDestination {
case .channel: return "has shared their position with you"
}
}
var positionDestNum: Int64 {
switch self {
case let .user(user): return user.num

View file

@ -3,7 +3,7 @@ import SwiftUI
struct TextMessageSize: View {
let maxbytes: Int
let totalBytes: Int
var body: some View {
ProgressView("\("bytes".localized): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes))
.frame(width: 130)

View file

@ -12,7 +12,7 @@ import TipKit
#endif
struct UserList: View {
@StateObject var appState = AppState.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@ -26,7 +26,7 @@ struct UserList: View {
@State private var hopsAway: Int = -1
@State private var deviceRole: Int = -1
@State var isEditingFilters = false
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false),
NSSortDescriptor(key: "userNode.favorite", ascending: false),
@ -38,7 +38,7 @@ struct UserList: View {
@State var selectedUserNum: Int64?
@State private var userSelection: UserEntity? // Nothing selected by default.
@State private var isPresentingDeleteUserMessagesConfirm: Bool = false
var body: some View {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)
let dateFormatString = (localeDateFormat ?? "MM/dd/YY")
@ -61,15 +61,15 @@ struct UserList: View {
.foregroundColor(.accentColor)
.brightness(0.2)
}
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
VStack(alignment: .leading){
HStack{
VStack(alignment: .leading) {
HStack {
Text(user.longName ?? "unknown".localized)
.font(.headline)
Spacer()
if (user.userNode?.favorite ?? false) {
if user.userNode?.favorite ?? false {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
@ -93,7 +93,7 @@ struct UserList: View {
}
}
}
if user.messageList.count > 0 {
HStack(alignment: .top) {
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
@ -106,7 +106,7 @@ struct UserList: View {
.frame(height: 62)
.contextMenu {
Button {
if node != nil && !(user.userNode?.favorite ?? false) {
let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
if success {
@ -238,9 +238,9 @@ struct UserList: View {
.scrollDismissesKeyboard(.immediately)
}
}
private func searchUserList() {
/// Case Insensitive Search Text Predicates
let searchPredicates = ["userId", "numString", "hwModel", "longName", "shortName"].map { property in
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
@ -269,7 +269,7 @@ struct UserList: View {
let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway == %i", Int32(hopsAway))
predicates.append(hopsAwayPredicate)
}
/// Online
if isOnline {
let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate)
@ -283,22 +283,22 @@ struct UserList: View {
/// Distance
if distanceFilter {
let pointOfInterest = LocationHelper.currentLocation
if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude {
let D: Double = maxDistance * 1.1
let R: Double = 6371009
let d: Double = maxDistance * 1.1
let r: Double = 6371009
let meanLatitidue = pointOfInterest.latitude * .pi / 180
let deltaLatitude = D / R * 180 / .pi
let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi
let deltaLatitude = d / r * 180 / .pi
let deltaLongitude = d / (r * cos(meanLatitidue)) * 180 / .pi
let minLatitude: Double = pointOfInterest.latitude - deltaLatitude
let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude
let minLongitude: Double = pointOfInterest.longitude - deltaLongitude
let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude
let distancePredicate = NSPredicate(format: "(SUBQUERY(userNode.positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude,minLatitude, maxLatitude)
let distancePredicate = NSPredicate(format: "(SUBQUERY(userNode.positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude, minLatitude, maxLatitude)
predicates.append(distancePredicate)
}
}
if predicates.count > 0 || !searchText.isEmpty {
if !searchText.isEmpty {
let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates)

View file

@ -113,7 +113,7 @@ struct EnvironmentMetricsLog: View {
GridItem(spacing: 0)
]
LazyVGrid(columns: columns, alignment: .leading, spacing: 1, pinnedViews: [.sectionHeaders]) {
GridRow {
Text("Temp")
.font(.caption)
@ -132,9 +132,9 @@ struct EnvironmentMetricsLog: View {
.fontWeight(.bold)
}
ForEach(environmentMetrics, id: \.self) { em in
GridRow {
Text(em.temperature.formattedTemperature())
.font(.caption)
Text("\(String(format: "%.0f", em.relativeHumidity))%")
@ -154,7 +154,7 @@ struct EnvironmentMetricsLog: View {
}
}
HStack {
Button(role: .destructive) {
isPresentingClearLogConfirm = true
} label: {
@ -188,7 +188,7 @@ struct EnvironmentMetricsLog: View {
.padding(.bottom)
.padding(.trailing)
}
} else {
if #available (iOS 17, *) {
ContentUnavailableView("No Environment Metrics", systemImage: "slash.circle")
@ -197,7 +197,7 @@ struct EnvironmentMetricsLog: View {
}
}
}
.navigationTitle("Environment Metrics Log")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:

View file

@ -10,7 +10,7 @@ import MapKit
@available(iOS 17.0, macOS 14.0, *)
struct MeshMapContent: MapContent {
@StateObject var appState = AppState.shared
/// Parameters
@Binding var showUserLocation: Bool
@ -24,13 +24,13 @@ struct MeshMapContent: MapContent {
@Binding var selectedPosition: PositionEntity?
@AppStorage("enableMapWaypoints") private var showWaypoints = false
@Binding var selectedWaypoint: WaypointEntity?
@FetchRequest(fetchRequest: PositionEntity.allPositionsFetchRequest(), animation: .easeIn)
var positions: FetchedResults<PositionEntity>
@FetchRequest(fetchRequest: WaypointEntity.allWaypointssFetchRequest(), animation: .none)
var waypoints: FetchedResults<WaypointEntity>
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)],
predicate: NSPredicate(format: "enabled == true", ""), animation: .none)
private var routes: FetchedResults<RouteEntity>
@ -39,26 +39,13 @@ struct MeshMapContent: MapContent {
@State private var scale: CGFloat = 0.5
@MapContentBuilder
var meshMap: some MapContent {
let loraNodes = positions.filter { $0.nodePosition?.viaMqtt ?? true == false }
let loraCoords = Array(loraNodes).compactMap({(position) -> CLLocationCoordinate2D in
return position.nodeCoordinate ?? LocationsHandler.DefaultLocation
})
/// Convex Hull
if showConvexHull {
if loraCoords.count > 0 {
let hull = loraCoords.getConvexHull()
MapPolygon(coordinates: hull)
.stroke(.blue, lineWidth: 3)
.foregroundStyle(.indigo.opacity(0.4))
}
}
/// Position Annotations
ForEach(Array(positions), id: \.id) { position in
var positionAnnotations: some MapContent {
ForEach(positions, id: \.id) { position in
/// Node color from node.num
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
let positionName = position.nodePosition?.user?.longName ?? "?"
/// Latest Position Anotations
Annotation(position.nodePosition?.user?.longName ?? "?", coordinate: position.coordinate) {
Annotation(positionName, coordinate: position.coordinate) {
LazyVStack {
ZStack {
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
@ -89,16 +76,14 @@ struct MeshMapContent: MapContent {
}
}
}
.onTapGesture { location in
selectedPosition = (selectedPosition == position ? nil : position)
}
}
/// Node History and Route Lines for favorites
if position.nodePosition?.favorite ?? false {
if let nodePosition = position.nodePosition,
nodePosition.favorite,
let positions = nodePosition.positions,
let nodePositions = Array(positions) as? [PositionEntity] {
if showRouteLines {
let nodePositions = Array(position.nodePosition!.positions!) as! [PositionEntity]
let routeCoords = nodePositions.compactMap({(pos) -> CLLocationCoordinate2D in
return pos.nodeCoordinate ?? LocationHelper.DefaultLocation
})
@ -114,7 +99,7 @@ struct MeshMapContent: MapContent {
.stroke(gradient, style: dashed)
}
if showNodeHistory {
ForEach(Array(position.nodePosition!.positions!) as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in
ForEach(nodePositions, id: \.self) { (mappin: PositionEntity) in
if mappin.latest == false && mappin.nodePosition?.favorite ?? false {
let pf = PositionFlags(rawValue: Int(mappin.nodePosition?.metadata?.positionFlags ?? 771))
let headingDegrees = Angle.degrees(Double(mappin.heading))
@ -129,11 +114,11 @@ struct MeshMapContent: MapContent {
.clipShape(Circle())
.rotationEffect(headingDegrees)
.frame(width: 16, height: 16)
} else {
Circle()
.fill(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))))
.strokeBorder(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))).isLight() ? .black : .white ,lineWidth: 2)
.strokeBorder(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))).isLight() ? .black : .white, lineWidth: 2)
.frame(width: 12, height: 12)
}
}
@ -147,19 +132,23 @@ struct MeshMapContent: MapContent {
/// Reduced Precision Map Circles
if 10...19 ~= position.precisionBits {
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
let radius : CLLocationDistance = pp?.precisionMeters ?? 0
let radius: CLLocationDistance = pp?.precisionMeters ?? 0
if radius > 0.0 {
MapCircle(center: position.coordinate, radius: radius)
.foregroundStyle(Color(nodeColor).opacity(0.25))
.stroke(.white, lineWidth: 2)
}
}
/// Routes
ForEach(Array(routes)) { route in
let routeLocations = Array(route.locations!) as! [LocationEntity]
let routeCoords = routeLocations.compactMap({(loc) -> CLLocationCoordinate2D in
}
}
@MapContentBuilder
var routeAnnotations: some MapContent {
ForEach(routes) { route in
if let routeLocations = route.locations, let locations = Array(routeLocations) as? [LocationEntity] {
let routeCoords = locations.compactMap {(loc) -> CLLocationCoordinate2D in
return loc.locationCoordinate ?? LocationHelper.DefaultLocation
})
}
Annotation("Start", coordinate: routeCoords.first ?? LocationHelper.DefaultLocation) {
ZStack {
Circle()
@ -184,18 +173,19 @@ struct MeshMapContent: MapContent {
)
MapPolyline(coordinates: routeCoords)
.stroke(Color(UIColor(hex: UInt32(route.color))), style: solid)
}
}
/// Waypoint Annotations
if waypoints.count > 0 && showWaypoints {
ForEach(Array(waypoints) as! [WaypointEntity], id: \.self) { waypoint in
}
@MapContentBuilder
var waypointAnnotations: some MapContent {
if waypoints.count > 0, showWaypoints, let waypoints = Array(waypoints) as? [WaypointEntity] {
ForEach(waypoints, id: \.self) { waypoint in
Annotation(waypoint.name ?? "?", coordinate: waypoint.coordinate) {
LazyVStack {
ZStack {
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 40)
.onTapGesture(perform: { location in
.onTapGesture(perform: { _ in
selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint)
})
}
@ -204,7 +194,27 @@ struct MeshMapContent: MapContent {
}
}
}
@MapContentBuilder
var meshMap: some MapContent {
let loraNodes = positions.filter { $0.nodePosition?.viaMqtt ?? true == false }
let loraCoords = Array(loraNodes).compactMap({(position) -> CLLocationCoordinate2D in
return position.nodeCoordinate ?? LocationsHandler.DefaultLocation
})
/// Convex Hull
if showConvexHull {
if loraCoords.count > 0 {
let hull = loraCoords.getConvexHull()
MapPolygon(coordinates: hull)
.stroke(.blue, lineWidth: 3)
.foregroundStyle(.indigo.opacity(0.4))
}
}
positionAnnotations
routeAnnotations
waypointAnnotations
}
@MapContentBuilder
var body: some MapContent {
meshMap

View file

@ -10,7 +10,7 @@ import CoreData
@available(iOS 17.0, macOS 14.0, *)
struct NodeMapContent: MapContent {
@ObservedObject var node: NodeInfoEntity
@State var showUserLocation: Bool = false
@State var positions: [PositionEntity] = []
@ -22,7 +22,7 @@ struct NodeMapContent: MapContent {
@AppStorage("enableMapTraffic") private var showTraffic: Bool = false
@AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = false
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid
// Map Configuration
@Namespace var mapScope
@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true)
@ -33,26 +33,26 @@ struct NodeMapContent: MapContent {
@State var isEditingSettings = false
@State var selectedPosition: PositionEntity?
@State var isMeshMap = false
@MapContentBuilder
var nodeMap: some MapContent {
let positionArray = node.positions?.array as? [PositionEntity] ?? []
let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in
return position.nodeCoordinate ?? LocationsHandler.DefaultLocation
})
/// Node Color from node.num
let nodeColor = UIColor(hex: UInt32(node.num))
/// Node Annotations
ForEach(node.positions?.array as? [PositionEntity] ?? [], id: \.id) { position in
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
let headingDegrees = Angle.degrees(Double(position.heading))
/// Reduced Precision Map Circle
if position.latest && 10...19 ~= position.precisionBits {
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
let radius : CLLocationDistance = pp?.precisionMeters ?? 0
let radius: CLLocationDistance = pp?.precisionMeters ?? 0
if radius > 0.0 {
MapCircle(center: position.coordinate, radius: radius)
.foregroundStyle(Color(nodeColor).opacity(0.25))
@ -73,7 +73,7 @@ struct NodeMapContent: MapContent {
}
}
/// Route Lines
if showRouteLines {
if showRouteLines {
let gradient = LinearGradient(
colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)],
startPoint: .leading, endPoint: .trailing
@ -153,11 +153,11 @@ struct NodeMapContent: MapContent {
.clipShape(Circle())
.rotationEffect(headingDegrees)
.frame(width: 16, height: 16)
} else {
Circle()
.fill(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))))
.strokeBorder(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))).isLight() ? .black : .white ,lineWidth: 2)
.strokeBorder(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))).isLight() ? .black : .white, lineWidth: 2)
.frame(width: 12, height: 12)
}
}
@ -168,7 +168,7 @@ struct NodeMapContent: MapContent {
}
}
}
@MapContentBuilder
var body: some MapContent {
if node.positions?.count ?? 0 > 0 {

View file

@ -24,7 +24,7 @@ struct MapSettingsForm: View {
@Binding var meshMap: Bool
var body: some View {
NavigationStack {
Form {
Section(header: Text("Map Options")) {
@ -63,7 +63,7 @@ struct MapSettingsForm: View {
UserDefaults.enableMapWaypoints = !waypoints
}
}
Toggle(isOn: $nodeHistory) {
Label("Node History", systemImage: "building.columns.fill")
}
@ -75,7 +75,7 @@ struct MapSettingsForm: View {
Toggle(isOn: $routeLines) {
Label("Route Lines", systemImage: "road.lanes")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.onTapGesture {
self.routeLines.toggle()
@ -123,6 +123,6 @@ Spacer()
}
.presentationDetents([.fraction(meshMap ? 0.55 : 0.45), .fraction(0.65)])
.presentationDragIndicator(.visible)
}
}

View file

@ -32,21 +32,21 @@ struct NodeMapSwiftUI: View {
@State var isShowingAltitude = false
@State var isEditingSettings = false
@State var isMeshMap = false
@State private var mapRegion = MKCoordinateRegion.init()
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
predicate: NSPredicate(
format: "expire == nil || expire >= %@", Date() as NSDate
), animation: .none)
private var waypoints: FetchedResults<WaypointEntity>
var body: some View {
var mostRecent = node.positions?.lastObject as? PositionEntity
if node.hasPositions {
ZStack {
MapReader { reader in
MapReader { _ in
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
NodeMapContent(node: node)
}

View file

@ -21,14 +21,26 @@ struct PositionAltitudeChart: View {
@Environment(\.dismiss) private var dismiss
@ObservedObject var node: NodeInfoEntity
@State private var lineWidth = 2.0
var body: some View {
var data: [PositionAltitude] {
let fiveYearsAgo = Calendar.current.date(byAdding: .year, value: -5, to: Date())
let nodePositions = Array(node.positions!) as! [PositionEntity]
let filteredPositions = nodePositions.filter({$0.time != nil && ($0.time ?? fiveYearsAgo!) > fiveYearsAgo!})
let data = filteredPositions.map { PositionAltitude(time: $0.time ?? Date(), altitude: Measurement(value: Double($0.altitude), unit: .meters) ) }
guard let nodePositions = node.positions,
let positions = Array(nodePositions) as? [PositionEntity]
else {
return []
}
let filteredPositions = positions.filter({$0.time != nil && ($0.time ?? fiveYearsAgo!) > fiveYearsAgo!})
return filteredPositions.map {
PositionAltitude(
time: $0.time ?? Date(),
altitude: Measurement(value: Double($0.altitude), unit: .meters)
)
}
}
var body: some View {
GroupBox(label: Label("Altitude", systemImage: "mountain.2")) {
Chart(data, id: \.time) {
LineMark(
x: .value("Time", $0.time),

View file

@ -25,7 +25,7 @@ struct PositionPopover: View {
VStack {
HStack {
ZStack {
if position.nodePosition?.isOnline ?? false {
Circle()
.fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
@ -42,13 +42,13 @@ struct PositionPopover: View {
}
CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 65)
}
Text(position.nodePosition?.user?.longName ?? "Unknown")
.font(.largeTitle)
}
Divider()
HStack (alignment: .center) {
VStack (alignment: .leading) {
HStack(alignment: .center) {
VStack(alignment: .leading) {
/// Time
Label {
Text("heard".localized + ":")
@ -131,7 +131,7 @@ struct PositionPopover: View {
}
.padding(.bottom, 5)
if position.nodePosition?.viaMqtt ?? false {
Label {
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
Text("MQTT")
@ -146,7 +146,7 @@ struct PositionPopover: View {
if let lastLocation = locationsHandler.locationsArray.last {
/// Distance
if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
let metersAway = position.coordinate.distance(from:CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude))
let metersAway = position.coordinate.distance(from: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude))
Label {
Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
.foregroundColor(.primary)
@ -160,7 +160,7 @@ struct PositionPopover: View {
Spacer()
}
Spacer()
VStack (alignment: .center) {
VStack(alignment: .center) {
if position.nodePosition != nil {
if position.nodePosition?.favorite ?? false {
Image(systemName: "star.fill")

View file

@ -1,4 +1,3 @@
//
// WaypointForm.swift
// Meshtastic
@ -11,7 +10,7 @@ import MapKit
import CoreLocation
struct WaypointForm: View {
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var dismiss
@State var waypoint: WaypointEntity
@ -27,7 +26,7 @@ struct WaypointForm: View {
@State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours
@State private var locked: Bool = false
@State private var lockedTo: Int64 = 0
var body: some View {
NavigationStack {
if editMode {
@ -35,7 +34,7 @@ struct WaypointForm: View {
.font(.largeTitle)
Divider()
Form {
let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: waypoint.coordinate.latitude , longitude: waypoint.coordinate.longitude ))
let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: waypoint.coordinate.latitude, longitude: waypoint.coordinate.longitude ))
Section(header: Text("Coordinate") ) {
HStack {
Text("Location: \(String(format: "%.5f", waypoint.coordinate.latitude) + "," + String(format: "%.5f", waypoint.coordinate.longitude))")
@ -91,14 +90,14 @@ struct WaypointForm: View {
.font(.title)
.focused($iconIsFocused)
.onChange(of: icon) { value in
// If you have anything other than emojis in your string make it empty
if !value.onlyEmojis() {
icon = ""
}
// If a second emoji is entered delete the first one
if value.count >= 1 {
if value.count > 1 {
let index = value.index(value.startIndex, offsetBy: 1)
icon = String(value[index])
@ -106,7 +105,7 @@ struct WaypointForm: View {
iconIsFocused = false
}
}
}
Toggle(isOn: $expires) {
Label("Expires", systemImage: "clock.badge.xmark")
@ -168,7 +167,7 @@ struct WaypointForm: View {
.controlSize(.regular)
.disabled(bleManager.connectedPeripheral == nil)
.padding(.bottom)
Button(role: .cancel) {
dismiss()
} label: {
@ -178,9 +177,9 @@ struct WaypointForm: View {
.buttonBorderShape(.capsule)
.controlSize(.regular)
.padding(.bottom)
if waypoint.id > 0 && bleManager.isConnected {
Menu {
Button("For me", action: {
bleManager.context!.delete(waypoint)
@ -211,7 +210,7 @@ struct WaypointForm: View {
}
newWaypoint.expire = UInt32(1)
if bleManager.sendWaypoint(waypoint: newWaypoint) {
bleManager.context!.delete(waypoint)
do {
try bleManager.context!.save()
@ -237,7 +236,7 @@ struct WaypointForm: View {
}
} else {
VStack {
HStack {
HStack {
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 65)
Spacer()
Text(waypoint.name ?? "?")
@ -258,7 +257,7 @@ struct WaypointForm: View {
}
}
Divider()
VStack (alignment: .leading) {
VStack(alignment: .leading) {
// Description
if (waypoint.longDescription ?? "").count > 0 {
Label {

View file

@ -22,7 +22,7 @@ struct NodeDetail: View {
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
NavigationStack {
GeometryReader { bounds in
GeometryReader { _ in
VStack {
ScrollView {
NodeInfoItem(node: node)
@ -78,12 +78,12 @@ struct NodeDetail: View {
Image(systemName: "flipphone")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Device Metrics Log")
.font(.title3)
}
.disabled(!node.hasDeviceMetrics)
Divider()
NavigationLink {
if #available (iOS 17, macOS 14, *) {
@ -91,12 +91,12 @@ struct NodeDetail: View {
} else {
NodeMapMapkit(node: node)
}
} label: {
Image(systemName: "map")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Node Map")
.font(.title3)
}
@ -108,7 +108,7 @@ struct NodeDetail: View {
Image(systemName: "mappin.and.ellipse")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Position Log")
.font(.title3)
}
@ -120,7 +120,7 @@ struct NodeDetail: View {
Image(systemName: "cloud.sun.rain")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Environment Metrics Log")
.font(.title3)
}
@ -133,7 +133,7 @@ struct NodeDetail: View {
Image(systemName: "signpost.right.and.left")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Trace Route Log")
.font(.title3)
}
@ -146,7 +146,7 @@ struct NodeDetail: View {
Image(systemName: "sensor")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Detection Sensor Log")
.font(.title3)
}
@ -159,7 +159,7 @@ struct NodeDetail: View {
Image(systemName: "figure.walk.motion")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("paxcounter.log")
.font(.title3)
}

View file

@ -14,9 +14,9 @@ struct NodeInfoItem: View {
@ObservedObject var node: NodeInfoEntity
var body: some View {
Divider()
HStack {
VStack(alignment: .center) {

View file

@ -15,9 +15,9 @@ struct NodeInfoItem: View {
var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast
var body: some View {
Divider()
HStack {
VStack(alignment: .center) {

View file

@ -20,14 +20,14 @@ struct NodeListFilter: View {
@Binding var maximumDistance: Double
@Binding var hopsAway: Int
@Binding var deviceRole: Int
var body: some View {
NavigationStack {
Form {
Section(header: Text(filterTitle)) {
Toggle(isOn: $viaLora) {
Label {
Text("Via Lora")
} icon: {
@ -37,7 +37,7 @@ struct NodeListFilter: View {
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $viaMqtt) {
Label {
Text("Via Mqtt")
} icon: {
@ -46,9 +46,9 @@ struct NodeListFilter: View {
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
Toggle(isOn: $isOnline) {
Label {
Text("Online")
} icon: {
@ -59,13 +59,13 @@ struct NodeListFilter: View {
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
Toggle(isOn: $isFavorite) {
Label {
Text("Favorites")
} icon: {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
.symbolRenderingMode(.hierarchical)
@ -73,9 +73,9 @@ struct NodeListFilter: View {
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
Toggle(isOn: $distanceFilter) {
Label {
Text("Distance")
} icon: {

View file

@ -9,14 +9,14 @@ import SwiftUI
import CoreLocation
struct NodeListItem: View {
@ObservedObject var node: NodeInfoEntity
var connected: Bool
var connectedNode: Int64
var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast
var body: some View {
NavigationLink(value: node) {
LazyVStack(alignment: .leading) {
HStack {
@ -69,7 +69,7 @@ struct NodeListItem: View {
Text("Role: \(role?.name ?? "unknown".localized)")
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
.foregroundColor(.gray)
}
if node.isStoreForwardRouter {
HStack {
@ -82,14 +82,29 @@ struct NodeListItem: View {
.foregroundColor(.gray)
}
}
if node.positions?.count ?? 0 > 0 && connectedNode != node.num {
HStack {
let lastPostion = node.positions!.reversed()[0] as! PositionEntity
if #available(iOS 17.0, macOS 14.0, *) {
if let currentLocation = LocationsHandler.shared.locationsArray.last {
let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude {
if let lastPostion = node.positions?.lastObject as? PositionEntity {
if #available(iOS 17.0, macOS 14.0, *) {
if let currentLocation = LocationsHandler.shared.locationsArray.last {
let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude {
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
let metersAway = nodeCoord.distance(from: myCoord)
Image(systemName: "lines.measurement.horizontal")
.font(.callout)
.symbolRenderingMode(.hierarchical)
.frame(width: 30)
DistanceText(meters: metersAway)
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
.foregroundColor(.gray)
}
}
} else {
let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude {
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
let metersAway = nodeCoord.distance(from: myCoord)
Image(systemName: "lines.measurement.horizontal")
@ -101,20 +116,6 @@ struct NodeListItem: View {
.foregroundColor(.gray)
}
}
} else {
let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude {
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
let metersAway = nodeCoord.distance(from: myCoord)
Image(systemName: "lines.measurement.horizontal")
.font(.callout)
.symbolRenderingMode(.hierarchical)
.frame(width: 30)
DistanceText(meters: metersAway)
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
.foregroundColor(.gray)
}
}
}
}
@ -131,7 +132,7 @@ struct NodeListItem: View {
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
}
}
if node.viaMqtt && connectedNode != node.num {
Image(systemName: "dot.radiowaves.up.forward")
.symbolRenderingMode(.hierarchical)

View file

@ -13,11 +13,9 @@ import Foundation
import MapKit
#endif
@available(iOS 17.0, macOS 14.0, *)
struct MeshMap: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@StateObject var appState = AppState.shared
@ -29,7 +27,7 @@ struct MeshMap: View {
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .standard
// Map Configuration
@Namespace var mapScope
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .flat, emphasis: MapStyle.StandardEmphasis.muted ,pointsOfInterest: .excludingAll, showsTraffic: false)
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .flat, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .excludingAll, showsTraffic: false)
@State var position = MapCameraPosition.automatic
@State var isEditingSettings = false
@State var selectedPosition: PositionEntity?
@ -39,15 +37,14 @@ struct MeshMap: View {
@State var newWaypointCoord: CLLocationCoordinate2D?
@State var isMeshMap = true
var body: some View {
NavigationStack {
ZStack {
MapReader { reader in
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
MeshMapContent(showUserLocation: $showUserLocation, showTraffic: $showTraffic, showPointsOfInterest: $showPointsOfInterest, selectedMapLayer: $selectedMapLayer, selectedPosition: $selectedPosition, selectedWaypoint: $selectedWaypoint)
}
.mapScope(mapScope)
.mapStyle(mapStyle)
@ -60,7 +57,7 @@ struct MeshMap: View {
.mapControlVisibility(.automatic)
}
.controlSize(.regular)
.onTapGesture(count: 1, perform: { position in
.onTapGesture(count: 1, perform: { position in
newWaypointCoord = reader.convert(position, from: .local) ?? CLLocationCoordinate2D.init()
})
.gesture(
@ -73,7 +70,7 @@ struct MeshMap: View {
print("Unable to retreive tap location from gesture data.")
return
}
guard let coordinate = reader.convert(point, from: .local) else {
print("Unable to convert local point to coordinate on map.")
return
@ -162,7 +159,7 @@ struct MeshMap: View {
}
.tint(Color(UIColor.secondarySystemBackground))
.foregroundColor(.accentColor)
.buttonStyle(.borderedProminent)
.buttonStyle(.borderedProminent)
}
.controlSize(.regular)
.padding(5)
@ -176,9 +173,9 @@ struct MeshMap: View {
if self.bleManager.context == nil {
self.bleManager.context = context
}
// let wayPointEntity = getWaypoint(id: Int64(deepLinkManager.waypointId) ?? -1, context: context)
//if wayPointEntity.id > 0 {
// if wayPointEntity.id > 0 {
// position = .camera(MapCamera(centerCoordinate: wayPointEntity.coordinate, distance: 1000, heading: 0, pitch: 60))
switch selectedMapLayer {
case .standard:

View file

@ -8,7 +8,7 @@ import SwiftUI
import CoreLocation
struct NodeList: View {
@StateObject var appState = AppState.shared
@State private var columnVisibility = NavigationSplitViewVisibility.all
@State private var selectedNode: NodeInfoEntity?
@ -26,25 +26,25 @@ struct NodeList: View {
@State private var maxDistance: Double = 800000
@State private var hopsAway: Int = -1
@State private var deviceRole: Int = -1
@State var isEditingFilters = false
@SceneStorage("selectedDetailView") var selectedDetailView: String?
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false),
sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false),
NSSortDescriptor(key: "lastHeard", ascending: false),
NSSortDescriptor(key: "user.longName", ascending: true)],
animation: .default)
var nodes: FetchedResults<NodeInfoEntity>
var body: some View {
NavigationSplitView(columnVisibility: $columnVisibility) {
// HStack {
// Button("Open Node") {
// UIApplication
@ -52,19 +52,19 @@ struct NodeList: View {
// .open(URL(string: "meshtastic://nodes?nodeNum=530606484")!)
// }
// }
let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
List(nodes, id: \.self, selection: $selectedNode) { node in
NodeListItem(node: node,
NodeListItem(node: node,
connected: bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num,
connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1))
.contextMenu {
Button {
if !node.favorite {
let success = bleManager.setFavoriteNode(node: node, connectedNodeNum: Int64(connectedNodeNum))
if success {
node.favorite = !node.favorite
@ -89,7 +89,7 @@ struct NodeList: View {
print("Favorited a node")
}
}
} label: {
Label(node.favorite ? "Un-Favorite" : "Favorite", systemImage: node.favorite ? "star.slash.fill" : "star.fill")
}
@ -132,14 +132,14 @@ struct NodeList: View {
isPresentingTraceRouteSentAlert = false
}
}
} label: {
Label("Trace Route", systemImage: "signpost.right.and.left")
}
if node.isStoreForwardRouter {
Button {
let success = bleManager.requestStoreAndForwardClientHistory(fromUser: connectedNode!.user!, toUser: node.user!)
let success = bleManager.requestStoreAndForwardClientHistory(fromUser: connectedNode!.user!, toUser: node.user!)
if success {
isPresentingClientHistorySentAlert = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
@ -152,7 +152,7 @@ struct NodeList: View {
}
}
if bleManager.connectedPeripheral != nil {
Button (role: .destructive) {
Button(role: .destructive) {
deleteNodeId = node.num
isPresentingDeleteNodeAlert = true
} label: {
@ -212,7 +212,7 @@ struct NodeList: View {
.disableAutocorrection(true)
.scrollDismissesKeyboard(.immediately)
.navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count)))
.listStyle(.plain)
.confirmationDialog(
@ -251,7 +251,7 @@ struct NodeList: View {
.navigationBarItems(
trailing:
ZStack {
if (UIDevice.current.userInterfaceIdiom != .phone) {
if UIDevice.current.userInterfaceIdiom != .phone {
Button {
columnVisibility = .detailOnly
} label: {
@ -264,7 +264,7 @@ struct NodeList: View {
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", phoneOnly: true)
})
}
} else {
if #available (iOS 17, *) {
ContentUnavailableView("select.node", systemImage: "flipphone")
@ -278,7 +278,7 @@ struct NodeList: View {
} else {
Text("Select something to view")
}
}
.navigationSplitViewStyle(.balanced)
.onChange(of: searchText) { _ in
@ -315,19 +315,18 @@ struct NodeList: View {
searchNodeList()
}
.onChange(of: (appState.navigationPath)) { newPath in
guard let deepLink = newPath else {
return
}
if deepLink.hasPrefix("meshtastic://nodes") {
if let urlComponent = URLComponents(string: deepLink) {
let queryItems = urlComponent.queryItems
let nodeNum = queryItems?.first(where: { $0.name == "nodenum" })?.value
if nodeNum == nil {
print("nodeNum not found")
}
else {
} else {
selectedNode = nodes.first(where: { $0.num == Int64(nodeNum ?? "-1") })
AppState.shared.navigationPath = nil
}
@ -371,7 +370,7 @@ struct NodeList: View {
let hopsAwayPredicate = NSPredicate(format: "hopsAway == %i", Int32(hopsAway))
predicates.append(hopsAwayPredicate)
}
/// Online
if isOnline {
let isOnlinePredicate = NSPredicate(format: "lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate)
@ -385,22 +384,22 @@ struct NodeList: View {
/// Distance
if distanceFilter {
let pointOfInterest = LocationHelper.currentLocation
if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude {
let D: Double = maxDistance * 1.1
let R: Double = 6371009
let d: Double = maxDistance * 1.1
let r: Double = 6371009
let meanLatitidue = pointOfInterest.latitude * .pi / 180
let deltaLatitude = D / R * 180 / .pi
let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi
let deltaLatitude = d / r * 180 / .pi
let deltaLongitude = d / (r * cos(meanLatitidue)) * 180 / .pi
let minLatitude: Double = pointOfInterest.latitude - deltaLatitude
let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude
let minLongitude: Double = pointOfInterest.longitude - deltaLongitude
let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude
let distancePredicate = NSPredicate(format: "(SUBQUERY(positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude,minLatitude, maxLatitude)
let distancePredicate = NSPredicate(format: "(SUBQUERY(positions, $position, $position.latest == TRUE && (%lf <= ($position.longitudeI / 1e7)) AND (($position.longitudeI / 1e7) <= %lf) AND (%lf <= ($position.latitudeI / 1e7)) AND (($position.latitudeI / 1e7) <= %lf))).@count > 0", minLongitude, maxLongitude, minLatitude, maxLatitude)
predicates.append(distancePredicate)
}
}
if predicates.count > 0 || !searchText.isEmpty {
if !searchText.isEmpty {
let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates)

View file

@ -25,13 +25,13 @@ struct PaxCounterLog: View {
var body: some View {
VStack {
if node.hasPax {
let oneWeekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date())
let pax = node.pax?.reversed() as? [PaxCounterEntity] ?? []
let chartData = pax
.filter { $0.time != nil && $0.time! >= oneWeekAgo! }
.sorted { $0.time! < $1.time! }
let maxValue = (chartData.map{ $0.wifi }.max() ?? 0) + (chartData.map{ $0.ble }.max() ?? 0) + 5
let maxValue = (chartData.map { $0.wifi }.max() ?? 0) + (chartData.map { $0.ble }.max() ?? 0) + 5
if chartData.count > 0 {
GroupBox(label: Label("\(pax.count) Readings Total", systemImage: "chart.xyaxis.line")) {
@ -47,7 +47,7 @@ struct PaxCounterLog: View {
.accessibilityValue("X: \(point.time!), Y: \(point.wifi + point.ble)")
.foregroundStyle(paxChartColor)
.interpolationMethod(.cardinal)
Plot {
PointMark(
x: .value("x", point.time!),

View file

@ -20,7 +20,7 @@ struct PositionLog: View {
@ObservedObject var node: NodeInfoEntity
@State private var isPresentingClearLogConfirm = false
@State private var sortOrder = [KeyPathComparator(\PositionEntity.time)]
var body: some View {
VStack {
if node.hasPositions {
@ -62,7 +62,7 @@ struct PositionLog: View {
}
.width(min: 180)
}
} else {
ScrollView {
// Use a grid on iOS as a table only shows a single column
@ -91,19 +91,21 @@ struct PositionLog: View {
.font(.caption2)
.fontWeight(.bold)
}
ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in
let altitude = Measurement(value: Double(mappin.altitude), unit: UnitLength.meters)
GridRow {
Text(String(format: "%.5f", mappin.latitude ?? 0))
.font(.caption2)
Text(String(format: "%.5f", mappin.longitude ?? 0))
.font(.caption2)
Text(String(mappin.satsInView))
.font(.caption2)
Text(altitude.formatted())
.font(.caption2)
Text(mappin.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized)
.font(.caption2)
if let positions = node.positions?.reversed() as? [PositionEntity] {
ForEach(positions, id: \.self) { (mappin: PositionEntity) in
let altitude = Measurement(value: Double(mappin.altitude), unit: UnitLength.meters)
GridRow {
Text(String(format: "%.5f", mappin.latitude ?? 0))
.font(.caption2)
Text(String(format: "%.5f", mappin.longitude ?? 0))
.font(.caption2)
Text(String(mappin.satsInView))
.font(.caption2)
Text(altitude.formatted())
.font(.caption2)
Text(mappin.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized)
.font(.caption2)
}
}
}
}
@ -160,7 +162,7 @@ struct PositionLog: View {
}
}
)
} else {
if #available (iOS 17, *) {
ContentUnavailableView("No Positions", systemImage: "mappin.slash")

View file

@ -15,7 +15,7 @@ struct TraceRouteLog: View {
@ObservedObject var locationsHandler = LocationsHandler.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State private var isPresentingClearLogConfirm: Bool = false
@State var isExporting = false
@State var exportString = ""
@ -23,16 +23,16 @@ struct TraceRouteLog: View {
@State private var selectedRoute: TraceRouteEntity?
// Map Configuration
@Namespace var mapScope
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted ,pointsOfInterest: .all, showsTraffic: true)
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .all, showsTraffic: true)
@State var position = MapCameraPosition.automatic
let distanceFormatter = MKDistanceFormatter()
var body: some View {
HStack (alignment: .top) {
HStack(alignment: .top) {
VStack {
VStack {
List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in
Label {
Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "\(route.hops?.count ?? 0) \(route.hops?.count ?? 0 == 1 ? "Hop": "Hops")") : "No Response")")
} icon: {
@ -63,12 +63,12 @@ struct TraceRouteLog: View {
}
.font(.title2)
}
let hopsArray = selectedRoute?.hops?.array as? [TraceRouteHopEntity] ?? []
let lineCoords = hopsArray.compactMap({(hop) -> CLLocationCoordinate2D in
return hop.coordinate ?? LocationHelper.DefaultLocation
})
if selectedRoute?.response ?? false {
if selectedRoute?.response ?? false {
if selectedRoute?.hasPositions ?? false {
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
Annotation("You", coordinate: selectedRoute?.coordinate ?? LocationHelper.DefaultLocation) {
@ -82,8 +82,7 @@ struct TraceRouteLog: View {
.annotationTitles(.automatic)
// Direct Trace Route
if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 == 0 {
if selectedRoute?.node?.positions?.count ?? 0 > 0 {
let mostRecent = selectedRoute?.node?.positions?.lastObject as! PositionEntity
if selectedRoute?.node?.positions?.count ?? 0 > 0, let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity {
var traceRouteCoords: [CLLocationCoordinate2D] = [selectedRoute?.coordinate ?? LocationsHandler.DefaultLocation, mostRecent.coordinate]
Annotation(selectedRoute?.node?.user?.shortName ?? "???", coordinate: mostRecent.nodeCoordinate ?? LocationHelper.DefaultLocation) {
ZStack {
@ -101,19 +100,21 @@ struct TraceRouteLog: View {
.stroke(.blue, style: dashed)
}
} else if selectedRoute?.hops?.count ?? 0 == 0 {
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
VStack {
/// Distance
if selectedRoute?.node?.positions?.count ?? 0 > 0 && selectedRoute?.coordinate != nil {
let mostRecent = selectedRoute?.node?.positions?.lastObject as! PositionEntity
if selectedRoute?.node?.positions?.count ?? 0 > 0,
selectedRoute?.coordinate != nil,
let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity {
let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude)
if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
let metersAway = selectedRoute?.coordinate?.distance(from:CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude))
let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude))
Label {
Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway ?? 0)))")
.foregroundColor(.primary)

View file

@ -22,7 +22,7 @@ struct AboutMeshtastic: View {
}
Section(header: Text("Apple Apps")) {
if locale.region?.identifier ?? "US" == "US" {
HStack {
Image("SOLAR_NODE")
@ -48,7 +48,7 @@ struct AboutMeshtastic: View {
}
}
.font(.title2)
Text("Version: \(Bundle.main.appVersionLong) (\(Bundle.main.appBuild)) ")
}

View file

@ -16,7 +16,7 @@ struct AppSettings: View {
VStack {
Form {
Section(header: Text("App Settings")) {
Button("Open Settings", systemImage: "gear") {
Button("Open Settings", systemImage: "gear") {
// Get the settings URL and open it
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)

View file

@ -44,10 +44,10 @@ struct Channels: View {
@State var positionsEnabled = true
@State var supportedVersion = true
@State var selectedChannel: ChannelEntity?
/// Minimum Version for granular position configuration
@State var minimumVersion = "2.2.24"
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "favorite", ascending: false),
NSSortDescriptor(key: "lastHeard", ascending: false),
@ -90,7 +90,7 @@ struct Channels: View {
positionPrecision = 32
preciseLocation = true
positionsEnabled = true
} else if !supportedVersion && channelRole == 2 {
positionPrecision = 0
preciseLocation = false
@ -135,7 +135,7 @@ struct Channels: View {
}
}
}
.sheet(item: $selectedChannel) { selection in
.sheet(item: $selectedChannel) { _ in
#if targetEnvironment(macCatalyst)
Text("channel")
.font(.largeTitle)
@ -159,7 +159,7 @@ struct Channels: View {
channel.settings.uplinkEnabled = uplink
channel.settings.downlinkEnabled = downlink
channel.settings.moduleSettings.positionPrecision = UInt32(positionPrecision)
selectedChannel!.role = Int32(channelRole)
selectedChannel!.index = channelIndex
selectedChannel!.name = channelName
@ -187,20 +187,21 @@ struct Channels: View {
print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)")
}
} else {
guard let channelEntity = node?.myInfo?.channels?.first(where: { ($0 as! ChannelEntity).index == channelIndex }) else {
guard let channelEntities = node?.myInfo?.channels as? [ChannelEntity],
let channelEntity = channelEntities.first(where: { $0.index == channelIndex }) else {
return
}
let objects = (channelEntity as! ChannelEntity).allPrivateMessages
let objects = channelEntity.allPrivateMessages
for object in objects {
context.delete(object)
}
for node in nodes {
if node.channel == (channelEntity as AnyObject).index {
if node.channel == channelEntity.index {
context.delete(node)
}
}
context.delete(channelEntity as! ChannelEntity)
context.delete(channelEntity)
do {
try context.save()
print("💾 Deleted Channel: \(channel.settings.name)")
@ -259,7 +260,7 @@ struct Channels: View {
uplink = false
downlink = false
hasChanges = true
let newChannel = ChannelEntity(context: context)
newChannel.id = channelIndex
newChannel.index = channelIndex
@ -297,10 +298,8 @@ func firstMissingChannelIndex(_ indexes: [Int]) -> Int {
var smallestIndex = 1
if indexes.isEmpty { return smallestIndex }
if smallestIndex <= indexes.count {
for element in smallestIndex...indexes.count {
if !indexes.contains(element) {
return element
}
for element in smallestIndex...indexes.count where !indexes.contains(element) {
return element
}
}
return indexes.count + 1
@ -333,7 +332,7 @@ enum PositionPrecision: Int, CaseIterable, Identifiable {
case twentyfour = 24
var id: Int { self.rawValue }
var precisionMeters: Double {
switch self {
case .two:
@ -384,7 +383,7 @@ enum PositionPrecision: Int, CaseIterable, Identifiable {
return 1.413763999910884
}
}
var description: String {
let distanceFormatter = MKDistanceFormatter()
return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))

View file

@ -5,7 +5,6 @@
// Copyright(c) Garth Vander Houwen 3/17/24.
//
import SwiftUI
#if canImport(MapKit)
import MapKit
@ -26,9 +25,9 @@ struct ChannelForm: View {
@Binding var hasChanges: Bool
@Binding var hasValidKey: Bool
@Binding var supportedVersion: Bool
var body: some View {
NavigationStack {
Form {
Section(header: Text("channel details")) {
@ -98,15 +97,14 @@ struct ChannelForm: View {
Color.clear :
Color.red
, lineWidth: 2.0)
)
.onChange(of: channelKey, perform: { _ in
let tempKey = Data(base64Encoded: channelKey) ?? Data()
if tempKey.count == channelKeySize || channelKeySize == -1{
if tempKey.count == channelKeySize || channelKeySize == -1 {
hasValidKey = true
}
else {
} else {
hasValidKey = false
}
hasChanges = true
@ -131,9 +129,9 @@ struct ChannelForm: View {
}
}
}
Section(header: Text("position")) {
VStack(alignment: .leading) {
Toggle(isOn: $positionsEnabled) {
Label(channelRole == 1 ? "Positions Enabled" : "Allow Position Requests", systemImage: positionsEnabled ? "mappin" : "mappin.slash")
@ -141,7 +139,7 @@ struct ChannelForm: View {
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.disabled(!supportedVersion)
}
if positionsEnabled {
VStack(alignment: .leading) {
Toggle(isOn: $preciseLocation) {
@ -156,7 +154,7 @@ struct ChannelForm: View {
}
}
}
if !preciseLocation {
VStack(alignment: .leading) {
Label("Approximate Location", systemImage: "location.slash.circle.fill")
@ -236,8 +234,7 @@ struct ChannelForm: View {
let tempKey = Data(base64Encoded: channelKey) ?? Data()
if tempKey.count == channelKeySize || channelKeySize == -1 {
hasValidKey = true
}
else {
} else {
hasValidKey = false
}
}

View file

@ -46,7 +46,7 @@ struct DeviceConfig: View {
.font(.callout)
}
.pickerStyle(DefaultPickerStyle())
VStack(alignment: .leading) {
Picker("Rebroadcast Mode", selection: $rebroadcastMode ) {
ForEach(RebroadcastModes.allCases) { rm in
@ -58,13 +58,13 @@ struct DeviceConfig: View {
.font(.callout)
}
.pickerStyle(DefaultPickerStyle())
Toggle(isOn: $isManaged) {
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Picker("Node Info Broadcast Interval", selection: $nodeInfoBroadcastSecs ) {
ForEach(UpdateIntervals.allCases) { ui in
if ui.rawValue >= 3600 {
@ -75,13 +75,13 @@ struct DeviceConfig: View {
.pickerStyle(DefaultPickerStyle())
}
Section(header: Text("Hardware")) {
Toggle(isOn: $doubleTapAsButtonPress) {
Label("Double Tap as Button", systemImage: "hand.tap")
Text("Treat double tap on supported accelerometers as a user button press.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $ledHeartbeatEnabled) {
Label("LED Heartbeat", systemImage: "waveform.path.ecg")
Text("Controls the blinking LED on the device. For most devices this will control one of the up to 4 LEDS, the charger and GPS LEDs are not controllable.")
@ -110,7 +110,7 @@ struct DeviceConfig: View {
}
})
.foregroundColor(.gray)
}
.keyboardType(.default)
.disableAutocorrection(true)
@ -165,7 +165,7 @@ struct DeviceConfig: View {
bleManager.disconnectPeripheral()
clearCoreDataDatabase(context: context, includeRoutes: false)
}
} else {
print("NodeDB Reset Failed")
}

View file

@ -37,7 +37,7 @@ struct DisplayConfig: View {
Text(dm.description)
}
}
Text("Override automatic OLED screen detection.")
.foregroundColor(.gray)
.font(.callout)
@ -54,13 +54,13 @@ struct DisplayConfig: View {
Text("Requires that there be an accelerometer on your device.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $flipScreen) {
Label("Flip Screen", systemImage: "pip.swap")
Text("Flip screen vertically")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
VStack(alignment: .leading) {
Picker("OLED Type", selection: $oledType ) {
ForEach(OledTypes.allCases) { ot in
@ -85,20 +85,20 @@ struct DisplayConfig: View {
.font(.callout)
}
.pickerStyle(DefaultPickerStyle())
VStack(alignment: .leading) {
Picker("Carousel Interval", selection: $screenCarouselInterval ) {
ForEach(ScreenCarouselIntervals.allCases) { sci in
Text(sci.description)
}
}
Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.")
.foregroundColor(.gray)
.font(.callout)
}
.pickerStyle(DefaultPickerStyle())
VStack(alignment: .leading) {
Picker("GPS Format", selection: $gpsFormat ) {
ForEach(GpsFormats.allCases) { lu in
@ -110,7 +110,7 @@ struct DisplayConfig: View {
.font(.callout)
}
.pickerStyle(DefaultPickerStyle())
VStack(alignment: .leading) {
Picker("Display Units", selection: $units ) {
ForEach(Units.allCases) { un in

View file

@ -69,7 +69,7 @@ struct LoRaConfig: View {
.font(.callout)
}
.pickerStyle(DefaultPickerStyle())
Toggle(isOn: $usePreset) {
Label("Use Preset", systemImage: "list.bullet.rectangle")
}
@ -91,7 +91,7 @@ struct LoRaConfig: View {
}
}
Section(header: Text("Advanced")) {
Toggle(isOn: $ignoreMqtt) {
Label("Ignore MQTT", systemImage: "server.rack")
}
@ -140,7 +140,7 @@ struct LoRaConfig: View {
.font(.callout)
}
.pickerStyle(DefaultPickerStyle())
VStack(alignment: .leading) {
HStack {
Text("Frequency Slot")
@ -163,12 +163,12 @@ struct LoRaConfig: View {
.foregroundColor(.gray)
.font(.callout)
}
Toggle(isOn: $rxBoostedGain) {
Label("RX Boosted Gain", systemImage: "waveform.badge.plus")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
HStack {
Label("Frequency Override", systemImage: "waveform.path.ecg")
Spacer()
@ -177,7 +177,7 @@ struct LoRaConfig: View {
.scrollDismissesKeyboard(.immediately)
.focused($focusedField, equals: .frequencyOverride)
}
HStack {
Image(systemName: "antenna.radiowaves.left.and.right")
.foregroundColor(.accentColor)

View file

@ -28,15 +28,15 @@ struct AmbientLightingConfig: View {
VStack {
Form {
ConfigHeader(title: "Ambient Lighting", config: \.ambientLightingConfig, node: node, onAppear: setAmbientLightingConfigValue)
Section(header: Text("options")) {
Toggle(isOn: $ledState) {
Label("LED State", systemImage: ledState ? "lightbulb.led.fill" : "lightbulb.led")
Text("The state of the LED (on/off)")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
HStack {
Image(systemName: "eyedropper")
.foregroundColor(.accentColor)

View file

@ -39,21 +39,21 @@ struct CannedMessagesConfig: View {
VStack {
Form {
ConfigHeader(title: "Canned messages", config: \.cannedMessageConfig, node: node, onAppear: setCannedMessagesValues)
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "list.bullet.rectangle.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $sendBell) {
Label("Send Bell", systemImage: "bell")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Picker("Configuration Presets", selection: $configPreset ) {
ForEach(ConfigPresets.allCases) { cp in
Text(cp.description)

View file

@ -44,9 +44,9 @@ struct DetectionSensorConfig: View {
VStack {
Form {
ConfigHeader(title: "Detection Sensor", config: \.detectionSensorConfig, node: node, onAppear: setDetectionSensorValues)
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "dot.radiowaves.right")
Text("Enables the detection sensor module, it needs to be enabled on both the node with the sensor, and any nodes that you want to receive detection sensor text messages or view the detection sensor log and chart.")
@ -90,7 +90,7 @@ struct DetectionSensorConfig: View {
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: name, perform: { _ in
let totalBytes = name.utf8.count
// Only mess with the value if it is too big
if totalBytes > 20 {
@ -102,7 +102,7 @@ struct DetectionSensorConfig: View {
Text("Friendly name used to format message sent to mesh. Example: A name \"Motion\" would result in a message \"Motion detected\"")
.font(.callout)
.foregroundStyle(.gray)
Picker("GPIO Pin to monitor", selection: $monitorPin) {
ForEach(0..<49) {
if $0 == 0 {
@ -113,13 +113,13 @@ struct DetectionSensorConfig: View {
}
}
.pickerStyle(DefaultPickerStyle())
Toggle(isOn: $detectionTriggeredHigh) {
Label("Detection trigger High", systemImage: "dial.high")
Text("Whether or not the GPIO pin state detection is triggered on HIGH (1) or LOW (0)")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $usePullup) {
Label("Uses pullup resistor", systemImage: "arrow.up.to.line")
Text(" Whether or not use INPUT_PULLUP mode for GPIO pin. Only applicable if the board uses pull-up resistors on the pin")

View file

@ -38,28 +38,28 @@ struct ExternalNotificationConfig: View {
ConfigHeader(title: "External notification", config: \.externalNotificationConfig, node: node, onAppear: setExternalNotificationValues)
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "megaphone")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $alertBell) {
Label("Alert when receiving a bell", systemImage: "bell")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $alertMessage) {
Label("Alert when receiving a message", systemImage: "message")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $usePWM) {
Label("Use PWM Buzzer", systemImage: "light.beacon.max.fill")
Text("Use a PWM output (like the RAK Buzzer) for tunes instead of an on/off output. This will ignore the output, output duration and active settings and use the device config buzzer GPIO option instead.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $useI2SAsBuzzer) {
Label("Use I2S As Buzzer", systemImage: "light.beacon.max.fill")
Text("Enables devices with native I2S audio output to use the RTTTL over speaker like a buzzer. T-Watch S3 and T-Deck for example have this capability.")
@ -76,7 +76,7 @@ struct ExternalNotificationConfig: View {
Text("If enabled, the 'output' Pin will be pulled active high, disabled means active low.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Picker("Output pin GPIO", selection: $output) {
ForEach(0..<49) {
if $0 == 0 {
@ -88,7 +88,7 @@ struct ExternalNotificationConfig: View {
}
.pickerStyle(DefaultPickerStyle())
.listRowSeparator(.visible)
Picker("GPIO Output Duration", selection: $outputMilliseconds ) {
ForEach(OutputIntervals.allCases) { oi in
Text(oi.description)
@ -100,7 +100,7 @@ struct ExternalNotificationConfig: View {
.foregroundColor(.gray)
.font(.callout)
.listRowSeparator(.visible)
Picker("Nag timeout", selection: $nagTimeout ) {
ForEach(NagIntervals.allCases) { oi in
Text(oi.description)
@ -112,7 +112,7 @@ struct ExternalNotificationConfig: View {
.foregroundColor(.gray)
.font(.callout)
}
Section(header: Text("Optional GPIO")
.font(.caption)
.foregroundColor(.gray)

View file

@ -32,8 +32,7 @@ struct MQTTConfig: View {
@State var mapPublishIntervalSecs = 3600
@State var preciseLocation: Bool = false
@State var mapPositionPrecision: Double = 13.0
let locale = Locale.current
var body: some View {
@ -41,7 +40,7 @@ struct MQTTConfig: View {
Form {
if node != nil && node?.loRaConfig != nil {
let rc = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))
if rc?.dutyCycle ?? 0 > 0 && rc?.dutyCycle ?? 0 < 100 {
if rc?.dutyCycle ?? 0 > 0 && rc?.dutyCycle ?? 0 < 100 {
Text("Your region has a \(rc?.dutyCycle ?? 0)% duty cycle. MQTT is not advised when you are duty cycle restricted, the extra traffic will quickly overwhelm your LoRa mesh.")
.font(.callout)
.foregroundColor(.red)
@ -51,19 +50,19 @@ struct MQTTConfig: View {
ConfigHeader(title: "MQTT", config: \.mqttConfig, node: node, onAppear: setMqttValues)
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "dot.radiowaves.up.forward")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $proxyToClientEnabled) {
Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right")
Text("Utilizes the network connection on your phone to connect to MQTT.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if enabled && proxyToClientEnabled && node!.mqttConfig!.proxyToClientEnabled == true {
Toggle(isOn: $mqttConnected) {
Label(mqttConnected ? "mqtt.disconnect".localized : "mqtt.connect".localized, systemImage: "server.rack")
@ -72,25 +71,25 @@ struct MQTTConfig: View {
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.red)
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Toggle(isOn: $encryptionEnabled) {
Label("Encryption Enabled", systemImage: "lock.icloud")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $jsonEnabled) {
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
Text("JSON mode is a limited, unencrypted MQTT output for locally integrating with home assistant")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Section(header: Text("Map Report")) {
Toggle(isOn: $mapReportingEnabled) {
Label("enabled", systemImage: "map")
}
@ -104,7 +103,7 @@ struct MQTTConfig: View {
}
}
.pickerStyle(DefaultPickerStyle())
VStack(alignment: .leading) {
Toggle(isOn: $preciseLocation) {
Label("Precise Location", systemImage: "scope")
@ -119,7 +118,7 @@ struct MQTTConfig: View {
}
}
}
if !preciseLocation {
VStack(alignment: .leading) {
Label("Approximate Location", systemImage: "location.slash.circle.fill")
@ -157,7 +156,7 @@ struct MQTTConfig: View {
Text("The root topic to use for MQTT.")
.foregroundColor(.gray)
.font(.callout)
if nearbyTopics.count > 0 {
Picker("Nearby Topics", selection: $selectedTopic ) {
ForEach(nearbyTopics, id: \.self) { nt in
@ -171,7 +170,7 @@ struct MQTTConfig: View {
.font(.callout)
}
}
Section(header: Text("Server")) {
HStack {
Label("Address", systemImage: "server.rack")
@ -190,7 +189,7 @@ struct MQTTConfig: View {
.keyboardType(.default)
}
.autocorrectionDisabled()
HStack {
Label("mqtt.username", systemImage: "person.text.rectangle")
TextField("mqtt.username", text: $username)
@ -198,9 +197,9 @@ struct MQTTConfig: View {
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: username, perform: { _ in
let totalBytes = username.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
username = String(username.dropLast())
@ -218,7 +217,7 @@ struct MQTTConfig: View {
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: password, perform: { _ in
let totalBytes = password.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
@ -371,20 +370,20 @@ struct MQTTConfig: View {
}
}
func setMqttValues() {
if #available(iOS 17.0, macOS 14.0, *) {
nearbyTopics = []
let geocoder = CLGeocoder()
if LocationsHandler.shared.locationsArray.count > 0 {
let region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))?.topic
defaultTopic = "msh/" + (region ?? "UNSET")
geocoder.reverseGeocodeLocation(LocationsHandler.shared.locationsArray.first!, completionHandler: {(placemarks, error) -> Void in
geocoder.reverseGeocodeLocation(LocationsHandler.shared.locationsArray.first!, completionHandler: {(placemarks, error) in
if error != nil {
print("Failed to reverse geocode location")
return
}
if let placemarks = placemarks, let placemark = placemarks.first {
let cc = locale.region?.identifier ?? "UNK"
/// Country Topic unless you are US
@ -412,9 +411,7 @@ struct MQTTConfig: View {
if !neightborhoodTopic.isEmpty {
nearbyTopics.append(neightborhoodTopic)
}
}
else
{
} else {
print("No Location")
}
})

View file

@ -60,7 +60,7 @@ struct PaxCounterConfig: View {
if self.bleManager.context == nil {
self.bleManager.context = context
}
setPaxValues()
// Need to request a PAX Counter module config from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.paxCounterConfig == nil {
@ -80,7 +80,7 @@ struct PaxCounterConfig: View {
hasChanges = $0 != val
}
}
SaveConfigButton(node: node, hasChanges: $hasChanges) {
guard let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context),
let fromUser = connectedNode.user,
@ -91,7 +91,7 @@ struct PaxCounterConfig: View {
var config = ModuleConfig.PaxcounterConfig()
config.enabled = enabled
config.paxcounterUpdateInterval = UInt32(paxcounterUpdateInterval)
let adminMessageId = bleManager.savePaxcounterModuleConfig(
config: config,
fromUser: fromUser,

View file

@ -24,7 +24,7 @@ struct RangeTestConfig: View {
VStack {
Form {
ConfigHeader(title: "Range", config: \.rangeTestConfig, node: node, onAppear: setRangeTestValues)
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "figure.walk")
@ -41,14 +41,14 @@ struct RangeTestConfig: View {
Text("This device will send out range test messages on the selected interval.")
.foregroundColor(.gray)
.font(.callout)
Toggle(isOn: $save) {
Label("save", systemImage: "square.and.arrow.down.fill")
Text("Saves a CSV with the range test message details, currently only available on ESP32 devices with a web server.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.disabled(!(node != nil && node?.metadata?.hasWifi ?? false))
}
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil)

View file

@ -22,7 +22,7 @@ struct RtttlConfig: View {
VStack {
Form {
ConfigHeader(title: "ringtone", config: \.rtttlConfig, node: node, onAppear: setRtttLConfigValue)
Section(header: Text("options")) {
HStack {
Label("ringtone", systemImage: "music.quarternote.3")

View file

@ -25,12 +25,12 @@ struct SerialConfig: View {
@State var timeout = 0
@State var overrideConsoleSerialPort = false
@State var mode = 0
var body: some View {
VStack {
Form {
ConfigHeader(title: "Serial", config: \.serialConfig, node: node, onAppear: setSerialValues)
Section(header: Text("options")) {
Toggle(isOn: $enabled) {

View file

@ -32,9 +32,9 @@ struct StoreForwardConfig: View {
VStack {
Form {
ConfigHeader(title: "storeforward", config: \.storeForwardConfig, node: node, onAppear: setStoreAndForwardValues)
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "envelope.arrow.triangle.branch")
Text("Enables the store and forward module. Store and forward must be enabled on both client and router devices.")
@ -66,7 +66,7 @@ struct StoreForwardConfig: View {
}
}
}
if isRouter {
Section(header: Text("Router Options")) {
Toggle(isOn: $heartbeat) {
@ -119,7 +119,7 @@ struct StoreForwardConfig: View {
print("Failed to save isRouter")
}
}
var sfc = ModuleConfig.StoreForwardConfig()
sfc.enabled = self.enabled
sfc.heartbeat = self.heartbeat
@ -144,7 +144,7 @@ struct StoreForwardConfig: View {
if self.bleManager.context == nil {
self.bleManager.context = context
}
// Need to request a Detection Sensor Module Config from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.storeForwardConfig == nil {
print("empty store and forward module config")

View file

@ -24,13 +24,12 @@ struct TelemetryConfig: View {
@State var powerMeasurementEnabled = false
@State var powerUpdateInterval = 0
@State var powerScreenEnabled = false
var body: some View {
VStack {
Form {
ConfigHeader(title: "Telemetry", config: \.telemetryConfig, node: node, onAppear: setTelemetryValues)
Section(header: Text("update.interval")) {
Picker("Device Metrics", selection: $deviceUpdateInterval ) {
ForEach(UpdateIntervals.allCases) { ui in
@ -176,7 +175,7 @@ struct TelemetryConfig: View {
if node != nil && node?.telemetryConfig != nil {
if newPowerUpdateInterval != node!.telemetryConfig!.powerUpdateInterval { hasChanges = true }
}
}
}
.onChange(of: powerScreenEnabled) { newPowerScreenEnabled in
if node != nil && node?.telemetryConfig != nil {
if newPowerScreenEnabled != node!.telemetryConfig!.powerScreenEnabled { hasChanges = true }

View file

@ -28,16 +28,16 @@ struct NetworkConfig: View {
VStack {
Form {
ConfigHeader(title: "Network", config: \.networkConfig, node: node, onAppear: setNetworkValues)
if (node != nil && node?.metadata?.hasWifi ?? false) {
if node != nil && node?.metadata?.hasWifi ?? false {
Section(header: Text("WiFi Options")) {
Toggle(isOn: $wifiEnabled) {
Label("enabled", systemImage: "wifi")
Text("Enabling WiFi will disable the bluetooth connection to the app.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
HStack {
Label("ssid", systemImage: "network")
TextField("ssid", text: $wifiSsid)
@ -74,7 +74,7 @@ struct NetworkConfig: View {
.keyboardType(.default)
}
}
if (node != nil && node?.metadata?.hasEthernet ?? false) {
if node != nil && node?.metadata?.hasEthernet ?? false {
Section(header: Text("Ethernet Options")) {
Toggle(isOn: $ethEnabled) {
Label("enabled", systemImage: "network")

View file

@ -76,8 +76,8 @@ struct PositionConfig: View {
@State var minimumVersion = "2.3.3"
@State private var supportedVersion = true
@State private var showingSetFixedAlert = false
//@State private var showingRemoveFixedAlert = false
// @State private var showingRemoveFixedAlert = false
var body: some View {
VStack {
Form {
@ -98,12 +98,12 @@ struct PositionConfig: View {
.foregroundColor(.gray)
.font(.callout)
}
Toggle(isOn: $smartPositionEnabled) {
Label("Smart Position", systemImage: "brain")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if smartPositionEnabled {
VStack(alignment: .leading) {
Picker("Minimum Interval", selection: $broadcastSmartMinimumIntervalSecs) {
@ -147,8 +147,7 @@ struct PositionConfig: View {
.padding(.top, 5)
.padding(.bottom, 5)
if gpsMode == 1 {
Text("Positions will be provided by your device GPS, if you select disabled or not present you can set a fixed position.")
.foregroundColor(.gray)
.font(.callout)
@ -170,7 +169,7 @@ struct PositionConfig: View {
if !(node?.positionConfig?.fixedPosition ?? false) {
Text("Your current location will be set as the fixed position and broadcast over the mesh on the position interval.")
} else {
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
@ -215,7 +214,7 @@ struct PositionConfig: View {
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Section(header: Text("Advanced Position Flags")) {
if includeAltitude {
Toggle(isOn: $includeAltitudeMsl) {
Label("Altitude is Mean Sea Level", systemImage: "arrow.up.to.line.compact")
@ -239,7 +238,7 @@ struct PositionConfig: View {
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
}
if gpsMode == 1 {
Section(header: Text("Advanced Device GPS")) {
Picker("GPS Receive GPIO", selection: $rxGpio) {

View file

@ -17,9 +17,9 @@ struct PowerConfig: View {
@State private var waitBluetoothSecs = 60
@State private var lsSecs = 300
@State private var minWakeSecs = 10
@State private var currentDevice: DeviceHardware?
@State private var hasChanges: Bool = false
@FocusState private var isFocused: Bool
@ -28,7 +28,7 @@ struct PowerConfig: View {
ConfigHeader(title: "config.power.title", config: \.powerConfig, node: node, onAppear: setPowerValues)
Section {
if (currentDevice?.architecture == .esp32 || currentDevice?.architecture == .esp32S3) || (currentDevice?.architecture == .nrf52840 && (node?.deviceConfig?.role ?? 0 == 5 || node?.deviceConfig?.role ?? 0 == 6)) {
if (currentDevice?.architecture == .esp32 || currentDevice?.architecture == .esp32S3) || (currentDevice?.architecture == .nrf52840 && (node?.deviceConfig?.role ?? 0 == 5 || node?.deviceConfig?.role ?? 0 == 6)) {
Toggle(isOn: $isPowerSaving) {
Label("config.power.saving", systemImage: "bolt")
Text("config.power.saving.description")
@ -120,13 +120,13 @@ struct PowerConfig: View {
if self.bleManager.context == nil {
self.bleManager.context = context
}
Api().loadDeviceHardwareData { (hw) in
for device in hw {
let currentHardware = node?.user?.hwModel ?? "UNSET"
let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "")
if deviceString == currentHardware {
if deviceString == currentHardware {
currentDevice = device
}
}

View file

@ -17,14 +17,14 @@ struct Firmware: View {
@State private var currentDevice: DeviceHardware?
@State private var latestStable: FirmwareRelease?
@State private var latestAlpha: FirmwareRelease?
var body: some View {
let supportedVersion = self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame
ScrollView {
VStack(alignment: .leading) {
let deviceString = currentDevice?.hwModelSlug.replacingOccurrences(of: "_", with: "")
HStack {
VStack {
Image(systemName: currentDevice?.activelySupported ?? false ? "checkmark.seal.fill" : "x.circle")
@ -45,7 +45,7 @@ struct Firmware: View {
.frame(width: 300, height: 300)
.cornerRadius(5)
}
if supportedVersion {
Text("Your Firmware is up to date")
.fixedSize(horizontal: false, vertical: true)
@ -72,7 +72,7 @@ struct Firmware: View {
.fixedSize(horizontal: false, vertical: true)
.font(.title2)
.padding(.bottom)
Text("Get the latest stable firmware")
.fixedSize(horizontal: false, vertical: true)
.font(.callout)
@ -81,7 +81,7 @@ struct Firmware: View {
Link("Release Notes", destination: URL(string: "\(latestStable?.pageURL ?? "https://meshtastic.org")")!)
.font(.caption)
.padding(.bottom)
Text("Get the latest alpha firmware")
.fixedSize(horizontal: false, vertical: true)
.font(.callout)
@ -90,10 +90,10 @@ struct Firmware: View {
Link("Release Notes", destination: URL(string: "\(latestAlpha?.pageURL ?? "https://meshtastic.org")")!)
.font(.caption)
.padding(.bottom)
if currentDevice?.architecture == Meshtastic.Architecture.nrf52840 {
VStack(alignment: .leading) {
Text("Drag & Drop is the recommended way to update firmware for NRF devices. If your iPhone or iPad is USB-C it will work with your regular USB-C charging cable, for lightning devices you need the Apple Lightning to USB camera adaptor.")
.fixedSize(horizontal: false, vertical: true)
.foregroundStyle(.gray)
@ -109,7 +109,7 @@ struct Firmware: View {
Button {
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
if connectedNode != nil {
if bleManager.sendEnterDfuMode(fromUser: connectedNode!.user!, toUser: node!.user!) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
bleManager.disconnectPeripheral(reconnect: false)
@ -178,18 +178,18 @@ struct Firmware: View {
.font(.title3)
Text(node?.user?.hwModel ?? "UNSET")
.font(.title3)
Text ( currentDevice?.architecture.rawValue ?? "UNKNOWN")
Text( currentDevice?.architecture.rawValue ?? "UNKNOWN")
.font(.title3)
}
}
.padding()
.padding(.bottom, 5)
.onAppear() {
.onAppear {
Api().loadDeviceHardwareData { (hw) in
for device in hw {
let currentHardware = node?.user?.hwModel ?? "UNSET"
let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "")
if deviceString == currentHardware {
if deviceString == currentHardware {
currentDevice = device
}
}

Some files were not shown because too many files have changed in this diff Show more