From 52a6620df6d24656d0e4c016bbc61b2ffd7aedfa Mon Sep 17 00:00:00 2001 From: Jake-B Date: Sat, 15 Mar 2025 09:05:36 -0400 Subject: [PATCH] Rainfall Compact Widgets --- Meshtastic.xcodeproj/project.pbxproj | 4 ++ .../Compact Widgets/CompactWidget.swift | 11 ++++ .../RainfallCompactWidget.swift | 66 +++++++++++++++++++ .../Views/Nodes/Helpers/NodeDetail.swift | 20 ++++++ 4 files changed, 101 insertions(+) create mode 100644 Meshtastic/Views/Helpers/Compact Widgets/RainfallCompactWidget.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index bf000340..19798229 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 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 */; }; + 233E99CB2D85AAA900CC3A77 /* RainfallCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.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 */; }; @@ -296,6 +297,7 @@ 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 = ""; }; + 233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainfallCompactWidget.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 = ""; }; @@ -638,6 +640,7 @@ 233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */, 233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */, 233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */, + 233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.swift */, ); path = "Compact Widgets"; sourceTree = ""; @@ -1528,6 +1531,7 @@ DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */, DD0BE3102CB9FDC4000BA445 /* DetectionSensorEnums.swift in Sources */, DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */, + 233E99CB2D85AAA900CC3A77 /* RainfallCompactWidget.swift in Sources */, DD2553592855B52700E55709 /* PositionConfig.swift in Sources */, DD97E96828EFE9A00056DDA4 /* About.swift in Sources */, DDDB444029F79AB000EE2349 /* UserDefaults.swift in Sources */, diff --git a/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift index 6bb35a0f..42411cc1 100644 --- a/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift +++ b/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift @@ -31,6 +31,17 @@ import SwiftUI WeightCompactWidget(weight: "123", unit: "kg") SoilTemperatureCompactWidget(temperature: "23", unit: "°C") SoilMoistureCompactWidget(moisture: "23", unit: "%") + + let rain: Float = 10.1 + let locale = NSLocale.current as NSLocale + let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches) + let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches + let unitLabel = usesMetricSystem ? "mm" : "in" + let measurement = Measurement(value: Double(rain), unit: UnitLength.millimeters) + let decimals = usesMetricSystem ? 0 : 1 + let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals))) + RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel) + RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel) } } } diff --git a/Meshtastic/Views/Helpers/Compact Widgets/RainfallCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/RainfallCompactWidget.swift new file mode 100644 index 00000000..9e71d213 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/RainfallCompactWidget.swift @@ -0,0 +1,66 @@ +// +// RainfallCompactWidgets.swift +// Meshtastic +// +// Created by Jake Bordens on 3/15/25. +// + +import SwiftUI + +struct RainfallCompactWidget: View { + enum RainfallTimeSpan: String { + case rainfall1H = "Rainfall 1H" + case rainfall24H = "Rainfall 24H" + } + + let timespan: RainfallTimeSpan + let rainfall: String + let unit: String + + private var icon: Image { + if timespan == .rainfall1H { + return Image(systemName: "cloud.rain.fill") + } + return Image(systemName: "cloud.heavyrain.fill") + } + + var body: some View { + VStack(alignment: .leading) { + HStack(alignment: .firstTextBaseline) { + icon.imageScale(.small) + .foregroundColor(.accentColor) + Text(timespan.rawValue) + .textCase(.uppercase) + .font(.callout) + } + HStack { + Text("\(rainfall)") + .font(rainfall.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) { + let rain: Float = 10.1 + let locale = NSLocale.current as NSLocale + let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches) + let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches + let unitLabel = usesMetricSystem ? "mm" : "in" + let measurement = Measurement(value: Double(rain), unit: UnitLength.millimeters) + let decimals = usesMetricSystem ? 0 : 1 + let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals))) + + RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel) + RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel) + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index caaf4a33..d3439b3f 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -245,6 +245,26 @@ struct NodeDetail: View { WindCompactWidget(speed: windSpeedMeasurement.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))), gust: node.latestEnvironmentMetrics?.windGust ?? 0.0 > 0.0 ? windGust?.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))) : "", direction: direction) } + if let rainfall1h = node.latestEnvironmentMetrics?.rainfall1H { + let locale = NSLocale.current as NSLocale + let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches) + let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches + let unitLabel = usesMetricSystem ? "mm" : "in" + let measurement = Measurement(value: Double(rainfall1h), unit: UnitLength.millimeters) + let decimals = usesMetricSystem ? 0 : 1 + let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals))) + RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel) + } + if let rainfall24h = node.latestEnvironmentMetrics?.rainfall24H { + let locale = NSLocale.current as NSLocale + let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches) + let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches + let unitLabel = usesMetricSystem ? "mm" : "in" + let measurement = Measurement(value: Double(rainfall24h), unit: UnitLength.millimeters) + let decimals = usesMetricSystem ? 0 : 1 + let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals))) + RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel) + } if let radiation = node.latestEnvironmentMetrics?.radiation { RadiationCompactWidget(radiation: radiation.formatted(.number.precision(.fractionLength(1))), unit: "µR/hr") }