// // MeterHomeTabView.swift // USB Meter // import SwiftUI struct MeterHomeTabView: View { @EnvironmentObject private var meter: Meter let size: CGSize let isLandscape: Bool @State private var dataGroupsViewVisibility = false @State private var recordingViewVisibility = false @State private var measurementsViewVisibility = false private let actionStripPadding: CGFloat = 10 private let actionDividerWidth: CGFloat = 1 private let actionButtonMaxWidth: CGFloat = 156 private let actionButtonMinWidth: CGFloat = 88 private let actionButtonHeight: CGFloat = 108 private let pageHorizontalPadding: CGFloat = 12 private let pageVerticalPadding: CGFloat = 12 var body: some View { Group { if isLandscape { landscapeFace { VStack(alignment: .leading, spacing: 12) { connectionCard(compact: true, showsActions: meter.operationalState == .dataIsAvailable) MeterOverviewSectionView(meter: meter) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } else { portraitFace { VStack(alignment: .leading, spacing: 12) { connectionCard( compact: prefersCompactPortraitLayout, showsActions: meter.operationalState == .dataIsAvailable ) MeterOverviewSectionView(meter: meter) } } } } } private var prefersCompactPortraitLayout: Bool { size.height < 760 || size.width < 380 } private func portraitFace(@ViewBuilder content: () -> Content) -> some View { ScrollView { content() .frame(maxWidth: .infinity, alignment: .topLeading) .padding(.horizontal, pageHorizontalPadding) .padding(.vertical, pageVerticalPadding) } } private func landscapeFace(@ViewBuilder content: () -> Content) -> some View { ScrollView { content() .frame(maxWidth: .infinity, alignment: .topLeading) .padding(.horizontal, pageHorizontalPadding) .padding(.vertical, pageVerticalPadding) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } private func connectionCard(compact: Bool = false, showsActions: Bool = false) -> some View { let cardContent = VStack(alignment: .leading, spacing: compact ? 12 : 18) { HStack(alignment: .top) { meterIdentity(compact: compact) Spacer() statusBadge } connectionActionArea(compact: compact) if showsActions { VStack(spacing: compact ? 10 : 12) { Rectangle() .fill(Color.secondary.opacity(0.12)) .frame(height: 1) actionGrid(compact: compact, embedded: true) } } } .padding(compact ? 16 : 20) .meterCard(tint: meter.color, fillOpacity: 0.22, strokeOpacity: 0.24) return cardContent .frame(maxWidth: .infinity, alignment: .topLeading) } private func meterIdentity(compact: Bool) -> some View { HStack(alignment: .firstTextBaseline, spacing: 8) { Text(meter.name) .font(.system(compact ? .title3 : .title2, design: .rounded).weight(.bold)) .lineLimit(1) .minimumScaleFactor(0.8) Text(meter.deviceModelName) .font((compact ? Font.caption : .subheadline).weight(.semibold)) .foregroundColor(.secondary) .lineLimit(1) .minimumScaleFactor(0.8) } } private func actionGrid(compact: Bool = false, embedded: Bool = false) -> some View { let currentActionHeight = compact ? CGFloat(86) : actionButtonHeight return GeometryReader { proxy in let buttonWidth = actionButtonWidth(for: proxy.size.width) let stripWidth = actionStripWidth(for: buttonWidth) let stripContent = HStack(spacing: 0) { meterSheetButton(icon: "square.grid.2x2.fill", title: meter.dataGroupsTitle, tint: .teal, width: buttonWidth, height: currentActionHeight, compact: compact) { dataGroupsViewVisibility.toggle() } .sheet(isPresented: $dataGroupsViewVisibility) { DataGroupsSheetView(visibility: $dataGroupsViewVisibility) .environmentObject(meter) } if meter.supportsRecordingView { actionStripDivider(height: currentActionHeight) meterSheetButton(icon: "gauge.with.dots.needle.50percent", title: "Charge Record", tint: .pink, width: buttonWidth, height: currentActionHeight, compact: compact) { recordingViewVisibility.toggle() } .sheet(isPresented: $recordingViewVisibility) { ChargeRecordSheetView(visibility: $recordingViewVisibility) .environmentObject(meter) } } actionStripDivider(height: currentActionHeight) meterSheetButton(icon: "clock.arrow.circlepath", title: "App History", tint: .blue, width: buttonWidth, height: currentActionHeight, compact: compact) { measurementsViewVisibility.toggle() } .sheet(isPresented: $measurementsViewVisibility) { AppHistorySheetView(visibility: $measurementsViewVisibility) .environmentObject(meter.measurements) } } .padding(actionStripPadding) .frame(width: stripWidth) HStack { Spacer(minLength: 0) stripContent .meterCard( tint: embedded ? meter.color : Color.secondary, fillOpacity: embedded ? 0.08 : 0.10, strokeOpacity: embedded ? 0.14 : 0.16, cornerRadius: embedded ? 24 : 22 ) Spacer(minLength: 0) } } .frame(height: currentActionHeight + (actionStripPadding * 2)) } private func connectionActionArea(compact: Bool = false) -> some View { MeterConnectionActionView( operationalState: meter.operationalState, compact: compact ) } private func meterSheetButton(icon: String, title: String, tint: Color, width: CGFloat, height: CGFloat, compact: Bool = false, action: @escaping () -> Void) -> some View { Button(action: action) { VStack(spacing: compact ? 8 : 10) { Image(systemName: icon) .font(.system(size: compact ? 18 : 20, weight: .semibold)) .frame(width: compact ? 34 : 40, height: compact ? 34 : 40) .background(Circle().fill(tint.opacity(0.14))) Text(title) .font((compact ? Font.caption : .footnote).weight(.semibold)) .multilineTextAlignment(.center) .lineLimit(2) .minimumScaleFactor(0.9) } .foregroundColor(tint) .frame(width: width, height: height) .contentShape(Rectangle()) } .buttonStyle(.plain) } private var visibleActionButtonCount: CGFloat { meter.supportsRecordingView ? 3 : 2 } private func actionButtonWidth(for availableWidth: CGFloat) -> CGFloat { let dividerWidth = actionDividerWidth * max(visibleActionButtonCount - 1, 0) let contentWidth = availableWidth - (actionStripPadding * 2) - dividerWidth let fittedWidth = floor(contentWidth / visibleActionButtonCount) return min(actionButtonMaxWidth, max(actionButtonMinWidth, fittedWidth)) } private func actionStripWidth(for buttonWidth: CGFloat) -> CGFloat { let dividerWidth = actionDividerWidth * max(visibleActionButtonCount - 1, 0) return (buttonWidth * visibleActionButtonCount) + dividerWidth + (actionStripPadding * 2) } private func actionStripDivider(height: CGFloat) -> some View { Rectangle() .fill(Color.secondary.opacity(0.16)) .frame(width: actionDividerWidth, height: max(44, height - 22)) } private var statusBadge: some View { MeterConnectionStatusBadgeView(text: statusText, color: statusColor) } private var statusText: String { switch meter.operationalState { case .notPresent: return "Missing" case .peripheralNotConnected: return "Ready" case .peripheralConnectionPending: return "Connecting" case .peripheralConnected: return "Linked" case .peripheralReady: return "Preparing" case .comunicating: return "Syncing" case .dataIsAvailable: return "Live" } } private var statusColor: Color { Meter.operationalColor(for: meter.operationalState) } }