import SwiftUI import SFSafeSymbols struct ContentView: View { 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) @EnvironmentObject var bluetoothClient: BluetoothClient @EnvironmentObject var storage: TemperatureStorage @State var showDeviceInfo = false @State var showHistory = false @State var showLog = false init() { } var averageTemperature: Double? { let t1 = bluetoothClient.deviceInfo?.sensor1?.optionalValue guard let t0 = bluetoothClient.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]) } var body: some View { VStack { Spacer() // Image(systemSymbol: temperatureIcon) // .font(.system(size: 100, weight: .light)) if hasTemperature { Text(temperatureString) .font(.system(size: 150, weight: .light)) .foregroundColor(.white) } Spacer() Button { self.showHistory = true } label: { TemperatureHistoryChart(points: $storage.recentMeasurements) .frame(height: 300) .background(Color.white.opacity(0.1)) .cornerRadius(8) } HStack(alignment: .center) { Button { self.showLog.toggle() } label: { Image(systemSymbol: .textBubble) .font(.system(size: 30, weight: .light)) .foregroundColor(.white) } Spacer() Button { self.showDeviceInfo = true } label: { if bluetoothClient.hasInfo { Image(systemSymbol: .iphone) .font(.system(size: 30, weight: .light)) } Text(bluetoothClient.deviceState.text) } .disabled(!bluetoothClient.hasInfo) .foregroundColor(.white) Spacer() Button { bluetoothClient.collectRecordedData() } label: { Image(systemSymbol: .arrowUpArrowDownCircle) .font(.system(size: 30, weight: .light)) .foregroundColor(.white) }.disabled(!bluetoothClient.isConnected) }.padding() } .padding() .sheet(isPresented: $showDeviceInfo) { if let info = bluetoothClient.deviceInfo { DeviceInfoView(info: info, isPresented: $showDeviceInfo) } else { EmptyView() } } .sheet(isPresented: $showHistory) { HistoryList() .environmentObject(storage) } .sheet(isPresented: $showLog) { LogView() .environmentObject(log) } .background(backgroundGradient) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { let storage = TemperatureStorage(lastMeasurements: TemperatureMeasurement.mockData) let client = BluetoothClient(storage: storage, deviceInfo: .mock) ContentView() .environmentObject(storage) .environmentObject(client) } } extension HorizontalAlignment { private struct InfoTextAlignment: AlignmentID { static func defaultValue(in context: ViewDimensions) -> CGFloat { context[HorizontalAlignment.leading] } } static let infoTextAlignmentGuide = HorizontalAlignment( InfoTextAlignment.self ) }