diff --git a/Localizable.xcstrings b/Localizable.xcstrings index ad47c8c8..d9937814 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -24326,6 +24326,9 @@ } } } + }, + "Ok" : { + }, "OK" : { "localizations" : { @@ -31585,6 +31588,7 @@ }, "Select Map Data File" : { "comment" : "Button text for selecting map data file", + "extractionState" : "stale", "localizations" : { "it" : { "stringUnit" : { @@ -31617,6 +31621,9 @@ } } } + }, + "Select Map File" : { + }, "Select Node" : { "localizations" : { @@ -39770,6 +39777,9 @@ } } } + }, + "Upload Map Overlays" : { + }, "Upload Success" : { "localizations" : { @@ -39805,40 +39815,8 @@ } } }, - "Uploaded Files" : { - "comment" : "Section header for uploaded files", - "localizations" : { - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "File Caricati" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "アップロードされたファイル" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Otpremljene Datoteke" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "已上传文件" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "已上傳檔案" - } - } - } + "Uploaded Map Overlays" : { + }, "Uptime" : { "localizations" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d41c74af..59b93928 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -796,7 +796,6 @@ DD4A911C2708C57100501B7E /* Settings */ = { isa = PBXGroup; children = ( - 3D3417D32E2DC293006A988B /* MapDataFiles.swift */, DDD5BB0E2C285F92007E03CA /* Logs */, DD93800C2BA74CE3008BEC06 /* Channels */, DD61937A2863876A00E59241 /* Config */, @@ -804,16 +803,17 @@ DDD5BB152C28B1E4007E03CA /* AppData.swift */, DDD5BB082C285DDC007E03CA /* AppLog.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, - DDAB580C2B0DAA9E00147258 /* Routes.swift */, - DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */, DDA0B6B1294CDC55001356EC /* Channels.swift */, DDD6EEAE29BC024700383354 /* Firmware.swift */, DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */, + DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */, + 3D3417D32E2DC293006A988B /* MapDataFiles.swift */, + DDAB580C2B0DAA9E00147258 /* Routes.swift */, + DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DD3501882852FC3B000FC853 /* Settings.swift */, DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, - DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */, ); path = Settings; sourceTree = ""; diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift index 6d40f47e..ef80098c 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapSettingsForm.swift @@ -120,7 +120,7 @@ struct MapSettingsForm: View { } } - Section(header: Text("Map Overlays")) { + Section(header: Text("Map Overlays")) { let hasUserData = GeoJSONOverlayManager.shared.hasUserData() // Master toggle for map overlays diff --git a/Meshtastic/Views/Settings/MapDataFiles.swift b/Meshtastic/Views/Settings/MapDataFiles.swift index 0dbc204d..beff897c 100644 --- a/Meshtastic/Views/Settings/MapDataFiles.swift +++ b/Meshtastic/Views/Settings/MapDataFiles.swift @@ -6,7 +6,7 @@ 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 @State private var processingProgress: Double = 0.0 @@ -14,76 +14,63 @@ struct MapDataFiles: View { @State private var errorMessage = "" @State private var showSuccess = false @State private var successMessage = "" - + var body: some View { - VStack(spacing: 20) { - // Header - VStack(alignment: .leading, spacing: 8) { - Text(NSLocalizedString("Upload Map Data", comment: "Title for map data upload screen")) - .font(.title2) - .fontWeight(.bold) - + Form { + Section(header: Text("Upload Map Overlays")) { Text("Upload GeoJSON files to display custom map overlays. Files are stored locally and can be up to 10MB.") - .font(.caption) + .font(.callout) .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal) - - // Upload Button - Button(action: { - isShowingFilePicker = true - }) { - HStack { - Image(systemName: "doc.badge.plus") - .font(.title2) - Text(NSLocalizedString("Select Map Data File", comment: "Button text for selecting map data file")) - .fontWeight(.medium) + // Upload Button + Button(action: { + isShowingFilePicker = true + }) { + HStack { + Image(systemName: "doc.badge.plus") + .font(.title2) + Text("Select Map File") + .fontWeight(.medium) + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.accentColor) + .foregroundColor(.white) + .cornerRadius(10) } - .frame(maxWidth: .infinity) - .padding() - .background(Color.accentColor) - .foregroundColor(.white) - .cornerRadius(10) - } - .disabled(isProcessing) - .padding(.horizontal) - - // Processing Indicator - if isProcessing { - VStack(spacing: 12) { - ProgressView(value: processingProgress) - .progressViewStyle(LinearProgressViewStyle()) - .padding(.horizontal) - - Text("Processing file...") - .font(.caption) - .foregroundColor(.secondary) + .disabled(isProcessing) + .padding(.horizontal) + + // Processing Indicator + if isProcessing { + VStack(spacing: 12) { + ProgressView(value: processingProgress) + .progressViewStyle(LinearProgressViewStyle()) + .padding(.horizontal) + + Text("Processing file...") + .font(.caption) + .foregroundColor(.secondary) + } } } - - // Current Files Section - VStack(alignment: .leading, spacing: 12) { - Text(NSLocalizedString("Uploaded Files", comment: "Section header for uploaded files")) - .font(.headline) - .padding(.horizontal) - + Section(header: Text("Uploaded Map Overlays")) { + let uploadedFiles = mapDataManager.getUploadedFiles() - + if uploadedFiles.isEmpty { VStack(spacing: 8) { Image(systemName: "doc.text") .font(.title) .foregroundColor(.secondary) - Text(NSLocalizedString("No files uploaded yet", comment: "Empty state text when no files are uploaded")) + Text("No files uploaded yet") .font(.caption) .foregroundColor(.secondary) } .frame(maxWidth: .infinity) - .padding(.vertical, 40) + //.padding(.vertical, 40) } else { ScrollView { - LazyVStack(spacing: 8) { + LazyVStack() { ForEach(uploadedFiles) { file in MapDataFileRow(file: file) { deleteFile(file) @@ -94,11 +81,7 @@ struct MapDataFiles: View { } } } - - Spacer() } - .navigationTitle("Map Data") - .navigationBarTitleDisplayMode(.inline) .fileImporter( isPresented: $isShowingFilePicker, allowedContentTypes: [ @@ -110,12 +93,12 @@ struct MapDataFiles: View { handleFileSelection(result) } .alert("Upload Error", isPresented: $showError) { - Button("OK") { } + Button("Ok") { } } message: { Text(errorMessage) } .alert("Upload Success", isPresented: $showSuccess) { - Button("OK") { } + Button("Ok") { } } message: { Text(successMessage) } @@ -124,48 +107,48 @@ struct MapDataFiles: View { mapDataManager.initialize() } } - + // MARK: - File Handling - + private func handleFileSelection(_ result: Result<[URL], Error>) { do { guard let selectedFile = try result.get().first else { return } - + // Start processing isProcessing = true processingProgress = 0.0 - + // Process file asynchronously Task { do { // Simulate progress await simulateProgress() - + let metadata = try await mapDataManager.processUploadedFile(from: selectedFile) - + await MainActor.run { isProcessing = false processingProgress = 1.0 - - successMessage = "Successfully uploaded '\(metadata.originalName)' with \(metadata.overlayCount) overlays" + + successMessage = "Successfully uploaded '\(metadata.originalName)' with \(metadata.overlayCount) overlays".localized showSuccess = true } } catch { await MainActor.run { isProcessing = false processingProgress = 0.0 - + errorMessage = error.localizedDescription showError = true } } } } catch { - errorMessage = "Failed to access file: \(error.localizedDescription)" + errorMessage = "Failed to access file: \(error.localizedDescription)".localized showError = true } } - + private func simulateProgress() async { for i in 1...10 { await MainActor.run { @@ -174,14 +157,14 @@ struct MapDataFiles: View { try? await Task.sleep(nanoseconds: 200_000_000) // 0.2 seconds } } - + private func deleteFile(_ file: MapDataMetadata) { Task { do { try await mapDataManager.deleteFile(file) } catch { await MainActor.run { - errorMessage = "Failed to delete file: \(error.localizedDescription)" + errorMessage = "Failed to delete file: \(error.localizedDescription)".localized showError = true } } @@ -194,56 +177,52 @@ struct MapDataFiles: View { struct MapDataFileRow: View { let file: MapDataMetadata let onDelete: () -> Void - + var body: some View { HStack { - VStack(alignment: .leading, spacing: 4) { + VStack { HStack { Text(file.originalName) .font(.headline) .lineLimit(1) - + Spacer() + Button(action: onDelete) { + Image(systemName: "trash") + .foregroundColor(.red) + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.small) } - HStack { Text(file.format.uppercased()) - .font(.caption) + .font(.caption2) + .fixedSize() .padding(.horizontal, 8) .padding(.vertical, 2) .background(Color.secondary.opacity(0.2)) .cornerRadius(4) - + Text(file.fileSizeString) .font(.caption) .foregroundColor(.secondary) - + Text("•") .font(.caption) .foregroundColor(.secondary) - + Text("\(file.overlayCount) overlays") .font(.caption) .foregroundColor(.secondary) - Spacer() - Text(file.uploadDateString) .font(.caption) .foregroundColor(.secondary) } } - - Button(action: onDelete) { - Image(systemName: "trash") - .foregroundColor(.red) - } - .buttonStyle(BorderlessButtonStyle()) } - .padding() - .background(Color(.systemBackground)) - .cornerRadius(8) - .shadow(color: .black.opacity(0.1), radius: 2, x: 0, y: 1) + Divider() } }