wire TOML export/import to iOS session browser
This commit is contained in:
parent
311fb8ecc7
commit
8e1153585b
|
|
@ -1,5 +1,6 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import GRDB
|
import GRDB
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
struct SessionView: View {
|
struct SessionView: View {
|
||||||
@Bindable var state: AppState
|
@Bindable var state: AppState
|
||||||
|
|
@ -191,6 +192,9 @@ struct SessionDetailView: View {
|
||||||
@State private var editing = false
|
@State private var editing = false
|
||||||
@State private var editLabel = ""
|
@State private var editLabel = ""
|
||||||
@State private var editNotes = ""
|
@State private var editNotes = ""
|
||||||
|
@State private var showingFileImporter = false
|
||||||
|
@State private var showingShareSheet = false
|
||||||
|
@State private var exportFileURL: URL?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
|
@ -201,6 +205,16 @@ struct SessionDetailView: View {
|
||||||
.onAppear { loadMeasurements() }
|
.onAppear { loadMeasurements() }
|
||||||
.onChange(of: session.id) { loadMeasurements() }
|
.onChange(of: session.id) { loadMeasurements() }
|
||||||
.sheet(isPresented: $editing) { editSheet }
|
.sheet(isPresented: $editing) { editSheet }
|
||||||
|
.sheet(isPresented: $showingShareSheet) {
|
||||||
|
if let url = exportFileURL {
|
||||||
|
ShareSheet(items: [url])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fileImporter(
|
||||||
|
isPresented: $showingFileImporter,
|
||||||
|
allowedContentTypes: [.plainText],
|
||||||
|
onCompletion: handleImportedFile
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadMeasurements() {
|
private func loadMeasurements() {
|
||||||
|
|
@ -208,6 +222,40 @@ struct SessionDetailView: View {
|
||||||
measurements = (try? Storage.shared.fetchMeasurements(sessionId: sid)) ?? []
|
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<URL, Error>) {
|
||||||
|
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 {
|
private var header: some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -222,6 +270,14 @@ struct SessionDetailView: View {
|
||||||
Image(systemName: "pencil.circle")
|
Image(systemName: "pencil.circle")
|
||||||
.imageScale(.large)
|
.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 {
|
HStack {
|
||||||
Text(session.startedAt, style: .date)
|
Text(session.startedAt, style: .date)
|
||||||
|
|
@ -368,3 +424,11 @@ struct MeasurementRow: View {
|
||||||
return (try? Storage.shared.dataPointCount(measurementId: mid)) ?? 0
|
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) {}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue