Merge pull request #391 from meshtastic/2.2.4_Working_Changes_iOS16

2.2.4 working changes  iOS 16
This commit is contained in:
Garth Vander Houwen 2023-09-05 17:12:01 -07:00 committed by GitHub
commit 55b18fe9b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 351 additions and 498 deletions

View file

@ -12,6 +12,8 @@
6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; };
C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; };
C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; };
DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */; };
DD007BB02AA5981000F5FA12 /* NodeInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */; };
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; };
DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */; };
DD14E72E2A82A614006E39BC /* RemoteHardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD14E72D2A82A614006E39BC /* RemoteHardware.swift */; };
@ -68,6 +70,7 @@
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; };
DD73FD1128750779000852D6 /* PositionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* PositionLog.swift */; };
DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */; };
DD77093F2AA1B146007A8BF0 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD77093E2AA1B146007A8BF0 /* UIColor.swift */; };
DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD798B062915928D005217CD /* ChannelMessageList.swift */; };
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; };
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; };
@ -77,7 +80,6 @@
DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */; };
DD86D40F2881BE4C00BAEB7A /* CsvDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD86D40E2881BE4C00BAEB7A /* CsvDocument.swift */; };
DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD86D4102881D16900BAEB7A /* WriteCsvFile.swift */; };
DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD882F5C2772E4640005BF05 /* Contacts.swift */; };
DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8EBF42285058FA00426DCA /* DisplayConfig.swift */; };
DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */; };
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */; };
@ -207,6 +209,8 @@
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = "<group>"; };
A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = "<group>"; };
DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = "<group>"; };
DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = "<group>"; };
DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV14.xcdatamodel; sourceTree = "<group>"; };
DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminMessageList.swift; sourceTree = "<group>"; };
DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV15.xcdatamodel; sourceTree = "<group>"; };
@ -268,6 +272,7 @@
DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = "<group>"; };
DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = "<group>"; };
DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = "<group>"; };
DD77093E2AA1B146007A8BF0 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
DD798B062915928D005217CD /* ChannelMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelMessageList.swift; sourceTree = "<group>"; };
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = "<group>"; };
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = "<group>"; };
@ -277,7 +282,6 @@
DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveChannelQRCode.swift; sourceTree = "<group>"; };
DD86D40E2881BE4C00BAEB7A /* CsvDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CsvDocument.swift; sourceTree = "<group>"; };
DD86D4102881D16900BAEB7A /* WriteCsvFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteCsvFile.swift; sourceTree = "<group>"; };
DD882F5C2772E4640005BF05 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = "<group>"; };
DD8EBF42285058FA00426DCA /* DisplayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayConfig.swift; sourceTree = "<group>"; };
DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConfig.swift; sourceTree = "<group>"; };
DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingError.swift; sourceTree = "<group>"; };
@ -437,6 +441,20 @@
path = Custom;
sourceTree = "<group>";
};
DD007BB12AA59B9A00F5FA12 /* CoreData */ = {
isa = PBXGroup;
children = (
DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */,
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */,
DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */,
DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */,
DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */,
DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */,
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */,
);
path = CoreData;
sourceTree = "<group>";
};
DD47E3CA26F0E50300029299 /* Nodes */ = {
isa = PBXGroup;
children = (
@ -630,16 +648,16 @@
DDC2E15626CE248E0042C5E4 /* Meshtastic */ = {
isa = PBXGroup;
children = (
DDDB443E29F79A9400EE2349 /* Extensions */,
DD90860A26F645B700DC5189 /* Meshtastic.entitlements */,
DD8ED9C6289CE4A100B3B0AB /* Enums */,
DD86D40D2881BDB300BAEB7A /* Export */,
DDDB443E29F79A9400EE2349 /* Extensions */,
DDC2E1A526CEB32B0042C5E4 /* Helpers */,
DDC2E18826CE24EE0042C5E4 /* Model */,
DDC4D5662754996200A4208E /* Persistence */,
DDAF8C5626ED07740058C060 /* Protobufs */,
DD86D40D2881BDB300BAEB7A /* Export */,
DDC2E1A526CEB32B0042C5E4 /* Helpers */,
DDC2E18726CE24E40042C5E4 /* Views */,
DDC2E18826CE24EE0042C5E4 /* Model */,
DDC2E18926CE24F70042C5E4 /* Resources */,
DDC2E18726CE24E40042C5E4 /* Views */,
DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */,
DDC2E16526CE248F0042C5E4 /* Info.plist */,
DDC2E15D26CE248F0042C5E4 /* Preview Content */,
@ -710,7 +728,6 @@
children = (
DDB8F4132A9EE5F000230ECE /* ChannelList.swift */,
DD798B062915928D005217CD /* ChannelMessageList.swift */,
DD882F5C2772E4640005BF05 /* Contacts.swift */,
DDB8F40F2A9EE5B400230ECE /* Messages.swift */,
DDB8F4112A9EE5DD00230ECE /* UserList.swift */,
DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */,
@ -759,13 +776,8 @@
isa = PBXGroup;
children = (
DDC4D567275499A500A4208E /* Persistence.swift */,
DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */,
DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */,
DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */,
DD964FC52975DBFD007C176F /* QueryCoreData.swift */,
DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */,
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */,
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */,
);
path = Persistence;
sourceTree = "<group>";
@ -781,6 +793,7 @@
DDDB443E29F79A9400EE2349 /* Extensions */ = {
isa = PBXGroup;
children = (
DD007BB12AA59B9A00F5FA12 /* CoreData */,
DDDB444529F8A96500EE2349 /* Character.swift */,
DDDB444929F8AA3A00EE2349 /* CLLocationCoordinate2D.swift */,
DDDB444B29F8AAA600EE2349 /* Color.swift */,
@ -790,6 +803,7 @@
DDDB444329F8A8DD00EE2349 /* Float.swift */,
DDDB444D29F8AB0E00EE2349 /* Int.swift */,
DDDB444729F8A9C900EE2349 /* String.swift */,
DD77093E2AA1B146007A8BF0 /* UIColor.swift */,
DDDB444F29F8AC9C00EE2349 /* UIImage.swift */,
DDDB443F29F79AB000EE2349 /* UserDefaults.swift */,
DDB75A0E2A05920E006ED576 /* FileManager.swift */,
@ -1073,6 +1087,7 @@
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */,
DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */,
DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */,
DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */,
DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */,
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */,
DD5E5207298EE33B00D21B61 /* connection_status.pb.swift in Sources */,
@ -1081,11 +1096,11 @@
DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */,
DDB8F4142A9EE5F000230ECE /* ChannelList.swift in Sources */,
DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */,
DD007BB02AA5981000F5FA12 /* NodeInfoEntityExtension.swift in Sources */,
DDDB444629F8A96500EE2349 /* Character.swift in Sources */,
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */,
DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */,
DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */,
DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */,
DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */,
6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */,
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */,
@ -1145,6 +1160,7 @@
DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */,
DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */,
DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */,
DD77093F2AA1B146007A8BF0 /* UIColor.swift in Sources */,
DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */,
DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */,
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */,
@ -1365,7 +1381,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.2.3;
MARKETING_VERSION = 2.2.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1399,7 +1415,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.2.3;
MARKETING_VERSION = 2.2.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1521,7 +1537,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.3;
MARKETING_VERSION = 2.2.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1554,7 +1570,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.3;
MARKETING_VERSION = 2.2.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View file

@ -0,0 +1,20 @@
//
// MyInfoEntityExtension.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 9/3/23.
//
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 }
return unreadMessages.count
}
}

View file

@ -0,0 +1,27 @@
//
// NodeInfoEntityExtension.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 9/3/23.
//
import Foundation
extension NodeInfoEntity {
var hasPositions: Bool {
return positions?.count ?? 0 > 0
}
var hasDeviceMetrics: Bool {
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 }
return environmentMetrics?.count ?? 0 > 0
}
}

View file

@ -18,7 +18,6 @@ extension UserEntity {
}
var unreadMessages: Int {
let unreadMessages = messageList.filter{ ($0 as AnyObject).read == false }
return unreadMessages.count
}

View file

@ -15,6 +15,10 @@ extension Date {
func formattedDate(format: String) -> String {
let dateformat = DateFormatter()
dateformat.dateFormat = format
return dateformat.string(from: self)
if self > Calendar.current.date(byAdding: .year, value: -5, to: Date())! {
return dateformat.string(from: self)
} else {
return "unknown.age".localized
}
}
}

View file

@ -0,0 +1,41 @@
//
// UIColor.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 8/31/23.
//
import Foundation
import Swift
import UIKit
extension UIColor {
private func makeColor(componentDelta: CGFloat) -> UIColor {
var red: CGFloat = 0
var blue: CGFloat = 0
var green: CGFloat = 0
var alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return UIColor(
red: add(componentDelta, toComponent: red),
green: add(componentDelta, toComponent: green),
blue: add(componentDelta, toComponent: blue),
alpha: alpha
)
}
func lighter(componentDelta: CGFloat = 0.1) -> UIColor {
return makeColor(componentDelta: componentDelta)
}
func darker(componentDelta: CGFloat = 0.1) -> UIColor {
return makeColor(componentDelta: -1*componentDelta)
}
private func add(_ value: CGFloat, toComponent: CGFloat) -> CGFloat {
return max(0, min(1, toComponent + value))
}
}

View file

@ -28,6 +28,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
@Published var isSwitchedOn: Bool = false
@Published var automaticallyReconnect: Bool = true
@Published var mqttProxyConnected: Bool = false
@StateObject var appState = AppState.shared
public var minimumVersion = "2.0.0"
public var connectedVersion: String
public var isConnecting: Bool = false
@ -471,7 +473,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
if nodeInfo != nil {
if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo!.num {
if nodeInfo!.user != nil {
connectedPeripheral.shortName = nodeInfo?.user?.shortName ?? "????"
connectedPeripheral.shortName = nodeInfo?.user?.shortName ?? "?"
connectedPeripheral.longName = nodeInfo?.user?.longName ?? "unknown".localized
}
}
@ -516,6 +518,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
let version = decodedInfo.metadata.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: decodedInfo.metadata.firmwareVersion))]
nowKnown = true
connectedVersion = String(version.dropLast())
appState.firmwareVersion = connectedVersion
}
let supportedVersion = connectedVersion == "0.0.0" || self.minimumVersion.compare(connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(connectedVersion, options: .numeric) == .orderedSame
@ -613,11 +616,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(connectedPeripheral.num))
do {
let fetchedNodeInfo = try context?.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] ?? []
if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].mqttConfig != nil {
//Subscribe to Mqtt Client Proxy if enabled
if fetchedNodeInfo[0].mqttConfig?.proxyToClientEnabled ?? false {
if fetchedNodeInfo.count == 1 {
// Subscribe to Mqtt Client Proxy if enabled
if fetchedNodeInfo[0].mqttConfig != nil && fetchedNodeInfo[0].mqttConfig?.enabled ?? false && fetchedNodeInfo[0].mqttConfig?.proxyToClientEnabled ?? false {
mqttManager.connectFromConfigSettings(node: fetchedNodeInfo[0])
} else {
if mqttProxyConnected {
mqttManager.mqttClientProxy?.disconnect()
}
}
// Set initial unread message badge states
let appState = AppState.shared
appState.unreadChannelMessages = fetchedNodeInfo[0].myInfo?.unreadMessages ?? 0
appState.unreadDirectMessages = fetchedNodeInfo[0].user?.unreadMessages ?? 0
//appState.connectedNode = fetchedNodeInfo[0]
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
}
if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].rangeTestConfig?.enabled == true {
wantRangeTestPackets = true;
@ -707,7 +721,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
if toUserNum > 0 {
newMessage.toUser = fetchedUsers.first(where: { $0.num == toUserNum })
newMessage.toUser?.lastMessage = Date()
newMessage.toUser?.objectWillChange.send()
}
newMessage.fromUser = fetchedUsers.first(where: { $0.num == fromUserNum })
newMessage.isEmoji = isEmoji
@ -718,6 +731,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
}
newMessage.messagePayload = message
newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: message)
newMessage.read = true
let dataType = PortNum.textMessageApp
var messageQuotesReplaced = message.replacingOccurrences(of: "", with: "'")
@ -2143,7 +2157,7 @@ extension BLEManager: CBCentralManagerDelegate {
print(" BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")")
}
let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String
let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "????", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral)
let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "?", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral)
let index = peripherals.map { $0.peripheral }.firstIndex(of: peripheral)
if let peripheralIndex = index {

View file

@ -237,7 +237,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
return nil
}
// Not Found Insert
if fetchedNode.isEmpty && nodeInfo.hasUser {
if fetchedNode.isEmpty {
let newNode = NodeInfoEntity(context: context)
newNode.id = Int64(nodeInfo.num)
@ -296,6 +296,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
}
do {
try context.save()
print("💾 Saved a new Node Info For: \(String(nodeInfo.num))")
return newNode
} catch {
context.rollback()
@ -314,7 +315,9 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
fetchedNode[0].channel = Int32(nodeInfo.channel)
if nodeInfo.hasUser {
if (fetchedNode[0].user == nil) {
fetchedNode[0].user = UserEntity(context: context)
}
fetchedNode[0].user!.userId = nodeInfo.user.id
fetchedNode[0].user!.num = Int64(nodeInfo.num)
fetchedNode[0].user!.longName = nodeInfo.user.longName
@ -532,26 +535,26 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
fetchedMessage![0].ackSNR = packet.rxSnr
fetchedMessage![0].ackTimestamp = Int32(packet.rxTime)
if fetchedMessage![0].toUser != nil {
fetchedMessage![0].toUser?.objectWillChange.send()
} else {
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", connectedNodeNum)
do {
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity]
if fetchedMyInfo?.count ?? 0 > 0 {
for ch in fetchedMyInfo![0].channels!.array as? [ChannelEntity] ?? [] {
if ch.index == packet.channel {
ch.objectWillChange.send()
}
}
}
} catch {
}
}
// if fetchedMessage![0].toUser != nil {
// //fetchedMessage![0].toUser?.objectWillChange.send()
// } else {
// let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
// fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", connectedNodeNum)
// do {
// let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity]
// if fetchedMyInfo?.count ?? 0 > 0 {
//
// for ch in fetchedMyInfo![0].channels!.array as? [ChannelEntity] ?? [] {
//
// if ch.index == packet.channel {
// // ch.objectWillChange.send()
// }
// }
// }
// } catch {
//
// }
// }
} else {
return
@ -750,10 +753,9 @@ func textMessageAppPacket(packet: MeshPacket, blockRangeTest: Bool, connectedNod
}
newMessage.messagePayload = messageText
newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: messageText)
if packet.to != 4294967295 {
if packet.to != 4294967295 && newMessage.fromUser != nil {
newMessage.fromUser?.lastMessage = Date()
}
var messageSaved = false
do {
@ -763,14 +765,20 @@ func textMessageAppPacket(packet: MeshPacket, blockRangeTest: Bool, connectedNod
messageSaved = true
if messageSaved {
let appState = AppState.shared
if newMessage.fromUser != nil && newMessage.toUser != nil && !(newMessage.fromUser?.mute ?? false) {
// Set Unread Message Indicators
if packet.to == connectedNode {
appState.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
}
// Create an iOS Notification for the received DM message and schedule it immediately
let manager = LocalNotificationManager()
manager.notifications = [
Notification(
id: ("notification.id.\(newMessage.messageId)"),
title: "\(newMessage.fromUser?.longName ?? "unknown".localized)",
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")",
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")",
content: messageText,
target: "message"
)
@ -787,6 +795,9 @@ func textMessageAppPacket(packet: MeshPacket, blockRangeTest: Bool, connectedNod
return
}
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)
@ -798,7 +809,7 @@ func textMessageAppPacket(packet: MeshPacket, blockRangeTest: Bool, connectedNod
Notification(
id: ("notification.id.\(newMessage.messageId)"),
title: "\(newMessage.fromUser?.longName ?? "unknown".localized)",
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")",
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")",
content: messageText,
target: "message")
]

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22G90" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22221.1" systemVersion="23A5337a" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
@ -181,6 +181,9 @@
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
<fetchedProperty name="allMessages" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="toUser == nil"/>
</fetchedProperty>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="myNodeNum"/>

View file

@ -14,6 +14,7 @@ struct MeshtasticAppleApp: App {
@State var incomingUrl: URL?
@State var channelSettings: String?
@StateObject var appState = AppState.shared
var body: some Scene {
WindowGroup {
ContentView()
@ -121,4 +122,8 @@ class AppState: ObservableObject {
static let shared = AppState()
@Published var tabSelection: Tab = .ble
@Published var unreadDirectMessages: Int = 0
@Published var unreadChannelMessages: Int = 0
@Published var firmwareVersion: String = "0.0.0"
@Published var connectedNode: NodeInfoEntity?
}

View file

@ -9,7 +9,12 @@ import SwiftUI
class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("App launched!")
print("🚀 Meshtstic Apple App launched!")
// Default User Default Values
UserDefaults.standard.register(defaults: ["blockRangeTest" : true])
UserDefaults.standard.register(defaults: ["meshMapRecentering" : true])
UserDefaults.standard.register(defaults: ["meshMapShowNodeHistory" : true])
UserDefaults.standard.register(defaults: ["meshMapShowRouteLines" : true])
UNUserNotificationCenter.current().delegate = self
return true
}

View file

@ -12,18 +12,6 @@ struct Peripheral: Identifiable {
var lastUpdate: Date
var peripheral: CBPeripheral
// init(id: String, num: Int64, name: String, shortName: String, longName: String, firmwareVersion: String, rssi: Int, lastUpdate: Date, peripheral: CBPeripheral) {
// self.id = id
// self.num = num
// self.name = name
// self.shortName = shortName
// self.longName = longName
// self.firmwareVersion = firmwareVersion
// self.rssi = rssi
// self.lastUpdate = lastUpdate
// self.peripheral = peripheral
// }
func getSignalStrength() -> BLESignalStrength {
if NSNumber(value: rssi).compare(NSNumber(-65)) == ComparisonResult.orderedDescending {
return BLESignalStrength.strong

View file

@ -143,7 +143,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)")
}
newNode.myInfo = myInfoEntity
newNode.objectWillChange.send()
//newNode.objectWillChange.send()
} else {
// Update an existing node
@ -476,7 +476,6 @@ func upsertLoRaConfigPacket(config: Meshtastic.Config.LoRaConfig, nodeNum: Int64
}
do {
try context.save()
context.refresh(fetchedNode[0], mergeChanges: true)
print("💾 Updated LoRa Config for node number: \(String(nodeNum))")
} catch {
context.rollback()

View file

@ -49,7 +49,7 @@ struct Connect: View {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected {
HStack {
VStack(alignment: .center) {
CircleText(text: node?.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 90, fontSize: (node?.user?.shortName ?? "???").isEmoji() ? 52 : (node?.user?.shortName?.count ?? 0 == 4 ? 26 : 36), textColor: UIColor(hex: UInt32(node?.num ?? 0)).isLight() ? .black : .white )
CircleText(text: node?.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 90)
}
.padding(.trailing)
VStack(alignment: .leading) {
@ -105,7 +105,7 @@ struct Connect: View {
}
#endif
Text("Num: \(String(node!.num))")
Text("Short Name: \(node?.user?.shortName ?? "????")")
Text("Short Name: \(node?.user?.shortName ?? "?")")
Text("Long Name: \(node?.user?.longName ?? "unknown".localized)")
Text("BLE RSSI: \(bleManager.connectedPeripheral.rssi)")
}
@ -247,7 +247,7 @@ struct Connect: View {
.navigationTitle("bluetooth")
.navigationBarItems(leading: MeshtasticLogo(), trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????", mqttProxyConnected: bleManager.mqttProxyConnected)
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected)
})
}
.sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) {

View file

@ -13,7 +13,7 @@ struct ContentView: View {
Label("messages", systemImage: "message")
}
.tag(Tab.contacts)
// .badge(42)
.badge(appState.unreadDirectMessages + appState.unreadChannelMessages)
Connect()
.tabItem {
Label("bluetooth", systemImage: "antenna.radiowaves.left.and.right")

View file

@ -8,29 +8,44 @@ import SwiftUI
struct CircleText: View {
var text: String
var color: Color
var circleSize: CGFloat? = 60
var fontSize: CGFloat? = 20
var brightness: Double? = 0
var textColor: Color? = .white
var circleSize: CGFloat = 45
var body: some View {
let font = Font.system(size: fontSize!)
ZStack {
Circle()
.fill(color)
.brightness(brightness ?? 0)
.frame(width: circleSize, height: circleSize)
Text(text).textCase(.uppercase).font(font).foregroundColor(textColor).fixedSize()
.frame(width: circleSize, height: circleSize, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/).offset(x: 0, y: 0)
Text(text)
.textCase(.uppercase)
.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@*/)
}
.aspectRatio(1, contentMode: .fit)
}
}
struct CircleText_Previews: PreviewProvider {
static var previews: some View {
CircleText(text: "MOMO", color: Color.accentColor)
.previewLayout(.fixed(width: 300, height: 100))
VStack {
CircleText(text: "MOMO", color: Color.secondary, circleSize: 80)
.previewLayout(.fixed(width: 300, height: 100))
CircleText(text: "WWWW", color: Color.accentColor, circleSize: 80)
.previewLayout(.fixed(width: 300, height: 100))
CircleText(text: "LCP", color: Color.primary, circleSize: 80)
.previewLayout(.fixed(width: 300, height: 100))
CircleText(text: "69", color: Color.green, circleSize: 80)
.previewLayout(.fixed(width: 300, height: 100))
CircleText(text: "N1", color: Color.yellow, circleSize: 80)
.previewLayout(.fixed(width: 300, height: 100))
CircleText(text: "8", color: Color.purple, circleSize: 80)
.previewLayout(.fixed(width: 300, height: 100))
CircleText(text: "😝", color: Color.red, circleSize: 80)
.previewLayout(.fixed(width: 300, height: 100))
CircleText(text: "🍔", color: Color.brown, circleSize: 80)
.previewLayout(.fixed(width: 300, height: 100))
}
}
}

View file

@ -27,7 +27,7 @@ struct NodeInfoView: View {
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
HStack {
VStack(alignment: .center) {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 150, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 105 : 55, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white )
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 150)
}
Divider()
VStack {
@ -58,8 +58,9 @@ struct NodeInfoView: View {
}
Divider()
}
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
if deviceMetrics?.count ?? 0 >= 1 {
if node.hasDeviceMetrics {
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
VStack(alignment: .center) {
BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0))
@ -123,7 +124,7 @@ struct NodeInfoView: View {
HStack {
VStack(alignment: .center) {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 42 : 20, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white )
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65)
}
if node.user != nil {
Divider()
@ -177,7 +178,7 @@ struct NodeInfoView: View {
.symbolRenderingMode(.hierarchical)
Text("User Id:").font(.title2)
}
Text(node.user?.userId ?? "??????").font(.title3).foregroundColor(.gray)
Text(node.user?.userId ?? "?").font(.title3).foregroundColor(.gray)
}
Divider()
VStack {
@ -196,7 +197,7 @@ struct NodeInfoView: View {
VStack {
if (node.positions?.count ?? 0) > 0 {
if node.hasPositions{
NavigationLink {
PositionLog(node: node)
@ -213,20 +214,22 @@ struct NodeInfoView: View {
Divider()
}
if (node.telemetries?.count ?? 0) > 0 {
if node.hasDeviceMetrics {
NavigationLink {
DeviceMetricsLog(node: node)
} label: {
Image(systemName: "flipphone")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Device Metrics Log")
.font(.title3)
}
Divider()
}
if node.hasEnvironmentMetrics {
NavigationLink {
EnvironmentMetricsLog(node: node)
} label: {

View file

@ -75,23 +75,24 @@ struct MapViewSwiftUI: UIViewRepresentable {
mapView.showsBuildings = true
mapView.showsScale = true
mapView.showsTraffic = true
#if targetEnvironment(macCatalyst)
// Show the default always visible compass and the mac only controls
mapView.showsCompass = true
mapView.showsZoomControls = true
mapView.showsPitchControl = true
#else
#if os(iOS)
// Move the default compass under the mapbuttons control
mapView.showsCompass = false
let compass = MKCompassButton(mapView: mapView)
compass.translatesAutoresizingMaskIntoConstraints = false
#if targetEnvironment(macCatalyst)
// Show the default always visible compass and the mac only controls
compass.compassVisibility = .visible
mapView.addSubview(compass)
mapView.showsZoomControls = true
mapView.showsPitchControl = true
compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -115).isActive = true
compass.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -5).isActive = true
#else
compass.compassVisibility = .adaptive
mapView.addSubview(compass)
compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true
compass.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 145).isActive = true
#endif
#endif
}
private func setMapBaseLayer(mapView: MKMapView) {
// Avoid refreshing UI if selectedLayer has not changed
@ -251,18 +252,17 @@ struct MapViewSwiftUI: UIViewRepresentable {
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(waypoints)
}
mapView.addAnnotations(showNodeHistory ? positions : latest)
if userTrackingMode == MKUserTrackingMode.none {
mapView.showsUserLocation = false
if UserDefaults.enableMapRecentering {
if latest.count == 1 {
mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true)
} else {
mapView.addAnnotations(showNodeHistory ? positions : latest)
mapView.fitAllAnnotations()
}
}
} else {
mapView.addAnnotations(showNodeHistory ? positions : latest)
mapView.showsUserLocation = true
}
mapView.setUserTrackingMode(userTrackingMode, animated: true)
@ -290,11 +290,11 @@ struct MapViewSwiftUI: UIViewRepresentable {
annotationView.tag = -1
annotationView.canShowCallout = true
if positionAnnotation.latest {
annotationView.markerTintColor = .systemRed
annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).darker()
annotationView.displayPriority = .required
annotationView.titleVisibility = .visible
} else {
annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0))
annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).lighter()
annotationView.displayPriority = .defaultHigh
annotationView.titleVisibility = .adaptive
}
@ -409,9 +409,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
switch view.annotation {
case let positionAnnotation as PositionEntity:
print(positionAnnotation)
case let waypointAnnotation as WaypointEntity:
case _ as WaypointEntity:
// Only Allow Edit for waypoint annotations with a id
if view.tag > 0 {
parent.onWaypointEdit(view.tag)
@ -442,7 +440,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
if let routePolyline = overlay as? MKPolyline {
let titleString = routePolyline.title ?? "0"
let renderer = MKPolylineRenderer(polyline: routePolyline)
renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0)
renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0).lighter()
renderer.lineWidth = 8
return renderer
}

View file

@ -10,6 +10,7 @@ import CoreData
struct ChannelList: View {
@StateObject var appState = AppState.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@ -32,7 +33,7 @@ struct ChannelList: View {
ForEach(node!.myInfo!.channels!.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in
if channel.name?.lowercased() ?? "" != "admin" && channel.name?.lowercased() ?? "" != "gpio" && channel.name?.lowercased() ?? "" != "serial" {
NavigationLink(destination: ChannelMessageList(channel: channel)) {
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 ))))
@ -47,7 +48,7 @@ struct ChannelList: View {
.foregroundColor(.accentColor)
.brightness(0.2)
}
CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 40)
CircleText(text: String(channel.index), color: .accentColor)
.brightness(0.2)
VStack(alignment: .leading){
@ -100,23 +101,6 @@ struct ChannelList: View {
}
.frame(height: 62)
.contextMenu {
Button {
channel.mute = !channel.mute
do {
try context.save()
// Would rather not do this but the merge changes on
// A single object is only working on mac GVH
context.refreshAllObjects()
// context.refresh(channel, mergeChanges: true)
} catch {
context.rollback()
print("💥 Save Channel Mute Error")
}
} label: {
Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash")
}
if channel.allPrivateMessages.count > 0 {
Button(role: .destructive) {
isPresentingDeleteChannelMessagesConfirm = true
@ -134,6 +118,7 @@ struct ChannelList: View {
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")

View file

@ -10,6 +10,7 @@ import CoreData
struct ChannelMessageList: View {
@StateObject var appState = AppState.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@ -23,6 +24,7 @@ struct ChannelMessageList: View {
var maxbytes = 228
@FocusState var focusedField: Field?
@ObservedObject var myInfo: MyInfoEntity
@ObservedObject var channel: ChannelEntity
@State var showDeleteMessageAlert = false
@State private var deleteMessageId: Int64 = 0
@ -56,7 +58,7 @@ struct ChannelMessageList: View {
HStack(alignment: .top) {
if currentUser { Spacer(minLength: 50) }
if !currentUser {
CircleText(text: message.fromUser?.shortName ?? "????", color: Color(UIColor(hex: UInt32(message.fromUser?.num ?? 0))), circleSize: 44, fontSize: 14, textColor: UIColor(hex: UInt32(message.fromUser?.num ?? 0)).isLight() ? .black : .white)
CircleText(text: message.fromUser?.shortName ?? "?", color: Color(UIColor(hex: UInt32(message.fromUser?.num ?? 0))), circleSize: 44)
.padding(.all, 5)
.offset(y: -5)
}
@ -170,7 +172,7 @@ struct ChannelMessageList: View {
VStack {
let image = tapback.messagePayload!.image(fontSize: 20)
Image(uiImage: image!).font(.caption)
Text("\(tapback.fromUser?.shortName ?? "????")")
Text("\(tapback.fromUser?.shortName ?? "?")")
.font(.caption2)
.foregroundColor(.gray)
.fixedSize()
@ -229,10 +231,12 @@ struct ChannelMessageList: View {
.onAppear {
if !message.read {
message.read = true
message.toUser?.objectWillChange.send()
do {
try context.save()
print("Read message \(message.messageId) ")
appState.unreadChannelMessages = myInfo.unreadMessages
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
context.refresh(myInfo, mergeChanges: true)
} catch {
print("Failed to read message \(message.messageId)")
}
@ -401,7 +405,7 @@ struct ChannelMessageList: View {
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
CircleText(text: String(channel.index), color: .accentColor, circleSize: 44, fontSize: 30).fixedSize()
CircleText(text: String(channel.index), color: .accentColor, circleSize: 44).fixedSize()
Text(String(channel.name ?? "unknown".localized).camelCaseToWords()).font(.headline)
}
}
@ -410,7 +414,7 @@ struct ChannelMessageList: View {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
}
}
}

View file

@ -1,301 +0,0 @@
//
// Contacts.swift
// MeshtasticApple
//
// Created by Garth Vander Houwen on 12/21/21.
//
//
//import SwiftUI
//import CoreData
//
//struct Contacts: View {
//
// @Environment(\.managedObjectContext) var context
// @EnvironmentObject var bleManager: BLEManager
//
// @FetchRequest(
// sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), NSSortDescriptor(key: "longName", ascending: true)],
// animation: .default)
//
// private var users: FetchedResults<UserEntity>
// @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 isPresentingDeleteChannelMessagesConfirm: Bool = false
// @State private var isPresentingDeleteUserMessagesConfirm: Bool = false
// @State private var isPresentingTraceRouteSentAlert = false
//
// var body: some View {
//
// NavigationSplitView {
// let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)
// let dateFormatString = (localeDateFormat ?? "MM/dd/YY")
// List {
// Section(header: Text("channels")) {
// // Display Contacts for the rest of the non admin channels
// if node != nil && node!.myInfo != nil && node!.myInfo!.channels != nil {
// ForEach(node!.myInfo!.channels!.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in
// if channel.name?.lowercased() ?? "" != "admin" && channel.name?.lowercased() ?? "" != "gpio" && channel.name?.lowercased() ?? "" != "serial" {
//
// NavigationLink(destination: ChannelMessageList(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, circleSize: 45, fontSize: 40)
// .brightness(0.2)
//
// VStack(alignment: .leading){
// HStack{
// if channel.name?.isEmpty ?? false {
// if channel.role == 1 {
// Text(String("PrimaryChannel").camelCaseToWords())
// } else {
// Text(String("Channel \(channel.index)").camelCaseToWords())
// }
// } else {
// Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords())
// }
//
// Spacer()
//
// if channel.allPrivateMessages.count > 0 {
//
// if lastMessageDay == currentDay {
// Text(lastMessageTime, style: .time )
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// } else if lastMessageDay == (currentDay - 1) {
// Text("Yesterday")
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
// Text(lastMessageTime.formattedDate(format: dateFormatString))
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// } else if lastMessageDay < (currentDay - 1800) {
// Text(lastMessageTime.formattedDate(format: dateFormatString))
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// }
// }
//
//// Image(systemName: "chevron.forward")
//// .font(.caption)
//// .foregroundColor(.secondary)
// }
//
// if channel.allPrivateMessages.count > 0 {
// HStack(alignment: .top) {
// Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// }
// }
// }
// }
// .frame(height: 62)
// .contextMenu {
// Button {
// channel.mute = !channel.mute
//
// do {
// try context.save()
// // Would rather not do this but the merge changes on
// // A single object is only working on mac GVH
// context.refreshAllObjects()
// // context.refresh(channel, mergeChanges: true)
// } catch {
// context.rollback()
// print("💥 Save Channel Mute Error")
// }
// } label: {
// Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash")
// }
//
// if channel.allPrivateMessages.count > 0 {
// Button(role: .destructive) {
// isPresentingDeleteChannelMessagesConfirm = true
// channelSelection = channel
// } label: {
// Label("Delete Messages", systemImage: "trash")
// }
// }
// }
// .confirmationDialog(
// "This conversation will be deleted.",
// isPresented: $isPresentingDeleteChannelMessagesConfirm,
// titleVisibility: .visible
// ) {
// Button(role: .destructive) {
// deleteChannelMessages(channel: channelSelection!, context: context)
// context.refresh(node!.myInfo!, mergeChanges: true)
// channelSelection = nil
// } label: {
// Text("delete")
// }
// }
// }
// }
// .padding([.top, .bottom])
// }
// }
// Section(header: Text("direct.messages")) {
//
// ForEach(users) { (user: UserEntity) in
//
// let mostRecent = user.messageList.last
// 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
// if user.num != bleManager.connectedPeripheral?.num ?? 0 {
//
// NavigationLink(destination: UserMessageList(user: user)) {
// ZStack {
// Image(systemName: "circle.fill")
// .opacity(user.unreadMessages > 0 ? 1 : 0)
// .font(.system(size: 10))
// .foregroundColor(.accentColor)
// .brightness(0.2)
// }
//
// CircleText(text: user.shortName ?? "???", color: Color(UIColor(hex: UInt32(user.num))), circleSize: 45, fontSize: (user.shortName ?? "???").isEmoji() ? 32 : (user.shortName?.count ?? 0 == 4 ? 14 : (user.shortName?.count ?? 0 == 3 ? 18 : 22)), brightness: 0.0, textColor: UIColor(hex: UInt32(user.num)).isLight() ? .black : .white)
//
// VStack(alignment: .leading){
// HStack{
// Text(user.longName ?? "unknown".localized)
//
// Spacer()
//
// if user.messageList.count > 0 {
// if lastMessageDay == currentDay {
// Text(lastMessageTime, style: .time )
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// } else if lastMessageDay == (currentDay - 1) {
// Text("Yesterday")
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
// Text(lastMessageTime.formattedDate(format: dateFormatString))
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// } else if lastMessageDay < (currentDay - 1800) {
// Text(lastMessageTime.formattedDate(format: dateFormatString))
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// }
// }
//
//// Image(systemName: "chevron.forward")
//// .font(.caption)
//// .foregroundColor(.secondary)
// }
//
// if user.messageList.count > 0 {
// HStack(alignment: .top) {
// Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
// .font(.system(size: 16))
// .foregroundColor(.secondary)
// }
// }
// }
// }
// .frame(height: 62)
// .contextMenu {
// Button {
// user.mute = !user.mute
// do {
// try context.save()
// } catch {
// context.rollback()
// print("💥 Save User Mute Error")
// }
// } label: {
// Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
// }
// Button {
// let success = bleManager.sendTraceRouteRequest(destNum: user.num, wantResponse: true)
// if success {
// isPresentingTraceRouteSentAlert = true
// }
// } label: {
// Label("Trace Route", systemImage: "signpost.right.and.left")
// }
// if user.messageList.count > 0 {
// Button(role: .destructive) {
// isPresentingDeleteUserMessagesConfirm = true
// userSelection = user
// } label: {
// Label("Delete Messages", systemImage: "trash")
// }
// }
// }
// .alert(
// "Trace Route Sent",
// isPresented: $isPresentingTraceRouteSentAlert
// ) {
// Button("OK", role: .cancel) { }
// }
// message: {
// Text("This could take a while, response will appear in the mesh log.")
// }
// .confirmationDialog(
// "This conversation will be deleted.",
// isPresented: $isPresentingDeleteUserMessagesConfirm,
// titleVisibility: .visible
// ) {
// Button(role: .destructive) {
// deleteUserMessages(user: userSelection!, context: context)
// context.refresh(node!.user!, mergeChanges: true)
// } label: {
// Text("delete")
// }
// }
// }
// }
// }
// }
// .listStyle(.grouped)
// .navigationTitle("contacts")
// .navigationBarItems(leading:
// MeshtasticLogo()
// )
// .onAppear {
// self.bleManager.context = context
// if UserDefaults.preferredPeripheralId.count > 0 {
// let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
// fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(bleManager.connectedPeripheral?.num ?? -1))
// do {
// guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
// return
// }
// // Found a node, check it for a region
// if !fetchedNode.isEmpty {
// node = fetchedNode[0]
// }
// } catch {
//
// }
// }
// }
// } detail: {
// if let user = userSelection {
// UserMessageList(user: user)
//
// } else {
// Text("select.contact")
// }
// }
// }
//}

View file

@ -10,6 +10,7 @@ import CoreData
struct Messages: View {
@StateObject var appState = AppState.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@ -38,6 +39,7 @@ struct Messages: View {
.brightness(0.2)
Text("channels")
.font(.title2)
.badge(appState.unreadChannelMessages)
}
NavigationLink {
UserList(node: node)
@ -48,9 +50,11 @@ struct Messages: View {
.brightness(0.2)
Text("direct.messages")
.font(.title2)
.badge(appState.unreadDirectMessages)
}
}
.navigationTitle("messages")
.navigationBarTitleDisplayMode(.large)
.navigationBarItems(leading: MeshtasticLogo())
.onAppear {
self.bleManager.context = context

View file

@ -10,6 +10,9 @@ import CoreData
struct UserList: View {
@StateObject var appState = AppState.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State private var searchText = ""
var usersQuery: Binding<String> {
Binding {
@ -19,12 +22,8 @@ struct UserList: View {
users.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "longName CONTAINS[c] %@ OR shortName CONTAINS[c] %@", newValue, newValue)
}
}
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), NSSortDescriptor(key: "longName", ascending: true)],
sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), NSSortDescriptor(key: "vip", ascending: false), NSSortDescriptor(key: "longName", ascending: true)],
animation: .default)
private var users: FetchedResults<UserEntity>
@ -56,14 +55,17 @@ struct UserList: View {
.brightness(0.2)
}
CircleText(text: user.shortName ?? "???", color: Color(UIColor(hex: UInt32(user.num))), circleSize: 45, fontSize: (user.shortName ?? "???").isEmoji() ? 32 : (user.shortName?.count ?? 0 == 4 ? 14 : (user.shortName?.count ?? 0 == 3 ? 18 : 22)), brightness: 0.0, textColor: UIColor(hex: UInt32(user.num)).isLight() ? .black : .white)
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
VStack(alignment: .leading){
HStack{
Text(user.longName ?? "unknown".localized)
Spacer()
if user.vip {
Image(systemName: "star.fill")
.foregroundColor(.secondary)
}
if user.messageList.count > 0 {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
@ -99,6 +101,17 @@ struct UserList: View {
}
.frame(height: 62)
.contextMenu {
Button {
user.vip = !user.vip
do {
try context.save()
} catch {
context.rollback()
print("💥 Save User VIP Error")
}
} label: {
Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill")
}
Button {
user.mute = !user.mute
do {
@ -144,6 +157,7 @@ struct UserList: View {
Button(role: .destructive) {
deleteUserMessages(user: userSelection!, context: context)
context.refresh(node!.user!, mergeChanges: true)
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
} label: {
Text("delete")
}

View file

@ -10,6 +10,7 @@ import CoreData
struct UserMessageList: View {
@StateObject var appState = AppState.shared
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@ -160,7 +161,7 @@ struct UserMessageList: View {
VStack {
let image = tapback.messagePayload!.image(fontSize: 20)
Image(uiImage: image!).font(.caption)
Text("\(tapback.fromUser?.shortName ?? "????")")
Text("\(tapback.fromUser?.shortName ?? "?")")
.font(.caption2)
.foregroundColor(.gray)
.fixedSize()
@ -219,10 +220,12 @@ struct UserMessageList: View {
.onAppear {
if !message.read {
message.read = true
message.toUser?.objectWillChange.send()
do {
try context.save()
print("Read message \(message.messageId) ")
appState.unreadDirectMessages = user.unreadMessages
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
} catch {
print("Failed to read message \(message.messageId)")
}
@ -364,7 +367,7 @@ struct UserMessageList: View {
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
CircleText(text: user.shortName ?? "???", color: Color(UIColor(hex: UInt32(user.num))), circleSize: 44, fontSize: (user.shortName ?? "???").isEmoji() ? 32 : (user.shortName?.count ?? 0 == 4 ? 14 : (user.shortName?.count ?? 0 == 3 ? 18 : 22)), brightness: 0.0, textColor: UIColor(hex: UInt32(user.num)).isLight() ? .black : .white)
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))), circleSize: 44)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
@ -372,7 +375,7 @@ struct UserMessageList: View {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
}
}
}

View file

@ -121,7 +121,7 @@ struct DetectionSensorLog: View {
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -157,7 +157,7 @@ struct DeviceMetricsLog: View {
.font(.caption)
Text("\(String(format: "%.2f", dm.airUtilTx))%")
.font(.caption)
Text(dm.time?.formattedDate(format: dateFormatString) ?? "Unknown time")
Text(dm.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized)
.font(.caption)
}
}
@ -208,7 +208,7 @@ struct DeviceMetricsLog: View {
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -190,7 +190,7 @@ struct EnvironmentMetricsLog: View {
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -16,9 +16,7 @@ struct NodeDetail: View {
@AppStorage("meshMapType") private var meshMapType = 0
@AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
@AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false
//@State private var mapType: MKMapType = .standard
@State private var selectedMapLayer: MapLayer = .standard
@State var mapRect: MKMapRect = MKMapRect()
@State var waypointCoordinate: WaypointCoordinate?
@State var editingWaypoint: Int = 0
@State private var loadedWeather: Bool = false
@ -26,15 +24,12 @@ struct NodeDetail: View {
@State private var showingForecast = false
@State private var showingShutdownConfirm: Bool = false
@State private var showingRebootConfirm: Bool = false
@State private var showOverlays: Bool = true
@State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay(
mapName: "offlinemap",
tileType: "png",
canReplaceMapContent: true
)
mapName: "offlinemap",
tileType: "png",
canReplaceMapContent: true
)
var node: NodeInfoEntity
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
predicate: NSPredicate(
format: "expire == nil || expire >= %@", Date() as NSDate
@ -49,13 +44,14 @@ struct NodeDetail: View {
@State private var attributionLink: URL?
@State private var attributionLogo: URL?
var body: some View {
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
NavigationStack {
GeometryReader { bounds in
VStack {
if node.positions?.count ?? 0 > 0 {
if node.hasPositions {
ZStack {
let positionArray = node.positions?.array as? [PositionEntity] ?? []
let lastTenThousand = Array(positionArray.prefix(10000))
@ -68,11 +64,9 @@ struct NodeDetail: View {
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
}
},
// visibleMapRect: $mapRect,
selectedMapLayer: selectedMapLayer,
positions: lastTenThousand,
waypoints: Array(waypoints),
// mapViewType: mapType,
userTrackingMode: MKUserTrackingMode.none,
showNodeHistory: meshMapShowNodeHistory,
showRouteLines: meshMapShowRouteLines,
@ -223,16 +217,12 @@ struct NodeDetail: View {
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context
// mapType = .standard// MeshMapTypes(rawValue: meshMapType)?.MKMapTypeValue() ?? .standard
}
.task(id: node.num) {
if !loadedWeather {
do {
if node.positions?.count ?? 0 > 0 {
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

View file

@ -47,7 +47,7 @@ struct NodeList: View {
LazyVStack(alignment: .leading) {
HStack {
VStack(alignment: .leading) {
CircleText(text: node.user?.shortName ?? "???", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65, fontSize: (node.user?.shortName ?? "???").isEmoji() ? 44 : (node.user?.shortName?.count ?? 0 == 4 ? 19 : 26), brightness: 0.0, textColor: UIColor(hex: UInt32(node.num)).isLight() ? .black : .white)
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65)
.padding(.trailing, 5)
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
if deviceMetrics?.count ?? 0 >= 1 {

View file

@ -232,7 +232,7 @@ struct NodeMap: View {
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName :
"????")
"?")
})
.onAppear(perform: {
UIApplication.shared.isIdleTimerDisabled = true

View file

@ -19,7 +19,11 @@ struct PositionLog: View {
@State var exportString = ""
var node: NodeInfoEntity
@State private var isPresentingClearLogConfirm = false
@State private var sortOrder = [KeyPathComparator(\PositionEntity.latitude)]
//@State private var sortOrder = [KeyPathComparator(\PositionEntity.latitude)]
@State var sortOrder: [KeyPathComparator<PositionEntity>] = [
.init(\.latitude, order: SortOrder.forward)
]
var body: some View {
NavigationStack {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
@ -27,7 +31,7 @@ struct PositionLog: View {
if UIDevice.current.userInterfaceIdiom == .pad && !useGrid || UIDevice.current.userInterfaceIdiom == .mac {
// Add a table for mac and ipad
let positions = node.positions?.reversed() as? [PositionEntity] ?? []
Table(positions) {
Table(positions, sortOrder: $sortOrder) {
TableColumn("Latitude") { position in
Text(String(format: "%.5f", position.latitude ?? 0))
}
@ -58,6 +62,7 @@ struct PositionLog: View {
}
.width(min: 180)
}
} else {
ScrollView {
// Use a grid on iOS as a table only shows a single column
@ -97,7 +102,7 @@ struct PositionLog: View {
.font(.caption2)
Text(altitude.formatted())
.font(.caption2)
Text(mappin.time?.formattedDate(format: dateFormatString) ?? "Unknown time")
Text(mappin.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized)
.font(.caption2)
}
}
@ -159,7 +164,7 @@ struct PositionLog: View {
.navigationTitle("Position Log \(node.positions?.count ?? 0) Points")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -67,7 +67,7 @@ struct AdminMessageList: View {
.navigationTitle("admin.log")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -143,7 +143,7 @@ struct AppSettings: View {
.navigationTitle("app.settings")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -64,8 +64,9 @@ struct Channels: View {
}) {
VStack(alignment: .leading) {
HStack {
CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 36, brightness: 0.1)
CircleText(text: String(channel.index), color: .accentColor, circleSize: 45)
.padding(.trailing, 5)
.brightness(0.1)
VStack {
HStack {
if channel.name?.isEmpty ?? false {
@ -281,7 +282,7 @@ struct Channels: View {
.navigationTitle("channels")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
bleManager.context = context

View file

@ -134,7 +134,7 @@ struct BluetoothConfig: View {
.navigationTitle("bluetooth.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -223,7 +223,7 @@ struct DeviceConfig: View {
.navigationTitle("device.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -178,7 +178,7 @@ struct DisplayConfig: View {
.navigationTitle("display.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -220,7 +220,7 @@ struct LoRaConfig: View {
.navigationTitle("lora.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {

View file

@ -262,7 +262,7 @@ struct CannedMessagesConfig: View {
.navigationTitle("canned.messages.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -168,7 +168,7 @@ struct DetectionSensorConfig: View {
.navigationTitle("detection.sensor.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -214,7 +214,7 @@ struct ExternalNotificationConfig: View {
.navigationTitle("external.notification.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -236,7 +236,7 @@ struct MQTTConfig: View {
.navigationTitle("mqtt.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -114,7 +114,7 @@ struct RangeTestConfig: View {
.navigationTitle("range.test.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -112,7 +112,7 @@ struct RtttlConfig: View {
.navigationTitle("ringtone.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -175,7 +175,7 @@ struct SerialConfig: View {
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {

View file

@ -134,7 +134,7 @@ struct StoreForwardConfig: View {
.navigationTitle("storeforward.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -130,7 +130,7 @@ struct TelemetryConfig: View {
.navigationTitle("telemetry.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -163,7 +163,7 @@ struct NetworkConfig: View {
.navigationTitle("network.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context

View file

@ -323,7 +323,7 @@ struct PositionConfig: View {
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {

View file

@ -185,9 +185,9 @@ struct ShareChannels: View {
if node != nil {
ShareLink("Share QR Code & Link",
item: Image(uiImage: qrImage),
subject: Text("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you"),
subject: Text("Meshtastic Node \(node?.user?.shortName ?? "?") has shared channels with you"),
message: Text(channelsUrl),
preview: SharePreview("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you",
preview: SharePreview("Meshtastic Node \(node?.user?.shortName ?? "?") has shared channels with you",
image: Image(uiImage: qrImage))
)
.buttonStyle(.bordered)
@ -257,7 +257,7 @@ struct ShareChannels: View {
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
bleManager.context = context

View file

@ -178,7 +178,7 @@ struct UserConfig: View {
.navigationTitle("User Config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
self.bleManager.context = context