From 35ce87c6682aed91c20a9d466ba84c6a547abb01 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 27 Jan 2023 20:22:17 -0800 Subject: [PATCH] Weather kit for the node details map --- Meshtastic/Meshtastic.entitlements | 2 + .../Persistence/PositionEntityExtension.swift | 9 +++ Meshtastic/Views/Nodes/NodeDetail.swift | 78 +++++++++++++++++-- Meshtastic/Views/Nodes/NodeMap.swift | 2 + 4 files changed, 83 insertions(+), 8 deletions(-) diff --git a/Meshtastic/Meshtastic.entitlements b/Meshtastic/Meshtastic.entitlements index 5c8b6ee4..26f4ce01 100644 --- a/Meshtastic/Meshtastic.entitlements +++ b/Meshtastic/Meshtastic.entitlements @@ -6,6 +6,8 @@ applinks:meshtastic.org/e/* + com.apple.developer.weatherkit + com.apple.security.app-sandbox com.apple.security.device.bluetooth diff --git a/Meshtastic/Persistence/PositionEntityExtension.swift b/Meshtastic/Persistence/PositionEntityExtension.swift index ca7bd3e9..23ec54b2 100644 --- a/Meshtastic/Persistence/PositionEntityExtension.swift +++ b/Meshtastic/Persistence/PositionEntityExtension.swift @@ -32,6 +32,15 @@ extension PositionEntity { } } + var nodeLocation: CLLocation? { + if latitudeI != 0 && longitudeI != 0 { + let location = CLLocation(latitude: latitude!, longitude: longitude!) + return location + } else { + return nil + } + } + var annotaton: MKPointAnnotation { let pointAnn = MKPointAnnotation() if nodeCoordinate != nil { diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 032c72be..f19b57ca 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -4,6 +4,7 @@ */ import SwiftUI +import WeatherKit import MapKit import CoreLocation @@ -11,6 +12,7 @@ struct NodeDetail: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @Environment(\.colorScheme) var colorScheme: ColorScheme @State var satsInView = 0 @State private var mapType: MKMapType = .standard @State var waypointCoordinate: CLLocationCoordinate2D? @@ -35,6 +37,14 @@ struct NodeDetail: View { ), animation: .easeIn) private var waypoints: FetchedResults + /// The current weather condition for the city. + @State private var condition: WeatherCondition? + @State private var temperature: Measurement? + @State private var symbolName: String = "cloud.fill" + + @State private var attributionLink: URL? + @State private var attributionLogo: URL? + var body: some View { let hwModelString = node.user?.hwModel ?? "UNSET" @@ -63,17 +73,31 @@ struct NodeDetail: View { overlays: self.overlays ) - VStack { + VStack (alignment: .leading) { Spacer() - Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ") - .font(.caption) - .offset(y: 20) - Picker("Map Type", selection: $mapType) { - ForEach(MeshMapType.allCases) { map in - Text(map.description).tag(map.MKMapTypeValue()) + HStack (alignment: .bottom, spacing: 1) { + + Picker("Map Type", selection: $mapType) { + ForEach(MeshMapType.allCases) { map in + Text(map.description).tag(map.MKMapTypeValue()) + } } + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) + .pickerStyle(.menu) + .padding(5) + VStack { + if mostRecent.satsInView > 0 { + Text("Sats: \(mostRecent.satsInView)") + .font(.caption) + .padding(.bottom, 1) + } + Label(temperature?.formatted() ?? "??", systemImage: symbolName) + .font(.caption) + } + .padding(10) + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) + .padding(5) } - .pickerStyle(.menu) } } .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) @@ -332,6 +356,20 @@ struct NodeDetail: View { } Divider() } + VStack { + AsyncImage(url: attributionLogo) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + ProgressView() + .controlSize(.mini) + } + .frame(height: 15) + + Link("Other data sources", destination: attributionLink ?? URL(string: "https://weather-data.apple.com/legal-attribution.html")!) + } + .font(.footnote) } if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { @@ -403,6 +441,30 @@ struct NodeDetail: View { .onAppear { self.bleManager.context = context } + .task(id: node.num) { + do { + + if node.positions?.count ?? 0 > 0 { + + let mostRecent = node.positions?.lastObject as! PositionEntity + + let weather = try await WeatherService.shared.weather(for: mostRecent.nodeLocation!) + condition = weather.currentWeather.condition + temperature = weather.currentWeather.temperature + symbolName = weather.currentWeather.symbolName + + let attribution = try await WeatherService.shared.attribution + attributionLink = attribution.legalPageURL + attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL + + } + + } catch { + print("Could not gather weather information...", error.localizedDescription) + condition = .clear + symbolName = "cloud.fill" + } + } } } } diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 99f21bca..b57c4a59 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -80,7 +80,9 @@ struct NodeMap: View { Text(map.description).tag(map.MKMapTypeValue()) } } + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) .pickerStyle(.menu) + .padding(.bottom, 5) } } .ignoresSafeArea(.all, edges: [.top, .leading, .trailing])