mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Create dedicated data layer types for querying and updating CoreData
This commit is contained in:
parent
3a248b6121
commit
b011cbde42
52 changed files with 2963 additions and 3035 deletions
|
|
@ -45,6 +45,7 @@ disabled_rules: # rule identifiers to exclude from running
|
|||
- operator_whitespace
|
||||
- multiple_closures_with_trailing_closure
|
||||
- todo
|
||||
- trailing_whitespace
|
||||
|
||||
# TODO: should review
|
||||
nesting:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; };
|
||||
DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */; };
|
||||
DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */; };
|
||||
DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */; };
|
||||
DD3CC6C228EB9D4900FA9159 /* UpdateCoreDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6C128EB9D4900FA9159 /* UpdateCoreDataController.swift */; };
|
||||
DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582528582E9B009B0E59 /* DeviceConfig.swift */; };
|
||||
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; };
|
||||
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582928585C32009B0E59 /* RangeTestConfig.swift */; };
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */; };
|
||||
DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */; };
|
||||
DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC32974767D007C176F /* MapViewFitExtension.swift */; };
|
||||
DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC52975DBFD007C176F /* QueryCoreData.swift */; };
|
||||
DD964FC62975DBFD007C176F /* QueryCoreDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC52975DBFD007C176F /* QueryCoreDataController.swift */; };
|
||||
DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; };
|
||||
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; };
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* IntervalEnums.swift */; };
|
||||
|
|
@ -292,7 +292,7 @@
|
|||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = "<group>"; };
|
||||
DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingEnums.swift; sourceTree = "<group>"; };
|
||||
DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCoreData.swift; sourceTree = "<group>"; };
|
||||
DD3CC6C128EB9D4900FA9159 /* UpdateCoreDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCoreDataController.swift; sourceTree = "<group>"; };
|
||||
DD3D17DC2C3D7B1400561584 /* MeshtasticDataModelV 39.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 39.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD41582528582E9B009B0E59 /* DeviceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfig.swift; sourceTree = "<group>"; };
|
||||
DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -344,7 +344,7 @@
|
|||
DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV6.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD964FC32974767D007C176F /* MapViewFitExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewFitExtension.swift; sourceTree = "<group>"; };
|
||||
DD964FC52975DBFD007C176F /* QueryCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreData.swift; sourceTree = "<group>"; };
|
||||
DD964FC52975DBFD007C176F /* QueryCoreDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreDataController.swift; sourceTree = "<group>"; };
|
||||
DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV32.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = "<group>"; };
|
||||
DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -869,8 +869,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC4D567275499A500A4208E /* Persistence.swift */,
|
||||
DD964FC52975DBFD007C176F /* QueryCoreData.swift */,
|
||||
DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */,
|
||||
DD964FC52975DBFD007C176F /* QueryCoreDataController.swift */,
|
||||
DD3CC6C128EB9D4900FA9159 /* UpdateCoreDataController.swift */,
|
||||
);
|
||||
path = Persistence;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1188,7 +1188,7 @@
|
|||
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */,
|
||||
DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */,
|
||||
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */,
|
||||
DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */,
|
||||
DD3CC6C228EB9D4900FA9159 /* UpdateCoreDataController.swift in Sources */,
|
||||
DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */,
|
||||
DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */,
|
||||
DDB8F4142A9EE5F000230ECE /* ChannelList.swift in Sources */,
|
||||
|
|
@ -1223,7 +1223,7 @@
|
|||
DDD5BB092C285DDC007E03CA /* AppLog.swift in Sources */,
|
||||
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */,
|
||||
DDC1B81A2AB5377B00C71E39 /* MessagesTips.swift in Sources */,
|
||||
DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */,
|
||||
DD964FC62975DBFD007C176F /* QueryCoreDataController.swift in Sources */,
|
||||
DDB75A112A059258006ED576 /* Url.swift in Sources */,
|
||||
DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */,
|
||||
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -11,9 +11,20 @@ import TipKit
|
|||
@main
|
||||
struct MeshtasticAppleApp: App {
|
||||
|
||||
@UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate
|
||||
let persistenceController = PersistenceController.shared
|
||||
@ObservedObject private var bleManager: BLEManager = BLEManager.shared
|
||||
@UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self)
|
||||
var appDelegate
|
||||
|
||||
@ObservedObject
|
||||
var bleManager: BLEManager
|
||||
|
||||
@ObservedObject
|
||||
var persistenceController: PersistenceController
|
||||
|
||||
@ObservedObject
|
||||
var updateCoreDataController: UpdateCoreDataController
|
||||
|
||||
@ObservedObject
|
||||
var queryCoreDataController: QueryCoreDataController
|
||||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
|
|
@ -23,10 +34,29 @@ struct MeshtasticAppleApp: App {
|
|||
@State var addChannels = false
|
||||
@StateObject var appState = AppState.shared
|
||||
|
||||
init() {
|
||||
let persistenceController = PersistenceController()
|
||||
let backgroundContext = persistenceController.container.newBackgroundContext()
|
||||
let updateCoreDataController = UpdateCoreDataController(context: backgroundContext)
|
||||
let queryCoreDataController = QueryCoreDataController(context: persistenceController.container.viewContext)
|
||||
|
||||
self.persistenceController = persistenceController
|
||||
self.updateCoreDataController = updateCoreDataController
|
||||
self.queryCoreDataController = queryCoreDataController
|
||||
self.bleManager = BLEManager(
|
||||
context: persistenceController.container.viewContext,
|
||||
updateCoreDataController: updateCoreDataController,
|
||||
queryCoreDataController: queryCoreDataController
|
||||
)
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
.environmentObject(persistenceController)
|
||||
.environmentObject(updateCoreDataController)
|
||||
.environmentObject(queryCoreDataController)
|
||||
.environmentObject(bleManager)
|
||||
.sheet(isPresented: $saveChannels) {
|
||||
SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager)
|
||||
|
|
@ -41,7 +71,7 @@ struct MeshtasticAppleApp: App {
|
|||
if (self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil {
|
||||
if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") {
|
||||
self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false
|
||||
if ((self.incomingUrl?.absoluteString.lowercased().contains("?")) != nil) {
|
||||
if (self.incomingUrl?.absoluteString.lowercased().contains("?")) != nil {
|
||||
guard let cs = components.last!.components(separatedBy: "?").first else {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@
|
|||
import CoreData
|
||||
import OSLog
|
||||
|
||||
class PersistenceController {
|
||||
|
||||
static let shared = PersistenceController()
|
||||
class PersistenceController: ObservableObject {
|
||||
|
||||
static var preview: PersistenceController = {
|
||||
let result = PersistenceController(inMemory: false)
|
||||
|
|
@ -72,6 +70,29 @@ class PersistenceController {
|
|||
Logger.data.error("Failed to destroy CoreData database, delete the app and re-install to clear data. Attempted to clear persistent store: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes: Bool) {
|
||||
for i in 0...container.managedObjectModel.entities.count-1 {
|
||||
|
||||
let entity = container.managedObjectModel.entities[i]
|
||||
let query = NSFetchRequest<NSFetchRequestResult>(entityName: entity.name!)
|
||||
var deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
|
||||
let entityName = entity.name ?? "UNK"
|
||||
|
||||
if includeRoutes {
|
||||
deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
|
||||
} else if !includeRoutes {
|
||||
if !(entityName.contains("RouteEntity") || entityName.contains("LocationEntity")) {
|
||||
deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
|
||||
}
|
||||
}
|
||||
do {
|
||||
try context.executeAndMergeChanges(using: deleteRequest)
|
||||
} catch {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSManagedObjectContext {
|
||||
|
|
@ -103,40 +124,7 @@ extension NSPersistentContainer {
|
|||
case invalidSource(String)
|
||||
}
|
||||
|
||||
/// Restore a persistent store for a URL `backupURL`.
|
||||
/// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app.
|
||||
/// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores.
|
||||
/// - Throws: `CopyPersistentStoreError` in various situations.
|
||||
/// - Returns: Nothing. If no errors are thrown, the restore is complete.
|
||||
// func restorePersistentStore(from backupURL: URL) throws -> Void {
|
||||
// guard backupURL.isFileURL else {
|
||||
// throw CopyPersistentStoreErrors.invalidSource("Backup URL must be a file URL")
|
||||
// }
|
||||
//
|
||||
// for persistentStoreDescription in persistentStoreDescriptions {
|
||||
// guard let loadedStoreURL = persistentStoreDescription.url else {
|
||||
// continue
|
||||
// }
|
||||
// guard FileManager.default.fileExists(atPath: backupURL.path) else {
|
||||
// throw CopyPersistentStoreErrors.invalidSource("Missing backup store for \(backupURL)")
|
||||
// }
|
||||
// do {
|
||||
// let storeOptions = persistentStoreDescription.options
|
||||
// let configurationName = persistentStoreDescription.configuration
|
||||
// let storeType = persistentStoreDescription.type
|
||||
//
|
||||
// // Replace the current store with the backup copy. This has a side effect of removing the current store from the Core Data stack.
|
||||
// // When restoring, it's necessary to use the current persistent store coordinator.
|
||||
// try persistentStoreCoordinator.replacePersistentStore(at: loadedStoreURL, destinationOptions: storeOptions, withPersistentStoreFrom: backupURL, sourceOptions: storeOptions, ofType: storeType)
|
||||
// // Add the persistent store at the same location we've been using, because it was removed in the previous step.
|
||||
// try persistentStoreCoordinator.addPersistentStore(ofType: storeType, configurationName: configurationName, at: loadedStoreURL, options: storeOptions)
|
||||
// } catch {
|
||||
// throw CopyPersistentStoreErrors.copyStoreError("Could not restore: \(error.localizedDescription)")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
/// Restore backup persistent stores located in the directory referenced by `backupURL`.
|
||||
/// Restore backup persistent stores located in the directory referenced by `backupURL`.
|
||||
///
|
||||
/// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app.
|
||||
/// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores.
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
//
|
||||
// QueryCoreData.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created(c) Garth Vander Houwen 1/16/23.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
public func getNodeInfo(id: Int64, context: NSManagedObjectContext) -> NodeInfoEntity? {
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(id))
|
||||
|
||||
do {
|
||||
let fetchedNode = try context.fetch(fetchNodeInfoRequest)
|
||||
if fetchedNode.count == 1 {
|
||||
return fetchedNode[0]
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getStoreAndForwardMessageIds(seconds: Int, context: NSManagedObjectContext) -> [UInt32] {
|
||||
|
||||
let time = seconds * -1
|
||||
let fetchMessagesRequest = MessageEntity.fetchRequest()
|
||||
let timeRange = Calendar.current.date(byAdding: .minute, value: time, to: Date())
|
||||
let milleseconds = Int32(timeRange?.timeIntervalSince1970 ?? 0)
|
||||
fetchMessagesRequest.predicate = NSPredicate(format: "receivedTimestamp >= %d", milleseconds)
|
||||
|
||||
do {
|
||||
let fetchedMessages = try context.fetch(fetchMessagesRequest)
|
||||
if fetchedMessages.count == 1 {
|
||||
return fetchedMessages.map { UInt32($0.messageId) }
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
public func getTraceRoute(id: Int64, context: NSManagedObjectContext) -> TraceRouteEntity? {
|
||||
|
||||
let fetchTraceRouteRequest = TraceRouteEntity.fetchRequest()
|
||||
fetchTraceRouteRequest.predicate = NSPredicate(format: "id == %lld", Int64(id))
|
||||
|
||||
do {
|
||||
let fetchedTraceRoute = try context.fetch(fetchTraceRouteRequest)
|
||||
if fetchedTraceRoute.count == 1 {
|
||||
return fetchedTraceRoute[0]
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getUser(id: Int64, context: NSManagedObjectContext) -> UserEntity {
|
||||
|
||||
let fetchUserRequest = UserEntity.fetchRequest()
|
||||
fetchUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(id))
|
||||
|
||||
do {
|
||||
let fetchedUser = try context.fetch(fetchUserRequest)
|
||||
if fetchedUser.count == 1 {
|
||||
return fetchedUser[0]
|
||||
}
|
||||
} catch {
|
||||
return UserEntity(context: context)
|
||||
}
|
||||
return UserEntity(context: context)
|
||||
}
|
||||
|
||||
public func getWaypoint(id: Int64, context: NSManagedObjectContext) -> WaypointEntity {
|
||||
|
||||
let fetchWaypointRequest = WaypointEntity.fetchRequest()
|
||||
fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(id))
|
||||
|
||||
do {
|
||||
let fetchedWaypoint = try context.fetch(fetchWaypointRequest)
|
||||
if fetchedWaypoint.count == 1 {
|
||||
return fetchedWaypoint[0]
|
||||
}
|
||||
} catch {
|
||||
return WaypointEntity(context: context)
|
||||
}
|
||||
return WaypointEntity(context: context)
|
||||
}
|
||||
99
Meshtastic/Persistence/QueryCoreDataController.swift
Normal file
99
Meshtastic/Persistence/QueryCoreDataController.swift
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// QueryCoreData.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created(c) Garth Vander Houwen 1/16/23.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
class QueryCoreDataController: ObservableObject {
|
||||
private let context: NSManagedObjectContext
|
||||
|
||||
init(context: NSManagedObjectContext) {
|
||||
self.context = context
|
||||
}
|
||||
|
||||
public func getNodeInfo(id: Int64) -> NodeInfoEntity? {
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(id))
|
||||
|
||||
do {
|
||||
let fetchedNode = try context.fetch(fetchNodeInfoRequest)
|
||||
if fetchedNode.count == 1 {
|
||||
return fetchedNode[0]
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getStoreAndForwardMessageIds(seconds: Int) -> [UInt32] {
|
||||
|
||||
let time = seconds * -1
|
||||
let fetchMessagesRequest = MessageEntity.fetchRequest()
|
||||
let timeRange = Calendar.current.date(byAdding: .minute, value: time, to: Date())
|
||||
let milleseconds = Int32(timeRange?.timeIntervalSince1970 ?? 0)
|
||||
fetchMessagesRequest.predicate = NSPredicate(format: "receivedTimestamp >= %d", milleseconds)
|
||||
|
||||
do {
|
||||
let fetchedMessages = try context.fetch(fetchMessagesRequest)
|
||||
if fetchedMessages.count == 1 {
|
||||
return fetchedMessages.map { UInt32($0.messageId) }
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
public func getTraceRoute(id: Int64) -> TraceRouteEntity? {
|
||||
|
||||
let fetchTraceRouteRequest = TraceRouteEntity.fetchRequest()
|
||||
fetchTraceRouteRequest.predicate = NSPredicate(format: "id == %lld", Int64(id))
|
||||
|
||||
do {
|
||||
let fetchedTraceRoute = try context.fetch(fetchTraceRouteRequest)
|
||||
if fetchedTraceRoute.count == 1 {
|
||||
return fetchedTraceRoute[0]
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getUser(id: Int64) -> UserEntity {
|
||||
|
||||
let fetchUserRequest = UserEntity.fetchRequest()
|
||||
fetchUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(id))
|
||||
|
||||
do {
|
||||
let fetchedUser = try context.fetch(fetchUserRequest)
|
||||
if fetchedUser.count == 1 {
|
||||
return fetchedUser[0]
|
||||
}
|
||||
} catch {
|
||||
return UserEntity(context: context)
|
||||
}
|
||||
return UserEntity(context: context)
|
||||
}
|
||||
|
||||
public func getWaypoint(id: Int64) -> WaypointEntity {
|
||||
|
||||
let fetchWaypointRequest = WaypointEntity.fetchRequest()
|
||||
fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(id))
|
||||
|
||||
do {
|
||||
let fetchedWaypoint = try context.fetch(fetchWaypointRequest)
|
||||
if fetchedWaypoint.count == 1 {
|
||||
return fetchedWaypoint[0]
|
||||
}
|
||||
} catch {
|
||||
return WaypointEntity(context: context)
|
||||
}
|
||||
return WaypointEntity(context: context)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
1312
Meshtastic/Persistence/UpdateCoreDataController.swift
Normal file
1312
Meshtastic/Persistence/UpdateCoreDataController.swift
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -226,7 +226,8 @@ struct Connect: View {
|
|||
} catch {
|
||||
Logger.data.error("🗂️ Core data backup copy error: \(error, privacy: .public)")
|
||||
}
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
// TODO: fix-me
|
||||
// clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
}
|
||||
UserDefaults.preferredPeripheralId = selectedPeripherialId
|
||||
self.bleManager.connectTo(peripheral: peripheral.peripheral)
|
||||
|
|
@ -323,11 +324,6 @@ struct Connect: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onAppear(perform: {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
})
|
||||
}
|
||||
#if canImport(ActivityKit)
|
||||
func startNodeActivity() {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import SwiftUI
|
|||
struct WaypointFormMapKit: View {
|
||||
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.managedObjectContext) private var context
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State var coordinate: WaypointCoordinate
|
||||
@FocusState private var iconIsFocused: Bool
|
||||
|
|
@ -122,7 +124,7 @@ struct WaypointFormMapKit: View {
|
|||
// Loading a waypoint from edit
|
||||
if coordinate.waypointId > 0 {
|
||||
newWaypoint.id = UInt32(coordinate.waypointId)
|
||||
let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!)
|
||||
let waypoint = queryCoreDataController.getWaypoint(id: Int64(coordinate.waypointId))
|
||||
newWaypoint.latitudeI = waypoint.latitudeI
|
||||
newWaypoint.longitudeI = waypoint.longitudeI
|
||||
} else {
|
||||
|
|
@ -179,14 +181,15 @@ struct WaypointFormMapKit: View {
|
|||
|
||||
Menu {
|
||||
Button("For me", action: {
|
||||
let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!)
|
||||
bleManager.context!.delete(waypoint)
|
||||
do {
|
||||
try bleManager.context!.save()
|
||||
} catch {
|
||||
bleManager.context!.rollback()
|
||||
}
|
||||
dismiss() })
|
||||
let waypoint = queryCoreDataController.getWaypoint(id: Int64(coordinate.waypointId))
|
||||
context.delete(waypoint)
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
}
|
||||
dismiss()
|
||||
})
|
||||
Button("For everyone", action: {
|
||||
var newWaypoint = Waypoint()
|
||||
|
||||
|
|
@ -230,7 +233,7 @@ struct WaypointFormMapKit: View {
|
|||
}
|
||||
.onAppear {
|
||||
if coordinate.waypointId > 0 {
|
||||
let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context!)
|
||||
let waypoint = queryCoreDataController.getWaypoint(id: Int64(coordinate.waypointId))
|
||||
name = waypoint.name ?? "Dropped Pin"
|
||||
description = waypoint.longDescription ?? ""
|
||||
icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ struct ChannelList: View {
|
|||
@StateObject var appState = AppState.shared
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var updateCoreDataController: UpdateCoreDataController
|
||||
|
||||
@State var node: NodeInfoEntity?
|
||||
|
||||
|
|
@ -140,7 +141,7 @@ struct ChannelList: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button(role: .destructive) {
|
||||
deleteChannelMessages(channel: channelSelection!, context: context)
|
||||
updateCoreDataController.deleteChannelMessages(channel: channelSelection!)
|
||||
context.refresh(myInfo, mergeChanges: true)
|
||||
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
|
||||
channelSelection = nil
|
||||
|
|
@ -148,11 +149,6 @@ struct ChannelList: View {
|
|||
Text("delete")
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.top, .bottom])
|
||||
|
|
|
|||
|
|
@ -131,9 +131,6 @@ struct ChannelMessageList: View {
|
|||
.padding([.top])
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
if channel.allPrivateMessages.count > 0 {
|
||||
scrollView.scrollTo(channel.allPrivateMessages.last!.messageId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,9 +87,6 @@ struct Messages: View {
|
|||
}
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
if UserDefaults.preferredPeripheralId.count > 0 {
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(UserDefaults.preferredPeripheralNum))
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ struct UserList: View {
|
|||
@StateObject var appState = AppState.shared
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var updateCoreDataController: UpdateCoreDataController
|
||||
@State private var searchText = ""
|
||||
@State private var viaLora = true
|
||||
@State private var viaMqtt = true
|
||||
|
|
@ -158,7 +159,7 @@ struct UserList: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button(role: .destructive) {
|
||||
deleteUserMessages(user: userSelection!, context: context)
|
||||
updateCoreDataController.deleteUserMessages(user: userSelection!)
|
||||
context.refresh(node!.user!, mergeChanges: true)
|
||||
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
|
||||
} label: {
|
||||
|
|
@ -210,9 +211,6 @@ struct UserList: View {
|
|||
userSelection = users.first(where: { $0.num == newUserNum })
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
searchUserList()
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, alignment: .trailing) {
|
||||
|
|
|
|||
|
|
@ -116,9 +116,6 @@ struct UserMessageList: View {
|
|||
.padding([.top])
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
if user.messageList.count > 0 {
|
||||
scrollView.scrollTo(user.messageList.last!.messageId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,11 +124,6 @@ struct DetectionSensorLog: View {
|
|||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct DeviceMetricsLog: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var updateCoreDataController: UpdateCoreDataController
|
||||
|
||||
@State private var isPresentingClearLogConfirm: Bool = false
|
||||
@State var isExporting = false
|
||||
|
|
@ -188,7 +189,7 @@ struct DeviceMetricsLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("device.metrics.delete", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 0, context: context) {
|
||||
if updateCoreDataController.clearTelemetry(destNum: node.num, metricsType: 0) {
|
||||
Logger.data.notice("Cleared Device Metrics for \(node.num)")
|
||||
} else {
|
||||
Logger.data.error("Clear Device Metrics Log Failed")
|
||||
|
|
@ -222,11 +223,6 @@ struct DeviceMetricsLog: View {
|
|||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct EnvironmentMetricsLog: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var updateCoreDataController: UpdateCoreDataController
|
||||
@State private var isPresentingClearLogConfirm: Bool = false
|
||||
@State var isExporting = false
|
||||
@State var exportString = ""
|
||||
|
|
@ -166,7 +167,7 @@ struct EnvironmentMetricsLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all environment metrics?", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 1, context: context) {
|
||||
if updateCoreDataController.clearTelemetry(destNum: node.num, metricsType: 1) {
|
||||
Logger.services.error("Clear Environment Metrics Log Failed")
|
||||
}
|
||||
}
|
||||
|
|
@ -199,11 +200,6 @@ struct EnvironmentMetricsLog: View {
|
|||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ struct DeleteNodeButton: View {
|
|||
var connectedNode: NodeInfoEntity
|
||||
|
||||
var node: NodeInfoEntity
|
||||
|
||||
@EnvironmentObject
|
||||
var queryCoreDataController: QueryCoreDataController
|
||||
|
||||
@State
|
||||
private var isPresentingAlert = false
|
||||
|
|
@ -31,10 +34,7 @@ struct DeleteNodeButton: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete Node", role: .destructive) {
|
||||
guard let deleteNode = getNodeInfo(
|
||||
id: node.num,
|
||||
context: context
|
||||
) else {
|
||||
guard let deleteNode = queryCoreDataController.getNodeInfo(id: node.num) else {
|
||||
Logger.data.error("Unable to find node info to delete node \(node.num)")
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import SwiftUI
|
|||
struct WaypointForm: View {
|
||||
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.managedObjectContext) private var context
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State var waypoint: WaypointEntity
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
|
|
@ -184,13 +186,14 @@ struct WaypointForm: View {
|
|||
|
||||
Menu {
|
||||
Button("For me", action: {
|
||||
bleManager.context!.delete(waypoint)
|
||||
context.delete(waypoint)
|
||||
do {
|
||||
try bleManager.context!.save()
|
||||
try context.save()
|
||||
} catch {
|
||||
bleManager.context!.rollback()
|
||||
context.rollback()
|
||||
}
|
||||
dismiss() })
|
||||
dismiss()
|
||||
})
|
||||
Button("For everyone", action: {
|
||||
var newWaypoint = Waypoint()
|
||||
newWaypoint.id = UInt32(waypoint.id)
|
||||
|
|
@ -213,11 +216,11 @@ struct WaypointForm: View {
|
|||
newWaypoint.expire = UInt32(1)
|
||||
if bleManager.sendWaypoint(waypoint: newWaypoint) {
|
||||
|
||||
bleManager.context!.delete(waypoint)
|
||||
context.delete(waypoint)
|
||||
do {
|
||||
try bleManager.context!.save()
|
||||
try context.save()
|
||||
} catch {
|
||||
bleManager.context!.rollback()
|
||||
context.rollback()
|
||||
}
|
||||
dismiss()
|
||||
} else {
|
||||
|
|
@ -351,7 +354,7 @@ struct WaypointForm: View {
|
|||
}
|
||||
.onAppear {
|
||||
if waypoint.id > 0 {
|
||||
let waypoint = getWaypoint(id: Int64(waypoint.id), context: bleManager.context!)
|
||||
let waypoint = queryCoreDataController.getWaypoint(id: Int64(waypoint.id))
|
||||
name = waypoint.name ?? "Dropped Pin"
|
||||
description = waypoint.longDescription ?? ""
|
||||
icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ struct NodeDetail: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@State private var showingShutdownConfirm: Bool = false
|
||||
@State private var showingRebootConfirm: Bool = false
|
||||
@State private var dateFormatRelative: Bool = true
|
||||
|
|
@ -34,10 +35,7 @@ struct NodeDetail: View {
|
|||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
let connectedNode = getNodeInfo(
|
||||
id: bleManager.connectedPeripheral?.num ?? -1,
|
||||
context: context
|
||||
)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
|
||||
Section("Hardware") {
|
||||
NodeInfoItem(node: node)
|
||||
|
|
@ -369,11 +367,6 @@ struct NodeDetail: View {
|
|||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,9 +253,6 @@ struct NodeList: View {
|
|||
}
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,12 +238,9 @@ struct NodeMap: View {
|
|||
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName :
|
||||
"?")
|
||||
})
|
||||
.onAppear(perform: {
|
||||
.onAppear {
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
})
|
||||
}
|
||||
.onDisappear(perform: {
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ struct PaxCounterLog: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var updateCoreDataController: UpdateCoreDataController
|
||||
|
||||
@State private var isPresentingClearLogConfirm: Bool = false
|
||||
@State var isExporting = false
|
||||
|
|
@ -175,7 +176,7 @@ struct PaxCounterLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("paxcounter.delete", role: .destructive) {
|
||||
if clearPax(destNum: node.num, context: context) {
|
||||
if updateCoreDataController.clearPax(destNum: node.num) {
|
||||
Logger.services.info("Cleared Pax Counter for \(node.num)")
|
||||
} else {
|
||||
Logger.services.error("Clear Pax Counter Log Failed")
|
||||
|
|
@ -209,11 +210,6 @@ struct PaxCounterLog: View {
|
|||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
.fileExporter(
|
||||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import OSLog
|
|||
struct PositionLog: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var updateCoreDataController: UpdateCoreDataController
|
||||
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
var useGrid: Bool {
|
||||
|
|
@ -130,7 +131,7 @@ struct PositionLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all positions?", role: .destructive) {
|
||||
if clearPositions(destNum: node.num, context: context) {
|
||||
if updateCoreDataController.clearPositions(destNum: node.num) {
|
||||
Logger.services.info("Successfully Cleared Position Log")
|
||||
} else {
|
||||
Logger.services.error("Clear Position Log Failed")
|
||||
|
|
@ -179,10 +180,5 @@ struct PositionLog: View {
|
|||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,10 +145,5 @@ struct TraceRouteLog: View {
|
|||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import OSLog
|
|||
struct AppSettings: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var persistenceController: PersistenceController
|
||||
@ObservedObject var tileManager = OfflineTileManager.shared
|
||||
@State var totalDownloadedTileSize = ""
|
||||
@State private var isPresentingCoreDataResetConfirm = false
|
||||
|
|
@ -57,7 +58,7 @@ struct AppSettings: View {
|
|||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite file \(error, privacy: .public)")
|
||||
}
|
||||
}
|
||||
clearCoreDataDatabase(context: context, includeRoutes: true)
|
||||
persistenceController.clearCoreDataDatabase(context: context, includeRoutes: true)
|
||||
context.refreshAllObjects()
|
||||
UserDefaults.standard.reset()
|
||||
}
|
||||
|
|
@ -94,10 +95,5 @@ struct AppSettings: View {
|
|||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,11 +280,6 @@ struct Channels: View {
|
|||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import SwiftUI
|
|||
struct BluetoothConfig: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
var node: NodeInfoEntity?
|
||||
@State var hasChanges = false
|
||||
|
|
@ -80,7 +81,7 @@ struct BluetoothConfig: View {
|
|||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
if let myNodeNum = bleManager.connectedPeripheral?.num,
|
||||
let connectedNode = getNodeInfo(id: myNodeNum, context: context) {
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: myNodeNum) {
|
||||
var bc = Config.BluetoothConfig()
|
||||
bc.enabled = enabled
|
||||
bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin
|
||||
|
|
@ -107,14 +108,11 @@ struct BluetoothConfig: View {
|
|||
}
|
||||
)
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setBluetoothValues()
|
||||
// Need to request a BluetoothConfig from the remote node before allowing changes
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral, let node, node.bluetoothConfig == nil {
|
||||
Logger.mesh.info("empty bluetooth config")
|
||||
let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: connectedPeripheral.num)
|
||||
if let connectedNode {
|
||||
_ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ struct DeviceConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var persistenceController: PersistenceController
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -166,7 +169,7 @@ struct DeviceConfig: View {
|
|||
if bleManager.sendNodeDBReset(fromUser: node!.user!, toUser: node!.user!) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
bleManager.disconnectPeripheral()
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
persistenceController.clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
@ -191,7 +194,8 @@ struct DeviceConfig: View {
|
|||
if bleManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
bleManager.disconnectPeripheral()
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
// TODO: re-enable me
|
||||
// clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
}
|
||||
} else {
|
||||
Logger.mesh.error("Factory Reset Failed")
|
||||
|
|
@ -202,7 +206,7 @@ struct DeviceConfig: View {
|
|||
}
|
||||
HStack {
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if connectedNode != nil {
|
||||
var dc = Config.DeviceConfig()
|
||||
dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue()
|
||||
|
|
@ -238,14 +242,11 @@ struct DeviceConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setDeviceValues()
|
||||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil {
|
||||
Logger.mesh.info("empty device config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
if node != nil && connectedNode != nil && connectedNode?.user != nil {
|
||||
_ = bleManager.requestDeviceConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ struct DisplayConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -129,7 +130,7 @@ struct DisplayConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.displayConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if connectedNode != nil {
|
||||
var dc = Config.DisplayConfig()
|
||||
dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue()
|
||||
|
|
@ -159,15 +160,12 @@ struct DisplayConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setDisplayValues()
|
||||
|
||||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.displayConfig == nil {
|
||||
Logger.mesh.info("empty display config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestDisplayConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ struct LoRaConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
@FocusState var focusedField: Field?
|
||||
|
||||
|
|
@ -191,7 +192,7 @@ struct LoRaConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.loRaConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0)
|
||||
if connectedNode != nil {
|
||||
var lc = Config.LoRaConfig()
|
||||
lc.hopLimit = UInt32(hopLimit)
|
||||
|
|
@ -226,14 +227,11 @@ struct LoRaConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setLoRaValues()
|
||||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.loRaConfig == nil {
|
||||
Logger.mesh.info("empty lora config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestLoRaConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct AmbientLightingConfig: View {
|
|||
@Environment(\.self) var environment
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -59,7 +60,7 @@ struct AmbientLightingConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.ambientLightingConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if connectedNode != nil {
|
||||
var al = ModuleConfig.AmbientLightingConfig()
|
||||
al.ledState = ledState
|
||||
|
|
@ -85,13 +86,10 @@ struct AmbientLightingConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setAmbientLightingConfigValue()
|
||||
// Need to request a Ambient Lighting Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.ambientLightingConfig == nil {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import SwiftUI
|
|||
struct CannedMessagesConfig: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
var node: NodeInfoEntity?
|
||||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
|
|
@ -178,7 +179,7 @@ struct CannedMessagesConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.cannedMessageConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
if hasChanges {
|
||||
if connectedNode != nil {
|
||||
var cmc = ModuleConfig.CannedMessageConfig()
|
||||
|
|
@ -229,14 +230,11 @@ struct CannedMessagesConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setCannedMessagesValues()
|
||||
// Need to request a CannedMessagesModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.cannedMessageConfig == nil {
|
||||
Logger.mesh.info("empty canned messages module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ struct DetectionSensorConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
var node: NodeInfoEntity?
|
||||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
|
|
@ -159,7 +160,7 @@ struct DetectionSensorConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.detectionSensorConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
if connectedNode != nil {
|
||||
var dsc = ModuleConfig.DetectionSensorConfig()
|
||||
dsc.enabled = self.enabled
|
||||
|
|
@ -185,14 +186,11 @@ struct DetectionSensorConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setDetectionSensorValues()
|
||||
// Need to request a Detection Sensor Module Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil {
|
||||
Logger.mesh.info("empty detection sensor module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct ExternalNotificationConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -162,7 +163,7 @@ struct ExternalNotificationConfig: View {
|
|||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
if connectedNode != nil {
|
||||
var enc = ModuleConfig.ExternalNotificationConfig()
|
||||
enc.enabled = enabled
|
||||
|
|
@ -195,14 +196,11 @@ struct ExternalNotificationConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setExternalNotificationValues()
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.externalNotificationConfig == nil {
|
||||
Logger.mesh.info("empty external notification module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,11 @@ struct MQTTConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
||||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
@State var hasChanges: Bool = false
|
||||
@State var enabled = false
|
||||
|
|
@ -246,7 +249,7 @@ struct MQTTConfig: View {
|
|||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
if connectedNode != nil {
|
||||
var mqtt = ModuleConfig.MQTTConfig()
|
||||
mqtt.enabled = self.enabled
|
||||
|
|
@ -357,14 +360,11 @@ struct MQTTConfig: View {
|
|||
}
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setMqttValues()
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil {
|
||||
Logger.mesh.info("empty mqtt module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import SwiftUI
|
|||
struct PaxCounterConfig: View {
|
||||
@Environment(\.managedObjectContext) private var context
|
||||
@EnvironmentObject private var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
let node: NodeInfoEntity?
|
||||
|
|
@ -58,14 +59,11 @@ struct PaxCounterConfig: View {
|
|||
)
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
|
||||
setPaxValues()
|
||||
// Need to request a PAX Counter module config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.paxCounterConfig == nil {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
@ -83,7 +81,7 @@ struct PaxCounterConfig: View {
|
|||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
guard let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context),
|
||||
guard let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num),
|
||||
let fromUser = connectedNode.user,
|
||||
let toUser = node?.user else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct RangeTestConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -56,7 +57,7 @@ struct RangeTestConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if connectedNode != nil {
|
||||
var rtc = ModuleConfig.RangeTestConfig()
|
||||
rtc.enabled = enabled
|
||||
|
|
@ -77,14 +78,11 @@ struct RangeTestConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setRangeTestValues()
|
||||
// Need to request a RangeTestModule Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.rangeTestConfig == nil {
|
||||
Logger.mesh.debug("empty range test module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import SwiftUI
|
|||
struct RtttlConfig: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -50,7 +51,7 @@ struct RtttlConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.rtttlConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if connectedNode != nil {
|
||||
let adminMessageId = bleManager.saveRtttlConfig(ringtone: ringtone.trimmingCharacters(in: .whitespacesAndNewlines), fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
|
|
@ -67,13 +68,10 @@ struct RtttlConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setRtttLConfigValue()
|
||||
// Need to request a Rtttl Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && (node?.rtttlConfig == nil || node?.rtttlConfig?.ringtone?.count ?? 0 == 0) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestRtttlConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct SerialConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -104,7 +105,7 @@ struct SerialConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.serialConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if connectedNode != nil {
|
||||
var sc = ModuleConfig.SerialConfig()
|
||||
sc.enabled = enabled
|
||||
|
|
@ -133,14 +134,11 @@ struct SerialConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setSerialValues()
|
||||
// Need to request a SerialModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.serialConfig == nil {
|
||||
Logger.mesh.debug("empty serial module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestSerialModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct StoreForwardConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
var node: NodeInfoEntity?
|
||||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
|
|
@ -108,7 +109,7 @@ struct StoreForwardConfig: View {
|
|||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
if connectedNode != nil {
|
||||
/// Let the user set isRouter for the connected node, for nodes on the mesh set isRouter based
|
||||
/// on receipt of a primary heartbeat
|
||||
|
|
@ -142,14 +143,10 @@ struct StoreForwardConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
|
||||
// Need to request a Detection Sensor Module Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.storeForwardConfig == nil {
|
||||
Logger.mesh.debug("empty store and forward module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct TelemetryConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -104,7 +105,7 @@ struct TelemetryConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.telemetryConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
if connectedNode != nil {
|
||||
var tc = ModuleConfig.TelemetryConfig()
|
||||
tc.deviceUpdateInterval = UInt32(deviceUpdateInterval)
|
||||
|
|
@ -130,14 +131,11 @@ struct TelemetryConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setTelemetryValues()
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.telemetryConfig == nil {
|
||||
Logger.mesh.info("empty telemetry module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct NetworkConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -89,7 +90,7 @@ struct NetworkConfig: View {
|
|||
.disabled(self.bleManager.connectedPeripheral == nil || node?.networkConfig == nil)
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if connectedNode != nil {
|
||||
var network = Config.NetworkConfig()
|
||||
network.wifiEnabled = self.wifiEnabled
|
||||
|
|
@ -114,14 +115,11 @@ struct NetworkConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setNetworkValues()
|
||||
// Need to request a NetworkConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.networkConfig == nil {
|
||||
Logger.mesh.info("empty network config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestNetworkConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ struct PositionConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
var node: NodeInfoEntity?
|
||||
@State var hasChanges = false
|
||||
|
|
@ -290,7 +291,7 @@ struct PositionConfig: View {
|
|||
if fixedPosition && !supportedVersion {
|
||||
_ = bleManager.sendPosition(channel: 0, destNum: node?.num ?? 0, wantResponse: true)
|
||||
}
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral!.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral!.num)
|
||||
|
||||
if connectedNode != nil {
|
||||
var pc = Config.PositionConfig()
|
||||
|
|
@ -377,15 +378,12 @@ struct PositionConfig: View {
|
|||
}
|
||||
)
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setPositionValues()
|
||||
supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame
|
||||
// Need to request a PositionConfig from the remote node before allowing changes
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral, node?.positionConfig == nil {
|
||||
Logger.mesh.info("empty position config")
|
||||
let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: connectedPeripheral.num)
|
||||
if let node, let connectedNode {
|
||||
_ = bleManager.requestPositionConfig(
|
||||
fromUser: connectedNode.user!,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import MeshtasticProtobufs
|
|||
struct PowerConfig: View {
|
||||
@Environment(\.managedObjectContext) private var context
|
||||
@EnvironmentObject private var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
let node: NodeInfoEntity?
|
||||
|
|
@ -118,10 +119,6 @@ struct PowerConfig: View {
|
|||
}
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
|
||||
Api().loadDeviceHardwareData { (hw) in
|
||||
|
||||
for device in hw {
|
||||
|
|
@ -136,7 +133,7 @@ struct PowerConfig: View {
|
|||
|
||||
// Need to request a Power config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.powerConfig == nil {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestPowerConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
|
|
@ -180,7 +177,7 @@ struct PowerConfig: View {
|
|||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
guard let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context),
|
||||
guard let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral.num),
|
||||
let fromUser = connectedNode.user,
|
||||
let toUser = node?.user else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import OSLog
|
|||
struct Firmware: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
var node: NodeInfoEntity?
|
||||
@State var minimumVersion = "2.3.15"
|
||||
@State var version = ""
|
||||
|
|
@ -108,7 +109,7 @@ struct Firmware: View {
|
|||
.foregroundStyle(.gray)
|
||||
.font(.caption)
|
||||
Button {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0)
|
||||
if connectedNode != nil {
|
||||
|
||||
if bleManager.sendEnterDfuMode(fromUser: connectedNode!.user!, toUser: node!.user!) {
|
||||
|
|
@ -158,7 +159,7 @@ struct Firmware: View {
|
|||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
Button {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0)
|
||||
if connectedNode != nil {
|
||||
if !bleManager.sendRebootOta(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex) {
|
||||
Logger.mesh.error("Reboot Failed")
|
||||
|
|
|
|||
|
|
@ -205,9 +205,9 @@ struct Settings: View {
|
|||
if selectedNode > 0 {
|
||||
let node = nodes.first(where: { $0.num == newValue })
|
||||
let connectedNode = nodes.first(where: { $0.num == preferredNodeNum })
|
||||
preferredNodeNum = Int(connectedNode?.num ?? 0)// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
preferredNodeNum = Int(connectedNode?.num ?? 0)
|
||||
if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil {
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context)
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context)
|
||||
if adminMessageId > 0 {
|
||||
Logger.mesh.info("Sent node metadata request from node details")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,7 +238,6 @@ struct ShareChannels: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
bleManager.context = context
|
||||
generateChannelSet()
|
||||
}
|
||||
.onChange(of: includeChannel0) { _ in generateChannelSet() }
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ struct UserConfig: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var queryCoreDataController: QueryCoreDataController
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
|
@ -149,8 +150,8 @@ struct UserConfig: View {
|
|||
return
|
||||
}
|
||||
|
||||
let connectedUser = getUser(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
let connectedUser = queryCoreDataController.getUser(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
let connectedNode = queryCoreDataController.getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1)
|
||||
if node != nil && connectedNode != nil {
|
||||
|
||||
if !isLicensed {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue