mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Cleanup node list
Added preview for NodeListItem, Added CoreData bindings, Align all icons, Deduplicate code for list items, Fix list view padding for tab bar transparency
This commit is contained in:
parent
41a252649a
commit
4d0312623d
10 changed files with 503 additions and 165 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>"; };
|
||||
|
|
@ -663,6 +665,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 = (
|
||||
|
|
@ -929,6 +942,7 @@
|
|||
DDC2E15626CE248E0042C5E4 /* Meshtastic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D32BA3902D54612800714840 /* CoreData */,
|
||||
BCB6137F2C6728E700485544 /* AppIntents */,
|
||||
DD1BD0EC2C603C5B008C0C70 /* Measurement */,
|
||||
25F5D5BC2C3F6D7B008036E3 /* Router */,
|
||||
|
|
@ -1021,7 +1035,6 @@
|
|||
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */,
|
||||
DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */,
|
||||
DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */,
|
||||
DDC3B273283F411B00AC321C /* LastHeardText.swift */,
|
||||
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */,
|
||||
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */,
|
||||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */,
|
||||
|
|
@ -1478,7 +1491,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).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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ struct NodeList: View {
|
|||
)
|
||||
/// Don't show message, trace route, position exchange or delete context menu items for the connected node
|
||||
if connectedNode.num != node.num {
|
||||
if (!node.viaMqtt || node.viaMqtt && node.hopsAway == 0) {
|
||||
if !node.viaMqtt || node.viaMqtt && node.hopsAway == 0 {
|
||||
Button(action: {
|
||||
if let url = URL(string: "meshtastic:///messages?userNum=\(node.num)") {
|
||||
UIApplication.shared.open(url)
|
||||
|
|
@ -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