1 contributor
243 lines | 9.586kb
//
//  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)
    }
}