diff --git a/TempTrack.xcodeproj/project.pbxproj b/TempTrack.xcodeproj/project.pbxproj index cde577d..0d84626 100644 --- a/TempTrack.xcodeproj/project.pbxproj +++ b/TempTrack.xcodeproj/project.pbxproj @@ -26,6 +26,9 @@ 88CDE0782A28AF2C00114294 /* TemperatureSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE0772A28AF2C00114294 /* TemperatureSensor.swift */; }; 88CDE07B2A28AF5100114294 /* BluetoothRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CDE07A2A28AF5100114294 /* BluetoothRequest.swift */; }; 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 */ @@ -47,6 +50,8 @@ 88CDE0772A28AF2C00114294 /* TemperatureSensor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureSensor.swift; sourceTree = ""; }; 88CDE07A2A28AF5100114294 /* BluetoothRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothRequest.swift; sourceTree = ""; }; 88CDE07D2A28AFF400114294 /* DeviceInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfoView.swift; sourceTree = ""; }; + E253A9212A2B39B700EC6B28 /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = ""; }; + E253A9232A2B462500EC6B28 /* TemperatureHistoryChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureHistoryChart.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,6 +59,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E253A9272A2CA48A00EC6B28 /* SQLite in Frameworks */, 88CDE0662A25D08F00114294 /* SFSafeSymbols in Frameworks */, 88CDE06B2A2899C900114294 /* BottomSheet in Frameworks */, ); @@ -81,14 +87,12 @@ 88CDE04D2A2508E900114294 /* TempTrack */ = { isa = PBXGroup; children = ( + 88CDE04E2A2508E900114294 /* TempTrackApp.swift */, + 88CDE0502A2508E900114294 /* ContentView.swift */, + E253A9202A2B39A700EC6B28 /* Extensions */, 88CDE07C2A28AFE700114294 /* Views */, 88CDE0792A28AF3E00114294 /* Bluetooth */, 88CDE06E2A28AE8D00114294 /* Temperature */, - 88CDE04E2A2508E900114294 /* TempTrackApp.swift */, - 88CDE05C2A250F3C00114294 /* DeviceManager.swift */, - 88CDE0732A28AEE500114294 /* DeviceManagerDelegate.swift */, - 88CDE05E2A250F5200114294 /* DeviceState.swift */, - 88CDE0502A2508E900114294 /* ContentView.swift */, 88CDE0522A2508EA00114294 /* Assets.xcassets */, 88CDE0542A2508EA00114294 /* Preview Content */, 88CDE0672A2698B400114294 /* TemperatureStorage.swift */, @@ -122,6 +126,9 @@ children = ( 88CDE0602A25108100114294 /* BluetoothClient.swift */, 88CDE07A2A28AF5100114294 /* BluetoothRequest.swift */, + 88CDE05C2A250F3C00114294 /* DeviceManager.swift */, + 88CDE0732A28AEE500114294 /* DeviceManagerDelegate.swift */, + 88CDE05E2A250F5200114294 /* DeviceState.swift */, ); path = Bluetooth; sourceTree = ""; @@ -130,10 +137,19 @@ isa = PBXGroup; children = ( 88CDE07D2A28AFF400114294 /* DeviceInfoView.swift */, + E253A9232A2B462500EC6B28 /* TemperatureHistoryChart.swift */, ); path = Views; sourceTree = ""; }; + E253A9202A2B39A700EC6B28 /* Extensions */ = { + isa = PBXGroup; + children = ( + E253A9212A2B39B700EC6B28 /* Color+Extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -153,6 +169,7 @@ packageProductDependencies = ( 88CDE0652A25D08F00114294 /* SFSafeSymbols */, 88CDE06A2A2899C900114294 /* BottomSheet */, + E253A9262A2CA48A00EC6B28 /* SQLite */, ); productName = TempTrack; productReference = 88CDE04B2A2508E900114294 /* TempTrack.app */; @@ -185,6 +202,7 @@ packageReferences = ( 88CDE0642A25D08F00114294 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */, 88CDE0692A2899C900114294 /* XCRemoteSwiftPackageReference "bottom-sheet" */, + E253A9252A2CA48900EC6B28 /* XCRemoteSwiftPackageReference "SQLite" */, ); productRefGroup = 88CDE04C2A2508E900114294 /* Products */; projectDirPath = ""; @@ -223,6 +241,8 @@ 88CDE0682A2698B400114294 /* TemperatureStorage.swift in Sources */, 88CDE0762A28AF0900114294 /* TemperatureValue.swift in Sources */, 88CDE07B2A28AF5100114294 /* BluetoothRequest.swift in Sources */, + E253A9242A2B462500EC6B28 /* TemperatureHistoryChart.swift in Sources */, + E253A9222A2B39B700EC6B28 /* Color+Extensions.swift in Sources */, 88CDE0612A25108100114294 /* BluetoothClient.swift in Sources */, 88CDE0742A28AEE500114294 /* DeviceManagerDelegate.swift in Sources */, 88CDE06D2A28A92000114294 /* DeviceInfo.swift in Sources */, @@ -449,6 +469,14 @@ 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 */ @@ -462,6 +490,11 @@ 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 64ac4f3..a493971 100644 --- a/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/TempTrack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols", "state" : { - "revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c", - "version" : "4.1.1" + "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" } } ], diff --git a/TempTrack.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate b/TempTrack.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..4945cb5 Binary files /dev/null and b/TempTrack.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/TempTrack.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist b/TempTrack.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..3d5a1f6 --- /dev/null +++ b/TempTrack.xcodeproj/xcuserdata/ch.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,35 @@ + + + + + SchemeUserState + + SQLite (Playground) 1.xcscheme + + isShown + + orderHint + 2 + + SQLite (Playground) 2.xcscheme + + isShown + + orderHint + 3 + + SQLite (Playground).xcscheme + + isShown + + orderHint + 1 + + TempTrack.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/TempTrack/Bluetooth/BluetoothClient.swift b/TempTrack/Bluetooth/BluetoothClient.swift index 301a2bd..d059250 100644 --- a/TempTrack/Bluetooth/BluetoothClient.swift +++ b/TempTrack/Bluetooth/BluetoothClient.swift @@ -55,7 +55,9 @@ final class BluetoothClient: ObservableObject { private var runningTransfer: TemperatureDataTransfer? func updateDeviceInfo() { - addRequest(.getInfo) + if case .configured = deviceState { + addRequest(.getInfo) + } } private var dataUpdateTimer: Timer? diff --git a/TempTrack/DeviceManager.swift b/TempTrack/Bluetooth/DeviceManager.swift similarity index 100% rename from TempTrack/DeviceManager.swift rename to TempTrack/Bluetooth/DeviceManager.swift diff --git a/TempTrack/DeviceManagerDelegate.swift b/TempTrack/Bluetooth/DeviceManagerDelegate.swift similarity index 100% rename from TempTrack/DeviceManagerDelegate.swift rename to TempTrack/Bluetooth/DeviceManagerDelegate.swift diff --git a/TempTrack/DeviceState.swift b/TempTrack/Bluetooth/DeviceState.swift similarity index 100% rename from TempTrack/DeviceState.swift rename to TempTrack/Bluetooth/DeviceState.swift diff --git a/TempTrack/ContentView.swift b/TempTrack/ContentView.swift index 025feae..91e3dbb 100644 --- a/TempTrack/ContentView.swift +++ b/TempTrack/ContentView.swift @@ -3,20 +3,56 @@ import SFSafeSymbols import BottomSheet struct ContentView: View { - + + private let updateInterval = 1.0 + + private let minTempColor = Color(hue: 0.624, saturation: 0.5, brightness: 1.0) + private let minTemperature = -20.0 + + private let maxTempColor = Color(hue: 1.0, saturation: 0.5, brightness: 1.0) + private let maxTemperature = 40.0 + + private let disconnectedColor = Color(white: 0.8) + @ObservedObject var client = BluetoothClient() - - init() { - - } - - init(client: BluetoothClient) { - self.client = client - } - + + @ObservedObject + var storage = TemperatureStorage() + @State var showDeviceInfo = false + + @State + var updateTimer: Timer? + + @State + var updateInfoToggle = true + + init() { + startRegularUpdates() + } + + init(client: BluetoothClient, values: [TemperatureMeasurement]) { + self.client = client + self.storage = .init(lastMeasurements: values) + startRegularUpdates() + } + + private func startRegularUpdates() { + guard updateTimer == nil else { + return + } + updateTimer = Timer.scheduledTimer(withTimeInterval: updateInterval, repeats: true) { timer in + self.updateInfoToggle.toggle() + } + + updateTimer?.fire() + } + + var hasDeviceInfo: Bool { + client.deviceInfo != nil + } var averageTemperature: Double? { let t1 = client.deviceInfo?.sensor1?.optionalValue @@ -54,54 +90,77 @@ struct ContentView: View { } return .thermometerHigh } - + + var backgroundColor: Color { + guard let temp = averageTemperature else { + return disconnectedColor + } + guard temp > minTemperature else { + return minTempColor + } + guard temp < maxTemperature else { + return maxTempColor + } + let ratio = (temp - minTemperature) / (maxTemperature - minTemperature) + return minTempColor.blend(to: maxTempColor, intensity: ratio) + } + + var backgroundGradient: Gradient { + let color = backgroundColor + let lighter = color.opacity(0.5) + return .init(colors: [lighter, color]) + } + var body: some View { VStack { - HStack { - Image(systemSymbol: .iphone) - .frame(width: 30) - Text(client.deviceState.text) - Spacer() - } Spacer() - Image(systemSymbol: temperatureIcon) - .font(.system(size: 200, weight: .light)) +// Image(systemSymbol: temperatureIcon) +// .font(.system(size: 100, weight: .light)) if hasTemperature { Text(temperatureString) - .font(.system(size: 100, weight: .light)) + .font(.system(size: 150, weight: .light)) + .foregroundColor(.white) } - + Spacer() + TemperatureHistoryChart(points: storage.lastMeasurements) + .frame(height: 150) + .background(Color.white.opacity(0.1)) + .cornerRadius(8) HStack(alignment: .center) { - Button(action: { - _ = client.collectRecordedData() - }) { - Text("Transfer") - }.padding() - Spacer() Button { self.showDeviceInfo = true } label: { - Image(systemSymbol: .infoCircle) - .font(.system(size: 40, weight: .regular)) - }.disabled(client.deviceInfo == nil) + if hasDeviceInfo { + Image(systemSymbol: .iphone) + .font(.system(size: 30, weight: .regular)) + } + Text(client.deviceState.text) + } + .disabled(!hasDeviceInfo) + .foregroundColor(.white) + }.padding() } .padding() - .bottomSheet(isPresented: $showDeviceInfo, height: 520) { + .bottomSheet(isPresented: $showDeviceInfo, height: 600) { if let info = client.deviceInfo { - DeviceInfoView(info: info) + DeviceInfoView( + info: info, + isPresented: $showDeviceInfo, updateToggle: $updateInfoToggle) } else { EmptyView() } } - + .background(backgroundGradient) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { - ContentView(client: BluetoothClient(deviceInfo: .mock)) + ContentView( + client: BluetoothClient(deviceInfo: .mock), + values: TemperatureMeasurement.mockData) } } diff --git a/TempTrack/Extensions/Color+Extensions.swift b/TempTrack/Extensions/Color+Extensions.swift new file mode 100644 index 0000000..963de9f --- /dev/null +++ b/TempTrack/Extensions/Color+Extensions.swift @@ -0,0 +1,24 @@ +import Foundation +import SwiftUI + +extension Color { + + func blend(to other: Color, intensity: CGFloat = 0.5) -> Color { + Color(UIColor(self).blend(to: UIColor(other), intensity: intensity)) + } +} + +extension UIColor { + + func blend(to other: UIColor, intensity: CGFloat = 0.5) -> UIColor { + let l2 = max(0.0, min(1.0, intensity)) + let l1 = 1 - l2 + var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0) + var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0) + + getRed(&r1, green: &g1, blue: &b1, alpha: &a1) + other.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) + + return UIColor(red: l1*r1 + l2*r2, green: l1*g1 + l2*g2, blue: l1*b1 + l2*b2, alpha: l1*a1 + l2*a2) + } +} diff --git a/TempTrack/TempTrackApp.swift b/TempTrack/TempTrackApp.swift index 26ca9f5..7c6ef9c 100644 --- a/TempTrack/TempTrackApp.swift +++ b/TempTrack/TempTrackApp.swift @@ -1,10 +1,3 @@ -// -// TempTrackApp.swift -// TempTrack -// -// Created by iMac on 29.05.23. -// - import SwiftUI @main diff --git a/TempTrack/Temperature/TemperatureMeasurement.swift b/TempTrack/Temperature/TemperatureMeasurement.swift index 06d2a46..5764b21 100644 --- a/TempTrack/Temperature/TemperatureMeasurement.swift +++ b/TempTrack/Temperature/TemperatureMeasurement.swift @@ -1,10 +1,113 @@ import Foundation -struct TemperatureMeasurement { +struct TemperatureMeasurement: Identifiable { var sensor0: TemperatureValue var sensor1: TemperatureValue var date: Date + + var id: Int { + Int(date.timeIntervalSince1970.rounded()) + } + + var secondsAgo: Int { + Int(date.timeIntervalSinceNow.rounded()) + } +} + +private extension TemperatureValue { + + init(value: Double?) { + if let value { + self = .value(value) + } else { + self = .notFound + } + } +} + +private extension TemperatureMeasurement { + + init(t0: Double?, t1: Double?, secs: Int) { + self.sensor0 = .init(value: t0) + self.sensor1 = .init(value: t1) + self.date = Date().addingTimeInterval(TimeInterval(secs-3600)) + } + + init(t0: Double?, t1: Double?, min: Int) { + self.init(t0: t0, t1: t1, secs: min * 60) + } +} + +extension TemperatureMeasurement { + + static let mockData: [TemperatureMeasurement] = { + let temps: [(Double?, Double?)] = [ + (20, 14), + (20, 13.5), + (20.5, 13.5), + (20.5, 13.5), + (21, 14), + (21, 14), + (nil, 14.5), + (nil, 14), + (nil, 14.5), + (nil, 14), + (nil, 14), + (nil, 14.5), + (nil, 15), + (5.0, 15), + (4.5, 15.5), + (4.5, 16), + (4.0, 16.5), + (3.0, 17), + (3.0, 19), + (2.5, 20), + (2.5, 20.5), + (2.0, 20.5), + (1.0, 20.5), + (0.5, 20.5), + (0.0, 20), + (0.0, 20), + (-1.0, 21.0), + (-0.5, 21.0), + (-3.0, 21.0), + (-3.5, 20.5), + (-4.0, 20.5), + (-5.0, 20.0), + (-5.0, nil), + (-5.5, nil), + (-5.0, nil), + (-5.5, nil), + (-6.0, nil), + (-5.0, nil), + (nil, nil), + (nil, nil), + (nil, nil), + (-5.0, nil), + (-4.5, nil), + (-4.0, 23.0), + (5.0, 24.0), + (7.0, 25.0), + (8.0, 25.5), + (8.5, 25.5), + (10.0, 25.5), + (10.5, 24.0), + (10.5, 24.0), + (10.5, 24.5), + (12.0, 23.5), + (12.5, 24.0), + (12.0, 23.5), + (14.0, 24.0), + (15.0, 25.0), + (15.0, 25.0), + (15.5, 25.0), + (15.0, 25.0), + ] + return temps.enumerated().map { + TemperatureMeasurement(t0: $0.element.0, t1: $0.element.1, min: $0.offset) + } + }() } diff --git a/TempTrack/Temperature/TemperatureValue.swift b/TempTrack/Temperature/TemperatureValue.swift index 6ba3b2f..75352ab 100644 --- a/TempTrack/Temperature/TemperatureValue.swift +++ b/TempTrack/Temperature/TemperatureValue.swift @@ -36,8 +36,8 @@ enum TemperatureValue { return "No sensor" case .invalidMeasurement: return "Invalid" - case .value(let double): - return "\(Int(double.rounded()))°C" + case .value(let value): + return String(format:" %.1f°C", value) } } } diff --git a/TempTrack/TemperatureStorage.swift b/TempTrack/TemperatureStorage.swift index 69ceabd..130e503 100644 --- a/TempTrack/TemperatureStorage.swift +++ b/TempTrack/TemperatureStorage.swift @@ -1,6 +1,70 @@ import Foundation +import Combine +import SQLite -final class TemperatureStorage { +final class TemperatureStorage: ObservableObject { + + static var documentDirectory: URL { + try! FileManager.default.url( + for: .documentDirectory, + in: .userDomainMask, + appropriateFor: nil, create: true) + } + + private let databaseUrl: URL + + @Published + var lastMeasurements: [TemperatureMeasurement] + + init(lastMeasurements: [TemperatureMeasurement] = []) { + self.lastMeasurements = lastMeasurements + self.databaseUrl = TemperatureStorage.documentDirectory.appendingPathComponent("db.sqlite3") + } + + private let table = Table("values") + private let i + + private func createDatabaseIfNeeded() throws { + let db = try Connection(databaseUrl.path) + + let users = Table("users") + let id = Expression("id") + let name = Expression("name") + let email = Expression("email") + + try db.run(users.create(ifNotExists: true) { t in + t.column(id, primaryKey: true) + t.column(name) + t.column(email, unique: true) + }) + // CREATE TABLE "users" ( + // "id" INTEGER PRIMARY KEY NOT NULL, + // "name" TEXT, + // "email" TEXT NOT NULL UNIQUE + // ) + + let insert = users.insert(name <- "Alice", email <- "alice@mac.com") + let rowid = try db.run(insert) + // INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') + + for user in try db.prepare(users) { + print("id: \(user[id]), name: \(user[name]), email: \(user[email])") + // id: 1, name: Optional("Alice"), email: alice@mac.com + } + // SELECT * FROM "users" + + let alice = users.filter(id == rowid) + + try db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) + // UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') + // WHERE ("id" = 1) + + try db.run(alice.delete()) + // DELETE FROM "users" WHERE ("id" = 1) + + try db.scalar(users.count) // 0 + // SELECT count(*) FROM "users" + } } diff --git a/TempTrack/Views/DeviceInfoView.swift b/TempTrack/Views/DeviceInfoView.swift index 21c1300..26bff3d 100644 --- a/TempTrack/Views/DeviceInfoView.swift +++ b/TempTrack/Views/DeviceInfoView.swift @@ -14,6 +14,12 @@ struct DeviceInfoView: View { private let storageWarnBytes = 500 let info: DeviceInfo + + @Binding + var isPresented: Bool + + @Binding + var updateToggle: Bool private var runTimeString: String { let number = info.numberOfSecondsRunning @@ -97,6 +103,16 @@ struct DeviceInfoView: View { var body: some View { VStack(alignment: .leading, spacing: 5) { + HStack { + Text("Device Info").font(.title2).bold() + Spacer() + Button(action: { isPresented = false }) { + Image(systemSymbol: .xmarkCircleFill) + .foregroundColor(.gray) + .font(.system(size: 26)) + } + } + .padding(.bottom) VStack(alignment: .leading, spacing: 5) { Text("Recording") .font(.headline) @@ -159,8 +175,11 @@ struct DeviceInfoView: View { struct DeviceInfoView_Previews: PreviewProvider { static var previews: some View { - DeviceInfoView(info: .mock) - .previewLayout(.fixed(width: 375, height: 500)) + DeviceInfoView( + info: .mock, + isPresented: .constant(true), + updateToggle: .constant(true)) + .previewLayout(.fixed(width: 375, height: 600)) } } diff --git a/TempTrack/Views/TemperatureHistoryChart.swift b/TempTrack/Views/TemperatureHistoryChart.swift new file mode 100644 index 0000000..2149160 --- /dev/null +++ b/TempTrack/Views/TemperatureHistoryChart.swift @@ -0,0 +1,58 @@ +import SwiftUI +import Charts + +struct TemperatureHistoryChart: View { + + let points: [TemperatureMeasurement] + + let upperTempLimit = 40.0 + let lowerTempLimit = -20.0 + + let pastDateLimit = -3600 + let futureDateLimit = 0 + + var body: some View { + Chart { + ForEach(points) { point in + if let s = point.sensor0.optionalValue { + LineMark( + x: .value("Date", point.secondsAgo), + y: .value("Temperature", s)) + .foregroundStyle(Color.red) + } + if let s = point.sensor1.optionalValue { + LineMark( + x: .value("Date", point.secondsAgo), + y: .value("Temperature", s)) + .foregroundStyle(by: .value("Type", "Sensor 1")) + } + } + } + .chartXScale(domain: pastDateLimit...futureDateLimit) + .chartYScale(domain: lowerTempLimit...upperTempLimit) + .chartXAxis(.hidden) + .chartLegend(.hidden) + .chartYAxis { + AxisMarks(position: .trailing, values: .automatic) { value in + AxisValueLabel(multiLabelAlignment: .trailing) { + if let intValue = value.as(Int.self) { + Text("\(intValue) km") + .font(.system(size: 10)) + .foregroundColor(.white) + } + } + } + //AxisMarks(position: .trailing, stroke: StrokeStyle(lineWidth: 0)) + } + .padding() + } +} + +struct TemperatureHistoryChart_Previews: PreviewProvider { + static var previews: some View { + TemperatureHistoryChart( + points: TemperatureMeasurement.mockData) + .previewLayout(.fixed(width: 350, height: 150)) + .background(.gray) + } +}