From 8e1153585b75a4f5db48b06305e2f433f7b24d3b Mon Sep 17 00:00:00 2001 From: jess Date: Thu, 2 Apr 2026 21:16:29 -0700 Subject: [PATCH] wire TOML export/import to iOS session browser --- cue-ios/CueIOS/Views/SessionView.swift | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/cue-ios/CueIOS/Views/SessionView.swift b/cue-ios/CueIOS/Views/SessionView.swift index d858ed0..f108020 100644 --- a/cue-ios/CueIOS/Views/SessionView.swift +++ b/cue-ios/CueIOS/Views/SessionView.swift @@ -1,5 +1,6 @@ import SwiftUI import GRDB +import UniformTypeIdentifiers struct SessionView: View { @Bindable var state: AppState @@ -191,6 +192,9 @@ struct SessionDetailView: View { @State private var editing = false @State private var editLabel = "" @State private var editNotes = "" + @State private var showingFileImporter = false + @State private var showingShareSheet = false + @State private var exportFileURL: URL? var body: some View { VStack(alignment: .leading, spacing: 0) { @@ -201,6 +205,16 @@ struct SessionDetailView: View { .onAppear { loadMeasurements() } .onChange(of: session.id) { loadMeasurements() } .sheet(isPresented: $editing) { editSheet } + .sheet(isPresented: $showingShareSheet) { + if let url = exportFileURL { + ShareSheet(items: [url]) + } + } + .fileImporter( + isPresented: $showingFileImporter, + allowedContentTypes: [.plainText], + onCompletion: handleImportedFile + ) } private func loadMeasurements() { @@ -208,6 +222,40 @@ struct SessionDetailView: View { measurements = (try? Storage.shared.fetchMeasurements(sessionId: sid)) ?? [] } + private func exportSession() { + guard let sid = session.id else { return } + do { + let toml = try Storage.shared.exportSession(sid) + let name = (session.label ?? "session").replacingOccurrences(of: " ", with: "_") + let url = FileManager.default.temporaryDirectory.appendingPathComponent("\(name).toml") + try toml.write(to: url, atomically: true, encoding: .utf8) + exportFileURL = url + showingShareSheet = true + } catch { + state.status = "Export failed: \(error.localizedDescription)" + } + } + + private func handleImportedFile(_ result: Result) { + switch result { + case .success(let url): + guard url.startAccessingSecurityScopedResource() else { + state.status = "Cannot access file" + return + } + defer { url.stopAccessingSecurityScopedResource() } + do { + let toml = try String(contentsOf: url, encoding: .utf8) + let _ = try Storage.shared.importSession(from: toml) + state.status = "Session imported" + } catch { + state.status = "Import failed: \(error.localizedDescription)" + } + case .failure(let error): + state.status = "File error: \(error.localizedDescription)" + } + } + private var header: some View { VStack(alignment: .leading, spacing: 4) { HStack { @@ -222,6 +270,14 @@ struct SessionDetailView: View { Image(systemName: "pencil.circle") .imageScale(.large) } + Button(action: { exportSession() }) { + Image(systemName: "square.and.arrow.up") + .imageScale(.large) + } + Button(action: { showingFileImporter = true }) { + Image(systemName: "square.and.arrow.down") + .imageScale(.large) + } } HStack { Text(session.startedAt, style: .date) @@ -368,3 +424,11 @@ struct MeasurementRow: View { return (try? Storage.shared.dataPointCount(measurementId: mid)) ?? 0 } } + +struct ShareSheet: UIViewControllerRepresentable { + let items: [Any] + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: items, applicationActivities: nil) + } + func updateUIViewController(_ vc: UIActivityViewController, context: Context) {} +}