mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
initial swift data conversion
This commit is contained in:
parent
183924d4dc
commit
b2c72ae166
130 changed files with 2939 additions and 2269 deletions
|
|
@ -5,48 +5,50 @@
|
|||
// Copyright(c) Garth Vander Houwen 11/7/22.
|
||||
//
|
||||
import Foundation
|
||||
import CoreData
|
||||
import SwiftData
|
||||
import MeshtasticProtobufs
|
||||
|
||||
extension ChannelEntity {
|
||||
var messagePredicate: NSPredicate {
|
||||
return NSPredicate(format: "channel == %ld AND toUser == nil AND isEmoji == false", self.index)
|
||||
}
|
||||
|
||||
var messageFetchRequest: NSFetchRequest<MessageEntity> {
|
||||
let fetchRequest = MessageEntity.fetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: true)]
|
||||
fetchRequest.predicate = messagePredicate
|
||||
return fetchRequest
|
||||
}
|
||||
|
||||
@MainActor
|
||||
var allPrivateMessages: [MessageEntity] {
|
||||
let context = PersistenceController.shared.container.viewContext
|
||||
let fetchRequest = messageFetchRequest
|
||||
|
||||
return (try? context.fetch(fetchRequest)) ?? [MessageEntity]()
|
||||
let context = PersistenceController.shared.context
|
||||
let channelIndex = self.index
|
||||
var descriptor = FetchDescriptor<MessageEntity>(
|
||||
predicate: #Predicate<MessageEntity> { msg in
|
||||
msg.channel == channelIndex && msg.toUser == nil && msg.isEmoji == false
|
||||
},
|
||||
sortBy: [SortDescriptor(\.messageTimestamp, order: .forward)]
|
||||
)
|
||||
return (try? context.fetch(descriptor)) ?? []
|
||||
}
|
||||
|
||||
@MainActor
|
||||
var mostRecentPrivateMessage: MessageEntity? {
|
||||
// Most recent channel message (descending, limit 1)
|
||||
let context = PersistenceController.shared.container.viewContext
|
||||
let fetchRequest = messageFetchRequest
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: false)]
|
||||
fetchRequest.fetchLimit = 1
|
||||
|
||||
return (try? context.fetch(fetchRequest))?.first
|
||||
let context = PersistenceController.shared.context
|
||||
let channelIndex = self.index
|
||||
var descriptor = FetchDescriptor<MessageEntity>(
|
||||
predicate: #Predicate<MessageEntity> { msg in
|
||||
msg.channel == channelIndex && msg.toUser == nil && msg.isEmoji == false
|
||||
},
|
||||
sortBy: [SortDescriptor(\.messageTimestamp, order: .reverse)]
|
||||
)
|
||||
descriptor.fetchLimit = 1
|
||||
return try? context.fetch(descriptor).first
|
||||
}
|
||||
|
||||
func unreadMessages(context: NSManagedObjectContext) -> Int {
|
||||
let fetchRequest = messageFetchRequest
|
||||
fetchRequest.sortDescriptors = [] // sort is irrelevant.
|
||||
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fetchRequest.predicate!, NSPredicate(format: "read == false")])
|
||||
|
||||
return (try? context.count(for: fetchRequest)) ?? 0
|
||||
@MainActor
|
||||
func unreadMessages(context: ModelContext) -> Int {
|
||||
let channelIndex = self.index
|
||||
let descriptor = FetchDescriptor<MessageEntity>(
|
||||
predicate: #Predicate<MessageEntity> { msg in
|
||||
msg.channel == channelIndex && msg.toUser == nil && msg.isEmoji == false && msg.read == false
|
||||
}
|
||||
)
|
||||
return (try? context.fetchCount(descriptor)) ?? 0
|
||||
}
|
||||
|
||||
// Backwards-compatible property (uses viewContext)
|
||||
var unreadMessages: Int { unreadMessages(context: PersistenceController.shared.container.viewContext) }
|
||||
@MainActor
|
||||
var unreadMessages: Int { unreadMessages(context: PersistenceController.shared.context) }
|
||||
|
||||
var protoBuf: Channel {
|
||||
var channel = Channel()
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import SwiftData
|
||||
import MeshtasticProtobufs
|
||||
|
||||
extension DeviceMetadataEntity {
|
||||
convenience init(
|
||||
context: NSManagedObjectContext,
|
||||
metadata: DeviceMetadata
|
||||
) {
|
||||
self.init(context: context)
|
||||
convenience init(metadata: DeviceMetadata) {
|
||||
self.init()
|
||||
self.time = Date()
|
||||
self.deviceStateVersion = Int32(metadata.deviceStateVersion)
|
||||
self.canShutdown = metadata.canShutdown
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import CoreData
|
||||
import SwiftData
|
||||
import MeshtasticProtobufs
|
||||
|
||||
extension ExternalNotificationConfigEntity {
|
||||
convenience init(
|
||||
context: NSManagedObjectContext,
|
||||
config: ModuleConfig.ExternalNotificationConfig
|
||||
) {
|
||||
self.init(context: context)
|
||||
convenience init(config: ModuleConfig.ExternalNotificationConfig) {
|
||||
self.init()
|
||||
self.enabled = config.enabled
|
||||
self.usePWM = config.usePwm
|
||||
self.alertBell = config.alertBell
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
// Copyright (c) Garth Vander Houwen 11/21/23.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import SwiftData
|
||||
import CoreLocation
|
||||
import MapKit
|
||||
import SwiftUI
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import CoreData
|
||||
import SwiftData
|
||||
import MeshtasticProtobufs
|
||||
|
||||
extension MQTTConfigEntity {
|
||||
convenience init(
|
||||
context: NSManagedObjectContext,
|
||||
config: ModuleConfig.MQTTConfig
|
||||
) {
|
||||
self.init(context: context)
|
||||
convenience init(config: ModuleConfig.MQTTConfig) {
|
||||
self.init()
|
||||
self.enabled = config.enabled
|
||||
self.proxyToClientEnabled = config.proxyToClientEnabled
|
||||
self.address = config.address
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
// Created by Ben on 8/22/23.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import SwiftData
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
import MapKit
|
||||
|
|
@ -40,18 +40,17 @@ extension MessageEntity {
|
|||
return re?.canRetry ?? false
|
||||
}
|
||||
|
||||
@MainActor
|
||||
var tapbacks: [MessageEntity] {
|
||||
let context = PersistenceController.shared.container.viewContext
|
||||
let fetchRequest = MessageEntity.fetchRequest()
|
||||
fetchRequest.sortDescriptors = [
|
||||
NSSortDescriptor(key: "messageTimestamp", ascending: true)
|
||||
]
|
||||
fetchRequest.predicate = NSPredicate(
|
||||
format: "replyID == %lld AND isEmoji == true",
|
||||
self.messageId
|
||||
let context = PersistenceController.shared.context
|
||||
let msgId = self.messageId
|
||||
let descriptor = FetchDescriptor<MessageEntity>(
|
||||
predicate: #Predicate<MessageEntity> { msg in
|
||||
msg.replyID == msgId && msg.isEmoji == true
|
||||
},
|
||||
sortBy: [SortDescriptor(\MessageEntity.messageTimestamp, order: .forward)]
|
||||
)
|
||||
|
||||
return (try? context.fetch(fetchRequest)) ?? [MessageEntity]()
|
||||
return (try? context.fetch(descriptor)) ?? []
|
||||
}
|
||||
|
||||
func displayTimestamp(aboveMessage: MessageEntity?) -> Bool {
|
||||
|
|
@ -61,43 +60,38 @@ extension MessageEntity {
|
|||
return false // First message will have no timestamp
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func relayDisplay() -> String? {
|
||||
|
||||
guard self.relayNode != 0 else { return nil }
|
||||
let context = PersistenceController.shared.container.viewContext
|
||||
let context = PersistenceController.shared.context
|
||||
|
||||
let relaySuffix = Int64(self.relayNode & 0xFF)
|
||||
let request: NSFetchRequest<UserEntity> = UserEntity.fetchRequest()
|
||||
request.predicate = NSPredicate(
|
||||
format: "(num & 0xFF) == %lld",
|
||||
relaySuffix
|
||||
)
|
||||
let descriptor = FetchDescriptor<UserEntity>()
|
||||
|
||||
do {
|
||||
let users = try context.fetch(request)
|
||||
|
||||
// If exactly one match is found, return its name
|
||||
if users.count == 1, let name = users.first?.longName, !name.isEmpty {
|
||||
return "\(name)"
|
||||
}
|
||||
|
||||
// If no exact match, find the node with the smallest hopsAway
|
||||
if let closestNode = users.min(by: { lhs, rhs in
|
||||
guard let lhsHops = lhs.userNode?.hopsAway,
|
||||
let rhsHops = rhs.userNode?.hopsAway
|
||||
else {
|
||||
return false
|
||||
}
|
||||
return lhsHops < rhsHops
|
||||
}), let name = closestNode.longName, !name.isEmpty {
|
||||
return "\(name)"
|
||||
}
|
||||
|
||||
// Fallback to hex node number if no matches
|
||||
return String(format: "Node 0x%02X", UInt32(self.relayNode & 0xFF))
|
||||
|
||||
} catch {
|
||||
guard let users = try? context.fetch(descriptor) else {
|
||||
return String(format: "Node 0x%02X", UInt32(self.relayNode & 0xFF))
|
||||
}
|
||||
let matchingUsers = users.filter { ($0.num & 0xFF) == relaySuffix }
|
||||
|
||||
// If exactly one match is found, return its name
|
||||
if matchingUsers.count == 1, let name = matchingUsers.first?.longName, !name.isEmpty {
|
||||
return "\(name)"
|
||||
}
|
||||
|
||||
// If no exact match, find the node with the smallest hopsAway
|
||||
if let closestNode = matchingUsers.min(by: { lhs, rhs in
|
||||
guard let lhsHops = lhs.userNode?.hopsAway,
|
||||
let rhsHops = rhs.userNode?.hopsAway
|
||||
else {
|
||||
return false
|
||||
}
|
||||
return lhsHops < rhsHops
|
||||
}), let name = closestNode.longName, !name.isEmpty {
|
||||
return "\(name)"
|
||||
}
|
||||
|
||||
// Fallback to hex node number if no matches
|
||||
return String(format: "Node 0x%02X", UInt32(self.relayNode & 0xFF))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,41 +6,36 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import SwiftData
|
||||
|
||||
extension MyInfoEntity {
|
||||
var messagePredicate: NSPredicate {
|
||||
return NSPredicate(format: "toUser == nil AND isEmoji == false")
|
||||
}
|
||||
|
||||
var messageFetchRequest: NSFetchRequest<MessageEntity> {
|
||||
let fetchRequest = MessageEntity.fetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: true)]
|
||||
fetchRequest.predicate = messagePredicate
|
||||
return fetchRequest
|
||||
}
|
||||
|
||||
@MainActor
|
||||
var messageList: [MessageEntity] {
|
||||
let context = PersistenceController.shared.container.viewContext
|
||||
let fetchRequest = messageFetchRequest
|
||||
|
||||
return (try? context.fetch(messageFetchRequest)) ?? [MessageEntity]()
|
||||
let context = PersistenceController.shared.context
|
||||
let descriptor = FetchDescriptor<MessageEntity>(
|
||||
predicate: #Predicate<MessageEntity> { msg in
|
||||
msg.toUser == nil && msg.isEmoji == false
|
||||
},
|
||||
sortBy: [SortDescriptor(\MessageEntity.messageTimestamp, order: .forward)]
|
||||
)
|
||||
return (try? context.fetch(descriptor)) ?? []
|
||||
}
|
||||
|
||||
func unreadMessages(context: NSManagedObjectContext) -> Int {
|
||||
// Returns the count of unread *channel* messages
|
||||
let fetchRequest = messageFetchRequest
|
||||
fetchRequest.sortDescriptors = [] // sort is irrelevant.
|
||||
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fetchRequest.predicate!, NSPredicate(format: "read == false")])
|
||||
|
||||
return (try? context.count(for: fetchRequest)) ?? 0
|
||||
@MainActor
|
||||
func unreadMessages(context: ModelContext) -> Int {
|
||||
let descriptor = FetchDescriptor<MessageEntity>(
|
||||
predicate: #Predicate<MessageEntity> { msg in
|
||||
msg.toUser == nil && msg.isEmoji == false && msg.read == false
|
||||
}
|
||||
)
|
||||
return (try? context.fetchCount(descriptor)) ?? 0
|
||||
}
|
||||
|
||||
// Backwards-compatible property (uses viewContext)
|
||||
var unreadMessages: Int { unreadMessages(context: PersistenceController.shared.container.viewContext) }
|
||||
@MainActor
|
||||
var unreadMessages: Int { unreadMessages(context: PersistenceController.shared.context) }
|
||||
|
||||
var hasAdmin: Bool {
|
||||
let adminChannel = channels?.filter { ($0 as AnyObject).name?.lowercased() == "admin" }
|
||||
return adminChannel?.count ?? 0 > 0
|
||||
let adminChannel = channels.filter { $0.name?.lowercased() == "admin" }
|
||||
return adminChannel.count > 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,68 +6,75 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import SwiftData
|
||||
|
||||
extension NodeInfoEntity {
|
||||
|
||||
var latestPosition: PositionEntity? {
|
||||
return self.positions?.lastObject as? PositionEntity
|
||||
return self.positions.sorted(by: { ($0.time ?? .distantPast) < ($1.time ?? .distantPast) }).last
|
||||
}
|
||||
|
||||
var latestDeviceMetrics: TelemetryEntity? {
|
||||
return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).lastObject as? TelemetryEntity
|
||||
return self.telemetries.filter { $0.metricsType == 0 }.sorted(by: { ($0.time ?? .distantPast) < ($1.time ?? .distantPast) }).last
|
||||
}
|
||||
|
||||
var latestEnvironmentMetrics: TelemetryEntity? {
|
||||
return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 1")).lastObject as? TelemetryEntity
|
||||
return self.telemetries.filter { $0.metricsType == 1 }.sorted(by: { ($0.time ?? .distantPast) < ($1.time ?? .distantPast) }).last
|
||||
}
|
||||
|
||||
var latestPowerMetrics: TelemetryEntity? {
|
||||
return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 2")).lastObject as? TelemetryEntity
|
||||
return self.telemetries.filter { $0.metricsType == 2 }.sorted(by: { ($0.time ?? .distantPast) < ($1.time ?? .distantPast) }).last
|
||||
}
|
||||
|
||||
var hasPositions: Bool {
|
||||
return self.positions?.count ?? 0 > 0
|
||||
return self.positions.count > 0
|
||||
}
|
||||
|
||||
var hasDeviceMetrics: Bool {
|
||||
let deviceMetrics = telemetries?.filter { ($0 as AnyObject).metricsType == 0 }
|
||||
return deviceMetrics?.count ?? 0 > 0
|
||||
let deviceMetrics = telemetries.filter { $0.metricsType == 0 }
|
||||
return deviceMetrics.count > 0
|
||||
}
|
||||
|
||||
var hasEnvironmentMetrics: Bool {
|
||||
let environmentMetrics = telemetries?.filter { ($0 as AnyObject).metricsType == 1 }
|
||||
return environmentMetrics?.count ?? 0 > 0
|
||||
let environmentMetrics = telemetries.filter { $0.metricsType == 1 }
|
||||
return environmentMetrics.count > 0
|
||||
}
|
||||
|
||||
func hasDataForLatestEnvironmentMetrics(attributes: [String]) -> Bool {
|
||||
guard let latest = self.latestEnvironmentMetrics else { return false }
|
||||
for attribute in attributes {
|
||||
guard self.latestEnvironmentMetrics?.entity.attributesByName.keys.contains(attribute) ?? false else {
|
||||
return false
|
||||
}
|
||||
if self.latestEnvironmentMetrics?.value(forKey: attribute) != nil {
|
||||
return true
|
||||
let mirror = Mirror(reflecting: latest)
|
||||
if let child = mirror.children.first(where: { $0.label == attribute }) {
|
||||
if child.value is Optional<Any> {
|
||||
let m = Mirror(reflecting: child.value)
|
||||
if m.displayStyle == .optional && m.children.count > 0 {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@MainActor
|
||||
var hasDetectionSensorMetrics: Bool {
|
||||
return user?.sensorMessageList.count ?? 0 > 0
|
||||
}
|
||||
|
||||
var hasPowerMetrics: Bool {
|
||||
let powerMetrics = telemetries?.filter { ($0 as AnyObject).metricsType == 2 }
|
||||
return powerMetrics?.count ?? 0 > 0
|
||||
let powerMetrics = telemetries.filter { $0.metricsType == 2 }
|
||||
return powerMetrics.count > 0
|
||||
}
|
||||
|
||||
var hasTraceRoutes: Bool {
|
||||
let routes = traceRoutes?.filter { ($0 as AnyObject).response }
|
||||
return routes?.count ?? 0 > 0
|
||||
let routes = traceRoutes.filter { $0.response }
|
||||
return routes.count > 0
|
||||
}
|
||||
|
||||
var hasPax: Bool {
|
||||
return pax?.count ?? 0 > 0
|
||||
return pax.count > 0
|
||||
}
|
||||
|
||||
var isStoreForwardRouter: Bool {
|
||||
|
|
@ -86,18 +93,18 @@ extension NodeInfoEntity {
|
|||
if UserDefaults.enableAdministration {
|
||||
return true
|
||||
} else {
|
||||
let adminChannel = myInfo?.channels?.filter { ($0 as AnyObject).name?.lowercased() == "admin" }
|
||||
let adminChannel = myInfo?.channels.filter { $0.name?.lowercased() == "admin" }
|
||||
return adminChannel?.count ?? 0 > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func createNodeInfo(num: Int64, context: NSManagedObjectContext) -> NodeInfoEntity {
|
||||
func createNodeInfo(num: Int64, context: ModelContext) -> NodeInfoEntity {
|
||||
|
||||
let newNode = NodeInfoEntity(context: context)
|
||||
let newNode = NodeInfoEntity()
|
||||
newNode.id = Int64(num)
|
||||
newNode.num = Int64(num)
|
||||
let newUser = UserEntity(context: context)
|
||||
let newUser = UserEntity()
|
||||
newUser.num = Int64(num)
|
||||
let userId = num.toHex()
|
||||
newUser.userId = "!\(userId)"
|
||||
|
|
@ -106,5 +113,7 @@ public func createNodeInfo(num: Int64, context: NSManagedObjectContext) -> NodeI
|
|||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
newNode.user = newUser
|
||||
context.insert(newNode)
|
||||
context.insert(newUser)
|
||||
return newNode
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
// Copyright(c) Garth Vander Houwen 11/28/21.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import SwiftData
|
||||
import CoreLocation
|
||||
import MapKit
|
||||
import MeshtasticProtobufs
|
||||
|
|
@ -14,44 +14,14 @@ import SwiftUI
|
|||
extension PositionEntity {
|
||||
|
||||
@MainActor
|
||||
static func allPositionsFetchRequest() -> NSFetchRequest<PositionEntity> {
|
||||
|
||||
let request: NSFetchRequest<PositionEntity> = PositionEntity.fetchRequest()
|
||||
request.sortDescriptors = [NSSortDescriptor(key: "time", ascending: false)]
|
||||
let positionPredicate = NSPredicate(format: "nodePosition != nil AND nodePosition.user != nil AND latest == true AND nodePosition.user.shortName != ''")
|
||||
request.predicate = positionPredicate
|
||||
|
||||
// Distance Predicate
|
||||
if let cl = LocationsHandler.currentLocation {
|
||||
|
||||
let d: Double = UserDefaults.meshMapDistance * 1.1
|
||||
let r: Double = 6371009 // Earth's mean radius in meters
|
||||
|
||||
// Calculate Bounding Box
|
||||
let meanLatitidue = cl.latitude * .pi / 180
|
||||
let deltaLatitude = d / r * 180 / .pi
|
||||
let deltaLongitude = d / (r * cos(meanLatitidue)) * 180 / .pi
|
||||
|
||||
let minLatitude: Double = cl.latitude - deltaLatitude
|
||||
let maxLatitude: Double = cl.latitude + deltaLatitude
|
||||
let minLongitude: Double = cl.longitude - deltaLongitude
|
||||
let maxLongitude: Double = cl.longitude + deltaLongitude
|
||||
|
||||
// Scale bounding box values by 1e7 and use integer attributes (longitudeI, latitudeI)
|
||||
let scale: Double = 1e7
|
||||
let minLongitudeI = Int(minLongitude * scale)
|
||||
let maxLongitudeI = Int(maxLongitude * scale)
|
||||
let minLatitudeI = Int(minLatitude * scale)
|
||||
let maxLatitudeI = Int(maxLatitude * scale)
|
||||
|
||||
// Use integer comparison in the predicate
|
||||
let distancePredicate = NSPredicate(format: "(%ld <= longitudeI) AND (longitudeI <= %ld) AND (%ld <= latitudeI) AND (latitudeI <= %ld)",
|
||||
minLongitudeI, maxLongitudeI, minLatitudeI, maxLatitudeI)
|
||||
|
||||
request.predicate = NSCompoundPredicate(type: .and, subpredicates: [positionPredicate, distancePredicate])
|
||||
}
|
||||
|
||||
return request
|
||||
static func allPositionsFetchDescriptor() -> FetchDescriptor<PositionEntity> {
|
||||
var descriptor = FetchDescriptor<PositionEntity>(
|
||||
predicate: #Predicate<PositionEntity> { pos in
|
||||
pos.nodePosition != nil && pos.latest == true
|
||||
},
|
||||
sortBy: [SortDescriptor(\.time, order: .reverse)]
|
||||
)
|
||||
return descriptor
|
||||
}
|
||||
var latitude: Double? {
|
||||
|
||||
|
|
@ -127,9 +97,19 @@ extension PositionEntity {
|
|||
}
|
||||
}
|
||||
|
||||
extension PositionEntity: MKAnnotation {
|
||||
public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationsHandler.DefaultLocation }
|
||||
public var fuzzedCoordinate: CLLocationCoordinate2D { fuzzedNodeCoordinate ?? LocationsHandler.DefaultLocation }
|
||||
public var title: String? { nodePosition?.user?.shortName ?? "Unknown".localized }
|
||||
public var subtitle: String? { time?.formatted() }
|
||||
class PositionAnnotation: NSObject, MKAnnotation {
|
||||
let positionEntity: PositionEntity
|
||||
@objc dynamic var coordinate: CLLocationCoordinate2D
|
||||
var fuzzedCoordinate: CLLocationCoordinate2D
|
||||
var title: String?
|
||||
var subtitle: String?
|
||||
|
||||
init(position: PositionEntity) {
|
||||
self.positionEntity = position
|
||||
self.coordinate = position.nodeCoordinate ?? LocationsHandler.DefaultLocation
|
||||
self.fuzzedCoordinate = position.fuzzedNodeCoordinate ?? LocationsHandler.DefaultLocation
|
||||
self.title = position.nodePosition?.user?.shortName ?? "Unknown".localized
|
||||
self.subtitle = position.time?.formatted()
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import CoreData
|
||||
import SwiftData
|
||||
import MeshtasticProtobufs
|
||||
|
||||
extension RangeTestConfigEntity {
|
||||
convenience init(
|
||||
context: NSManagedObjectContext,
|
||||
config: ModuleConfig.RangeTestConfig
|
||||
) {
|
||||
self.init(context: context)
|
||||
convenience init(config: ModuleConfig.RangeTestConfig) {
|
||||
self.init()
|
||||
self.sender = Int32(config.sender)
|
||||
self.enabled = config.enabled
|
||||
self.save = config.save
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import CoreData
|
||||
import SwiftData
|
||||
import MeshtasticProtobufs
|
||||
|
||||
extension SerialConfigEntity {
|
||||
convenience init(
|
||||
context: NSManagedObjectContext,
|
||||
config: ModuleConfig.SerialConfig
|
||||
) {
|
||||
self.init(context: context)
|
||||
convenience init(config: ModuleConfig.SerialConfig) {
|
||||
self.init()
|
||||
self.enabled = config.enabled
|
||||
self.echo = config.echo
|
||||
self.rxd = Int32(config.rxd)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import CoreData
|
||||
import SwiftData
|
||||
import MeshtasticProtobufs
|
||||
|
||||
extension StoreForwardConfigEntity {
|
||||
convenience init(
|
||||
context: NSManagedObjectContext,
|
||||
config: ModuleConfig.StoreForwardConfig
|
||||
) {
|
||||
self.init(context: context)
|
||||
convenience init(config: ModuleConfig.StoreForwardConfig) {
|
||||
self.init()
|
||||
self.enabled = config.enabled
|
||||
self.heartbeat = config.heartbeat
|
||||
self.records = Int32(config.records)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
// Copyright(c) Garth Vander Houwen 12/7/23.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import SwiftData
|
||||
import CoreLocation
|
||||
import MapKit
|
||||
import SwiftUI
|
||||
|
|
|
|||
|
|
@ -6,64 +6,39 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import SwiftData
|
||||
import MeshtasticProtobufs
|
||||
|
||||
extension UserEntity {
|
||||
var messagePredicate: NSPredicate {
|
||||
return NSPredicate(format: "((toUser == %@) OR (fromUser == %@)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10", self, self)
|
||||
}
|
||||
|
||||
var messageFetchRequest: NSFetchRequest<MessageEntity> {
|
||||
let fetchRequest = MessageEntity.fetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: true)]
|
||||
fetchRequest.predicate = messagePredicate
|
||||
return fetchRequest
|
||||
}
|
||||
|
||||
@MainActor
|
||||
var messageList: [MessageEntity] {
|
||||
let context = PersistenceController.shared.container.viewContext
|
||||
let fetchRequest = messageFetchRequest
|
||||
|
||||
return (try? context.fetch(fetchRequest)) ?? [MessageEntity]()
|
||||
let context = PersistenceController.shared.context
|
||||
let messages = (self.sentMessages ?? []) + (self.receivedMessages ?? [])
|
||||
return messages.filter { msg in
|
||||
msg.toUser != nil && msg.fromUser != nil && !msg.isEmoji && !msg.admin && msg.portNum != 10
|
||||
}.sorted { $0.messageTimestamp < $1.messageTimestamp }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
var mostRecentMessage: MessageEntity? {
|
||||
// Most contacts will have no DMs history, so we can return early.
|
||||
guard self.lastMessage != nil else { return nil; }
|
||||
|
||||
// Most recent DM for this user (descending, limit 1)
|
||||
let context = PersistenceController.shared.container.viewContext
|
||||
let fetchRequest = messageFetchRequest
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: false)]
|
||||
fetchRequest.fetchLimit = 1
|
||||
|
||||
return (try? context.fetch(fetchRequest))?.first
|
||||
guard self.lastMessage != nil else { return nil }
|
||||
return messageList.last
|
||||
}
|
||||
|
||||
@MainActor
|
||||
var sensorMessageList: [MessageEntity] {
|
||||
let context = PersistenceController.shared.container.viewContext
|
||||
let fetchRequest = MessageEntity.fetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: true)]
|
||||
fetchRequest.predicate = NSPredicate(format: "(fromUser == %@) AND portNum = 10", self)
|
||||
|
||||
return (try? context.fetch(fetchRequest)) ?? [MessageEntity]()
|
||||
return (self.sentMessages ?? []).filter { $0.portNum == 10 }
|
||||
.sorted { $0.messageTimestamp < $1.messageTimestamp }
|
||||
}
|
||||
|
||||
func unreadMessages(context: NSManagedObjectContext, skipLastMessageCheck: Bool = false) -> Int {
|
||||
// Most contacts will have no DMs history, so we can return early.
|
||||
// (For our own node, set skipLastMessageCheck=true, because we don't update lastMessage on our own connected node.)
|
||||
guard self.lastMessage != nil || skipLastMessageCheck else { return 0; }
|
||||
|
||||
let fetchRequest = messageFetchRequest
|
||||
fetchRequest.sortDescriptors = [] // sort is irrelevant.
|
||||
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fetchRequest.predicate!, NSPredicate(format: "read == false")])
|
||||
|
||||
return (try? context.count(for: fetchRequest)) ?? 0
|
||||
@MainActor
|
||||
func unreadMessages(context: ModelContext, skipLastMessageCheck: Bool = false) -> Int {
|
||||
guard self.lastMessage != nil || skipLastMessageCheck else { return 0 }
|
||||
return messageList.filter { !$0.read }.count
|
||||
}
|
||||
|
||||
// Backwards-compatible property (uses viewContext)
|
||||
var unreadMessages: Int { unreadMessages(context: PersistenceController.shared.container.viewContext) }
|
||||
@MainActor
|
||||
var unreadMessages: Int { unreadMessages(context: PersistenceController.shared.context) }
|
||||
|
||||
/// SVG Images for Vendors who are signed project backers
|
||||
var hardwareImage: String? {
|
||||
|
|
@ -159,26 +134,22 @@ extension UserEntity {
|
|||
}
|
||||
}
|
||||
|
||||
public func createUser(num: Int64, context: NSManagedObjectContext) throws -> UserEntity {
|
||||
func createUser(num: Int64, context: ModelContext) throws -> UserEntity {
|
||||
// Validate Input
|
||||
guard num >= 0 else {
|
||||
throw CoreDataError.invalidInput(message: "User number cannot be negative.")
|
||||
}
|
||||
|
||||
var newUser: UserEntity! // Use an implicitly unwrapped optional, but ensure it's assigned
|
||||
|
||||
context.performAndWait {
|
||||
newUser = UserEntity(context: context)
|
||||
newUser.num = num
|
||||
let userId = num.toHex()
|
||||
newUser.userId = userId
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
newUser.unmessagable = false
|
||||
}
|
||||
|
||||
let newUser = UserEntity()
|
||||
newUser.num = num
|
||||
let userId = num.toHex()
|
||||
newUser.userId = userId
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
newUser.unmessagable = false
|
||||
context.insert(newUser)
|
||||
return newUser
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,20 +4,22 @@
|
|||
//
|
||||
// Copyright (c) Garth Vander Houwen 1/13/23.
|
||||
//
|
||||
import CoreData
|
||||
import SwiftData
|
||||
import CoreLocation
|
||||
import MapKit
|
||||
import SwiftUI
|
||||
|
||||
extension WaypointEntity {
|
||||
|
||||
static func allWaypointssFetchRequest() -> NSFetchRequest<WaypointEntity> {
|
||||
let request: NSFetchRequest<WaypointEntity> = WaypointEntity.fetchRequest()
|
||||
request.fetchLimit = 50
|
||||
request.returnsDistinctResults = true
|
||||
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: false)]
|
||||
request.predicate = NSPredicate(format: "expire == nil || expire >= %@", Date() as NSDate)
|
||||
return request
|
||||
@MainActor
|
||||
static func allWaypointsFetchDescriptor() -> FetchDescriptor<WaypointEntity> {
|
||||
let now = Date()
|
||||
return FetchDescriptor<WaypointEntity>(
|
||||
predicate: #Predicate<WaypointEntity> { wp in
|
||||
wp.expire == nil || wp.expire! >= now
|
||||
},
|
||||
sortBy: [SortDescriptor(\.name, order: .reverse)]
|
||||
)
|
||||
}
|
||||
|
||||
var latitude: Double? {
|
||||
|
|
@ -54,26 +56,38 @@ extension WaypointEntity {
|
|||
}
|
||||
}
|
||||
|
||||
extension WaypointEntity: MKAnnotation {
|
||||
extension WaypointEntity {
|
||||
@MainActor
|
||||
public var coordinate: CLLocationCoordinate2D {
|
||||
var mapCoordinate: CLLocationCoordinate2D {
|
||||
get {
|
||||
waypointCoordinate ?? LocationsHandler.DefaultLocation
|
||||
}
|
||||
set {
|
||||
latitudeI = Int32(newValue.latitude * 1e7)
|
||||
longitudeI = Int32(newValue.longitude * 1e7)
|
||||
}
|
||||
}
|
||||
|
||||
public var title: String? {
|
||||
var mapTitle: String? {
|
||||
name ?? "Dropped Pin"
|
||||
}
|
||||
|
||||
public var subtitle: String? {
|
||||
var mapSubtitle: String? {
|
||||
(longDescription ?? "") +
|
||||
String(expire != nil ? "\n⌛ Expires \(String(describing: expire?.formatted()))" : "") +
|
||||
String(locked > 0 ? "\n🔒 Locked" : "")
|
||||
String(locked ? "\n🔒 Locked" : "")
|
||||
}
|
||||
}
|
||||
|
||||
class WaypointAnnotation: NSObject, MKAnnotation {
|
||||
let waypointEntity: WaypointEntity
|
||||
@objc dynamic var coordinate: CLLocationCoordinate2D
|
||||
var title: String?
|
||||
var subtitle: String?
|
||||
|
||||
@MainActor
|
||||
init(waypoint: WaypointEntity) {
|
||||
self.waypointEntity = waypoint
|
||||
self.coordinate = waypoint.mapCoordinate
|
||||
self.title = waypoint.mapTitle
|
||||
self.subtitle = waypoint.mapSubtitle
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue