TempTrack-iOS/TempTrack/ContentView.swift

179 lines
4.8 KiB
Swift
Raw Normal View History

2023-05-29 18:23:13 +02:00
import SwiftUI
2023-06-03 08:15:00 +02:00
import SFSafeSymbols
import BottomSheet
2023-05-29 18:23:13 +02:00
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)
2023-06-03 08:15:00 +02:00
@ObservedObject
var client = BluetoothClient()
@ObservedObject
var storage = TemperatureStorage()
@State
var showDeviceInfo = false
@State
var updateTimer: Timer?
@State
var updateInfoToggle = true
2023-06-03 08:15:00 +02:00
init() {
startRegularUpdates()
2023-06-03 08:15:00 +02:00
}
init(client: BluetoothClient, values: [TemperatureMeasurement]) {
2023-06-03 08:15:00 +02:00
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
2023-06-03 08:15:00 +02:00
}
var averageTemperature: Double? {
let t1 = client.deviceInfo?.sensor1?.optionalValue
guard let t0 = client.deviceInfo?.sensor0?.optionalValue else {
return t1
}
guard let t1 else {
return t0
}
return (t0 + t1) / 2
}
var hasTemperature: Bool {
averageTemperature != nil
}
var temperatureString: String {
guard let temp = averageTemperature else {
return "?"
}
return String(format: "%.0f°", locale: .current, temp)
}
var temperatureIcon: SFSymbol {
guard let temp = averageTemperature else {
return .thermometerMediumSlash
}
guard temp > 0 else {
return .thermometerSnowflake
}
guard temp > 15 else {
return .thermometerLow
}
guard temp > 25 else {
return .thermometerMedium
}
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])
}
2023-05-29 18:23:13 +02:00
var body: some View {
VStack {
2023-06-03 08:15:00 +02:00
Spacer()
// Image(systemSymbol: temperatureIcon)
// .font(.system(size: 100, weight: .light))
2023-06-03 08:15:00 +02:00
if hasTemperature {
Text(temperatureString)
.font(.system(size: 150, weight: .light))
.foregroundColor(.white)
2023-06-03 08:15:00 +02:00
}
2023-06-03 08:15:00 +02:00
Spacer()
TemperatureHistoryChart(points: storage.lastMeasurements)
.frame(height: 150)
.background(Color.white.opacity(0.1))
.cornerRadius(8)
2023-06-03 08:15:00 +02:00
HStack(alignment: .center) {
Button {
self.showDeviceInfo = true
} label: {
if hasDeviceInfo {
Image(systemSymbol: .iphone)
.font(.system(size: 30, weight: .regular))
}
Text(client.deviceState.text)
}
.disabled(!hasDeviceInfo)
.foregroundColor(.white)
2023-06-03 08:15:00 +02:00
}.padding()
2023-05-29 18:23:13 +02:00
}
.padding()
.bottomSheet(isPresented: $showDeviceInfo, height: 600) {
2023-06-03 08:15:00 +02:00
if let info = client.deviceInfo {
DeviceInfoView(
info: info,
isPresented: $showDeviceInfo, updateToggle: $updateInfoToggle)
2023-06-03 08:15:00 +02:00
} else {
EmptyView()
}
}
.background(backgroundGradient)
2023-05-29 18:23:13 +02:00
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(
client: BluetoothClient(deviceInfo: .mock),
values: TemperatureMeasurement.mockData)
2023-05-29 18:23:13 +02:00
}
}
2023-06-03 08:15:00 +02:00
extension HorizontalAlignment {
private struct InfoTextAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[HorizontalAlignment.leading]
}
}
static let infoTextAlignmentGuide = HorizontalAlignment(
InfoTextAlignment.self
)
}