import SwiftUI import SFSafeSymbols private let df: DateFormatter = { let df = DateFormatter() df.locale = .current df.dateStyle = .short df.timeStyle = .short return df }() struct DeviceInfoView: View { private let storageWarnBytes = 500 let info: DeviceInfo @Binding var isPresented: Bool private var runTimeString: String { let number = info.numberOfSecondsRunning guard number >= 60 else { return "\(number) seconds" } let minutes = number / 60 guard minutes > 1 else { return "1 minute" } guard minutes >= 60 else { return "\(minutes) minutes" } let hours = minutes / 60 guard hours > 1 else { return "1 hour" } guard hours >= 60 else { return "\(hours) hours" } let days = hours / 24 guard days > 1 else { return "1 day" } return "\(days) days" } private var nextUpdateText: String { let secs = Int(info.nextMeasurement.timeIntervalSinceNow.rounded()) guard secs > 1 else { return "Now" } return "In \(secs) seconds" } private var storageIcon: SFSymbol { if info.storageSize - info.numberOfRecordedBytes < storageWarnBytes { return .externaldriveTrianglebadgeExclamationmark } return .externaldrive } private var storageText: String { if info.storageSize <= 0 { return "\(info.numberOfRecordedBytes)" } return "\(info.numberOfRecordedBytes) / \(info.storageSize) Bytes (\(info.storageFillPercentage) %)" } func sensorView(_ sensor: TemperatureSensor?, id: Int) -> some View { VStack(alignment: .leading, spacing: 5) { Text("Sensor \(id)") .font(.headline) if let sensor { HStack { Image(systemSymbol: sensor.temperatureIcon) .frame(width: 30) Text(sensor.temperatureText) } HStack { Image(systemSymbol: .arrowTriangle2Circlepath) .frame(width: 30) Text(sensor.updateText) Spacer() } HStack { Image(systemSymbol: .tag) .frame(width: 30) Text(sensor.hexAddress) } } else { HStack { Image(systemSymbol: .thermometerMediumSlash) .frame(width: 30) Text("Not connected") } } } } var updateText: String { guard info.receivedDate.secondsToNow > 3 else { return "Updated Now" } return "Updated \(info.receivedDate.timePassedText)" } var clockOffsetText: String { guard info.hasDeviceStartTimeSet else { return "Clock not synchronized" } let offset = info.clockOffset.roundedInt guard abs(offset) > 1 else { return "No clock offset" } return "Offset: \(offset) seconds" } var body: some View { NavigationView { VStack(alignment: .leading, spacing: 5) { VStack(alignment: .leading, spacing: 5) { Text("Recording") .font(.headline) HStack { Image(systemSymbol: .power) .frame(width: 30) Text(df.string(from: info.deviceStartTime)) Spacer() } HStack { Image(systemSymbol: .clock) .frame(width: 30) Text("\(runTimeString)") } HStack { Image(systemSymbol: .clockBadgeExclamationmark) .frame(width: 30) Text(clockOffsetText) } HStack { Image(systemSymbol: .stopwatch) .frame(width: 30) Text("Every \(info.measurementInterval) seconds") Spacer() } HStack { Image(systemSymbol: .arrowTriangle2Circlepath) .frame(width: 30) Text(nextUpdateText) Spacer() } } VStack(alignment: .leading, spacing: 5) { Text("Storage") .font(.headline) HStack { Image(systemSymbol: .speedometer) .frame(width: 30) Text("\(info.numberOfStoredMeasurements) Measurements (\(info.totalNumberOfMeasurements) total)") } HStack { Image(systemSymbol: storageIcon) .frame(width: 30) Text(storageText) } HStack { Image(systemSymbol: .iphoneAndArrowForward) .frame(width: 30) Text("\(info.transferBlockSize) Byte Block Size") } } sensorView(info.sensor0, id: 0) sensorView(info.sensor1, id: 1) Spacer() HStack { Spacer() TimelineView(.periodic(from: Date(), by: 1)) { context in Text(updateText) .font(.footnote) .textCase(.uppercase) } Spacer() } } .padding() .navigationTitle("Device Info") .navigationBarTitleDisplayMode(.large) } } } struct DeviceInfoView_Previews: PreviewProvider { static var previews: some View { DeviceInfoView(info: .mock, isPresented: .constant(true)) .previewLayout(.fixed(width: 375, height: 650)) } }