2023-07-02 17:29:39 +02:00
|
|
|
import SwiftUI
|
|
|
|
import SFSafeSymbols
|
|
|
|
|
|
|
|
struct TransferView: View {
|
|
|
|
|
|
|
|
private let storageWarnBytes = 500
|
|
|
|
|
|
|
|
|
|
|
|
let bluetoothClient: BluetoothDevice
|
|
|
|
|
2023-07-03 13:28:51 +02:00
|
|
|
@Binding
|
|
|
|
var info: DeviceInfo?
|
|
|
|
|
2023-07-02 17:29:39 +02:00
|
|
|
@EnvironmentObject
|
|
|
|
var storage: PersistentStorage
|
|
|
|
|
2023-07-03 13:28:51 +02:00
|
|
|
@EnvironmentObject
|
|
|
|
var transfer: TransferHandler
|
2023-07-02 17:29:39 +02:00
|
|
|
|
|
|
|
private var storageIcon: SFSymbol {
|
2023-07-03 13:28:51 +02:00
|
|
|
guard let info else {
|
2023-07-02 17:29:39 +02:00
|
|
|
return .externaldrive
|
|
|
|
}
|
|
|
|
if info.storageSize - info.numberOfRecordedBytes < storageWarnBytes {
|
|
|
|
return .externaldriveTrianglebadgeExclamationmark
|
|
|
|
}
|
|
|
|
return .externaldrive
|
|
|
|
}
|
|
|
|
|
|
|
|
private var measurementsText: String {
|
2023-07-03 13:28:51 +02:00
|
|
|
guard let info else {
|
2023-07-02 17:29:39 +02:00
|
|
|
return "No measurements"
|
|
|
|
}
|
|
|
|
return "\(info.numberOfStoredMeasurements) measurements (\(info.time.totalNumberOfMeasurements) total)"
|
|
|
|
}
|
|
|
|
|
|
|
|
private var storageText: String {
|
2023-07-03 13:28:51 +02:00
|
|
|
guard let info else {
|
2023-07-02 17:29:39 +02:00
|
|
|
return "No data"
|
|
|
|
}
|
|
|
|
if info.storageSize <= 0 {
|
|
|
|
return "\(info.numberOfRecordedBytes) Bytes"
|
|
|
|
}
|
|
|
|
return "\(info.numberOfRecordedBytes) / \(info.storageSize) Bytes (\(info.storageFillPercentage) %)"
|
|
|
|
}
|
|
|
|
|
|
|
|
private var transferSizeText: String {
|
2023-07-03 13:28:51 +02:00
|
|
|
guard let info else {
|
2023-07-02 17:29:39 +02:00
|
|
|
return "No transfer size"
|
|
|
|
}
|
|
|
|
return "\(info.transferBlockSize) Byte Block Size"
|
|
|
|
}
|
|
|
|
|
|
|
|
private var transferByteText: String {
|
2023-07-03 13:28:51 +02:00
|
|
|
let total = Int(transfer.totalBytes)
|
2023-07-02 17:29:39 +02:00
|
|
|
guard total > 0 else {
|
|
|
|
return "No data"
|
|
|
|
}
|
2023-07-03 13:28:51 +02:00
|
|
|
return "\(Int(transfer.bytesTransferred)) / \(total) Bytes"
|
2023-07-02 17:29:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private var transferMeasurementText: String {
|
2023-07-03 13:28:51 +02:00
|
|
|
guard !transfer.measurements.isEmpty else {
|
2023-07-02 17:29:39 +02:00
|
|
|
return "No measurements"
|
|
|
|
}
|
2023-07-03 13:28:51 +02:00
|
|
|
return "\(transfer.measurements.count) measurements"
|
2023-07-02 17:29:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
NavigationView {
|
|
|
|
VStack {
|
|
|
|
VStack(alignment: .leading, spacing: 5) {
|
|
|
|
Text("Storage")
|
|
|
|
.font(.headline)
|
|
|
|
IconAndTextView(
|
|
|
|
icon: .speedometer,
|
|
|
|
text: measurementsText)
|
|
|
|
IconAndTextView(
|
|
|
|
icon: storageIcon,
|
|
|
|
text: storageText)
|
|
|
|
IconAndTextView(
|
|
|
|
icon: .iphoneAndArrowForward,
|
|
|
|
text: transferSizeText)
|
|
|
|
}
|
|
|
|
|
|
|
|
Button(action: clearStorage) {
|
|
|
|
Text("Remove recorded data")
|
|
|
|
}
|
2023-07-03 13:28:51 +02:00
|
|
|
.disabled(transfer.transferIsRunning)
|
2023-07-02 17:29:39 +02:00
|
|
|
.padding()
|
|
|
|
|
|
|
|
VStack(alignment: .leading, spacing: 5) {
|
|
|
|
Text("Transfer")
|
|
|
|
.font(.headline)
|
2023-07-03 13:28:51 +02:00
|
|
|
ProgressView(value: transfer.bytesTransferred, total: transfer.totalBytes)
|
2023-07-02 17:29:39 +02:00
|
|
|
.progressViewStyle(.linear)
|
|
|
|
.padding(.vertical, 5)
|
|
|
|
IconAndTextView(
|
|
|
|
icon: .externaldrive,
|
|
|
|
text: transferByteText)
|
|
|
|
IconAndTextView(
|
|
|
|
icon: .speedometer,
|
|
|
|
text: transferMeasurementText)
|
|
|
|
}
|
|
|
|
HStack {
|
|
|
|
Button(action: transferData) {
|
|
|
|
Text("Transfer")
|
|
|
|
}
|
2023-07-03 13:28:51 +02:00
|
|
|
.disabled(transfer.transferIsRunning)
|
2023-07-02 17:29:39 +02:00
|
|
|
.padding()
|
|
|
|
Spacer()
|
|
|
|
Button(action: saveTransfer) {
|
|
|
|
Text("Save")
|
|
|
|
}
|
2023-07-03 13:28:51 +02:00
|
|
|
.disabled(transfer.transferIsRunning || transfer.measurements.isEmpty)
|
2023-07-02 17:29:39 +02:00
|
|
|
.padding()
|
|
|
|
Spacer()
|
|
|
|
Button(action: discardTransfer) {
|
|
|
|
Text("Discard")
|
|
|
|
}
|
2023-07-03 13:28:51 +02:00
|
|
|
.disabled(transfer.transferIsRunning || transfer.measurements.isEmpty)
|
2023-07-02 17:29:39 +02:00
|
|
|
.padding()
|
|
|
|
}
|
|
|
|
Spacer()
|
|
|
|
VStack {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.padding()
|
|
|
|
.navigationTitle("Data Transfer")
|
|
|
|
.navigationBarTitleDisplayMode(.large)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func transferData() {
|
2023-07-03 13:28:51 +02:00
|
|
|
guard let info else {
|
2023-07-02 17:29:39 +02:00
|
|
|
return
|
|
|
|
}
|
2023-07-03 13:28:51 +02:00
|
|
|
transfer.startTransfer(from: bluetoothClient, with: info, storage: storage)
|
2023-07-02 17:29:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func discardTransfer() {
|
2023-07-03 13:28:51 +02:00
|
|
|
transfer.discardTransfer()
|
2023-07-02 17:29:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func saveTransfer() {
|
2023-07-03 13:28:51 +02:00
|
|
|
transfer.saveTransfer(in: storage)
|
2023-07-02 17:29:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func clearStorage() {
|
2023-07-03 13:28:51 +02:00
|
|
|
guard let byteCount = info?.numberOfRecordedBytes else {
|
2023-07-02 17:29:39 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
Task {
|
|
|
|
guard await bluetoothClient.deleteDeviceData(byteCount: byteCount) else {
|
|
|
|
log.warning("Failed to delete data")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.warning("Device storage cleared")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct TransferView_Previews: PreviewProvider {
|
|
|
|
static var previews: some View {
|
|
|
|
let storage = PersistentStorage(lastMeasurements: TemperatureMeasurement.mockData)
|
2023-07-03 13:28:51 +02:00
|
|
|
TransferView(bluetoothClient: .init(), info: .constant(.mock))
|
2023-07-02 17:29:39 +02:00
|
|
|
.environmentObject(storage)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private extension TemperatureValue {
|
|
|
|
|
|
|
|
var relativeValue: Double {
|
|
|
|
if case .value(let double) = self {
|
|
|
|
return double
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}
|