Showing 31 changed files with 762 additions and 392 deletions
+106 -0
Documentation/Project Structure and Naming.md
@@ -0,0 +1,106 @@
1
+# Project Structure and Naming
2
+
3
+This document defines how we name SwiftUI views and how we place files in the project so the codebase matches the product language and the UI hierarchy.
4
+
5
+## Core Rules
6
+
7
+- Name folders after the feature or navigation surface the user sees.
8
+  - Example: the Home tab lives in `Views/Meter/Tabs/Home/`, not in `Connection/`.
9
+- Name the main view in a folder after the feature it owns.
10
+  - Example: `MeterHomeTabView.swift` is the root view for the meter Home tab.
11
+- Keep the file name and the Swift type name identical.
12
+  - Example: `MeterOverviewSectionView.swift` contains `MeterOverviewSectionView`.
13
+- Prefer product language over implementation history.
14
+  - If the app says "Home", use `Home` in code instead of older terms like `Connection`.
15
+- Use `Components/` only for reusable building blocks.
16
+  - A component is used in multiple views or is clearly meant to be reused.
17
+  - Example: `MeterInfoCardView` and `MeterInfoRowView`.
18
+- Use `Subviews/` for views that belong to a single parent feature.
19
+  - A subview is used once or is tightly coupled to one screen/tab.
20
+  - Example: `Views/Meter/Tabs/Home/Subviews/MeterOverviewSectionView.swift`.
21
+- Use `Sheets/` for modal flows presented from another screen.
22
+  - Example: `Views/Meter/Sheets/AppHistory/AppHistorySheetView.swift`.
23
+- Keep reusable components close to the narrowest shared owner.
24
+  - If a component is reused only inside Meter screens, keep it in `Views/Meter/Components/`.
25
+  - Do not move it to app-wide `Views/Components/` unless it is truly generic.
26
+- Keep small support types next to their owner.
27
+  - If a helper type exists for one view only, keep it in the same file or the same feature subfolder.
28
+- Avoid vague verbs or placeholder names.
29
+  - Prefer `MeterNameEditorView` over `EditNameView`.
30
+  - Prefer `MeterConnectionActionView` over `ConnectionPrimaryActionView`.
31
+- A folder should describe ownership, not implementation detail.
32
+  - `Home/Subviews/` is better than `Connection/Components/` when the views are single-use parts of the Home tab.
33
+
34
+## Naming Checklist
35
+
36
+Before adding or renaming a file, check:
37
+
38
+- Can someone guess the file location from the screen name?
39
+- Does the type name say what the view renders?
40
+- Is this reused enough to deserve `Components/`?
41
+- If it is single-use, does it live under the parent feature's `Subviews/` folder?
42
+- Does the code use the same words as the UI?
43
+
44
+## Current Meter Tab Pattern
45
+
46
+Use this structure for Meter tab work:
47
+
48
+```text
49
+Views/Meter/
50
+  Components/
51
+    MeasurementChartView.swift
52
+    MeterInfoCardView.swift
53
+    MeterInfoRowView.swift
54
+  Sheets/
55
+    AppHistory/
56
+      AppHistorySheetView.swift
57
+      Subviews/
58
+        AppHistorySampleView.swift
59
+    ChargeRecord/
60
+      ChargeRecordSheetView.swift
61
+      Subviews/
62
+        ChargeRecordMetricsTableView.swift
63
+    DataGroups/
64
+      DataGroupsSheetView.swift
65
+      Subviews/
66
+        DataGroupRowView.swift
67
+  Tabs/
68
+    Home/
69
+      MeterHomeTabView.swift
70
+      Subviews/
71
+        MeterConnectionActionView.swift
72
+        MeterConnectionStatusBadgeView.swift
73
+        MeterOverviewSectionView.swift
74
+    Live/
75
+      MeterLiveTabView.swift
76
+      Subviews/
77
+        LoadResistanceIconView.swift
78
+        MeterLiveContentView.swift
79
+        MeterLiveMetricRange.swift
80
+    Chart/
81
+      MeterChartTabView.swift
82
+    Settings/
83
+      MeterSettingsTabView.swift
84
+      Subviews/
85
+        MeterCurrentScreenSummaryView.swift
86
+        MeterNameEditorView.swift
87
+        MeterScreenControlButtonView.swift
88
+        MeterScreenControlsView.swift
89
+        ScreenBrightnessEditorView.swift
90
+        ScreenTimeoutEditorView.swift
91
+```
92
+
93
+## Refactor Examples
94
+
95
+- `Connection/` -> `Home/`
96
+- `MeterConnectionTabView` -> `MeterHomeTabView`
97
+- `ConnectionHomeInfoPreviewView` -> `MeterOverviewSectionView`
98
+- `ConnectionPrimaryActionView` -> `MeterConnectionActionView`
99
+- `EditNameView` -> `MeterNameEditorView`
100
+- `MeasurementsView` -> `AppHistorySheetView`
101
+- `RecordingView` -> `ChargeRecordSheetView`
102
+- `ControlView` -> `MeterScreenControlsView`
103
+
104
+## Decision Rule
105
+
106
+If a new name makes a teammate look in the right folder on the first try, it is probably a good name.
+2 -0
Documentation/README.md
@@ -10,6 +10,8 @@ It is intended to keep the repository root focused on the app itself while prese
10 10
   Narrative context and decisions that explain how the project got here.
11 11
 - `Platform Decision - iOS 15.md`
12 12
   App-level platform choices that affect implementation.
13
+- `Project Structure and Naming.md`
14
+  Naming and file-organization rules for views, features, components, and subviews.
13 15
 - `Research Resources/`
14 16
   External source material plus the notes derived from it.
15 17
 
+126 -116
USB Meter.xcodeproj/project.pbxproj
@@ -9,44 +9,44 @@
9 9
 /* Begin PBXBuildFile section */
10 10
 		3407A133FADB8858DC2A1FED /* MeterNameStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7396C8BB36F4E7F8E0CD8FF8 /* MeterNameStore.swift */; };
11 11
 		4308CF8624176CAB0002E80B /* DataGroupRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4308CF8524176CAB0002E80B /* DataGroupRowView.swift */; };
12
-		4308CF882417770D0002E80B /* DataGroupsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4308CF872417770D0002E80B /* DataGroupsView.swift */; };
12
+		4308CF882417770D0002E80B /* DataGroupsSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4308CF872417770D0002E80B /* DataGroupsSheetView.swift */; };
13 13
 		430CB4FC245E07EB006525C2 /* ChevronView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430CB4FB245E07EB006525C2 /* ChevronView.swift */; };
14 14
 		4311E63A241384960080EA59 /* DeviceHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4311E639241384960080EA59 /* DeviceHelpView.swift */; };
15 15
 		4327461B24619CED0009BE4B /* MeterRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4327461A24619CED0009BE4B /* MeterRowView.swift */; };
16 16
 		432EA6442445A559006FC905 /* ChartContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432EA6432445A559006FC905 /* ChartContext.swift */; };
17 17
 		4347F01D28D717C1007EE7B1 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 4347F01C28D717C1007EE7B1 /* CryptoSwift */; };
18 18
 		4351E7BB24685ACD00E798A3 /* CGPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4351E7BA24685ACD00E798A3 /* CGPoint.swift */; };
19
-		43554B2F24443939004E66F5 /* MeasurementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43554B2E24443939004E66F5 /* MeasurementsView.swift */; };
20
-		43554B32244449B5004E66F5 /* MeasurementPointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43554B31244449B5004E66F5 /* MeasurementPointView.swift */; };
19
+		43554B2F24443939004E66F5 /* AppHistorySheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43554B2E24443939004E66F5 /* AppHistorySheetView.swift */; };
20
+		43554B32244449B5004E66F5 /* AppHistorySampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43554B31244449B5004E66F5 /* AppHistorySampleView.swift */; };
21 21
 		43554B3424444B0E004E66F5 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43554B3324444B0E004E66F5 /* Date.swift */; };
22 22
 		4360A34D241CBB3800B464F9 /* RSSIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4360A34C241CBB3800B464F9 /* RSSIView.swift */; };
23
-		437D47D12415F91B00B7768E /* LiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437D47D02415F91B00B7768E /* LiveView.swift */; };
23
+		437D47D12415F91B00B7768E /* MeterLiveContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437D47D02415F91B00B7768E /* MeterLiveContentView.swift */; };
24 24
 		437D47D32415FB7E00B7768E /* Decimal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437D47D22415FB7E00B7768E /* Decimal.swift */; };
25
-		437D47D52415FD8C00B7768E /* RecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437D47D42415FD8C00B7768E /* RecordingView.swift */; };
26
-		437D47D72415FDF300B7768E /* ControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437D47D62415FDF300B7768E /* ControlView.swift */; };
25
+		437D47D52415FD8C00B7768E /* ChargeRecordSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437D47D42415FD8C00B7768E /* ChargeRecordSheetView.swift */; };
26
+		437D47D72415FDF300B7768E /* MeterScreenControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437D47D62415FDF300B7768E /* MeterScreenControlsView.swift */; };
27 27
 		437F0AB72463108F005DEBEC /* MeasurementChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437F0AB62463108F005DEBEC /* MeasurementChartView.swift */; };
28 28
 		4383B460240EB2D000DAAEBF /* Meter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4383B45F240EB2D000DAAEBF /* Meter.swift */; };
29 29
 		4383B462240EB5E400DAAEBF /* AppData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4383B461240EB5E400DAAEBF /* AppData.swift */; };
30 30
 		4383B465240EB6B200DAAEBF /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4383B464240EB6B200DAAEBF /* UserDefault.swift */; };
31 31
 		4383B468240F845500DAAEBF /* MacAdress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4383B467240F845500DAAEBF /* MacAdress.swift */; };
32 32
 		4383B46A240FE4A600DAAEBF /* MeterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4383B469240FE4A600DAAEBF /* MeterView.swift */; };
33
-		D28F11013C8E4A7A00A10011 /* MeterConnectionTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11023C8E4A7A00A10012 /* MeterConnectionTabView.swift */; };
33
+		D28F11013C8E4A7A00A10011 /* MeterHomeTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11023C8E4A7A00A10012 /* MeterHomeTabView.swift */; };
34 34
 		D28F11033C8E4A7A00A10013 /* MeterLiveTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11043C8E4A7A00A10014 /* MeterLiveTabView.swift */; };
35 35
 		D28F11053C8E4A7A00A10015 /* MeterChartTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11063C8E4A7A00A10016 /* MeterChartTabView.swift */; };
36 36
 		D28F11073C8E4A7A00A10017 /* MeterSettingsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11083C8E4A7A00A10018 /* MeterSettingsTabView.swift */; };
37
-		D28F11113C8E4A7A00A10021 /* MeterInfoCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11123C8E4A7A00A10022 /* MeterInfoCard.swift */; };
38
-		D28F11133C8E4A7A00A10023 /* MeterInfoRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11143C8E4A7A00A10024 /* MeterInfoRow.swift */; };
39
-		D28F11153C8E4A7A00A10025 /* EditNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11163C8E4A7A00A10026 /* EditNameView.swift */; };
40
-		D28F11173C8E4A7A00A10027 /* EditScreenTimeoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11183C8E4A7A00A10028 /* EditScreenTimeoutView.swift */; };
41
-		D28F11193C8E4A7A00A10029 /* EditScreenBrightnessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F111A3C8E4A7A00A1002A /* EditScreenBrightnessView.swift */; };
42
-		D28F11213C8E4A7A00A10031 /* LiveMetricRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11223C8E4A7A00A10032 /* LiveMetricRange.swift */; };
43
-		D28F11233C8E4A7A00A10033 /* LoadResistanceSymbolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11243C8E4A7A00A10034 /* LoadResistanceSymbolView.swift */; };
44
-		D28F11313C8E4A7A00A10041 /* ControlActionButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11323C8E4A7A00A10042 /* ControlActionButtonView.swift */; };
45
-		D28F11333C8E4A7A00A10043 /* ControlCurrentScreenCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11343C8E4A7A00A10044 /* ControlCurrentScreenCardView.swift */; };
46
-		D28F11353C8E4A7A00A10045 /* RecordingMetricsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11363C8E4A7A00A10046 /* RecordingMetricsTableView.swift */; };
47
-		D28F11393C8E4A7A00A10049 /* ConnectionStatusBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F113A3C8E4A7A00A1004A /* ConnectionStatusBadgeView.swift */; };
48
-		D28F113B3C8E4A7A00A1004B /* ConnectionPrimaryActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F113C3C8E4A7A00A1004C /* ConnectionPrimaryActionView.swift */; };
49
-		D28F113D3C8E4A7A00A1004D /* ConnectionHomeInfoPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F113E3C8E4A7A00A1004E /* ConnectionHomeInfoPreviewView.swift */; };
37
+		D28F11113C8E4A7A00A10021 /* MeterInfoCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11123C8E4A7A00A10022 /* MeterInfoCardView.swift */; };
38
+		D28F11133C8E4A7A00A10023 /* MeterInfoRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11143C8E4A7A00A10024 /* MeterInfoRowView.swift */; };
39
+		D28F11153C8E4A7A00A10025 /* MeterNameEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11163C8E4A7A00A10026 /* MeterNameEditorView.swift */; };
40
+		D28F11173C8E4A7A00A10027 /* ScreenTimeoutEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11183C8E4A7A00A10028 /* ScreenTimeoutEditorView.swift */; };
41
+		D28F11193C8E4A7A00A10029 /* ScreenBrightnessEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F111A3C8E4A7A00A1002A /* ScreenBrightnessEditorView.swift */; };
42
+		D28F11213C8E4A7A00A10031 /* MeterLiveMetricRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11223C8E4A7A00A10032 /* MeterLiveMetricRange.swift */; };
43
+		D28F11233C8E4A7A00A10033 /* LoadResistanceIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11243C8E4A7A00A10034 /* LoadResistanceIconView.swift */; };
44
+		D28F11313C8E4A7A00A10041 /* MeterScreenControlButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11323C8E4A7A00A10042 /* MeterScreenControlButtonView.swift */; };
45
+		D28F11333C8E4A7A00A10043 /* MeterCurrentScreenSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11343C8E4A7A00A10044 /* MeterCurrentScreenSummaryView.swift */; };
46
+		D28F11353C8E4A7A00A10045 /* ChargeRecordMetricsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11363C8E4A7A00A10046 /* ChargeRecordMetricsTableView.swift */; };
47
+		D28F11393C8E4A7A00A10049 /* MeterConnectionStatusBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F113A3C8E4A7A00A1004A /* MeterConnectionStatusBadgeView.swift */; };
48
+		D28F113B3C8E4A7A00A1004B /* MeterConnectionActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F113C3C8E4A7A00A1004C /* MeterConnectionActionView.swift */; };
49
+		D28F113D3C8E4A7A00A1004D /* MeterOverviewSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F113E3C8E4A7A00A1004E /* MeterOverviewSectionView.swift */; };
50 50
 		438695892463F062008855A9 /* Measurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438695882463F062008855A9 /* Measurements.swift */; };
51 51
 		4386958B2F6A1001008855A9 /* UMProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4386958A2F6A1001008855A9 /* UMProtocol.swift */; };
52 52
 		4386958D2F6A1002008855A9 /* TC66Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4386958C2F6A1002008855A9 /* TC66Protocol.swift */; };
@@ -104,43 +104,43 @@
104 104
 		1C6B6BB32A2D4F5100A0B001 /* Users-Manual-4216091.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = "Users-Manual-4216091.pdf"; sourceTree = "<group>"; };
105 105
 		1C6B6BB42A2D4F5100A0B001 /* HM-10 and DX-BT18 Module Working Summary.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "HM-10 and DX-BT18 Module Working Summary.md"; sourceTree = "<group>"; };
106 106
 		4308CF8524176CAB0002E80B /* DataGroupRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGroupRowView.swift; sourceTree = "<group>"; };
107
-		4308CF872417770D0002E80B /* DataGroupsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGroupsView.swift; sourceTree = "<group>"; };
107
+		4308CF872417770D0002E80B /* DataGroupsSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGroupsSheetView.swift; sourceTree = "<group>"; };
108 108
 		430CB4FB245E07EB006525C2 /* ChevronView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronView.swift; sourceTree = "<group>"; };
109 109
 		4311E639241384960080EA59 /* DeviceHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceHelpView.swift; sourceTree = "<group>"; };
110 110
 		4327461A24619CED0009BE4B /* MeterRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterRowView.swift; sourceTree = "<group>"; };
111 111
 		432EA6432445A559006FC905 /* ChartContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartContext.swift; sourceTree = "<group>"; };
112 112
 		4351E7BA24685ACD00E798A3 /* CGPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPoint.swift; sourceTree = "<group>"; };
113
-		43554B2E24443939004E66F5 /* MeasurementsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasurementsView.swift; sourceTree = "<group>"; };
114
-		43554B31244449B5004E66F5 /* MeasurementPointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasurementPointView.swift; sourceTree = "<group>"; };
113
+		43554B2E24443939004E66F5 /* AppHistorySheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHistorySheetView.swift; sourceTree = "<group>"; };
114
+		43554B31244449B5004E66F5 /* AppHistorySampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHistorySampleView.swift; sourceTree = "<group>"; };
115 115
 		43554B3324444B0E004E66F5 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
116 116
 		4360A34C241CBB3800B464F9 /* RSSIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSIView.swift; sourceTree = "<group>"; };
117
-		437D47D02415F91B00B7768E /* LiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveView.swift; sourceTree = "<group>"; };
117
+		437D47D02415F91B00B7768E /* MeterLiveContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterLiveContentView.swift; sourceTree = "<group>"; };
118 118
 		437D47D22415FB7E00B7768E /* Decimal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Decimal.swift; sourceTree = "<group>"; };
119
-		437D47D42415FD8C00B7768E /* RecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingView.swift; sourceTree = "<group>"; };
120
-		437D47D62415FDF300B7768E /* ControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlView.swift; sourceTree = "<group>"; };
119
+		437D47D42415FD8C00B7768E /* ChargeRecordSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargeRecordSheetView.swift; sourceTree = "<group>"; };
120
+		437D47D62415FDF300B7768E /* MeterScreenControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterScreenControlsView.swift; sourceTree = "<group>"; };
121 121
 		437F0AB62463108F005DEBEC /* MeasurementChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasurementChartView.swift; sourceTree = "<group>"; };
122 122
 		4383B45F240EB2D000DAAEBF /* Meter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Meter.swift; sourceTree = "<group>"; };
123 123
 		4383B461240EB5E400DAAEBF /* AppData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppData.swift; sourceTree = "<group>"; };
124 124
 		4383B464240EB6B200DAAEBF /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = "<group>"; };
125 125
 		4383B467240F845500DAAEBF /* MacAdress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAdress.swift; sourceTree = "<group>"; };
126 126
 		4383B469240FE4A600DAAEBF /* MeterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterView.swift; sourceTree = "<group>"; };
127
-		D28F11023C8E4A7A00A10012 /* MeterConnectionTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterConnectionTabView.swift; sourceTree = "<group>"; };
127
+		D28F11023C8E4A7A00A10012 /* MeterHomeTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterHomeTabView.swift; sourceTree = "<group>"; };
128 128
 		D28F11043C8E4A7A00A10014 /* MeterLiveTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterLiveTabView.swift; sourceTree = "<group>"; };
129 129
 		D28F11063C8E4A7A00A10016 /* MeterChartTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterChartTabView.swift; sourceTree = "<group>"; };
130 130
 		D28F11083C8E4A7A00A10018 /* MeterSettingsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterSettingsTabView.swift; sourceTree = "<group>"; };
131
-		D28F11123C8E4A7A00A10022 /* MeterInfoCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterInfoCard.swift; sourceTree = "<group>"; };
132
-		D28F11143C8E4A7A00A10024 /* MeterInfoRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterInfoRow.swift; sourceTree = "<group>"; };
133
-		D28F11163C8E4A7A00A10026 /* EditNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditNameView.swift; sourceTree = "<group>"; };
134
-		D28F11183C8E4A7A00A10028 /* EditScreenTimeoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditScreenTimeoutView.swift; sourceTree = "<group>"; };
135
-		D28F111A3C8E4A7A00A1002A /* EditScreenBrightnessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditScreenBrightnessView.swift; sourceTree = "<group>"; };
136
-		D28F11223C8E4A7A00A10032 /* LiveMetricRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveMetricRange.swift; sourceTree = "<group>"; };
137
-		D28F11243C8E4A7A00A10034 /* LoadResistanceSymbolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadResistanceSymbolView.swift; sourceTree = "<group>"; };
138
-		D28F11323C8E4A7A00A10042 /* ControlActionButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlActionButtonView.swift; sourceTree = "<group>"; };
139
-		D28F11343C8E4A7A00A10044 /* ControlCurrentScreenCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlCurrentScreenCardView.swift; sourceTree = "<group>"; };
140
-		D28F11363C8E4A7A00A10046 /* RecordingMetricsTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingMetricsTableView.swift; sourceTree = "<group>"; };
141
-		D28F113A3C8E4A7A00A1004A /* ConnectionStatusBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionStatusBadgeView.swift; sourceTree = "<group>"; };
142
-		D28F113C3C8E4A7A00A1004C /* ConnectionPrimaryActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionPrimaryActionView.swift; sourceTree = "<group>"; };
143
-		D28F113E3C8E4A7A00A1004E /* ConnectionHomeInfoPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionHomeInfoPreviewView.swift; sourceTree = "<group>"; };
131
+		D28F11123C8E4A7A00A10022 /* MeterInfoCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterInfoCardView.swift; sourceTree = "<group>"; };
132
+		D28F11143C8E4A7A00A10024 /* MeterInfoRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterInfoRowView.swift; sourceTree = "<group>"; };
133
+		D28F11163C8E4A7A00A10026 /* MeterNameEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterNameEditorView.swift; sourceTree = "<group>"; };
134
+		D28F11183C8E4A7A00A10028 /* ScreenTimeoutEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTimeoutEditorView.swift; sourceTree = "<group>"; };
135
+		D28F111A3C8E4A7A00A1002A /* ScreenBrightnessEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenBrightnessEditorView.swift; sourceTree = "<group>"; };
136
+		D28F11223C8E4A7A00A10032 /* MeterLiveMetricRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterLiveMetricRange.swift; sourceTree = "<group>"; };
137
+		D28F11243C8E4A7A00A10034 /* LoadResistanceIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadResistanceIconView.swift; sourceTree = "<group>"; };
138
+		D28F11323C8E4A7A00A10042 /* MeterScreenControlButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterScreenControlButtonView.swift; sourceTree = "<group>"; };
139
+		D28F11343C8E4A7A00A10044 /* MeterCurrentScreenSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterCurrentScreenSummaryView.swift; sourceTree = "<group>"; };
140
+		D28F11363C8E4A7A00A10046 /* ChargeRecordMetricsTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargeRecordMetricsTableView.swift; sourceTree = "<group>"; };
141
+		D28F113A3C8E4A7A00A1004A /* MeterConnectionStatusBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterConnectionStatusBadgeView.swift; sourceTree = "<group>"; };
142
+		D28F113C3C8E4A7A00A1004C /* MeterConnectionActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterConnectionActionView.swift; sourceTree = "<group>"; };
143
+		D28F113E3C8E4A7A00A1004E /* MeterOverviewSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterOverviewSectionView.swift; sourceTree = "<group>"; };
144 144
 		438695882463F062008855A9 /* Measurements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurements.swift; sourceTree = "<group>"; };
145 145
 		4386958A2F6A1001008855A9 /* UMProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UMProtocol.swift; sourceTree = "<group>"; };
146 146
 		4386958C2F6A1002008855A9 /* TC66Protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TC66Protocol.swift; sourceTree = "<group>"; };
@@ -279,21 +279,21 @@
279 279
 			path = "Vendor Contacts";
280 280
 			sourceTree = "<group>";
281 281
 		};
282
-		4308CF89241777130002E80B /* Data Groups */ = {
282
+		4308CF89241777130002E80B /* DataGroups */ = {
283 283
 			isa = PBXGroup;
284 284
 			children = (
285
-				4308CF872417770D0002E80B /* DataGroupsView.swift */,
286
-				4308CF8524176CAB0002E80B /* DataGroupRowView.swift */,
285
+				4308CF872417770D0002E80B /* DataGroupsSheetView.swift */,
286
+				D28F11263C8E4A7A00A10036 /* Subviews */,
287 287
 			);
288
-			path = "Data Groups";
288
+			path = DataGroups;
289 289
 			sourceTree = "<group>";
290 290
 		};
291
-		432F6ED8246684060043912E /* Chart */ = {
291
+		432F6ED8246684060043912E /* Subviews */ = {
292 292
 			isa = PBXGroup;
293 293
 			children = (
294
-				437F0AB62463108F005DEBEC /* MeasurementChartView.swift */,
294
+				43554B31244449B5004E66F5 /* AppHistorySampleView.swift */,
295 295
 			);
296
-			path = Chart;
296
+			path = Subviews;
297 297
 			sourceTree = "<group>";
298 298
 		};
299 299
 		4347F01B28D717C1007EE7B1 /* Frameworks */ = {
@@ -303,114 +303,115 @@
303 303
 			name = Frameworks;
304 304
 			sourceTree = "<group>";
305 305
 		};
306
-		43554B3024444983004E66F5 /* Measurements */ = {
306
+		43554B3024444983004E66F5 /* AppHistory */ = {
307 307
 			isa = PBXGroup;
308 308
 			children = (
309
-				43554B2E24443939004E66F5 /* MeasurementsView.swift */,
310
-				43554B31244449B5004E66F5 /* MeasurementPointView.swift */,
311
-				432F6ED8246684060043912E /* Chart */,
309
+				43554B2E24443939004E66F5 /* AppHistorySheetView.swift */,
310
+				432F6ED8246684060043912E /* Subviews */,
312 311
 			);
313
-			path = Measurements;
312
+			path = AppHistory;
314 313
 			sourceTree = "<group>";
315 314
 		};
316
-		D28F11253C8E4A7A00A10035 /* Components */ = {
315
+		D28F11253C8E4A7A00A10035 /* Subviews */ = {
317 316
 			isa = PBXGroup;
318 317
 			children = (
319
-				D28F11223C8E4A7A00A10032 /* LiveMetricRange.swift */,
320
-				D28F11243C8E4A7A00A10034 /* LoadResistanceSymbolView.swift */,
318
+				437D47D02415F91B00B7768E /* MeterLiveContentView.swift */,
319
+				D28F11223C8E4A7A00A10032 /* MeterLiveMetricRange.swift */,
320
+				D28F11243C8E4A7A00A10034 /* LoadResistanceIconView.swift */,
321 321
 			);
322
-			path = Components;
322
+			path = Subviews;
323 323
 			sourceTree = "<group>";
324 324
 		};
325
-		D28F11263C8E4A7A00A10036 /* Live */ = {
325
+		D28F11263C8E4A7A00A10036 /* Subviews */ = {
326 326
 			isa = PBXGroup;
327 327
 			children = (
328
-				437D47D02415F91B00B7768E /* LiveView.swift */,
329
-				D28F11253C8E4A7A00A10035 /* Components */,
328
+				4308CF8524176CAB0002E80B /* DataGroupRowView.swift */,
330 329
 			);
331
-			path = Live;
330
+			path = Subviews;
332 331
 			sourceTree = "<group>";
333 332
 		};
334 333
 		D28F11373C8E4A7A00A10047 /* Components */ = {
335 334
 			isa = PBXGroup;
336 335
 			children = (
337
-				D28F11323C8E4A7A00A10042 /* ControlActionButtonView.swift */,
338
-				D28F11343C8E4A7A00A10044 /* ControlCurrentScreenCardView.swift */,
336
+				D28F11323C8E4A7A00A10042 /* MeterScreenControlButtonView.swift */,
337
+				D28F11343C8E4A7A00A10044 /* MeterCurrentScreenSummaryView.swift */,
339 338
 			);
340 339
 			path = Components;
341 340
 			sourceTree = "<group>";
342 341
 		};
343
-		D28F11383C8E4A7A00A10048 /* Components */ = {
342
+		D28F11383C8E4A7A00A10048 /* Subviews */ = {
344 343
 			isa = PBXGroup;
345 344
 			children = (
346
-				D28F11363C8E4A7A00A10046 /* RecordingMetricsTableView.swift */,
345
+				D28F11363C8E4A7A00A10046 /* ChargeRecordMetricsTableView.swift */,
347 346
 			);
348
-			path = Components;
347
+			path = Subviews;
349 348
 			sourceTree = "<group>";
350 349
 		};
351
-		D28F11273C8E4A7A00A10037 /* Recording */ = {
350
+		D28F11273C8E4A7A00A10037 /* ChargeRecord */ = {
352 351
 			isa = PBXGroup;
353 352
 			children = (
354
-				437D47D42415FD8C00B7768E /* RecordingView.swift */,
355
-				D28F11383C8E4A7A00A10048 /* Components */,
353
+				437D47D42415FD8C00B7768E /* ChargeRecordSheetView.swift */,
354
+				D28F11383C8E4A7A00A10048 /* Subviews */,
356 355
 			);
357
-			path = Recording;
356
+			path = ChargeRecord;
358 357
 			sourceTree = "<group>";
359 358
 		};
360 359
 		D28F11283C8E4A7A00A10038 /* Control */ = {
361 360
 			isa = PBXGroup;
362 361
 			children = (
363
-				437D47D62415FDF300B7768E /* ControlView.swift */,
362
+				437D47D62415FDF300B7768E /* MeterScreenControlsView.swift */,
364 363
 				D28F11373C8E4A7A00A10047 /* Components */,
365 364
 			);
366 365
 			path = Control;
367 366
 			sourceTree = "<group>";
368 367
 		};
369
-		D28F10013C8E4A7A00A10001 /* Screens */ = {
368
+		D28F10013C8E4A7A00A10001 /* Sheets */ = {
370 369
 			isa = PBXGroup;
371 370
 			children = (
372
-				D28F11263C8E4A7A00A10036 /* Live */,
373
-				D28F11273C8E4A7A00A10037 /* Recording */,
374
-				D28F11283C8E4A7A00A10038 /* Control */,
371
+				4308CF89241777130002E80B /* DataGroups */,
372
+				43554B3024444983004E66F5 /* AppHistory */,
373
+				D28F11273C8E4A7A00A10037 /* ChargeRecord */,
375 374
 			);
376
-			path = Screens;
375
+			path = Sheets;
377 376
 			sourceTree = "<group>";
378 377
 		};
379
-		D28F111B3C8E4A7A00A1002B /* Components */ = {
378
+		D28F111B3C8E4A7A00A1002B /* Subviews */ = {
380 379
 			isa = PBXGroup;
381 380
 			children = (
382
-				D28F11123C8E4A7A00A10022 /* MeterInfoCard.swift */,
383
-				D28F11143C8E4A7A00A10024 /* MeterInfoRow.swift */,
384
-				D28F113A3C8E4A7A00A1004A /* ConnectionStatusBadgeView.swift */,
385
-				D28F113C3C8E4A7A00A1004C /* ConnectionPrimaryActionView.swift */,
386
-				D28F113E3C8E4A7A00A1004E /* ConnectionHomeInfoPreviewView.swift */,
381
+				D28F113A3C8E4A7A00A1004A /* MeterConnectionStatusBadgeView.swift */,
382
+				D28F113C3C8E4A7A00A1004C /* MeterConnectionActionView.swift */,
383
+				D28F113E3C8E4A7A00A1004E /* MeterOverviewSectionView.swift */,
387 384
 			);
388
-			path = Components;
385
+			path = Subviews;
389 386
 			sourceTree = "<group>";
390 387
 		};
391
-		D28F111C3C8E4A7A00A1002C /* Components */ = {
388
+		D28F111C3C8E4A7A00A1002C /* Subviews */ = {
392 389
 			isa = PBXGroup;
393 390
 			children = (
394
-				D28F11163C8E4A7A00A10026 /* EditNameView.swift */,
395
-				D28F11183C8E4A7A00A10028 /* EditScreenTimeoutView.swift */,
396
-				D28F111A3C8E4A7A00A1002A /* EditScreenBrightnessView.swift */,
391
+				D28F11163C8E4A7A00A10026 /* MeterNameEditorView.swift */,
392
+				D28F11183C8E4A7A00A10028 /* ScreenTimeoutEditorView.swift */,
393
+				D28F111A3C8E4A7A00A1002A /* ScreenBrightnessEditorView.swift */,
394
+				437D47D62415FDF300B7768E /* MeterScreenControlsView.swift */,
395
+				D28F11323C8E4A7A00A10042 /* MeterScreenControlButtonView.swift */,
396
+				D28F11343C8E4A7A00A10044 /* MeterCurrentScreenSummaryView.swift */,
397 397
 			);
398
-			path = Components;
398
+			path = Subviews;
399 399
 			sourceTree = "<group>";
400 400
 		};
401
-		D28F110A3C8E4A7A00A1001A /* Connection */ = {
401
+		D28F110A3C8E4A7A00A1001A /* Home */ = {
402 402
 			isa = PBXGroup;
403 403
 			children = (
404
-				D28F11023C8E4A7A00A10012 /* MeterConnectionTabView.swift */,
405
-				D28F111B3C8E4A7A00A1002B /* Components */,
404
+				D28F11023C8E4A7A00A10012 /* MeterHomeTabView.swift */,
405
+				D28F111B3C8E4A7A00A1002B /* Subviews */,
406 406
 			);
407
-			path = Connection;
407
+			path = Home;
408 408
 			sourceTree = "<group>";
409 409
 		};
410 410
 		D28F110B3C8E4A7A00A1001B /* Live */ = {
411 411
 			isa = PBXGroup;
412 412
 			children = (
413 413
 				D28F11043C8E4A7A00A10014 /* MeterLiveTabView.swift */,
414
+				D28F11253C8E4A7A00A10035 /* Subviews */,
414 415
 			);
415 416
 			path = Live;
416 417
 			sourceTree = "<group>";
@@ -427,7 +428,7 @@
427 428
 			isa = PBXGroup;
428 429
 			children = (
429 430
 				D28F11083C8E4A7A00A10018 /* MeterSettingsTabView.swift */,
430
-				D28F111C3C8E4A7A00A1002C /* Components */,
431
+				D28F111C3C8E4A7A00A1002C /* Subviews */,
431 432
 			);
432 433
 			path = Settings;
433 434
 			sourceTree = "<group>";
@@ -435,7 +436,7 @@
435 436
 		D28F11093C8E4A7A00A10019 /* Tabs */ = {
436 437
 			isa = PBXGroup;
437 438
 			children = (
438
-				D28F110A3C8E4A7A00A1001A /* Connection */,
439
+				D28F110A3C8E4A7A00A1001A /* Home */,
439 440
 				D28F110B3C8E4A7A00A1001B /* Live */,
440 441
 				D28F110C3C8E4A7A00A1001C /* Chart */,
441 442
 				D28F110D3C8E4A7A00A1001D /* Settings */,
@@ -460,14 +461,23 @@
460 461
 			path = Components;
461 462
 			sourceTree = "<group>";
462 463
 		};
464
+		D28F113F3C8E4A7A00A1004F /* Components */ = {
465
+			isa = PBXGroup;
466
+			children = (
467
+				D28F11123C8E4A7A00A10022 /* MeterInfoCardView.swift */,
468
+				D28F11143C8E4A7A00A10024 /* MeterInfoRowView.swift */,
469
+				437F0AB62463108F005DEBEC /* MeasurementChartView.swift */,
470
+			);
471
+			path = Components;
472
+			sourceTree = "<group>";
473
+		};
463 474
 		437D47CF2415F8CF00B7768E /* Meter */ = {
464 475
 			isa = PBXGroup;
465 476
 			children = (
466 477
 				4383B469240FE4A600DAAEBF /* MeterView.swift */,
478
+				D28F113F3C8E4A7A00A1004F /* Components */,
467 479
 				D28F11093C8E4A7A00A10019 /* Tabs */,
468
-				D28F10013C8E4A7A00A10001 /* Screens */,
469
-				4308CF89241777130002E80B /* Data Groups */,
470
-				43554B3024444983004E66F5 /* Measurements */,
480
+				D28F10013C8E4A7A00A10001 /* Sheets */,
471 481
 			);
472 482
 			path = Meter;
473 483
 			sourceTree = "<group>";
@@ -678,30 +688,30 @@
678 688
 			buildActionMask = 2147483647;
679 689
 			files = (
680 690
 				43874C852415611200525397 /* Double.swift in Sources */,
681
-				437D47D72415FDF300B7768E /* ControlView.swift in Sources */,
682
-				4308CF882417770D0002E80B /* DataGroupsView.swift in Sources */,
691
+				437D47D72415FDF300B7768E /* MeterScreenControlsView.swift in Sources */,
692
+				4308CF882417770D0002E80B /* DataGroupsSheetView.swift in Sources */,
683 693
 				4383B468240F845500DAAEBF /* MacAdress.swift in Sources */,
684 694
 				43CBF681240D153000255B8B /* CBManagerState.swift in Sources */,
685 695
 				4383B46A240FE4A600DAAEBF /* MeterView.swift in Sources */,
686
-				D28F11013C8E4A7A00A10011 /* MeterConnectionTabView.swift in Sources */,
696
+				D28F11013C8E4A7A00A10011 /* MeterHomeTabView.swift in Sources */,
687 697
 				D28F11033C8E4A7A00A10013 /* MeterLiveTabView.swift in Sources */,
688 698
 				D28F11053C8E4A7A00A10015 /* MeterChartTabView.swift in Sources */,
689 699
 				D28F11073C8E4A7A00A10017 /* MeterSettingsTabView.swift in Sources */,
690
-				D28F11113C8E4A7A00A10021 /* MeterInfoCard.swift in Sources */,
691
-				D28F11133C8E4A7A00A10023 /* MeterInfoRow.swift in Sources */,
692
-				D28F11153C8E4A7A00A10025 /* EditNameView.swift in Sources */,
693
-				D28F11173C8E4A7A00A10027 /* EditScreenTimeoutView.swift in Sources */,
694
-				D28F11193C8E4A7A00A10029 /* EditScreenBrightnessView.swift in Sources */,
695
-				D28F11213C8E4A7A00A10031 /* LiveMetricRange.swift in Sources */,
696
-				D28F11233C8E4A7A00A10033 /* LoadResistanceSymbolView.swift in Sources */,
697
-				D28F11313C8E4A7A00A10041 /* ControlActionButtonView.swift in Sources */,
698
-				D28F11333C8E4A7A00A10043 /* ControlCurrentScreenCardView.swift in Sources */,
699
-				D28F11353C8E4A7A00A10045 /* RecordingMetricsTableView.swift in Sources */,
700
-				D28F11393C8E4A7A00A10049 /* ConnectionStatusBadgeView.swift in Sources */,
701
-				D28F113B3C8E4A7A00A1004B /* ConnectionPrimaryActionView.swift in Sources */,
702
-				D28F113D3C8E4A7A00A1004D /* ConnectionHomeInfoPreviewView.swift in Sources */,
700
+				D28F11113C8E4A7A00A10021 /* MeterInfoCardView.swift in Sources */,
701
+				D28F11133C8E4A7A00A10023 /* MeterInfoRowView.swift in Sources */,
702
+				D28F11153C8E4A7A00A10025 /* MeterNameEditorView.swift in Sources */,
703
+				D28F11173C8E4A7A00A10027 /* ScreenTimeoutEditorView.swift in Sources */,
704
+				D28F11193C8E4A7A00A10029 /* ScreenBrightnessEditorView.swift in Sources */,
705
+				D28F11213C8E4A7A00A10031 /* MeterLiveMetricRange.swift in Sources */,
706
+				D28F11233C8E4A7A00A10033 /* LoadResistanceIconView.swift in Sources */,
707
+				D28F11313C8E4A7A00A10041 /* MeterScreenControlButtonView.swift in Sources */,
708
+				D28F11333C8E4A7A00A10043 /* MeterCurrentScreenSummaryView.swift in Sources */,
709
+				D28F11353C8E4A7A00A10045 /* ChargeRecordMetricsTableView.swift in Sources */,
710
+				D28F11393C8E4A7A00A10049 /* MeterConnectionStatusBadgeView.swift in Sources */,
711
+				D28F113B3C8E4A7A00A1004B /* MeterConnectionActionView.swift in Sources */,
712
+				D28F113D3C8E4A7A00A1004D /* MeterOverviewSectionView.swift in Sources */,
703 713
 				4360A34D241CBB3800B464F9 /* RSSIView.swift in Sources */,
704
-				437D47D12415F91B00B7768E /* LiveView.swift in Sources */,
714
+				437D47D12415F91B00B7768E /* MeterLiveContentView.swift in Sources */,
705 715
 				4383B465240EB6B200DAAEBF /* UserDefault.swift in Sources */,
706 716
 				3407A133FADB8858DC2A1FED /* MeterNameStore.swift in Sources */,
707 717
 				43CBF677240C043E00255B8B /* BluetoothManager.swift in Sources */,
@@ -715,17 +725,17 @@
715 725
 				43874C7F2414F3F400525397 /* Float.swift in Sources */,
716 726
 				4383B462240EB5E400DAAEBF /* AppData.swift in Sources */,
717 727
 				4386958D2F6A1002008855A9 /* TC66Protocol.swift in Sources */,
718
-				437D47D52415FD8C00B7768E /* RecordingView.swift in Sources */,
728
+				437D47D52415FD8C00B7768E /* ChargeRecordSheetView.swift in Sources */,
719 729
 				432EA6442445A559006FC905 /* ChartContext.swift in Sources */,
720 730
 				4308CF8624176CAB0002E80B /* DataGroupRowView.swift in Sources */,
721 731
 				4386958F2F6A4E3E008855A9 /* MeterCapabilities.swift in Sources */,
722
-				43554B32244449B5004E66F5 /* MeasurementPointView.swift in Sources */,
732
+				43554B32244449B5004E66F5 /* AppHistorySampleView.swift in Sources */,
723 733
 				43F7792B2465AE1600745DF4 /* UIView.swift in Sources */,
724 734
 				43ED78AE2420A0BE00974487 /* BluetoothSerial.swift in Sources */,
725 735
 				43CBF662240BF3EB00255B8B /* SceneDelegate.swift in Sources */,
726 736
 				4351E7BB24685ACD00E798A3 /* CGPoint.swift in Sources */,
727 737
 				4327461B24619CED0009BE4B /* MeterRowView.swift in Sources */,
728
-				43554B2F24443939004E66F5 /* MeasurementsView.swift in Sources */,
738
+				43554B2F24443939004E66F5 /* AppHistorySheetView.swift in Sources */,
729 739
 				430CB4FC245E07EB006525C2 /* ChevronView.swift in Sources */,
730 740
 				43554B3424444B0E004E66F5 /* Date.swift in Sources */,
731 741
 				4311E63A241384960080EA59 /* DeviceHelpView.swift in Sources */,
+0 -0
USB Meter/Views/Meter/Measurements/Chart/MeasurementChartView.swift → USB Meter/Views/Meter/Components/MeasurementChartView.swift
File renamed without changes.
+2 -2
USB Meter/Views/Meter/Tabs/Connection/Components/MeterInfoCard.swift → USB Meter/Views/Meter/Components/MeterInfoCardView.swift
@@ -1,11 +1,11 @@
1 1
 //
2
-//  MeterInfoCard.swift
2
+//  MeterInfoCardView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 
6 6
 import SwiftUI
7 7
 
8
-struct MeterInfoCard<Content: View>: View {
8
+struct MeterInfoCardView<Content: View>: View {
9 9
     let title: String
10 10
     let tint: Color
11 11
     @ViewBuilder var content: Content
+2 -2
USB Meter/Views/Meter/Tabs/Connection/Components/MeterInfoRow.swift → USB Meter/Views/Meter/Components/MeterInfoRowView.swift
@@ -1,11 +1,11 @@
1 1
 //
2
-//  MeterInfoRow.swift
2
+//  MeterInfoRowView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 
6 6
 import SwiftUI
7 7
 
8
-struct MeterInfoRow: View {
8
+struct MeterInfoRowView: View {
9 9
     let label: String
10 10
     let value: String
11 11
 
+263 -49
USB Meter/Views/Meter/MeterView.swift
@@ -11,15 +11,75 @@ import SwiftUI
11 11
 import CoreBluetooth
12 12
 
13 13
 struct MeterView: View {
14
+    private struct TabBarStyle {
15
+        let showsTitles: Bool
16
+        let horizontalPadding: CGFloat
17
+        let topPadding: CGFloat
18
+        let bottomPadding: CGFloat
19
+        let chipHorizontalPadding: CGFloat
20
+        let chipVerticalPadding: CGFloat
21
+        let outerPadding: CGFloat
22
+        let maxWidth: CGFloat
23
+        let barBackgroundOpacity: CGFloat
24
+        let materialOpacity: CGFloat
25
+        let shadowOpacity: CGFloat
26
+        let floatingInset: CGFloat
27
+
28
+        static let portrait = TabBarStyle(
29
+            showsTitles: true,
30
+            horizontalPadding: 16,
31
+            topPadding: 10,
32
+            bottomPadding: 8,
33
+            chipHorizontalPadding: 10,
34
+            chipVerticalPadding: 7,
35
+            outerPadding: 6,
36
+            maxWidth: 420,
37
+            barBackgroundOpacity: 0.10,
38
+            materialOpacity: 0.78,
39
+            shadowOpacity: 0,
40
+            floatingInset: 0
41
+        )
42
+
43
+        static let landscapeInline = TabBarStyle(
44
+            showsTitles: true,
45
+            horizontalPadding: 12,
46
+            topPadding: 10,
47
+            bottomPadding: 8,
48
+            chipHorizontalPadding: 10,
49
+            chipVerticalPadding: 7,
50
+            outerPadding: 6,
51
+            maxWidth: 420,
52
+            barBackgroundOpacity: 0.10,
53
+            materialOpacity: 0.78,
54
+            shadowOpacity: 0,
55
+            floatingInset: 0
56
+        )
57
+
58
+        static let landscapeFloating = TabBarStyle(
59
+            showsTitles: false,
60
+            horizontalPadding: 16,
61
+            topPadding: 10,
62
+            bottomPadding: 0,
63
+            chipHorizontalPadding: 11,
64
+            chipVerticalPadding: 11,
65
+            outerPadding: 7,
66
+            maxWidth: 260,
67
+            barBackgroundOpacity: 0.02,
68
+            materialOpacity: 0,
69
+            shadowOpacity: 0.18,
70
+            floatingInset: 12
71
+        )
72
+    }
73
+
14 74
     private enum MeterTab: Hashable {
15
-        case connection
75
+        case home
16 76
         case live
17 77
         case chart
18 78
         case settings
19 79
 
20 80
         var title: String {
21 81
             switch self {
22
-            case .connection: return "Home"
82
+            case .home: return "Home"
23 83
             case .live: return "Live"
24 84
             case .chart: return "Chart"
25 85
             case .settings: return "Settings"
@@ -28,7 +88,7 @@ struct MeterView: View {
28 88
 
29 89
         var systemImage: String {
30 90
             switch self {
31
-            case .connection: return "house.fill"
91
+            case .home: return "house.fill"
32 92
             case .live: return "waveform.path.ecg"
33 93
             case .chart: return "chart.xyaxis.line"
34 94
             case .settings: return "gearshape.fill"
@@ -40,15 +100,23 @@ struct MeterView: View {
40 100
     @Environment(\.dismiss) private var dismiss
41 101
 
42 102
     private static let isMacIPadApp: Bool = ProcessInfo.processInfo.isiOSAppOnMac
103
+    #if os(iOS)
104
+    private static let isPhone: Bool = UIDevice.current.userInterfaceIdiom == .phone
105
+    #else
106
+    private static let isPhone: Bool = false
107
+    #endif
43 108
     
44
-    @State private var selectedMeterTab: MeterTab = .connection
109
+    @State private var selectedMeterTab: MeterTab = .home
45 110
     @State private var navBarTitle: String = "Meter"
46 111
     @State private var navBarShowRSSI: Bool = false
47 112
     @State private var navBarRSSI: Int = 0
113
+    @State private var landscapeTabBarHeight: CGFloat = 0
48 114
 
49 115
     var body: some View {
50 116
         GeometryReader { proxy in
51 117
             let landscape = isLandscape(size: proxy.size)
118
+            let usesOverlayTabBar = landscape && Self.isPhone
119
+            let tabBarStyle = tabBarStyle(for: landscape, usesOverlayTabBar: usesOverlayTabBar)
52 120
 
53 121
             VStack(spacing: 0) {
54 122
                 if Self.isMacIPadApp {
@@ -56,9 +124,13 @@ struct MeterView: View {
56 124
                 }
57 125
                 Group {
58 126
                     if landscape {
59
-                        landscapeDeck(size: proxy.size)
127
+                        landscapeDeck(
128
+                            size: proxy.size,
129
+                            usesOverlayTabBar: usesOverlayTabBar,
130
+                            tabBarStyle: tabBarStyle
131
+                        )
60 132
                     } else {
61
-                        portraitContent(size: proxy.size)
133
+                        portraitContent(size: proxy.size, tabBarStyle: tabBarStyle)
62 134
                     }
63 135
                 }
64 136
                 .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
@@ -115,6 +187,13 @@ struct MeterView: View {
115 187
 
116 188
             Spacer()
117 189
 
190
+            MeterConnectionToolbarButton(
191
+                operationalState: meter.operationalState,
192
+                showsTitle: true,
193
+                connectAction: { meter.connect() },
194
+                disconnectAction: { meter.disconnect() }
195
+            )
196
+
118 197
             if meter.operationalState > .notPresent {
119 198
                 RSSIView(RSSI: meter.btSerial.averageRSSI)
120 199
                     .frame(width: 18, height: 18)
@@ -135,17 +214,47 @@ struct MeterView: View {
135 214
         }
136 215
     }
137 216
 
138
-    private func portraitContent(size: CGSize) -> some View {
139
-        portraitSegmentedDeck(size: size)
217
+    private func portraitContent(size: CGSize, tabBarStyle: TabBarStyle) -> some View {
218
+        portraitSegmentedDeck(size: size, tabBarStyle: tabBarStyle)
219
+    }
220
+
221
+    @ViewBuilder
222
+    private func landscapeDeck(size: CGSize, usesOverlayTabBar: Bool, tabBarStyle: TabBarStyle) -> some View {
223
+        if usesOverlayTabBar {
224
+            landscapeOverlaySegmentedDeck(size: size, tabBarStyle: tabBarStyle)
225
+        } else {
226
+            landscapeSegmentedDeck(size: size, tabBarStyle: tabBarStyle)
227
+        }
140 228
     }
141 229
 
142
-    private func landscapeDeck(size: CGSize) -> some View {
143
-        landscapeSegmentedDeck(size: size)
230
+    private func landscapeOverlaySegmentedDeck(size: CGSize, tabBarStyle: TabBarStyle) -> some View {
231
+        ZStack(alignment: .top) {
232
+            landscapeSegmentedContent(size: size)
233
+                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
234
+                .padding(.top, landscapeContentTopPadding(for: tabBarStyle))
235
+                .id(selectedMeterTab)
236
+                .transition(.opacity.combined(with: .move(edge: .trailing)))
237
+
238
+            segmentedTabBar(style: tabBarStyle, showsConnectionAction: !Self.isMacIPadApp)
239
+        }
240
+        .animation(.easeInOut(duration: 0.22), value: selectedMeterTab)
241
+        .animation(.easeInOut(duration: 0.22), value: availableMeterTabs)
242
+        .onAppear {
243
+            normalizeSelectedTab()
244
+        }
245
+        .onChange(of: availableMeterTabs) { _ in
246
+            normalizeSelectedTab()
247
+        }
248
+        .onPreferenceChange(MeterTabBarHeightPreferenceKey.self) { height in
249
+            if height > 0 {
250
+                landscapeTabBarHeight = height
251
+            }
252
+        }
144 253
     }
145 254
 
146
-    private func landscapeSegmentedDeck(size: CGSize) -> some View {
255
+    private func landscapeSegmentedDeck(size: CGSize, tabBarStyle: TabBarStyle) -> some View {
147 256
         VStack(spacing: 0) {
148
-            segmentedTabBar(horizontalPadding: 12)
257
+            segmentedTabBar(style: tabBarStyle, showsConnectionAction: !Self.isMacIPadApp)
149 258
 
150 259
             landscapeSegmentedContent(size: size)
151 260
                 .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
@@ -162,9 +271,9 @@ struct MeterView: View {
162 271
         }
163 272
     }
164 273
 
165
-    private func portraitSegmentedDeck(size: CGSize) -> some View {
274
+    private func portraitSegmentedDeck(size: CGSize, tabBarStyle: TabBarStyle) -> some View {
166 275
         VStack(spacing: 0) {
167
-            segmentedTabBar(horizontalPadding: 16)
276
+            segmentedTabBar(style: tabBarStyle)
168 277
 
169 278
             portraitSegmentedContent(size: size)
170 279
                 .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
@@ -181,8 +290,11 @@ struct MeterView: View {
181 290
         }
182 291
     }
183 292
 
184
-    private func segmentedTabBar(horizontalPadding: CGFloat) -> some View {
185
-        HStack {
293
+    private func segmentedTabBar(style: TabBarStyle, showsConnectionAction: Bool = false) -> some View {
294
+        let isFloating = style.floatingInset > 0
295
+        let cornerRadius = style.showsTitles ? 14.0 : 22.0
296
+
297
+        return HStack {
186 298
             Spacer(minLength: 0)
187 299
 
188 300
             HStack(spacing: 8) {
@@ -197,69 +309,136 @@ struct MeterView: View {
197 309
                         HStack(spacing: 6) {
198 310
                             Image(systemName: tab.systemImage)
199 311
                                 .font(.subheadline.weight(.semibold))
200
-                            Text(tab.title)
201
-                                .font(.subheadline.weight(.semibold))
202
-                                .lineLimit(1)
312
+                            if style.showsTitles {
313
+                                Text(tab.title)
314
+                                    .font(.subheadline.weight(.semibold))
315
+                                    .lineLimit(1)
316
+                            }
203 317
                         }
204
-                        .foregroundColor(isSelected ? .white : .primary)
205
-                        .padding(.horizontal, 10)
206
-                        .padding(.vertical, 7)
318
+                        .foregroundColor(
319
+                            isSelected
320
+                            ? .white
321
+                            : (isFloating ? .white.opacity(0.82) : .primary)
322
+                        )
323
+                        .padding(.horizontal, style.chipHorizontalPadding)
324
+                        .padding(.vertical, style.chipVerticalPadding)
207 325
                         .frame(maxWidth: .infinity)
208 326
                         .background(
209 327
                             Capsule()
210
-                                .fill(isSelected ? meter.color : Color.secondary.opacity(0.12))
328
+                                .fill(
329
+                                    isSelected
330
+                                    ? meter.color.opacity(isFloating ? 0.94 : 1)
331
+                                    : (isFloating ? Color.white.opacity(0.045) : Color.secondary.opacity(0.12))
332
+                                )
211 333
                         )
212 334
                     }
213 335
                     .buttonStyle(.plain)
214 336
                     .accessibilityLabel(tab.title)
215 337
                 }
216 338
             }
217
-            .frame(maxWidth: 420)
218
-            .padding(6)
339
+            .frame(maxWidth: style.maxWidth)
340
+            .padding(style.outerPadding)
219 341
             .background(
220
-                RoundedRectangle(cornerRadius: 14, style: .continuous)
221
-                    .fill(Color.secondary.opacity(0.10))
342
+                RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
343
+                    .fill(
344
+                        isFloating
345
+                        ? LinearGradient(
346
+                            colors: [
347
+                                Color.white.opacity(0.14),
348
+                                Color.white.opacity(0.06)
349
+                            ],
350
+                            startPoint: .topLeading,
351
+                            endPoint: .bottomTrailing
352
+                        )
353
+                        : LinearGradient(
354
+                            colors: [
355
+                                Color.secondary.opacity(style.barBackgroundOpacity),
356
+                                Color.secondary.opacity(style.barBackgroundOpacity)
357
+                            ],
358
+                            startPoint: .topLeading,
359
+                            endPoint: .bottomTrailing
360
+                        )
361
+                    )
222 362
             )
363
+            .overlay {
364
+                RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
365
+                    .stroke(
366
+                        isFloating ? Color.white.opacity(0.10) : Color.clear,
367
+                        lineWidth: 1
368
+                    )
369
+            }
370
+            .background {
371
+                if !isFloating {
372
+                    RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
373
+                        .fill(.ultraThinMaterial)
374
+                }
375
+            }
376
+            .shadow(color: Color.black.opacity(style.shadowOpacity), radius: isFloating ? 28 : 24, x: 0, y: isFloating ? 16 : 12)
223 377
 
224 378
             Spacer(minLength: 0)
225 379
         }
226
-        .padding(.horizontal, horizontalPadding)
227
-        .padding(.top, 10)
228
-        .padding(.bottom, 8)
380
+        .padding(.horizontal, style.horizontalPadding)
381
+        .padding(.top, style.topPadding)
382
+        .padding(.bottom, style.bottomPadding)
229 383
         .background(
230
-            Rectangle()
231
-                .fill(.ultraThinMaterial)
232
-                .opacity(0.78)
233
-                .ignoresSafeArea(edges: .top)
384
+            GeometryReader { geometry in
385
+                Color.clear
386
+                    .preference(key: MeterTabBarHeightPreferenceKey.self, value: geometry.size.height)
387
+            }
234 388
         )
389
+        .padding(.horizontal, style.floatingInset)
390
+        .background {
391
+            if style.floatingInset == 0 {
392
+                Rectangle()
393
+                    .fill(.ultraThinMaterial)
394
+                    .opacity(style.materialOpacity)
395
+                    .ignoresSafeArea(edges: .top)
396
+            }
397
+        }
235 398
         .overlay(alignment: .bottom) {
236
-            Rectangle()
237
-                .fill(Color.secondary.opacity(0.12))
238
-                .frame(height: 1)
399
+            if style.floatingInset == 0 {
400
+                Rectangle()
401
+                    .fill(Color.secondary.opacity(0.12))
402
+                    .frame(height: 1)
403
+            }
404
+        }
405
+        .overlay(alignment: .trailing) {
406
+            if showsConnectionAction {
407
+                MeterConnectionToolbarButton(
408
+                    operationalState: meter.operationalState,
409
+                    showsTitle: false,
410
+                    connectAction: { meter.connect() },
411
+                    disconnectAction: { meter.disconnect() }
412
+                )
413
+                .font(.title3.weight(.semibold))
414
+                .padding(.trailing, style.horizontalPadding + style.floatingInset + 4)
415
+                .padding(.top, style.topPadding)
416
+                .padding(.bottom, style.bottomPadding)
417
+            }
239 418
         }
240 419
     }
241 420
 
242 421
     @ViewBuilder
243 422
     private func landscapeSegmentedContent(size: CGSize) -> some View {
244 423
         switch selectedMeterTab {
245
-        case .connection:
246
-            MeterConnectionTabView(size: size, isLandscape: true)
424
+        case .home:
425
+            MeterHomeTabView(size: size, isLandscape: true)
247 426
         case .live:
248 427
             if meter.operationalState == .dataIsAvailable {
249 428
                 MeterLiveTabView(size: size, isLandscape: true)
250 429
             } else {
251
-                MeterConnectionTabView(size: size, isLandscape: true)
430
+                MeterHomeTabView(size: size, isLandscape: true)
252 431
             }
253 432
         case .chart:
254 433
             if meter.measurements.power.context.isValid && meter.operationalState == .dataIsAvailable {
255 434
                 MeterChartTabView(size: size, isLandscape: true)
256 435
             } else {
257
-                MeterConnectionTabView(size: size, isLandscape: true)
436
+                MeterHomeTabView(size: size, isLandscape: true)
258 437
             }
259 438
         case .settings:
260 439
             MeterSettingsTabView(isMacIPadApp: Self.isMacIPadApp) {
261 440
                 withAnimation(.easeInOut(duration: 0.22)) {
262
-                    selectedMeterTab = .connection
441
+                    selectedMeterTab = .home
263 442
                 }
264 443
             }
265 444
         }
@@ -268,31 +447,31 @@ struct MeterView: View {
268 447
     @ViewBuilder
269 448
     private func portraitSegmentedContent(size: CGSize) -> some View {
270 449
         switch selectedMeterTab {
271
-        case .connection:
272
-            MeterConnectionTabView(size: size, isLandscape: false)
450
+        case .home:
451
+            MeterHomeTabView(size: size, isLandscape: false)
273 452
         case .live:
274 453
             if meter.operationalState == .dataIsAvailable {
275 454
                 MeterLiveTabView(size: size, isLandscape: false)
276 455
             } else {
277
-                MeterConnectionTabView(size: size, isLandscape: false)
456
+                MeterHomeTabView(size: size, isLandscape: false)
278 457
             }
279 458
         case .chart:
280 459
             if meter.measurements.power.context.isValid && meter.operationalState == .dataIsAvailable {
281 460
                 MeterChartTabView(size: size, isLandscape: false)
282 461
             } else {
283
-                MeterConnectionTabView(size: size, isLandscape: false)
462
+                MeterHomeTabView(size: size, isLandscape: false)
284 463
             }
285 464
         case .settings:
286 465
             MeterSettingsTabView(isMacIPadApp: Self.isMacIPadApp) {
287 466
                 withAnimation(.easeInOut(duration: 0.22)) {
288
-                    selectedMeterTab = .connection
467
+                    selectedMeterTab = .home
289 468
                 }
290 469
             }
291 470
         }
292 471
     }
293 472
 
294 473
     private var availableMeterTabs: [MeterTab] {
295
-        var tabs: [MeterTab] = [.connection]
474
+        var tabs: [MeterTab] = [.home]
296 475
 
297 476
         if meter.operationalState == .dataIsAvailable {
298 477
             tabs.append(.live)
@@ -310,7 +489,7 @@ struct MeterView: View {
310 489
     private func normalizeSelectedTab() {
311 490
         guard availableMeterTabs.contains(selectedMeterTab) else {
312 491
             withAnimation(.easeInOut(duration: 0.22)) {
313
-                selectedMeterTab = .connection
492
+                selectedMeterTab = .home
314 493
             }
315 494
             return
316 495
         }
@@ -333,6 +512,34 @@ struct MeterView: View {
333 512
         size.width > size.height
334 513
     }
335 514
 
515
+    private func tabBarStyle(for landscape: Bool, usesOverlayTabBar: Bool) -> TabBarStyle {
516
+        if usesOverlayTabBar {
517
+            return .landscapeFloating
518
+        }
519
+
520
+        if landscape {
521
+            return .landscapeInline
522
+        }
523
+
524
+        return .portrait
525
+    }
526
+
527
+    private func landscapeContentTopPadding(for style: TabBarStyle) -> CGFloat {
528
+        if style.floatingInset > 0 {
529
+            return max(landscapeTabBarHeight * 0.44, 26)
530
+        }
531
+
532
+        return max(landscapeTabBarHeight - 6, 0)
533
+    }
534
+
535
+}
536
+
537
+private struct MeterTabBarHeightPreferenceKey: PreferenceKey {
538
+    static var defaultValue: CGFloat = 0
539
+
540
+    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
541
+        value = max(value, nextValue())
542
+    }
336 543
 }
337 544
 
338 545
 // MARK: - Conditional navigation bar modifier (skipped on Designed-for-iPad / Mac)
@@ -351,6 +558,13 @@ private struct IOSOnlyNavBar: ViewModifier {
351 558
                 .navigationBarTitle(title)
352 559
                 .toolbar {
353 560
                     ToolbarItemGroup(placement: .navigationBarTrailing) {
561
+                        MeterConnectionToolbarButton(
562
+                            operationalState: meter.operationalState,
563
+                            showsTitle: false,
564
+                            connectAction: { meter.connect() },
565
+                            disconnectAction: { meter.disconnect() }
566
+                        )
567
+                        .font(.body.weight(.semibold))
354 568
                         if showRSSI {
355 569
                             RSSIView(RSSI: rssi)
356 570
                                 .frame(width: 18, height: 18)
+3 -3
USB Meter/Views/Meter/Measurements/MeasurementsView.swift → USB Meter/Views/Meter/Sheets/AppHistory/AppHistorySheetView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  MeasurementView.swift
2
+//  AppHistorySheetView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 13/04/2020.
@@ -8,7 +8,7 @@
8 8
 
9 9
 import SwiftUI
10 10
 
11
-struct MeasurementsView: View {
11
+struct AppHistorySheetView: View {
12 12
     
13 13
     @EnvironmentObject private var measurements: Measurements
14 14
     
@@ -37,7 +37,7 @@ struct MeasurementsView: View {
37 37
                     } else {
38 38
                         LazyVStack(spacing: 12) {
39 39
                             ForEach(measurements.power.points) { point in
40
-                                MeasurementPointView(
40
+                                AppHistorySampleView(
41 41
                                     power: point,
42 42
                                     voltage: measurements.voltage.points[point.id],
43 43
                                     current: measurements.current.points[point.id]
+2 -2
USB Meter/Views/Meter/Measurements/MeasurementPointView.swift → USB Meter/Views/Meter/Sheets/AppHistory/Subviews/AppHistorySampleView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  MeasurementView.swift
2
+//  AppHistorySampleView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 13/04/2020.
@@ -8,7 +8,7 @@
8 8
 
9 9
 import SwiftUI
10 10
 
11
-struct MeasurementPointView: View {
11
+struct AppHistorySampleView: View {
12 12
     
13 13
     var power: Measurements.Measurement.Point
14 14
     var voltage: Measurements.Measurement.Point
+6 -6
USB Meter/Views/Meter/Screens/Recording/RecordingView.swift → USB Meter/Views/Meter/Sheets/ChargeRecord/ChargeRecordSheetView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  RecordingView.swift
2
+//  ChargeRecordSheetView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 09/03/2020.
@@ -8,7 +8,7 @@
8 8
 
9 9
 import SwiftUI
10 10
 
11
-struct RecordingView: View {
11
+struct ChargeRecordSheetView: View {
12 12
     
13 13
     @Binding var visibility: Bool
14 14
     @EnvironmentObject private var usbMeter: Meter
@@ -42,7 +42,7 @@ struct RecordingView: View {
42 42
                     .padding(18)
43 43
                     .meterCard(tint: .pink, fillOpacity: 0.18, strokeOpacity: 0.24)
44 44
 
45
-                    RecordingMetricsTableView(
45
+                    ChargeRecordMetricsTableView(
46 46
                         labels: ["Capacity", "Energy", "Duration", "Stop Threshold"],
47 47
                         values: [
48 48
                             "\(usbMeter.chargeRecordAH.format(decimalDigits: 3)) Ah",
@@ -98,7 +98,7 @@ struct RecordingView: View {
98 98
                         VStack(alignment: .leading, spacing: 12) {
99 99
                             Text("Meter Totals")
100 100
                                 .font(.headline)
101
-                            RecordingMetricsTableView(
101
+                            ChargeRecordMetricsTableView(
102 102
                                 labels: ["Capacity", "Energy", "Duration", "Meter Threshold"],
103 103
                                 values: [
104 104
                                     "\(usbMeter.recordedAH.format(decimalDigits: 3)) Ah",
@@ -141,8 +141,8 @@ struct RecordingView: View {
141 141
     }
142 142
 }
143 143
 
144
-struct RecordingView_Previews: PreviewProvider {
144
+struct ChargeRecordSheetView_Previews: PreviewProvider {
145 145
     static var previews: some View {
146
-        RecordingView(visibility: .constant(true))
146
+        ChargeRecordSheetView(visibility: .constant(true))
147 147
     }
148 148
 }
+2 -2
USB Meter/Views/Meter/Screens/Recording/Components/RecordingMetricsTableView.swift → USB Meter/Views/Meter/Sheets/ChargeRecord/Subviews/ChargeRecordMetricsTableView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  RecordingMetricsTableView.swift
2
+//  ChargeRecordMetricsTableView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 29/03/2026.
@@ -9,7 +9,7 @@
9 9
 
10 10
 import SwiftUI
11 11
 
12
-struct RecordingMetricsTableView: View {
12
+struct ChargeRecordMetricsTableView: View {
13 13
     let labels: [String]
14 14
     let values: [String]
15 15
 
+2 -2
USB Meter/Views/Meter/Data Groups/DataGroupsView.swift → USB Meter/Views/Meter/Sheets/DataGroups/DataGroupsSheetView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  DataGroupsView.swift
2
+//  DataGroupsSheetView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 10/03/2020.
@@ -8,7 +8,7 @@
8 8
 
9 9
 import SwiftUI
10 10
 
11
-struct DataGroupsView: View {
11
+struct DataGroupsSheetView: View {
12 12
     
13 13
     @Binding var visibility: Bool
14 14
     @EnvironmentObject private var usbMeter: Meter
+1 -1
USB Meter/Views/Meter/Data Groups/DataGroupRowView.swift → USB Meter/Views/Meter/Sheets/DataGroups/Subviews/DataGroupRowView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  DataGroupView.swift
2
+//  DataGroupRowView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 10/03/2020.
+8 -6
USB Meter/Views/Meter/Tabs/Chart/MeterChartTabView.swift
@@ -48,10 +48,12 @@ struct MeterChartTabView: View {
48 48
     }
49 49
 
50 50
     private func landscapeFace<Content: View>(@ViewBuilder content: () -> Content) -> some View {
51
-        content()
52
-            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
53
-            .padding(.horizontal, pageHorizontalPadding)
54
-            .padding(.vertical, pageVerticalPadding)
55
-            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
51
+        ScrollView {
52
+            content()
53
+                .frame(maxWidth: .infinity, alignment: .topLeading)
54
+                .padding(.horizontal, pageHorizontalPadding)
55
+                .padding(.vertical, pageVerticalPadding)
56
+        }
57
+        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
56 58
     }
57
-}
59
+}
+0 -61
USB Meter/Views/Meter/Tabs/Connection/Components/ConnectionHomeInfoPreviewView.swift
@@ -1,61 +0,0 @@
1
-//
2
-//  ConnectionHomeInfoPreviewView.swift
3
-//  USB Meter
4
-//
5
-//  Created by Bogdan Timofte on 29/03/2026.
6
-//  Co-authored-by: GPT-5.3-Codex.
7
-//  Copyright © 2026 Bogdan Timofte. All rights reserved.
8
-//
9
-
10
-import SwiftUI
11
-
12
-struct ConnectionHomeInfoPreviewView: View {
13
-    let meter: Meter
14
-
15
-    var body: some View {
16
-        VStack(spacing: 14) {
17
-            MeterInfoCard(title: "Overview", tint: meter.color) {
18
-                MeterInfoRow(label: "Name", value: meter.name)
19
-                MeterInfoRow(label: "Device Model", value: meter.deviceModelName)
20
-                MeterInfoRow(label: "Advertised Model", value: meter.modelString)
21
-                MeterInfoRow(label: "MAC", value: meter.btSerial.macAddress.description)
22
-                if meter.modelNumber != 0 {
23
-                    MeterInfoRow(label: "Model Identifier", value: "\(meter.modelNumber)")
24
-                }
25
-                MeterInfoRow(label: "Working Voltage", value: meter.documentedWorkingVoltage)
26
-                MeterInfoRow(label: "Temperature Unit", value: meter.temperatureUnitDescription)
27
-                MeterInfoRow(label: "Last Seen", value: meterHistoryText(for: meter.lastSeen))
28
-                MeterInfoRow(label: "Last Connected", value: meterHistoryText(for: meter.lastConnectedAt))
29
-            }
30
-
31
-            MeterInfoCard(title: "Live Device Details", tint: .indigo) {
32
-                if meter.operationalState == .dataIsAvailable {
33
-                    if !meter.firmwareVersion.isEmpty {
34
-                        MeterInfoRow(label: "Firmware", value: meter.firmwareVersion)
35
-                    }
36
-                    if meter.supportsChargerDetection {
37
-                        MeterInfoRow(label: "Detected Charger", value: meter.chargerTypeDescription)
38
-                    }
39
-                    if meter.serialNumber != 0 {
40
-                        MeterInfoRow(label: "Serial", value: "\(meter.serialNumber)")
41
-                    }
42
-                    if meter.bootCount != 0 {
43
-                        MeterInfoRow(label: "Boot Count", value: "\(meter.bootCount)")
44
-                    }
45
-                } else {
46
-                    Text("Connect to the meter to load firmware, serial, and boot details.")
47
-                        .font(.footnote)
48
-                        .foregroundColor(.secondary)
49
-                }
50
-            }
51
-        }
52
-        .padding(.horizontal, 12)
53
-    }
54
-
55
-    private func meterHistoryText(for date: Date?) -> String {
56
-        guard let date else {
57
-            return "Never"
58
-        }
59
-        return date.format(as: "yyyy-MM-dd HH:mm")
60
-    }
61
-}
+0 -65
USB Meter/Views/Meter/Tabs/Connection/Components/ConnectionPrimaryActionView.swift
@@ -1,65 +0,0 @@
1
-//
2
-//  ConnectionPrimaryActionView.swift
3
-//  USB Meter
4
-//
5
-//  Created by Bogdan Timofte on 29/03/2026.
6
-//  Co-authored-by: GPT-5.3-Codex.
7
-//  Copyright © 2026 Bogdan Timofte. All rights reserved.
8
-//
9
-
10
-import SwiftUI
11
-
12
-struct ConnectionPrimaryActionView: View {
13
-    let operationalState: Meter.OperationalState
14
-    let compact: Bool
15
-    let connectAction: () -> Void
16
-    let disconnectAction: () -> Void
17
-
18
-    private var connected: Bool {
19
-        operationalState >= .peripheralConnectionPending
20
-    }
21
-
22
-    private var actionTint: Color {
23
-        connected ? Color(red: 0.66, green: 0.39, blue: 0.35) : Color(red: 0.20, green: 0.46, blue: 0.43)
24
-    }
25
-
26
-    var body: some View {
27
-        Group {
28
-            if operationalState == .notPresent {
29
-                HStack(spacing: 10) {
30
-                    Image(systemName: "exclamationmark.triangle.fill")
31
-                        .foregroundColor(.orange)
32
-                    Text("Not found at this time.")
33
-                        .fontWeight(.semibold)
34
-                    Spacer()
35
-                }
36
-                .padding(compact ? 12 : 16)
37
-                .meterCard(tint: .orange, fillOpacity: 0.14, strokeOpacity: 0.18)
38
-            } else {
39
-                Button(action: {
40
-                    if connected {
41
-                        disconnectAction()
42
-                    } else {
43
-                        connectAction()
44
-                    }
45
-                }) {
46
-                    HStack(spacing: 12) {
47
-                        Image(systemName: connected ? "xmark.circle.fill" : "bolt.horizontal.circle.fill")
48
-                            .foregroundColor(actionTint)
49
-                            .frame(width: 30, height: 30)
50
-                            .background(Circle().fill(actionTint.opacity(0.12)))
51
-                        Text(connected ? "Disconnect" : "Connect")
52
-                            .fontWeight(.semibold)
53
-                            .foregroundColor(.primary)
54
-                        Spacer()
55
-                    }
56
-                    .padding(.horizontal, 18)
57
-                    .padding(.vertical, compact ? 10 : 14)
58
-                    .frame(maxWidth: .infinity)
59
-                    .meterCard(tint: actionTint, fillOpacity: 0.14, strokeOpacity: 0.20)
60
-                }
61
-                .buttonStyle(.plain)
62
-            }
63
-        }
64
-    }
65
-}
+19 -19
USB Meter/Views/Meter/Tabs/Connection/MeterConnectionTabView.swift → USB Meter/Views/Meter/Tabs/Home/MeterHomeTabView.swift
@@ -1,11 +1,11 @@
1 1
 //
2
-//  MeterConnectionTabView.swift
2
+//  MeterHomeTabView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 
6 6
 import SwiftUI
7 7
 
8
-struct MeterConnectionTabView: View {
8
+struct MeterHomeTabView: View {
9 9
     @EnvironmentObject private var meter: Meter
10 10
 
11 11
     let size: CGSize
@@ -29,7 +29,7 @@ struct MeterConnectionTabView: View {
29 29
                 landscapeFace {
30 30
                     VStack(alignment: .leading, spacing: 12) {
31 31
                         connectionCard(compact: true, showsActions: meter.operationalState == .dataIsAvailable)
32
-                        ConnectionHomeInfoPreviewView(meter: meter)
32
+                        MeterOverviewSectionView(meter: meter)
33 33
                     }
34 34
                     .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
35 35
                 }
@@ -37,17 +37,17 @@ struct MeterConnectionTabView: View {
37 37
                 portraitFace {
38 38
                     VStack(alignment: .leading, spacing: 12) {
39 39
                         connectionCard(
40
-                            compact: prefersCompactPortraitConnection,
40
+                            compact: prefersCompactPortraitLayout,
41 41
                             showsActions: meter.operationalState == .dataIsAvailable
42 42
                         )
43
-                        ConnectionHomeInfoPreviewView(meter: meter)
43
+                        MeterOverviewSectionView(meter: meter)
44 44
                     }
45 45
                 }
46 46
             }
47 47
         }
48 48
     }
49 49
 
50
-    private var prefersCompactPortraitConnection: Bool {
50
+    private var prefersCompactPortraitLayout: Bool {
51 51
         size.height < 760 || size.width < 380
52 52
     }
53 53
 
@@ -61,11 +61,13 @@ struct MeterConnectionTabView: View {
61 61
     }
62 62
 
63 63
     private func landscapeFace<Content: View>(@ViewBuilder content: () -> Content) -> some View {
64
-        content()
65
-            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
66
-            .padding(.horizontal, pageHorizontalPadding)
67
-            .padding(.vertical, pageVerticalPadding)
68
-            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
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)
69 71
     }
70 72
 
71 73
     private func connectionCard(compact: Bool = false, showsActions: Bool = false) -> some View {
@@ -121,7 +123,7 @@ struct MeterConnectionTabView: View {
121 123
                     dataGroupsViewVisibility.toggle()
122 124
                 }
123 125
                 .sheet(isPresented: $dataGroupsViewVisibility) {
124
-                    DataGroupsView(visibility: $dataGroupsViewVisibility)
126
+                    DataGroupsSheetView(visibility: $dataGroupsViewVisibility)
125 127
                         .environmentObject(meter)
126 128
                 }
127 129
 
@@ -131,7 +133,7 @@ struct MeterConnectionTabView: View {
131 133
                         recordingViewVisibility.toggle()
132 134
                     }
133 135
                     .sheet(isPresented: $recordingViewVisibility) {
134
-                        RecordingView(visibility: $recordingViewVisibility)
136
+                        ChargeRecordSheetView(visibility: $recordingViewVisibility)
135 137
                             .environmentObject(meter)
136 138
                     }
137 139
                 }
@@ -141,7 +143,7 @@ struct MeterConnectionTabView: View {
141 143
                     measurementsViewVisibility.toggle()
142 144
                 }
143 145
                 .sheet(isPresented: $measurementsViewVisibility) {
144
-                    MeasurementsView(visibility: $measurementsViewVisibility)
146
+                    AppHistorySheetView(visibility: $measurementsViewVisibility)
145 147
                         .environmentObject(meter.measurements)
146 148
                 }
147 149
             }
@@ -164,11 +166,9 @@ struct MeterConnectionTabView: View {
164 166
     }
165 167
 
166 168
     private func connectionActionArea(compact: Bool = false) -> some View {
167
-        ConnectionPrimaryActionView(
169
+        MeterConnectionActionView(
168 170
             operationalState: meter.operationalState,
169
-            compact: compact,
170
-            connectAction: { meter.connect() },
171
-            disconnectAction: { meter.disconnect() }
171
+            compact: compact
172 172
         )
173 173
     }
174 174
 
@@ -215,7 +215,7 @@ struct MeterConnectionTabView: View {
215 215
     }
216 216
 
217 217
     private var statusBadge: some View {
218
-        ConnectionStatusBadgeView(text: statusText, color: statusColor)
218
+        MeterConnectionStatusBadgeView(text: statusText, color: statusColor)
219 219
     }
220 220
 
221 221
     private var statusText: String {
+73 -0
USB Meter/Views/Meter/Tabs/Home/Subviews/MeterConnectionActionView.swift
@@ -0,0 +1,73 @@
1
+//
2
+//  MeterConnectionActionView.swift
3
+//  USB Meter
4
+//
5
+//  Created by Bogdan Timofte on 29/03/2026.
6
+//  Co-authored-by: GPT-5.3-Codex.
7
+//  Copyright © 2026 Bogdan Timofte. All rights reserved.
8
+//
9
+
10
+import SwiftUI
11
+
12
+struct MeterConnectionActionView: View {
13
+    let operationalState: Meter.OperationalState
14
+    let compact: Bool
15
+
16
+    var body: some View {
17
+        if operationalState == .notPresent {
18
+            HStack(spacing: 10) {
19
+                Image(systemName: "exclamationmark.triangle.fill")
20
+                    .foregroundColor(.orange)
21
+                Text("Not found at this time.")
22
+                    .fontWeight(.semibold)
23
+                Spacer()
24
+            }
25
+            .padding(compact ? 12 : 16)
26
+            .meterCard(tint: .orange, fillOpacity: 0.14, strokeOpacity: 0.18)
27
+        }
28
+    }
29
+}
30
+
31
+struct MeterConnectionToolbarButton: View {
32
+    let operationalState: Meter.OperationalState
33
+    let showsTitle: Bool
34
+    let connectAction: () -> Void
35
+    let disconnectAction: () -> Void
36
+
37
+    private var connected: Bool {
38
+        operationalState >= .peripheralConnectionPending
39
+    }
40
+
41
+    private var actionTint: Color {
42
+        connected ? Color(red: 0.66, green: 0.39, blue: 0.35) : Color(red: 0.20, green: 0.46, blue: 0.43)
43
+    }
44
+
45
+    private var title: String {
46
+        connected ? "Disconnect" : "Connect"
47
+    }
48
+
49
+    private var systemImage: String {
50
+        connected ? "xmark.circle.fill" : "bolt.horizontal.circle.fill"
51
+    }
52
+
53
+    var body: some View {
54
+        if operationalState != .notPresent {
55
+            Button(action: {
56
+                if connected {
57
+                    disconnectAction()
58
+                } else {
59
+                    connectAction()
60
+                }
61
+            }) {
62
+                if showsTitle {
63
+                    Label(title, systemImage: systemImage)
64
+                } else {
65
+                    Image(systemName: systemImage)
66
+                }
67
+            }
68
+            .foregroundStyle(actionTint)
69
+            .accessibilityLabel(title)
70
+            .help(title)
71
+        }
72
+    }
73
+}
+2 -2
USB Meter/Views/Meter/Tabs/Connection/Components/ConnectionStatusBadgeView.swift → USB Meter/Views/Meter/Tabs/Home/Subviews/MeterConnectionStatusBadgeView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  ConnectionStatusBadgeView.swift
2
+//  MeterConnectionStatusBadgeView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 29/03/2026.
@@ -9,7 +9,7 @@
9 9
 
10 10
 import SwiftUI
11 11
 
12
-struct ConnectionStatusBadgeView: View {
12
+struct MeterConnectionStatusBadgeView: View {
13 13
     let text: String
14 14
     let color: Color
15 15
 
+57 -0
USB Meter/Views/Meter/Tabs/Home/Subviews/MeterOverviewSectionView.swift
@@ -0,0 +1,57 @@
1
+//
2
+//  MeterOverviewSectionView.swift
3
+//  USB Meter
4
+//
5
+//  Created by Bogdan Timofte on 29/03/2026.
6
+//  Co-authored-by: GPT-5.3-Codex.
7
+//  Copyright © 2026 Bogdan Timofte. All rights reserved.
8
+//
9
+
10
+import SwiftUI
11
+
12
+struct MeterOverviewSectionView: View {
13
+    let meter: Meter
14
+
15
+    var body: some View {
16
+        VStack(spacing: 14) {
17
+            MeterInfoCardView(title: "Overview", tint: meter.color) {
18
+                MeterInfoRowView(label: "Name", value: meter.name)
19
+                MeterInfoRowView(label: "Device Model", value: meter.deviceModelName)
20
+                MeterInfoRowView(label: "Advertised Model", value: meter.modelString)
21
+                MeterInfoRowView(label: "MAC", value: meter.btSerial.macAddress.description)
22
+                if meter.modelNumber != 0 {
23
+                    MeterInfoRowView(label: "Model Identifier", value: "\(meter.modelNumber)")
24
+                }
25
+                MeterInfoRowView(label: "Working Voltage", value: meter.documentedWorkingVoltage)
26
+                MeterInfoRowView(label: "Temperature Unit", value: meter.temperatureUnitDescription)
27
+                MeterInfoRowView(label: "Last Seen", value: meterHistoryText(for: meter.lastSeen))
28
+                MeterInfoRowView(label: "Last Connected", value: meterHistoryText(for: meter.lastConnectedAt))
29
+                if meter.operationalState == .dataIsAvailable {
30
+                    if !meter.firmwareVersion.isEmpty {
31
+                        MeterInfoRowView(label: "Firmware", value: meter.firmwareVersion)
32
+                    }
33
+                    if meter.serialNumber != 0 {
34
+                        MeterInfoRowView(label: "Serial", value: "\(meter.serialNumber)")
35
+                    }
36
+                    if meter.bootCount != 0 {
37
+                        MeterInfoRowView(label: "Boot Count", value: "\(meter.bootCount)")
38
+                    }
39
+                } else {
40
+                    Text("Connect to the meter to load firmware, serial, and boot details.")
41
+                        .font(.footnote)
42
+                        .foregroundColor(.secondary)
43
+                        .multilineTextAlignment(.leading)
44
+                }
45
+            }
46
+
47
+        }
48
+        .padding(.horizontal, 12)
49
+    }
50
+
51
+    private func meterHistoryText(for date: Date?) -> String {
52
+        guard let date else {
53
+            return "Never"
54
+        }
55
+        return date.format(as: "yyyy-MM-dd HH:mm")
56
+    }
57
+}
+11 -9
USB Meter/Views/Meter/Tabs/Live/MeterLiveTabView.swift
@@ -17,14 +17,14 @@ struct MeterLiveTabView: View {
17 17
         Group {
18 18
             if isLandscape {
19 19
                 landscapeFace {
20
-                    LiveView(compactLayout: true, availableSize: size)
20
+                    MeterLiveContentView(compactLayout: true, availableSize: size)
21 21
                         .padding(contentCardPadding)
22 22
                         .frame(maxWidth: .infinity, alignment: .topLeading)
23 23
                         .meterCard(tint: meter.color, fillOpacity: 0.12, strokeOpacity: 0.20)
24 24
                 }
25 25
             } else {
26 26
                 portraitFace {
27
-                    LiveView(compactLayout: prefersCompactPortraitConnection, availableSize: size)
27
+                    MeterLiveContentView(compactLayout: prefersCompactPortraitLayout, availableSize: size)
28 28
                         .padding(contentCardPadding)
29 29
                         .meterCard(tint: meter.color, fillOpacity: 0.12, strokeOpacity: 0.20)
30 30
                 }
@@ -34,7 +34,7 @@ struct MeterLiveTabView: View {
34 34
 
35 35
     @EnvironmentObject private var meter: Meter
36 36
 
37
-    private var prefersCompactPortraitConnection: Bool {
37
+    private var prefersCompactPortraitLayout: Bool {
38 38
         size.height < 760 || size.width < 380
39 39
     }
40 40
 
@@ -48,10 +48,12 @@ struct MeterLiveTabView: View {
48 48
     }
49 49
 
50 50
     private func landscapeFace<Content: View>(@ViewBuilder content: () -> Content) -> some View {
51
-        content()
52
-            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
53
-            .padding(.horizontal, pageHorizontalPadding)
54
-            .padding(.vertical, pageVerticalPadding)
55
-            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
51
+        ScrollView {
52
+            content()
53
+                .frame(maxWidth: .infinity, alignment: .topLeading)
54
+                .padding(.horizontal, pageHorizontalPadding)
55
+                .padding(.vertical, pageVerticalPadding)
56
+        }
57
+        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
56 58
     }
57
-}
59
+}
+2 -2
USB Meter/Views/Meter/Screens/Live/Components/LoadResistanceSymbolView.swift → USB Meter/Views/Meter/Tabs/Live/Subviews/LoadResistanceIconView.swift
@@ -1,11 +1,11 @@
1 1
 //
2
-//  LoadResistanceSymbolView.swift
2
+//  LoadResistanceIconView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 
6 6
 import SwiftUI
7 7
 
8
-struct LoadResistanceSymbolView: View {
8
+struct LoadResistanceIconView: View {
9 9
     let color: Color
10 10
 
11 11
     var body: some View {
+40 -10
USB Meter/Views/Meter/Screens/Live/LiveView.swift → USB Meter/Views/Meter/Tabs/Live/Subviews/MeterLiveContentView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  LiveView.swift
2
+//  MeterLiveContentView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 09/03/2020.
@@ -8,7 +8,7 @@
8 8
 
9 9
 import SwiftUI
10 10
 
11
-struct LiveView: View {
11
+struct MeterLiveContentView: View {
12 12
     @EnvironmentObject private var meter: Meter
13 13
     var compactLayout: Bool = false
14 14
     var availableSize: CGSize? = nil
@@ -22,6 +22,15 @@ struct LiveView: View {
22 22
                 statusBadge
23 23
             }
24 24
 
25
+            MeterInfoCardView(title: "Detected Meter", tint: .indigo) {
26
+                MeterInfoRowView(label: "Name", value: meter.name.isEmpty ? "Meter" : meter.name)
27
+                MeterInfoRowView(label: "Model", value: meter.deviceModelSummary)
28
+                MeterInfoRowView(label: "Advertised Model", value: meter.modelString)
29
+                MeterInfoRowView(label: "MAC", value: meter.btSerial.macAddress.description)
30
+                MeterInfoRowView(label: "Last Seen", value: meterHistoryText(for: meter.lastSeen))
31
+                MeterInfoRowView(label: "Last Connected", value: meterHistoryText(for: meter.lastConnectedAt))
32
+            }
33
+
25 34
             LazyVGrid(columns: liveMetricColumns, spacing: compactLayout ? 10 : 12) {
26 35
                 liveMetricCard(
27 36
                     title: "Voltage",
@@ -69,7 +78,7 @@ struct LiveView: View {
69 78
 
70 79
                 liveMetricCard(
71 80
                     title: "Load",
72
-                    customSymbol: AnyView(LoadResistanceSymbolView(color: .yellow)),
81
+                    customSymbol: AnyView(LoadResistanceIconView(color: .yellow)),
73 82
                     color: .yellow,
74 83
                     value: "\(meter.loadResistance.format(decimalDigits: 1)) \u{2126}",
75 84
                     detailText: "Measured resistance"
@@ -80,7 +89,7 @@ struct LiveView: View {
80 89
                     symbol: "dot.radiowaves.left.and.right",
81 90
                     color: .mint,
82 91
                     value: "\(meter.btSerial.averageRSSI) dBm",
83
-                    range: LiveMetricRange(
92
+                    range: MeterLiveMetricRange(
84 93
                         minLabel: "Min",
85 94
                         maxLabel: "Max",
86 95
                         minValue: "\(meter.btSerial.minRSSI) dBm",
@@ -88,6 +97,20 @@ struct LiveView: View {
88 97
                     ),
89 98
                     valueFont: .system(compactLayout ? .subheadline : .headline, design: .rounded).weight(.bold)
90 99
                 )
100
+
101
+                if meter.supportsChargerDetection {
102
+                    liveMetricCard(
103
+                        title: "Detected Charger",
104
+                        symbol: "powerplug.fill",
105
+                        color: .indigo,
106
+                        value: meter.chargerTypeDescription,
107
+                        detailText: "Source handshake",
108
+                        valueFont: .system(compactLayout ? .subheadline : .headline, design: .rounded).weight(.bold),
109
+                        valueLineLimit: 2,
110
+                        valueMonospacedDigits: false,
111
+                        valueMinimumScaleFactor: 0.72
112
+                    )
113
+                }
91 114
             }
92 115
         }
93 116
         .frame(maxWidth: .infinity, alignment: .topLeading)
@@ -129,7 +152,7 @@ struct LiveView: View {
129 152
         customSymbol: AnyView? = nil,
130 153
         color: Color,
131 154
         value: String,
132
-        range: LiveMetricRange? = nil,
155
+        range: MeterLiveMetricRange? = nil,
133 156
         detailText: String? = nil,
134 157
         valueFont: Font? = nil,
135 158
         valueLineLimit: Int = 1,
@@ -190,7 +213,7 @@ struct LiveView: View {
190 213
         .meterCard(tint: color, fillOpacity: 0.10, strokeOpacity: 0.12)
191 214
     }
192 215
 
193
-    private func metricRangeTable(_ range: LiveMetricRange) -> some View {
216
+    private func metricRangeTable(_ range: MeterLiveMetricRange) -> some View {
194 217
         VStack(alignment: .leading, spacing: 4) {
195 218
             HStack(spacing: 12) {
196 219
                 Text(range.minLabel)
@@ -212,10 +235,10 @@ struct LiveView: View {
212 235
         }
213 236
     }
214 237
 
215
-    private func metricRange(min: Double, max: Double, unit: String) -> LiveMetricRange? {
238
+    private func metricRange(min: Double, max: Double, unit: String) -> MeterLiveMetricRange? {
216 239
         guard min.isFinite, max.isFinite else { return nil }
217 240
 
218
-        return LiveMetricRange(
241
+        return MeterLiveMetricRange(
219 242
             minLabel: "Min",
220 243
             maxLabel: "Max",
221 244
             minValue: "\(min.format(decimalDigits: 3)) \(unit)",
@@ -223,15 +246,22 @@ struct LiveView: View {
223 246
         )
224 247
     }
225 248
 
226
-    private func temperatureRange() -> LiveMetricRange? {
249
+    private func temperatureRange() -> MeterLiveMetricRange? {
227 250
         let value = meter.primaryTemperatureDescription
228 251
         guard !value.isEmpty else { return nil }
229 252
 
230
-        return LiveMetricRange(
253
+        return MeterLiveMetricRange(
231 254
             minLabel: "Min",
232 255
             maxLabel: "Max",
233 256
             minValue: value,
234 257
             maxValue: value
235 258
         )
236 259
     }
260
+
261
+    private func meterHistoryText(for date: Date?) -> String {
262
+        guard let date else {
263
+            return "Never"
264
+        }
265
+        return date.format(as: "yyyy-MM-dd HH:mm")
266
+    }
237 267
 }
+2 -2
USB Meter/Views/Meter/Screens/Live/Components/LiveMetricRange.swift → USB Meter/Views/Meter/Tabs/Live/Subviews/MeterLiveMetricRange.swift
@@ -1,11 +1,11 @@
1 1
 //
2
-//  LiveMetricRange.swift
2
+//  MeterLiveMetricRange.swift
3 3
 //  USB Meter
4 4
 //
5 5
 
6 6
 import Foundation
7 7
 
8
-struct LiveMetricRange {
8
+struct MeterLiveMetricRange {
9 9
     let minLabel: String
10 10
     let maxLabel: String
11 11
     let minValue: String
+5 -5
USB Meter/Views/Meter/Tabs/Settings/MeterSettingsTabView.swift
@@ -32,7 +32,7 @@ struct MeterSettingsTabView: View {
32 32
                             ChevronView(rotate: $editingName)
33 33
                         }
34 34
                         if editingName {
35
-                            EditNameView(editingName: $editingName, newName: meter.name)
35
+                            MeterNameEditorView(editingName: $editingName, newName: meter.name)
36 36
                         }
37 37
                     }
38 38
 
@@ -52,7 +52,7 @@ struct MeterSettingsTabView: View {
52 52
 
53 53
                     if meter.operationalState == .dataIsAvailable && meter.model == .TC66C {
54 54
                         settingsCard(title: "Screen Reporting", tint: .orange) {
55
-                            MeterInfoRow(label: "Current Screen", value: "Not Reported")
55
+                            MeterInfoRowView(label: "Current Screen", value: "Not Reported")
56 56
                             Text("TC66 is the exception: it does not report the current screen in the payload, so the app keeps this note here instead of showing it on the home screen.")
57 57
                                 .font(.footnote)
58 58
                                 .foregroundColor(.secondary)
@@ -74,7 +74,7 @@ struct MeterSettingsTabView: View {
74 74
                                     .foregroundColor(.secondary)
75 75
                             }
76 76
 
77
-                            ControlView(showsHeader: false)
77
+                            MeterScreenControlsView(showsHeader: false)
78 78
                         }
79 79
                     }
80 80
 
@@ -89,7 +89,7 @@ struct MeterSettingsTabView: View {
89 89
                                 ChevronView(rotate: $editingScreenTimeout)
90 90
                             }
91 91
                             if editingScreenTimeout {
92
-                                EditScreenTimeoutView()
92
+                                ScreenTimeoutEditorView()
93 93
                             }
94 94
                         }
95 95
 
@@ -103,7 +103,7 @@ struct MeterSettingsTabView: View {
103 103
                                 ChevronView(rotate: $editingScreenBrightness)
104 104
                             }
105 105
                             if editingScreenBrightness {
106
-                                EditScreenBrightnessView()
106
+                                ScreenBrightnessEditorView()
107 107
                             }
108 108
                         }
109 109
                     }
+2 -2
USB Meter/Views/Meter/Screens/Control/Components/ControlCurrentScreenCardView.swift → USB Meter/Views/Meter/Tabs/Settings/Subviews/MeterCurrentScreenSummaryView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  ControlCurrentScreenCardView.swift
2
+//  MeterCurrentScreenSummaryView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 29/03/2026.
@@ -9,7 +9,7 @@
9 9
 
10 10
 import SwiftUI
11 11
 
12
-struct ControlCurrentScreenCardView: View {
12
+struct MeterCurrentScreenSummaryView: View {
13 13
     let reportsCurrentScreenIndex: Bool
14 14
     let currentScreenDescription: String
15 15
     let isExpandedCompactLayout: Bool
+2 -2
USB Meter/Views/Meter/Tabs/Settings/Components/EditNameView.swift → USB Meter/Views/Meter/Tabs/Settings/Subviews/MeterNameEditorView.swift
@@ -1,11 +1,11 @@
1 1
 //
2
-//  EditNameView.swift
2
+//  MeterNameEditorView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 
6 6
 import SwiftUI
7 7
 
8
-struct EditNameView: View {
8
+struct MeterNameEditorView: View {
9 9
     @EnvironmentObject private var meter: Meter
10 10
 
11 11
     @Binding var editingName: Bool
+2 -2
USB Meter/Views/Meter/Screens/Control/Components/ControlActionButtonView.swift → USB Meter/Views/Meter/Tabs/Settings/Subviews/MeterScreenControlButtonView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  ControlActionButtonView.swift
2
+//  MeterScreenControlButtonView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 29/03/2026.
@@ -9,7 +9,7 @@
9 9
 
10 10
 import SwiftUI
11 11
 
12
-struct ControlActionButtonView: View {
12
+struct MeterScreenControlButtonView: View {
13 13
     let title: String
14 14
     let symbol: String
15 15
     let tint: Color
+16 -16
USB Meter/Views/Meter/Screens/Control/ControlView.swift → USB Meter/Views/Meter/Tabs/Settings/Subviews/MeterScreenControlsView.swift
@@ -1,5 +1,5 @@
1 1
 //
2
-//  ControlView.swift
2
+//  MeterScreenControlsView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 //  Created by Bogdan Timofte on 09/03/2020.
@@ -8,7 +8,7 @@
8 8
 
9 9
 import SwiftUI
10 10
 
11
-struct ControlView: View {
11
+struct MeterScreenControlsView: View {
12 12
     
13 13
     @EnvironmentObject private var meter: Meter
14 14
     var compactLayout: Bool = false
@@ -34,7 +34,7 @@ struct ControlView: View {
34 34
 
35 35
                     VStack(spacing: 12) {
36 36
                         HStack(spacing: 12) {
37
-                            ControlActionButtonView(
37
+                            MeterScreenControlButtonView(
38 38
                                 title: "Prev",
39 39
                                 symbol: "chevron.left",
40 40
                                 tint: .indigo,
@@ -43,7 +43,7 @@ struct ControlView: View {
43 43
                                 action: { meter.previousScreen() }
44 44
                             )
45 45
 
46
-                            ControlCurrentScreenCardView(
46
+                            MeterCurrentScreenSummaryView(
47 47
                                 reportsCurrentScreenIndex: meter.reportsCurrentScreenIndex,
48 48
                                 currentScreenDescription: meter.currentScreenDescription,
49 49
                                 isExpandedCompactLayout: usesExpandedCompactLayout
@@ -54,7 +54,7 @@ struct ControlView: View {
54 54
                         }
55 55
 
56 56
                         HStack(spacing: 12) {
57
-                            ControlActionButtonView(
57
+                            MeterScreenControlButtonView(
58 58
                                 title: "Rotate",
59 59
                                 symbol: "rotate.right.fill",
60 60
                                 tint: .orange,
@@ -63,7 +63,7 @@ struct ControlView: View {
63 63
                                 action: { meter.rotateScreen() }
64 64
                             )
65 65
 
66
-                            ControlActionButtonView(
66
+                            MeterScreenControlButtonView(
67 67
                                 title: "Next",
68 68
                                 symbol: "chevron.right",
69 69
                                 tint: .indigo,
@@ -77,7 +77,7 @@ struct ControlView: View {
77 77
                     Spacer(minLength: 0)
78 78
                 } else {
79 79
                     HStack(spacing: 10) {
80
-                        ControlActionButtonView(
80
+                        MeterScreenControlButtonView(
81 81
                             title: "Prev",
82 82
                             symbol: "chevron.left",
83 83
                             tint: .indigo,
@@ -86,7 +86,7 @@ struct ControlView: View {
86 86
                             action: { meter.previousScreen() }
87 87
                         )
88 88
 
89
-                        ControlCurrentScreenCardView(
89
+                        MeterCurrentScreenSummaryView(
90 90
                             reportsCurrentScreenIndex: meter.reportsCurrentScreenIndex,
91 91
                             currentScreenDescription: meter.currentScreenDescription,
92 92
                             isExpandedCompactLayout: usesExpandedCompactLayout
@@ -95,7 +95,7 @@ struct ControlView: View {
95 95
                             .padding(.horizontal, 10)
96 96
                             .meterCard(tint: meter.color, fillOpacity: 0.06, strokeOpacity: 0.10)
97 97
 
98
-                        ControlActionButtonView(
98
+                        MeterScreenControlButtonView(
99 99
                             title: "Rotate",
100 100
                             symbol: "rotate.right.fill",
101 101
                             tint: .orange,
@@ -104,7 +104,7 @@ struct ControlView: View {
104 104
                             action: { meter.rotateScreen() }
105 105
                         )
106 106
 
107
-                        ControlActionButtonView(
107
+                        MeterScreenControlButtonView(
108 108
                             title: "Next",
109 109
                             symbol: "chevron.right",
110 110
                             tint: .indigo,
@@ -116,7 +116,7 @@ struct ControlView: View {
116 116
                 }
117 117
             } else {
118 118
                 HStack(spacing: 12) {
119
-                    ControlActionButtonView(
119
+                    MeterScreenControlButtonView(
120 120
                         title: "Prev",
121 121
                         symbol: "chevron.left",
122 122
                         tint: .indigo,
@@ -125,7 +125,7 @@ struct ControlView: View {
125 125
                         action: { meter.previousScreen() }
126 126
                     )
127 127
 
128
-                    ControlCurrentScreenCardView(
128
+                    MeterCurrentScreenSummaryView(
129 129
                         reportsCurrentScreenIndex: meter.reportsCurrentScreenIndex,
130 130
                         currentScreenDescription: meter.currentScreenDescription,
131 131
                         isExpandedCompactLayout: usesExpandedCompactLayout
@@ -134,7 +134,7 @@ struct ControlView: View {
134 134
                     .padding(.horizontal, 12)
135 135
                     .meterCard(tint: meter.color, fillOpacity: 0.06, strokeOpacity: 0.10)
136 136
 
137
-                    ControlActionButtonView(
137
+                    MeterScreenControlButtonView(
138 138
                         title: "Next",
139 139
                         symbol: "chevron.right",
140 140
                         tint: .indigo,
@@ -144,7 +144,7 @@ struct ControlView: View {
144 144
                     )
145 145
                 }
146 146
 
147
-                ControlActionButtonView(
147
+                MeterScreenControlButtonView(
148 148
                     title: "Rotate Screen",
149 149
                     symbol: "rotate.right.fill",
150 150
                     tint: .orange,
@@ -163,8 +163,8 @@ struct ControlView: View {
163 163
 
164 164
 }
165 165
 
166
-struct ControlView_Previews: PreviewProvider {
166
+struct MeterScreenControlsView_Previews: PreviewProvider {
167 167
     static var previews: some View {
168
-        ControlView()
168
+        MeterScreenControlsView()
169 169
     }
170 170
 }
+2 -2
USB Meter/Views/Meter/Tabs/Settings/Components/EditScreenBrightnessView.swift → USB Meter/Views/Meter/Tabs/Settings/Subviews/ScreenBrightnessEditorView.swift
@@ -1,11 +1,11 @@
1 1
 //
2
-//  EditScreenBrightnessView.swift
2
+//  ScreenBrightnessEditorView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 
6 6
 import SwiftUI
7 7
 
8
-struct EditScreenBrightnessView: View {
8
+struct ScreenBrightnessEditorView: View {
9 9
     @EnvironmentObject private var meter: Meter
10 10
 
11 11
     var body: some View {
+2 -2
USB Meter/Views/Meter/Tabs/Settings/Components/EditScreenTimeoutView.swift → USB Meter/Views/Meter/Tabs/Settings/Subviews/ScreenTimeoutEditorView.swift
@@ -1,11 +1,11 @@
1 1
 //
2
-//  EditScreenTimeoutView.swift
2
+//  ScreenTimeoutEditorView.swift
3 3
 //  USB Meter
4 4
 //
5 5
 
6 6
 import SwiftUI
7 7
 
8
-struct EditScreenTimeoutView: View {
8
+struct ScreenTimeoutEditorView: View {
9 9
     @EnvironmentObject private var meter: Meter
10 10
 
11 11
     var body: some View {