Add small weather widget back to node map

This commit is contained in:
Garth Vander Houwen 2023-09-10 15:41:03 -07:00
parent 0dac9bc6a5
commit b80b9e65b3
6 changed files with 149 additions and 267 deletions

View file

@ -162,7 +162,6 @@
DDDE5A1129AFE69700490C6C /* MeshActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */; };
DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; };
DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; };
DDDEE5E129DA3E1100A8E078 /* NodeInfoViewOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDEE5E029DA3E1100A8E078 /* NodeInfoViewOld.swift */; };
DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; };
DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; };
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; };
@ -382,7 +381,6 @@
DDDE5A0429AF163E00490C6C /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = "<group>"; };
DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshActivityAttributes.swift; sourceTree = "<group>"; };
DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
DDDEE5E029DA3E1100A8E078 /* NodeInfoViewOld.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoViewOld.swift; sourceTree = "<group>"; };
DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = "<group>"; };
DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = "<group>"; };
DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = "<group>"; };
@ -474,7 +472,6 @@
DD73FD1028750779000852D6 /* PositionLog.swift */,
DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */,
6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */,
DDDEE5E029DA3E1100A8E078 /* NodeInfoViewOld.swift */,
DD2E65252767A01F00E45FC5 /* NodeDetailOld.swift */,
DD47E3CD26F103C600029299 /* NodeListOld.swift */,
);
@ -1130,7 +1127,6 @@
6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */,
DD47E3CE26F103C600029299 /* NodeListOld.swift in Sources */,
DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */,
DDDEE5E129DA3E1100A8E078 /* NodeInfoViewOld.swift in Sources */,
DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */,
DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */,
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */,

View file

@ -19,7 +19,7 @@ struct ContentView: View {
Label("bluetooth", systemImage: "antenna.radiowaves.left.and.right")
}
.tag(Tab.ble)
NodeListSplit()
NodeList()
.tabItem {
Label("nodes", systemImage: "flipphone")
}

View file

@ -0,0 +1,93 @@
//
// NodeInfoItem.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 9/9/23.
//
import SwiftUI
import CoreLocation
import MapKit
struct NodeInfoItem: View {
@ObservedObject var node: NodeInfoEntity
var body: some View {
Divider()
HStack {
VStack(alignment: .center) {
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65)
}
if node.user != nil {
Divider()
VStack {
Image(node.user!.hwModel ?? "unset".localized)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 75, height: 75)
.cornerRadius(5)
Text(String(node.user!.hwModel ?? "unset".localized))
.font(.caption2).fixedSize()
}
}
if node.snr != 0 {
Divider()
VStack(alignment: .center) {
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate)
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
Text("Signal \(signalStrength.description)").font(.footnote)
Text("SNR \(String(format: "%.2f", node.snr))dB")
.foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate))
.font(.caption2)
Text("RSSI \(node.rssi)dB")
.foregroundColor(getRssiColor(rssi: node.rssi))
.font(.caption2)
}
}
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
if deviceMetrics?.count ?? 0 >= 1 {
Divider()
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
VStack(alignment: .center) {
BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0))
if mostRecent?.voltage ?? 0 > 0 {
Text(String(format: "%.2f", mostRecent?.voltage ?? 0) + " V")
.font(.callout)
.foregroundColor(.gray)
.fixedSize()
}
}
}
}
Divider()
HStack(alignment: .center) {
VStack {
HStack {
Image(systemName: "number")
.font(.title2)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Node Number:").font(.title2)
}
Text(String(node.num)).font(.title3).foregroundColor(.gray)
}
Divider()
VStack {
HStack {
Image(systemName: "person")
.font(.title2)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("User Id:").font(.title2)
}
Text(node.user?.userId ?? "?").font(.title3).foregroundColor(.gray)
}
}
Divider()
}
}

View file

@ -7,11 +7,21 @@
import SwiftUI
import CoreLocation
import MapKit
import WeatherKit
struct NodeMapControl: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
/// Weather
/// The current weather condition for the city.
@State private var condition: WeatherCondition?
@State private var temperature: Measurement<UnitTemperature>?
@State private var humidity: Int?
@State private var symbolName: String = "cloud.fill"
@State private var attributionLink: URL?
@State private var attributionLogo: URL?
@Environment(\.colorScheme) var colorScheme: ColorScheme
@AppStorage("meshMapType") private var meshMapType = 0
@AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
@ -76,6 +86,50 @@ struct NodeMapControl: View {
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
.pickerStyle(.menu)
.padding(5)
VStack {
Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
.font(.caption)
Label("\(humidity ?? 0)%", systemImage: "humidity")
.font(.caption2)
}
.padding(10)
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
.padding(5)
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)
.padding(.bottom, 5)
.task {
do {
if node.hasPositions {
let mostRecent = node.positions?.lastObject as? PositionEntity
let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude))
condition = weather.currentWeather.condition
temperature = weather.currentWeather.temperature
humidity = Int(weather.currentWeather.humidity * 100)
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"
}
}
}
}
}

View file

@ -1,261 +0,0 @@
////
//// NodeInfoView.swift
//// Meshtastic
////
//// Created by Garth Vander Houwen on 4/2/23.
////
//
////
//// DistanceText.swift
//// Meshtastic
////
//// Copyright(c) Garth Vander Houwen 8/19/22.
////
//
//import SwiftUI
//import CoreLocation
//import MapKit
//
//struct NodeInfoView: View {
//
// var node: NodeInfoEntity
//
// var body: some View {
// let hwModelString = node.user?.hwModel ?? "UNSET"
//
// Divider()
// if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
// HStack {
// VStack(alignment: .center) {
// CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 150)
// }
// Divider()
// VStack {
// if node.user != nil {
// Image(hwModelString)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 100, height: 100)
// .cornerRadius(5)
//
// Text(String(hwModelString))
// .foregroundColor(.gray)
// .font(.title).fixedSize()
// }
// }
// Divider()
// if node.snr != 0 {
// VStack(alignment: .center) {
// let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate)
// LoRaSignalStrengthIndicator(signalStrength: signalStrength)
// Text("Signal \(signalStrength.description)").font(.title)
// Text("SNR \(String(format: "%.2f", node.snr))dB")
// .foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate))
// .font(.title3)
// Text("RSSI \(node.rssi)dB")
// .foregroundColor(getRssiColor(rssi: node.rssi))
// .font(.title3)
// }
// Divider()
// }
//
// if node.hasDeviceMetrics {
// let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
// let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
// VStack(alignment: .center) {
// BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0))
// if mostRecent?.voltage ?? 0 > 0.0 {
//
// Text(String(format: "%.2f", mostRecent?.voltage ?? 0.0) + " V")
// .font(.title)
// .foregroundColor(.gray)
// .fixedSize()
// }
// }
// .padding()
// }
// }
// .padding()
//
// Divider()
// HStack(alignment: .center) {
//
// VStack {
// HStack {
// Image(systemName: "person")
// .font(.title)
// .foregroundColor(.accentColor)
// .symbolRenderingMode(.hierarchical)
// Text("user").font(.title)+Text(":").font(.title)
// }
// Text("!\(String(format: "%02x", node.num))")
// .font(.title).foregroundColor(.gray)
// }
// Divider()
// VStack {
// HStack {
// Image(systemName: "number")
// .font(.title2)
// .foregroundColor(.accentColor)
// .symbolRenderingMode(.hierarchical)
// Text("Node Number:").font(.title)
// }
// Text(String(node.num)).font(.title).foregroundColor(.gray)
// }
// Divider()
// VStack {
// HStack {
// Image(systemName: "clock.badge.checkmark.fill")
// .font(.title)
// .foregroundColor(.accentColor)
// .symbolRenderingMode(.hierarchical)
// Text("heard.last").font(.title)+Text(":").font(.title)
//
// }
// DateTimeText(dateTime: node.lastHeard)
// .font(.title3)
// .foregroundColor(.gray)
// }
// }
// Divider()
//
// } else {
//
// HStack {
//
// VStack(alignment: .center) {
// CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65)
// }
// if node.user != nil {
// Divider()
// VStack {
// Image(node.user!.hwModel ?? "unset".localized)
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 75, height: 75)
// .cornerRadius(5)
// Text(String(node.user!.hwModel ?? "unset".localized))
// .font(.caption2).fixedSize()
// }
// }
// if node.snr != 0 {
// Divider()
// VStack(alignment: .center) {
// let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate)
// LoRaSignalStrengthIndicator(signalStrength: signalStrength)
// Text("Signal \(signalStrength.description)").font(.footnote)
// Text("SNR \(String(format: "%.2f", node.snr))dB")
// .foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate))
// .font(.caption2)
// Text("RSSI \(node.rssi)dB")
// .foregroundColor(getRssiColor(rssi: node.rssi))
// .font(.caption2)
// }
// }
// let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
// if deviceMetrics?.count ?? 0 >= 1 {
// Divider()
// let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
// VStack(alignment: .center) {
// BatteryGauge(batteryLevel: Double(mostRecent?.batteryLevel ?? 0))
// if mostRecent?.voltage ?? 0 > 0 {
//
// Text(String(format: "%.2f", mostRecent?.voltage ?? 0) + " V")
// .font(.callout)
// .foregroundColor(.gray)
// .fixedSize()
// }
// }
// }
// }
// Divider()
// HStack(alignment: .center) {
// VStack {
// HStack {
// Image(systemName: "person")
// .font(.title2)
// .foregroundColor(.accentColor)
// .symbolRenderingMode(.hierarchical)
// Text("User Id:").font(.title2)
// }
// Text(node.user?.userId ?? "?").font(.title3).foregroundColor(.gray)
// }
// Divider()
// VStack {
// HStack {
// Image(systemName: "number")
// .font(.title2)
// .foregroundColor(.accentColor)
// .symbolRenderingMode(.hierarchical)
// Text("Node Number:").font(.title2)
// }
// Text(String(node.num)).font(.title3).foregroundColor(.gray)
// }
// }
// Divider()
// }
//
// VStack {
//
// if node.hasPositions{
//
// NavigationLink {
// PositionLog(node: node)
// } label: {
//
// Image(systemName: "building.columns")
// .symbolRenderingMode(.hierarchical)
// .font(.title)
//
// Text("Position Log")
// .font(.title3)
// }
// .fixedSize(horizontal: false, vertical: true)
// Divider()
// }
//
// if node.hasDeviceMetrics {
//
// NavigationLink {
// DeviceMetricsLog(node: node)
// } label: {
//
// Image(systemName: "flipphone")
// .symbolRenderingMode(.hierarchical)
// .font(.title)
//
// Text("Device Metrics Log")
// .font(.title3)
// }
// Divider()
// }
// if node.hasEnvironmentMetrics {
// NavigationLink {
// EnvironmentMetricsLog(node: node)
// } label: {
//
// Image(systemName: "chart.xyaxis.line")
// .symbolRenderingMode(.hierarchical)
// .font(.title)
//
// Text("Environment Metrics Log")
// .font(.title3)
// }
// Divider()
// }
// NavigationLink {
// DetectionSensorLog(node: node)
// } label: {
//
// Image(systemName: "sensor")
// .symbolRenderingMode(.hierarchical)
// .font(.title)
//
// Text("Detection Sensor Log")
// .font(.title3)
// }
// .fixedSize(horizontal: false, vertical: true)
// Divider()
// }
// }
//}

View file

@ -17,7 +17,7 @@ enum SelectedDetail {
struct NodeListSplit: View {
struct NodeList: View {
@State private var columnVisibility = NavigationSplitViewVisibility.all
@State private var selectedNode: NodeInfoEntity?