mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Waypoint form mockup
Additional map fixes Emoji only keyboard and validation for waypoint emoji
This commit is contained in:
parent
8bc645412b
commit
02801ab16e
11 changed files with 266 additions and 39 deletions
|
|
@ -66,6 +66,8 @@
|
|||
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */; };
|
||||
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; };
|
||||
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; };
|
||||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */; };
|
||||
DD964FBF296E76EF007C176F /* WaypointFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormView.swift */; };
|
||||
DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; };
|
||||
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; };
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* IntervalEnums.swift */; };
|
||||
|
|
@ -187,6 +189,8 @@
|
|||
DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = "<group>"; };
|
||||
DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = "<group>"; };
|
||||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = "<group>"; };
|
||||
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = "<group>"; };
|
||||
DD964FBE296E76EF007C176F /* WaypointFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormView.swift; sourceTree = "<group>"; };
|
||||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = "<group>"; };
|
||||
DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
|
||||
DD994B68295F88B60013760A /* IntervalEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntervalEnums.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -273,6 +277,7 @@
|
|||
C9A88B54278B503C00BD810A /* MapViewModule.swift */,
|
||||
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */,
|
||||
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */,
|
||||
DD964FBE296E76EF007C176F /* WaypointFormView.swift */,
|
||||
);
|
||||
path = Map;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -546,6 +551,7 @@
|
|||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */,
|
||||
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */,
|
||||
DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */,
|
||||
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -729,6 +735,7 @@
|
|||
DDCFF601285453A7005FA625 /* localonly.pb.swift in Sources */,
|
||||
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */,
|
||||
DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */,
|
||||
DD964FBF296E76EF007C176F /* WaypointFormView.swift in Sources */,
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */,
|
||||
DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */,
|
||||
|
|
@ -743,6 +750,7 @@
|
|||
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
|
||||
DDC4D568275499A500A4208E /* Persistence.swift in Sources */,
|
||||
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */,
|
||||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */,
|
||||
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
|
||||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */,
|
||||
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -789,9 +789,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! positionPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.positionApp
|
||||
//if destNum != emptyNodeNum {
|
||||
dataMessage.wantResponse = wantResponse
|
||||
//}
|
||||
dataMessage.wantResponse = wantResponse
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio: ToRadio!
|
||||
|
|
@ -809,18 +807,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
|
||||
@objc func positionTimerFired(timer: Timer) {
|
||||
|
||||
// Check for connected node
|
||||
if connectedPeripheral != nil {
|
||||
|
||||
// Send a position out to the mesh if "share location with the mesh" is enabled in settings
|
||||
if userSettings!.provideLocation {
|
||||
|
||||
let success = sendPosition(destNum: connectedPeripheral.num, wantResponse: false)
|
||||
if !success {
|
||||
|
||||
print("Failed to send positon to device")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
74
Meshtastic/Helpers/EmojiOnlyTextField.swift
Normal file
74
Meshtastic/Helpers/EmojiOnlyTextField.swift
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// EmojiKeyboard.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 1/10/23.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
class SwiftUIEmojiTextField: UITextField {
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
}
|
||||
|
||||
func setEmoji() {
|
||||
_ = self.textInputMode
|
||||
}
|
||||
|
||||
override var textInputContextIdentifier: String? {
|
||||
return ""
|
||||
}
|
||||
|
||||
override var textInputMode: UITextInputMode? {
|
||||
for mode in UITextInputMode.activeInputModes {
|
||||
if mode.primaryLanguage == "emoji" {
|
||||
self.keyboardType = .default // do not remove this
|
||||
return mode
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct EmojiOnlyTextField: UIViewRepresentable {
|
||||
@Binding var text: String
|
||||
var placeholder: String = ""
|
||||
|
||||
func makeUIView(context: Context) -> SwiftUIEmojiTextField {
|
||||
let emojiTextField = SwiftUIEmojiTextField()
|
||||
emojiTextField.placeholder = placeholder
|
||||
emojiTextField.text = text
|
||||
emojiTextField.delegate = context.coordinator
|
||||
return emojiTextField
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: SwiftUIEmojiTextField, context: Context) {
|
||||
uiView.text = text
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(parent: self)
|
||||
}
|
||||
|
||||
class Coordinator: NSObject, UITextFieldDelegate {
|
||||
var parent: EmojiOnlyTextField
|
||||
init(parent: EmojiOnlyTextField) {
|
||||
self.parent = parent
|
||||
}
|
||||
func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.parent.text = textField.text ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//struct EmojiContentView: View {
|
||||
//
|
||||
// @State private var text: String = ""
|
||||
//
|
||||
// var body: some View {
|
||||
// EmojiTextField(text: $text, placeholder: "Enter emoji")
|
||||
// }
|
||||
//}
|
||||
|
|
@ -1,6 +1,13 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension Character {
|
||||
var isEmoji: Bool {
|
||||
guard let scalar = unicodeScalars.first else { return false }
|
||||
return scalar.properties.isEmoji && (scalar.value >= 0x203C || unicodeScalars.count > 1)
|
||||
}
|
||||
}
|
||||
|
||||
extension Data {
|
||||
var macAddressString: String {
|
||||
let mac: String = reduce("") {$0 + String(format: "%02x:", $1)}
|
||||
|
|
@ -73,6 +80,10 @@ extension String {
|
|||
return base64url
|
||||
}
|
||||
|
||||
func onlyEmojis() -> Bool {
|
||||
return count > 0 && !contains { !$0.isEmoji }
|
||||
}
|
||||
|
||||
func image(fontSize:CGFloat = 40, bgColor:UIColor = UIColor.clear, imageSize:CGSize? = nil) -> UIImage?
|
||||
{
|
||||
let font = UIFont.systemFont(ofSize: fontSize)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ class LocationHelper: NSObject, ObservableObject {
|
|||
|
||||
// Apple Park
|
||||
static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090)
|
||||
|
||||
static let DefaultAltitude = CLLocationDistance(integerLiteral: 0)
|
||||
static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0)
|
||||
static let DefaultHeading = CLLocationDirection(integerLiteral: 0)
|
||||
|
|
@ -82,6 +81,7 @@ class LocationHelper: NSObject, ObservableObject {
|
|||
super.init()
|
||||
locationManager.delegate = self
|
||||
locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
locationManager.allowsBackgroundLocationUpdates = true
|
||||
locationManager.requestWhenInUseAuthorization()
|
||||
locationManager.startUpdatingLocation()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,5 +43,6 @@ extension PositionEntity {
|
|||
|
||||
extension PositionEntity: MKAnnotation {
|
||||
public var coordinate: CLLocationCoordinate2D { nodeCoordinate! }
|
||||
public var subtitle: String? { time?.formatted() }
|
||||
public var title: String? { nodePosition?.user?.shortName ?? NSLocalizedString("unknown", comment: "Unknown") }
|
||||
public var subtitle: String? { time?.formatted() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1150,10 +1150,14 @@ struct Waypoint {
|
|||
/// Name of the waypoint - max 30 chars
|
||||
var name: String = String()
|
||||
|
||||
///*
|
||||
///
|
||||
/// Description of the waypoint - max 100 chars
|
||||
var description_p: String = String()
|
||||
|
||||
///
|
||||
/// Designator icon for the waypoint in the form of a unicode emoji
|
||||
var emoji: UInt32 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
|
@ -2778,6 +2782,7 @@ extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
5: .same(proto: "locked"),
|
||||
6: .same(proto: "name"),
|
||||
7: .same(proto: "description"),
|
||||
8: .same(proto: "emoji"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -2793,6 +2798,7 @@ extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
case 5: try { try decoder.decodeSingularBoolField(value: &self.locked) }()
|
||||
case 6: try { try decoder.decodeSingularStringField(value: &self.name) }()
|
||||
case 7: try { try decoder.decodeSingularStringField(value: &self.description_p) }()
|
||||
case 8: try { try decoder.decodeSingularFixed32Field(value: &self.emoji) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -2820,6 +2826,9 @@ extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
if !self.description_p.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.description_p, fieldNumber: 7)
|
||||
}
|
||||
if self.emoji != 0 {
|
||||
try visitor.visitSingularFixed32Field(value: self.emoji, fieldNumber: 8)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -2831,6 +2840,7 @@ extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
if lhs.locked != rhs.locked {return false}
|
||||
if lhs.name != rhs.name {return false}
|
||||
if lhs.description_p != rhs.description_p {return false}
|
||||
if lhs.emoji != rhs.emoji {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,18 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
mapView.mapType = mapViewType
|
||||
mapView.setRegion(region, animated: true)
|
||||
mapView.isRotateEnabled = true
|
||||
mapView.isPitchEnabled = true
|
||||
mapView.showsBuildings = true;
|
||||
mapView.addAnnotations(positions)
|
||||
mapView.showsUserLocation = true
|
||||
mapView.setUserTrackingMode(.followWithHeading, animated: true)
|
||||
mapView.showsCompass = true
|
||||
mapView.showsScale = true
|
||||
mapView.isScrollEnabled = true
|
||||
mapView.delegate = context.coordinator
|
||||
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.tapMap(sender:)))
|
||||
mapView.addGestureRecognizer(gestureRecognizer)
|
||||
|
||||
return mapView
|
||||
}
|
||||
|
||||
|
|
@ -33,14 +43,14 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
}
|
||||
|
||||
final class MapCoordinator: NSObject, MKMapViewDelegate {
|
||||
|
||||
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
|
||||
switch annotation {
|
||||
|
||||
case _ as MKClusterAnnotation:
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "nodeGroup")
|
||||
annotationView.markerTintColor = .darkGray
|
||||
annotationView.markerTintColor = .systemRed
|
||||
return annotationView
|
||||
case _ as PositionEntity:
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "Node")
|
||||
|
|
@ -53,5 +63,11 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
default: return nil
|
||||
}
|
||||
}
|
||||
@objc func tapMap(sender: UITapGestureRecognizer) {
|
||||
if sender.state == .ended {
|
||||
//let locationInMap = sender.location(in: control.mapView)
|
||||
//let coordinateSet = control.mapView.convert(locationInMap, toCoordinateFrom: control.mapView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
127
Meshtastic/Views/Map/WaypointFormView.swift
Normal file
127
Meshtastic/Views/Map/WaypointFormView.swift
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// WaypointFormView.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 1/10/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct WaypointFormView: View {
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var id: Int32?
|
||||
@State private var name: String = ""
|
||||
@State private var description: String = ""
|
||||
@State private var emoji: String = ""
|
||||
@FocusState private var emojiIsFocused: Bool
|
||||
@State private var latitude: Double = 0.0
|
||||
@State private var longitude: Double = 0.0
|
||||
@State private var expire: Date = Date.now.addingTimeInterval(60 * 60)
|
||||
@State private var locked: Bool = false
|
||||
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
Form {
|
||||
|
||||
Section(header: Text("Waypoint")) {
|
||||
Text("Lat/Long ") + Text(" \(String(latitude) + "," + String(longitude))").foregroundColor(Color.gray)
|
||||
HStack {
|
||||
Text("Name")
|
||||
Spacer()
|
||||
TextField(
|
||||
"Name",
|
||||
text: $name
|
||||
)
|
||||
.foregroundColor(Color.gray)
|
||||
.onChange(of: name, perform: { value in
|
||||
let totalBytes = name.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 30 {
|
||||
let firstNBytes = Data(name.utf8.prefix(30))
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
// Set the name back to the last place where it was the right size
|
||||
name = maxBytesString
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
HStack {
|
||||
Text("Description")
|
||||
Spacer()
|
||||
TextField(
|
||||
"Description",
|
||||
text: $description,
|
||||
axis: .vertical
|
||||
)
|
||||
.foregroundColor(Color.gray)
|
||||
.onChange(of: description, perform: { value in
|
||||
let totalBytes = description.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 100 {
|
||||
let firstNBytes = Data(description.utf8.prefix(100))
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
// Set the name back to the last place where it was the right size
|
||||
description = maxBytesString
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
HStack {
|
||||
Text("Emoji")
|
||||
Spacer()
|
||||
EmojiOnlyTextField(text: $emoji, placeholder: "emoji")
|
||||
.font(.title)
|
||||
.focused($emojiIsFocused)
|
||||
.onChange(of: emoji) { value in
|
||||
|
||||
// If you have anything other than emojis in your string make it empty
|
||||
if !value.onlyEmojis() {
|
||||
emoji = ""
|
||||
}
|
||||
// If a second emoji is entered delete the first one
|
||||
if value.count >= 1 {
|
||||
|
||||
if value.count > 1 {
|
||||
let index = value.index(value.startIndex, offsetBy: 1)
|
||||
emoji = String(value[index])
|
||||
}
|
||||
emojiIsFocused = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Toggle(isOn: $locked) {
|
||||
Label("Locked", systemImage: "lock")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
DatePicker("Expire", selection: $expire, in: Date.now...)
|
||||
.datePickerStyle(.compact)
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("cancel", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ struct NodeDetail: View {
|
|||
@State var satsInView = 0
|
||||
@State private var showingShutdownConfirm: Bool = false
|
||||
@State private var showingRebootConfirm: Bool = false
|
||||
@State var presentingWaypointForm = false
|
||||
|
||||
var node: NodeInfoEntity
|
||||
|
||||
|
|
@ -35,7 +36,10 @@ struct NodeDetail: View {
|
|||
ZStack {
|
||||
let annotations = node.positions?.array as! [PositionEntity]
|
||||
ZStack {
|
||||
MapViewSwiftUI(positions: annotations, region: MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)), mapViewType: mapType)
|
||||
MapViewSwiftUI(positions: annotations, region: MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)
|
||||
//MKCoordinateSpan(latitudeDelta: 0.16405544070813249, longitudeDelta: 0.1232528799585566)
|
||||
|
||||
), mapViewType: mapType)
|
||||
VStack {
|
||||
Spacer()
|
||||
Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ")
|
||||
|
|
@ -129,7 +133,6 @@ struct NodeDetail: View {
|
|||
.symbolRenderingMode(.hierarchical)
|
||||
Text("user").font(.title)+Text(":").font(.title)
|
||||
}
|
||||
//Text(node.user?.userId ?? "??????").font(.title).foregroundColor(.gray)
|
||||
Text("!\(String(format:"%02x", node.num))")
|
||||
.font(.title).foregroundColor(.gray)
|
||||
}
|
||||
|
|
@ -173,7 +176,6 @@ struct NodeDetail: View {
|
|||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
Divider()
|
||||
|
||||
} else {
|
||||
|
|
@ -194,7 +196,6 @@ struct NodeDetail: View {
|
|||
.font(.callout).fixedSize()
|
||||
}
|
||||
}
|
||||
.padding(5)
|
||||
|
||||
if node.snr > 0 {
|
||||
Divider()
|
||||
|
|
@ -210,19 +211,13 @@ struct NodeDetail: View {
|
|||
.foregroundColor(.gray)
|
||||
.fixedSize()
|
||||
}
|
||||
.padding(5)
|
||||
}
|
||||
|
||||
if node.telemetries?.count ?? 0 >= 1 {
|
||||
|
||||
let mostRecent = node.telemetries?.lastObject as! TelemetryEntity
|
||||
|
||||
Divider()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
|
||||
BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel))
|
||||
|
||||
if mostRecent.voltage > 0 {
|
||||
|
||||
Text(String(format: "%.2f", mostRecent.voltage) + " V")
|
||||
|
|
@ -230,14 +225,11 @@ struct NodeDetail: View {
|
|||
.foregroundColor(.gray)
|
||||
.fixedSize()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
HStack(alignment: .center) {
|
||||
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
Image(systemName: "person")
|
||||
|
|
@ -260,7 +252,6 @@ struct NodeDetail: View {
|
|||
Text(String(node.num)).font(.title3).foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.padding(5)
|
||||
Divider()
|
||||
HStack {
|
||||
Image(systemName: "globe")
|
||||
|
|
@ -270,7 +261,7 @@ struct NodeDetail: View {
|
|||
Text("MAC Address: ")
|
||||
Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray)
|
||||
}
|
||||
.padding([.bottom], 0)
|
||||
.padding([.bottom], 10)
|
||||
Divider()
|
||||
}
|
||||
|
||||
|
|
@ -374,15 +365,17 @@ struct NodeDetail: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(5)
|
||||
}
|
||||
} }
|
||||
}
|
||||
//.offset( y:-40)
|
||||
}
|
||||
.edgesIgnoringSafeArea([.leading, .trailing])
|
||||
.sheet(isPresented: $presentingWaypointForm ) {//, onDismiss: didDismissSheet) {
|
||||
|
||||
WaypointFormView()
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
}
|
||||
.navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "")), displayMode: .inline)
|
||||
.padding(.bottom, 10)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -250,16 +250,10 @@ struct Channels: View {
|
|||
let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
|
||||
if adminMessageId > 0 {
|
||||
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save.
|
||||
|
||||
self.isPresentingEditView = false
|
||||
channelName = ""
|
||||
hasChanges = false
|
||||
// Would rather send a getChannel but I can't seem serialize it properly yet
|
||||
bleManager.getChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
//bleManager.sendWantConfig()
|
||||
}
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue