mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Remove ios 16 conditions
This commit is contained in:
parent
386042b8df
commit
d477de80c2
23 changed files with 386 additions and 598 deletions
|
|
@ -11481,9 +11481,6 @@
|
|||
},
|
||||
"Map Tile Data" : {
|
||||
|
||||
},
|
||||
"Map Type" : {
|
||||
|
||||
},
|
||||
"map.centering" : {
|
||||
"extractionState" : "manual",
|
||||
|
|
|
|||
|
|
@ -1202,65 +1202,37 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
@MainActor
|
||||
public func getPositionFromPhoneGPS(destNum: Int64, fixedPosition: Bool) -> Position? {
|
||||
var positionPacket = Position()
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
|
||||
guard let lastLocation = LocationsHandler.shared.locationsArray.last else {
|
||||
return nil
|
||||
}
|
||||
guard let lastLocation = LocationsHandler.shared.locationsArray.last else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if lastLocation == CLLocation(latitude: 0, longitude: 0) {
|
||||
return nil
|
||||
}
|
||||
|
||||
positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7)
|
||||
let timestamp = lastLocation.timestamp
|
||||
positionPacket.time = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(lastLocation.altitude)
|
||||
positionPacket.satsInView = UInt32(LocationsHandler.satsInView)
|
||||
let currentSpeed = lastLocation.speed
|
||||
if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) {
|
||||
positionPacket.groundSpeed = UInt32(currentSpeed)
|
||||
}
|
||||
let currentHeading = lastLocation.course
|
||||
if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) {
|
||||
positionPacket.groundTrack = UInt32(currentHeading)
|
||||
}
|
||||
/// Set location source for time
|
||||
if !fixedPosition {
|
||||
/// From GPS treat time as good
|
||||
positionPacket.locationSource = Position.LocSource.locExternal
|
||||
} else {
|
||||
/// From GPS, but time can be old and have drifted
|
||||
positionPacket.locationSource = Position.LocSource.locManual
|
||||
}
|
||||
if lastLocation == CLLocation(latitude: 0, longitude: 0) {
|
||||
return nil
|
||||
}
|
||||
|
||||
positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7)
|
||||
let timestamp = lastLocation.timestamp
|
||||
positionPacket.time = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(lastLocation.altitude)
|
||||
positionPacket.satsInView = UInt32(LocationsHandler.satsInView)
|
||||
let currentSpeed = lastLocation.speed
|
||||
if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) {
|
||||
positionPacket.groundSpeed = UInt32(currentSpeed)
|
||||
}
|
||||
let currentHeading = lastLocation.course
|
||||
if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) {
|
||||
positionPacket.groundTrack = UInt32(currentHeading)
|
||||
}
|
||||
/// Set location source for time
|
||||
if !fixedPosition {
|
||||
/// From GPS treat time as good
|
||||
positionPacket.locationSource = Position.LocSource.locExternal
|
||||
} else {
|
||||
|
||||
positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7)
|
||||
let timestamp = LocationHelper.shared.locationManager.location?.timestamp ?? Date()
|
||||
positionPacket.time = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(LocationHelper.shared.locationManager.location?.altitude ?? 0)
|
||||
positionPacket.satsInView = UInt32(LocationHelper.satsInView)
|
||||
let currentSpeed = LocationHelper.shared.locationManager.location?.speed ?? 0
|
||||
if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) {
|
||||
positionPacket.groundSpeed = UInt32(currentSpeed)
|
||||
}
|
||||
let currentHeading = LocationHelper.shared.locationManager.location?.course ?? 0
|
||||
if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) {
|
||||
positionPacket.groundTrack = UInt32(currentHeading)
|
||||
}
|
||||
/// Set location source for time
|
||||
if !fixedPosition {
|
||||
/// From GPS treat time as good
|
||||
positionPacket.locationSource = Position.LocSource.locExternal
|
||||
} else {
|
||||
/// From GPS, but time can be old and have drifted
|
||||
positionPacket.locationSource = Position.LocSource.locManual
|
||||
}
|
||||
/// From GPS, but time can be old and have drifted
|
||||
positionPacket.locationSource = Position.LocSource.locManual
|
||||
}
|
||||
return positionPacket
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import CoreData
|
|||
import OSLog
|
||||
import TipKit
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
@main
|
||||
struct MeshtasticAppleApp: App {
|
||||
|
||||
|
|
@ -109,23 +108,21 @@ struct MeshtasticAppleApp: App {
|
|||
}
|
||||
})
|
||||
.task {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
#if DEBUG
|
||||
/// Optionally, call `Tips.resetDatastore()` before `Tips.configure()` to reset the state of all tips. This will allow tips to re-appear even after they have been dismissed by the user.
|
||||
/// This is for testing only, and should not be enabled in release builds.
|
||||
try? Tips.resetDatastore()
|
||||
#endif
|
||||
#if DEBUG
|
||||
/// Optionally, call `Tips.resetDatastore()` before `Tips.configure()` to reset the state of all tips. This will allow tips to re-appear even after they have been dismissed by the user.
|
||||
/// This is for testing only, and should not be enabled in release builds.
|
||||
try? Tips.resetDatastore()
|
||||
#endif
|
||||
|
||||
try? Tips.configure(
|
||||
[
|
||||
// Reset which tips have been shown and what parameters have been tracked, useful during testing and for this sample project
|
||||
.datastoreLocation(.applicationDefault),
|
||||
// When should the tips be presented? If you use .immediate, they'll all be presented whenever a screen with a tip appears.
|
||||
// You can adjust this on per tip level as well
|
||||
.displayFrequency(.immediate)
|
||||
]
|
||||
)
|
||||
}
|
||||
try? Tips.configure(
|
||||
[
|
||||
// Reset which tips have been shown and what parameters have been tracked, useful during testing and for this sample project
|
||||
.datastoreLocation(.applicationDefault),
|
||||
// When should the tips be presented? If you use .immediate, they'll all be presented whenever a screen with a tip appears.
|
||||
// You can adjust this on per tip level as well
|
||||
.displayFrequency(.immediate)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { (newScenePhase) in
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import SwiftUI
|
|||
import OSLog
|
||||
|
||||
class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
|
||||
|
||||
var router: Router?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
Logger.services.info("🚀 [App] Meshtstic Apple App launched!")
|
||||
// Default User Default Values
|
||||
|
|
@ -19,14 +19,11 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
UserDefaults.standard.register(defaults: ["meshMapShowNodeHistory": true])
|
||||
UserDefaults.standard.register(defaults: ["meshMapShowRouteLines": true])
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
let locationsHandler = LocationsHandler.shared
|
||||
locationsHandler.startLocationUpdates()
|
||||
|
||||
// If a background activity session was previously active, reinstantiate it after the background launch.
|
||||
if locationsHandler.backgroundActivity {
|
||||
locationsHandler.backgroundActivity = true
|
||||
}
|
||||
let locationsHandler = LocationsHandler.shared
|
||||
locationsHandler.startLocationUpdates()
|
||||
// If a background activity session was previously active, reinstantiate it after the background launch.
|
||||
if locationsHandler.backgroundActivity {
|
||||
locationsHandler.backgroundActivity = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -38,7 +35,7 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
) {
|
||||
completionHandler([.list, .banner, .sound])
|
||||
}
|
||||
|
||||
|
||||
// This method is called when a user clicks on the notification
|
||||
func userNotificationCenter(
|
||||
_ center: UNUserNotificationCenter,
|
||||
|
|
@ -46,11 +43,10 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
withCompletionHandler completionHandler: @escaping () -> Void
|
||||
) {
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
|
||||
|
||||
switch response.actionIdentifier {
|
||||
case UNNotificationDefaultActionIdentifier:
|
||||
break
|
||||
|
||||
case "messageNotification.thumbsUpAction":
|
||||
if let channel = userInfo["channel"] as? Int32,
|
||||
let replyID = userInfo["messageId"] as? Int64 {
|
||||
|
|
@ -66,7 +62,6 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
Logger.services.error("Failed to retrieve channel or messageId from userInfo")
|
||||
}
|
||||
break
|
||||
|
||||
case "messageNotification.thumbsDownAction":
|
||||
if let channel = userInfo["channel"] as? Int32,
|
||||
let replyID = userInfo["messageId"] as? Int64 {
|
||||
|
|
@ -82,7 +77,6 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
Logger.services.error("Failed to retrieve channel or messageId from userInfo")
|
||||
}
|
||||
break
|
||||
|
||||
case "messageNotification.replyInputAction":
|
||||
if let userInput = (response as? UNTextInputNotificationResponse)?.userText,
|
||||
let channel = userInfo["channel"] as? Int32,
|
||||
|
|
@ -99,11 +93,10 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
Logger.services.error("Failed to retrieve user input, channel, or messageId from userInfo")
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
if let targetValue = userInfo["target"] as? String,
|
||||
let deepLink = userInfo["path"] as? String,
|
||||
let url = URL(string: deepLink) {
|
||||
|
|
@ -112,7 +105,6 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
} else {
|
||||
Logger.services.error("Failed to handle notification response: \(userInfo)")
|
||||
}
|
||||
|
||||
completionHandler()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,9 +47,7 @@ struct Connect: View {
|
|||
if bleManager.isSwitchedOn {
|
||||
Section(header: Text("connected.radio").font(.title)) {
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral, connectedPeripheral.peripheral.state == .connected {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
TipView(BluetoothConnectionTip(), arrowEdge: .bottom)
|
||||
}
|
||||
TipView(BluetoothConnectionTip(), arrowEdge: .bottom)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
VStack(alignment: .center) {
|
||||
|
|
@ -76,12 +74,10 @@ struct Connect: View {
|
|||
.foregroundColor(.green)
|
||||
} else {
|
||||
HStack {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
Image(systemName: "square.stack.3d.down.forward")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3))
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
Image(systemName: "square.stack.3d.down.forward")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3))
|
||||
.foregroundColor(.orange)
|
||||
Text("communicating").font(.callout)
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
struct ContentView: View {
|
||||
@ObservedObject
|
||||
var appState: AppState
|
||||
|
|
@ -39,20 +38,12 @@ struct ContentView: View {
|
|||
}
|
||||
.tag(NavigationState.Tab.nodes)
|
||||
|
||||
if #available(iOS 17.0, macOS 14.0, *), !UserDefaults.mapUseLegacy {
|
||||
MeshMap(router: appState.router)
|
||||
.tabItem {
|
||||
Label("map", systemImage: "map")
|
||||
}
|
||||
.tag(NavigationState.Tab.map)
|
||||
} else {
|
||||
NodeMap(router: appState.router)
|
||||
.tabItem {
|
||||
Label("map", systemImage: "map")
|
||||
}
|
||||
.tag(NavigationState.Tab.map)
|
||||
}
|
||||
|
||||
MeshMap(router: appState.router)
|
||||
.tabItem {
|
||||
Label("map", systemImage: "map")
|
||||
}
|
||||
.tag(NavigationState.Tab.map)
|
||||
|
||||
Settings(
|
||||
router: appState.router
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,164 +1,164 @@
|
|||
////
|
||||
//// NodeMapControl.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Created by Garth Vander Houwen on 9/9/23.
|
||||
////
|
||||
//import SwiftUI
|
||||
//import CoreLocation
|
||||
//import MapKit
|
||||
//import WeatherKit
|
||||
//import OSLog
|
||||
//
|
||||
// NodeMapControl.swift
|
||||
// Meshtastic
|
||||
//struct NodeMapMapkit: View {
|
||||
//
|
||||
// Created by Garth Vander Houwen on 9/9/23.
|
||||
// @Environment(\.managedObjectContext) var context
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
// /// Weather
|
||||
// /// The current weather condition for the city.
|
||||
// @State private var condition: WeatherCondition?
|
||||
// @State private var temperature: Measurement<UnitTemperature>?
|
||||
// @State private var humidity: Int?
|
||||
// @State private var symbolName: String = "cloud.fill"
|
||||
// @State private var attributionLink: URL?
|
||||
// @State private var attributionLogo: URL?
|
||||
//
|
||||
import SwiftUI
|
||||
import CoreLocation
|
||||
import MapKit
|
||||
import WeatherKit
|
||||
import OSLog
|
||||
|
||||
struct NodeMapMapkit: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
/// Weather
|
||||
/// The current weather condition for the city.
|
||||
@State private var condition: WeatherCondition?
|
||||
@State private var temperature: Measurement<UnitTemperature>?
|
||||
@State private var humidity: Int?
|
||||
@State private var symbolName: String = "cloud.fill"
|
||||
@State private var attributionLink: URL?
|
||||
@State private var attributionLogo: URL?
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||
@AppStorage("meshMapType") private var meshMapType = 0
|
||||
@AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
|
||||
@AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false
|
||||
@State private var selectedMapLayer: MapLayer = .standard
|
||||
@State var waypointCoordinate: WaypointCoordinate?
|
||||
@State var editingWaypoint: Int = 0
|
||||
@State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay(
|
||||
mapName: "offlinemap",
|
||||
tileType: "png",
|
||||
canReplaceMapContent: true
|
||||
)
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
|
||||
predicate: NSPredicate(
|
||||
format: "expire == nil || expire >= %@", Date() as NSDate
|
||||
), animation: .none)
|
||||
private var waypoints: FetchedResults<WaypointEntity>
|
||||
@ObservedObject var node: NodeInfoEntity
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationStack {
|
||||
GeometryReader { bounds in
|
||||
VStack {
|
||||
if node.hasPositions {
|
||||
ZStack {
|
||||
let positionArray = node.positions?.array as? [PositionEntity] ?? []
|
||||
let lastTenThousand = Array(positionArray.prefix(10000))
|
||||
// let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) }
|
||||
ZStack {
|
||||
MapViewSwiftUI(onLongPress: { coord in
|
||||
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0)
|
||||
}, onWaypointEdit: { wpId in
|
||||
if wpId > 0 {
|
||||
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
|
||||
}
|
||||
},
|
||||
selectedMapLayer: selectedMapLayer,
|
||||
positions: lastTenThousand,
|
||||
waypoints: Array(waypoints),
|
||||
userTrackingMode: MKUserTrackingMode.none,
|
||||
showNodeHistory: meshMapShowNodeHistory,
|
||||
showRouteLines: meshMapShowRouteLines,
|
||||
customMapOverlay: self.customMapOverlay
|
||||
)
|
||||
VStack(alignment: .leading) {
|
||||
Spacer()
|
||||
HStack(alignment: .bottom, spacing: 1) {
|
||||
Picker("Map Type", selection: $selectedMapLayer) {
|
||||
ForEach(MapLayer.allCases, id: \.self) { layer in
|
||||
if layer == MapLayer.offline && UserDefaults.enableOfflineMaps {
|
||||
Text(layer.localized)
|
||||
} else if layer != MapLayer.offline {
|
||||
Text(layer.localized)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: (selectedMapLayer)) { newMapLayer in
|
||||
UserDefaults.mapLayer = newMapLayer
|
||||
}
|
||||
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
.pickerStyle(.menu)
|
||||
.padding(5)
|
||||
VStack {
|
||||
VStack {
|
||||
Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
|
||||
.font(.caption)
|
||||
|
||||
Label("\(humidity ?? 0)%", systemImage: "humidity")
|
||||
.font(.caption2)
|
||||
|
||||
AsyncImage(url: attributionLogo) { image in
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
.controlSize(.mini)
|
||||
}
|
||||
.frame(height: 10)
|
||||
|
||||
Link("Other data sources", destination: attributionLink ?? URL(string: "https://weather-data.apple.com/legal-attribution.html")!)
|
||||
.font(.caption2)
|
||||
}
|
||||
.padding(5)
|
||||
|
||||
}
|
||||
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
.padding(5)
|
||||
.task {
|
||||
do {
|
||||
if node.hasPositions {
|
||||
let mostRecent = node.positions?.lastObject as? PositionEntity
|
||||
let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude))
|
||||
condition = weather.currentWeather.condition
|
||||
temperature = weather.currentWeather.temperature
|
||||
humidity = Int(weather.currentWeather.humidity * 100)
|
||||
symbolName = weather.currentWeather.symbolName
|
||||
let attribution = try await WeatherService.shared.attribution
|
||||
attributionLink = attribution.legalPageURL
|
||||
attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("Could not gather weather information: \(error.localizedDescription)")
|
||||
condition = .clear
|
||||
symbolName = "cloud.fill"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
|
||||
.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65)
|
||||
}
|
||||
} else {
|
||||
HStack {
|
||||
}
|
||||
.padding([.top], 20)
|
||||
}
|
||||
}
|
||||
.edgesIgnoringSafeArea([.leading, .trailing])
|
||||
.sheet(item: $waypointCoordinate, content: { wpc in
|
||||
WaypointFormMapKit(coordinate: wpc)
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
})
|
||||
.navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
// @Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||
// @AppStorage("meshMapType") private var meshMapType = 0
|
||||
// @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
|
||||
// @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false
|
||||
// @State private var selectedMapLayer: MapLayer = .standard
|
||||
// @State var waypointCoordinate: WaypointCoordinate?
|
||||
// @State var editingWaypoint: Int = 0
|
||||
// @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay(
|
||||
// mapName: "offlinemap",
|
||||
// tileType: "png",
|
||||
// canReplaceMapContent: true
|
||||
// )
|
||||
// @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
|
||||
// predicate: NSPredicate(
|
||||
// format: "expire == nil || expire >= %@", Date() as NSDate
|
||||
// ), animation: .none)
|
||||
// private var waypoints: FetchedResults<WaypointEntity>
|
||||
// @ObservedObject var node: NodeInfoEntity
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// NavigationStack {
|
||||
// GeometryReader { bounds in
|
||||
// VStack {
|
||||
// if node.hasPositions {
|
||||
// ZStack {
|
||||
// let positionArray = node.positions?.array as? [PositionEntity] ?? []
|
||||
// let lastTenThousand = Array(positionArray.prefix(10000))
|
||||
// // let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) }
|
||||
// ZStack {
|
||||
// MapViewSwiftUI(onLongPress: { coord in
|
||||
// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0)
|
||||
// }, onWaypointEdit: { wpId in
|
||||
// if wpId > 0 {
|
||||
// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
|
||||
// }
|
||||
// },
|
||||
// selectedMapLayer: selectedMapLayer,
|
||||
// positions: lastTenThousand,
|
||||
// waypoints: Array(waypoints),
|
||||
// userTrackingMode: MKUserTrackingMode.none,
|
||||
// showNodeHistory: meshMapShowNodeHistory,
|
||||
// showRouteLines: meshMapShowRouteLines,
|
||||
// customMapOverlay: self.customMapOverlay
|
||||
// )
|
||||
// VStack(alignment: .leading) {
|
||||
// Spacer()
|
||||
// HStack(alignment: .bottom, spacing: 1) {
|
||||
// Picker("Map Type", selection: $selectedMapLayer) {
|
||||
// ForEach(MapLayer.allCases, id: \.self) { layer in
|
||||
// if layer == MapLayer.offline && UserDefaults.enableOfflineMaps {
|
||||
// Text(layer.localized)
|
||||
// } else if layer != MapLayer.offline {
|
||||
// Text(layer.localized)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .onChange(of: (selectedMapLayer)) { newMapLayer in
|
||||
// UserDefaults.mapLayer = newMapLayer
|
||||
// }
|
||||
// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
// .pickerStyle(.menu)
|
||||
// .padding(5)
|
||||
// VStack {
|
||||
// VStack {
|
||||
// Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
|
||||
// .font(.caption)
|
||||
//
|
||||
// Label("\(humidity ?? 0)%", systemImage: "humidity")
|
||||
// .font(.caption2)
|
||||
//
|
||||
// AsyncImage(url: attributionLogo) { image in
|
||||
// image
|
||||
// .resizable()
|
||||
// .scaledToFit()
|
||||
// } placeholder: {
|
||||
// ProgressView()
|
||||
// .controlSize(.mini)
|
||||
// }
|
||||
// .frame(height: 10)
|
||||
//
|
||||
// Link("Other data sources", destination: attributionLink ?? URL(string: "https://weather-data.apple.com/legal-attribution.html")!)
|
||||
// .font(.caption2)
|
||||
// }
|
||||
// .padding(5)
|
||||
//
|
||||
// }
|
||||
// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
// .padding(5)
|
||||
// .task {
|
||||
// do {
|
||||
// if node.hasPositions {
|
||||
// let mostRecent = node.positions?.lastObject as? PositionEntity
|
||||
// let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude))
|
||||
// condition = weather.currentWeather.condition
|
||||
// temperature = weather.currentWeather.temperature
|
||||
// humidity = Int(weather.currentWeather.humidity * 100)
|
||||
// symbolName = weather.currentWeather.symbolName
|
||||
// let attribution = try await WeatherService.shared.attribution
|
||||
// attributionLink = attribution.legalPageURL
|
||||
// attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL
|
||||
// }
|
||||
// } catch {
|
||||
// Logger.services.error("Could not gather weather information: \(error.localizedDescription)")
|
||||
// condition = .clear
|
||||
// symbolName = "cloud.fill"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
|
||||
// .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65)
|
||||
// }
|
||||
// } else {
|
||||
// HStack {
|
||||
// }
|
||||
// .padding([.top], 20)
|
||||
// }
|
||||
// }
|
||||
// .edgesIgnoringSafeArea([.leading, .trailing])
|
||||
// .sheet(item: $waypointCoordinate, content: { wpc in
|
||||
// WaypointFormMapKit(coordinate: wpc)
|
||||
// .presentationDetents([.medium, .large])
|
||||
// .presentationDragIndicator(.automatic)
|
||||
// })
|
||||
// .navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline)
|
||||
// .navigationBarItems(trailing:
|
||||
// ZStack {
|
||||
// ConnectedDevice(
|
||||
// bluetoothOn: bleManager.isSwitchedOn,
|
||||
// deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
// name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
// })
|
||||
// }
|
||||
// .padding(.bottom, 2)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -47,23 +47,14 @@ struct MessageText: View {
|
|||
let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue)
|
||||
if tapBackDestination.overlaySensorMessage {
|
||||
VStack {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
isDetectionSensorMessage ? Image(systemName: "sensor.fill")
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
.foregroundStyle(Color.orange)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3))
|
||||
.offset(x: 20, y: -20)
|
||||
: nil
|
||||
} else {
|
||||
isDetectionSensorMessage ? Image(systemName: "sensor.fill")
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
.foregroundStyle(Color.orange)
|
||||
.offset(x: 20, y: -20)
|
||||
: nil
|
||||
}
|
||||
isDetectionSensorMessage ? Image(systemName: "sensor.fill")
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
.foregroundStyle(Color.orange)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3))
|
||||
.offset(x: 20, y: -20)
|
||||
: nil
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ struct Messages: View {
|
|||
}
|
||||
}
|
||||
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
TipView(MessagesTip(), arrowEdge: .top)
|
||||
}
|
||||
TipView(MessagesTip(), arrowEdge: .top)
|
||||
}
|
||||
.navigationTitle("messages")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
|
|
|
|||
|
|
@ -36,32 +36,31 @@ struct DeviceMetricsLog: View {
|
|||
.sorted { $0.time! < $1.time! }
|
||||
if chartData.count > 0 {
|
||||
GroupBox(label: Label("\(deviceMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
Chart {
|
||||
ForEach(chartData, id: \.self) { point in
|
||||
Plot {
|
||||
LineMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.batteryLevel)
|
||||
)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)")
|
||||
.foregroundStyle(batteryChartColor)
|
||||
.interpolationMethod(.linear)
|
||||
Plot {
|
||||
PointMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.channelUtilization)
|
||||
)
|
||||
.symbolSize(25)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)")
|
||||
.foregroundStyle(channelUtilizationChartColor)
|
||||
if let chartSelection {
|
||||
RuleMark(x: .value("Second", chartSelection, unit: .second))
|
||||
.foregroundStyle(.tertiary.opacity(0.5))
|
||||
Chart {
|
||||
ForEach(chartData, id: \.self) { point in
|
||||
Plot {
|
||||
LineMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.batteryLevel)
|
||||
)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)")
|
||||
.foregroundStyle(batteryChartColor)
|
||||
.interpolationMethod(.linear)
|
||||
Plot {
|
||||
PointMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.channelUtilization)
|
||||
)
|
||||
.symbolSize(25)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)")
|
||||
.foregroundStyle(channelUtilizationChartColor)
|
||||
if let chartSelection {
|
||||
RuleMark(x: .value("Second", chartSelection, unit: .second))
|
||||
.foregroundStyle(.tertiary.opacity(0.5))
|
||||
// .annotation(
|
||||
// position: .automatic,
|
||||
// overflowResolution: .init(x: .fit, y: .disabled)
|
||||
|
|
@ -75,91 +74,37 @@ struct DeviceMetricsLog: View {
|
|||
// .foregroundStyle(Color.accentColor.opacity(0.2))
|
||||
// }
|
||||
// }
|
||||
}
|
||||
RuleMark(y: .value("Network Status Orange", 25))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10]))
|
||||
.foregroundStyle(.orange)
|
||||
RuleMark(y: .value("Network Status Red", 50))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10]))
|
||||
.foregroundStyle(.red)
|
||||
Plot {
|
||||
PointMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.airUtilTx)
|
||||
)
|
||||
.symbolSize(25)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)")
|
||||
.foregroundStyle(airtimeChartColor)
|
||||
}
|
||||
}
|
||||
.chartXAxis(content: {
|
||||
AxisMarks(position: .top)
|
||||
})
|
||||
.chartXAxis(.automatic)
|
||||
.chartXSelection(value: $chartSelection)
|
||||
.chartYScale(domain: 0...100)
|
||||
.chartForegroundStyleScale([
|
||||
idiom == .phone ? "Battery" : "Battery Level": batteryChartColor,
|
||||
"Channel Utilization": channelUtilizationChartColor,
|
||||
"Airtime": airtimeChartColor
|
||||
])
|
||||
.chartLegend(position: .automatic, alignment: .bottom)
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
Chart {
|
||||
ForEach(chartData, id: \.self) { point in
|
||||
Plot {
|
||||
LineMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.batteryLevel)
|
||||
)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)")
|
||||
.foregroundStyle(batteryChartColor)
|
||||
.interpolationMethod(.linear)
|
||||
Plot {
|
||||
PointMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.channelUtilization)
|
||||
)
|
||||
.symbolSize(25)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)")
|
||||
.foregroundStyle(channelUtilizationChartColor)
|
||||
RuleMark(y: .value("Network Status Orange", 25))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10]))
|
||||
.foregroundStyle(.orange)
|
||||
RuleMark(y: .value("Network Status Red", 50))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10]))
|
||||
.foregroundStyle(.red)
|
||||
Plot {
|
||||
PointMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.airUtilTx)
|
||||
)
|
||||
.symbolSize(25)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)")
|
||||
.foregroundStyle(airtimeChartColor)
|
||||
RuleMark(y: .value("Network Status Orange", 25))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10]))
|
||||
.foregroundStyle(.orange)
|
||||
RuleMark(y: .value("Network Status Red", 50))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10]))
|
||||
.foregroundStyle(.red)
|
||||
Plot {
|
||||
PointMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.airUtilTx)
|
||||
)
|
||||
.symbolSize(25)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)")
|
||||
.foregroundStyle(airtimeChartColor)
|
||||
}
|
||||
.chartXAxis(content: {
|
||||
AxisMarks(position: .top)
|
||||
})
|
||||
.chartXAxis(.automatic)
|
||||
.chartYScale(domain: 0...100)
|
||||
.chartForegroundStyleScale([
|
||||
idiom == .phone ? "Battery" : "Battery Level": batteryChartColor,
|
||||
"Channel Utilization": channelUtilizationChartColor,
|
||||
"Airtime": airtimeChartColor
|
||||
])
|
||||
.chartLegend(position: .automatic, alignment: .bottom)
|
||||
}
|
||||
.chartXAxis(content: {
|
||||
AxisMarks(position: .top)
|
||||
})
|
||||
.chartXAxis(.automatic)
|
||||
.chartXSelection(value: $chartSelection)
|
||||
.chartYScale(domain: 0...100)
|
||||
.chartForegroundStyleScale([
|
||||
idiom == .phone ? "Battery" : "Battery Level": batteryChartColor,
|
||||
"Channel Utilization": channelUtilizationChartColor,
|
||||
"Airtime": airtimeChartColor
|
||||
])
|
||||
.chartLegend(position: .automatic, alignment: .bottom)
|
||||
}
|
||||
.frame(minHeight: 240)
|
||||
}
|
||||
|
|
@ -269,11 +214,7 @@ struct DeviceMetricsLog: View {
|
|||
chartSelection = metrics.time
|
||||
}
|
||||
} else {
|
||||
if #available (iOS 17, *) {
|
||||
ContentUnavailableView("No Device Metrics", systemImage: "slash.circle")
|
||||
} else {
|
||||
Text("No Device Metrics")
|
||||
}
|
||||
ContentUnavailableView("No Device Metrics", systemImage: "slash.circle")
|
||||
}
|
||||
}
|
||||
.navigationTitle("device.metrics.log")
|
||||
|
|
|
|||
|
|
@ -193,11 +193,7 @@ struct EnvironmentMetricsLog: View {
|
|||
}
|
||||
|
||||
} else {
|
||||
if #available (iOS 17, *) {
|
||||
ContentUnavailableView("No Environment Metrics", systemImage: "slash.circle")
|
||||
} else {
|
||||
Text("No Environment Metrics")
|
||||
}
|
||||
ContentUnavailableView("No Environment Metrics", systemImage: "slash.circle")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
import SwiftUI
|
||||
import MapKit
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct MeshMapContent: MapContent {
|
||||
|
||||
/// Parameters
|
||||
|
|
|
|||
|
|
@ -214,20 +214,12 @@ struct PositionPopover: View {
|
|||
.padding(.bottom)
|
||||
}
|
||||
if position.nodePosition?.hasDetectionSensorMetrics ?? false {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
Image(systemName: "sensor.fill")
|
||||
.symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3))
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom)
|
||||
} else {
|
||||
Image(systemName: "sensor.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom)
|
||||
}
|
||||
Image(systemName: "sensor.fill")
|
||||
.symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3))
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom)
|
||||
}
|
||||
BatteryGauge(node: position.nodePosition!)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,11 +221,7 @@ struct NodeDetail: View {
|
|||
.disabled(!node.hasDeviceMetrics)
|
||||
|
||||
NavigationLink {
|
||||
if #available (iOS 17, macOS 14, *) {
|
||||
NodeMapSwiftUI(node: node, showUserLocation: connectedNode?.num ?? 0 == node.num)
|
||||
} else {
|
||||
NodeMapMapkit(node: node)
|
||||
}
|
||||
NodeMapSwiftUI(node: node, showUserLocation: connectedNode?.num ?? 0 == node.num)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Node Map")
|
||||
|
|
@ -260,19 +256,17 @@ struct NodeDetail: View {
|
|||
}
|
||||
.disabled(!node.hasEnvironmentMetrics)
|
||||
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
NavigationLink {
|
||||
TraceRouteLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Trace Route Log")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
NavigationLink {
|
||||
TraceRouteLog(node: node)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Trace Route Log")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
.disabled(node.traceRoutes?.count ?? 0 == 0)
|
||||
}
|
||||
.disabled(node.traceRoutes?.count ?? 0 == 0)
|
||||
|
||||
NavigationLink {
|
||||
DetectionSensorLog(node: node)
|
||||
|
|
|
|||
|
|
@ -101,36 +101,9 @@ struct NodeListItem: View {
|
|||
if node.positions?.count ?? 0 > 0 && connectedNode != node.num {
|
||||
HStack {
|
||||
if let lastPostion = node.positions?.lastObject as? PositionEntity {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
if let currentLocation = LocationsHandler.shared.locationsArray.last {
|
||||
let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
|
||||
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude {
|
||||
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
|
||||
let metersAway = nodeCoord.distance(from: myCoord)
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.frame(width: 30)
|
||||
DistanceText(meters: metersAway)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord)
|
||||
let headingDegrees = Angle.degrees(trueBearing)
|
||||
Image(systemName: "location.north")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(headingDegrees)
|
||||
let heading = Measurement(value: trueBearing, unit: UnitAngle.degrees)
|
||||
Text("\(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)
|
||||
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude {
|
||||
if let currentLocation = LocationsHandler.shared.locationsArray.last {
|
||||
let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
|
||||
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude {
|
||||
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
|
||||
let metersAway = nodeCoord.distance(from: myCoord)
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
|
|
@ -139,7 +112,7 @@ struct NodeListItem: View {
|
|||
.frame(width: 30)
|
||||
DistanceText(meters: metersAway)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.gray)
|
||||
let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord)
|
||||
let headingDegrees = Angle.degrees(trueBearing)
|
||||
Image(systemName: "location.north")
|
||||
|
|
@ -211,13 +184,11 @@ struct NodeListItem: View {
|
|||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
if node.hasTraceRoutes {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
if node.hasTraceRoutes {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,18 +274,10 @@ struct NodeList: View {
|
|||
)
|
||||
}
|
||||
} else {
|
||||
if #available (iOS 17, *) {
|
||||
ContentUnavailableView("select.node", systemImage: "flipphone")
|
||||
} else {
|
||||
Text("select.node")
|
||||
}
|
||||
ContentUnavailableView("select.node", systemImage: "flipphone")
|
||||
}
|
||||
} detail: {
|
||||
if #available (iOS 17, *) {
|
||||
ContentUnavailableView("", systemImage: "line.3.horizontal")
|
||||
} else {
|
||||
Text("Select something to view")
|
||||
}
|
||||
ContentUnavailableView("", systemImage: "line.3.horizontal")
|
||||
}
|
||||
.navigationSplitViewStyle(.balanced)
|
||||
.onChange(of: searchText) { _ in
|
||||
|
|
|
|||
|
|
@ -196,11 +196,7 @@ struct PaxCounterLog: View {
|
|||
.padding(.trailing)
|
||||
}
|
||||
} else {
|
||||
if #available (iOS 17, *) {
|
||||
ContentUnavailableView("paxcounter.content.unavailable", systemImage: "slash.circle")
|
||||
} else {
|
||||
Text("paxcounter.content.unavailable")
|
||||
}
|
||||
ContentUnavailableView("paxcounter.content.unavailable", systemImage: "slash.circle")
|
||||
}
|
||||
}
|
||||
.navigationTitle("paxcounter.log")
|
||||
|
|
|
|||
|
|
@ -166,11 +166,7 @@ struct PositionLog: View {
|
|||
)
|
||||
|
||||
} else {
|
||||
if #available (iOS 17, *) {
|
||||
ContentUnavailableView("No Positions", systemImage: "mappin.slash")
|
||||
} else {
|
||||
Text("No Positions")
|
||||
}
|
||||
ContentUnavailableView("No Positions", systemImage: "mappin.slash")
|
||||
}
|
||||
}
|
||||
.navigationTitle("Position Log \(node.positions?.count ?? 0) Points")
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ struct AppData: View {
|
|||
VStack {
|
||||
|
||||
Section(header: Text("phone.gps")) {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
GPSStatus()
|
||||
}
|
||||
GPSStatus()
|
||||
}
|
||||
Divider()
|
||||
Button(action: {
|
||||
|
|
|
|||
|
|
@ -60,9 +60,7 @@ struct Channels: View {
|
|||
|
||||
VStack {
|
||||
List {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
TipView(CreateChannelsTip(), arrowEdge: .bottom)
|
||||
}
|
||||
TipView(CreateChannelsTip(), arrowEdge: .bottom)
|
||||
if node != nil && node?.myInfo != nil {
|
||||
ForEach(node?.myInfo?.channels?.array as? [ChannelEntity] ?? [], id: \.self) { (channel: ChannelEntity) in
|
||||
Button(action: {
|
||||
|
|
|
|||
|
|
@ -353,52 +353,50 @@ struct MQTTConfig: View {
|
|||
}
|
||||
func setMqttValues() {
|
||||
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
nearbyTopics = []
|
||||
let geocoder = CLGeocoder()
|
||||
if LocationsHandler.shared.locationsArray.count > 0 {
|
||||
let region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))?.topic
|
||||
defaultTopic = "msh/" + (region ?? "UNSET")
|
||||
geocoder.reverseGeocodeLocation(LocationsHandler.shared.locationsArray.first!, completionHandler: {(placemarks, error) in
|
||||
if let error {
|
||||
Logger.services.error("Failed to reverse geocode location: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
nearbyTopics = []
|
||||
let geocoder = CLGeocoder()
|
||||
if LocationsHandler.shared.locationsArray.count > 0 {
|
||||
let region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))?.topic
|
||||
defaultTopic = "msh/" + (region ?? "UNSET")
|
||||
geocoder.reverseGeocodeLocation(LocationsHandler.shared.locationsArray.first!, completionHandler: {(placemarks, error) in
|
||||
if let error {
|
||||
Logger.services.error("Failed to reverse geocode location: \(error.localizedDescription)")
|
||||
return
|
||||
if let placemarks = placemarks, let placemark = placemarks.first {
|
||||
let cc = locale.region?.identifier ?? "UNK"
|
||||
/// Country Topic unless you are US
|
||||
if placemark.isoCountryCode ?? "unknown" != cc {
|
||||
let countryTopic = defaultTopic + "/" + (placemark.isoCountryCode ?? "")
|
||||
if !countryTopic.isEmpty {
|
||||
nearbyTopics.append(countryTopic)
|
||||
}
|
||||
}
|
||||
|
||||
if let placemarks = placemarks, let placemark = placemarks.first {
|
||||
let cc = locale.region?.identifier ?? "UNK"
|
||||
/// Country Topic unless you are US
|
||||
if placemark.isoCountryCode ?? "unknown" != cc {
|
||||
let countryTopic = defaultTopic + "/" + (placemark.isoCountryCode ?? "")
|
||||
if !countryTopic.isEmpty {
|
||||
nearbyTopics.append(countryTopic)
|
||||
}
|
||||
}
|
||||
let stateTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "")
|
||||
if !stateTopic.isEmpty {
|
||||
nearbyTopics.append(stateTopic)
|
||||
}
|
||||
let countyTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subAdministrativeArea?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !countyTopic.isEmpty {
|
||||
nearbyTopics.append(countyTopic)
|
||||
}
|
||||
let cityTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.locality?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !cityTopic.isEmpty {
|
||||
nearbyTopics.append(cityTopic)
|
||||
}
|
||||
let neightborhoodTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subLocality?.lowercased()
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
.replacingOccurrences(of: "'", with: "") ?? "")
|
||||
if !neightborhoodTopic.isEmpty {
|
||||
nearbyTopics.append(neightborhoodTopic)
|
||||
}
|
||||
} else {
|
||||
Logger.services.debug("No Location")
|
||||
let stateTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "")
|
||||
if !stateTopic.isEmpty {
|
||||
nearbyTopics.append(stateTopic)
|
||||
}
|
||||
})
|
||||
}
|
||||
let countyTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subAdministrativeArea?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !countyTopic.isEmpty {
|
||||
nearbyTopics.append(countyTopic)
|
||||
}
|
||||
let cityTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.locality?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !cityTopic.isEmpty {
|
||||
nearbyTopics.append(cityTopic)
|
||||
}
|
||||
let neightborhoodTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subLocality?.lowercased()
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
.replacingOccurrences(of: "'", with: "") ?? "")
|
||||
if !neightborhoodTopic.isEmpty {
|
||||
nearbyTopics.append(neightborhoodTopic)
|
||||
}
|
||||
} else {
|
||||
Logger.services.debug("No Location")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.enabled = node?.mqttConfig?.enabled ?? false
|
||||
self.proxyToClientEnabled = node?.mqttConfig?.proxyToClientEnabled ?? false
|
||||
self.address = node?.mqttConfig?.address ?? ""
|
||||
|
|
|
|||
|
|
@ -154,16 +154,14 @@ struct Settings: View {
|
|||
|
||||
var moduleConfigurationSection: some View {
|
||||
Section("module.configuration") {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
NavigationLink(value: SettingsNavigationState.ambientLighting) {
|
||||
Label {
|
||||
Text("ambient.lighting")
|
||||
} icon: {
|
||||
Image(systemName: "light.max")
|
||||
}
|
||||
NavigationLink(value: SettingsNavigationState.ambientLighting) {
|
||||
Label {
|
||||
Text("ambient.lighting")
|
||||
} icon: {
|
||||
Image(systemName: "light.max")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.cannedMessages) {
|
||||
Label {
|
||||
Text("canned.messages")
|
||||
|
|
@ -321,25 +319,24 @@ struct Settings: View {
|
|||
Image(systemName: "gearshape")
|
||||
}
|
||||
}
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
NavigationLink(value: SettingsNavigationState.routes) {
|
||||
Label {
|
||||
Text("routes")
|
||||
} icon: {
|
||||
Image(systemName: "road.lanes.curved.right")
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.routeRecorder) {
|
||||
Label {
|
||||
Text("route.recorder")
|
||||
} icon: {
|
||||
Image(systemName: "record.circle")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
NavigationLink(value: SettingsNavigationState.routes) {
|
||||
Label {
|
||||
Text("routes")
|
||||
} icon: {
|
||||
Image(systemName: "road.lanes.curved.right")
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.routeRecorder) {
|
||||
Label {
|
||||
Text("route.recorder")
|
||||
} icon: {
|
||||
Image(systemName: "record.circle")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !(node?.deviceConfig?.isManaged ?? false) {
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
Section("Configure") {
|
||||
|
|
@ -403,9 +400,7 @@ struct Settings: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
TipView(AdminChannelTip(), arrowEdge: .top)
|
||||
}
|
||||
TipView(AdminChannelTip(), arrowEdge: .top)
|
||||
} else {
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
Text("Connected Node \(node?.user?.longName ?? "unknown".localized)")
|
||||
|
|
@ -416,9 +411,7 @@ struct Settings: View {
|
|||
radioConfigurationSection
|
||||
deviceConfigurationSection
|
||||
moduleConfigurationSection
|
||||
if #available (iOS 17.0, *) {
|
||||
loggingSection
|
||||
}
|
||||
loggingSection
|
||||
#if DEBUG
|
||||
developersSection
|
||||
#endif
|
||||
|
|
@ -433,13 +426,9 @@ struct Settings: View {
|
|||
case .appSettings:
|
||||
AppSettings()
|
||||
case .routes:
|
||||
if #available(iOS 17.0, *) {
|
||||
Routes()
|
||||
}
|
||||
Routes()
|
||||
case .routeRecorder:
|
||||
if #available(iOS 17.0, *) {
|
||||
RouteRecorder()
|
||||
}
|
||||
RouteRecorder()
|
||||
case .lora:
|
||||
LoRaConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
case .channels:
|
||||
|
|
@ -461,9 +450,7 @@ struct Settings: View {
|
|||
case .power:
|
||||
PowerConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
case .ambientLighting:
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
AmbientLightingConfig(node: node)
|
||||
}
|
||||
AmbientLightingConfig(node: node)
|
||||
case .cannedMessages:
|
||||
CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
case .detectionSensor:
|
||||
|
|
@ -489,9 +476,7 @@ struct Settings: View {
|
|||
case .meshLog:
|
||||
MeshLog()
|
||||
case .debugLogs:
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
AppLog()
|
||||
}
|
||||
AppLog()
|
||||
case .appFiles:
|
||||
AppData()
|
||||
case .firmwareUpdates:
|
||||
|
|
|
|||
|
|
@ -52,10 +52,8 @@ struct ShareChannels: View {
|
|||
|
||||
var body: some View {
|
||||
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
VStack {
|
||||
TipView(ShareChannelsTip(), arrowEdge: .bottom)
|
||||
}
|
||||
VStack {
|
||||
TipView(ShareChannelsTip(), arrowEdge: .bottom)
|
||||
}
|
||||
GeometryReader { bounds in
|
||||
let smallest = min(bounds.size.width, bounds.size.height)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue