From abe0144d48a0e81c90b7611fb02da3ef4bf04792 Mon Sep 17 00:00:00 2001 From: Jacob Powers Date: Tue, 22 Jul 2025 04:13:54 +0000 Subject: [PATCH] fixes --- Meshtastic.xcodeproj/project.pbxproj | 8 +-- Meshtastic/Helpers/MapDataManager.swift | 71 ++++++++++++------- .../Nodes/Helpers/Map/MapSettingsForm.swift | 14 ++-- Meshtastic/Views/Nodes/MeshMap.swift | 5 ++ Meshtastic/Views/Settings/AppData.swift | 2 +- ...MapDataUpload.swift => MapDataFiles.swift} | 7 +- 6 files changed, 64 insertions(+), 43 deletions(-) rename Meshtastic/Views/Settings/{MapDataUpload.swift => MapDataFiles.swift} (98%) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index a17f29d1..02246fa1 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -60,7 +60,7 @@ 3D3417C82E29D38A006A988B /* GeoJSONOverlayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3417C72E29D38A006A988B /* GeoJSONOverlayConfig.swift */; }; 3D3417CB2E29D3B0006A988B /* Color+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3417C92E29D3B0006A988B /* Color+Hex.swift */; }; 3D3417D22E2DC260006A988B /* MapDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3417D12E2DC260006A988B /* MapDataManager.swift */; }; - 3D3417D42E2DC293006A988B /* MapDataUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3417D32E2DC293006A988B /* MapDataUpload.swift */; }; + 3D3417D42E2DC293006A988B /* MapDataFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3417D32E2DC293006A988B /* MapDataFiles.swift */; }; 6D825E622C34786C008DBEE4 /* CommonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D825E612C34786C008DBEE4 /* CommonRegex.swift */; }; 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; }; @@ -335,7 +335,7 @@ 3D3417C72E29D38A006A988B /* GeoJSONOverlayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJSONOverlayConfig.swift; sourceTree = ""; }; 3D3417C92E29D3B0006A988B /* Color+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Hex.swift"; sourceTree = ""; }; 3D3417D12E2DC260006A988B /* MapDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDataManager.swift; sourceTree = ""; }; - 3D3417D32E2DC293006A988B /* MapDataUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDataUpload.swift; sourceTree = ""; }; + 3D3417D32E2DC293006A988B /* MapDataFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDataFiles.swift; sourceTree = ""; }; 6D825E612C34786C008DBEE4 /* CommonRegex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonRegex.swift; sourceTree = ""; }; 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; @@ -797,7 +797,7 @@ DD4A911C2708C57100501B7E /* Settings */ = { isa = PBXGroup; children = ( - 3D3417D32E2DC293006A988B /* MapDataUpload.swift */, + 3D3417D32E2DC293006A988B /* MapDataManager.swift */, DDD5BB0E2C285F92007E03CA /* Logs */, DD93800C2BA74CE3008BEC06 /* Channels */, DD61937A2863876A00E59241 /* Config */, @@ -1453,7 +1453,7 @@ DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DDDB444C29F8AAA600EE2349 /* Color.swift in Sources */, DDDFE73F2D0D48FF0044463C /* IgnoreNodeButton.swift in Sources */, - 3D3417D42E2DC293006A988B /* MapDataUpload.swift in Sources */, + 3D3417D42E2DC293006A988B /* MapDataManager.swift in Sources */, DDB8F4122A9EE5DD00230ECE /* UserList.swift in Sources */, DDB75A0F2A05920E006ED576 /* FileManager.swift in Sources */, DD3D17E02C3FB67200561584 /* LocalWeatherConditions.swift in Sources */, diff --git a/Meshtastic/Helpers/MapDataManager.swift b/Meshtastic/Helpers/MapDataManager.swift index 0991224b..04609e7a 100644 --- a/Meshtastic/Helpers/MapDataManager.swift +++ b/Meshtastic/Helpers/MapDataManager.swift @@ -1,9 +1,10 @@ import Foundation import MapKit import OSLog +import Combine /// Manager for handling user-uploaded map data files -class MapDataManager { +class MapDataManager: ObservableObject { static let shared = MapDataManager() private init() {} @@ -14,7 +15,7 @@ class MapDataManager { private let metadataFileName = "upload_history.json" // MARK: - Properties - private var uploadedFiles: [MapDataMetadata] = [] + @Published private var uploadedFiles: [MapDataMetadata] = [] private var activeFeatureCollection: GeoJSONFeatureCollection? // MARK: - File Management @@ -90,13 +91,14 @@ class MapDataManager { // 5. Process and validate content let metadata = try await processFileContent(at: destURL, originalName: originalName) - // 6. Save metadata - uploadedFiles.append(metadata) + // 6. Save metadata and update UI on main thread + await MainActor.run { + uploadedFiles.append(metadata) + // Clear cached configuration to force reload + activeFeatureCollection = nil + } try saveMetadata() - // 7. Clear cached configuration to force reload - activeFeatureCollection = nil - return metadata } @@ -181,7 +183,7 @@ class MapDataManager { guard let fileURL = getUserUploadedDirectory()?.appendingPathComponent(file.filename) else { throw MapDataError.fileNotFound } - + let data = try Data(contentsOf: fileURL) return try JSONDecoder().decode(GeoJSONFeatureCollection.self, from: data) } @@ -193,9 +195,9 @@ class MapDataManager { guard !files.isEmpty else { return nil } - + var allFeatures: [GeoJSONFeature] = [] - + for file in files { do { if let featureCollection = try loadFeatureCollectionFromFile(file) { @@ -206,7 +208,7 @@ class MapDataManager { continue } } - + guard !allFeatures.isEmpty else { return nil } @@ -221,13 +223,13 @@ class MapDataManager { // Find active user files let activeFiles = uploadedFiles.filter { $0.isActive } - + guard !activeFiles.isEmpty else { return nil } var allFeatures: [GeoJSONFeature] = [] - + // Load features from all active files for activeFile in activeFiles { @@ -239,7 +241,7 @@ class MapDataManager { // Check if file exists before trying to load it if !FileManager.default.fileExists(atPath: fileURL.path) { Logger.services.error("📁 MapDataManager: Active file does not exist at path: \(fileURL.path, privacy: .public)") - + // Remove the missing file from our metadata if let index = uploadedFiles.firstIndex(where: { $0.filename == activeFile.filename }) { uploadedFiles.remove(at: index) @@ -261,13 +263,13 @@ class MapDataManager { Logger.services.error("📁 MapDataManager: Failed to load feature collection from \(activeFile.filename, privacy: .public): \(error.localizedDescription, privacy: .public)") } } - + // Create combined feature collection let combinedCollection = GeoJSONFeatureCollection( type: "FeatureCollection", features: allFeatures ) - + activeFeatureCollection = combinedCollection return combinedCollection } @@ -278,12 +280,12 @@ class MapDataManager { func getUploadedFiles() -> [MapDataMetadata] { return uploadedFiles } - + /// Toggle the active state of an uploaded file func toggleFileActive(_ fileId: UUID) { if let index = uploadedFiles.firstIndex(where: { $0.id == fileId }) { uploadedFiles[index].isActive.toggle() - + // Save metadata changes do { try saveMetadata() @@ -297,13 +299,13 @@ class MapDataManager { /// Delete uploaded file func deleteFile(_ metadata: MapDataMetadata) async throws { - + guard let fileURL = getUserUploadedDirectory()?.appendingPathComponent(metadata.filename) else { Logger.services.error("🗑️ MapDataManager: Could not construct file URL for: \(metadata.filename, privacy: .public)") throw MapDataError.fileNotFound } - + // Check if file exists before trying to delete if !FileManager.default.fileExists(atPath: fileURL.path) { Logger.services.warning("🗑️ MapDataManager: File does not exist at path: \(fileURL.path, privacy: .public)") @@ -316,10 +318,13 @@ class MapDataManager { throw error } - if let index = uploadedFiles.firstIndex(where: { $0.filename == metadata.filename }) { - uploadedFiles.remove(at: index) - } else { - Logger.services.warning("🗑️ MapDataManager: File not found in uploadedFiles array") + // Update UI-related properties on main thread + await MainActor.run { + if let index = uploadedFiles.firstIndex(where: { $0.filename == metadata.filename }) { + uploadedFiles.remove(at: index) + } else { + Logger.services.warning("🗑️ MapDataManager: File not found in uploadedFiles array") + } } do { @@ -330,13 +335,20 @@ class MapDataManager { } // Clear cache if this was the active file - if activeFeatureCollection != nil { - activeFeatureCollection = nil + await MainActor.run { + if activeFeatureCollection != nil { + activeFeatureCollection = nil + } } - + // Clear GeoJSON overlay manager cache GeoJSONOverlayManager.shared.clearCache() + // Notify UI components that a file was deleted + await MainActor.run { + NotificationCenter.default.post(name: Foundation.Notification.Name.mapDataFileDeleted, object: metadata.id) + } + } @@ -446,4 +458,9 @@ enum MapDataError: Error, LocalizedError { return "Failed to save file." } } +} + +// MARK: - Notification Names +extension Foundation.Notification.Name { + static let mapDataFileDeleted = Foundation.Notification.Name("mapDataFileDeleted") } \ No newline at end of file diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift index b735f07b..a1f8d36c 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift @@ -18,7 +18,7 @@ struct MapSettingsForm: View { @AppStorage("enableMapWaypoints") private var enableMapWaypoints = true @AppStorage("enableMapShowFavorites") private var enableMapShowFavorites = false @AppStorage("mapOverlaysEnabled") private var mapOverlaysEnabled = false - @State private var uploadedFiles: [MapDataMetadata] = [] + @ObservedObject private var mapDataManager = MapDataManager.shared @Binding var traffic: Bool @Binding var pointsOfInterest: Bool @Binding var mapLayer: MapLayer @@ -142,7 +142,7 @@ struct MapSettingsForm: View { // Show individual file toggles when overlays are enabled if mapOverlaysEnabled && hasUserData { - if !uploadedFiles.isEmpty { + if !mapDataManager.getUploadedFiles().isEmpty { // Data source info HStack { Image(systemName: "info.circle") @@ -155,7 +155,7 @@ struct MapSettingsForm: View { .padding(.leading, 35) // Individual file toggles - ForEach(uploadedFiles) { file in + ForEach(mapDataManager.getUploadedFiles()) { file in Toggle(isOn: Binding( get: { return enabledOverlayConfigs.contains(file.id) @@ -193,7 +193,7 @@ struct MapSettingsForm: View { } // Manage data link - NavigationLink(destination: MapDataUpload()) { + NavigationLink(destination: MapDataFiles()) { HStack { Image(systemName: "folder") .foregroundColor(.accentColor) @@ -221,7 +221,7 @@ struct MapSettingsForm: View { } } else if !hasUserData { // Upload prompt when no data available - NavigationLink(destination: MapDataUpload()) { + NavigationLink(destination: MapDataFiles()) { HStack { Image(systemName: "arrow.up.doc") .foregroundColor(.accentColor) @@ -257,8 +257,8 @@ Spacer() .presentationDragIndicator(.visible) .presentationBackgroundInteraction(.enabled(upThrough: .medium)) .onAppear { - // Load files on appear - uploadedFiles = GeoJSONOverlayManager.shared.getUploadedFilesWithState() + // Initialize map data manager + mapDataManager.initialize() } } diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 054dd7fb..9e7f642c 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -220,6 +220,11 @@ struct MeshMap: View { .onDisappear(perform: { UIApplication.shared.isIdleTimerDisabled = false }) + .onReceive(NotificationCenter.default.publisher(for: Foundation.Notification.Name.mapDataFileDeleted)) { notification in + if let deletedFileId = notification.object as? UUID { + enabledOverlayConfigs.remove(deletedFileId) + } + } } // moves the map to a new coordinate diff --git a/Meshtastic/Views/Settings/AppData.swift b/Meshtastic/Views/Settings/AppData.swift index 468a7ef2..4706b62e 100644 --- a/Meshtastic/Views/Settings/AppData.swift +++ b/Meshtastic/Views/Settings/AppData.swift @@ -28,7 +28,7 @@ struct AppData: View { // Map Data Section Section(header: Text("Map Data")) { - NavigationLink(destination: MapDataUpload()) { + NavigationLink(destination: MapDataFiles()) { HStack { Image(systemName: "map") .symbolRenderingMode(.hierarchical) diff --git a/Meshtastic/Views/Settings/MapDataUpload.swift b/Meshtastic/Views/Settings/MapDataFiles.swift similarity index 98% rename from Meshtastic/Views/Settings/MapDataUpload.swift rename to Meshtastic/Views/Settings/MapDataFiles.swift index b6166bc4..7e1bb686 100644 --- a/Meshtastic/Views/Settings/MapDataUpload.swift +++ b/Meshtastic/Views/Settings/MapDataFiles.swift @@ -2,9 +2,10 @@ import SwiftUI import UniformTypeIdentifiers import OSLog -struct MapDataUpload: View { +struct MapDataFiles: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @ObservedObject private var mapDataManager = MapDataManager.shared @State private var isShowingFilePicker = false @State private var isProcessing = false @@ -14,8 +15,6 @@ struct MapDataUpload: View { @State private var showSuccess = false @State private var successMessage = "" - private let mapDataManager = MapDataManager.shared - var body: some View { VStack(spacing: 20) { // Header @@ -251,6 +250,6 @@ struct MapDataFileRow: View { #Preview { NavigationView { - MapDataUpload() + MapDataFiles() } } \ No newline at end of file