@@ -3,15 +3,15 @@ |
||
| 3 | 3 |
archiveVersion = 1; |
| 4 | 4 |
classes = {
|
| 5 | 5 |
}; |
| 6 |
- objectVersion = 54; |
|
| 6 |
+ objectVersion = 70; |
|
| 7 | 7 |
objects = {
|
| 8 | 8 |
|
| 9 | 9 |
/* Begin PBXBuildFile section */ |
| 10 |
+ 3407A133FADB8858DC2A1FED /* MeterNameStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7396C8BB36F4E7F8E0CD8FF8 /* MeterNameStore.swift */; };
|
|
| 10 | 11 |
4308CF8624176CAB0002E80B /* DataGroupRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4308CF8524176CAB0002E80B /* DataGroupRowView.swift */; };
|
| 11 | 12 |
4308CF882417770D0002E80B /* DataGroupsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4308CF872417770D0002E80B /* DataGroupsView.swift */; };
|
| 12 | 13 |
430CB4FC245E07EB006525C2 /* ChevronView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430CB4FB245E07EB006525C2 /* ChevronView.swift */; };
|
| 13 | 14 |
4311E63A241384960080EA59 /* DeviceHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4311E639241384960080EA59 /* DeviceHelpView.swift */; };
|
| 14 |
- E430FB6B7CB3E0D4189F6D7D /* MeterMappingDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA4CE53B6B2C4EBA42C81A /* MeterMappingDebugView.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 */; };
|
@@ -28,9 +28,25 @@ |
||
| 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 |
- 3407A133FADB8858DC2A1FED /* MeterNameStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7396C8BB36F4E7F8E0CD8FF8 /* MeterNameStore.swift */; };
|
|
| 32 | 31 |
4383B468240F845500DAAEBF /* MacAdress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4383B467240F845500DAAEBF /* MacAdress.swift */; };
|
| 33 | 32 |
4383B46A240FE4A600DAAEBF /* MeterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4383B469240FE4A600DAAEBF /* MeterView.swift */; };
|
| 33 |
+ D28F11013C8E4A7A00A10011 /* MeterConnectionTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11023C8E4A7A00A10012 /* MeterConnectionTabView.swift */; };
|
|
| 34 |
+ D28F11033C8E4A7A00A10013 /* MeterLiveTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11043C8E4A7A00A10014 /* MeterLiveTabView.swift */; };
|
|
| 35 |
+ D28F11053C8E4A7A00A10015 /* MeterChartTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11063C8E4A7A00A10016 /* MeterChartTabView.swift */; };
|
|
| 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 */; };
|
|
| 34 | 50 |
438695892463F062008855A9 /* Measurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438695882463F062008855A9 /* Measurements.swift */; };
|
| 35 | 51 |
4386958B2F6A1001008855A9 /* UMProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4386958A2F6A1001008855A9 /* UMProtocol.swift */; };
|
| 36 | 52 |
4386958D2F6A1002008855A9 /* TC66Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4386958C2F6A1002008855A9 /* TC66Protocol.swift */; };
|
@@ -48,9 +64,11 @@ |
||
| 48 | 64 |
43CBF66F240BF3ED00255B8B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43CBF66D240BF3ED00255B8B /* LaunchScreen.storyboard */; };
|
| 49 | 65 |
43CBF677240C043E00255B8B /* BluetoothManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CBF676240C043E00255B8B /* BluetoothManager.swift */; };
|
| 50 | 66 |
43CBF681240D153000255B8B /* CBManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CBF680240D153000255B8B /* CBManagerState.swift */; };
|
| 51 |
- 43DFBE402441A37B004A47EA /* BorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DFBE3F2441A37B004A47EA /* BorderView.swift */; };
|
|
| 52 | 67 |
43ED78AE2420A0BE00974487 /* BluetoothSerial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43ED78AD2420A0BE00974487 /* BluetoothSerial.swift */; };
|
| 53 | 68 |
43F7792B2465AE1600745DF4 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F7792A2465AE1600745DF4 /* UIView.swift */; };
|
| 69 |
+ AAD5F9A72B1CAC0700F8E4F9 /* MeterDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD5F9A32B1CAC0700F8E4F9 /* MeterDetailView.swift */; };
|
|
| 70 |
+ AAD5F9B12B1CAC7A00F8E4F9 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD5F9B22B1CAC7A00F8E4F9 /* SidebarView.swift */; };
|
|
| 71 |
+ E430FB6B7CB3E0D4189F6D7D /* MeterMappingDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA4CE53B6B2C4EBA42C81A /* MeterMappingDebugView.swift */; };
|
|
| 54 | 72 |
/* End PBXBuildFile section */ |
| 55 | 73 |
|
| 56 | 74 |
/* Begin PBXFileReference section */ |
@@ -89,7 +107,6 @@ |
||
| 89 | 107 |
4308CF872417770D0002E80B /* DataGroupsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGroupsView.swift; sourceTree = "<group>"; };
|
| 90 | 108 |
430CB4FB245E07EB006525C2 /* ChevronView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronView.swift; sourceTree = "<group>"; };
|
| 91 | 109 |
4311E639241384960080EA59 /* DeviceHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceHelpView.swift; sourceTree = "<group>"; };
|
| 92 |
- 56BA4CE53B6B2C4EBA42C81A /* MeterMappingDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterMappingDebugView.swift; sourceTree = "<group>"; };
|
|
| 93 | 110 |
4327461A24619CED0009BE4B /* MeterRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterRowView.swift; sourceTree = "<group>"; };
|
| 94 | 111 |
432EA6432445A559006FC905 /* ChartContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartContext.swift; sourceTree = "<group>"; };
|
| 95 | 112 |
4351E7BA24685ACD00E798A3 /* CGPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPoint.swift; sourceTree = "<group>"; };
|
@@ -105,9 +122,25 @@ |
||
| 105 | 122 |
4383B45F240EB2D000DAAEBF /* Meter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Meter.swift; sourceTree = "<group>"; };
|
| 106 | 123 |
4383B461240EB5E400DAAEBF /* AppData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppData.swift; sourceTree = "<group>"; };
|
| 107 | 124 |
4383B464240EB6B200DAAEBF /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = "<group>"; };
|
| 108 |
- 7396C8BB36F4E7F8E0CD8FF8 /* MeterNameStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterNameStore.swift; sourceTree = "<group>"; };
|
|
| 109 | 125 |
4383B467240F845500DAAEBF /* MacAdress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAdress.swift; sourceTree = "<group>"; };
|
| 110 | 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>"; };
|
|
| 128 |
+ D28F11043C8E4A7A00A10014 /* MeterLiveTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterLiveTabView.swift; sourceTree = "<group>"; };
|
|
| 129 |
+ D28F11063C8E4A7A00A10016 /* MeterChartTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterChartTabView.swift; sourceTree = "<group>"; };
|
|
| 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>"; };
|
|
| 111 | 144 |
438695882463F062008855A9 /* Measurements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurements.swift; sourceTree = "<group>"; };
|
| 112 | 145 |
4386958A2F6A1001008855A9 /* UMProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UMProtocol.swift; sourceTree = "<group>"; };
|
| 113 | 146 |
4386958C2F6A1002008855A9 /* TC66Protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TC66Protocol.swift; sourceTree = "<group>"; };
|
@@ -128,11 +161,18 @@ |
||
| 128 | 161 |
43CBF676240C043E00255B8B /* BluetoothManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothManager.swift; sourceTree = "<group>"; };
|
| 129 | 162 |
43CBF67A240C0D8A00255B8B /* USB Meter.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "USB Meter.entitlements"; sourceTree = "<group>"; };
|
| 130 | 163 |
43CBF680240D153000255B8B /* CBManagerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBManagerState.swift; sourceTree = "<group>"; };
|
| 131 |
- 43DFBE3F2441A37B004A47EA /* BorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderView.swift; sourceTree = "<group>"; };
|
|
| 132 | 164 |
43ED78AD2420A0BE00974487 /* BluetoothSerial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothSerial.swift; sourceTree = "<group>"; };
|
| 133 | 165 |
43F7792A2465AE1600745DF4 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
|
| 166 |
+ 56BA4CE53B6B2C4EBA42C81A /* MeterMappingDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterMappingDebugView.swift; sourceTree = "<group>"; };
|
|
| 167 |
+ 7396C8BB36F4E7F8E0CD8FF8 /* MeterNameStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterNameStore.swift; sourceTree = "<group>"; };
|
|
| 168 |
+ AAD5F9A32B1CAC0700F8E4F9 /* MeterDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterDetailView.swift; sourceTree = "<group>"; };
|
|
| 169 |
+ AAD5F9B22B1CAC7A00F8E4F9 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
|
|
| 134 | 170 |
/* End PBXFileReference section */ |
| 135 | 171 |
|
| 172 |
+/* Begin PBXFileSystemSynchronizedRootGroup section */ |
|
| 173 |
+ 43BE08E12F78F49500250EEC /* SidebarList */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = SidebarList; sourceTree = "<group>"; };
|
|
| 174 |
+/* End PBXFileSystemSynchronizedRootGroup section */ |
|
| 175 |
+ |
|
| 136 | 176 |
/* Begin PBXFrameworksBuildPhase section */ |
| 137 | 177 |
43CBF659240BF3EB00255B8B /* Frameworks */ = {
|
| 138 | 178 |
isa = PBXFrameworksBuildPhase; |
@@ -273,16 +313,160 @@ |
||
| 273 | 313 |
path = Measurements; |
| 274 | 314 |
sourceTree = "<group>"; |
| 275 | 315 |
}; |
| 276 |
- 437D47CF2415F8CF00B7768E /* Meter */ = {
|
|
| 316 |
+ D28F11253C8E4A7A00A10035 /* Components */ = {
|
|
| 317 |
+ isa = PBXGroup; |
|
| 318 |
+ children = ( |
|
| 319 |
+ D28F11223C8E4A7A00A10032 /* LiveMetricRange.swift */, |
|
| 320 |
+ D28F11243C8E4A7A00A10034 /* LoadResistanceSymbolView.swift */, |
|
| 321 |
+ ); |
|
| 322 |
+ path = Components; |
|
| 323 |
+ sourceTree = "<group>"; |
|
| 324 |
+ }; |
|
| 325 |
+ D28F11263C8E4A7A00A10036 /* Live */ = {
|
|
| 277 | 326 |
isa = PBXGroup; |
| 278 | 327 |
children = ( |
| 279 |
- 4383B469240FE4A600DAAEBF /* MeterView.swift */, |
|
| 280 | 328 |
437D47D02415F91B00B7768E /* LiveView.swift */, |
| 329 |
+ D28F11253C8E4A7A00A10035 /* Components */, |
|
| 330 |
+ ); |
|
| 331 |
+ path = Live; |
|
| 332 |
+ sourceTree = "<group>"; |
|
| 333 |
+ }; |
|
| 334 |
+ D28F11373C8E4A7A00A10047 /* Components */ = {
|
|
| 335 |
+ isa = PBXGroup; |
|
| 336 |
+ children = ( |
|
| 337 |
+ D28F11323C8E4A7A00A10042 /* ControlActionButtonView.swift */, |
|
| 338 |
+ D28F11343C8E4A7A00A10044 /* ControlCurrentScreenCardView.swift */, |
|
| 339 |
+ ); |
|
| 340 |
+ path = Components; |
|
| 341 |
+ sourceTree = "<group>"; |
|
| 342 |
+ }; |
|
| 343 |
+ D28F11383C8E4A7A00A10048 /* Components */ = {
|
|
| 344 |
+ isa = PBXGroup; |
|
| 345 |
+ children = ( |
|
| 346 |
+ D28F11363C8E4A7A00A10046 /* RecordingMetricsTableView.swift */, |
|
| 347 |
+ ); |
|
| 348 |
+ path = Components; |
|
| 349 |
+ sourceTree = "<group>"; |
|
| 350 |
+ }; |
|
| 351 |
+ D28F11273C8E4A7A00A10037 /* Recording */ = {
|
|
| 352 |
+ isa = PBXGroup; |
|
| 353 |
+ children = ( |
|
| 281 | 354 |
437D47D42415FD8C00B7768E /* RecordingView.swift */, |
| 355 |
+ D28F11383C8E4A7A00A10048 /* Components */, |
|
| 356 |
+ ); |
|
| 357 |
+ path = Recording; |
|
| 358 |
+ sourceTree = "<group>"; |
|
| 359 |
+ }; |
|
| 360 |
+ D28F11283C8E4A7A00A10038 /* Control */ = {
|
|
| 361 |
+ isa = PBXGroup; |
|
| 362 |
+ children = ( |
|
| 282 | 363 |
437D47D62415FDF300B7768E /* ControlView.swift */, |
| 283 |
- 4308CF89241777130002E80B /* Data Groups */, |
|
| 364 |
+ D28F11373C8E4A7A00A10047 /* Components */, |
|
| 365 |
+ ); |
|
| 366 |
+ path = Control; |
|
| 367 |
+ sourceTree = "<group>"; |
|
| 368 |
+ }; |
|
| 369 |
+ D28F10013C8E4A7A00A10001 /* Screens */ = {
|
|
| 370 |
+ isa = PBXGroup; |
|
| 371 |
+ children = ( |
|
| 372 |
+ D28F11263C8E4A7A00A10036 /* Live */, |
|
| 373 |
+ D28F11273C8E4A7A00A10037 /* Recording */, |
|
| 374 |
+ D28F11283C8E4A7A00A10038 /* Control */, |
|
| 375 |
+ ); |
|
| 376 |
+ path = Screens; |
|
| 377 |
+ sourceTree = "<group>"; |
|
| 378 |
+ }; |
|
| 379 |
+ D28F111B3C8E4A7A00A1002B /* Components */ = {
|
|
| 380 |
+ isa = PBXGroup; |
|
| 381 |
+ children = ( |
|
| 382 |
+ D28F11123C8E4A7A00A10022 /* MeterInfoCard.swift */, |
|
| 383 |
+ D28F11143C8E4A7A00A10024 /* MeterInfoRow.swift */, |
|
| 384 |
+ D28F113A3C8E4A7A00A1004A /* ConnectionStatusBadgeView.swift */, |
|
| 385 |
+ D28F113C3C8E4A7A00A1004C /* ConnectionPrimaryActionView.swift */, |
|
| 386 |
+ D28F113E3C8E4A7A00A1004E /* ConnectionHomeInfoPreviewView.swift */, |
|
| 387 |
+ ); |
|
| 388 |
+ path = Components; |
|
| 389 |
+ sourceTree = "<group>"; |
|
| 390 |
+ }; |
|
| 391 |
+ D28F111C3C8E4A7A00A1002C /* Components */ = {
|
|
| 392 |
+ isa = PBXGroup; |
|
| 393 |
+ children = ( |
|
| 394 |
+ D28F11163C8E4A7A00A10026 /* EditNameView.swift */, |
|
| 395 |
+ D28F11183C8E4A7A00A10028 /* EditScreenTimeoutView.swift */, |
|
| 396 |
+ D28F111A3C8E4A7A00A1002A /* EditScreenBrightnessView.swift */, |
|
| 397 |
+ ); |
|
| 398 |
+ path = Components; |
|
| 399 |
+ sourceTree = "<group>"; |
|
| 400 |
+ }; |
|
| 401 |
+ D28F110A3C8E4A7A00A1001A /* Connection */ = {
|
|
| 402 |
+ isa = PBXGroup; |
|
| 403 |
+ children = ( |
|
| 404 |
+ D28F11023C8E4A7A00A10012 /* MeterConnectionTabView.swift */, |
|
| 405 |
+ D28F111B3C8E4A7A00A1002B /* Components */, |
|
| 406 |
+ ); |
|
| 407 |
+ path = Connection; |
|
| 408 |
+ sourceTree = "<group>"; |
|
| 409 |
+ }; |
|
| 410 |
+ D28F110B3C8E4A7A00A1001B /* Live */ = {
|
|
| 411 |
+ isa = PBXGroup; |
|
| 412 |
+ children = ( |
|
| 413 |
+ D28F11043C8E4A7A00A10014 /* MeterLiveTabView.swift */, |
|
| 414 |
+ ); |
|
| 415 |
+ path = Live; |
|
| 416 |
+ sourceTree = "<group>"; |
|
| 417 |
+ }; |
|
| 418 |
+ D28F110C3C8E4A7A00A1001C /* Chart */ = {
|
|
| 419 |
+ isa = PBXGroup; |
|
| 420 |
+ children = ( |
|
| 421 |
+ D28F11063C8E4A7A00A10016 /* MeterChartTabView.swift */, |
|
| 422 |
+ ); |
|
| 423 |
+ path = Chart; |
|
| 424 |
+ sourceTree = "<group>"; |
|
| 425 |
+ }; |
|
| 426 |
+ D28F110D3C8E4A7A00A1001D /* Settings */ = {
|
|
| 427 |
+ isa = PBXGroup; |
|
| 428 |
+ children = ( |
|
| 429 |
+ D28F11083C8E4A7A00A10018 /* MeterSettingsTabView.swift */, |
|
| 430 |
+ D28F111C3C8E4A7A00A1002C /* Components */, |
|
| 431 |
+ ); |
|
| 432 |
+ path = Settings; |
|
| 433 |
+ sourceTree = "<group>"; |
|
| 434 |
+ }; |
|
| 435 |
+ D28F11093C8E4A7A00A10019 /* Tabs */ = {
|
|
| 436 |
+ isa = PBXGroup; |
|
| 437 |
+ children = ( |
|
| 438 |
+ D28F110A3C8E4A7A00A1001A /* Connection */, |
|
| 439 |
+ D28F110B3C8E4A7A00A1001B /* Live */, |
|
| 440 |
+ D28F110C3C8E4A7A00A1001C /* Chart */, |
|
| 441 |
+ D28F110D3C8E4A7A00A1001D /* Settings */, |
|
| 442 |
+ ); |
|
| 443 |
+ path = Tabs; |
|
| 444 |
+ sourceTree = "<group>"; |
|
| 445 |
+ }; |
|
| 446 |
+ D28F10033C8E4A7A00A10003 /* Generic */ = {
|
|
| 447 |
+ isa = PBXGroup; |
|
| 448 |
+ children = ( |
|
| 284 | 449 |
4360A34C241CBB3800B464F9 /* RSSIView.swift */, |
| 285 | 450 |
430CB4FB245E07EB006525C2 /* ChevronView.swift */, |
| 451 |
+ ); |
|
| 452 |
+ path = Generic; |
|
| 453 |
+ sourceTree = "<group>"; |
|
| 454 |
+ }; |
|
| 455 |
+ D28F10023C8E4A7A00A10002 /* Components */ = {
|
|
| 456 |
+ isa = PBXGroup; |
|
| 457 |
+ children = ( |
|
| 458 |
+ D28F10033C8E4A7A00A10003 /* Generic */, |
|
| 459 |
+ ); |
|
| 460 |
+ path = Components; |
|
| 461 |
+ sourceTree = "<group>"; |
|
| 462 |
+ }; |
|
| 463 |
+ 437D47CF2415F8CF00B7768E /* Meter */ = {
|
|
| 464 |
+ isa = PBXGroup; |
|
| 465 |
+ children = ( |
|
| 466 |
+ 4383B469240FE4A600DAAEBF /* MeterView.swift */, |
|
| 467 |
+ D28F11093C8E4A7A00A10019 /* Tabs */, |
|
| 468 |
+ D28F10013C8E4A7A00A10001 /* Screens */, |
|
| 469 |
+ 4308CF89241777130002E80B /* Data Groups */, |
|
| 286 | 470 |
43554B3024444983004E66F5 /* Measurements */, |
| 287 | 471 |
); |
| 288 | 472 |
path = Meter; |
@@ -371,11 +555,13 @@ |
||
| 371 | 555 |
isa = PBXGroup; |
| 372 | 556 |
children = ( |
| 373 | 557 |
43CBF666240BF3EB00255B8B /* ContentView.swift */, |
| 558 |
+ AAD5F9BB2B1CC10000F8E4F9 /* Sidebar */, |
|
| 374 | 559 |
56BA4CE53B6B2C4EBA42C81A /* MeterMappingDebugView.swift */, |
| 375 | 560 |
4327461A24619CED0009BE4B /* MeterRowView.swift */, |
| 376 | 561 |
437D47CF2415F8CF00B7768E /* Meter */, |
| 562 |
+ D28F10023C8E4A7A00A10002 /* Components */, |
|
| 377 | 563 |
4311E639241384960080EA59 /* DeviceHelpView.swift */, |
| 378 |
- 43DFBE3F2441A37B004A47EA /* BorderView.swift */, |
|
| 564 |
+ AAD5F9A32B1CAC0700F8E4F9 /* MeterDetailView.swift */, |
|
| 379 | 565 |
); |
| 380 | 566 |
path = Views; |
| 381 | 567 |
sourceTree = "<group>"; |
@@ -396,6 +582,15 @@ |
||
| 396 | 582 |
path = Extensions; |
| 397 | 583 |
sourceTree = "<group>"; |
| 398 | 584 |
}; |
| 585 |
+ AAD5F9BB2B1CC10000F8E4F9 /* Sidebar */ = {
|
|
| 586 |
+ isa = PBXGroup; |
|
| 587 |
+ children = ( |
|
| 588 |
+ AAD5F9B22B1CAC7A00F8E4F9 /* SidebarView.swift */, |
|
| 589 |
+ 43BE08E12F78F49500250EEC /* SidebarList */, |
|
| 590 |
+ ); |
|
| 591 |
+ path = Sidebar; |
|
| 592 |
+ sourceTree = "<group>"; |
|
| 593 |
+ }; |
|
| 399 | 594 |
/* End PBXGroup section */ |
| 400 | 595 |
|
| 401 | 596 |
/* Begin PBXNativeTarget section */ |
@@ -411,6 +606,9 @@ |
||
| 411 | 606 |
); |
| 412 | 607 |
dependencies = ( |
| 413 | 608 |
); |
| 609 |
+ fileSystemSynchronizedGroups = ( |
|
| 610 |
+ 43BE08E12F78F49500250EEC /* SidebarList */, |
|
| 611 |
+ ); |
|
| 414 | 612 |
name = "USB Meter"; |
| 415 | 613 |
packageProductDependencies = ( |
| 416 | 614 |
4347F01C28D717C1007EE7B1 /* CryptoSwift */, |
@@ -485,17 +683,33 @@ |
||
| 485 | 683 |
4383B468240F845500DAAEBF /* MacAdress.swift in Sources */, |
| 486 | 684 |
43CBF681240D153000255B8B /* CBManagerState.swift in Sources */, |
| 487 | 685 |
4383B46A240FE4A600DAAEBF /* MeterView.swift in Sources */, |
| 686 |
+ D28F11013C8E4A7A00A10011 /* MeterConnectionTabView.swift in Sources */, |
|
| 687 |
+ D28F11033C8E4A7A00A10013 /* MeterLiveTabView.swift in Sources */, |
|
| 688 |
+ D28F11053C8E4A7A00A10015 /* MeterChartTabView.swift in Sources */, |
|
| 689 |
+ 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 */, |
|
| 488 | 703 |
4360A34D241CBB3800B464F9 /* RSSIView.swift in Sources */, |
| 489 | 704 |
437D47D12415F91B00B7768E /* LiveView.swift in Sources */, |
| 490 |
- 4360A34F241D5CF100B464F9 /* MeterSettingsView.swift in Sources */, |
|
| 491 | 705 |
4383B465240EB6B200DAAEBF /* UserDefault.swift in Sources */, |
| 492 | 706 |
3407A133FADB8858DC2A1FED /* MeterNameStore.swift in Sources */, |
| 493 | 707 |
43CBF677240C043E00255B8B /* BluetoothManager.swift in Sources */, |
| 494 | 708 |
43CBF660240BF3EB00255B8B /* AppDelegate.swift in Sources */, |
| 495 | 709 |
438B9555246D2D7500E61AE7 /* Path.swift in Sources */, |
| 496 | 710 |
4383B460240EB2D000DAAEBF /* Meter.swift in Sources */, |
| 711 |
+ AAD5F9B12B1CAC7A00F8E4F9 /* SidebarView.swift in Sources */, |
|
| 497 | 712 |
43CBF667240BF3EB00255B8B /* ContentView.swift in Sources */, |
| 498 |
- 43DFBE402441A37B004A47EA /* BorderView.swift in Sources */, |
|
| 499 | 713 |
437F0AB72463108F005DEBEC /* MeasurementChartView.swift in Sources */, |
| 500 | 714 |
437D47D32415FB7E00B7768E /* Decimal.swift in Sources */, |
| 501 | 715 |
43874C7F2414F3F400525397 /* Float.swift in Sources */, |
@@ -515,6 +729,7 @@ |
||
| 515 | 729 |
430CB4FC245E07EB006525C2 /* ChevronView.swift in Sources */, |
| 516 | 730 |
43554B3424444B0E004E66F5 /* Date.swift in Sources */, |
| 517 | 731 |
4311E63A241384960080EA59 /* DeviceHelpView.swift in Sources */, |
| 732 |
+ AAD5F9A72B1CAC0700F8E4F9 /* MeterDetailView.swift in Sources */, |
|
| 518 | 733 |
E430FB6B7CB3E0D4189F6D7D /* MeterMappingDebugView.swift in Sources */, |
| 519 | 734 |
439D996524234B98008DE3AA /* BluetoothRadio.swift in Sources */, |
| 520 | 735 |
438695892463F062008855A9 /* Measurements.swift in Sources */, |
@@ -748,7 +963,6 @@ |
||
| 748 | 963 |
productName = CryptoSwift; |
| 749 | 964 |
}; |
| 750 | 965 |
/* End XCSwiftPackageProductDependency section */ |
| 751 |
- |
|
| 752 | 966 |
}; |
| 753 | 967 |
rootObject = 43CBF654240BF3EB00255B8B /* Project object */; |
| 754 | 968 |
} |
@@ -11,7 +11,7 @@ import Combine |
||
| 11 | 11 |
import CoreBluetooth |
| 12 | 12 |
|
| 13 | 13 |
final class AppData : ObservableObject {
|
| 14 |
- struct KnownMeterSummary: Identifiable {
|
|
| 14 |
+ struct MeterSummary: Identifiable {
|
|
| 15 | 15 |
let macAddress: String |
| 16 | 16 |
let displayName: String |
| 17 | 17 |
let modelSummary: String |
@@ -73,8 +73,8 @@ final class AppData : ObservableObject {
|
||
| 73 | 73 |
meterStore.upsert(macAddress: macAddress, name: nil, temperatureUnitRawValue: preference.rawValue) |
| 74 | 74 |
} |
| 75 | 75 |
|
| 76 |
- func registerKnownMeter(macAddress: String, modelName: String?, advertisedName: String?) {
|
|
| 77 |
- meterStore.registerKnownMeter(macAddress: macAddress, modelName: modelName, advertisedName: advertisedName) |
|
| 76 |
+ func registerMeter(macAddress: String, modelName: String?, advertisedName: String?) {
|
|
| 77 |
+ meterStore.registerMeter(macAddress: macAddress, modelName: modelName, advertisedName: advertisedName) |
|
| 78 | 78 |
} |
| 79 | 79 |
|
| 80 | 80 |
func noteMeterSeen(at date: Date, macAddress: String) {
|
@@ -93,7 +93,7 @@ final class AppData : ObservableObject {
|
||
| 93 | 93 |
meterStore.lastConnected(for: macAddress) |
| 94 | 94 |
} |
| 95 | 95 |
|
| 96 |
- var knownMeters: [KnownMeterSummary] {
|
|
| 96 |
+ var meterSummaries: [MeterSummary] {
|
|
| 97 | 97 |
let liveMetersByMAC = Dictionary(uniqueKeysWithValues: meters.values.map { ($0.btSerial.macAddress.description, $0) })
|
| 98 | 98 |
let recordsByMAC = Dictionary(uniqueKeysWithValues: meterStore.allRecords().map { ($0.macAddress, $0) })
|
| 99 | 99 |
let macAddresses = Set(recordsByMAC.keys).union(liveMetersByMAC.keys) |
@@ -102,10 +102,10 @@ final class AppData : ObservableObject {
|
||
| 102 | 102 |
let liveMeter = liveMetersByMAC[macAddress] |
| 103 | 103 |
let record = recordsByMAC[macAddress] |
| 104 | 104 |
|
| 105 |
- return KnownMeterSummary( |
|
| 105 |
+ return MeterSummary( |
|
| 106 | 106 |
macAddress: macAddress, |
| 107 | 107 |
displayName: liveMeter?.name ?? record?.customName ?? macAddress, |
| 108 |
- modelSummary: liveMeter?.deviceModelSummary ?? record?.modelName ?? "Known meter", |
|
| 108 |
+ modelSummary: liveMeter?.deviceModelSummary ?? record?.modelName ?? "Meter", |
|
| 109 | 109 |
advertisedName: liveMeter?.modelString ?? record?.advertisedName, |
| 110 | 110 |
lastSeen: liveMeter?.lastSeen ?? record?.lastSeen, |
| 111 | 111 |
lastConnected: liveMeter?.lastConnectedAt ?? record?.lastConnected, |
@@ -152,3 +152,18 @@ final class AppData : ObservableObject {
|
||
| 152 | 152 |
} |
| 153 | 153 |
} |
| 154 | 154 |
} |
| 155 |
+ |
|
| 156 |
+extension AppData.MeterSummary {
|
|
| 157 |
+ var tint: Color {
|
|
| 158 |
+ switch modelSummary {
|
|
| 159 |
+ case "UM25C": |
|
| 160 |
+ return .blue |
|
| 161 |
+ case "UM34C": |
|
| 162 |
+ return .yellow |
|
| 163 |
+ case "TC66C": |
|
| 164 |
+ return Model.TC66C.color |
|
| 165 |
+ default: |
|
| 166 |
+ return .secondary |
|
| 167 |
+ } |
|
| 168 |
+ } |
|
| 169 |
+} |
|
@@ -9,533 +9,12 @@ |
||
| 9 | 9 |
//MARK: Bluetooth Icon: https://upload.wikimedia.org/wikipedia/commons/d/da/Bluetooth.svg |
| 10 | 10 |
|
| 11 | 11 |
import SwiftUI |
| 12 |
-import Combine |
|
| 13 | 12 |
|
| 14 | 13 |
struct ContentView: View {
|
| 15 |
- private enum HelpAutoReason: String {
|
|
| 16 |
- case bluetoothPermission |
|
| 17 |
- case cloudSyncUnavailable |
|
| 18 |
- case noDevicesDetected |
|
| 19 |
- |
|
| 20 |
- var tint: Color {
|
|
| 21 |
- switch self {
|
|
| 22 |
- case .bluetoothPermission: |
|
| 23 |
- return .orange |
|
| 24 |
- case .cloudSyncUnavailable: |
|
| 25 |
- return .indigo |
|
| 26 |
- case .noDevicesDetected: |
|
| 27 |
- return .yellow |
|
| 28 |
- } |
|
| 29 |
- } |
|
| 30 |
- |
|
| 31 |
- var symbol: String {
|
|
| 32 |
- switch self {
|
|
| 33 |
- case .bluetoothPermission: |
|
| 34 |
- return "bolt.horizontal.circle.fill" |
|
| 35 |
- case .cloudSyncUnavailable: |
|
| 36 |
- return "icloud.slash.fill" |
|
| 37 |
- case .noDevicesDetected: |
|
| 38 |
- return "magnifyingglass.circle.fill" |
|
| 39 |
- } |
|
| 40 |
- } |
|
| 41 |
- |
|
| 42 |
- var badgeTitle: String {
|
|
| 43 |
- switch self {
|
|
| 44 |
- case .bluetoothPermission: |
|
| 45 |
- return "Required" |
|
| 46 |
- case .cloudSyncUnavailable: |
|
| 47 |
- return "Sync Off" |
|
| 48 |
- case .noDevicesDetected: |
|
| 49 |
- return "Suggested" |
|
| 50 |
- } |
|
| 51 |
- } |
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- @EnvironmentObject private var appData: AppData |
|
| 55 |
- @State private var isHelpExpanded = false |
|
| 56 |
- @State private var dismissedAutoHelpReason: HelpAutoReason? |
|
| 57 |
- @State private var now = Date() |
|
| 58 |
- private let helpRefreshTimer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() |
|
| 59 |
- private let noDevicesHelpDelay: TimeInterval = 12 |
|
| 60 |
- |
|
| 61 | 14 |
var body: some View {
|
| 62 | 15 |
NavigationView {
|
| 63 |
- ScrollView {
|
|
| 64 |
- VStack(alignment: .leading, spacing: 18) {
|
|
| 65 |
- headerCard |
|
| 66 |
- helpSection |
|
| 67 |
- devicesSection |
|
| 68 |
- debugSection |
|
| 69 |
- } |
|
| 70 |
- .padding() |
|
| 71 |
- } |
|
| 72 |
- .background( |
|
| 73 |
- LinearGradient( |
|
| 74 |
- colors: [ |
|
| 75 |
- appData.bluetoothManager.managerState.color.opacity(0.18), |
|
| 76 |
- Color.clear |
|
| 77 |
- ], |
|
| 78 |
- startPoint: .topLeading, |
|
| 79 |
- endPoint: .bottomTrailing |
|
| 80 |
- ) |
|
| 81 |
- .ignoresSafeArea() |
|
| 82 |
- ) |
|
| 83 |
- .navigationBarTitle(Text("USB Meters"), displayMode: .inline)
|
|
| 84 |
- } |
|
| 85 |
- .onAppear {
|
|
| 86 |
- appData.bluetoothManager.start() |
|
| 87 |
- now = Date() |
|
| 88 |
- } |
|
| 89 |
- .onReceive(helpRefreshTimer) { currentDate in
|
|
| 90 |
- now = currentDate |
|
| 91 |
- } |
|
| 92 |
- .onChange(of: activeHelpAutoReason) { newReason in
|
|
| 93 |
- if newReason == nil {
|
|
| 94 |
- dismissedAutoHelpReason = nil |
|
| 95 |
- } |
|
| 96 |
- } |
|
| 97 |
- } |
|
| 98 |
- |
|
| 99 |
- private var headerCard: some View {
|
|
| 100 |
- VStack(alignment: .leading, spacing: 10) {
|
|
| 101 |
- Text("USB Meters")
|
|
| 102 |
- .font(.system(.title2, design: .rounded).weight(.bold)) |
|
| 103 |
- Text("Browse nearby supported meters and jump into live diagnostics, charge records, and device controls.")
|
|
| 104 |
- .font(.footnote) |
|
| 105 |
- .foregroundColor(.secondary) |
|
| 106 |
- HStack {
|
|
| 107 |
- Label("Bluetooth", systemImage: "bolt.horizontal.circle.fill")
|
|
| 108 |
- .font(.footnote.weight(.semibold)) |
|
| 109 |
- .foregroundColor(appData.bluetoothManager.managerState.color) |
|
| 110 |
- Spacer() |
|
| 111 |
- Text(bluetoothStatusText) |
|
| 112 |
- .font(.caption.weight(.semibold)) |
|
| 113 |
- .foregroundColor(.secondary) |
|
| 114 |
- } |
|
| 115 |
- } |
|
| 116 |
- .padding(18) |
|
| 117 |
- .meterCard(tint: appData.bluetoothManager.managerState.color, fillOpacity: 0.22, strokeOpacity: 0.26) |
|
| 118 |
- } |
|
| 119 |
- |
|
| 120 |
- private var helpSection: some View {
|
|
| 121 |
- VStack(alignment: .leading, spacing: 12) {
|
|
| 122 |
- Button(action: toggleHelpSection) {
|
|
| 123 |
- HStack(spacing: 14) {
|
|
| 124 |
- Image(systemName: helpSectionSymbol) |
|
| 125 |
- .font(.system(size: 18, weight: .semibold)) |
|
| 126 |
- .foregroundColor(helpSectionTint) |
|
| 127 |
- .frame(width: 42, height: 42) |
|
| 128 |
- .background(Circle().fill(helpSectionTint.opacity(0.18))) |
|
| 129 |
- |
|
| 130 |
- VStack(alignment: .leading, spacing: 4) {
|
|
| 131 |
- Text("Help")
|
|
| 132 |
- .font(.headline) |
|
| 133 |
- Text(helpSectionSummary) |
|
| 134 |
- .font(.caption) |
|
| 135 |
- .foregroundColor(.secondary) |
|
| 136 |
- } |
|
| 137 |
- |
|
| 138 |
- Spacer() |
|
| 139 |
- |
|
| 140 |
- if let activeHelpAutoReason {
|
|
| 141 |
- Text(activeHelpAutoReason.badgeTitle) |
|
| 142 |
- .font(.caption2.weight(.bold)) |
|
| 143 |
- .foregroundColor(activeHelpAutoReason.tint) |
|
| 144 |
- .padding(.horizontal, 10) |
|
| 145 |
- .padding(.vertical, 6) |
|
| 146 |
- .background( |
|
| 147 |
- Capsule(style: .continuous) |
|
| 148 |
- .fill(activeHelpAutoReason.tint.opacity(0.12)) |
|
| 149 |
- ) |
|
| 150 |
- .overlay( |
|
| 151 |
- Capsule(style: .continuous) |
|
| 152 |
- .stroke(activeHelpAutoReason.tint.opacity(0.22), lineWidth: 1) |
|
| 153 |
- ) |
|
| 154 |
- } |
|
| 155 |
- |
|
| 156 |
- Image(systemName: helpIsExpanded ? "chevron.up" : "chevron.down") |
|
| 157 |
- .font(.footnote.weight(.bold)) |
|
| 158 |
- .foregroundColor(.secondary) |
|
| 159 |
- } |
|
| 160 |
- .padding(14) |
|
| 161 |
- .meterCard(tint: helpSectionTint, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 18) |
|
| 162 |
- } |
|
| 163 |
- .buttonStyle(.plain) |
|
| 164 |
- |
|
| 165 |
- if helpIsExpanded {
|
|
| 166 |
- if let activeHelpAutoReason {
|
|
| 167 |
- helpNoticeCard(for: activeHelpAutoReason) |
|
| 168 |
- } |
|
| 169 |
- |
|
| 170 |
- if activeHelpAutoReason == .cloudSyncUnavailable {
|
|
| 171 |
- Button(action: openSettings) {
|
|
| 172 |
- sidebarLinkCard( |
|
| 173 |
- title: "Open Settings", |
|
| 174 |
- subtitle: "Check Apple ID, iCloud Drive, and any restrictions affecting Cloud sync.", |
|
| 175 |
- symbol: "gearshape.fill", |
|
| 176 |
- tint: .indigo |
|
| 177 |
- ) |
|
| 178 |
- } |
|
| 179 |
- .buttonStyle(.plain) |
|
| 180 |
- } |
|
| 181 |
- |
|
| 182 |
- NavigationLink(destination: appData.bluetoothManager.managerState.helpView) {
|
|
| 183 |
- sidebarLinkCard( |
|
| 184 |
- title: "Bluetooth", |
|
| 185 |
- subtitle: "Permissions, adapter state, and connection tips.", |
|
| 186 |
- symbol: "bolt.horizontal.circle.fill", |
|
| 187 |
- tint: appData.bluetoothManager.managerState.color |
|
| 188 |
- ) |
|
| 189 |
- } |
|
| 190 |
- .buttonStyle(.plain) |
|
| 191 |
- |
|
| 192 |
- NavigationLink(destination: DeviceHelpView()) {
|
|
| 193 |
- sidebarLinkCard( |
|
| 194 |
- title: "Device", |
|
| 195 |
- subtitle: "Quick checks when a meter is not responding as expected.", |
|
| 196 |
- symbol: "questionmark.circle.fill", |
|
| 197 |
- tint: .orange |
|
| 198 |
- ) |
|
| 199 |
- } |
|
| 200 |
- .buttonStyle(.plain) |
|
| 201 |
- } |
|
| 202 |
- } |
|
| 203 |
- .animation(.easeInOut(duration: 0.22), value: helpIsExpanded) |
|
| 204 |
- } |
|
| 205 |
- |
|
| 206 |
- private var devicesSection: some View {
|
|
| 207 |
- VStack(alignment: .leading, spacing: 12) {
|
|
| 208 |
- HStack {
|
|
| 209 |
- Text("Known Meters")
|
|
| 210 |
- .font(.headline) |
|
| 211 |
- Spacer() |
|
| 212 |
- Text("\(appData.knownMeters.count)")
|
|
| 213 |
- .font(.caption.weight(.bold)) |
|
| 214 |
- .padding(.horizontal, 10) |
|
| 215 |
- .padding(.vertical, 6) |
|
| 216 |
- .meterCard(tint: .blue, fillOpacity: 0.18, strokeOpacity: 0.24, cornerRadius: 999) |
|
| 217 |
- } |
|
| 218 |
- |
|
| 219 |
- if appData.knownMeters.isEmpty {
|
|
| 220 |
- Text(devicesEmptyStateText) |
|
| 221 |
- .font(.footnote) |
|
| 222 |
- .foregroundColor(.secondary) |
|
| 223 |
- .frame(maxWidth: .infinity, alignment: .leading) |
|
| 224 |
- .padding(18) |
|
| 225 |
- .meterCard( |
|
| 226 |
- tint: isWaitingForFirstDiscovery ? .blue : .secondary, |
|
| 227 |
- fillOpacity: 0.14, |
|
| 228 |
- strokeOpacity: 0.20 |
|
| 229 |
- ) |
|
| 230 |
- } else {
|
|
| 231 |
- ForEach(appData.knownMeters) { knownMeter in
|
|
| 232 |
- if let meter = knownMeter.meter {
|
|
| 233 |
- NavigationLink(destination: MeterView().environmentObject(meter)) {
|
|
| 234 |
- MeterRowView() |
|
| 235 |
- .environmentObject(meter) |
|
| 236 |
- } |
|
| 237 |
- .buttonStyle(.plain) |
|
| 238 |
- } else {
|
|
| 239 |
- knownMeterCard(for: knownMeter) |
|
| 240 |
- } |
|
| 241 |
- } |
|
| 242 |
- } |
|
| 243 |
- } |
|
| 244 |
- } |
|
| 245 |
- |
|
| 246 |
- private var debugSection: some View {
|
|
| 247 |
- VStack(alignment: .leading, spacing: 12) {
|
|
| 248 |
- Text("Debug")
|
|
| 249 |
- .font(.headline) |
|
| 250 |
- debugLink |
|
| 251 |
- } |
|
| 252 |
- } |
|
| 253 |
- |
|
| 254 |
- private var debugLink: some View {
|
|
| 255 |
- NavigationLink(destination: MeterMappingDebugView()) {
|
|
| 256 |
- sidebarLinkCard( |
|
| 257 |
- title: "Meter Sync Debug", |
|
| 258 |
- subtitle: "Inspect meter name sync data and iCloud KVS visibility as seen by this device.", |
|
| 259 |
- symbol: "list.bullet.rectangle", |
|
| 260 |
- tint: .purple |
|
| 261 |
- ) |
|
| 262 |
- } |
|
| 263 |
- .buttonStyle(.plain) |
|
| 264 |
- } |
|
| 265 |
- |
|
| 266 |
- private var bluetoothStatusText: String {
|
|
| 267 |
- switch appData.bluetoothManager.managerState {
|
|
| 268 |
- case .poweredOff: |
|
| 269 |
- return "Off" |
|
| 270 |
- case .poweredOn: |
|
| 271 |
- return "On" |
|
| 272 |
- case .resetting: |
|
| 273 |
- return "Resetting" |
|
| 274 |
- case .unauthorized: |
|
| 275 |
- return "Unauthorized" |
|
| 276 |
- case .unknown: |
|
| 277 |
- return "Unknown" |
|
| 278 |
- case .unsupported: |
|
| 279 |
- return "Unsupported" |
|
| 280 |
- @unknown default: |
|
| 281 |
- return "Other" |
|
| 282 |
- } |
|
| 283 |
- } |
|
| 284 |
- |
|
| 285 |
- private var helpIsExpanded: Bool {
|
|
| 286 |
- isHelpExpanded || shouldAutoExpandHelp |
|
| 287 |
- } |
|
| 288 |
- |
|
| 289 |
- private var shouldAutoExpandHelp: Bool {
|
|
| 290 |
- guard let activeHelpAutoReason else {
|
|
| 291 |
- return false |
|
| 292 |
- } |
|
| 293 |
- return dismissedAutoHelpReason != activeHelpAutoReason |
|
| 294 |
- } |
|
| 295 |
- |
|
| 296 |
- private var activeHelpAutoReason: HelpAutoReason? {
|
|
| 297 |
- if appData.bluetoothManager.managerState == .unauthorized {
|
|
| 298 |
- return .bluetoothPermission |
|
| 299 |
- } |
|
| 300 |
- if shouldPromptForCloudSync {
|
|
| 301 |
- return .cloudSyncUnavailable |
|
| 302 |
- } |
|
| 303 |
- if hasWaitedLongEnoughForDevices {
|
|
| 304 |
- return .noDevicesDetected |
|
| 305 |
- } |
|
| 306 |
- return nil |
|
| 307 |
- } |
|
| 308 |
- |
|
| 309 |
- private var shouldPromptForCloudSync: Bool {
|
|
| 310 |
- switch appData.cloudAvailability {
|
|
| 311 |
- case .noAccount, .error: |
|
| 312 |
- return true |
|
| 313 |
- case .unknown, .available: |
|
| 314 |
- return false |
|
| 315 |
- } |
|
| 316 |
- } |
|
| 317 |
- |
|
| 318 |
- private var hasWaitedLongEnoughForDevices: Bool {
|
|
| 319 |
- guard appData.bluetoothManager.managerState == .poweredOn else {
|
|
| 320 |
- return false |
|
| 321 |
- } |
|
| 322 |
- guard appData.meters.isEmpty else {
|
|
| 323 |
- return false |
|
| 324 |
- } |
|
| 325 |
- guard let scanStartedAt = appData.bluetoothManager.scanStartedAt else {
|
|
| 326 |
- return false |
|
| 327 |
- } |
|
| 328 |
- return now.timeIntervalSince(scanStartedAt) >= noDevicesHelpDelay |
|
| 329 |
- } |
|
| 330 |
- |
|
| 331 |
- private var isWaitingForFirstDiscovery: Bool {
|
|
| 332 |
- guard appData.bluetoothManager.managerState == .poweredOn else {
|
|
| 333 |
- return false |
|
| 334 |
- } |
|
| 335 |
- guard appData.meters.isEmpty else {
|
|
| 336 |
- return false |
|
| 337 |
- } |
|
| 338 |
- guard let scanStartedAt = appData.bluetoothManager.scanStartedAt else {
|
|
| 339 |
- return false |
|
| 340 |
- } |
|
| 341 |
- return now.timeIntervalSince(scanStartedAt) < noDevicesHelpDelay |
|
| 342 |
- } |
|
| 343 |
- |
|
| 344 |
- private var devicesEmptyStateText: String {
|
|
| 345 |
- if isWaitingForFirstDiscovery {
|
|
| 346 |
- return "Scanning for nearby supported meters..." |
|
| 347 |
- } |
|
| 348 |
- return "No known meters yet. Nearby supported meters will appear here and remain available after they disappear." |
|
| 349 |
- } |
|
| 350 |
- |
|
| 351 |
- private var helpSectionTint: Color {
|
|
| 352 |
- activeHelpAutoReason?.tint ?? .secondary |
|
| 353 |
- } |
|
| 354 |
- |
|
| 355 |
- private var helpSectionSymbol: String {
|
|
| 356 |
- activeHelpAutoReason?.symbol ?? "questionmark.circle.fill" |
|
| 357 |
- } |
|
| 358 |
- |
|
| 359 |
- private var helpSectionSummary: String {
|
|
| 360 |
- switch activeHelpAutoReason {
|
|
| 361 |
- case .bluetoothPermission: |
|
| 362 |
- return "Bluetooth permission is needed before scanning can begin." |
|
| 363 |
- case .cloudSyncUnavailable: |
|
| 364 |
- return appData.cloudAvailability.helpMessage |
|
| 365 |
- case .noDevicesDetected: |
|
| 366 |
- return "No supported devices were found after \(Int(noDevicesHelpDelay)) seconds." |
|
| 367 |
- case nil: |
|
| 368 |
- return "Connection tips and quick checks when discovery needs help." |
|
| 369 |
- } |
|
| 370 |
- } |
|
| 371 |
- |
|
| 372 |
- private func toggleHelpSection() {
|
|
| 373 |
- withAnimation(.easeInOut(duration: 0.22)) {
|
|
| 374 |
- if shouldAutoExpandHelp {
|
|
| 375 |
- dismissedAutoHelpReason = activeHelpAutoReason |
|
| 376 |
- isHelpExpanded = false |
|
| 377 |
- } else {
|
|
| 378 |
- isHelpExpanded.toggle() |
|
| 379 |
- } |
|
| 380 |
- } |
|
| 381 |
- } |
|
| 382 |
- |
|
| 383 |
- private func openSettings() {
|
|
| 384 |
- guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else {
|
|
| 385 |
- return |
|
| 386 |
- } |
|
| 387 |
- UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) |
|
| 388 |
- } |
|
| 389 |
- |
|
| 390 |
- private func helpNoticeCard(for reason: HelpAutoReason) -> some View {
|
|
| 391 |
- VStack(alignment: .leading, spacing: 8) {
|
|
| 392 |
- Text(helpNoticeTitle(for: reason)) |
|
| 393 |
- .font(.subheadline.weight(.semibold)) |
|
| 394 |
- Text(helpNoticeDetail(for: reason)) |
|
| 395 |
- .font(.caption) |
|
| 396 |
- .foregroundColor(.secondary) |
|
| 397 |
- } |
|
| 398 |
- .frame(maxWidth: .infinity, alignment: .leading) |
|
| 399 |
- .padding(14) |
|
| 400 |
- .meterCard(tint: reason.tint, fillOpacity: 0.14, strokeOpacity: 0.20, cornerRadius: 18) |
|
| 401 |
- } |
|
| 402 |
- |
|
| 403 |
- private func helpNoticeTitle(for reason: HelpAutoReason) -> String {
|
|
| 404 |
- switch reason {
|
|
| 405 |
- case .bluetoothPermission: |
|
| 406 |
- return "Bluetooth access needs attention" |
|
| 407 |
- case .cloudSyncUnavailable: |
|
| 408 |
- return appData.cloudAvailability.helpTitle |
|
| 409 |
- case .noDevicesDetected: |
|
| 410 |
- return "No supported meters found yet" |
|
| 411 |
- } |
|
| 412 |
- } |
|
| 413 |
- |
|
| 414 |
- private func helpNoticeDetail(for reason: HelpAutoReason) -> String {
|
|
| 415 |
- switch reason {
|
|
| 416 |
- case .bluetoothPermission: |
|
| 417 |
- return "Open Bluetooth help to review the permission state and jump into Settings if access is blocked." |
|
| 418 |
- case .cloudSyncUnavailable: |
|
| 419 |
- return appData.cloudAvailability.helpMessage |
|
| 420 |
- case .noDevicesDetected: |
|
| 421 |
- return "Open Device help for quick checks like meter power, Bluetooth availability on the meter, or an existing connection to another phone." |
|
| 422 |
- } |
|
| 423 |
- } |
|
| 424 |
- |
|
| 425 |
- private func sidebarLinkCard( |
|
| 426 |
- title: String, |
|
| 427 |
- subtitle: String, |
|
| 428 |
- symbol: String, |
|
| 429 |
- tint: Color |
|
| 430 |
- ) -> some View {
|
|
| 431 |
- HStack(spacing: 14) {
|
|
| 432 |
- Image(systemName: symbol) |
|
| 433 |
- .font(.system(size: 18, weight: .semibold)) |
|
| 434 |
- .foregroundColor(tint) |
|
| 435 |
- .frame(width: 42, height: 42) |
|
| 436 |
- .background(Circle().fill(tint.opacity(0.18))) |
|
| 437 |
- |
|
| 438 |
- VStack(alignment: .leading, spacing: 4) {
|
|
| 439 |
- Text(title) |
|
| 440 |
- .font(.headline) |
|
| 441 |
- Text(subtitle) |
|
| 442 |
- .font(.caption) |
|
| 443 |
- .foregroundColor(.secondary) |
|
| 444 |
- } |
|
| 445 |
- |
|
| 446 |
- Spacer() |
|
| 447 |
- |
|
| 448 |
- Image(systemName: "chevron.right") |
|
| 449 |
- .font(.footnote.weight(.bold)) |
|
| 450 |
- .foregroundColor(.secondary) |
|
| 451 |
- } |
|
| 452 |
- .padding(14) |
|
| 453 |
- .meterCard(tint: tint, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 18) |
|
| 454 |
- } |
|
| 455 |
- |
|
| 456 |
- private func knownMeterCard(for knownMeter: AppData.KnownMeterSummary) -> some View {
|
|
| 457 |
- HStack(spacing: 14) {
|
|
| 458 |
- Image(systemName: "sensor.tag.radiowaves.forward.fill") |
|
| 459 |
- .font(.system(size: 18, weight: .semibold)) |
|
| 460 |
- .foregroundColor(knownMeterTint(for: knownMeter)) |
|
| 461 |
- .frame(width: 42, height: 42) |
|
| 462 |
- .background( |
|
| 463 |
- Circle() |
|
| 464 |
- .fill(knownMeterTint(for: knownMeter).opacity(0.18)) |
|
| 465 |
- ) |
|
| 466 |
- .overlay(alignment: .bottomTrailing) {
|
|
| 467 |
- Circle() |
|
| 468 |
- .fill(Color.red) |
|
| 469 |
- .frame(width: 12, height: 12) |
|
| 470 |
- .overlay( |
|
| 471 |
- Circle() |
|
| 472 |
- .stroke(Color(uiColor: .systemBackground), lineWidth: 2) |
|
| 473 |
- ) |
|
| 474 |
- } |
|
| 475 |
- |
|
| 476 |
- VStack(alignment: .leading, spacing: 4) {
|
|
| 477 |
- Text(knownMeter.displayName) |
|
| 478 |
- .font(.headline) |
|
| 479 |
- Text(knownMeter.modelSummary) |
|
| 480 |
- .font(.caption) |
|
| 481 |
- .foregroundColor(.secondary) |
|
| 482 |
- if let advertisedName = knownMeter.advertisedName, advertisedName != knownMeter.modelSummary {
|
|
| 483 |
- Text("Advertised as \(advertisedName)")
|
|
| 484 |
- .font(.caption2) |
|
| 485 |
- .foregroundColor(.secondary) |
|
| 486 |
- } |
|
| 487 |
- } |
|
| 488 |
- |
|
| 489 |
- Spacer() |
|
| 490 |
- |
|
| 491 |
- VStack(alignment: .trailing, spacing: 4) {
|
|
| 492 |
- HStack(spacing: 6) {
|
|
| 493 |
- Circle() |
|
| 494 |
- .fill(Color.red) |
|
| 495 |
- .frame(width: 8, height: 8) |
|
| 496 |
- Text("Missing")
|
|
| 497 |
- .font(.caption.weight(.semibold)) |
|
| 498 |
- .foregroundColor(.secondary) |
|
| 499 |
- } |
|
| 500 |
- .padding(.horizontal, 10) |
|
| 501 |
- .padding(.vertical, 6) |
|
| 502 |
- .background( |
|
| 503 |
- Capsule(style: .continuous) |
|
| 504 |
- .fill(Color.red.opacity(0.12)) |
|
| 505 |
- ) |
|
| 506 |
- .overlay( |
|
| 507 |
- Capsule(style: .continuous) |
|
| 508 |
- .stroke(Color.red.opacity(0.22), lineWidth: 1) |
|
| 509 |
- ) |
|
| 510 |
- Text(knownMeter.macAddress) |
|
| 511 |
- .font(.caption2) |
|
| 512 |
- .foregroundColor(.secondary) |
|
| 513 |
- if let lastSeen = knownMeter.lastSeen {
|
|
| 514 |
- Text("Seen \(lastSeen.format(as: "yyyy-MM-dd HH:mm"))")
|
|
| 515 |
- .font(.caption2) |
|
| 516 |
- .foregroundColor(.secondary) |
|
| 517 |
- } |
|
| 518 |
- } |
|
| 519 |
- } |
|
| 520 |
- .padding(14) |
|
| 521 |
- .meterCard( |
|
| 522 |
- tint: knownMeterTint(for: knownMeter), |
|
| 523 |
- fillOpacity: 0.16, |
|
| 524 |
- strokeOpacity: 0.22, |
|
| 525 |
- cornerRadius: 18 |
|
| 526 |
- ) |
|
| 527 |
- } |
|
| 528 |
- |
|
| 529 |
- private func knownMeterTint(for knownMeter: AppData.KnownMeterSummary) -> Color {
|
|
| 530 |
- switch knownMeter.modelSummary {
|
|
| 531 |
- case "UM25C": |
|
| 532 |
- return .blue |
|
| 533 |
- case "UM34C": |
|
| 534 |
- return .yellow |
|
| 535 |
- case "TC66C": |
|
| 536 |
- return Model.TC66C.color |
|
| 537 |
- default: |
|
| 538 |
- return .secondary |
|
| 16 |
+ SidebarView() |
|
| 17 |
+ .navigationBarTitle(Text("USB Meters"), displayMode: .inline)
|
|
| 539 | 18 |
} |
| 540 | 19 |
} |
| 541 | 20 |
} |
@@ -0,0 +1,113 @@ |
||
| 1 |
+import SwiftUI |
|
| 2 |
+ |
|
| 3 |
+struct MeterDetailView: View {
|
|
| 4 |
+ let knownMeter: AppData.KnownMeterSummary |
|
| 5 |
+ |
|
| 6 |
+ var body: some View {
|
|
| 7 |
+ ScrollView {
|
|
| 8 |
+ VStack(spacing: 18) {
|
|
| 9 |
+ headerCard |
|
| 10 |
+ statusCard |
|
| 11 |
+ historyCard |
|
| 12 |
+ } |
|
| 13 |
+ .padding() |
|
| 14 |
+ } |
|
| 15 |
+ .background( |
|
| 16 |
+ LinearGradient( |
|
| 17 |
+ colors: [knownMeter.tint.opacity(0.18), Color.clear], |
|
| 18 |
+ startPoint: .topLeading, |
|
| 19 |
+ endPoint: .bottomTrailing |
|
| 20 |
+ ) |
|
| 21 |
+ .ignoresSafeArea() |
|
| 22 |
+ ) |
|
| 23 |
+ .navigationTitle(knownMeter.displayName) |
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ private var headerCard: some View {
|
|
| 27 |
+ VStack(alignment: .leading, spacing: 8) {
|
|
| 28 |
+ Text(knownMeter.displayName) |
|
| 29 |
+ .font(.title2.weight(.semibold)) |
|
| 30 |
+ Text(knownMeter.modelSummary) |
|
| 31 |
+ .font(.subheadline) |
|
| 32 |
+ .foregroundColor(.secondary) |
|
| 33 |
+ if let advertisedName = knownMeter.advertisedName {
|
|
| 34 |
+ Text("Advertised as " + advertisedName)
|
|
| 35 |
+ .font(.caption2) |
|
| 36 |
+ .foregroundColor(.secondary) |
|
| 37 |
+ } |
|
| 38 |
+ } |
|
| 39 |
+ .frame(maxWidth: .infinity, alignment: .leading) |
|
| 40 |
+ .padding(18) |
|
| 41 |
+ .meterCard(tint: knownMeter.tint, fillOpacity: 0.22, strokeOpacity: 0.28, cornerRadius: 20) |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ private var statusCard: some View {
|
|
| 45 |
+ VStack(alignment: .leading, spacing: 10) {
|
|
| 46 |
+ Text("Status")
|
|
| 47 |
+ .font(.headline) |
|
| 48 |
+ HStack(spacing: 8) {
|
|
| 49 |
+ Circle() |
|
| 50 |
+ .fill(knownMeter.tint) |
|
| 51 |
+ .frame(width: 10, height: 10) |
|
| 52 |
+ Text("Offline")
|
|
| 53 |
+ .font(.caption.weight(.semibold)) |
|
| 54 |
+ .foregroundColor(.secondary) |
|
| 55 |
+ } |
|
| 56 |
+ Text("The meter is not currently connected. Bring it within Bluetooth range or wake it up to open live diagnostics.")
|
|
| 57 |
+ .font(.caption) |
|
| 58 |
+ .foregroundColor(.secondary) |
|
| 59 |
+ } |
|
| 60 |
+ .frame(maxWidth: .infinity, alignment: .leading) |
|
| 61 |
+ .padding(18) |
|
| 62 |
+ .meterCard(tint: knownMeter.tint, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 18) |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ private var historyCard: some View {
|
|
| 66 |
+ VStack(alignment: .leading, spacing: 10) {
|
|
| 67 |
+ Text("History")
|
|
| 68 |
+ .font(.headline) |
|
| 69 |
+ infoRow(label: "Last Seen", value: lastSeenText) |
|
| 70 |
+ Divider() |
|
| 71 |
+ infoRow(label: "Last Connected", value: lastConnectedText) |
|
| 72 |
+ } |
|
| 73 |
+ .frame(maxWidth: .infinity, alignment: .leading) |
|
| 74 |
+ .padding(18) |
|
| 75 |
+ .meterCard(tint: .secondary, fillOpacity: 0.12, strokeOpacity: 0.18, cornerRadius: 18) |
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ private func infoRow(label: String, value: String) -> some View {
|
|
| 79 |
+ HStack {
|
|
| 80 |
+ Text(label) |
|
| 81 |
+ Spacer() |
|
| 82 |
+ Text(value) |
|
| 83 |
+ .foregroundColor(.secondary) |
|
| 84 |
+ .font(.caption) |
|
| 85 |
+ } |
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ private var lastSeenText: String {
|
|
| 89 |
+ guard let date = knownMeter.lastSeen else { return "—" }
|
|
| 90 |
+ return date.format(as: "yyyy-MM-dd HH:mm") |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ private var lastConnectedText: String {
|
|
| 94 |
+ guard let date = knownMeter.lastConnected else { return "—" }
|
|
| 95 |
+ return date.format(as: "yyyy-MM-dd HH:mm") |
|
| 96 |
+ } |
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+struct MeterDetailView_Previews: PreviewProvider {
|
|
| 100 |
+ static var previews: some View {
|
|
| 101 |
+ MeterDetailView( |
|
| 102 |
+ knownMeter: AppData.KnownMeterSummary( |
|
| 103 |
+ macAddress: "AA:BB:CC:DD:EE:FF", |
|
| 104 |
+ displayName: "Desk Meter", |
|
| 105 |
+ modelSummary: "UM25C", |
|
| 106 |
+ advertisedName: "UM25C-123", |
|
| 107 |
+ lastSeen: Date(), |
|
| 108 |
+ lastConnected: Date().addingTimeInterval(-3600), |
|
| 109 |
+ meter: nil |
|
| 110 |
+ ) |
|
| 111 |
+ ) |
|
| 112 |
+ } |
|
| 113 |
+} |
|
@@ -61,9 +61,6 @@ struct MeterRowView: View {
|
||
| 61 | 61 |
Capsule(style: .continuous) |
| 62 | 62 |
.stroke(connectivityTint.opacity(0.22), lineWidth: 1) |
| 63 | 63 |
) |
| 64 |
- Text(meter.btSerial.macAddress.description) |
|
| 65 |
- .font(.caption2) |
|
| 66 |
- .foregroundColor(.secondary) |
|
| 67 | 64 |
} |
| 68 | 65 |
} |
| 69 | 66 |
.padding(14) |