Add CSV export for days

This commit is contained in:
Christoph Hagen
2025-01-31 13:37:44 +01:00
parent 00e4da3f21
commit e9f6bafe33
6 changed files with 173 additions and 6 deletions

View File

@ -0,0 +1,40 @@
import Foundation
struct CsvConverter {
private let dateFormatter: DateFormatter
init() {
dateFormatter = .init()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .medium
}
private var header: String {
"Index;Date;Sensor 0;Sensor 1\n"
}
func convert(measurements: [TemperatureMeasurement]) -> String {
header + measurements.enumerated().map { (index, measurement) -> String in
let parts: [String] = [
"\(index)",
dateFormatter.string(from: measurement.date),
convert(measurement.sensor0),
convert(measurement.sensor1)
]
return parts.joined(separator: ";")
}.joined(separator: "\n")
}
private func convert(_ value: TemperatureValue) -> String {
switch value {
case .notFound:
return ""
case .invalidMeasurement:
return "?"
case .value(let value):
return String(format:"%.1f", value)
}
}
}

View File

@ -1,4 +1,5 @@
import SwiftUI
import SFSafeSymbols
private let df: DateFormatter = {
let df = DateFormatter()
@ -21,19 +22,45 @@ struct DayView: View {
@EnvironmentObject
var storage: PersistentStorage
@State
private var showShareSheet = false
private let title: String
init(dateIndex: Int) {
self.dateIndex = dateIndex
self.title = Date(dateIndex: dateIndex)
.formatted(date: .abbreviated, time: .omitted)
}
var entries: [TemperatureMeasurement] {
storage.loadMeasurements(for: dateIndex)
}
var body: some View {
TemperatureDayOverview(points: entries)
List(entries) { entry in
HStack {
Text(df.string(from: entry.date))
Spacer()
Text(entry.displayText)
VStack {
TemperatureDayOverview(points: entries)
List(entries) { entry in
HStack {
Text(entry.date.formatted(date: .omitted, time: .standard))
Spacer()
Text(entry.displayText)
}
}
}
.navigationTitle(title)
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem {
Button(action: { showShareSheet = true }) {
Label("Export", systemSymbol: .arrowUpDoc)
}
}
}
.sheet(isPresented: $showShareSheet) {
ShareCsvSheet(isPresented: $showShareSheet,
measurements: entries)
}
}
}

View File

@ -39,6 +39,7 @@ struct ExportSheet: View {
Button("Delete archive", action: deleteArchive)
.padding()
}
Button("Dismiss", action: { isPresented = false })
}
.padding()
}

View File

@ -0,0 +1,91 @@
import SwiftUI
struct ShareCsvSheet: View {
@Binding var isPresented: Bool
@State
private var isCreatingFile = false
@State var url: URL?
@State var error: String?
@EnvironmentObject
private var storage: PersistentStorage
let measurements: [TemperatureMeasurement]
var body: some View {
VStack {
Text("Export")
.font(.title)
Text("Create a CSV file of all measurements for the day")
if isCreatingFile {
ProgressView()
Text("Creating file...")
}
if let error {
Text(error)
.foregroundStyle(.red)
}
Spacer()
Button("Create file", action: createFile)
.disabled(isCreatingFile)
.padding()
if let url {
ShareLink(item: url) {
Label("Share file", systemImage: "square.and.arrow.up")
}
.padding()
Button("Delete file", action: deleteFile)
.padding()
}
Button("Dismiss", action: { isPresented = false })
}
.padding()
}
private func createFile() {
guard !isCreatingFile else {
return
}
isCreatingFile = true
DispatchQueue.main.async {
do {
let url = FileManager.default.temporaryDirectory.appendingPathComponent("export.csv")
let converter = CsvConverter()
let content = converter.convert(measurements: measurements)
try content.write(to: url, atomically: true, encoding: .utf8)
DispatchQueue.main.async {
self.url = url
self.error = nil
self.isCreatingFile = false
}
} catch {
DispatchQueue.main.async {
self.error = "Failed to save file: \(error.localizedDescription)"
self.isCreatingFile = false
}
}
}
}
private func deleteFile() {
guard let url else {
return
}
do {
try FileManager.default.removeItem(at: url)
self.error = nil
self.url = nil
} catch {
self.error = "Failed to delete archive: \(error.localizedDescription)"
}
}
}
#Preview {
ShareCsvSheet(isPresented: .constant(true),
measurements: [])
}