From 7ca655535acd5e3db998d23ee723ac54b41eb9f2 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 9 Sep 2023 19:10:05 -0700 Subject: [PATCH] Add map to new 3 column layout, comment out old views --- Meshtastic/Views/ContentView.swift | 8 +- .../Views/Nodes/Helpers/NodeDetailItem.swift | 2 +- .../Views/Nodes/Helpers/NodeMapControl.swift | 93 +++- Meshtastic/Views/Nodes/NodeDetail.swift | 496 +++++++++--------- Meshtastic/Views/Nodes/NodeList.swift | 266 +++++----- 5 files changed, 475 insertions(+), 390 deletions(-) diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 751b7a38..5f2340fe 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -19,16 +19,11 @@ struct ContentView: View { Label("bluetooth", systemImage: "antenna.radiowaves.left.and.right") } .tag(Tab.ble) - NodeList() - .tabItem { - Label("nodes", systemImage: "flipphone") - } - .tag(Tab.nodes) NodeListSplit() .tabItem { Label("nodes", systemImage: "flipphone") } - .tag(Tab.nodes2) + .tag(Tab.nodes) NodeMap() .tabItem { Label("map", systemImage: "map") @@ -56,6 +51,5 @@ enum Tab { case map case ble case nodes - case nodes2 case settings } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift index 5d1b340d..7c4d04f4 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetailItem.swift @@ -66,7 +66,7 @@ struct NodeDetailItem: View { NavigationLink { PositionLog(node: node) } label: { - Image(systemName: "building.columns") + Image(systemName: "mappin.and.ellipse") .symbolRenderingMode(.hierarchical) .font(.title) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeMapControl.swift b/Meshtastic/Views/Nodes/Helpers/NodeMapControl.swift index 68723969..1a816892 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeMapControl.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeMapControl.swift @@ -10,9 +10,100 @@ import MapKit struct NodeMapControl: View { + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @Environment(\.colorScheme) var colorScheme: ColorScheme + @AppStorage("meshMapType") private var meshMapType = 0 + @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false + @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false + @State private var selectedMapLayer: MapLayer = .standard + @State var waypointCoordinate: WaypointCoordinate? + @State var editingWaypoint: Int = 0 + @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( + mapName: "offlinemap", + tileType: "png", + canReplaceMapContent: true + ) + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], + predicate: NSPredicate( + format: "expire == nil || expire >= %@", Date() as NSDate + ), animation: .none) + private var waypoints: FetchedResults @ObservedObject var node: NodeInfoEntity var body: some View { - Text("I am a map") + + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + NavigationStack { + GeometryReader { bounds in + VStack { + if node.hasPositions { + ZStack { + let positionArray = node.positions?.array as? [PositionEntity] ?? [] + let lastTenThousand = Array(positionArray.prefix(10000)) + // let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) } + ZStack { + MapViewSwiftUI(onLongPress: { coord in + waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0) + }, onWaypointEdit: { wpId in + if wpId > 0 { + waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) + } + }, + selectedMapLayer: selectedMapLayer, + positions: lastTenThousand, + waypoints: Array(waypoints), + userTrackingMode: MKUserTrackingMode.none, + showNodeHistory: meshMapShowNodeHistory, + showRouteLines: meshMapShowRouteLines, + customMapOverlay: self.customMapOverlay + ) + VStack(alignment: .leading) { + Spacer() + HStack(alignment: .bottom, spacing: 1) { + Picker("Map Type", selection: $selectedMapLayer) { + ForEach(MapLayer.allCases, id: \.self) { layer in + if layer == MapLayer.offline && UserDefaults.enableOfflineMaps { + Text(layer.localized) + } else if layer != MapLayer.offline { + Text(layer.localized) + } + } + } + .onChange(of: (selectedMapLayer)) { newMapLayer in + UserDefaults.mapLayer = newMapLayer + } + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) + .pickerStyle(.menu) + .padding(5) + } + } + } + .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65) + } + } else { + HStack { + } + .padding([.top], 20) + } + } + .edgesIgnoringSafeArea([.leading, .trailing]) + .sheet(item: $waypointCoordinate, content: { wpc in + WaypointFormView(coordinate: wpc) + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.automatic) + }) + .navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline) + .navigationBarItems(trailing: + ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") + }) + } + .padding(.bottom, 2) + } } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 356e9e59..f0e78ea5 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -1,248 +1,248 @@ -/* - Abstract: - A view showing the details for a node. - */ - -import SwiftUI -import WeatherKit -import MapKit -import CoreLocation - -struct NodeDetail: View { - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - @Environment(\.colorScheme) var colorScheme: ColorScheme - @AppStorage("meshMapType") private var meshMapType = 0 - @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false - @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false - @State private var selectedMapLayer: MapLayer = .standard - @State var waypointCoordinate: WaypointCoordinate? - @State var editingWaypoint: Int = 0 - @State private var loadedWeather: Bool = false - @State private var showingDetailsPopover = false - @State private var showingForecast = false - @State private var showingShutdownConfirm: Bool = false - @State private var showingRebootConfirm: Bool = false - @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( - mapName: "offlinemap", - tileType: "png", - canReplaceMapContent: true - ) - var node: NodeInfoEntity - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], - predicate: NSPredicate( - format: "expire == nil || expire >= %@", Date() as NSDate - ), animation: .none) - private var waypoints: FetchedResults - - /// The current weather condition for the city. - @State private var condition: WeatherCondition? - @State private var temperature: Measurement? - @State private var humidity: Int? - @State private var symbolName: String = "cloud.fill" - - @State private var attributionLink: URL? - @State private var attributionLogo: URL? - - var body: some View { - - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) - NavigationStack { - GeometryReader { bounds in - VStack { - if node.hasPositions { - ZStack { - let positionArray = node.positions?.array as? [PositionEntity] ?? [] - let lastTenThousand = Array(positionArray.prefix(10000)) - // let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) } - ZStack { - MapViewSwiftUI(onLongPress: { coord in - waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0) - }, onWaypointEdit: { wpId in - if wpId > 0 { - waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) - } - }, - selectedMapLayer: selectedMapLayer, - positions: lastTenThousand, - waypoints: Array(waypoints), - userTrackingMode: MKUserTrackingMode.none, - showNodeHistory: meshMapShowNodeHistory, - showRouteLines: meshMapShowRouteLines, - customMapOverlay: self.customMapOverlay - ) - VStack(alignment: .leading) { - Spacer() - HStack(alignment: .bottom, spacing: 1) { - Picker("Map Type", selection: $selectedMapLayer) { - ForEach(MapLayer.allCases, id: \.self) { layer in - if layer == MapLayer.offline && UserDefaults.enableOfflineMaps { - Text(layer.localized) - } else if layer != MapLayer.offline { - Text(layer.localized) - } - } - } - .onChange(of: (selectedMapLayer)) { newMapLayer in - UserDefaults.mapLayer = newMapLayer - } - .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) - #if targetEnvironment(macCatalyst) - .popover(isPresented: $showingForecast, - arrowEdge: .top) { - Text("Today's Weather Forecast") - .font(.title) - .padding() - let nodeLocation = node.positions?.lastObject as? PositionEntity - NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) ) - .frame(height: 250) - } - #else - .sheet(isPresented: $showingForecast) { - Text("Today's Weather Forecast") - .font(.title) - .padding() - let nodeLocation = node.positions?.lastObject as? PositionEntity - NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) ).frame(height: 250) - .presentationDetents([.medium]) - .presentationDragIndicator(.automatic) - } - #endif - .gesture( - LongPressGesture(minimumDuration: 0.5) - .onEnded { _ in - showingForecast = true - } - ) - } - } - } - .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) - .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65) - } - } else { - HStack { - } - .padding([.top], 20) - } - ScrollView { - NodeInfoView(node: node) - if self.bleManager.connectedPeripheral != nil && node.metadata != nil { - HStack { - if node.metadata?.canShutdown ?? false { - - Button(action: { - showingShutdownConfirm = true - }) { - Label("Power Off", systemImage: "power") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $showingShutdownConfirm - ) { - Button("Shutdown Node?", role: .destructive) { - if !bleManager.sendShutdown(fromUser: connectedNode!.user!, toUser: node.user!, adminIndex: connectedNode!.myInfo!.adminIndex) { - print("Shutdown Failed") - } - } - } - } - - Button(action: { - showingRebootConfirm = true - }) { - Label("reboot", systemImage: "arrow.triangle.2.circlepath") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog("are.you.sure", - isPresented: $showingRebootConfirm - ) { - Button("reboot.node", role: .destructive) { - if !bleManager.sendReboot(fromUser: connectedNode!.user!, toUser: node.user!, adminIndex: connectedNode!.myInfo!.adminIndex) { - print("Reboot Failed") - } - } - } - } - .padding(5) - Divider() - } - if node.positions?.count ?? 0 > 0 { - 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) - } - } - } - .edgesIgnoringSafeArea([.leading, .trailing]) - .sheet(item: $waypointCoordinate, content: { wpc in - WaypointFormView(coordinate: wpc) - .presentationDetents([.medium, .large]) - .presentationDragIndicator(.automatic) - }) - .navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline) - .navigationBarItems(trailing: - ZStack { - ConnectedDevice( - bluetoothOn: bleManager.isSwitchedOn, - deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) - .task(id: node.num) { - if !loadedWeather { - 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 - loadedWeather = true - } - } catch { - print("Could not gather weather information...", error.localizedDescription) - condition = .clear - symbolName = "cloud.fill" - } - } - } - } - .padding(.bottom, 2) - } - } -} +///* +// Abstract: +// A view showing the details for a node. +// */ +// +//import SwiftUI +//import WeatherKit +//import MapKit +//import CoreLocation +// +//struct NodeDetail: View { +// +// @Environment(\.managedObjectContext) var context +// @EnvironmentObject var bleManager: BLEManager +// @Environment(\.colorScheme) var colorScheme: ColorScheme +// @AppStorage("meshMapType") private var meshMapType = 0 +// @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false +// @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false +// @State private var selectedMapLayer: MapLayer = .standard +// @State var waypointCoordinate: WaypointCoordinate? +// @State var editingWaypoint: Int = 0 +// @State private var loadedWeather: Bool = false +// @State private var showingDetailsPopover = false +// @State private var showingForecast = false +// @State private var showingShutdownConfirm: Bool = false +// @State private var showingRebootConfirm: Bool = false +// @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( +// mapName: "offlinemap", +// tileType: "png", +// canReplaceMapContent: true +// ) +// var node: NodeInfoEntity +// @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], +// predicate: NSPredicate( +// format: "expire == nil || expire >= %@", Date() as NSDate +// ), animation: .none) +// private var waypoints: FetchedResults +// +// /// The current weather condition for the city. +// @State private var condition: WeatherCondition? +// @State private var temperature: Measurement? +// @State private var humidity: Int? +// @State private var symbolName: String = "cloud.fill" +// +// @State private var attributionLink: URL? +// @State private var attributionLogo: URL? +// +// var body: some View { +// +// let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) +// NavigationStack { +// GeometryReader { bounds in +// VStack { +// if node.hasPositions { +// ZStack { +// let positionArray = node.positions?.array as? [PositionEntity] ?? [] +// let lastTenThousand = Array(positionArray.prefix(10000)) +// // let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) } +// ZStack { +// MapViewSwiftUI(onLongPress: { coord in +// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0) +// }, onWaypointEdit: { wpId in +// if wpId > 0 { +// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId)) +// } +// }, +// selectedMapLayer: selectedMapLayer, +// positions: lastTenThousand, +// waypoints: Array(waypoints), +// userTrackingMode: MKUserTrackingMode.none, +// showNodeHistory: meshMapShowNodeHistory, +// showRouteLines: meshMapShowRouteLines, +// customMapOverlay: self.customMapOverlay +// ) +// VStack(alignment: .leading) { +// Spacer() +// HStack(alignment: .bottom, spacing: 1) { +// Picker("Map Type", selection: $selectedMapLayer) { +// ForEach(MapLayer.allCases, id: \.self) { layer in +// if layer == MapLayer.offline && UserDefaults.enableOfflineMaps { +// Text(layer.localized) +// } else if layer != MapLayer.offline { +// Text(layer.localized) +// } +// } +// } +// .onChange(of: (selectedMapLayer)) { newMapLayer in +// UserDefaults.mapLayer = newMapLayer +// } +// .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) +// #if targetEnvironment(macCatalyst) +// .popover(isPresented: $showingForecast, +// arrowEdge: .top) { +// Text("Today's Weather Forecast") +// .font(.title) +// .padding() +// let nodeLocation = node.positions?.lastObject as? PositionEntity +// NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) ) +// .frame(height: 250) +// } +// #else +// .sheet(isPresented: $showingForecast) { +// Text("Today's Weather Forecast") +// .font(.title) +// .padding() +// let nodeLocation = node.positions?.lastObject as? PositionEntity +// NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) ).frame(height: 250) +// .presentationDetents([.medium]) +// .presentationDragIndicator(.automatic) +// } +// #endif +// .gesture( +// LongPressGesture(minimumDuration: 0.5) +// .onEnded { _ in +// showingForecast = true +// } +// ) +// } +// } +// } +// .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) +// .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65) +// } +// } else { +// HStack { +// } +// .padding([.top], 20) +// } +// ScrollView { +// NodeInfoView(node: node) +// if self.bleManager.connectedPeripheral != nil && node.metadata != nil { +// HStack { +// if node.metadata?.canShutdown ?? false { +// +// Button(action: { +// showingShutdownConfirm = true +// }) { +// Label("Power Off", systemImage: "power") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding() +// .confirmationDialog( +// "are.you.sure", +// isPresented: $showingShutdownConfirm +// ) { +// Button("Shutdown Node?", role: .destructive) { +// if !bleManager.sendShutdown(fromUser: connectedNode!.user!, toUser: node.user!, adminIndex: connectedNode!.myInfo!.adminIndex) { +// print("Shutdown Failed") +// } +// } +// } +// } +// +// Button(action: { +// showingRebootConfirm = true +// }) { +// Label("reboot", systemImage: "arrow.triangle.2.circlepath") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding() +// .confirmationDialog("are.you.sure", +// isPresented: $showingRebootConfirm +// ) { +// Button("reboot.node", role: .destructive) { +// if !bleManager.sendReboot(fromUser: connectedNode!.user!, toUser: node.user!, adminIndex: connectedNode!.myInfo!.adminIndex) { +// print("Reboot Failed") +// } +// } +// } +// } +// .padding(5) +// Divider() +// } +// if node.positions?.count ?? 0 > 0 { +// 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) +// } +// } +// } +// .edgesIgnoringSafeArea([.leading, .trailing]) +// .sheet(item: $waypointCoordinate, content: { wpc in +// WaypointFormView(coordinate: wpc) +// .presentationDetents([.medium, .large]) +// .presentationDragIndicator(.automatic) +// }) +// .navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline) +// .navigationBarItems(trailing: +// ZStack { +// ConnectedDevice( +// bluetoothOn: bleManager.isSwitchedOn, +// deviceConnected: bleManager.connectedPeripheral != nil, +// name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") +// }) +// .task(id: node.num) { +// if !loadedWeather { +// 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 +// loadedWeather = true +// } +// } catch { +// print("Could not gather weather information...", error.localizedDescription) +// condition = .clear +// symbolName = "cloud.fill" +// } +// } +// } +// } +// .padding(.bottom, 2) +// } +// } +//} diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 3ab4a478..c47b1909 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -1,136 +1,136 @@ +//// +//// NodeList.swift +//// Meshtastic +//// +//// Copyright(c) Garth Vander Houwen 8/7/21. +//// // -// NodeList.swift -// Meshtastic +//// Abstract: +//// A view showing a list of devices that have been seen on the mesh network from the perspective of the connected device. // -// Copyright(c) Garth Vander Houwen 8/7/21. +//import SwiftUI +//import CoreLocation // - -// Abstract: -// A view showing a list of devices that have been seen on the mesh network from the perspective of the connected device. - -import SwiftUI -import CoreLocation - -struct NodeList: View { - - @State private var searchText = "" - var nodesQuery: Binding { - Binding { - searchText - } set: { newValue in - searchText = newValue - nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue) - } - } - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - - @FetchRequest( - sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], - animation: .default) - - private var nodes: FetchedResults - - @State private var selection: NodeInfoEntity? // Nothing selected by default. - - var body: some View { - - NavigationSplitView { - let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) - let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) - List(nodes, id: \.self, selection: $selection) { node in - if nodes.count == 0 { - Text("no.nodes").font(.title) - } else { - NavigationLink(value: node) { - let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num) - LazyVStack(alignment: .leading) { - HStack { - VStack(alignment: .leading) { - CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65) - .padding(.trailing, 5) - let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) - if deviceMetrics?.count ?? 0 >= 1 { - let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity - BatteryLevelCompact(batteryLevel: mostRecent?.batteryLevel, font: .caption2, iconFont: .callout, color: .accentColor) - } - } - VStack(alignment: .leading) { - Text(node.user?.longName ?? "unknown".localized) - .fontWeight(.medium) - .font(.callout) - if connected { - HStack(alignment: .bottom) { - Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") - .font(.footnote) - .symbolRenderingMode(.hierarchical) - .foregroundColor(.green) - Text("connected").font(.caption) - } - } - HStack(alignment: .bottom) { - Image(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill") - .font(.footnote) - .symbolRenderingMode(.hierarchical) - .foregroundColor(node.isOnline ? .green : .orange) - LastHeardText(lastHeard: node.lastHeard) - .font(.caption) - } - if node.positions?.count ?? 0 > 0 && (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 != node.num) { - HStack(alignment: .bottom) { - let lastPostion = node.positions!.reversed()[0] as! PositionEntity - let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) - if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { - let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) - let metersAway = nodeCoord.distance(from: myCoord) - Image(systemName: "lines.measurement.horizontal") - .font(.footnote) - .symbolRenderingMode(.hierarchical) - DistanceText(meters: metersAway).font(.caption) - } - } - } - if node.channel > 0 { - HStack(alignment: .bottom) { - Image(systemName: "fibrechannel") - .font(.footnote) - .symbolRenderingMode(.hierarchical) - Text("Channel: \(node.channel)") - .font(.footnote) - } - } - if !connected { - HStack(alignment: .bottom) { let preset = ModemPresets(rawValue: Int(connectedNode?.loRaConfig?.modemPreset ?? 0)) - LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) - } - } - } - .frame(maxWidth: .infinity, alignment: .leading) - } - } - } - .padding([.top, .bottom]) - } - } - .listStyle(.plain) - .navigationSplitViewColumnWidth(300) - .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) - .navigationBarItems(leading: - MeshtasticLogo() - ) - .onAppear { - if self.bleManager.context == nil { - self.bleManager.context = context - } - } - } detail: { - if let node = selection { - NodeDetail(node: node) - } else { - Text("select.node") - } - } - .searchable(text: nodesQuery, prompt: "Find a node") - } -} +//struct NodeList: View { +// +// @State private var searchText = "" +// var nodesQuery: Binding { +// Binding { +// searchText +// } set: { newValue in +// searchText = newValue +// nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue) +// } +// } +// +// @Environment(\.managedObjectContext) var context +// @EnvironmentObject var bleManager: BLEManager +// +// @FetchRequest( +// sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], +// animation: .default) +// +// private var nodes: FetchedResults +// +// @State private var selection: NodeInfoEntity? // Nothing selected by default. +// +// var body: some View { +// +// NavigationSplitView { +// let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) +// let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) +// List(nodes, id: \.self, selection: $selection) { node in +// if nodes.count == 0 { +// Text("no.nodes").font(.title) +// } else { +// NavigationLink(value: node) { +// let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num) +// LazyVStack(alignment: .leading) { +// HStack { +// VStack(alignment: .leading) { +// CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 65) +// .padding(.trailing, 5) +// let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) +// if deviceMetrics?.count ?? 0 >= 1 { +// let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity +// BatteryLevelCompact(batteryLevel: mostRecent?.batteryLevel, font: .caption2, iconFont: .callout, color: .accentColor) +// } +// } +// VStack(alignment: .leading) { +// Text(node.user?.longName ?? "unknown".localized) +// .fontWeight(.medium) +// .font(.callout) +// if connected { +// HStack(alignment: .bottom) { +// Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") +// .font(.footnote) +// .symbolRenderingMode(.hierarchical) +// .foregroundColor(.green) +// Text("connected").font(.caption) +// } +// } +// HStack(alignment: .bottom) { +// Image(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill") +// .font(.footnote) +// .symbolRenderingMode(.hierarchical) +// .foregroundColor(node.isOnline ? .green : .orange) +// LastHeardText(lastHeard: node.lastHeard) +// .font(.caption) +// } +// if node.positions?.count ?? 0 > 0 && (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 != node.num) { +// HStack(alignment: .bottom) { +// let lastPostion = node.positions!.reversed()[0] as! PositionEntity +// let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) +// if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { +// let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) +// let metersAway = nodeCoord.distance(from: myCoord) +// Image(systemName: "lines.measurement.horizontal") +// .font(.footnote) +// .symbolRenderingMode(.hierarchical) +// DistanceText(meters: metersAway).font(.caption) +// } +// } +// } +// if node.channel > 0 { +// HStack(alignment: .bottom) { +// Image(systemName: "fibrechannel") +// .font(.footnote) +// .symbolRenderingMode(.hierarchical) +// Text("Channel: \(node.channel)") +// .font(.footnote) +// } +// } +// if !connected { +// HStack(alignment: .bottom) { let preset = ModemPresets(rawValue: Int(connectedNode?.loRaConfig?.modemPreset ?? 0)) +// LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) +// } +// } +// } +// .frame(maxWidth: .infinity, alignment: .leading) +// } +// } +// } +// .padding([.top, .bottom]) +// } +// } +// .listStyle(.plain) +// .navigationSplitViewColumnWidth(300) +// .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) +// .navigationBarItems(leading: +// MeshtasticLogo() +// ) +// .onAppear { +// if self.bleManager.context == nil { +// self.bleManager.context = context +// } +// } +// } detail: { +// if let node = selection { +// NodeDetail(node: node) +// } else { +// Text("select.node") +// } +// } +// .searchable(text: nodesQuery, prompt: "Find a node") +// } +//}