From 796c1abb31537c8cb74502570fd33e7c381d1074 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 11 Jul 2024 08:42:27 -0700 Subject: [PATCH] Weatherkit widgets --- Localizable.xcstrings | 9 ++ Meshtastic.xcodeproj/project.pbxproj | 4 + .../xcschemes/WidgetsExtension.xcscheme | 1 + .../xcshareddata/swiftpm/Package.resolved | 2 +- .../CoreData/NodeInfoEntityExtension.swift | 8 ++ .../Weather/LocalWeatherConditions.swift | 114 ++++++++++++++++++ .../Views/Nodes/Helpers/NodeDetail.swift | 6 + 7 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift diff --git a/Localizable.xcstrings b/Localizable.xcstrings index e58123c2..b530f9f7 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -6953,6 +6953,9 @@ }, "Enter DFU Mode" : { + }, + "Environment" : { + }, "Environment Metrics Log" : { @@ -8265,6 +8268,9 @@ }, "Humidity" : { + }, + "HUMIDITY" : { + }, "hybrid" : { "extractionState" : "migrated", @@ -20839,6 +20845,9 @@ }, "The compass heading on the screen outside of the circle will always point north." : { + }, + "The dew point is %@ right now." : { + }, "The fastest that position updates will be sent if the minimum distance has been satisfied" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index f336ce5f..e25d0cec 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */; }; DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */; }; DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */; }; + DD3D17E02C3FB67200561584 /* LocalWeatherConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3D17DF2C3FB67200561584 /* LocalWeatherConditions.swift */; }; DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582528582E9B009B0E59 /* DeviceConfig.swift */; }; DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; }; DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582928585C32009B0E59 /* RangeTestConfig.swift */; }; @@ -294,6 +295,7 @@ DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingEnums.swift; sourceTree = ""; }; DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCoreData.swift; sourceTree = ""; }; DD3D17DC2C3D7B1400561584 /* MeshtasticDataModelV 39.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 39.xcdatamodel"; sourceTree = ""; }; + DD3D17DF2C3FB67200561584 /* LocalWeatherConditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalWeatherConditions.swift; sourceTree = ""; }; DD41582528582E9B009B0E59 /* DeviceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfig.swift; sourceTree = ""; }; DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = ""; }; DD41582928585C32009B0E59 /* RangeTestConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeTestConfig.swift; sourceTree = ""; }; @@ -605,6 +607,7 @@ DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */, DD354FD82BD96A0B0061A25F /* IAQScale.swift */, DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */, + DD3D17DF2C3FB67200561584 /* LocalWeatherConditions.swift */, ); path = Weather; sourceTree = ""; @@ -1134,6 +1137,7 @@ DDDB444C29F8AAA600EE2349 /* Color.swift in Sources */, DDB8F4122A9EE5DD00230ECE /* UserList.swift in Sources */, DDB75A0F2A05920E006ED576 /* FileManager.swift in Sources */, + DD3D17E02C3FB67200561584 /* LocalWeatherConditions.swift in Sources */, DD1933782B084F4200771CD5 /* Measurement.swift in Sources */, DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */, DD93800E2BA74D0C008BEC06 /* ChannelForm.swift in Sources */, diff --git a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme index 880339bc..decd8381 100644 --- a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme +++ b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme @@ -89,6 +89,7 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" + askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved index ae36a98b..10db40a7 100644 --- a/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "c5be9820b6e5add3da0e3bd134c3826b3eece5f926d667cb3800a26572f9e63c", + "originHash" : "74b3ad6215f078d89f4436b6ce0e318f145842efa3453bbe055ab76057de7d6b", "pins" : [ { "identity" : "cocoamqtt", diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index 9258b424..16a8332c 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -10,6 +10,14 @@ import CoreData extension NodeInfoEntity { + var latestPosition: PositionEntity? { + return self.positions?.lastObject as? PositionEntity + } + + var latestEnvironmentMetrics: TelemetryEntity? { + return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).lastObject as? TelemetryEntity + } + var hasPositions: Bool { return positions?.count ?? 0 > 0 } diff --git a/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift new file mode 100644 index 00000000..4425fcbe --- /dev/null +++ b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift @@ -0,0 +1,114 @@ +// +// LocalWeatherConditions.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 7/9/24. +// +import SwiftUI +import MapKit +import WeatherKit +import OSLog + +struct LocalWeatherConditions: View { + @State var location: CLLocation? + /// Weather + /// The current weather condition for the city. + @State private var condition: WeatherCondition? + @State private var temperature: Measurement? + @State private var temperatureCompact: String? + @State private var dewPoint: Measurement? + @State private var dewPointString: String? + @State private var humidity: Int? + @State private var pressure: Measurement? + @State private var symbolName: String = "cloud.fill" + @State private var attributionLink: URL? + @State private var attributionLogo: URL? + + @Environment(\.colorScheme) var colorScheme: ColorScheme + var body: some View { + if location != nil { + ZStack { + VStack { + HStack { + VStack { + VStack(alignment: .leading) { + Text(temperatureCompact ?? "??") + .font(.largeTitle) + Text(condition?.description ?? "??") + .font(.title3) + Image(systemName: symbolName).resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 40, height: 40) + } + .padding() + } + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + .frame(maxWidth: 225, maxHeight: 225) + + VStack { + VStack(alignment: .leading) { + Label { Text("HUMIDITY") } icon: { Image(systemName: "humidity").symbolRenderingMode(.multicolor) } + .font(.caption) + VStack(alignment: .leading) { + Text("\(humidity ?? 0)%") + .font(.largeTitle) + .padding(.bottom) + Text("The dew point is \(temperatureCompact ?? "?") right now.") + .lineLimit(3) + .fixedSize(horizontal: false, vertical: true) + .font(.caption) + } + } + .padding() + } + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + .frame(maxWidth: 225, maxHeight: 225) + } + } + } + VStack { + HStack { + AsyncImage(url: attributionLogo) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + ProgressView() + .controlSize(.mini) + } + .frame(height: 10) + Link("Other data sources", destination: attributionLink ?? URL(string: "https://weather-data.apple.com/legal-attribution.html")!) + .font(.caption2) + } + .padding(5) + } + .task { + do { + if location != nil { + let weather = try await WeatherService.shared.weather(for: location!) + let numFormatter = NumberFormatter() + let measurementFormatter = MeasurementFormatter() + numFormatter.maximumFractionDigits = 0 + measurementFormatter.numberFormatter = numFormatter + measurementFormatter.unitStyle = .short + condition = weather.currentWeather.condition + temperature = weather.currentWeather.temperature + temperatureCompact = measurementFormatter.string(from: dewPoint ?? Measurement(value: 0, unit: .celsius)) + dewPoint = weather.currentWeather.dewPoint + humidity = Int(weather.currentWeather.humidity * 100) + pressure = weather.currentWeather.pressure + symbolName = weather.currentWeather.symbolName + let attribution = try await WeatherService.shared.attribution + attributionLink = attribution.legalPageURL + attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL + } + } catch { + Logger.services.error("Could not gather weather information: \(error.localizedDescription)") + condition = .clear + symbolName = "cloud.fill" + } + } + .padding(5) + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 70cdcb8d..7deab98b 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -157,6 +157,12 @@ struct NodeDetail: View { } } } + + // if node.hasEnvironmentMetrics { + Section("Environment") { + LocalWeatherConditions(location: node.latestPosition?.nodeLocation) + } + // } Section("Logs") { // Metrics