From 77504bb8d43eba6b647416c72fa65ff56d57a5cc Mon Sep 17 00:00:00 2001 From: Jake-B Date: Sat, 15 Mar 2025 07:58:44 -0400 Subject: [PATCH] Update Environment Metrics to support latest Telemetry protobufs --- Localizable.xcstrings | 29 +- Meshtastic.xcodeproj/project.pbxproj | 46 ++- .../soil.moisture.symbolset/Contents.json | 12 + .../soilMoisture.variable.svg | 366 ++++++++++++++++++ .../soil.temperature.symbolset/Contents.json | 12 + .../soilTemp.variable.svg | 363 +++++++++++++++++ .../Compact Widgets/CompactWidget.swift | 36 ++ .../CurrentConditionsCompact.swift | 34 ++ .../DistanceCompactWidget.swift | 39 ++ .../HumidityCompactWidget.swift | 47 +++ .../PressureCompactWidget.swift | 43 ++ .../RadiationCompactWidget.swift | 39 ++ .../Compact Widgets/SoilCompactWidgets.swift | 72 ++++ .../WeatherConditionsCompactWidget.swift | 42 ++ .../Compact Widgets/WeightCompactWidget.swift | 39 ++ .../Compact Widgets/WindCompactWidget.swift | 45 +++ .../EnvironmentDefaultColumns.swift | 145 +++++++ .../EnvironmentDefaultSeries.swift | 263 ++++++++++++- .../Views/Nodes/Helpers/NodeDetail.swift | 2 +- 19 files changed, 1641 insertions(+), 33 deletions(-) create mode 100644 Meshtastic/Assets.xcassets/soil.moisture.symbolset/Contents.json create mode 100644 Meshtastic/Assets.xcassets/soil.moisture.symbolset/soilMoisture.variable.svg create mode 100644 Meshtastic/Assets.xcassets/soil.temperature.symbolset/Contents.json create mode 100644 Meshtastic/Assets.xcassets/soil.temperature.symbolset/soilTemp.variable.svg create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/CurrentConditionsCompact.swift create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/DistanceCompactWidget.swift create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/HumidityCompactWidget.swift create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/PressureCompactWidget.swift create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/RadiationCompactWidget.swift create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/SoilCompactWidgets.swift create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/WeatherConditionsCompactWidget.swift create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/WeightCompactWidget.swift create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/WindCompactWidget.swift diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 6c2731d1..068905ec 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -12138,23 +12138,6 @@ } } }, - "HUMIDITY" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "LUFTFEUCHTIGKEIT" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "ВЛАЖНОСТ" - } - } - } - }, "hybrid" : { "extractionState" : "migrated", "localizations" : { @@ -23607,6 +23590,9 @@ } } } + }, + "Radiation" : { + }, "Radio Disconnected" : { "extractionState" : "manual", @@ -28513,6 +28499,12 @@ } } } + }, + "Soil Moisture" : { + + }, + "Soil Temp" : { + }, "Specifies how long the monitored GPIO should output." : { "localizations" : { @@ -32676,6 +32668,9 @@ } } } + }, + "Weight" : { + }, "What does the lock mean?" : { "localizations" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index fc1539f2..bf000340 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -11,6 +11,15 @@ 231B3F222D087A4C0069A07D /* MetricsColumnList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */; }; 231B3F252D087C3C0069A07D /* EnvironmentDefaultColumns.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */; }; 231B3F272D0885240069A07D /* MetricsColumnDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F262D0885240069A07D /* MetricsColumnDetail.swift */; }; + 233E99B62D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */; }; + 233E99B82D849C6500CC3A77 /* HumidityCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */; }; + 233E99BA2D849C7000CC3A77 /* PressureCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */; }; + 233E99BC2D849C8C00CC3A77 /* WindCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */; }; + 233E99BE2D849D3200CC3A77 /* RadiationCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */; }; + 233E99C12D849D6000CC3A77 /* DistanceCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */; }; + 233E99C32D849D7A00CC3A77 /* WeightCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */; }; + 233E99C52D84A0B600CC3A77 /* CompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C42D84A0B600CC3A77 /* CompactWidget.swift */; }; + 233E99C72D84A70900CC3A77 /* SoilCompactWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */; }; 2344A2AB2D66974300170A77 /* ManagedAttributePropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2344A2AA2D66973D00170A77 /* ManagedAttributePropertyWrapper.swift */; }; 2344A2AF2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2344A2AD2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift */; }; 2344A2B02D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */; }; @@ -278,6 +287,15 @@ 231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultColumns.swift; sourceTree = ""; }; 231B3F262D0885240069A07D /* MetricsColumnDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnDetail.swift; sourceTree = ""; }; 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 50.xcdatamodel"; sourceTree = ""; }; + 233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherConditionsCompactWidget.swift; sourceTree = ""; }; + 233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HumidityCompactWidget.swift; sourceTree = ""; }; + 233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PressureCompactWidget.swift; sourceTree = ""; }; + 233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindCompactWidget.swift; sourceTree = ""; }; + 233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadiationCompactWidget.swift; sourceTree = ""; }; + 233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistanceCompactWidget.swift; sourceTree = ""; }; + 233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightCompactWidget.swift; sourceTree = ""; }; + 233E99C42D84A0B600CC3A77 /* CompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactWidget.swift; sourceTree = ""; }; + 233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoilCompactWidgets.swift; sourceTree = ""; }; 2344A2AA2D66973D00170A77 /* ManagedAttributePropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAttributePropertyWrapper.swift; sourceTree = ""; }; 2344A2AD2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataClass.swift"; sourceTree = ""; }; 2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataProperties.swift"; sourceTree = ""; }; @@ -607,6 +625,23 @@ path = "Metrics Columns"; sourceTree = ""; }; + 233E99B42D849C2D00CC3A77 /* Compact Widgets */ = { + isa = PBXGroup; + children = ( + 233E99C42D84A0B600CC3A77 /* CompactWidget.swift */, + 233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */, + 233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */, + 233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */, + 233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */, + DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */, + 233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */, + 233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */, + 233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */, + 233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */, + ); + path = "Compact Widgets"; + sourceTree = ""; + }; 2344A2AC2D66978000170A77 /* CoreData */ = { isa = PBXGroup; children = ( @@ -778,7 +813,6 @@ isa = PBXGroup; children = ( DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */, - DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */, DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */, DD354FD82BD96A0B0061A25F /* IAQScale.swift */, DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */, @@ -1039,6 +1073,7 @@ DDC2E18D26CE25CB0042C5E4 /* Helpers */ = { isa = PBXGroup; children = ( + 233E99B42D849C2D00CC3A77 /* Compact Widgets */, DD6F65772C6EAB860053C113 /* Help */, DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */, DD3CC24B2C498D6C001BD3A2 /* BatteryCompact.swift */, @@ -1407,7 +1442,9 @@ 251926902C3CB44900249DF5 /* ClientHistoryButton.swift in Sources */, DDD5BB102C285FB3007E03CA /* AppLogFilter.swift in Sources */, 2373AE172D0A26620086C749 /* EnvironmentDefaultSeries.swift in Sources */, + 233E99B82D849C6500CC3A77 /* HumidityCompactWidget.swift in Sources */, DD4640202AFF10F4002A5ECB /* WaypointForm.swift in Sources */, + 233E99C12D849D6000CC3A77 /* DistanceCompactWidget.swift in Sources */, DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DD15E4F32B8BA56E00654F61 /* PaxCounterConfig.swift in Sources */, @@ -1437,6 +1474,7 @@ DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */, B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */, DDB8F4102A9EE5B400230ECE /* Messages.swift in Sources */, + 233E99C32D849D7A00CC3A77 /* WeightCompactWidget.swift in Sources */, DDDB26482AACD6D1003AFCB7 /* NodeMapMapkit.swift in Sources */, DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */, DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */, @@ -1454,6 +1492,7 @@ DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */, DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */, DD6F65792C6EADE60053C113 /* DirectMessagesHelp.swift in Sources */, + 233E99B62D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift in Sources */, 25F5D5C02C3F6DA6008036E3 /* Router.swift in Sources */, DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */, 25C49D902C471AEA0024FBD1 /* Constants.swift in Sources */, @@ -1492,6 +1531,7 @@ DD2553592855B52700E55709 /* PositionConfig.swift in Sources */, DD97E96828EFE9A00056DDA4 /* About.swift in Sources */, DDDB444029F79AB000EE2349 /* UserDefaults.swift in Sources */, + 233E99BA2D849C7000CC3A77 /* PressureCompactWidget.swift in Sources */, DDB6ABE028B13AC700384BA1 /* DeviceEnums.swift in Sources */, DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */, D93068DD2B81CA820066FBC8 /* ConfigHeader.swift in Sources */, @@ -1501,6 +1541,7 @@ DDD5BB092C285DDC007E03CA /* AppLog.swift in Sources */, BC5EBA3C2D002A2000C442FF /* MessageNodeIntent.swift in Sources */, DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */, + 233E99C52D84A0B600CC3A77 /* CompactWidget.swift in Sources */, DDC1B81A2AB5377B00C71E39 /* MessagesTips.swift in Sources */, DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */, DDB75A112A059258006ED576 /* Url.swift in Sources */, @@ -1536,6 +1577,7 @@ 8D3F8A3F2D44BB02009EAAA4 /* PowerMetrics.swift in Sources */, 2519268A2C3BB1B200249DF5 /* ExchangePositionsButton.swift in Sources */, DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */, + 233E99BE2D849D3200CC3A77 /* RadiationCompactWidget.swift in Sources */, DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */, @@ -1567,12 +1609,14 @@ DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */, DD77093F2AA1B146007A8BF0 /* UIColor.swift in Sources */, DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */, + 233E99C72D84A70900CC3A77 /* SoilCompactWidgets.swift in Sources */, BCE2D3C92C7C377F008E6199 /* FactoryResetNodeIntent.swift in Sources */, DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */, DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */, DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */, DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */, + 233E99BC2D849C8C00CC3A77 /* WindCompactWidget.swift in Sources */, B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */, BC6B45FF2CB2F98900723CEB /* SaveChannelSettingsIntent.swift in Sources */, D93068D72B8146690066FBC8 /* MessageText.swift in Sources */, diff --git a/Meshtastic/Assets.xcassets/soil.moisture.symbolset/Contents.json b/Meshtastic/Assets.xcassets/soil.moisture.symbolset/Contents.json new file mode 100644 index 00000000..c5fa326a --- /dev/null +++ b/Meshtastic/Assets.xcassets/soil.moisture.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "soilMoisture.variable.svg", + "idiom" : "universal" + } + ] +} diff --git a/Meshtastic/Assets.xcassets/soil.moisture.symbolset/soilMoisture.variable.svg b/Meshtastic/Assets.xcassets/soil.moisture.symbolset/soilMoisture.variable.svg new file mode 100644 index 00000000..346bc90e --- /dev/null +++ b/Meshtastic/Assets.xcassets/soil.moisture.symbolset/soilMoisture.variable.svg @@ -0,0 +1,366 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.6.0 + Requires Xcode 16 or greater + Generated from thermometer.variable + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Meshtastic/Assets.xcassets/soil.temperature.symbolset/Contents.json b/Meshtastic/Assets.xcassets/soil.temperature.symbolset/Contents.json new file mode 100644 index 00000000..d136a31b --- /dev/null +++ b/Meshtastic/Assets.xcassets/soil.temperature.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "soilTemp.variable.svg", + "idiom" : "universal" + } + ] +} diff --git a/Meshtastic/Assets.xcassets/soil.temperature.symbolset/soilTemp.variable.svg b/Meshtastic/Assets.xcassets/soil.temperature.symbolset/soilTemp.variable.svg new file mode 100644 index 00000000..b5501a8a --- /dev/null +++ b/Meshtastic/Assets.xcassets/soil.temperature.symbolset/soilTemp.variable.svg @@ -0,0 +1,363 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.6.0 + Requires Xcode 16 or greater + Generated from thermometer.variable + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift new file mode 100644 index 00000000..6bb35a0f --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift @@ -0,0 +1,36 @@ +// +// CompactWidget.swift +// Meshtastic +// +// Created by Jake Bordens on 3/14/25. +// + +import SwiftUI + +// This file was created for the purpose of previewing +// all of the Compact Widgets in one place. + +// In the future, it could be used for a CompactWidget superclass, if desired. + +#Preview { + + let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2) + Form { + LazyVGrid(columns: gridItemLayout) { + HumidityCompactWidget(humidity: 27, dewPoint: "32°") + HumidityCompactWidget(humidity: 27, dewPoint: nil) + WeatherConditionsCompactWidget(temperature: "24°F", symbolName: "sun.rain.fill", description: "Raining") + PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: true) + PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: false) + WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: "SW") + WindCompactWidget(speed: "12 mph", gust: nil, direction: "SW") + WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: nil) + WindCompactWidget(speed: "12 mph", gust: nil, direction: nil) + RadiationCompactWidget(radiation: "15", unit: "µR/hr") + DistanceCompactWidget(distance: "123", unit: "mm") + WeightCompactWidget(weight: "123", unit: "kg") + SoilTemperatureCompactWidget(temperature: "23", unit: "°C") + SoilMoistureCompactWidget(moisture: "23", unit: "%") + } + } +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/CurrentConditionsCompact.swift b/Meshtastic/Views/Helpers/Compact Widgets/CurrentConditionsCompact.swift new file mode 100644 index 00000000..5f0edf46 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/CurrentConditionsCompact.swift @@ -0,0 +1,34 @@ +// +// CurrentConditionsCompact.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 2/5/23. +// +import SwiftUI + +struct CurrentConditionsCompact: View { + var temp: Float + var condition: WeatherConditions + + var body: some View { + Label("\(String(temp.formattedTemperature()))", systemImage: condition.symbolName) + .font(.caption) + .foregroundColor(.gray) + .symbolRenderingMode(.multicolor) + } +} + +struct CurrentConditionsCompact_Previews: PreviewProvider { + static var previews: some View { + + VStack { + CurrentConditionsCompact(temp: 22, condition: WeatherConditions.clear) + CurrentConditionsCompact(temp: 17, condition: WeatherConditions.cloudy) + CurrentConditionsCompact(temp: -5, condition: WeatherConditions.frigid) + CurrentConditionsCompact(temp: 38, condition: WeatherConditions.hot) + CurrentConditionsCompact(temp: 10, condition: WeatherConditions.rain) + CurrentConditionsCompact(temp: 30, condition: WeatherConditions.smoky) + CurrentConditionsCompact(temp: -2, condition: WeatherConditions.snow) + } + } +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/DistanceCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/DistanceCompactWidget.swift new file mode 100644 index 00000000..9f04406e --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/DistanceCompactWidget.swift @@ -0,0 +1,39 @@ +// +// DistanceCompactWidget.swift +// Meshtastic +// +// Created by Jake Bordens on 3/14/25. +// + +import SwiftUI + +struct DistanceCompactWidget: View { + let distance: String + let unit: String + + var body: some View { + VStack(alignment: .leading) { + HStack(alignment: .firstTextBaseline) { + Image(systemName: "ruler") + .imageScale(.small) + .foregroundColor(.accentColor) + Text("Distance") + .textCase(.uppercase) + .font(.callout) + } + HStack { + Text("\(distance)") + .font(distance.length < 4 ? .system(size: 50) : .system(size: 40) ) + Text(unit) + .font(.system(size: 14)) + } + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +#Preview { + DistanceCompactWidget(distance: "123", unit: "mm") +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/HumidityCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/HumidityCompactWidget.swift new file mode 100644 index 00000000..b20981dd --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/HumidityCompactWidget.swift @@ -0,0 +1,47 @@ +// +// HumidityCompactWidget.swift +// Meshtastic +// +// Created by Jake Bordens on 3/14/25. +// +import SwiftUI + +struct HumidityCompactWidget: View { + let humidity: Int + let dewPoint: String? + var body: some View { + VStack(alignment: .leading) { + HStack(spacing: 5.0) { + Image(systemName: "humidity") + .foregroundColor(.accentColor) + .font(.callout) + Text("Humidity") + .textCase(.uppercase) + .font(.caption) + } + Text("\(humidity)%") + .font(.largeTitle) + .padding(.bottom, 5) + if let dewPoint { + Text("The dew point is \(dewPoint) right now.") + .lineLimit(3) + .allowsTightening(true) + .fixedSize(horizontal: false, vertical: true) + .font(.caption2) + } + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +#Preview { + let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2) + Form { + LazyVGrid(columns: gridItemLayout) { + HumidityCompactWidget(humidity: 27, dewPoint: "32°") + HumidityCompactWidget(humidity: 27, dewPoint: nil) + } + } +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/PressureCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/PressureCompactWidget.swift new file mode 100644 index 00000000..6f2f6583 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/PressureCompactWidget.swift @@ -0,0 +1,43 @@ +// +// PressureCompactWidget.swift +// Meshtastic +// +// Created by Jake Bordens on 3/14/25. +// +import SwiftUI + +struct PressureCompactWidget: View { + let pressure: String + let unit: String + let low: Bool + var body: some View { + VStack(alignment: .leading) { + HStack(spacing: 5.0) { + Image(systemName: "gauge") + .foregroundColor(.accentColor) + .font(.callout) + Text("Pressure") + .textCase(.uppercase) + .font(.caption) + } + Text(pressure) + .font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) ) + Text(low ? "LOW" : "HIGH") + .padding(.bottom, 10) + Text(unit) + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +#Preview { + let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2) + Form { + LazyVGrid(columns: gridItemLayout) { + PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: true) + PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: false) + } + } +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/RadiationCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/RadiationCompactWidget.swift new file mode 100644 index 00000000..b5cd7232 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/RadiationCompactWidget.swift @@ -0,0 +1,39 @@ +// +// RadiationCompactWidget.swift +// Meshtastic +// +// Created by Jake Bordens on 3/14/25. +// + +import SwiftUI + +struct RadiationCompactWidget: View { + let radiation: String + let unit: String + + var body: some View { + VStack(alignment: .leading) { + HStack(alignment: .firstTextBaseline) { + Text(verbatim: "☢") + .font(.system(size: 30, design: .monospaced)) + .foregroundColor(.accentColor) + Text("Radiation") + .textCase(.uppercase) + .font(.callout) + } + HStack { + Text("\(radiation)") + .font(radiation.length < 4 ? .system(size: 50) : .system(size: 34) ) + Text(unit) + .font(.system(size: 14)) + } + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +#Preview { + RadiationCompactWidget(radiation: "15", unit: "µR/hr") +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/SoilCompactWidgets.swift b/Meshtastic/Views/Helpers/Compact Widgets/SoilCompactWidgets.swift new file mode 100644 index 00000000..7f51d21e --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/SoilCompactWidgets.swift @@ -0,0 +1,72 @@ +// +// SoilCompactWidgets.swift +// Meshtastic +// +// Created by Jake Bordens on 3/14/25. +// + +import SwiftUI + +struct SoilTemperatureCompactWidget: View { + let temperature: String + let unit: String + + var body: some View { + VStack(alignment: .leading) { + HStack(alignment: .firstTextBaseline) { + Image("soil.temperature") + .imageScale(.small) + .foregroundColor(.accentColor) + Text("Soil Temp") + .textCase(.uppercase) + .font(.callout) + } + HStack { + Text("\(temperature)") + .font(temperature.length < 4 ? .system(size: 50) : .system(size: 40) ) + Text(unit) + .font(.system(size: 14)) + } + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +struct SoilMoistureCompactWidget: View { + let moisture: String + let unit: String + + var body: some View { + VStack(alignment: .leading) { + HStack(alignment: .firstTextBaseline) { + Image("soil.moisture") + .imageScale(.small) + .foregroundColor(.accentColor) + Text("Soil Moisture") + .textCase(.uppercase) + .font(.callout) + } + HStack { + Text("\(moisture)") + .font(moisture.length < 4 ? .system(size: 50) : .system(size: 40) ) + Text(unit) + .font(.system(size: 14)) + } + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +#Preview { + let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2) + Form { + LazyVGrid(columns: gridItemLayout) { + SoilTemperatureCompactWidget(temperature: "23", unit: "°C") + SoilMoistureCompactWidget(moisture: "23", unit: "%") + } + } +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/WeatherConditionsCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/WeatherConditionsCompactWidget.swift new file mode 100644 index 00000000..4918c7b6 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/WeatherConditionsCompactWidget.swift @@ -0,0 +1,42 @@ +// +// WeatherConditionsCompactWidget.swift +// Meshtastic +// +// Created by Jake Bordens on 3/14/25. +// +import SwiftUI + +struct WeatherConditionsCompactWidget: View { + let temperature: String + let symbolName: String + let description: String + var body: some View { + VStack(alignment: .leading) { + HStack(spacing: 5.0) { + Image(systemName: symbolName) + .foregroundColor(.accentColor) + .font(.callout) + Text(description) + .lineLimit(2) + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) + .fixedSize(horizontal: false, vertical: true) + .font(.caption) + } + Text(temperature) + .font(temperature.length < 4 ? .system(size: 72) : .system(size: 54) ) + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +#Preview { + let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2) + Form { + LazyVGrid(columns: gridItemLayout) { + WeatherConditionsCompactWidget(temperature: "24°F", symbolName: "sun.rain.fill", description: "Raining") + } + } +} + diff --git a/Meshtastic/Views/Helpers/Compact Widgets/WeightCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/WeightCompactWidget.swift new file mode 100644 index 00000000..f6b6df81 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/WeightCompactWidget.swift @@ -0,0 +1,39 @@ +// +// WeightCompactWidget.swift +// Meshtastic +// +// Created by Jake Bordens on 3/14/25. +// + +import SwiftUI + +struct WeightCompactWidget: View { + let weight: String + let unit: String + + var body: some View { + VStack(alignment: .leading) { + HStack(alignment: .firstTextBaseline) { + Image(systemName: "scalemass") + .imageScale(.small) + .foregroundColor(.accentColor) + Text("Weight") + .textCase(.uppercase) + .font(.callout) + } + HStack { + Text("\(weight)") + .font(weight.length < 4 ? .system(size: 50) : .system(size: 40) ) + Text(unit) + .font(.system(size: 14)) + } + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +#Preview { + WeightCompactWidget(weight: "123", unit: "kg") +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/WindCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/WindCompactWidget.swift new file mode 100644 index 00000000..3b3c5273 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/WindCompactWidget.swift @@ -0,0 +1,45 @@ +// +// WindCompactWidget.swift +// Meshtastic +// +// Created by Jake Bordens on 3/14/25. +// +import SwiftUI + +struct WindCompactWidget: View { + let speed: String + let gust: String? + let direction: String? + + var body: some View { + let hasGust = ((gust ?? "").isEmpty == false) + VStack(alignment: .leading) { + Label { Text("Wind").textCase(.uppercase) } icon: { Image(systemName: "wind").foregroundColor(.accentColor) } + if let direction { + Text("\(direction)") + .font(!hasGust ? .callout : .caption) + .padding(.bottom, 10) + } + Text(speed) + .font(.system(size: 35)) + if let gust, !gust.isEmpty { + Text("Gusts \(gust)") + } + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} + +#Preview { + let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2) + Form { + LazyVGrid(columns: gridItemLayout) { + WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: "SW") + WindCompactWidget(speed: "12 mph", gust: nil, direction: "SW") + WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: nil) + WindCompactWidget(speed: "12 mph", gust: nil, direction: nil) + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift index 245c2f9c..fe221f5a 100644 --- a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift +++ b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift @@ -73,6 +73,77 @@ extension MetricsColumnList { } }), + // Various Lux + MetricsTableColumn( + id: "lux", + keyPath: \.lux, + name: "Lux", + abbreviatedName: "Lux", + minWidth: 30, maxWidth: 50, + visible: false, + tableBody: { _, lux in + lux.map { + Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))") + } ?? Text(Constants.nilValueIndicator) + }), + + MetricsTableColumn( + id: "whiteLux", + keyPath: \.whiteLux, + name: "White Lux", + abbreviatedName: "White", + minWidth: 30, maxWidth: 50, + visible: false, + tableBody: { _, lux in + lux.map { + Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))") + } ?? Text(Constants.nilValueIndicator) + }), + + MetricsTableColumn( + id: "uvLux", + keyPath: \.uvLux, + name: "UV Lux", + abbreviatedName: "UV", + minWidth: 30, maxWidth: 50, + visible: false, + tableBody: { _, lux in + lux.map { + Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))") + } ?? Text(Constants.nilValueIndicator) + }), + + MetricsTableColumn( + id: "irLux", + keyPath: \.irLux, + name: "IR Lux", + abbreviatedName: "IR", + minWidth: 30, maxWidth: 50, + visible: false, + tableBody: { _, lux in + lux.map { + Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))") + } ?? Text(Constants.nilValueIndicator) + }), + + // Radiation + MetricsTableColumn( + id: "radiation", + keyPath: \.radiation, + name: "Radiation", + abbreviatedName: "☢️", + minWidth: 30, maxWidth: 50, + visible: false, + tableBody: { _, radiation in + radiation.map { + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + Text(verbatim: "\($0.formatted(.number.grouping(.never).precision(.fractionLength(1)))) µR/h") + } else { + Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))") + } + } ?? Text(Constants.nilValueIndicator) + }), + // Wind Direction Series Configuration MetricsTableColumn( id: "windDirection", @@ -127,6 +198,80 @@ extension MetricsColumnList { } ?? Text(verbatim: Constants.nilValueIndicator) }), + // Weight + MetricsTableColumn( + id: "weight", + keyPath: \.weight, + name: "Weight", + abbreviatedName: "kg", + minWidth: 30, maxWidth: 60, + visible: false, + tableBody: { _, weight in + weight.map { + let weight = Measurement( + value: Double($0), unit: UnitMass.kilograms) + return Text( + weight.formatted( + .measurement( + width: .abbreviated, + numberFormatStyle: .number.grouping(.never) + .precision( + .fractionLength(0)))) + ) + } ?? Text(Constants.nilValueIndicator) + }), + + // Distance sensor, often used for water level + MetricsTableColumn( + id: "distance", + keyPath: \.distance, + name: "Distance", + abbreviatedName: "Dist", + minWidth: 30, maxWidth: 50, + visible: false, + tableBody: { _, distance in + distance.map { + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + Text(verbatim: "\($0.formatted(.number.grouping(.never).precision(.fractionLength(1)))) mm") + } else { + Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))") + } + } ?? Text(Constants.nilValueIndicator) + }), + + // Soil Temperature + MetricsTableColumn( + id: "soilTemperature", + keyPath: \.soilTemperature, + name: "Soil Temperature", + abbreviatedName: "Soil Temp", + minWidth: 30, maxWidth: 50, + visible: false, + tableBody: { _, soilTemperature in + soilTemperature.map { + Text($0.formattedTemperature()) + } ?? Text(verbatim: Constants.nilValueIndicator) + + }), + + // Soil Moisture + MetricsTableColumn( + id: "soilMoisture", + keyPath: \.soilMoisture, + name: "Soil Moisture", + abbreviatedName: "Moist", + minWidth: 30, maxWidth: 50, + visible: false, + tableBody: { _, moisture in + moisture.map { + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(0))))%") + } else { + Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(0))))") + } + } ?? Text(Constants.nilValueIndicator) + }), + // Timestamp Series Configuration -- for use in table only MetricsTableColumn( id: "time", diff --git a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultSeries.swift b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultSeries.swift index e9422ee4..1db9b025 100644 --- a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultSeries.swift +++ b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultSeries.swift @@ -63,9 +63,9 @@ extension MetricsSeriesList { abbreviatedName: "Hum", initialYAxisRange: 0.0...100.0, foregroundStyle: { _ in - .linearGradient( - colors: [Color(UIColor.purple.darker(componentDelta: 0.2)), .purple], - startPoint: .bottom, endPoint: .top + .linearGradient( + colors: [Color(UIColor.purple.darker(componentDelta: 0.2)), .purple], + startPoint: .bottom, endPoint: .top ) }, chartBody: { series, _, time, humidity in @@ -80,7 +80,7 @@ extension MetricsSeriesList { .alignsMarkStylesWithPlotArea() } }), - + // Barometric Pressure Series Configuration MetricsChartSeries( id: "barometricPressure", @@ -89,10 +89,10 @@ extension MetricsSeriesList { abbreviatedName: "Bar", visible: false, foregroundStyle: { _ in - .linearGradient( - colors: [Color(UIColor.green.darker(componentDelta: 0.3)), .green], - startPoint: .bottom, endPoint: .top - ) + .linearGradient( + colors: [Color(UIColor.green.darker(componentDelta: 0.3)), .green], + startPoint: .bottom, endPoint: .top + ) }, chartBody: { series, _, time, pressure in if let pressure { @@ -106,7 +106,7 @@ extension MetricsSeriesList { .alignsMarkStylesWithPlotArea() } }), - + // Indoor Air Quality Series Configuration MetricsChartSeries( id: "iaq", @@ -134,6 +134,241 @@ extension MetricsSeriesList { .alignsMarkStylesWithPlotArea() } }), + + // Lux + MetricsChartSeries( + id: "lux", + keyPath: \.lux, + name: "Lux", + abbreviatedName: "Lux", + visible: false, + foregroundStyle: { _ in + .linearGradient( + colors: [Color(UIColor.cyan.lighter(componentDelta: 0.3)), .cyan], + startPoint: .bottom, endPoint: .top + ) + }, + chartBody: { series, _, time, lux in + if let lux { + LineMark( + x: .value("Time", time), + y: .value(series.abbreviatedName, lux) + ) + .interpolationMethod(.catmullRom) + .foregroundStyle(by: .value("Series", series.abbreviatedName)) + .lineStyle(StrokeStyle(lineWidth: 4)) + .alignsMarkStylesWithPlotArea() + } + }), + + // White Lux + MetricsChartSeries( + id: "whiteLux", + keyPath: \.whiteLux, + name: "White Lux", + abbreviatedName: "White", + visible: false, + foregroundStyle: { _ in + .linearGradient( + colors: [Color(UIColor.cyan.lighter(componentDelta: 0.5)), Color(UIColor.cyan.lighter(componentDelta: 0.2))], + startPoint: .bottom, endPoint: .top + ) + }, + chartBody: { series, _, time, lux in + if let lux { + LineMark( + x: .value("Time", time), + y: .value(series.abbreviatedName, lux) + ) + .interpolationMethod(.catmullRom) + .foregroundStyle(by: .value("Series", series.abbreviatedName)) + .lineStyle(StrokeStyle(lineWidth: 4)) + .alignsMarkStylesWithPlotArea() + } + }), + + // UV Lux + MetricsChartSeries( + id: "uvLux", + keyPath: \.uvLux, + name: "UV Lux", + abbreviatedName: "UV", + visible: false, + foregroundStyle: { _ in + .linearGradient( + colors: [Color(UIColor.systemIndigo.lighter(componentDelta: 0.4)), Color(UIColor.systemIndigo.lighter(componentDelta: 0.2))], + startPoint: .bottom, endPoint: .top + ) + }, + chartBody: { series, _, time, lux in + if let lux { + LineMark( + x: .value("Time", time), + y: .value(series.abbreviatedName, lux) + ) + .interpolationMethod(.catmullRom) + .foregroundStyle(by: .value("Series", series.abbreviatedName)) + .lineStyle(StrokeStyle(lineWidth: 4)) + .alignsMarkStylesWithPlotArea() + } + }), + + // IR Lux + MetricsChartSeries( + id: "irLux", + keyPath: \.irLux, + name: "IR Lux", + abbreviatedName: "IR", + visible: false, + foregroundStyle: { _ in + .linearGradient( + colors: [Color(UIColor.red.darker(componentDelta: 0.5)), .red], + startPoint: .bottom, endPoint: .top + ) + }, + chartBody: { series, _, time, lux in + if let lux { + LineMark( + x: .value("Time", time), + y: .value(series.abbreviatedName, lux) + ) + .interpolationMethod(.catmullRom) + .foregroundStyle(by: .value("Series", series.abbreviatedName)) + .lineStyle(StrokeStyle(lineWidth: 4)) + .alignsMarkStylesWithPlotArea() + } + }), + + // Radiation + MetricsChartSeries( + id: "radiation", + keyPath: \.radiation, + name: "Radiation", + abbreviatedName: "☢️", + minumumYAxisSpan: 20.0, + visible: false, + foregroundStyle: { _ in + .linearGradient( + colors: [Color(UIColor.orange.darker(componentDelta: 0.4)), .orange], + startPoint: .bottom, endPoint: .top + ) + }, + chartBody: { series, _, time, radiation in + if let radiation { + LineMark( + x: .value("Time", time), + y: .value(series.abbreviatedName, radiation) + ) + .interpolationMethod(.catmullRom) + .foregroundStyle(by: .value("Series", series.abbreviatedName)) + .lineStyle(StrokeStyle(lineWidth: 4)) + .alignsMarkStylesWithPlotArea() + } + }), + + // Weight + MetricsChartSeries( + id: "weight", + keyPath: \.weight, + name: "Weight", + abbreviatedName: "kg", + visible: false, + foregroundStyle: { _ in + .linearGradient( + colors: [Color(UIColor.systemPink.darker(componentDelta: 0.5)), .pink], + startPoint: .bottom, endPoint: .top + ) + }, + chartBody: { series, _, time, weight in + if let weight { + LineMark( + x: .value("Time", time), + y: .value(series.abbreviatedName, weight) + ) + .interpolationMethod(.catmullRom) + .foregroundStyle(by: .value("Series", series.abbreviatedName)) + .lineStyle(StrokeStyle(lineWidth: 4)) + .alignsMarkStylesWithPlotArea() + } + }), + + // Distance + MetricsChartSeries( + id: "distance", + keyPath: \.distance, + name: "Distance", + abbreviatedName: "Dist", + visible: false, + foregroundStyle: { _ in + .linearGradient( + colors: [Color(UIColor.systemTeal.darker(componentDelta: 0.7)), Color(UIColor.systemTeal)], + startPoint: .bottom, endPoint: .top + ) + }, + chartBody: { series, _, time, distance in + if let distance { + LineMark( + x: .value("Time", time), + y: .value(series.abbreviatedName, distance) + ) + .interpolationMethod(.catmullRom) + .foregroundStyle(by: .value("Series", series.abbreviatedName)) + .lineStyle(StrokeStyle(lineWidth: 4)) + .alignsMarkStylesWithPlotArea() + } + }), + + // Soil Temperature + MetricsChartSeries( + id: "soilTemperature", + keyPath: \.soilTemperature, + name: "Soil Temperature", + abbreviatedName: "Soil Temp", + visible: false, + foregroundStyle: { _ in + .linearGradient( + colors: [Color(UIColor.brown.darker(componentDelta: 0.4)), .brown], + startPoint: .bottom, endPoint: .top + ) + }, + chartBody: { series, _, time, soilTemp in + if let soilTemp { + LineMark( + x: .value("Time", time), + y: .value(series.abbreviatedName, soilTemp) + ) + .interpolationMethod(.catmullRom) + .foregroundStyle(by: .value("Series", series.abbreviatedName)) + .lineStyle(StrokeStyle(lineWidth: 4)) + .alignsMarkStylesWithPlotArea() + } + }), + + // Soil Temperature + MetricsChartSeries( + id: "soilMoisture", + keyPath: \.soilMoisture, + name: "Soil Moisture", + abbreviatedName: "Moist", + visible: false, + foregroundStyle: { _ in + .linearGradient( + colors: [Color(UIColor.blue.darker(componentDelta: 0.4)), .brown], + startPoint: .bottom, endPoint: .top + ) + }, + chartBody: { series, _, time, soilMoisture in + if let soilMoisture { + LineMark( + x: .value("Time", time), + y: .value(series.abbreviatedName, soilMoisture) + ) + .interpolationMethod(.catmullRom) + .foregroundStyle(by: .value("Series", series.abbreviatedName)) + .lineStyle(StrokeStyle(lineWidth: 4)) + .alignsMarkStylesWithPlotArea() + } + }), // Combined Wind Speed and Direction Series Configuration -- For use in Chart only MetricsChartSeries( @@ -143,10 +378,10 @@ extension MetricsSeriesList { abbreviatedName: "Speed/Dir", visible: false, foregroundStyle: { _ in - .linearGradient( - colors: [Color(UIColor.yellow.darker(componentDelta: 0.3)), Color(UIColor.yellow.darker(componentDelta: 0.1))], - startPoint: .bottom, endPoint: .top - ) + .linearGradient( + colors: [Color(UIColor.yellow.darker(componentDelta: 0.3)), Color(UIColor.yellow.darker(componentDelta: 0.1))], + startPoint: .bottom, endPoint: .top + ) }, chartBody: { series, _, time, wsad in if let wsad { @@ -216,7 +451,7 @@ func generateStops(minTemp: Double, maxTemp: Double, tempUnit: UnitTemperature, ((tempUnit == .celsius ? 20 : 68), .yellow), ((tempUnit == .celsius ? 30 : 86), .orange), ((tempUnit == .celsius ? 55 : 125), .red) - ] + ] for (stopValue, color) in stopTargets { let stopLocation = transform(stopValue, from: minTemp...maxTemp, to: 0...1) gradientStops.append(Gradient.Stop(color: color.opacity(opacity), location: stopLocation)) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 958f4bfc..caaf4a33 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -246,7 +246,7 @@ struct NodeDetail: View { gust: node.latestEnvironmentMetrics?.windGust ?? 0.0 > 0.0 ? windGust?.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))) : "", direction: direction) } if let radiation = node.latestEnvironmentMetrics?.radiation { - RadiationCompactWidget(radiation: radiation.formatted(.number.precision(.fractionLength(2))), unit: "µR/hr") + RadiationCompactWidget(radiation: radiation.formatted(.number.precision(.fractionLength(1))), unit: "µR/hr") } if let weight = node.latestEnvironmentMetrics?.weight { WeightCompactWidget(weight: weight.formatted(.number.precision(.fractionLength(1))), unit: "kg")