@@ -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. |
|
@@ -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 |
|
@@ -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 */, |
@@ -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 |
@@ -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 |
|
@@ -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) |
@@ -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] |
@@ -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 |
@@ -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 |
} |
@@ -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 |
|
@@ -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,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. |
@@ -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 |
+} |
|
@@ -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 |
-} |
|
@@ -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 |
-} |
|
@@ -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 {
|
@@ -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 |
+} |
|
@@ -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 |
|
@@ -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 |
+} |
|
@@ -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 |
+} |
|
@@ -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 {
|
@@ -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 |
} |
@@ -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 |
@@ -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 |
} |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
} |
@@ -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 {
|
@@ -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 {
|