From f731927dcdb410c96df252be2bfe1d1b327c8fa2 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Sun, 11 Jun 2023 21:57:07 +0200 Subject: [PATCH] Refactor globals --- TempTrack.xcodeproj/project.pbxproj | 38 ---------- .../xcshareddata/swiftpm/Package.resolved | 22 +----- .../UserInterfaceState.xcuserstate | Bin 41667 -> 44975 bytes TempTrack/Bluetooth/BluetoothClient.swift | 45 ++++++++---- TempTrack/ContentView.swift | 7 +- TempTrack/Storage/TemperatureStorage.swift | 68 +++++++----------- TempTrack/TempTrackApp.swift | 9 ++- .../Temperature/TemperatureDataTransfer.swift | 12 ++-- .../TemperatureDataTransferDelegate.swift | 8 --- .../Temperature/TemperatureMeasurement.swift | 7 +- 10 files changed, 77 insertions(+), 139 deletions(-) delete mode 100644 TempTrack/Temperature/TemperatureDataTransferDelegate.swift diff --git a/TempTrack.xcodeproj/project.pbxproj b/TempTrack.xcodeproj/project.pbxproj index d4cd673..068de4f 100644 --- a/TempTrack.xcodeproj/project.pbxproj +++ b/TempTrack.xcodeproj/project.pbxproj @@ -28,10 +28,8 @@ 88CDE0632A253AD900114294 /* TemperatureDataTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0622A253AD900114294 /* TemperatureDataTransfer.swift */; }; 88CDE0662A25D08F00114294 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = 88CDE0652A25D08F00114294 /* SFSafeSymbols */; }; 88CDE0682A2698B400114294 /* TemperatureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0672A2698B400114294 /* TemperatureStorage.swift */; }; - 88CDE06B2A2899C900114294 /* BottomSheet in Frameworks */ = {isa = PBXBuildFile; productRef = 88CDE06A2A2899C900114294 /* BottomSheet */; }; 88CDE06D2A28A92000114294 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE06C2A28A92000114294 /* DeviceInfo.swift */; }; 88CDE0702A28AEA300114294 /* TemperatureMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE06F2A28AEA300114294 /* TemperatureMeasurement.swift */; }; - 88CDE0722A28AEB900114294 /* TemperatureDataTransferDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0712A28AEB900114294 /* TemperatureDataTransferDelegate.swift */; }; 88CDE0742A28AEE500114294 /* DeviceManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0732A28AEE500114294 /* DeviceManagerDelegate.swift */; }; 88CDE0762A28AF0900114294 /* TemperatureValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0752A28AF0900114294 /* TemperatureValue.swift */; }; 88CDE0782A28AF2C00114294 /* TemperatureSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0772A28AF2C00114294 /* TemperatureSensor.swift */; }; @@ -39,7 +37,6 @@ 88CDE07E2A28AFF400114294 /* DeviceInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE07D2A28AFF400114294 /* DeviceInfoView.swift */; }; E253A9222A2B39B700EC6B28 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E253A9212A2B39B700EC6B28 /* Color+Extensions.swift */; }; E253A9242A2B462500EC6B28 /* TemperatureHistoryChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = E253A9232A2B462500EC6B28 /* TemperatureHistoryChart.swift */; }; - E253A9272A2CA48A00EC6B28 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = E253A9262A2CA48A00EC6B28 /* SQLite */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -65,7 +62,6 @@ 88CDE0672A2698B400114294 /* TemperatureStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureStorage.swift; sourceTree = ""; }; 88CDE06C2A28A92000114294 /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = ""; }; 88CDE06F2A28AEA300114294 /* TemperatureMeasurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureMeasurement.swift; sourceTree = ""; }; - 88CDE0712A28AEB900114294 /* TemperatureDataTransferDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureDataTransferDelegate.swift; sourceTree = ""; }; 88CDE0732A28AEE500114294 /* DeviceManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceManagerDelegate.swift; sourceTree = ""; }; 88CDE0752A28AF0900114294 /* TemperatureValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureValue.swift; sourceTree = ""; }; 88CDE0772A28AF2C00114294 /* TemperatureSensor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureSensor.swift; sourceTree = ""; }; @@ -81,9 +77,7 @@ buildActionMask = 2147483647; files = ( 88404DD02A2E718B00D30244 /* BinaryCodable in Frameworks */, - E253A9272A2CA48A00EC6B28 /* SQLite in Frameworks */, 88CDE0662A25D08F00114294 /* SFSafeSymbols in Frameworks */, - 88CDE06B2A2899C900114294 /* BottomSheet in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -143,7 +137,6 @@ isa = PBXGroup; children = ( 88CDE06F2A28AEA300114294 /* TemperatureMeasurement.swift */, - 88CDE0712A28AEB900114294 /* TemperatureDataTransferDelegate.swift */, 88CDE0622A253AD900114294 /* TemperatureDataTransfer.swift */, 88CDE0752A28AF0900114294 /* TemperatureValue.swift */, 88CDE0772A28AF2C00114294 /* TemperatureSensor.swift */, @@ -208,8 +201,6 @@ name = TempTrack; packageProductDependencies = ( 88CDE0652A25D08F00114294 /* SFSafeSymbols */, - 88CDE06A2A2899C900114294 /* BottomSheet */, - E253A9262A2CA48A00EC6B28 /* SQLite */, 88404DCF2A2E718B00D30244 /* BinaryCodable */, ); productName = TempTrack; @@ -242,8 +233,6 @@ mainGroup = 88CDE0422A2508E800114294; packageReferences = ( 88CDE0642A25D08F00114294 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */, - 88CDE0692A2899C900114294 /* XCRemoteSwiftPackageReference "bottom-sheet" */, - E253A9252A2CA48900EC6B28 /* XCRemoteSwiftPackageReference "SQLite" */, 88404DCE2A2E718B00D30244 /* XCRemoteSwiftPackageReference "BinaryCodable" */, ); productRefGroup = 88CDE04C2A2508E900114294 /* Products */; @@ -281,7 +270,6 @@ 88CDE05D2A250F3C00114294 /* DeviceManager.swift in Sources */, 88404DDF2A2F68E100D30244 /* TemperatureDayOverview.swift in Sources */, 88CDE04F2A2508E900114294 /* TempTrackApp.swift in Sources */, - 88CDE0722A28AEB900114294 /* TemperatureDataTransferDelegate.swift in Sources */, 88CDE0782A28AF2C00114294 /* TemperatureSensor.swift in Sources */, 88CDE0682A2698B400114294 /* TemperatureStorage.swift in Sources */, 88404DE92A31F7D500D30244 /* Data+Extensions.swift in Sources */, @@ -521,22 +509,6 @@ minimumVersion = 4.0.0; }; }; - 88CDE0692A2899C900114294 /* XCRemoteSwiftPackageReference "bottom-sheet" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/weitieda/bottom-sheet"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; - E253A9252A2CA48900EC6B28 /* XCRemoteSwiftPackageReference "SQLite" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/stephencelis/SQLite.swift"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.14.1; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -550,16 +522,6 @@ package = 88CDE0642A25D08F00114294 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; productName = SFSafeSymbols; }; - 88CDE06A2A2899C900114294 /* BottomSheet */ = { - isa = XCSwiftPackageProductDependency; - package = 88CDE0692A2899C900114294 /* XCRemoteSwiftPackageReference "bottom-sheet" */; - productName = BottomSheet; - }; - E253A9262A2CA48A00EC6B28 /* SQLite */ = { - isa = XCSwiftPackageProductDependency; - package = E253A9252A2CA48900EC6B28 /* XCRemoteSwiftPackageReference "SQLite" */; - productName = SQLite; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 88CDE0432A2508E800114294 /* Project object */; diff --git a/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5dfd079..b4fdc68 100644 --- a/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,31 +9,13 @@ "version" : "2.0.0" } }, - { - "identity" : "bottom-sheet", - "kind" : "remoteSourceControl", - "location" : "https://github.com/weitieda/bottom-sheet", - "state" : { - "revision" : "6b21007153365235418f3943a960a1f9546592e0", - "version" : "1.0.12" - } - }, { "identity" : "sfsafesymbols", "kind" : "remoteSourceControl", "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols", "state" : { - "revision" : "2bcd249b49178247e6b52bac7d67d6e338a40cee", - "version" : "4.1.0" - } - }, - { - "identity" : "sqlite.swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/stephencelis/SQLite.swift", - "state" : { - "revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb", - "version" : "0.14.1" + "revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c", + "version" : "4.1.1" } } ], diff --git a/TempTrack.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate b/TempTrack.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate index 4945cb5e6de3975f1ff2c62798a3cf3a00da8842..0e9a75dccde2c891461d062d1c493c2bc4ff6659 100644 GIT binary patch delta 22356 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 Qq3*K}rTgrChh0nlKR*)%Z2$lO diff --git a/TempTrack/Bluetooth/BluetoothClient.swift b/TempTrack/Bluetooth/BluetoothClient.swift index 63958cc..d023516 100644 --- a/TempTrack/Bluetooth/BluetoothClient.swift +++ b/TempTrack/Bluetooth/BluetoothClient.swift @@ -3,15 +3,18 @@ import SwiftUI final class BluetoothClient: ObservableObject { - weak var delegate: TemperatureDataTransferDelegate? - private let updateInterval = 3.0 + + private let minimumOffsetToUpdateDeviceClock = 5.0 private let connection = DeviceManager() + + private let storage: TemperatureStorage - init(deviceInfo: DeviceInfo? = nil) { - connection.delegate = self + init(storage: TemperatureStorage, deviceInfo: DeviceInfo? = nil) { + self.storage = storage self.deviceInfo = deviceInfo + connection.delegate = self } func connect() -> Bool { @@ -111,16 +114,17 @@ final class BluetoothClient: ObservableObject { // MARK: Device time private func updateDeviceTimeIfNeeded() { - guard let info = deviceInfo else { + guard let deviceInfo else { return } - guard !info.hasDeviceStartTimeSet else { + guard !deviceInfo.hasDeviceStartTimeSet || deviceInfo.clockOffset > minimumOffsetToUpdateDeviceClock else { return } + guard !openRequests.contains(where: { if case .setDeviceStartTime = $0 { return true }; return false }) else { return } - let time = info.deviceStartTime.seconds + let time = deviceInfo.deviceStartTime.seconds addRequest(.setDeviceStartTime(deviceStartTimeSeconds: time)) print("Setting device start time to \(time) s (\(Date().seconds) current)") } @@ -144,7 +148,6 @@ final class BluetoothClient: ObservableObject { let transfer = TemperatureDataTransfer(info: info) runningTransfer = transfer - runningTransfer?.delegate = delegate let next = transfer.nextRequest() addRequest(next) return true @@ -171,12 +174,13 @@ final class BluetoothClient: ObservableObject { return } self.deviceInfo = newInfo - guard let runningTransfer else { - return + if let runningTransfer { + runningTransfer.update(info: newInfo) + let next = runningTransfer.nextRequest() + addRequest(next) + } else if newInfo.numberOfStoredMeasurements > 0 { + collectRecordedData() } - runningTransfer.update(info: newInfo) - let next = runningTransfer.nextRequest() - addRequest(next) } } @@ -226,13 +230,24 @@ extension BluetoothClient: DeviceManagerDelegate { case .getRecordingData(let offset, let count): didReceive(data: payload, offset: offset, count: count) case .clearRecordingBuffer: - runningTransfer?.completeTransfer() - runningTransfer = nil + didClearDeviceStorage() + case .setDeviceStartTime: print("Device time set") break } } + + private func didClearDeviceStorage() { + guard let runningTransfer else { + return + } + runningTransfer.completeTransfer() + storage.add(runningTransfer.measurements) + self.runningTransfer = nil + + updateDeviceTimeIfNeeded() + } func deviceManager(didChangeState state: DeviceState) { DispatchQueue.main.async { diff --git a/TempTrack/ContentView.swift b/TempTrack/ContentView.swift index 8ae2455..d7a4501 100644 --- a/TempTrack/ContentView.swift +++ b/TempTrack/ContentView.swift @@ -1,6 +1,5 @@ import SwiftUI import SFSafeSymbols -import BottomSheet struct ContentView: View { @@ -144,9 +143,11 @@ struct ContentView: View { struct ContentView_Previews: PreviewProvider { static var previews: some View { + let storage = TemperatureStorage(lastMeasurements: TemperatureMeasurement.mockData) + let client = BluetoothClient(storage: storage, deviceInfo: .mock) ContentView() - .environmentObject(TemperatureStorage(lastMeasurements: TemperatureMeasurement.mockData)) - .environmentObject(BluetoothClient(deviceInfo: .mock)) + .environmentObject(storage) + .environmentObject(client) } } diff --git a/TempTrack/Storage/TemperatureStorage.swift b/TempTrack/Storage/TemperatureStorage.swift index 694329d..3913c38 100644 --- a/TempTrack/Storage/TemperatureStorage.swift +++ b/TempTrack/Storage/TemperatureStorage.swift @@ -11,10 +11,15 @@ final class TemperatureStorage: ObservableObject { in: .userDomainMask, appropriateFor: nil, create: true) } - + @AppStorage("newestDate") private var newestMeasurementTime: Int = 0 - + + /** + The date of the latest measurement. + + Incoming data older than this date will be rejected to prevent duplicate measurements + */ private var newestMeasurementDate: Date { get { Date(seconds: newestMeasurementTime) @@ -30,8 +35,6 @@ final class TemperatureStorage: ObservableObject { @Published var dailyMeasurementCounts: [MeasurementDailyCount] = [] - private var unsavedMeasurements: [TemperatureMeasurement] = [] - private let fileNameFormatter: DateFormatter private let storageFolder: URL @@ -98,6 +101,12 @@ final class TemperatureStorage: ObservableObject { .filter { $0.date >= startDate } recentMeasurements = yesterdayValues + todayValues } + + private func updateLastMeasurements(_ measurements: [TemperatureMeasurement]) { + let startDate = Date().addingTimeInterval(-lastValueInterval).seconds + let new = recentMeasurements + measurements + recentMeasurements = Array(new.drop { $0.id < startDate }) + } private func loadMeasurements(for date: Date) -> [TemperatureMeasurement] { loadMeasurements(from: fileName(for: date)) @@ -123,14 +132,20 @@ final class TemperatureStorage: ObservableObject { return [] } } - - func save() { - for (dateIndex, values) in unsavedMeasurements.splitByDate() { + + func add(_ measurements: [TemperatureMeasurement]) { + let lastDate = self.newestMeasurementDate.seconds + let newValues = measurements + .filter { $0.id > lastDate } + .splitByDate() + + for (dateIndex, values) in newValues { let count = saveNew(values, for: dateIndex) - print("Day \(dateIndex): \(count) of \(values.count) saved") + setDailyCount(count, for: dateIndex) + print("Day \(dateIndex): \(count) values") } - unsavedMeasurements = [] saveDailyCounts() + updateLastMeasurements(measurements) } /** @@ -138,21 +153,9 @@ final class TemperatureStorage: ObservableObject { */ private func saveNew(_ measurements: [TemperatureMeasurement], for dateIndex: Int) -> Int { let fileName = fileName(for: dateIndex) - var existing = loadMeasurements(from: fileName) - guard !existing.isEmpty else { - save(measurements, for: fileName) - setDailyCount(measurements.count, for: dateIndex) - return measurements.count - } - var inserted = 0 - for value in measurements { - if existing.insert(value) { - inserted += 1 - } - } - save(existing, for: fileName) - setDailyCount(existing.count, for: dateIndex) - return inserted + let values = loadMeasurements(from: fileName) + measurements + save(values, for: fileName) + return values.count } private func save(_ measurements: [TemperatureMeasurement], for fileName: String) { @@ -239,23 +242,6 @@ final class TemperatureStorage: ObservableObject { } -extension TemperatureStorage: TemperatureDataTransferDelegate { - - func didReceiveRecording(_ measurement: TemperatureMeasurement) { - // Add to unsaved measurements - if unsavedMeasurements.insert(measurement) { - incrementCount(for: measurement.date.dateIndex) - } - - // Add to last measurements - recentMeasurements.insert(measurement) - } - - func saveAfterTransfer() { - save() - } -} - private extension Array where Element == TemperatureMeasurement { @discardableResult diff --git a/TempTrack/TempTrackApp.swift b/TempTrack/TempTrackApp.swift index c598434..f56400d 100644 --- a/TempTrack/TempTrackApp.swift +++ b/TempTrack/TempTrackApp.swift @@ -1,7 +1,9 @@ import SwiftUI -let storage = TemperatureStorage() -let bluetoothClient = BluetoothClient() +private let storage = TemperatureStorage() +private let bluetoothClient: BluetoothClient = { + .init(storage: storage) +}() @main struct TempTrackApp: App { @@ -11,9 +13,6 @@ struct TempTrackApp: App { ContentView() .environmentObject(storage) .environmentObject(bluetoothClient) - .onAppear { - bluetoothClient.delegate = storage - } } } } diff --git a/TempTrack/Temperature/TemperatureDataTransfer.swift b/TempTrack/Temperature/TemperatureDataTransfer.swift index 0037bb8..3bf1588 100644 --- a/TempTrack/Temperature/TemperatureDataTransfer.swift +++ b/TempTrack/Temperature/TemperatureDataTransfer.swift @@ -6,8 +6,6 @@ final class TemperatureDataTransfer { private let interval: Int - weak var delegate: TemperatureDataTransferDelegate? - private var dataBuffer: Data = Data() private(set) var currentByteIndex = 0 @@ -17,8 +15,11 @@ final class TemperatureDataTransfer { private(set) var blockSize: Int private var numberOfRecordingsInCurrentTransfer = 0 - - private(set) var lastRecording: TemperatureMeasurement = .init(sensor0: .notFound, sensor1: .notFound, date: .now) + + var measurements: [TemperatureMeasurement] = [] + + /// The last temperatures to calculate relative values + private var lastRecording: TemperatureMeasurement = .init(sensor0: .notFound, sensor1: .notFound, date: .now) private var dateOfNextRecording: Date { startDateOfCurrentTransfer.addingTimeInterval(TimeInterval(numberOfRecordingsInCurrentTransfer * interval)) @@ -78,7 +79,6 @@ final class TemperatureDataTransfer { func completeTransfer() { processBytes() - delegate?.saveAfterTransfer() } private func addRelative(byte: UInt8) { @@ -100,7 +100,7 @@ final class TemperatureDataTransfer { lastRecording.sensor1 = measurement.sensor1 } lastRecording.id = measurement.id - delegate?.didReceiveRecording(measurement) + measurements.append(measurement) } private func convertTemp(value: UInt8, relativeTo previous: TemperatureValue) -> TemperatureValue { diff --git a/TempTrack/Temperature/TemperatureDataTransferDelegate.swift b/TempTrack/Temperature/TemperatureDataTransferDelegate.swift deleted file mode 100644 index 22165ad..0000000 --- a/TempTrack/Temperature/TemperatureDataTransferDelegate.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -protocol TemperatureDataTransferDelegate: AnyObject { - - func didReceiveRecording(_ measurement: TemperatureMeasurement) - - func saveAfterTransfer() -} diff --git a/TempTrack/Temperature/TemperatureMeasurement.swift b/TempTrack/Temperature/TemperatureMeasurement.swift index 2ec490f..1bb8f02 100644 --- a/TempTrack/Temperature/TemperatureMeasurement.swift +++ b/TempTrack/Temperature/TemperatureMeasurement.swift @@ -5,7 +5,8 @@ struct TemperatureMeasurement: Identifiable { var sensor0: TemperatureValue var sensor1: TemperatureValue - + + /// The seconds since 1970 var id: Int var date: Date { @@ -18,7 +19,7 @@ struct TemperatureMeasurement: Identifiable { } var secondsToNow: Int { - Date().seconds - id + id - Date().seconds } var maximumValue: Double? { @@ -172,7 +173,7 @@ extension TemperatureMeasurement { TemperatureMeasurement( sensor0: .init(value: $0.element.0), sensor1: .init(value: $0.element.1), - id: seconds + $0.offset * 60) + id: seconds + ($0.offset - temps.count) * 60) } }() }