diff options
| author | Fuwn <[email protected]> | 2025-09-22 02:22:32 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2025-09-22 02:22:32 -0700 |
| commit | 1d634eafc0c316e0a88d1e321a9025f1c944686a (patch) | |
| tree | 673eca151426d0db04ab4cbb3ff10f9ed2a9c8df /Sora/Views/Settings/Section/SettingsSectionImportExportView.swift | |
| parent | feat: Development commit (diff) | |
| download | sora-testing-1d634eafc0c316e0a88d1e321a9025f1c944686a.tar.xz sora-testing-1d634eafc0c316e0a88d1e321a9025f1c944686a.zip | |
feat: Development commit
Diffstat (limited to 'Sora/Views/Settings/Section/SettingsSectionImportExportView.swift')
| -rw-r--r-- | Sora/Views/Settings/Section/SettingsSectionImportExportView.swift | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift b/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift new file mode 100644 index 0000000..b74fe4b --- /dev/null +++ b/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift @@ -0,0 +1,131 @@ +import SwiftUI +import UniformTypeIdentifiers + +struct SettingsSectionImportExportView: View { + @EnvironmentObject private var settings: SettingsManager + @State private var isFileExporterPresented = false + @State private var isFileImporterPresented = false + @State private var exportError: Error? + @State private var importError: Error? + private let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd_HH-mm-ss" + return formatter + }() + + var body: some View { + Group { + Button("Import Bookmarks") { + isFileImporterPresented = true + } + + Button("Export Bookmarks") { + exportBookmarksToFile() + } + } + #if os(macOS) + .trailingFrame() + .fileExporter( + isPresented: $isFileExporterPresented, + document: try? JSONFileDocument(settings.exportBookmarks()), + contentType: .json, + defaultFilename: "sora_bookmarks.json" + ) { result in + switch result { + case .success: + break + + case .failure(let error): + exportError = error + } + } + #endif + .fileImporter( + isPresented: $isFileImporterPresented, + allowedContentTypes: [.json], + allowsMultipleSelection: false + ) { result in + handleImportResult(result) + } + .alert( + "Export Failed", + isPresented: Binding( + get: { exportError != nil }, + set: { if !$0 { exportError = nil } } + ) + ) { + Button("OK", role: .cancel) { () } + } message: { + Text(exportError?.localizedDescription ?? "An unknown error occurred while exporting.") + } + .alert( + "Import Failed", + isPresented: Binding( + get: { importError != nil }, + set: { if !$0 { importError = nil } } + ) + ) { + Button("OK", role: .cancel) { () } + } message: { + Text(importError?.localizedDescription ?? "An unknown error occurred while importing.") + } + } + + private func exportBookmarksToFile() { + do { + let data = try settings.exportBookmarks() + let timestamp = dateFormatter.string(from: Date()) + + #if os(macOS) + _ = data + isFileExporterPresented = true + #elseif os(iOS) + let temporaryURL = FileManager.default.temporaryDirectory + .appendingPathComponent("sora_bookmarks_\(timestamp).json") + + try data.write(to: temporaryURL) + + let activityController = UIActivityViewController( + activityItems: [temporaryURL], + applicationActivities: nil + ) + + if let windowScene = UIApplication.shared.connectedScenes + .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene, + let rootViewController = windowScene.windows.first?.rootViewController + { + activityController.popoverPresentationController?.sourceView = rootViewController.view + + rootViewController.present(activityController, animated: true) + } + + activityController.completionWithItemsHandler = { _, _, _, _ in + try? FileManager.default.removeItem(at: temporaryURL) + } + #endif + } catch { + exportError = error + } + } + + private func handleImportResult(_ result: Result<[URL], Error>) { + do { + guard let selectedFile = try result.get().first else { return } + guard selectedFile.startAccessingSecurityScopedResource() else { + throw ImportError.accessDenied + } + + defer { selectedFile.stopAccessingSecurityScopedResource() } + + let data = try Data(contentsOf: selectedFile) + + try settings.importBookmarks(from: data) + } catch { + importError = error + } + } + + private enum ImportError: Error { + case accessDenied + } +} |