Add export function

This commit is contained in:
Christoph Hagen
2025-01-31 13:06:11 +01:00
parent 740f776af6
commit 00e4da3f21
10 changed files with 189 additions and 13 deletions

View File

@ -8,6 +8,8 @@ protocol BluetoothDeviceDelegate: AnyObject {
actor BluetoothDevice: NSObject, ObservableObject {
private let storage: PersistentStorage!
let peripheral: CBPeripheral!
private let characteristic: CBCharacteristic!
@ -24,9 +26,10 @@ actor BluetoothDevice: NSObject, ObservableObject {
self.delegate = delegate
}
init(peripheral: CBPeripheral, characteristic: CBCharacteristic) {
init(storage: PersistentStorage, peripheral: CBPeripheral, characteristic: CBCharacteristic) {
self.peripheral = peripheral
self.characteristic = characteristic
self.storage = storage
super.init()
peripheral.delegate = self
@ -35,6 +38,7 @@ actor BluetoothDevice: NSObject, ObservableObject {
override init() {
self.peripheral = nil
self.characteristic = nil
self.storage = nil
super.init()
}
@ -46,7 +50,6 @@ actor BluetoothDevice: NSObject, ObservableObject {
}
lastDeviceInfo = info
delegate?.bluetoothDevice(didUpdate: info)
#warning("Don't use global variable")
storage.save(deviceInfo: info)
}

View File

@ -15,6 +15,8 @@ final class BluetoothScanner: NSObject, CBCentralManagerDelegate, ObservableObje
private let characteristicUUID = CBUUID(string: "22071991-cccc-cccc-cccc-000000000002")
let storage: PersistentStorage!
private var manager: CBCentralManager! = nil
@Published
@ -71,7 +73,8 @@ final class BluetoothScanner: NSObject, CBCentralManagerDelegate, ObservableObje
}
}
override init() {
init(storage: PersistentStorage) {
self.storage = storage
connectionState = .noDeviceFound
super.init()
self.manager = CBCentralManager(delegate: self, queue: nil)
@ -200,7 +203,7 @@ extension BluetoothScanner: CBPeripheralDelegate {
return
}
configuredDevice = .init(peripheral: peripheral, characteristic: desiredCharacteristic)
configuredDevice = .init(storage: storage, peripheral: peripheral, characteristic: desiredCharacteristic)
Task {
await configuredDevice?.set(delegate: self)
}

View File

@ -271,7 +271,7 @@ struct ContentView: View {
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let storage = PersistentStorage(lastMeasurements: TemperatureMeasurement.mockData)
ContentView(scanner: .init())
ContentView(scanner: .init(storage: storage))
.environmentObject(storage)
}
}

View File

@ -2,6 +2,7 @@ import Foundation
import Combine
import BinaryCodable
import SwiftUI
import ZIPFoundation
final class PersistentStorage: ObservableObject {
@ -353,6 +354,34 @@ final class PersistentStorage: ObservableObject {
defer { updateTransferCount() }
return save(data: data, date: date, in: "transfers")
}
// MARK: Export
private var zipArchive: URL {
FileManager.default.temporaryDirectory.appendingPathComponent("data.zip")
}
func createZip() throws -> URL {
// Define the destination zip file path
let archive = zipArchive
// Create the zip file
try FileManager.default.zipItem(
at: PersistentStorage.documentDirectory,
to: archive,
shouldKeepParent: false,
compressionMethod: .deflate)
return archive
}
func removeZipArchive() throws {
let archive = zipArchive
// Remove existing zip file if it exists
if FileManager.default.fileExists(atPath: archive.path) {
print("Removing archive file")
try FileManager.default.removeItem(at: archive)
}
}
}
private extension Array where Element == TemperatureMeasurement {

View File

@ -1,14 +1,25 @@
import SwiftUI
let storage = PersistentStorage()
private let scanner = BluetoothScanner()
private let transfer = TransferHandler()
@main
struct TempTrackApp: App {
@StateObject
private var storage: PersistentStorage
@StateObject
private var scanner: BluetoothScanner
@StateObject
private var transfer = TransferHandler()
init() {
let storage = PersistentStorage()
self._scanner = .init(wrappedValue: .init(storage: storage))
self._storage = .init(wrappedValue: storage)
}
var body: some Scene {
WindowGroup {
ContentView(scanner: scanner)

View File

@ -0,0 +1,81 @@
import SwiftUI
struct ExportSheet: View {
@Binding var isPresented: Bool
@State
private var isCreatingArchive = false
@State var url: URL?
@State var error: String?
@EnvironmentObject
private var storage: PersistentStorage
var body: some View {
VStack {
Text("Export")
.font(.title)
Text("Create a zip file of all stored data and share it.")
if isCreatingArchive {
ProgressView()
Text("Creating archive...")
}
if let error {
Text(error)
.foregroundStyle(.red)
}
Spacer()
Button("Create archive", action: createArchive)
.disabled(isCreatingArchive)
.padding()
if let url {
ShareLink(item: url) {
Label("Share archive", systemImage: "square.and.arrow.up")
}
.padding()
Button("Delete archive", action: deleteArchive)
.padding()
}
}
.padding()
}
private func createArchive() {
guard !isCreatingArchive else {
return
}
isCreatingArchive = true
DispatchQueue.main.async {
do {
let url = try storage.createZip()
DispatchQueue.main.async {
self.url = url
self.error = nil
self.isCreatingArchive = false
}
} catch {
DispatchQueue.main.async {
self.error = "Failed to create archive: \(error.localizedDescription)"
self.isCreatingArchive = false
}
}
}
}
private func deleteArchive() {
do {
try storage.removeZipArchive()
self.error = nil
self.url = nil
} catch {
self.error = "Failed to delete archive: \(error.localizedDescription)"
}
}
}
#Preview {
ExportSheet(isPresented: .constant(true))
}

View File

@ -1,10 +1,14 @@
import SwiftUI
import SFSafeSymbols
struct HistoryList: View {
@EnvironmentObject
var storage: PersistentStorage
@State
private var showExportSheet = false
var body: some View {
NavigationView {
List(storage.dailyMeasurementCounts) { day in
@ -25,6 +29,16 @@ struct HistoryList: View {
}
.navigationTitle("History")
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem {
Button(action: { showExportSheet = true }) {
Label("Export", systemSymbol: .arrowUpDoc)
}
}
}
.sheet(isPresented: $showExportSheet) {
ExportSheet(isPresented: $showExportSheet)
}
}
}