Meshtastic-Apple/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift

207 lines
7.1 KiB
Swift
Raw Normal View History

2024-07-11 08:42:27 -07:00
//
// LocalWeatherConditions.swift
// Meshtastic
//
// Created by Garth Vander Houwen on 7/9/24.
//
import SwiftUI
import MapKit
import WeatherKit
import OSLog
struct LocalWeatherConditions: View {
2024-07-11 17:22:23 -07:00
private let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
2024-07-11 08:42:27 -07:00
@State var location: CLLocation?
/// Weather
/// The current weather condition for the city.
@State private var condition: WeatherCondition?
2024-07-11 17:43:05 -07:00
@State private var temperature: String = ""
@State private var dewPoint: String = ""
2024-07-11 08:42:27 -07:00
@State private var humidity: Int?
@State private var pressure: Measurement<UnitPressure>?
2024-07-11 22:51:44 -07:00
@State private var windSpeed: String = ""
@State private var windGust: String = ""
@State private var windDirection: Measurement<UnitAngle>?
@State private var windCompassDirection: String = ""
2024-07-11 08:42:27 -07:00
@State private var symbolName: String = "cloud.fill"
@State private var attributionLink: URL?
@State private var attributionLogo: URL?
2024-07-11 17:43:05 -07:00
2024-07-11 08:42:27 -07:00
@Environment(\.colorScheme) var colorScheme: ColorScheme
var body: some View {
if location != nil {
VStack {
2024-07-11 17:22:23 -07:00
LazyVGrid(columns: gridItemLayout) {
2024-07-11 17:43:05 -07:00
WeatherConditionsCompactWidget(temperature: temperature, symbolName: symbolName, description: condition?.description.uppercased() ?? "??")
HumidityCompactWidget(humidity: humidity ?? 0, dewPoint: dewPoint)
2024-07-12 11:38:06 -07:00
PressureCompactWidget(pressure: String(pressure?.value ?? 0.0 / 100), unit: pressure?.unit.symbol ?? "??", low: pressure?.value ?? 0.0 <= 1009.144)
2024-07-11 22:51:44 -07:00
WindCompactWidget(speed: windSpeed, gust: windGust, direction: windCompassDirection)
2024-07-11 08:42:27 -07:00
}
}
.padding(.top)
2024-07-11 08:42:27 -07:00
.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
2024-07-11 17:43:05 -07:00
measurementFormatter.locale = Locale.current
2024-07-11 08:42:27 -07:00
condition = weather.currentWeather.condition
2024-07-11 17:43:05 -07:00
temperature = measurementFormatter.string(from: weather.currentWeather.temperature)
dewPoint = measurementFormatter.string(from: weather.currentWeather.dewPoint)
2024-07-11 08:42:27 -07:00
humidity = Int(weather.currentWeather.humidity * 100)
pressure = weather.currentWeather.pressure
windSpeed = measurementFormatter.string(from: weather.currentWeather.wind.speed)
2024-07-11 22:51:44 -07:00
windGust = measurementFormatter.string(from: weather.currentWeather.wind.gust ?? Measurement(value: 0.0, unit: weather.currentWeather.wind.gust!.unit))
windDirection = weather.currentWeather.wind.direction
windCompassDirection = weather.currentWeather.wind.compassDirection.description
2024-07-11 08:42:27 -07:00
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"
}
}
2024-07-11 17:22:23 -07:00
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(2)
}
}
}
}
struct WeatherConditionsCompactWidget: View {
let temperature: String
let symbolName: String
let description: String
var body: some View {
2024-07-12 17:52:21 -07:00
VStack(alignment: .leading) {
2024-08-23 19:12:07 -07:00
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)
}
2024-07-12 17:52:21 -07:00
Text(temperature)
2024-09-22 07:12:53 -07:00
.font(temperature.length < 4 ? .system(size: 72) : .system(size: 54) )
2024-07-11 17:22:23 -07:00
}
2024-08-23 19:12:07 -07:00
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
2024-08-21 15:37:04 -07:00
.padding()
2024-07-12 17:52:21 -07:00
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
2024-07-11 17:22:23 -07:00
}
}
struct HumidityCompactWidget: View {
let humidity: Int
let dewPoint: String
var body: some View {
2024-07-12 17:52:21 -07:00
VStack(alignment: .leading) {
2024-08-23 19:12:07 -07:00
HStack(spacing: 5.0) {
Image(systemName: "humidity")
.foregroundColor(.accentColor)
.font(.callout)
Text("HUMIDITY")
.font(.caption)
}
2024-07-12 17:52:21 -07:00
Text("\(humidity)%")
.font(.largeTitle)
2024-08-23 19:12:07 -07:00
.padding(.bottom, 5)
2024-07-12 17:52:21 -07:00
Text("The dew point is \(dewPoint) right now.")
.lineLimit(3)
2024-08-23 19:12:07 -07:00
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
2024-07-12 17:52:21 -07:00
.fixedSize(horizontal: false, vertical: true)
2024-08-23 19:12:07 -07:00
.font(.caption2)
2024-07-11 08:42:27 -07:00
}
2024-08-23 19:12:07 -07:00
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
2024-08-21 15:37:04 -07:00
.padding()
2024-07-12 17:52:21 -07:00
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
2024-07-11 08:42:27 -07:00
}
}
2024-07-11 22:51:44 -07:00
struct PressureCompactWidget: View {
let pressure: String
let unit: String
2024-07-12 11:38:06 -07:00
let low: Bool
2024-07-11 22:51:44 -07:00
var body: some View {
2024-07-12 17:52:21 -07:00
VStack(alignment: .leading) {
2024-08-23 19:12:07 -07:00
HStack(spacing: 5.0) {
Image(systemName: "gauge")
.foregroundColor(.accentColor)
.font(.callout)
Text("PRESSURE")
.font(.caption)
}
2024-07-12 17:52:21 -07:00
Text(pressure)
.font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) )
Text(low ? "LOW" : "HIGH")
2024-08-23 19:12:07 -07:00
.padding(.bottom, 10)
2024-07-12 17:52:21 -07:00
Text(unit)
2024-07-11 22:51:44 -07:00
}
2024-08-23 19:12:07 -07:00
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
2024-08-21 15:37:04 -07:00
.padding()
2024-07-12 17:52:21 -07:00
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
2024-07-11 22:51:44 -07:00
}
}
struct WindCompactWidget: View {
let speed: String
let gust: String
let direction: String
var body: some View {
2024-07-12 17:52:21 -07:00
VStack(alignment: .leading) {
Label { Text("WIND") } icon: { Image(systemName: "wind").foregroundColor(.accentColor) }
Text("\(direction)")
2024-08-21 14:13:16 -07:00
.font(gust.isEmpty ? .callout : .caption)
2024-07-12 17:52:21 -07:00
.padding(.bottom, 10)
Text(speed)
2024-08-21 14:13:16 -07:00
.font(gust.isEmpty ? .system(size: 45) : .system(size: 35))
if !gust.isEmpty {
Text("Gusts \(gust)")
}
2024-07-11 22:51:44 -07:00
}
2024-08-23 19:12:07 -07:00
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
2024-08-21 15:37:04 -07:00
.padding()
2024-07-12 17:52:21 -07:00
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
2024-07-11 22:51:44 -07:00
}
}
2024-07-12 00:24:09 -07:00
/// Magnus Formula
func calculateDewPoint(temp: Float, relativeHumidity: Float) -> Double {
let a: Float = 17.27
let b: Float = 237.7
let alpha = ((a * temp) / (b + temp)) + log(relativeHumidity / 100.0)
let dewPoint = (b * alpha) / (a - alpha)
let dewPointUnit = Measurement<UnitTemperature>(value: Double(dewPoint), unit: .celsius)
let locale = NSLocale.current as NSLocale
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
var format: UnitTemperature = .celsius
if localeUnit! as? String == "Fahrenheit" {
format = .fahrenheit
}
return dewPointUnit.converted(to: format).value
}