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])