1 contributor
//
// 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<Content: View>(@ViewBuilder content: () -> Content) -> some View {
ScrollView {
content()
.frame(maxWidth: .infinity, alignment: .topLeading)
.padding(.horizontal, pageHorizontalPadding)
.padding(.vertical, pageVerticalPadding)
}
}
private func landscapeFace<Content: View>(@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)
}
}