Newer Older
235 lines | 9.097kb
Bogdan Timofte authored 2 months ago
1
//
Bogdan Timofte authored 2 months ago
2
//  MeterHomeTabView.swift
Bogdan Timofte authored 2 months ago
3
//  USB Meter
4
//
5

            
6
import SwiftUI
7

            
Bogdan Timofte authored 2 months ago
8
struct MeterHomeTabView: View {
Bogdan Timofte authored 2 months ago
9
    @EnvironmentObject private var meter: Meter
10

            
11
    let size: CGSize
12
    let isLandscape: Bool
Bogdan Timofte authored a month ago
13
    let showChargeRecordTab: () -> Void
14
    let showDataGroupsTab: () -> Void
Bogdan Timofte authored 2 months ago
15

            
16
    @State private var measurementsViewVisibility = false
17

            
18
    private let actionStripPadding: CGFloat = 10
19
    private let actionDividerWidth: CGFloat = 1
20
    private let actionButtonMaxWidth: CGFloat = 156
21
    private let actionButtonMinWidth: CGFloat = 88
22
    private let actionButtonHeight: CGFloat = 108
23
    private let pageHorizontalPadding: CGFloat = 12
24
    private let pageVerticalPadding: CGFloat = 12
25

            
26
    var body: some View {
27
        Group {
28
            if isLandscape {
29
                landscapeFace {
30
                    VStack(alignment: .leading, spacing: 12) {
31
                        connectionCard(compact: true, showsActions: meter.operationalState == .dataIsAvailable)
Bogdan Timofte authored 2 months ago
32
                        MeterOverviewSectionView(meter: meter)
Bogdan Timofte authored 2 months ago
33
                    }
34
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
35
                }
36
            } else {
37
                portraitFace {
38
                    VStack(alignment: .leading, spacing: 12) {
39
                        connectionCard(
Bogdan Timofte authored 2 months ago
40
                            compact: prefersCompactPortraitLayout,
Bogdan Timofte authored 2 months ago
41
                            showsActions: meter.operationalState == .dataIsAvailable
42
                        )
Bogdan Timofte authored 2 months ago
43
                        MeterOverviewSectionView(meter: meter)
Bogdan Timofte authored 2 months ago
44
                    }
45
                }
46
            }
47
        }
48
    }
49

            
Bogdan Timofte authored 2 months ago
50
    private var prefersCompactPortraitLayout: Bool {
Bogdan Timofte authored 2 months ago
51
        size.height < 760 || size.width < 380
52
    }
53

            
54
    private func portraitFace<Content: View>(@ViewBuilder content: () -> Content) -> some View {
55
        ScrollView {
56
            content()
57
                .frame(maxWidth: .infinity, alignment: .topLeading)
58
                .padding(.horizontal, pageHorizontalPadding)
59
                .padding(.vertical, pageVerticalPadding)
60
        }
61
    }
62

            
63
    private func landscapeFace<Content: View>(@ViewBuilder content: () -> Content) -> some View {
Bogdan Timofte authored 2 months ago
64
        ScrollView {
65
            content()
66
                .frame(maxWidth: .infinity, alignment: .topLeading)
67
                .padding(.horizontal, pageHorizontalPadding)
68
                .padding(.vertical, pageVerticalPadding)
69
        }
70
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
Bogdan Timofte authored 2 months ago
71
    }
72

            
73
    private func connectionCard(compact: Bool = false, showsActions: Bool = false) -> some View {
Bogdan Timofte authored 2 months ago
74
        let cardContent = VStack(alignment: .leading, spacing: compact ? 12 : 18) {
Bogdan Timofte authored 2 months ago
75
            HStack(alignment: .top) {
76
                meterIdentity(compact: compact)
77
                Spacer()
78
                statusBadge
79
            }
80

            
81
            connectionActionArea(compact: compact)
82

            
83
            if showsActions {
84
                VStack(spacing: compact ? 10 : 12) {
85
                    Rectangle()
86
                        .fill(Color.secondary.opacity(0.12))
87
                        .frame(height: 1)
88

            
89
                    actionGrid(compact: compact, embedded: true)
90
                }
91
            }
92
        }
93
        .padding(compact ? 16 : 20)
94
        .meterCard(tint: meter.color, fillOpacity: 0.22, strokeOpacity: 0.24)
Bogdan Timofte authored 2 months ago
95

            
Bogdan Timofte authored 2 months ago
96
        return cardContent
97
            .frame(maxWidth: .infinity, alignment: .topLeading)
Bogdan Timofte authored 2 months ago
98
    }
99

            
100
    private func meterIdentity(compact: Bool) -> some View {
101
        HStack(alignment: .firstTextBaseline, spacing: 8) {
102
            Text(meter.name)
103
                .font(.system(compact ? .title3 : .title2, design: .rounded).weight(.bold))
104
                .lineLimit(1)
105
                .minimumScaleFactor(0.8)
106

            
107
            Text(meter.deviceModelName)
108
                .font((compact ? Font.caption : .subheadline).weight(.semibold))
109
                .foregroundColor(.secondary)
110
                .lineLimit(1)
111
                .minimumScaleFactor(0.8)
112
        }
113
    }
114

            
115
    private func actionGrid(compact: Bool = false, embedded: Bool = false) -> some View {
116
        let currentActionHeight = compact ? CGFloat(86) : actionButtonHeight
117

            
118
        return GeometryReader { proxy in
119
            let buttonWidth = actionButtonWidth(for: proxy.size.width)
120
            let stripWidth = actionStripWidth(for: buttonWidth)
121
            let stripContent = HStack(spacing: 0) {
122
                meterSheetButton(icon: "square.grid.2x2.fill", title: meter.dataGroupsTitle, tint: .teal, width: buttonWidth, height: currentActionHeight, compact: compact) {
Bogdan Timofte authored a month ago
123
                    showDataGroupsTab()
Bogdan Timofte authored 2 months ago
124
                }
125

            
126
                if meter.supportsRecordingView {
127
                    actionStripDivider(height: currentActionHeight)
128
                    meterSheetButton(icon: "gauge.with.dots.needle.50percent", title: "Charge Record", tint: .pink, width: buttonWidth, height: currentActionHeight, compact: compact) {
Bogdan Timofte authored a month ago
129
                        showChargeRecordTab()
Bogdan Timofte authored 2 months ago
130
                    }
131
                }
132

            
133
                actionStripDivider(height: currentActionHeight)
Bogdan Timofte authored 2 months ago
134
                meterSheetButton(icon: "waveform.path.ecg", title: "Measurement Series", tint: .blue, width: buttonWidth, height: currentActionHeight, compact: compact) {
Bogdan Timofte authored 2 months ago
135
                    measurementsViewVisibility.toggle()
136
                }
137
                .sheet(isPresented: $measurementsViewVisibility) {
Bogdan Timofte authored 2 months ago
138
                    MeasurementSeriesSheetView(visibility: $measurementsViewVisibility)
Bogdan Timofte authored 2 months ago
139
                        .environmentObject(meter.measurements)
140
                }
141
            }
142
            .padding(actionStripPadding)
143
            .frame(width: stripWidth)
144

            
145
            HStack {
146
                Spacer(minLength: 0)
147
                stripContent
148
                    .meterCard(
149
                        tint: embedded ? meter.color : Color.secondary,
150
                        fillOpacity: embedded ? 0.08 : 0.10,
151
                        strokeOpacity: embedded ? 0.14 : 0.16,
152
                        cornerRadius: embedded ? 24 : 22
153
                    )
154
                Spacer(minLength: 0)
155
            }
156
        }
157
        .frame(height: currentActionHeight + (actionStripPadding * 2))
158
    }
159

            
160
    private func connectionActionArea(compact: Bool = false) -> some View {
Bogdan Timofte authored 2 months ago
161
        MeterConnectionActionView(
Bogdan Timofte authored 2 months ago
162
            operationalState: meter.operationalState,
Bogdan Timofte authored 2 months ago
163
            compact: compact
Bogdan Timofte authored 2 months ago
164
        )
165
    }
166

            
167
    private func meterSheetButton(icon: String, title: String, tint: Color, width: CGFloat, height: CGFloat, compact: Bool = false, action: @escaping () -> Void) -> some View {
168
        Button(action: action) {
169
            VStack(spacing: compact ? 8 : 10) {
170
                Image(systemName: icon)
171
                    .font(.system(size: compact ? 18 : 20, weight: .semibold))
172
                    .frame(width: compact ? 34 : 40, height: compact ? 34 : 40)
173
                    .background(Circle().fill(tint.opacity(0.14)))
174
                Text(title)
175
                    .font((compact ? Font.caption : .footnote).weight(.semibold))
176
                    .multilineTextAlignment(.center)
177
                    .lineLimit(2)
178
                    .minimumScaleFactor(0.9)
179
            }
180
            .foregroundColor(tint)
181
            .frame(width: width, height: height)
182
            .contentShape(Rectangle())
183
        }
184
        .buttonStyle(.plain)
185
    }
186

            
187
    private var visibleActionButtonCount: CGFloat {
188
        meter.supportsRecordingView ? 3 : 2
189
    }
190

            
191
    private func actionButtonWidth(for availableWidth: CGFloat) -> CGFloat {
192
        let dividerWidth = actionDividerWidth * max(visibleActionButtonCount - 1, 0)
193
        let contentWidth = availableWidth - (actionStripPadding * 2) - dividerWidth
194
        let fittedWidth = floor(contentWidth / visibleActionButtonCount)
195
        return min(actionButtonMaxWidth, max(actionButtonMinWidth, fittedWidth))
196
    }
197

            
198
    private func actionStripWidth(for buttonWidth: CGFloat) -> CGFloat {
199
        let dividerWidth = actionDividerWidth * max(visibleActionButtonCount - 1, 0)
200
        return (buttonWidth * visibleActionButtonCount) + dividerWidth + (actionStripPadding * 2)
201
    }
202

            
203
    private func actionStripDivider(height: CGFloat) -> some View {
204
        Rectangle()
205
            .fill(Color.secondary.opacity(0.16))
206
            .frame(width: actionDividerWidth, height: max(44, height - 22))
207
    }
208

            
209
    private var statusBadge: some View {
Bogdan Timofte authored 2 months ago
210
        MeterConnectionStatusBadgeView(text: statusText, color: statusColor)
Bogdan Timofte authored 2 months ago
211
    }
212

            
213
    private var statusText: String {
214
        switch meter.operationalState {
215
        case .notPresent:
216
            return "Missing"
217
        case .peripheralNotConnected:
218
            return "Ready"
219
        case .peripheralConnectionPending:
220
            return "Connecting"
221
        case .peripheralConnected:
222
            return "Linked"
223
        case .peripheralReady:
224
            return "Preparing"
225
        case .comunicating:
226
            return "Syncing"
227
        case .dataIsAvailable:
228
            return "Live"
229
        }
230
    }
231

            
232
    private var statusColor: Color {
233
        Meter.operationalColor(for: meter.operationalState)
234
    }
235
}