mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #1089 from gh123man/cleanup-nodelist-view
Improve Node List Layout and Styling
This commit is contained in:
commit
8a7d765acb
10 changed files with 502 additions and 164 deletions
|
|
@ -180,7 +180,6 @@
|
|||
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; };
|
||||
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */; };
|
||||
DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; };
|
||||
DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; };
|
||||
DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */; };
|
||||
DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; };
|
||||
DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */; };
|
||||
|
|
@ -305,6 +304,10 @@
|
|||
BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartNodeIntent.swift; sourceTree = "<group>"; };
|
||||
BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsProvider.swift; sourceTree = "<group>"; };
|
||||
BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FactoryResetNodeIntent.swift; sourceTree = "<group>"; };
|
||||
D32BA3912D54617800714840 /* NodeInfoEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NodeInfoEntity+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
D32BA3922D54617800714840 /* NodeInfoEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NodeInfoEntity+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
D32BA3932D54617800714840 /* UserEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserEntity+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
D32BA3942D54617800714840 /* UserEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserEntity+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContextMenuItems.swift; sourceTree = "<group>"; };
|
||||
D93068D42B812B700066FBC8 /* MessageDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDestination.swift; sourceTree = "<group>"; };
|
||||
D93068D62B8146690066FBC8 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -474,7 +477,6 @@
|
|||
DDC2E16526CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = "<group>"; };
|
||||
DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = "<group>"; };
|
||||
DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorConfig.swift; sourceTree = "<group>"; };
|
||||
DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV16.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -664,6 +666,17 @@
|
|||
path = Custom;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D32BA3902D54612800714840 /* CoreData */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D32BA3912D54617800714840 /* NodeInfoEntity+CoreDataClass.swift */,
|
||||
D32BA3922D54617800714840 /* NodeInfoEntity+CoreDataProperties.swift */,
|
||||
D32BA3932D54617800714840 /* UserEntity+CoreDataClass.swift */,
|
||||
D32BA3942D54617800714840 /* UserEntity+CoreDataProperties.swift */,
|
||||
);
|
||||
path = CoreData;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D9C9839E2B79D0C600BDBE6A /* TextMessageField */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -931,6 +944,7 @@
|
|||
DDC2E15626CE248E0042C5E4 /* Meshtastic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D32BA3902D54612800714840 /* CoreData */,
|
||||
BCB6137F2C6728E700485544 /* AppIntents */,
|
||||
DD1BD0EC2C603C5B008C0C70 /* Measurement */,
|
||||
25F5D5BC2C3F6D7B008036E3 /* Router */,
|
||||
|
|
@ -1023,7 +1037,6 @@
|
|||
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */,
|
||||
DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */,
|
||||
DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */,
|
||||
DDC3B273283F411B00AC321C /* LastHeardText.swift */,
|
||||
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */,
|
||||
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */,
|
||||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */,
|
||||
|
|
@ -1488,7 +1501,6 @@
|
|||
231B3F212D087A4C0069A07D /* MetricTableColumn.swift in Sources */,
|
||||
231B3F222D087A4C0069A07D /* MetricsColumnList.swift in Sources */,
|
||||
DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */,
|
||||
DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */,
|
||||
DDDE5A1029AFE69700490C6C /* MeshActivityAttributes.swift in Sources */,
|
||||
DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */,
|
||||
251926852C3BA97800249DF5 /* FavoriteNodeButton.swift in Sources */,
|
||||
|
|
|
|||
15
Meshtastic/CoreData/NodeInfoEntity+CoreDataClass.swift
Normal file
15
Meshtastic/CoreData/NodeInfoEntity+CoreDataClass.swift
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// NodeInfoEntity+CoreDataClass.swift
|
||||
//
|
||||
//
|
||||
// Created by Brian Floersch on 2/5/25.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@objc(NodeInfoEntity)
|
||||
public class NodeInfoEntity: NSManagedObject {
|
||||
|
||||
}
|
||||
200
Meshtastic/CoreData/NodeInfoEntity+CoreDataProperties.swift
Normal file
200
Meshtastic/CoreData/NodeInfoEntity+CoreDataProperties.swift
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
//
|
||||
// NodeInfoEntity+CoreDataProperties.swift
|
||||
//
|
||||
//
|
||||
// Created by Brian Floersch on 2/5/25.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
extension NodeInfoEntity {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<NodeInfoEntity> {
|
||||
return NSFetchRequest<NodeInfoEntity>(entityName: "NodeInfoEntity")
|
||||
}
|
||||
|
||||
@NSManaged public var bleName: String?
|
||||
@NSManaged public var channel: Int32
|
||||
@NSManaged public var favorite: Bool
|
||||
@NSManaged public var firstHeard: Date?
|
||||
@NSManaged public var hopsAway: Int32
|
||||
@NSManaged public var id: Int64
|
||||
@NSManaged public var ignored: Bool
|
||||
@NSManaged public var lastHeard: Date?
|
||||
@NSManaged public var num: Int64
|
||||
@NSManaged public var peripheralId: String?
|
||||
@NSManaged public var rssi: Int32
|
||||
@NSManaged public var sessionExpiration: Date?
|
||||
@NSManaged public var sessionPasskey: Data?
|
||||
@NSManaged public var snr: Float
|
||||
@NSManaged public var viaMqtt: Bool
|
||||
@NSManaged public var ambientLightingConfig: AmbientLightingConfigEntity?
|
||||
@NSManaged public var bluetoothConfig: BluetoothConfigEntity?
|
||||
@NSManaged public var cannedMessageConfig: CannedMessageConfigEntity?
|
||||
@NSManaged public var detectionSensorConfig: DetectionSensorConfigEntity?
|
||||
@NSManaged public var deviceConfig: DeviceConfigEntity?
|
||||
@NSManaged public var displayConfig: DisplayConfigEntity?
|
||||
@NSManaged public var externalNotificationConfig: ExternalNotificationConfigEntity?
|
||||
@NSManaged public var loRaConfig: LoRaConfigEntity?
|
||||
@NSManaged public var metadata: DeviceMetadataEntity?
|
||||
@NSManaged public var mqttConfig: MQTTConfigEntity?
|
||||
@NSManaged public var myInfo: MyInfoEntity?
|
||||
@NSManaged public var networkConfig: NetworkConfigEntity?
|
||||
@NSManaged public var pax: NSOrderedSet?
|
||||
@NSManaged public var paxCounterConfig: PaxCounterConfigEntity?
|
||||
@NSManaged public var positionConfig: PositionConfigEntity?
|
||||
@NSManaged public var positions: NSOrderedSet?
|
||||
@NSManaged public var powerConfig: PowerConfigEntity?
|
||||
@NSManaged public var rangeTestConfig: RangeTestConfigEntity?
|
||||
@NSManaged public var rtttlConfig: RTTTLConfigEntity?
|
||||
@NSManaged public var securityConfig: SecurityConfigEntity?
|
||||
@NSManaged public var serialConfig: SerialConfigEntity?
|
||||
@NSManaged public var storeForwardConfig: StoreForwardConfigEntity?
|
||||
@NSManaged public var telemetries: NSOrderedSet?
|
||||
@NSManaged public var telemetryConfig: TelemetryConfigEntity?
|
||||
@NSManaged public var traceRoutes: NSOrderedSet?
|
||||
@NSManaged public var user: UserEntity?
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for pax
|
||||
extension NodeInfoEntity {
|
||||
|
||||
@objc(insertObject:inPaxAtIndex:)
|
||||
@NSManaged public func insertIntoPax(_ value: PaxCounterEntity, at idx: Int)
|
||||
|
||||
@objc(removeObjectFromPaxAtIndex:)
|
||||
@NSManaged public func removeFromPax(at idx: Int)
|
||||
|
||||
@objc(insertPax:atIndexes:)
|
||||
@NSManaged public func insertIntoPax(_ values: [PaxCounterEntity], at indexes: NSIndexSet)
|
||||
|
||||
@objc(removePaxAtIndexes:)
|
||||
@NSManaged public func removeFromPax(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectInPaxAtIndex:withObject:)
|
||||
@NSManaged public func replacePax(at idx: Int, with value: PaxCounterEntity)
|
||||
|
||||
@objc(replacePaxAtIndexes:withPax:)
|
||||
@NSManaged public func replacePax(at indexes: NSIndexSet, with values: [PaxCounterEntity])
|
||||
|
||||
@objc(addPaxObject:)
|
||||
@NSManaged public func addToPax(_ value: PaxCounterEntity)
|
||||
|
||||
@objc(removePaxObject:)
|
||||
@NSManaged public func removeFromPax(_ value: PaxCounterEntity)
|
||||
|
||||
@objc(addPax:)
|
||||
@NSManaged public func addToPax(_ values: NSOrderedSet)
|
||||
|
||||
@objc(removePax:)
|
||||
@NSManaged public func removeFromPax(_ values: NSOrderedSet)
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for positions
|
||||
extension NodeInfoEntity {
|
||||
|
||||
@objc(insertObject:inPositionsAtIndex:)
|
||||
@NSManaged public func insertIntoPositions(_ value: PositionEntity, at idx: Int)
|
||||
|
||||
@objc(removeObjectFromPositionsAtIndex:)
|
||||
@NSManaged public func removeFromPositions(at idx: Int)
|
||||
|
||||
@objc(insertPositions:atIndexes:)
|
||||
@NSManaged public func insertIntoPositions(_ values: [PositionEntity], at indexes: NSIndexSet)
|
||||
|
||||
@objc(removePositionsAtIndexes:)
|
||||
@NSManaged public func removeFromPositions(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectInPositionsAtIndex:withObject:)
|
||||
@NSManaged public func replacePositions(at idx: Int, with value: PositionEntity)
|
||||
|
||||
@objc(replacePositionsAtIndexes:withPositions:)
|
||||
@NSManaged public func replacePositions(at indexes: NSIndexSet, with values: [PositionEntity])
|
||||
|
||||
@objc(addPositionsObject:)
|
||||
@NSManaged public func addToPositions(_ value: PositionEntity)
|
||||
|
||||
@objc(removePositionsObject:)
|
||||
@NSManaged public func removeFromPositions(_ value: PositionEntity)
|
||||
|
||||
@objc(addPositions:)
|
||||
@NSManaged public func addToPositions(_ values: NSOrderedSet)
|
||||
|
||||
@objc(removePositions:)
|
||||
@NSManaged public func removeFromPositions(_ values: NSOrderedSet)
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for telemetries
|
||||
extension NodeInfoEntity {
|
||||
|
||||
@objc(insertObject:inTelemetriesAtIndex:)
|
||||
@NSManaged public func insertIntoTelemetries(_ value: TelemetryEntity, at idx: Int)
|
||||
|
||||
@objc(removeObjectFromTelemetriesAtIndex:)
|
||||
@NSManaged public func removeFromTelemetries(at idx: Int)
|
||||
|
||||
@objc(insertTelemetries:atIndexes:)
|
||||
@NSManaged public func insertIntoTelemetries(_ values: [TelemetryEntity], at indexes: NSIndexSet)
|
||||
|
||||
@objc(removeTelemetriesAtIndexes:)
|
||||
@NSManaged public func removeFromTelemetries(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectInTelemetriesAtIndex:withObject:)
|
||||
@NSManaged public func replaceTelemetries(at idx: Int, with value: TelemetryEntity)
|
||||
|
||||
@objc(replaceTelemetriesAtIndexes:withTelemetries:)
|
||||
@NSManaged public func replaceTelemetries(at indexes: NSIndexSet, with values: [TelemetryEntity])
|
||||
|
||||
@objc(addTelemetriesObject:)
|
||||
@NSManaged public func addToTelemetries(_ value: TelemetryEntity)
|
||||
|
||||
@objc(removeTelemetriesObject:)
|
||||
@NSManaged public func removeFromTelemetries(_ value: TelemetryEntity)
|
||||
|
||||
@objc(addTelemetries:)
|
||||
@NSManaged public func addToTelemetries(_ values: NSOrderedSet)
|
||||
|
||||
@objc(removeTelemetries:)
|
||||
@NSManaged public func removeFromTelemetries(_ values: NSOrderedSet)
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for traceRoutes
|
||||
extension NodeInfoEntity {
|
||||
|
||||
@objc(insertObject:inTraceRoutesAtIndex:)
|
||||
@NSManaged public func insertIntoTraceRoutes(_ value: TraceRouteEntity, at idx: Int)
|
||||
|
||||
@objc(removeObjectFromTraceRoutesAtIndex:)
|
||||
@NSManaged public func removeFromTraceRoutes(at idx: Int)
|
||||
|
||||
@objc(insertTraceRoutes:atIndexes:)
|
||||
@NSManaged public func insertIntoTraceRoutes(_ values: [TraceRouteEntity], at indexes: NSIndexSet)
|
||||
|
||||
@objc(removeTraceRoutesAtIndexes:)
|
||||
@NSManaged public func removeFromTraceRoutes(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectInTraceRoutesAtIndex:withObject:)
|
||||
@NSManaged public func replaceTraceRoutes(at idx: Int, with value: TraceRouteEntity)
|
||||
|
||||
@objc(replaceTraceRoutesAtIndexes:withTraceRoutes:)
|
||||
@NSManaged public func replaceTraceRoutes(at indexes: NSIndexSet, with values: [TraceRouteEntity])
|
||||
|
||||
@objc(addTraceRoutesObject:)
|
||||
@NSManaged public func addToTraceRoutes(_ value: TraceRouteEntity)
|
||||
|
||||
@objc(removeTraceRoutesObject:)
|
||||
@NSManaged public func removeFromTraceRoutes(_ value: TraceRouteEntity)
|
||||
|
||||
@objc(addTraceRoutes:)
|
||||
@NSManaged public func addToTraceRoutes(_ values: NSOrderedSet)
|
||||
|
||||
@objc(removeTraceRoutes:)
|
||||
@NSManaged public func removeFromTraceRoutes(_ values: NSOrderedSet)
|
||||
|
||||
}
|
||||
15
Meshtastic/CoreData/UserEntity+CoreDataClass.swift
Normal file
15
Meshtastic/CoreData/UserEntity+CoreDataClass.swift
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// UserEntity+CoreDataClass.swift
|
||||
//
|
||||
//
|
||||
// Created by Brian Floersch on 2/5/25.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@objc(UserEntity)
|
||||
public class UserEntity: NSManagedObject {
|
||||
|
||||
}
|
||||
108
Meshtastic/CoreData/UserEntity+CoreDataProperties.swift
Normal file
108
Meshtastic/CoreData/UserEntity+CoreDataProperties.swift
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
//
|
||||
// UserEntity+CoreDataProperties.swift
|
||||
//
|
||||
//
|
||||
// Created by Brian Floersch on 2/5/25.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
extension UserEntity {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<UserEntity> {
|
||||
return NSFetchRequest<UserEntity>(entityName: "UserEntity")
|
||||
}
|
||||
|
||||
@NSManaged public var hwDisplayName: String?
|
||||
@NSManaged public var hwModel: String?
|
||||
@NSManaged public var hwModelId: Int32
|
||||
@NSManaged public var isLicensed: Bool
|
||||
@NSManaged public var keyMatch: Bool
|
||||
@NSManaged public var lastMessage: Date?
|
||||
@NSManaged public var longName: String?
|
||||
@NSManaged public var mute: Bool
|
||||
@NSManaged public var newPublicKey: Data?
|
||||
@NSManaged public var num: Int64
|
||||
@NSManaged public var numString: String?
|
||||
@NSManaged public var pkiEncrypted: Bool
|
||||
@NSManaged public var publicKey: Data?
|
||||
@NSManaged public var role: Int32
|
||||
@NSManaged public var shortName: String?
|
||||
@NSManaged public var userId: String?
|
||||
@NSManaged public var receivedMessages: NSOrderedSet?
|
||||
@NSManaged public var sentMessages: NSOrderedSet?
|
||||
@NSManaged public var userNode: NodeInfoEntity?
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for receivedMessages
|
||||
extension UserEntity {
|
||||
|
||||
@objc(insertObject:inReceivedMessagesAtIndex:)
|
||||
@NSManaged public func insertIntoReceivedMessages(_ value: MessageEntity, at idx: Int)
|
||||
|
||||
@objc(removeObjectFromReceivedMessagesAtIndex:)
|
||||
@NSManaged public func removeFromReceivedMessages(at idx: Int)
|
||||
|
||||
@objc(insertReceivedMessages:atIndexes:)
|
||||
@NSManaged public func insertIntoReceivedMessages(_ values: [MessageEntity], at indexes: NSIndexSet)
|
||||
|
||||
@objc(removeReceivedMessagesAtIndexes:)
|
||||
@NSManaged public func removeFromReceivedMessages(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectInReceivedMessagesAtIndex:withObject:)
|
||||
@NSManaged public func replaceReceivedMessages(at idx: Int, with value: MessageEntity)
|
||||
|
||||
@objc(replaceReceivedMessagesAtIndexes:withReceivedMessages:)
|
||||
@NSManaged public func replaceReceivedMessages(at indexes: NSIndexSet, with values: [MessageEntity])
|
||||
|
||||
@objc(addReceivedMessagesObject:)
|
||||
@NSManaged public func addToReceivedMessages(_ value: MessageEntity)
|
||||
|
||||
@objc(removeReceivedMessagesObject:)
|
||||
@NSManaged public func removeFromReceivedMessages(_ value: MessageEntity)
|
||||
|
||||
@objc(addReceivedMessages:)
|
||||
@NSManaged public func addToReceivedMessages(_ values: NSOrderedSet)
|
||||
|
||||
@objc(removeReceivedMessages:)
|
||||
@NSManaged public func removeFromReceivedMessages(_ values: NSOrderedSet)
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for sentMessages
|
||||
extension UserEntity {
|
||||
|
||||
@objc(insertObject:inSentMessagesAtIndex:)
|
||||
@NSManaged public func insertIntoSentMessages(_ value: MessageEntity, at idx: Int)
|
||||
|
||||
@objc(removeObjectFromSentMessagesAtIndex:)
|
||||
@NSManaged public func removeFromSentMessages(at idx: Int)
|
||||
|
||||
@objc(insertSentMessages:atIndexes:)
|
||||
@NSManaged public func insertIntoSentMessages(_ values: [MessageEntity], at indexes: NSIndexSet)
|
||||
|
||||
@objc(removeSentMessagesAtIndexes:)
|
||||
@NSManaged public func removeFromSentMessages(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectInSentMessagesAtIndex:withObject:)
|
||||
@NSManaged public func replaceSentMessages(at idx: Int, with value: MessageEntity)
|
||||
|
||||
@objc(replaceSentMessagesAtIndexes:withSentMessages:)
|
||||
@NSManaged public func replaceSentMessages(at indexes: NSIndexSet, with values: [MessageEntity])
|
||||
|
||||
@objc(addSentMessagesObject:)
|
||||
@NSManaged public func addToSentMessages(_ value: MessageEntity)
|
||||
|
||||
@objc(removeSentMessagesObject:)
|
||||
@NSManaged public func removeFromSentMessages(_ value: MessageEntity)
|
||||
|
||||
@objc(addSentMessages:)
|
||||
@NSManaged public func addToSentMessages(_ values: NSOrderedSet)
|
||||
|
||||
@objc(removeSentMessages:)
|
||||
@NSManaged public func removeFromSentMessages(_ values: NSOrderedSet)
|
||||
|
||||
}
|
||||
|
|
@ -9,6 +9,14 @@ import Foundation
|
|||
|
||||
extension Date {
|
||||
|
||||
var lastHeard: String {
|
||||
if timeIntervalSince1970 > 0 {
|
||||
formatted()
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func formattedDate(format: String) -> String {
|
||||
let dateformat = DateFormatter()
|
||||
dateformat.dateFormat = format
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import SwiftUI
|
||||
//
|
||||
// LastHeardText.swift
|
||||
// Meshtastic Apple
|
||||
//
|
||||
// Created by Garth Vander Houwen on 5/25/22.
|
||||
//
|
||||
struct LastHeardText: View {
|
||||
var lastHeard: Date?
|
||||
|
||||
var body: some View {
|
||||
if let lastHeard, lastHeard.timeIntervalSince1970 > 0 {
|
||||
Text(lastHeard.formatted())
|
||||
} else {
|
||||
Text("unknown")
|
||||
}
|
||||
}
|
||||
}
|
||||
struct LastHeardText_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LastHeardText(lastHeard: Date())
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
.environment(\.locale, .init(identifier: "en"))
|
||||
LastHeardText(lastHeard: Date())
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
.environment(\.locale, .init(identifier: "de"))
|
||||
}
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ struct PositionPopover: View {
|
|||
if idiom != .phone {
|
||||
Text("heard".localized + ":")
|
||||
}
|
||||
LastHeardText(lastHeard: position.time)
|
||||
Text(position.time?.lastHeard ?? "unknown")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
|
|
|
|||
|
|
@ -15,12 +15,43 @@ struct NodeListItem: View {
|
|||
var connectedNode: Int64
|
||||
var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast
|
||||
|
||||
var body: some View {
|
||||
var userKeyStatus: (String, Color) {
|
||||
var image = "lock.open.fill"
|
||||
var color = Color.yellow
|
||||
if node.user?.pkiEncrypted ?? false {
|
||||
if !(node.user?.keyMatch ?? false) {
|
||||
/// Public Key on the User and the Public Key on the Last Message don't match
|
||||
image = "key.slash"
|
||||
color = .red
|
||||
} else {
|
||||
image = "lock.fill"
|
||||
color = .green
|
||||
}
|
||||
}
|
||||
return (image, color)
|
||||
}
|
||||
|
||||
var locationData: (PositionEntity, CLLocation)? {
|
||||
guard let lastPostion = node.positions?.lastObject as? PositionEntity else {
|
||||
return nil
|
||||
}
|
||||
guard let currentLocation = LocationsHandler.shared.locationsArray.last else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
|
||||
|
||||
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude {
|
||||
return (lastPostion, myCoord)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(value: node) {
|
||||
LazyVStack(alignment: .leading) {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .center) {
|
||||
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70)
|
||||
.padding(.trailing, 5)
|
||||
if node.latestDeviceMetrics != nil {
|
||||
|
|
@ -30,23 +61,11 @@ struct NodeListItem: View {
|
|||
}
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
if node.user?.pkiEncrypted ?? false {
|
||||
if !(node.user?.keyMatch ?? false) {
|
||||
/// Public Key on the User and the Public Key on the Last Message don't match
|
||||
Image(systemName: "key.slash")
|
||||
.foregroundColor(.red)
|
||||
} else {
|
||||
Image(systemName: "lock.fill")
|
||||
.foregroundColor(.green)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "lock.open.fill")
|
||||
.foregroundColor(.yellow)
|
||||
}
|
||||
Text(node.user?.longName ?? "unknown".localized)
|
||||
.font(.headline)
|
||||
.fontWeight(.regular)
|
||||
.allowsTightening(true)
|
||||
let (image, color) = userKeyStatus
|
||||
IconAndText(systemName: image,
|
||||
imageColor: color,
|
||||
text: node.user?.longName ?? "unknown".localized,
|
||||
textColor: .primary)
|
||||
if node.favorite {
|
||||
Spacer()
|
||||
Image(systemName: "star.fill")
|
||||
|
|
@ -54,149 +73,82 @@ struct NodeListItem: View {
|
|||
}
|
||||
}
|
||||
if connected {
|
||||
HStack {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right.circle.fill")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.green)
|
||||
.frame(width: 30)
|
||||
Text("connected")
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Image(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(node.isOnline ? .green : .orange)
|
||||
.frame(width: 30)
|
||||
LastHeardText(lastHeard: node.lastHeard)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
HStack {
|
||||
let role = DeviceRoles(rawValue: Int(node.user?.role ?? 0))
|
||||
Image(systemName: role?.systemName ?? "figure")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 30)
|
||||
Text("Role: \(role?.name ?? "unknown".localized)")
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
IconAndText(systemName: "antenna.radiowaves.left.and.right.circle.fill",
|
||||
imageColor: .green,
|
||||
text: "connected".localized)
|
||||
}
|
||||
IconAndText(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill",
|
||||
imageColor: node.isOnline ? .green : .orange,
|
||||
text: node.lastHeard?.lastHeard ?? "unknown")
|
||||
let role = DeviceRoles(rawValue: Int(node.user?.role ?? 0))
|
||||
IconAndText(systemName: role?.systemName ?? "figure",
|
||||
text: "Role: \(role?.name ?? "unknown".localized)")
|
||||
if node.isStoreForwardRouter {
|
||||
HStack {
|
||||
Image(systemName: "envelope.arrow.triangle.branch")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.frame(width: 30)
|
||||
Text("storeforward".localized)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
IconAndText(systemName: "envelope.arrow.triangle.branch",
|
||||
renderingMode: .multicolor,
|
||||
text: "storeforward".localized)
|
||||
}
|
||||
|
||||
if node.positions?.count ?? 0 > 0 && connectedNode != node.num {
|
||||
HStack {
|
||||
if let lastPostion = node.positions?.lastObject as? PositionEntity {
|
||||
if let currentLocation = LocationsHandler.shared.locationsArray.last {
|
||||
let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
|
||||
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude {
|
||||
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
|
||||
let metersAway = nodeCoord.distance(from: myCoord)
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.frame(width: 30)
|
||||
DistanceText(meters: metersAway)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord)
|
||||
let headingDegrees = Measurement(value: trueBearing, unit: UnitAngle.degrees)
|
||||
Image(systemName: "location.north")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(Angle(degrees: headingDegrees.value))
|
||||
let heading = Measurement(value: trueBearing, unit: UnitAngle.degrees)
|
||||
Text("\(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
if let (lastPostion, myCoord) = locationData {
|
||||
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
|
||||
let metersAway = nodeCoord.distance(from: myCoord)
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.frame(width: 30)
|
||||
DistanceText(meters: metersAway)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord)
|
||||
let headingDegrees = Measurement(value: trueBearing, unit: UnitAngle.degrees).reciprocal()
|
||||
Image(systemName: "location.north")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(Angle(degrees: headingDegrees.value))
|
||||
let heading = Measurement(value: trueBearing, unit: UnitAngle.degrees).reciprocal()
|
||||
Text("\(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
if node.channel > 0 {
|
||||
HStack {
|
||||
Image(systemName: "\(node.channel).circle.fill")
|
||||
.font(.title2)
|
||||
.frame(width: 30)
|
||||
Text("Channel")
|
||||
.foregroundColor(.secondary)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
}
|
||||
IconAndText(systemName: "\(node.channel).circle.fill", text: "Channel")
|
||||
}
|
||||
|
||||
if node.viaMqtt && connectedNode != node.num {
|
||||
Image(systemName: "dot.radiowaves.up.forward")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
Text("MQTT")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
IconAndText(systemName: "dot.radiowaves.up.forward",
|
||||
renderingMode: .multicolor,
|
||||
text: "MQTT")
|
||||
}
|
||||
}
|
||||
if node.hasPositions || node.hasEnvironmentMetrics || node.hasDetectionSensorMetrics || node.hasTraceRoutes {
|
||||
HStack {
|
||||
Image(systemName: "scroll")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
Text("Logs:")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption2)
|
||||
.allowsTightening(true)
|
||||
IconAndText(systemName: "scroll", text: "Logs:")
|
||||
if node.hasDeviceMetrics {
|
||||
Image(systemName: "flipphone")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
DefaultIcon(systemName: "flipphone")
|
||||
}
|
||||
if node.hasPositions {
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
|
||||
DefaultIcon(systemName: "mappin.and.ellipse")
|
||||
}
|
||||
if node.hasEnvironmentMetrics {
|
||||
Image(systemName: "cloud.sun.rain")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
|
||||
DefaultIcon(systemName: "cloud.sun.rain")
|
||||
}
|
||||
if node.hasDetectionSensorMetrics {
|
||||
Image(systemName: "sensor")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
DefaultIcon(systemName: "sensor")
|
||||
}
|
||||
if node.hasTraceRoutes {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
DefaultIcon(systemName: "signpost.right.and.left")
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.hopsAway > 0 {
|
||||
HStack {
|
||||
Image(systemName: "hare")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
Text("Hops Away:")
|
||||
.foregroundColor(.secondary)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
IconAndText(systemName: "hare", text: "Hops Away:")
|
||||
Image(systemName: "\(node.hopsAway).square")
|
||||
.font(.title2)
|
||||
}
|
||||
|
|
@ -215,3 +167,60 @@ struct NodeListItem: View {
|
|||
.padding(.bottom, 4)
|
||||
}
|
||||
}
|
||||
|
||||
struct DefaultIcon: View {
|
||||
let systemName: String
|
||||
|
||||
var body: some View {
|
||||
Image(systemName: systemName)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
|
||||
struct IconAndText: View {
|
||||
let systemName: String
|
||||
var imageColor: Color?
|
||||
var renderingMode: SymbolRenderingMode = .hierarchical
|
||||
let text: String
|
||||
var textColor: Color = .gray
|
||||
|
||||
@ViewBuilder
|
||||
var image: some View {
|
||||
if let color = imageColor {
|
||||
Image(systemName: systemName)
|
||||
.foregroundColor(color)
|
||||
} else {
|
||||
Image(systemName: systemName)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
image
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(renderingMode)
|
||||
.frame(width: 30)
|
||||
Text(text)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(textColor)
|
||||
.allowsTightening(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VStack(alignment: .leading) {
|
||||
IconAndText(systemName: "antenna.radiowaves.left.and.right.circle.fill", text: "foo")
|
||||
IconAndText(systemName: "antenna.radiowaves.left.and.right.circle", text: "bar")
|
||||
NodeListItem(node: {
|
||||
let context = PersistenceController.preview.container.viewContext
|
||||
let nodeInfo = NodeInfoEntity(context: context)
|
||||
let user = UserEntity(context: context)
|
||||
user.longName = "Test User"
|
||||
user.shortName = "TU"
|
||||
nodeInfo.user = user
|
||||
return nodeInfo
|
||||
}(), connected: true, connectedNode: 0, modemPreset: .longFast)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,7 +200,6 @@ struct NodeList: View {
|
|||
.controlSize(.regular)
|
||||
.padding(5)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.searchable(text: $searchText, placement: .automatic, prompt: "Find a node")
|
||||
.disableAutocorrection(true)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue