mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #418 from meshtastic/2.2.9-Working_Changes
2.2.9 working changes
This commit is contained in:
commit
13e752cd4e
20 changed files with 214 additions and 117 deletions
|
|
@ -1423,7 +1423,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.8;
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1457,7 +1457,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.8;
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1579,7 +1579,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.8;
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1612,7 +1612,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.8;
|
||||
MARKETING_VERSION = 2.2.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ enum UserTrackingModes: Int, CaseIterable, Identifiable {
|
|||
}
|
||||
|
||||
enum LocationUpdateInterval: Int, CaseIterable, Identifiable {
|
||||
case fiveSeconds = 5
|
||||
case tenSeconds = 10
|
||||
case fifteenSeconds = 15
|
||||
case thirtySeconds = 30
|
||||
|
|
@ -96,6 +97,8 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable {
|
|||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
switch self {
|
||||
case .fiveSeconds:
|
||||
return "interval.five.seconds".localized
|
||||
case .tenSeconds:
|
||||
return "interval.ten.seconds".localized
|
||||
case .fifteenSeconds:
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@
|
|||
import Foundation
|
||||
|
||||
extension Date {
|
||||
static var currentTimeStamp: Int64 {
|
||||
return Int64(Date().timeIntervalSince1970 * 1000)
|
||||
}
|
||||
|
||||
func formattedDate(format: String) -> String {
|
||||
let dateformat = DateFormatter()
|
||||
|
|
|
|||
|
|
@ -123,9 +123,6 @@ extension UserDefaults {
|
|||
UserDefaults.standard.set(newValue, forKey: "enableMapPointsOfInterest")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static var enableOfflineMaps: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableOfflineMaps")
|
||||
|
|
|
|||
|
|
@ -144,6 +144,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
func disconnectPeripheral(reconnect: Bool = true) {
|
||||
|
||||
guard let connectedPeripheral = connectedPeripheral else { return }
|
||||
if mqttProxyConnected {
|
||||
mqttManager.mqttClientProxy?.disconnect()
|
||||
}
|
||||
automaticallyReconnect = reconnect
|
||||
centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
|
||||
FROMRADIO_characteristic = nil
|
||||
|
|
@ -272,6 +275,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
}
|
||||
if ![FROMNUM_characteristic, TORADIO_characteristic].contains(nil) {
|
||||
if mqttProxyConnected {
|
||||
mqttManager.mqttClientProxy?.disconnect()
|
||||
}
|
||||
sendWantConfig()
|
||||
}
|
||||
}
|
||||
|
|
@ -290,7 +296,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
func onMqttMessageReceived(message: CocoaMQTTMessage) {
|
||||
|
||||
print("📲 Mqtt Client Proxy onMqttMessageReceived for topic: \(message.topic)")
|
||||
|
||||
if message.topic.contains("/stat/") {
|
||||
return
|
||||
}
|
||||
|
|
@ -305,7 +311,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
let binaryData: Data = try! toRadio.serializedData()
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
print("📲 Sent Mqtt client proxy message to the connected device.")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -443,7 +449,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
payload: [UInt8](decodedInfo.mqttClientProxyMessage.data),
|
||||
retained: decodedInfo.mqttClientProxyMessage.retained
|
||||
)
|
||||
print("📲 Publish Mqtt client proxy message received on FromRadio to the Mqtt server \(message)")
|
||||
mqttManager.mqttClientProxy?.publish(message)
|
||||
}
|
||||
|
||||
|
|
@ -635,10 +640,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
// Use context to pass the radio name with the timer
|
||||
// Use a RunLoop to prevent the timer from running on the main UI thread
|
||||
if UserDefaults.provideLocation {
|
||||
let interval = UserDefaults.provideLocationInterval > 0 ? UserDefaults.provideLocationInterval : 30
|
||||
if positionTimer != nil {
|
||||
positionTimer!.invalidate()
|
||||
}
|
||||
positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((UserDefaults.provideLocationInterval )), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true)
|
||||
positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((UserDefaults.provideLocationInterval)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true)
|
||||
if positionTimer != nil {
|
||||
RunLoop.current.add(positionTimer!, forMode: .common)
|
||||
}
|
||||
|
|
@ -787,7 +792,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
meshPacket.from = fromNodeNum
|
||||
meshPacket.wantAck = true
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! waypoint.serializedData()
|
||||
do {
|
||||
dataMessage.payload = try waypoint.serializedData()
|
||||
}
|
||||
catch {
|
||||
// Could not serialiaze the payload
|
||||
return false
|
||||
}
|
||||
|
||||
dataMessage.portnum = PortNum.waypointApp
|
||||
meshPacket.decoded = dataMessage
|
||||
var toRadio: ToRadio!
|
||||
|
|
@ -870,7 +882,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
success = true
|
||||
let logString = String.localizedStringWithFormat("mesh.log.sharelocation %@".localized, String(fromNodeNum))
|
||||
print(positionPacket)
|
||||
MeshLogger.log("📍 \(logString)")
|
||||
}
|
||||
return success
|
||||
|
|
|
|||
|
|
@ -258,6 +258,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)
|
||||
|
|
@ -265,9 +266,19 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
newUser.shortName = nodeInfo.user.shortName
|
||||
newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
|
||||
newNode.user = newUser
|
||||
} else {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(nodeInfo.num)
|
||||
let userId = String(format:"%2X", nodeInfo.num)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
newNode.user = newUser
|
||||
}
|
||||
|
||||
if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) {
|
||||
if (nodeInfo.position.longitudeI != 0 && nodeInfo.position.latitudeI != 0) && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) {
|
||||
let position = PositionEntity(context: context)
|
||||
position.latest = true
|
||||
position.seqNo = Int32(nodeInfo.position.seqNumber)
|
||||
|
|
@ -306,7 +317,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
} catch {
|
||||
print("💥 Fetch MyInfo Error")
|
||||
}
|
||||
} else if nodeInfo.hasUser && nodeInfo.num > 0 {
|
||||
} else if nodeInfo.num > 0 {
|
||||
|
||||
fetchedNode[0].id = Int64(nodeInfo.num)
|
||||
fetchedNode[0].num = Int64(nodeInfo.num)
|
||||
|
|
@ -323,6 +334,18 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
fetchedNode[0].user!.longName = nodeInfo.user.longName
|
||||
fetchedNode[0].user!.shortName = nodeInfo.user.shortName
|
||||
fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
|
||||
} else {
|
||||
if (fetchedNode[0].user == nil) {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(nodeInfo.num)
|
||||
let userId = String(format:"%2X", nodeInfo.num)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
fetchedNode[0].user = newUser
|
||||
}
|
||||
}
|
||||
|
||||
if nodeInfo.hasDeviceMetrics {
|
||||
|
|
@ -340,7 +363,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
|
||||
if nodeInfo.hasPosition {
|
||||
|
||||
if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) {
|
||||
if (nodeInfo.position.longitudeI != 0 && nodeInfo.position.latitudeI != 0) && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) {
|
||||
|
||||
let position = PositionEntity(context: context)
|
||||
position.latitudeI = nodeInfo.position.latitudeI
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class MqttClientProxyManager {
|
|||
weak var delegate: MqttClientProxyManagerDelegate?
|
||||
var mqttClientProxy: CocoaMQTT?
|
||||
var topic = "msh/2/c"
|
||||
var debugLog = false
|
||||
func connectFromConfigSettings(node: NodeInfoEntity) {
|
||||
let defaultServerAddress = "mqtt.meshtastic.org"
|
||||
let useSsl = node.mqttConfig?.tlsEnabled == true
|
||||
|
|
@ -58,9 +59,9 @@ class MqttClientProxyManager {
|
|||
mqttClient.password = password
|
||||
mqttClient.keepAlive = 60
|
||||
mqttClient.cleanSession = cleanSession
|
||||
#if DEBUG
|
||||
mqttClient.logLevel = .debug
|
||||
#endif
|
||||
if debugLog {
|
||||
mqttClient.logLevel = .debug
|
||||
}
|
||||
mqttClient.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout")
|
||||
mqttClient.autoReconnect = true
|
||||
mqttClient.delegate = self
|
||||
|
|
@ -82,7 +83,9 @@ class MqttClientProxyManager {
|
|||
}
|
||||
func publish(message: String, topic: String, qos: CocoaMQTTQoS) {
|
||||
mqttClientProxy?.publish(topic, withString: message, qos: qos)
|
||||
print("📲 MQTT Client Proxy publish for: " + topic)
|
||||
if debugLog {
|
||||
print("📲 MQTT Client Proxy publish for: " + topic)
|
||||
}
|
||||
}
|
||||
func disconnect() {
|
||||
if let client = mqttClientProxy {
|
||||
|
|
@ -130,15 +133,21 @@ extension MqttClientProxyManager: CocoaMQTTDelegate {
|
|||
delegate?.onMqttDisconnected()
|
||||
}
|
||||
func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) {
|
||||
print("📲 MQTT Client Proxy didPublishMessage from MqttClientProxyManager: \(message)")
|
||||
if debugLog {
|
||||
print("📲 MQTT Client Proxy didPublishMessage from MqttClientProxyManager: \(message)")
|
||||
}
|
||||
}
|
||||
func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) {
|
||||
print("📲 MQTT Client Proxy didPublishAck from MqttClientProxyManager: \(id)")
|
||||
if debugLog {
|
||||
print("📲 MQTT Client Proxy didPublishAck from MqttClientProxyManager: \(id)")
|
||||
}
|
||||
}
|
||||
|
||||
public func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) {
|
||||
delegate?.onMqttMessageReceived(message: message)
|
||||
print("📲 MQTT Client Proxy message received on topic: \(message.topic)")
|
||||
if debugLog {
|
||||
print("📲 MQTT Client Proxy message received on topic: \(message.topic)")
|
||||
}
|
||||
}
|
||||
func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) {
|
||||
print("📲 MQTT Client Proxy didSubscribeTopics: \(success.allKeys.count) topics. failed: \(failed.count) topics")
|
||||
|
|
|
|||
|
|
@ -178,6 +178,18 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
fetchedNode[0].user!.longName = nodeInfoMessage.user.longName
|
||||
fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName
|
||||
fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased()
|
||||
} else {
|
||||
if (fetchedNode[0].user == nil) {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(nodeInfoMessage.num)
|
||||
let userId = String(format:"%2X", nodeInfoMessage.num)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
fetchedNode[0].user! = newUser
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
|
@ -225,7 +237,6 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
position.latest = false
|
||||
}
|
||||
}
|
||||
print("Incoming position message: \n \(positionMessage)")
|
||||
let position = PositionEntity(context: context)
|
||||
position.latest = true
|
||||
position.snr = packet.rxSnr
|
||||
|
|
|
|||
|
|
@ -237,6 +237,13 @@ struct Config {
|
|||
/// When used in conjunction with power.is_power_saving = true, nodes will wake up,
|
||||
/// send environment telemetry, and then sleep for telemetry.environment_update_interval seconds.
|
||||
case sensor // = 6
|
||||
|
||||
///
|
||||
/// TAK device role
|
||||
/// Used for nodes dedicated for connection to an ATAK EUD.
|
||||
/// Turns off many of the routine broadcasts to favor CoT packet stream
|
||||
/// from the Meshtastic ATAK plugin -> IMeshService -> Node
|
||||
case tak // = 7
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
|
|
@ -252,6 +259,7 @@ struct Config {
|
|||
case 4: self = .repeater
|
||||
case 5: self = .tracker
|
||||
case 6: self = .sensor
|
||||
case 7: self = .tak
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -265,6 +273,7 @@ struct Config {
|
|||
case .repeater: return 4
|
||||
case .tracker: return 5
|
||||
case .sensor: return 6
|
||||
case .tak: return 7
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -1285,6 +1294,7 @@ extension Config.DeviceConfig.Role: CaseIterable {
|
|||
.repeater,
|
||||
.tracker,
|
||||
.sensor,
|
||||
.tak,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -1692,6 +1702,7 @@ extension Config.DeviceConfig.Role: SwiftProtobuf._ProtoNameProviding {
|
|||
4: .same(proto: "REPEATER"),
|
||||
5: .same(proto: "TRACKER"),
|
||||
6: .same(proto: "SENSOR"),
|
||||
7: .same(proto: "TAK"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -208,6 +208,10 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
/// Heltec HT-CT62 with ESP32-C3 CPU and SX1262 LoRa
|
||||
case heltecHt62 // = 53
|
||||
|
||||
///
|
||||
/// EBYTE SPI LoRa module and ESP32-S3
|
||||
case ebyteEsp32S3 // = 54
|
||||
|
||||
///
|
||||
/// ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
/// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
|
|
@ -265,6 +269,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case 51: self = .tWatchS3
|
||||
case 52: self = .picomputerS3
|
||||
case 53: self = .heltecHt62
|
||||
case 54: self = .ebyteEsp32S3
|
||||
case 255: self = .privateHw
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
|
|
@ -316,6 +321,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case .tWatchS3: return 51
|
||||
case .picomputerS3: return 52
|
||||
case .heltecHt62: return 53
|
||||
case .ebyteEsp32S3: return 54
|
||||
case .privateHw: return 255
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
|
|
@ -372,6 +378,7 @@ extension HardwareModel: CaseIterable {
|
|||
.tWatchS3,
|
||||
.picomputerS3,
|
||||
.heltecHt62,
|
||||
.ebyteEsp32S3,
|
||||
.privateHw,
|
||||
]
|
||||
}
|
||||
|
|
@ -2534,6 +2541,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
|
|||
51: .same(proto: "T_WATCH_S3"),
|
||||
52: .same(proto: "PICOMPUTER_S3"),
|
||||
53: .same(proto: "HELTEC_HT62"),
|
||||
54: .same(proto: "EBYTE_ESP32_S3"),
|
||||
255: .same(proto: "PRIVATE_HW"),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ struct CircleText: View {
|
|||
.foregroundColor(color.isLight() ? .black : .white)
|
||||
.font(.system(size: 500))
|
||||
.minimumScaleFactor(0.001)
|
||||
.frame(width: circleSize * 0.94, height: circleSize * 0.94, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
||||
.frame(width: circleSize * 0.95, height: circleSize * 0.95, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ struct LastHeardText: View {
|
|||
|
||||
var body: some View {
|
||||
if lastHeard != nil && lastHeard! >= sixMonthsAgo! {
|
||||
Text("heard")+Text(" \(LastHeardText.formatter.localizedString(for: lastHeard!, relativeTo: Date.now))")
|
||||
Text(lastHeard?.formatted() ?? "unknown.age".localized)
|
||||
} else {
|
||||
Text("unknown.age")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ struct LoRaSignalStrengthMeter: View {
|
|||
Gauge(value: Double(signalStrength.rawValue), in: 0...3) {
|
||||
} currentValueLabel: {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.font(.caption)
|
||||
.font(.callout)
|
||||
Text("Signal \(signalStrength.description)")
|
||||
.font(.caption)
|
||||
.font(.callout)
|
||||
}
|
||||
.gaugeStyle(.accessoryLinear)
|
||||
.tint(gradient)
|
||||
|
|
|
|||
|
|
@ -26,14 +26,14 @@ struct NodeListItem: View {
|
|||
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
|
||||
if deviceMetrics?.count ?? 0 >= 1 {
|
||||
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
|
||||
BatteryLevelCompact(batteryLevel: mostRecent?.batteryLevel, font: .caption2, iconFont: .callout, color: .accentColor)
|
||||
BatteryLevelCompact(batteryLevel: mostRecent?.batteryLevel, font: .caption, iconFont: .callout, color: .accentColor)
|
||||
}
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text(node.user?.longName ?? "unknown".localized)
|
||||
.fontWeight(.medium)
|
||||
.font(.callout)
|
||||
.font(.headline)
|
||||
if node.user?.vip ?? false {
|
||||
Spacer()
|
||||
Image(systemName: "star.fill")
|
||||
|
|
@ -43,19 +43,19 @@ struct NodeListItem: View {
|
|||
if connected {
|
||||
HStack {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right.circle.fill")
|
||||
.font(.footnote)
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.green)
|
||||
Text("connected").font(.caption)
|
||||
Text("connected").font(.callout)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Image(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill")
|
||||
.font(.footnote)
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(node.isOnline ? .green : .orange)
|
||||
LastHeardText(lastHeard: node.lastHeard)
|
||||
.font(.caption)
|
||||
.font(.callout)
|
||||
}
|
||||
if node.positions?.count ?? 0 > 0 && connectedNode != node.num {
|
||||
HStack {
|
||||
|
|
@ -65,19 +65,19 @@ struct NodeListItem: View {
|
|||
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
|
||||
let metersAway = nodeCoord.distance(from: myCoord)
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.font(.footnote)
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
DistanceText(meters: metersAway).font(.caption)
|
||||
DistanceText(meters: metersAway).font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.channel > 0 {
|
||||
HStack {
|
||||
Image(systemName: "fibrechannel")
|
||||
.font(.footnote)
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Channel: \(node.channel)")
|
||||
.font(.caption)
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ struct NodeMapSwiftUI: View {
|
|||
@ObservedObject var node: NodeInfoEntity
|
||||
@State var showUserLocation: Bool = false
|
||||
@State var positions: [PositionEntity] = []
|
||||
//@State var waypoints: [WaypointEntity] = []
|
||||
/// Map State User Defaults
|
||||
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
|
||||
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
|
||||
|
|
@ -30,24 +29,20 @@ struct NodeMapSwiftUI: View {
|
|||
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid
|
||||
// Map Configuration
|
||||
@Namespace var mapScope
|
||||
@State private var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true)
|
||||
@State private var position = MapCameraPosition.automatic
|
||||
@State private var scene: MKLookAroundScene?
|
||||
@State private var isLookingAround = false
|
||||
@State private var isEditingSettings = false
|
||||
@State private var selected: PositionEntity?
|
||||
@State private var selectedWaypoint: WaypointEntity?
|
||||
@State private var selectedWaypointRect: CGRect = .zero
|
||||
@State private var selectedWaypointPoint: CGPoint = .zero
|
||||
@State private var showingPositionPopover = false
|
||||
@State private var showingWaypointPopover = false
|
||||
@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true)
|
||||
@State var position = MapCameraPosition.automatic
|
||||
@State var scene: MKLookAroundScene?
|
||||
@State var isLookingAround = false
|
||||
@State var isEditingSettings = false
|
||||
@State var selected: PositionEntity?
|
||||
@State var selectedWaypoint: WaypointEntity?
|
||||
@State var showingPositionPopover = false
|
||||
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
|
||||
predicate: NSPredicate(
|
||||
format: "expire == nil || expire >= %@", Date() as NSDate
|
||||
), animation: .none)
|
||||
private var waypoints: FetchedResults<WaypointEntity>
|
||||
@State var waypoiintSelectionRect: CGRect = .zero
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -90,15 +85,10 @@ struct NodeMapSwiftUI: View {
|
|||
Annotation(waypoint.name ?? "?", coordinate: waypoint.coordinate) {
|
||||
ZStack {
|
||||
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 35)
|
||||
.onTapGesture(coordinateSpace: .global) { location in
|
||||
|
||||
.onTapGesture(coordinateSpace: .named("nodemap")) { location in
|
||||
print("Tapped at \(location)")
|
||||
let pinLocation = reader.convert(location, from: .local)
|
||||
print(pinLocation)
|
||||
let size = CGSize(width: 1, height: 50)
|
||||
let rect = CGRect(origin: location, size: size)
|
||||
selectedWaypointRect = rect
|
||||
selectedWaypointPoint = location
|
||||
showingWaypointPopover = true
|
||||
selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint)
|
||||
}
|
||||
}
|
||||
|
|
@ -203,12 +193,11 @@ struct NodeMapSwiftUI: View {
|
|||
.padding(.horizontal, 20)
|
||||
}
|
||||
}
|
||||
.popover(item: $selectedWaypoint, attachmentAnchor: .rect(.rect(selectedWaypointRect)), arrowEdge: .bottom) { selection in
|
||||
//.popover(isPresented: $showingWaypointPopover, arrowEdge: .bottom) {
|
||||
.sheet(item: $selectedWaypoint) { selection in
|
||||
WaypointPopover(waypoint: selection)
|
||||
.presentationDetents([.fraction(0.3), .medium])
|
||||
.padding()
|
||||
.opacity(0.8)
|
||||
.presentationCompactAdaptation(.popover)
|
||||
}
|
||||
.sheet(isPresented: $isEditingSettings) {
|
||||
VStack {
|
||||
|
|
@ -293,9 +282,7 @@ struct NodeMapSwiftUI: View {
|
|||
.padding()
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.fraction(0.60)])
|
||||
//.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
.presentationDetents([.fraction(0.4), .medium])
|
||||
}
|
||||
.onChange(of: node) {
|
||||
let mostRecent = node.positions?.lastObject as? PositionEntity
|
||||
|
|
@ -353,10 +340,11 @@ struct NodeMapSwiftUI: View {
|
|||
}
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
MapZoomStepper(scope: mapScope)
|
||||
.mapControlVisibility(.visible)
|
||||
MapPitchSlider(scope: mapScope)
|
||||
.mapControlVisibility(.visible)
|
||||
/// Hide non fuctional catalyst controls
|
||||
// MapZoomStepper(scope: mapScope)
|
||||
// .mapControlVisibility(.visible)
|
||||
// MapPitchSlider(scope: mapScope)
|
||||
// .mapControlVisibility(.visible)
|
||||
#endif
|
||||
}
|
||||
.controlSize(.regular)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ import SwiftUI
|
|||
import MapKit
|
||||
|
||||
struct WaypointPopover: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
var waypoint: WaypointEntity
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.blue)
|
||||
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange)
|
||||
Text(waypoint.name ?? "?")
|
||||
.font(.title3)
|
||||
if waypoint.locked > 0 {
|
||||
|
|
@ -31,7 +32,6 @@ struct WaypointPopover: View {
|
|||
Label {
|
||||
Text(waypoint.longDescription ?? "")
|
||||
.foregroundColor(.primary)
|
||||
.font(.footnote)
|
||||
.multilineTextAlignment(.leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
} icon: {
|
||||
|
|
@ -40,43 +40,57 @@ struct WaypointPopover: View {
|
|||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
Divider()
|
||||
}
|
||||
/// Coordinate
|
||||
Label {
|
||||
Text("Coordinates: \(String(format: "%.6f", waypoint.coordinate.latitude)), \(String(format: "%.6f", waypoint.coordinate.longitude))")
|
||||
//.font(.footnote)
|
||||
.textSelection(.enabled)
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
Divider()
|
||||
/// Created
|
||||
Label {
|
||||
Text("Created: \(waypoint.created?.formatted() ?? "?")")
|
||||
.foregroundColor(.primary)
|
||||
.font(.footnote)
|
||||
} icon: {
|
||||
Image(systemName: "clock.badge.checkmark")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
Divider()
|
||||
/// Updated
|
||||
if waypoint.lastUpdated != nil {
|
||||
Label {
|
||||
Text("Updated: \(waypoint.lastUpdated?.formatted() ?? "?")")
|
||||
.foregroundColor(.primary)
|
||||
.font(.footnote)
|
||||
} icon: {
|
||||
Image(systemName: "clock.arrow.circlepath")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
Divider()
|
||||
}
|
||||
/// Updated
|
||||
/// Expires
|
||||
if waypoint.expire != nil {
|
||||
Label {
|
||||
Text("Expires: \(waypoint.expire?.formatted() ?? "?")")
|
||||
.foregroundColor(.primary)
|
||||
.font(.footnote)
|
||||
} icon: {
|
||||
Image(systemName: "clock.badge.xmark")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
Divider()
|
||||
}
|
||||
/// Distance
|
||||
if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
|
||||
|
|
@ -84,14 +98,26 @@ struct WaypointPopover: View {
|
|||
Label {
|
||||
Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
|
||||
.foregroundColor(.primary)
|
||||
.font(.footnote)
|
||||
} icon: {
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
#endif
|
||||
}
|
||||
.tag(waypoint.id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ struct AdminMessageList: View {
|
|||
|
||||
var body: some View {
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current)
|
||||
let localeTimeFormat = DateFormatter.dateFormat(fromTemplate: "h:mm:ss a", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss a")
|
||||
let timeFormatString = (localeTimeFormat ?? "h:mm:ss a")
|
||||
|
||||
List {
|
||||
if user != nil {
|
||||
|
|
@ -55,7 +57,7 @@ struct AdminMessageList: View {
|
|||
}
|
||||
|
||||
if am.receivedACK && am.ackTimestamp > 0 {
|
||||
Text(" \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: "h:mm:ss a"))")
|
||||
Text(" \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: timeFormatString))")
|
||||
.foregroundColor(am.realACK ? .gray : .orange)
|
||||
.font(.caption2)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ struct BluetoothConfig: View {
|
|||
setBluetoothValues()
|
||||
}
|
||||
}
|
||||
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? -1 {
|
||||
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
|
||||
.font(.title3)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ struct Settings: View {
|
|||
private var nodes: FetchedResults<NodeInfoEntity>
|
||||
@State private var selectedNode: Int = 0
|
||||
@State private var connectedNodeNum: Int = 0
|
||||
@State private var initialLoad: Bool = true
|
||||
@State private var selection: SettingsSidebar = .about
|
||||
enum SettingsSidebar {
|
||||
case appSettings
|
||||
|
|
@ -59,39 +58,44 @@ struct Settings: View {
|
|||
}
|
||||
.tag(SettingsSidebar.appSettings)
|
||||
let node = nodes.first(where: { $0.num == connectedNodeNum })
|
||||
let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 ? true : false
|
||||
if !(node?.deviceConfig?.isManaged ?? false) {
|
||||
Section("Configure") {
|
||||
Picker("Configuring Node", selection: $selectedNode) {
|
||||
if selectedNode == 0 {
|
||||
Text("Connect to a Node").tag(0)
|
||||
}
|
||||
ForEach(nodes) { node in
|
||||
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Text("BLE Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
} else if node.metadata != nil {
|
||||
Text("Remote Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
} else {
|
||||
Text("Request Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
if hasAdmin {
|
||||
Picker("Configuring Node", selection: $selectedNode) {
|
||||
if selectedNode == 0 {
|
||||
Text("Connect to a Node").tag(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(.automatic)
|
||||
.labelsHidden()
|
||||
.onChange(of: selectedNode) { newValue in
|
||||
if selectedNode > 0 {
|
||||
let node = nodes.first(where: { $0.num == newValue })
|
||||
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
|
||||
connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil {
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context)
|
||||
if adminMessageId > 0 {
|
||||
print("Sent node metadata request from node details")
|
||||
ForEach(nodes) { node in
|
||||
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Text("BLE Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
} else if node.metadata != nil {
|
||||
Text("Remote Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
} else if hasAdmin {
|
||||
Text("Request Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(.automatic)
|
||||
.labelsHidden()
|
||||
.onChange(of: selectedNode) { newValue in
|
||||
if selectedNode > 0 {
|
||||
let node = nodes.first(where: { $0.num == newValue })
|
||||
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
|
||||
connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil {
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context)
|
||||
if adminMessageId > 0 {
|
||||
print("Sent node metadata request from node details")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("Configuring Node \(node?.user?.longName ?? "unknown".localized)")
|
||||
}
|
||||
}
|
||||
Section("radio.configuration") {
|
||||
|
|
@ -276,12 +280,11 @@ struct Settings: View {
|
|||
}
|
||||
}
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
self.connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
if initialLoad {
|
||||
selectedNode = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
initialLoad = false
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
self.connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
selectedNode = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
}
|
||||
.listStyle(GroupedListStyle())
|
||||
.navigationTitle("settings")
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ struct UserConfig: View {
|
|||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
@State var hasChanges = false
|
||||
@State var shortName = ""
|
||||
@State var longName = ""
|
||||
@State var longName: String = ""
|
||||
@State var isLicensed = false
|
||||
@State var overrideDutyCycle = false
|
||||
@State var overrideFrequency: Float = 0.0
|
||||
|
|
@ -157,10 +157,11 @@ struct UserConfig: View {
|
|||
}
|
||||
} else {
|
||||
var ham = HamParameters()
|
||||
// ham.shortName = shortName
|
||||
ham.shortName = shortName
|
||||
ham.callSign = longName
|
||||
ham.txPower = Int32(txPower)
|
||||
ham.frequency = overrideFrequency
|
||||
print(ham)
|
||||
let adminMessageId = bleManager.saveLicensedUser(ham: ham, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
hasChanges = false
|
||||
|
|
@ -201,7 +202,14 @@ struct UserConfig: View {
|
|||
}
|
||||
.onChange(of: isLicensed) { newIsLicensed in
|
||||
if node != nil && node!.user != nil {
|
||||
if newIsLicensed != node?.user!.isLicensed { hasChanges = true }
|
||||
if newIsLicensed != node?.user!.isLicensed {
|
||||
hasChanges = true
|
||||
if newIsLicensed {
|
||||
if node?.user?.longName?.count ?? 0 > 8 {
|
||||
longName = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: overrideFrequency) { _ in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue