mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Turn new messaging views back on
This commit is contained in:
parent
a8917c9aa0
commit
7416676c69
9 changed files with 1041 additions and 1018 deletions
|
|
@ -65,12 +65,15 @@
|
|||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="fetchedProperty" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="UserEntity"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="MessageEntity" positionX="-36" positionY="63" width="128" height="164"/>
|
||||
<element name="MyInfoEntity" positionX="-18" positionY="81" width="128" height="149"/>
|
||||
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="149"/>
|
||||
<element name="PositionEntity" positionX="-54" positionY="54" width="128" height="119"/>
|
||||
<element name="UserEntity" positionX="0" positionY="144" width="128" height="179"/>
|
||||
<element name="UserEntity" positionX="0" positionY="144" width="128" height="200"/>
|
||||
</elements>
|
||||
</model>
|
||||
|
|
@ -34,14 +34,16 @@ class PersistenceController {
|
|||
init(inMemory: Bool = false) {
|
||||
|
||||
container = NSPersistentContainer(name: "Meshtastic")
|
||||
|
||||
if inMemory {
|
||||
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
|
||||
}
|
||||
|
||||
container.loadPersistentStores(completionHandler: { (_, error) in
|
||||
|
||||
// Merge policy that favors in memory data over data in the db
|
||||
self.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||
//self.container.viewContext.automaticallyMergesChangesFromParent = true
|
||||
self.container.viewContext.automaticallyMergesChangesFromParent = true
|
||||
|
||||
if let error = error as NSError? {
|
||||
|
||||
|
|
|
|||
|
|
@ -19,21 +19,21 @@ struct ContentView: View {
|
|||
var body: some View {
|
||||
|
||||
TabView(selection: $selection) {
|
||||
// Contacts()
|
||||
// .tabItem {
|
||||
// Label("Messages", systemImage: "text.bubble")
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// .symbolVariant(.none)
|
||||
//
|
||||
// }
|
||||
// .tag(Tab.contacts)
|
||||
Channels()
|
||||
.tabItem {
|
||||
Label("Messages", systemImage: "text.bubble")
|
||||
Contacts()
|
||||
.tabItem {
|
||||
Label("Messages", systemImage: "text.bubble")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.symbolVariant(.none)
|
||||
}
|
||||
.tag(Tab.messages)
|
||||
.symbolVariant(.none)
|
||||
|
||||
}
|
||||
.tag(Tab.contacts)
|
||||
// Channels()
|
||||
// .tabItem {
|
||||
// Label("Messages", systemImage: "text.bubble")
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// .symbolVariant(.none)
|
||||
// }
|
||||
// .tag(Tab.messages)
|
||||
Connect()
|
||||
.tabItem {
|
||||
Label("Bluetooth", systemImage: "dot.radiowaves.left.and.right")
|
||||
|
|
@ -41,13 +41,13 @@ struct ContentView: View {
|
|||
.symbolVariant(.none)
|
||||
}
|
||||
.tag(Tab.ble)
|
||||
NodeList()
|
||||
.tabItem {
|
||||
Label("Nodes", systemImage: "flipphone")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.symbolVariant(.none)
|
||||
}
|
||||
.tag(Tab.nodes)
|
||||
// NodeList()
|
||||
// .tabItem {
|
||||
// Label("Nodes", systemImage: "flipphone")
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// .symbolVariant(.none)
|
||||
// }
|
||||
// .tag(Tab.nodes)
|
||||
NodeMap()
|
||||
.tabItem {
|
||||
Label("Mesh Map", systemImage: "map")
|
||||
|
|
|
|||
|
|
@ -1,44 +1,44 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreBluetooth
|
||||
|
||||
struct Channels: View {
|
||||
|
||||
@State private var isShowingDetailView = true
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationView {
|
||||
|
||||
NavigationLink(destination: Messages(), isActive: $isShowingDetailView) {
|
||||
|
||||
List {
|
||||
|
||||
HStack {
|
||||
|
||||
Image(systemName: "megaphone.fill")
|
||||
.font(.largeTitle)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.padding(.trailing)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
Text("All - Broadcast")
|
||||
.font(.largeTitle)
|
||||
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
.navigationTitle("Contacts")
|
||||
}
|
||||
.navigationViewStyle(DoubleColumnNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageList_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
Channels()
|
||||
}
|
||||
}
|
||||
}
|
||||
//import Foundation
|
||||
//import SwiftUI
|
||||
//import CoreBluetooth
|
||||
//
|
||||
//struct Channels: View {
|
||||
//
|
||||
// @State private var isShowingDetailView = true
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// NavigationView {
|
||||
//
|
||||
// NavigationLink(destination: Messages(), isActive: $isShowingDetailView) {
|
||||
//
|
||||
// List {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// Image(systemName: "megaphone.fill")
|
||||
// .font(.largeTitle)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// .padding(.trailing)
|
||||
// .foregroundColor(.accentColor)
|
||||
//
|
||||
// Text("All - Broadcast")
|
||||
// .font(.largeTitle)
|
||||
//
|
||||
// }.padding()
|
||||
// }
|
||||
// }
|
||||
// .navigationTitle("Contacts")
|
||||
// }
|
||||
// .navigationViewStyle(DoubleColumnNavigationViewStyle())
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//struct MessageList_Previews: PreviewProvider {
|
||||
//
|
||||
// static var previews: some View {
|
||||
// Group {
|
||||
// Channels()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,141 +1,142 @@
|
|||
////
|
||||
//// Contacts.swift
|
||||
//// MeshtasticClient
|
||||
////
|
||||
//// Created by Garth Vander Houwen on 12/21/21.
|
||||
////
|
||||
//
|
||||
//import SwiftUI
|
||||
// Contacts.swift
|
||||
// MeshtasticClient
|
||||
//
|
||||
//struct Contacts: View {
|
||||
// Created by Garth Vander Houwen on 12/21/21.
|
||||
//
|
||||
// @Environment(\.managedObjectContext) var context
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
//
|
||||
// @FetchRequest(
|
||||
// sortDescriptors: [NSSortDescriptor(key: "longName", ascending: true)],
|
||||
// animation: .default)
|
||||
//
|
||||
// private var users: FetchedResults<UserEntity>
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// NavigationView {
|
||||
//
|
||||
// List(users) { (user: UserEntity) in
|
||||
//
|
||||
// if user.receivedMessages?.count ?? 0 > 0 {
|
||||
//
|
||||
// let currentUserNum = self.bleManager.connectedPeripheral != nil ? self.bleManager.connectedPeripheral.num : 0
|
||||
//
|
||||
// let mostRecentBC = user.receivedMessages?.array.last as! MessageEntity
|
||||
//
|
||||
// let mostRecentDM = user.receivedMessages?.array.last(where: {($0 as! MessageEntity).toUser!.num == currentUserNum }) as? MessageEntity
|
||||
//
|
||||
// let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64(mostRecentDM?.messageTimestamp ?? mostRecentBC.messageTimestamp)))
|
||||
// let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
|
||||
// let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
|
||||
//
|
||||
// if user.num == bleManager.broadcastNodeNum {//user.num != currentUserNum && (user.num == bleManager.broadcastNodeNum || mostRecentDM != nil) {
|
||||
//
|
||||
// NavigationLink(destination: UserMessageList(user: user)) {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// CircleText(text: user.shortName ?? "???", color: Color.blue)
|
||||
// }
|
||||
// .padding([.leading, .trailing])
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// Text(user.longName ?? "Unknown").font(.headline).fixedSize()
|
||||
// }
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// if lastMessageDay == currentDay {
|
||||
//
|
||||
// Text(lastMessageTime, style: .time )
|
||||
// .font(.caption)
|
||||
// .foregroundColor(.gray)
|
||||
//
|
||||
// } else if lastMessageDay == (currentDay - 1) {
|
||||
//
|
||||
// Text("Yesterday")
|
||||
// .font(.callout)
|
||||
// .foregroundColor(.gray)
|
||||
//
|
||||
// } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
|
||||
//
|
||||
// Text(lastMessageTime, style: .date)
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// Text(lastMessageTime, style: .date)
|
||||
// }
|
||||
// }.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
// }
|
||||
// .listRowSeparator(.hidden).frame(height: 5)
|
||||
//
|
||||
// HStack(alignment: .top) {
|
||||
// Text(mostRecentDM != nil ? mostRecentDM?.messagePayload as! String : (mostRecentBC.messagePayload ?? "Unknown" ))
|
||||
// .frame(height: 60)
|
||||
// .truncationMode(.tail)
|
||||
// .foregroundColor(Color.gray)
|
||||
// .frame(maxWidth: .infinity, alignment: .leading)
|
||||
// }
|
||||
// }.padding(.top, 15)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// } else if false {// self.bleManager.connectedPeripheral == nil || ((self.bleManager.connectedPeripheral != nil ? self.bleManager.connectedPeripheral.num : 0) != user.num) {
|
||||
//
|
||||
// NavigationLink(destination: UserMessageList(user: user)) {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// CircleText(text: user.shortName ?? "???", color: Color.blue)
|
||||
// }
|
||||
// .padding(.trailing)
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// Text(user.longName ?? "Unknown").font(.headline).fixedSize()
|
||||
// }
|
||||
//
|
||||
// VStack {
|
||||
// Text(" ")
|
||||
// }
|
||||
// .frame(maxWidth: .infinity, alignment: .trailing)
|
||||
// }
|
||||
// .listRowSeparator(.hidden).frame(height: 5)
|
||||
// }
|
||||
// }.padding()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .navigationTitle("Contacts")
|
||||
// .navigationBarTitleDisplayMode(.inline)
|
||||
// }
|
||||
// .listStyle(PlainListStyle())
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//struct Contacts_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// Contacts()
|
||||
// }
|
||||
//}
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct Contacts: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(key: "longName", ascending: true)],
|
||||
animation: .default)
|
||||
|
||||
private var users: FetchedResults<UserEntity>
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationView {
|
||||
|
||||
List(users) { (user: UserEntity) in
|
||||
|
||||
if user.receivedMessages?.count ?? 0 > 0 {
|
||||
|
||||
let currentUserNum = self.bleManager.connectedPeripheral != nil ? self.bleManager.connectedPeripheral.num : 0
|
||||
|
||||
let mostRecentBC = user.receivedMessages?.array.last as! MessageEntity
|
||||
|
||||
let mostRecentDM = user.receivedMessages?.array.last(where: {($0 as! MessageEntity).toUser!.num == currentUserNum }) as? MessageEntity
|
||||
|
||||
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64(mostRecentDM?.messageTimestamp ?? mostRecentBC.messageTimestamp)))
|
||||
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
|
||||
let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
|
||||
|
||||
if user.num == bleManager.broadcastNodeNum {//user.num != currentUserNum && (user.num == bleManager.broadcastNodeNum || mostRecentDM != nil) {
|
||||
|
||||
NavigationLink(destination: UserMessageList(user: user)
|
||||
.environment(\.managedObjectContext, self.context)) {
|
||||
|
||||
HStack {
|
||||
|
||||
VStack {
|
||||
|
||||
CircleText(text: user.shortName ?? "???", color: Color.blue)
|
||||
}
|
||||
.padding([.leading, .trailing])
|
||||
|
||||
VStack {
|
||||
|
||||
HStack {
|
||||
|
||||
VStack {
|
||||
|
||||
Text(user.longName ?? "Unknown").font(.headline).fixedSize()
|
||||
}
|
||||
|
||||
VStack {
|
||||
|
||||
if lastMessageDay == currentDay {
|
||||
|
||||
Text(lastMessageTime, style: .time )
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
} else if lastMessageDay == (currentDay - 1) {
|
||||
|
||||
Text("Yesterday")
|
||||
.font(.callout)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
|
||||
|
||||
Text(lastMessageTime, style: .date)
|
||||
|
||||
} else {
|
||||
|
||||
Text(lastMessageTime, style: .date)
|
||||
}
|
||||
}.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.listRowSeparator(.hidden).frame(height: 5)
|
||||
|
||||
HStack(alignment: .top) {
|
||||
Text(mostRecentDM != nil ? mostRecentDM?.messagePayload as! String : (mostRecentBC.messagePayload ?? "Unknown" ))
|
||||
.frame(height: 60)
|
||||
.truncationMode(.tail)
|
||||
.foregroundColor(Color.gray)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}.padding(.top, 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if false {// self.bleManager.connectedPeripheral == nil || ((self.bleManager.connectedPeripheral != nil ? self.bleManager.connectedPeripheral.num : 0) != user.num) {
|
||||
|
||||
NavigationLink(destination: UserMessageList(user: user)) {
|
||||
|
||||
HStack {
|
||||
|
||||
VStack {
|
||||
|
||||
CircleText(text: user.shortName ?? "???", color: Color.blue)
|
||||
}
|
||||
.padding(.trailing)
|
||||
|
||||
VStack {
|
||||
|
||||
HStack {
|
||||
|
||||
VStack {
|
||||
|
||||
Text(user.longName ?? "Unknown").font(.headline).fixedSize()
|
||||
}
|
||||
|
||||
VStack {
|
||||
Text(" ")
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.listRowSeparator(.hidden).frame(height: 5)
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Contacts")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct Contacts_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Contacts()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,204 +1,204 @@
|
|||
import SwiftUI
|
||||
import MapKit
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
|
||||
struct Messages: View {
|
||||
|
||||
enum Field: Hashable {
|
||||
case messageText
|
||||
}
|
||||
|
||||
// CoreData
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(keyPath: \MessageEntity.messageTimestamp, ascending: true)],
|
||||
animation: .default)
|
||||
var messages: FetchedResults<MessageEntity>
|
||||
|
||||
// Keyboard State
|
||||
@State var typingMessage: String = ""
|
||||
@State private var totalBytes = 0
|
||||
private var maxbytes = 228
|
||||
@State private var lastTypingMessage = ""
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
@State var showDeleteMessageAlert = false
|
||||
@State private var deleteMessageId: Int64 = 0
|
||||
|
||||
public var broadcastNodeId: UInt32 = 4294967295
|
||||
|
||||
var body: some View {
|
||||
|
||||
Text("\(messages.count) Messages").font(.caption)
|
||||
GeometryReader { bounds in
|
||||
|
||||
VStack {
|
||||
|
||||
ScrollViewReader { scrollView in
|
||||
|
||||
if self.messages.count > 0 {
|
||||
|
||||
ScrollView {
|
||||
|
||||
ForEach(messages) { message in
|
||||
|
||||
HStack(alignment: .top) {
|
||||
let currentUser: Bool = (bleManager.connectedPeripheral == nil) ? false : ((bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == message.fromUser?.num) ? true : false )
|
||||
|
||||
CircleText(text: (message.fromUser?.shortName ?? "???"), color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5)
|
||||
.gesture(LongPressGesture(minimumDuration: 2)
|
||||
.onEnded {_ in
|
||||
print(messages)
|
||||
print("I want to delete message: \(message.messageId)")
|
||||
self.showDeleteMessageAlert = true
|
||||
self.deleteMessageId = message.messageId
|
||||
|
||||
print(deleteMessageId)
|
||||
})
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(message.messagePayload ?? "EMPTY MESSAGE")
|
||||
.textSelection(.enabled)
|
||||
.padding(10)
|
||||
.foregroundColor(.white)
|
||||
.background(currentUser ? Color.blue : Color(.darkGray))
|
||||
.cornerRadius(10)
|
||||
HStack(spacing: 4) {
|
||||
|
||||
let time = Int32(message.messageTimestamp)
|
||||
let messageDate = Date(timeIntervalSince1970: TimeInterval(time))
|
||||
|
||||
if time != 0 {
|
||||
Text(messageDate, style: .date).font(.caption2).foregroundColor(.gray)
|
||||
Text(messageDate, style: .time).font(.caption2).foregroundColor(.gray)
|
||||
} else {
|
||||
Text("Unknown").font(.caption2).foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.alert(isPresented: $showDeleteMessageAlert) {
|
||||
Alert(title: Text("Are you sure you want to delete this message?"), message: Text("This action is permanent."),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
print("OK button tapped")
|
||||
if deleteMessageId > 0 {
|
||||
|
||||
let message = messages.first(where: { $0.messageId == deleteMessageId })
|
||||
|
||||
context.delete(message!)
|
||||
do {
|
||||
try context.save()
|
||||
|
||||
deleteMessageId = 0
|
||||
|
||||
} catch {
|
||||
print("Failed to delete message \(deleteMessageId)")
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
}
|
||||
.onChange(of: messages.count, perform: { newValue in
|
||||
|
||||
if messages.count > 0 {
|
||||
|
||||
scrollView.scrollTo(messages[messages.count-1].id, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
)
|
||||
.onAppear(perform: {
|
||||
|
||||
self.bleManager.context = context
|
||||
if messages.count > 0 {
|
||||
|
||||
scrollView.scrollTo(messages[messages.count-1].id, anchor: .bottom)
|
||||
}
|
||||
})
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
HStack(alignment: .top) {
|
||||
|
||||
ZStack {
|
||||
|
||||
let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0)
|
||||
TextEditor(text: $typingMessage)
|
||||
.onChange(of: typingMessage, perform: { value in
|
||||
|
||||
let size = value.utf8.count
|
||||
totalBytes = size
|
||||
if totalBytes <= maxbytes {
|
||||
// Allow the user to type
|
||||
lastTypingMessage = typingMessage
|
||||
} else {
|
||||
// Set the message back and remove the bytes over the count
|
||||
self.typingMessage = lastTypingMessage
|
||||
}
|
||||
})
|
||||
.keyboardType(kbType!)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .keyboard) {
|
||||
|
||||
Button("Dismiss Keyboard") {
|
||||
focusedField = nil
|
||||
}
|
||||
.font(.subheadline)
|
||||
|
||||
Spacer()
|
||||
|
||||
ProgressView("Bytes: \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes))
|
||||
.frame(width: 130)
|
||||
.padding(5)
|
||||
.font(.subheadline)
|
||||
.accentColor(.accentColor)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.focused($focusedField, equals: .messageText)
|
||||
.multilineTextAlignment(.leading)
|
||||
.frame(minHeight: bounds.size.height / 4, maxHeight: bounds.size.height / 4)
|
||||
|
||||
Text(typingMessage).opacity(0).padding(.all, 0)
|
||||
|
||||
}
|
||||
.overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1))
|
||||
.padding(.bottom, 15)
|
||||
|
||||
Button(action: {
|
||||
if self.bleManager.sendMessage(message: typingMessage, toUserNum: Int64(self.bleManager.broadcastNodeNum)) {
|
||||
typingMessage = ""
|
||||
focusedField = nil
|
||||
}
|
||||
|
||||
}) {
|
||||
Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.blue)
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.all, 15)
|
||||
}
|
||||
}
|
||||
.navigationTitle("All - Broadcast")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(
|
||||
bluetoothOn: self.bleManager.isSwitchedOn,
|
||||
deviceConnected: self.bleManager.connectedPeripheral != nil,
|
||||
name: (self.bleManager.connectedPeripheral != nil) ? self.bleManager.connectedPeripheral.shortName : "???")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
//import SwiftUI
|
||||
//import MapKit
|
||||
//import Foundation
|
||||
//import CoreLocation
|
||||
//
|
||||
//struct Messages: View {
|
||||
//
|
||||
// enum Field: Hashable {
|
||||
// case messageText
|
||||
// }
|
||||
//
|
||||
// // CoreData
|
||||
// @Environment(\.managedObjectContext) var context
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
//
|
||||
// @FetchRequest(
|
||||
// sortDescriptors: [NSSortDescriptor(keyPath: \MessageEntity.messageTimestamp, ascending: true)],
|
||||
// animation: .default)
|
||||
// var messages: FetchedResults<MessageEntity>
|
||||
//
|
||||
// // Keyboard State
|
||||
// @State var typingMessage: String = ""
|
||||
// @State private var totalBytes = 0
|
||||
// private var maxbytes = 228
|
||||
// @State private var lastTypingMessage = ""
|
||||
// @FocusState private var focusedField: Field?
|
||||
//
|
||||
// @State var showDeleteMessageAlert = false
|
||||
// @State private var deleteMessageId: Int64 = 0
|
||||
//
|
||||
// public var broadcastNodeId: UInt32 = 4294967295
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// Text("\(messages.count) Messages").font(.caption)
|
||||
// GeometryReader { bounds in
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// ScrollViewReader { scrollView in
|
||||
//
|
||||
// if self.messages.count > 0 {
|
||||
//
|
||||
// ScrollView {
|
||||
//
|
||||
// ForEach(messages) { message in
|
||||
//
|
||||
// HStack(alignment: .top) {
|
||||
// let currentUser: Bool = (bleManager.connectedPeripheral == nil) ? false : ((bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == message.fromUser?.num) ? true : false )
|
||||
//
|
||||
// CircleText(text: (message.fromUser?.shortName ?? "???"), color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5)
|
||||
// .gesture(LongPressGesture(minimumDuration: 2)
|
||||
// .onEnded {_ in
|
||||
// print(messages)
|
||||
// print("I want to delete message: \(message.messageId)")
|
||||
// self.showDeleteMessageAlert = true
|
||||
// self.deleteMessageId = message.messageId
|
||||
//
|
||||
// print(deleteMessageId)
|
||||
// })
|
||||
//
|
||||
// VStack(alignment: .leading) {
|
||||
// Text(message.messagePayload ?? "EMPTY MESSAGE")
|
||||
// .textSelection(.enabled)
|
||||
// .padding(10)
|
||||
// .foregroundColor(.white)
|
||||
// .background(currentUser ? Color.blue : Color(.darkGray))
|
||||
// .cornerRadius(10)
|
||||
// HStack(spacing: 4) {
|
||||
//
|
||||
// let time = Int32(message.messageTimestamp)
|
||||
// let messageDate = Date(timeIntervalSince1970: TimeInterval(time))
|
||||
//
|
||||
// if time != 0 {
|
||||
// Text(messageDate, style: .date).font(.caption2).foregroundColor(.gray)
|
||||
// Text(messageDate, style: .time).font(.caption2).foregroundColor(.gray)
|
||||
// } else {
|
||||
// Text("Unknown").font(.caption2).foregroundColor(.gray)
|
||||
// }
|
||||
// }
|
||||
// .padding(.bottom, 10)
|
||||
// }
|
||||
// Spacer()
|
||||
// }
|
||||
// .alert(isPresented: $showDeleteMessageAlert) {
|
||||
// Alert(title: Text("Are you sure you want to delete this message?"), message: Text("This action is permanent."),
|
||||
// primaryButton: .destructive(Text("Delete")) {
|
||||
// print("OK button tapped")
|
||||
// if deleteMessageId > 0 {
|
||||
//
|
||||
// let message = messages.first(where: { $0.messageId == deleteMessageId })
|
||||
//
|
||||
// context.delete(message!)
|
||||
// do {
|
||||
// try context.save()
|
||||
//
|
||||
// deleteMessageId = 0
|
||||
//
|
||||
// } catch {
|
||||
// print("Failed to delete message \(deleteMessageId)")
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// },
|
||||
// secondaryButton: .cancel()
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// .onChange(of: messages.count, perform: { newValue in
|
||||
//
|
||||
// if messages.count > 0 {
|
||||
//
|
||||
// scrollView.scrollTo(messages[messages.count-1].id, anchor: .bottom)
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// .onAppear(perform: {
|
||||
//
|
||||
// self.bleManager.context = context
|
||||
// if messages.count > 0 {
|
||||
//
|
||||
// scrollView.scrollTo(messages[messages.count-1].id, anchor: .bottom)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// .padding(.horizontal)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// HStack(alignment: .top) {
|
||||
//
|
||||
// ZStack {
|
||||
//
|
||||
// let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0)
|
||||
// TextEditor(text: $typingMessage)
|
||||
// .onChange(of: typingMessage, perform: { value in
|
||||
//
|
||||
// let size = value.utf8.count
|
||||
// totalBytes = size
|
||||
// if totalBytes <= maxbytes {
|
||||
// // Allow the user to type
|
||||
// lastTypingMessage = typingMessage
|
||||
// } else {
|
||||
// // Set the message back and remove the bytes over the count
|
||||
// self.typingMessage = lastTypingMessage
|
||||
// }
|
||||
// })
|
||||
// .keyboardType(kbType!)
|
||||
// .toolbar {
|
||||
// ToolbarItemGroup(placement: .keyboard) {
|
||||
//
|
||||
// Button("Dismiss Keyboard") {
|
||||
// focusedField = nil
|
||||
// }
|
||||
// .font(.subheadline)
|
||||
//
|
||||
// Spacer()
|
||||
//
|
||||
// ProgressView("Bytes: \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes))
|
||||
// .frame(width: 130)
|
||||
// .padding(5)
|
||||
// .font(.subheadline)
|
||||
// .accentColor(.accentColor)
|
||||
// }
|
||||
// }
|
||||
// .padding(.horizontal, 8)
|
||||
// .focused($focusedField, equals: .messageText)
|
||||
// .multilineTextAlignment(.leading)
|
||||
// .frame(minHeight: bounds.size.height / 4, maxHeight: bounds.size.height / 4)
|
||||
//
|
||||
// Text(typingMessage).opacity(0).padding(.all, 0)
|
||||
//
|
||||
// }
|
||||
// .overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1))
|
||||
// .padding(.bottom, 15)
|
||||
//
|
||||
// Button(action: {
|
||||
// if self.bleManager.sendMessage(message: typingMessage, toUserNum: Int64(self.bleManager.broadcastNodeNum)) {
|
||||
// typingMessage = ""
|
||||
// focusedField = nil
|
||||
// }
|
||||
//
|
||||
// }) {
|
||||
// Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.blue)
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// .padding(.all, 15)
|
||||
// }
|
||||
// }
|
||||
// .navigationTitle("All - Broadcast")
|
||||
// .navigationBarTitleDisplayMode(.inline)
|
||||
// .navigationBarItems(trailing:
|
||||
//
|
||||
// ZStack {
|
||||
//
|
||||
// ConnectedDevice(
|
||||
// bluetoothOn: self.bleManager.isSwitchedOn,
|
||||
// deviceConnected: self.bleManager.connectedPeripheral != nil,
|
||||
// name: (self.bleManager.connectedPeripheral != nil) ? self.bleManager.connectedPeripheral.shortName : "???")
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,206 +1,206 @@
|
|||
////
|
||||
//// UserMessageList.swift
|
||||
//// MeshtasticClient
|
||||
////
|
||||
//// Created by Garth Vander Houwen on 12/24/21.
|
||||
////
|
||||
//
|
||||
//import SwiftUI
|
||||
//import CoreData
|
||||
// UserMessageList.swift
|
||||
// MeshtasticClient
|
||||
//
|
||||
//struct UserMessageList: View {
|
||||
// Created by Garth Vander Houwen on 12/24/21.
|
||||
//
|
||||
// @Environment(\.managedObjectContext) var context
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
//
|
||||
// enum Field: Hashable {
|
||||
// case messageText
|
||||
// }
|
||||
// // Keyboard State
|
||||
// @State var typingMessage: String = ""
|
||||
// @State private var totalBytes = 0
|
||||
// var maxbytes = 228
|
||||
// @State var lastTypingMessage = ""
|
||||
// @FocusState var focusedField: Field?
|
||||
//
|
||||
// var user: UserEntity
|
||||
//
|
||||
// @State var showDeleteMessageAlert = false
|
||||
// @State private var deleteMessageId: Int64 = 0
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
//// HStack {
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// // List {
|
||||
//
|
||||
// ScrollViewReader { _ in
|
||||
//
|
||||
// ScrollView {
|
||||
//
|
||||
// if user.receivedMessages?.count ?? 0 > 0 {
|
||||
//
|
||||
// ForEach( user.receivedMessages?.array as! [MessageEntity], id: \.self) { (message: MessageEntity) in
|
||||
//
|
||||
// // HStack {
|
||||
// let currentUser: Bool = (bleManager.connectedPeripheral == nil) ? false : ((bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == message.fromUser?.num) ? true : false )
|
||||
//
|
||||
//
|
||||
// if message.toUser!.num == Int64(bleManager.broadcastNodeNum) || ((bleManager.connectedPeripheral) != nil && bleManager.connectedPeripheral.num == message.fromUser?.num) ? true : true {
|
||||
//
|
||||
//
|
||||
// HStack (alignment: .top) {
|
||||
//
|
||||
// if currentUser { Spacer(minLength:50) }
|
||||
//
|
||||
// if !currentUser {
|
||||
//
|
||||
// CircleText(text: (message.fromUser?.shortName ?? "???"), color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5)
|
||||
// .gesture(LongPressGesture(minimumDuration: 2).onEnded {_ in
|
||||
//
|
||||
// print("I want to delete message: \(message.messageId)")
|
||||
// self.showDeleteMessageAlert = true
|
||||
// self.deleteMessageId = message.messageId
|
||||
// print(deleteMessageId)
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// VStack(alignment: currentUser ? .trailing : .leading) {
|
||||
//
|
||||
// Text(message.messagePayload ?? "EMPTY MESSAGE")
|
||||
// .textSelection(.enabled)
|
||||
// .padding(10)
|
||||
// .foregroundColor(.white)
|
||||
// .background(currentUser ? Color.blue : Color(.darkGray))
|
||||
// .cornerRadius(15)
|
||||
//
|
||||
// HStack(spacing: 4) {
|
||||
//
|
||||
// let time = Int32(message.messageTimestamp)
|
||||
// let messageDate = Date(timeIntervalSince1970: TimeInterval(time))
|
||||
//
|
||||
// if time != 0 {
|
||||
// Text(messageDate, style: .date).font(.caption2).foregroundColor(.gray)
|
||||
// Text(messageDate, style: .time).font(.caption2).foregroundColor(.gray)
|
||||
// } else {
|
||||
// Text("Unknown").font(.caption2).foregroundColor(.gray)
|
||||
// }
|
||||
// }
|
||||
// .padding(.bottom, 10)
|
||||
// }
|
||||
// if !currentUser {
|
||||
// Spacer(minLength:50)
|
||||
// }
|
||||
// }
|
||||
// .padding(.trailing)
|
||||
// .frame(maxWidth: .infinity)
|
||||
// }
|
||||
// // }
|
||||
// }
|
||||
// .listRowSeparator(.hidden)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // }
|
||||
// // .padding(.top)
|
||||
//
|
||||
// HStack(alignment: .top) {
|
||||
//
|
||||
// ZStack {
|
||||
//
|
||||
// let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0)
|
||||
// TextEditor(text: $typingMessage)
|
||||
// .onChange(of: typingMessage, perform: { value in
|
||||
//
|
||||
// let size = value.utf8.count
|
||||
// totalBytes = size
|
||||
// if totalBytes <= maxbytes {
|
||||
// // Allow the user to type
|
||||
// lastTypingMessage = typingMessage
|
||||
// } else {
|
||||
// // Set the message back and remove the bytes over the count
|
||||
// self.typingMessage = lastTypingMessage
|
||||
// }
|
||||
// })
|
||||
// .keyboardType(kbType!)
|
||||
// .toolbar {
|
||||
// ToolbarItemGroup(placement: .keyboard) {
|
||||
//
|
||||
// Button("Dismiss Keyboard") {
|
||||
// focusedField = nil
|
||||
// }
|
||||
// .font(.subheadline)
|
||||
//
|
||||
// Spacer()
|
||||
//
|
||||
// ProgressView("Bytes: \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes))
|
||||
// .frame(width: 130)
|
||||
// .padding(5)
|
||||
// .font(.subheadline)
|
||||
// .accentColor(.accentColor)
|
||||
// }
|
||||
// }
|
||||
// .padding(.horizontal, 8)
|
||||
// .focused($focusedField, equals: .messageText)
|
||||
// .multilineTextAlignment(.leading)
|
||||
// .frame(minHeight: 100, maxHeight: 160)
|
||||
//
|
||||
// Text(typingMessage).opacity(0).padding(.all, 0)
|
||||
//
|
||||
// }
|
||||
// .overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1))
|
||||
// .padding(.bottom, 15)
|
||||
//
|
||||
// Button(action: {
|
||||
// if bleManager.sendMessage(message: typingMessage, toUserNum: user.num) {
|
||||
// typingMessage = ""
|
||||
// focusedField = nil
|
||||
// } else {
|
||||
//
|
||||
// _ = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { (_) in
|
||||
//
|
||||
// if bleManager.sendMessage(message: typingMessage, toUserNum: user.num) {
|
||||
// typingMessage = ""
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }) {
|
||||
// Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.blue)
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// .padding(.all, 15)
|
||||
// }
|
||||
//// }
|
||||
// .navigationViewStyle(.stack)
|
||||
// .navigationBarTitleDisplayMode(.inline)
|
||||
// .toolbar {
|
||||
// ToolbarItem(placement: .principal) {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// CircleText(text: user.shortName ?? "???", color: .blue).fixedSize()
|
||||
// Text(user.longName ?? "Unknown").foregroundColor(.gray).font(.caption2).fixedSize()
|
||||
// }
|
||||
// }
|
||||
// ToolbarItem(placement: .navigationBarTrailing) {
|
||||
// ZStack {
|
||||
//
|
||||
// ConnectedDevice(
|
||||
// bluetoothOn: bleManager.isSwitchedOn,
|
||||
// deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
// name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "???")
|
||||
// }
|
||||
// }
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
struct UserMessageList: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
enum Field: Hashable {
|
||||
case messageText
|
||||
}
|
||||
// Keyboard State
|
||||
@State var typingMessage: String = ""
|
||||
@State private var totalBytes = 0
|
||||
var maxbytes = 228
|
||||
@State var lastTypingMessage = ""
|
||||
@FocusState var focusedField: Field?
|
||||
|
||||
var user: UserEntity
|
||||
|
||||
@State var showDeleteMessageAlert = false
|
||||
@State private var deleteMessageId: Int64 = 0
|
||||
|
||||
var body: some View {
|
||||
|
||||
// HStack {
|
||||
|
||||
VStack {
|
||||
|
||||
// List {
|
||||
|
||||
ScrollViewReader { _ in
|
||||
|
||||
ScrollView {
|
||||
|
||||
if user.receivedMessages?.count ?? 0 > 0 {
|
||||
|
||||
ForEach( user.receivedMessages?.array as! [MessageEntity], id: \.self) { (message: MessageEntity) in
|
||||
|
||||
// HStack {
|
||||
let currentUser: Bool = (bleManager.connectedPeripheral == nil) ? false : ((bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == message.fromUser?.num) ? true : false )
|
||||
|
||||
|
||||
if message.toUser!.num == Int64(bleManager.broadcastNodeNum) || ((bleManager.connectedPeripheral) != nil && bleManager.connectedPeripheral.num == message.fromUser?.num) ? true : true {
|
||||
|
||||
|
||||
HStack (alignment: .top) {
|
||||
|
||||
if currentUser { Spacer(minLength:50) }
|
||||
|
||||
if !currentUser {
|
||||
|
||||
CircleText(text: (message.fromUser?.shortName ?? "???"), color: currentUser ? .accentColor : Color(.darkGray)).padding(.all, 5)
|
||||
.gesture(LongPressGesture(minimumDuration: 2).onEnded {_ in
|
||||
|
||||
print("I want to delete message: \(message.messageId)")
|
||||
self.showDeleteMessageAlert = true
|
||||
self.deleteMessageId = message.messageId
|
||||
print(deleteMessageId)
|
||||
})
|
||||
}
|
||||
|
||||
VStack(alignment: currentUser ? .trailing : .leading) {
|
||||
|
||||
Text(message.messagePayload ?? "EMPTY MESSAGE")
|
||||
.textSelection(.enabled)
|
||||
.padding(10)
|
||||
.foregroundColor(.white)
|
||||
.background(currentUser ? Color.blue : Color(.darkGray))
|
||||
.cornerRadius(15)
|
||||
|
||||
HStack(spacing: 4) {
|
||||
|
||||
let time = Int32(message.messageTimestamp)
|
||||
let messageDate = Date(timeIntervalSince1970: TimeInterval(time))
|
||||
|
||||
if time != 0 {
|
||||
Text(messageDate, style: .date).font(.caption2).foregroundColor(.gray)
|
||||
Text(messageDate, style: .time).font(.caption2).foregroundColor(.gray)
|
||||
} else {
|
||||
Text("Unknown").font(.caption2).foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
if !currentUser {
|
||||
Spacer(minLength:50)
|
||||
}
|
||||
}
|
||||
.padding(.trailing)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
// }
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
// .padding(.top)
|
||||
|
||||
HStack(alignment: .top) {
|
||||
|
||||
ZStack {
|
||||
|
||||
let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0)
|
||||
TextEditor(text: $typingMessage)
|
||||
.onChange(of: typingMessage, perform: { value in
|
||||
|
||||
let size = value.utf8.count
|
||||
totalBytes = size
|
||||
if totalBytes <= maxbytes {
|
||||
// Allow the user to type
|
||||
lastTypingMessage = typingMessage
|
||||
} else {
|
||||
// Set the message back and remove the bytes over the count
|
||||
self.typingMessage = lastTypingMessage
|
||||
}
|
||||
})
|
||||
.keyboardType(kbType!)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .keyboard) {
|
||||
|
||||
Button("Dismiss Keyboard") {
|
||||
focusedField = nil
|
||||
}
|
||||
.font(.subheadline)
|
||||
|
||||
Spacer()
|
||||
|
||||
ProgressView("Bytes: \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes))
|
||||
.frame(width: 130)
|
||||
.padding(5)
|
||||
.font(.subheadline)
|
||||
.accentColor(.accentColor)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.focused($focusedField, equals: .messageText)
|
||||
.multilineTextAlignment(.leading)
|
||||
.frame(minHeight: 100, maxHeight: 160)
|
||||
|
||||
Text(typingMessage).opacity(0).padding(.all, 0)
|
||||
|
||||
}
|
||||
.overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1))
|
||||
.padding(.bottom, 15)
|
||||
|
||||
Button(action: {
|
||||
if bleManager.sendMessage(message: typingMessage, toUserNum: user.num) {
|
||||
typingMessage = ""
|
||||
focusedField = nil
|
||||
} else {
|
||||
|
||||
_ = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { (_) in
|
||||
|
||||
if bleManager.sendMessage(message: typingMessage, toUserNum: user.num) {
|
||||
typingMessage = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}) {
|
||||
Image(systemName: "arrow.up.circle.fill").font(.largeTitle).foregroundColor(.blue)
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.all, 15)
|
||||
}
|
||||
// }
|
||||
// .onAppear(perform: {
|
||||
//
|
||||
// self.bleManager.context = context
|
||||
//
|
||||
// })
|
||||
// }
|
||||
//
|
||||
//}
|
||||
.navigationViewStyle(.stack)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .principal) {
|
||||
|
||||
HStack {
|
||||
|
||||
CircleText(text: user.shortName ?? "???", color: .blue).fixedSize()
|
||||
Text(user.longName ?? "Unknown").foregroundColor(.gray).font(.caption2).fixedSize()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "???")
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear(perform: {
|
||||
|
||||
self.bleManager.context = context
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,281 +1,298 @@
|
|||
/*
|
||||
Abstract:
|
||||
A view showing the details for a node.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
import CoreLocation
|
||||
|
||||
struct NodeDetail: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
var node: NodeInfoEntity
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack {
|
||||
|
||||
GeometryReader { bounds in
|
||||
|
||||
VStack {
|
||||
|
||||
if node.positions?.count ?? 0 > 0 {
|
||||
|
||||
let mostRecent = node.positions?.lastObject as! PositionEntity
|
||||
|
||||
if mostRecent.coordinate != nil {
|
||||
|
||||
let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!)
|
||||
|
||||
let regionBinding = Binding<MKCoordinateRegion>(
|
||||
get: {
|
||||
MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
|
||||
},
|
||||
set: { _ in }
|
||||
)
|
||||
let annotations = [MapLocation(name: node.user!.shortName ?? "???", coordinate: mostRecent.coordinate!)]
|
||||
|
||||
Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in
|
||||
MapAnnotation(
|
||||
coordinate: location.coordinate,
|
||||
content: {
|
||||
CircleText(text: node.user!.shortName ?? "???", color: .accentColor)
|
||||
}
|
||||
)
|
||||
}
|
||||
.frame(idealWidth: bounds.size.width, maxHeight: bounds.size.height / 3)
|
||||
} else {
|
||||
|
||||
Image(node.user?.hwModel ?? "UNSET")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: bounds.size.width, height: bounds.size.height / 2)
|
||||
}
|
||||
} else {
|
||||
|
||||
Image(node.user?.hwModel ?? "UNSET")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: bounds.size.width, height: bounds.size.height / 2)
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
|
||||
if node.lastHeard != nil {
|
||||
|
||||
HStack {
|
||||
|
||||
Image(systemName: "clock.badge.checkmark.fill")
|
||||
.font(.title)
|
||||
.foregroundColor(.accentColor)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.title3)
|
||||
}
|
||||
.padding()
|
||||
Divider()
|
||||
}
|
||||
|
||||
HStack {
|
||||
|
||||
VStack(alignment: .center) {
|
||||
Text("AKA").font(.title2).fixedSize()
|
||||
CircleText(text: node.user?.shortName ?? "???", color: .accentColor)
|
||||
.offset(y: 10)
|
||||
}
|
||||
.padding(5)
|
||||
|
||||
Divider()
|
||||
|
||||
VStack {
|
||||
|
||||
Image(node.user!.hwModel ?? "UNSET")
|
||||
.resizable()
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(5)
|
||||
|
||||
Text(String(node.user!.hwModel ?? "UNSET"))
|
||||
.font(.callout).fixedSize()
|
||||
}
|
||||
.padding(5)
|
||||
|
||||
if node.snr > 0 {
|
||||
Divider()
|
||||
VStack(alignment: .center) {
|
||||
|
||||
Image(systemName: "waveform.path")
|
||||
.font(.title)
|
||||
.foregroundColor(.accentColor)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("SNR").font(.title2).fixedSize()
|
||||
Text(String(node.snr))
|
||||
.font(.title2)
|
||||
.foregroundColor(.gray)
|
||||
.fixedSize()
|
||||
}
|
||||
.padding(5)
|
||||
}
|
||||
|
||||
if node.positions!.count > 0 {
|
||||
|
||||
let mostRecent = node.positions?.lastObject as! PositionEntity
|
||||
|
||||
Divider()
|
||||
|
||||
VStack(alignment: .center) {
|
||||
|
||||
BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor)
|
||||
.padding(.bottom)
|
||||
if mostRecent.batteryLevel > 0 {
|
||||
|
||||
Text("Battery")
|
||||
.font(.title2)
|
||||
.fixedSize()
|
||||
.textCase(.uppercase)
|
||||
Text(String(mostRecent.batteryLevel) + "%")
|
||||
.font(.title2)
|
||||
.foregroundColor(.gray)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
} else {
|
||||
|
||||
Text("Powered")
|
||||
.font(.callout)
|
||||
.fixedSize()
|
||||
.textCase(.uppercase)
|
||||
}
|
||||
}
|
||||
.padding(5)
|
||||
}
|
||||
}
|
||||
.padding(4)
|
||||
|
||||
Divider()
|
||||
|
||||
HStack(alignment: .center) {
|
||||
VStack {
|
||||
HStack {
|
||||
Image(systemName: "person")
|
||||
.font(.title2)
|
||||
.foregroundColor(.accentColor)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Unique 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)
|
||||
}
|
||||
}.padding(5)
|
||||
HStack {
|
||||
Text("MAC Address: ")
|
||||
Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray)
|
||||
}
|
||||
|
||||
if node.positions?.count ?? 0 > 1 {
|
||||
|
||||
Divider()
|
||||
|
||||
HStack {
|
||||
|
||||
Image(systemName: "map.circle.fill")
|
||||
.font(.title)
|
||||
.foregroundColor(.accentColor)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Location History").font(.title2)
|
||||
}
|
||||
.padding()
|
||||
|
||||
Divider()
|
||||
|
||||
ForEach(node.positions!.array as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in
|
||||
|
||||
if mappin.coordinate != nil {
|
||||
|
||||
VStack {
|
||||
|
||||
HStack {
|
||||
|
||||
Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline)
|
||||
Text("Lat/Long:").font(.caption)
|
||||
Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
|
||||
Text("Altitude:")
|
||||
.font(.caption)
|
||||
|
||||
Text("\(String(mappin.altitude))m")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
HStack {
|
||||
|
||||
Image(systemName: "clock.badge.checkmark.fill")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.accentColor)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Time:")
|
||||
.font(.caption)
|
||||
Text("\(mappin.time!, style: .date) \(mappin.time!, style: .time)")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
Divider()
|
||||
|
||||
Text("Battery").font(.caption).fixedSize()
|
||||
Text(String(mappin.batteryLevel) + "%")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
.padding(1)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 5) // Without some padding here there is a transparent contentview bug
|
||||
}
|
||||
}
|
||||
}.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
}
|
||||
}
|
||||
.navigationTitle(node.user!.longName ?? "Unknown")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "???")
|
||||
}
|
||||
)
|
||||
.onAppear(perform: {
|
||||
|
||||
self.bleManager.context = context
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeInfoEntityDetail_Previews: PreviewProvider {
|
||||
|
||||
static let bleManager = BLEManager()
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
|
||||
// NodeDetail(node: node)
|
||||
}
|
||||
}
|
||||
}
|
||||
///*
|
||||
//Abstract:
|
||||
//A view showing the details for a node.
|
||||
//*/
|
||||
//
|
||||
//import SwiftUI
|
||||
//import MapKit
|
||||
//import CoreLocation
|
||||
//
|
||||
//struct NodeDetail: View {
|
||||
//
|
||||
// @Environment(\.managedObjectContext) var context
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
//
|
||||
// var node: NodeInfoEntity
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// GeometryReader { bounds in
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// if node.positions?.count ?? 0 > 0 {
|
||||
//
|
||||
// let mostRecent = node.positions?.lastObject as! PositionEntity
|
||||
//
|
||||
// if mostRecent.coordinate != nil {
|
||||
//
|
||||
// let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!)
|
||||
//
|
||||
// let regionBinding = Binding<MKCoordinateRegion>(
|
||||
// get: {
|
||||
// MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
|
||||
// },
|
||||
// set: { _ in }
|
||||
// )
|
||||
// let annotations = [MapLocation(name: node.user!.shortName ?? "???", coordinate: mostRecent.coordinate!)]
|
||||
//
|
||||
// Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .none, annotationItems: annotations) { location in
|
||||
// MapAnnotation(
|
||||
// coordinate: location.coordinate,
|
||||
// content: {
|
||||
// CircleText(text: node.user!.shortName ?? "???", color: .accentColor)
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// .frame(idealWidth: bounds.size.width, maxHeight: bounds.size.height / 3)
|
||||
// } else {
|
||||
//
|
||||
// Image(node.user?.hwModel ?? "UNSET")
|
||||
// .resizable()
|
||||
// .aspectRatio(contentMode: .fit)
|
||||
// .frame(width: bounds.size.width, height: bounds.size.height / 2)
|
||||
// }
|
||||
// } else {
|
||||
//
|
||||
// Image(node.user?.hwModel ?? "UNSET")
|
||||
// .resizable()
|
||||
// .aspectRatio(contentMode: .fit)
|
||||
// .frame(width: bounds.size.width, height: bounds.size.height / 2)
|
||||
// }
|
||||
//
|
||||
// ScrollView {
|
||||
//
|
||||
// if node.lastHeard != nil {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// Image(systemName: "clock.badge.checkmark.fill")
|
||||
// .font(.title)
|
||||
// .foregroundColor(.accentColor)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.title3)
|
||||
// }
|
||||
// .padding()
|
||||
// Divider()
|
||||
// }
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// VStack(alignment: .center) {
|
||||
// Text("AKA").font(.title2).fixedSize()
|
||||
// CircleText(text: node.user?.shortName ?? "???", color: .accentColor)
|
||||
// .offset(y: 10)
|
||||
// }
|
||||
// .padding(5)
|
||||
//
|
||||
// Divider()
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// Image(node.user!.hwModel ?? "UNSET")
|
||||
// .resizable()
|
||||
// .frame(width: 50, height: 50)
|
||||
// .cornerRadius(5)
|
||||
//
|
||||
// Text(String(node.user!.hwModel ?? "UNSET"))
|
||||
// .font(.callout).fixedSize()
|
||||
// }
|
||||
// .padding(5)
|
||||
//
|
||||
// if node.snr > 0 {
|
||||
// Divider()
|
||||
// VStack(alignment: .center) {
|
||||
//
|
||||
// Image(systemName: "waveform.path")
|
||||
// .font(.title)
|
||||
// .foregroundColor(.accentColor)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// Text("SNR").font(.title2).fixedSize()
|
||||
// Text(String(node.snr))
|
||||
// .font(.title2)
|
||||
// .foregroundColor(.gray)
|
||||
// .fixedSize()
|
||||
// }
|
||||
// .padding(5)
|
||||
// }
|
||||
//
|
||||
// if node.positions!.count > 0 {
|
||||
//
|
||||
// let mostRecent = node.positions?.lastObject as! PositionEntity
|
||||
//
|
||||
// Divider()
|
||||
//
|
||||
// VStack(alignment: .center) {
|
||||
//
|
||||
// BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor)
|
||||
// .padding(.bottom)
|
||||
// if mostRecent.batteryLevel > 0 {
|
||||
//
|
||||
// Text("Battery")
|
||||
// .font(.title2)
|
||||
// .fixedSize()
|
||||
// .textCase(.uppercase)
|
||||
// Text(String(mostRecent.batteryLevel) + "%")
|
||||
// .font(.title2)
|
||||
// .foregroundColor(.gray)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// } else {
|
||||
//
|
||||
// Text("Powered")
|
||||
// .font(.callout)
|
||||
// .fixedSize()
|
||||
// .textCase(.uppercase)
|
||||
// }
|
||||
// }
|
||||
// .padding(5)
|
||||
// }
|
||||
// }
|
||||
// .padding(4)
|
||||
//
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
// .padding(5)
|
||||
// Divider()
|
||||
// HStack {
|
||||
// Image(systemName: "globe")
|
||||
// .font(.headline)
|
||||
// .foregroundColor(.accentColor)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// Text("MAC Address: ")
|
||||
// Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray)
|
||||
// }
|
||||
// .padding()
|
||||
//
|
||||
// if node.positions?.count ?? 0 > 1 {
|
||||
//
|
||||
// Divider()
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// Image(systemName: "location.circle.fill")
|
||||
// .font(.title)
|
||||
// .foregroundColor(.accentColor)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// Text("Location History").font(.title2)
|
||||
// }
|
||||
// .padding()
|
||||
//
|
||||
// Divider()
|
||||
//
|
||||
// ForEach(node.positions!.array as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in
|
||||
//
|
||||
// if mappin.coordinate != nil {
|
||||
//
|
||||
// VStack {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline)
|
||||
// Text("Lat/Long:").font(.caption)
|
||||
// Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))")
|
||||
// .foregroundColor(.gray)
|
||||
// .font(.caption)
|
||||
//
|
||||
// Image(systemName: "arrow.up.arrow.down.circle")
|
||||
// .font(.subheadline)
|
||||
// .foregroundColor(.accentColor)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
//
|
||||
// Text("Alt:")
|
||||
// .font(.caption)
|
||||
//
|
||||
// Text("\(String(mappin.altitude))m")
|
||||
// .foregroundColor(.gray)
|
||||
// .font(.caption)
|
||||
// }
|
||||
// HStack {
|
||||
//
|
||||
// Image(systemName: "clock.badge.checkmark.fill")
|
||||
// .font(.subheadline)
|
||||
// .foregroundColor(.accentColor)
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// Text("Time:")
|
||||
// .font(.caption)
|
||||
// Text("\(mappin.time!, style: .date) \(mappin.time!, style: .time)")
|
||||
// .foregroundColor(.gray)
|
||||
// .font(.caption)
|
||||
// Divider()
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// BatteryIcon(batteryLevel: mappin.batteryLevel, font: .subheadline, color: .accentColor)
|
||||
//
|
||||
// if mappin.batteryLevel > 0 {
|
||||
//
|
||||
// Text(String(mappin.batteryLevel) + "%")
|
||||
// .font(.caption2)
|
||||
// .foregroundColor(.gray)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .padding(1)
|
||||
// Divider()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
// }
|
||||
// }
|
||||
// .navigationTitle(node.user!.longName ?? "Unknown")
|
||||
// .navigationBarTitleDisplayMode(.inline)
|
||||
// .navigationBarItems(trailing:
|
||||
//
|
||||
// ZStack {
|
||||
//
|
||||
// ConnectedDevice(
|
||||
// bluetoothOn: bleManager.isSwitchedOn,
|
||||
// deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
// name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "???")
|
||||
// }
|
||||
// )
|
||||
// .onAppear(perform: {
|
||||
//
|
||||
// self.bleManager.context = context
|
||||
//
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//struct NodeInfoEntityDetail_Previews: PreviewProvider {
|
||||
//
|
||||
// static let bleManager = BLEManager()
|
||||
//
|
||||
// static var previews: some View {
|
||||
// Group {
|
||||
//
|
||||
// // NodeDetail(node: node)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,129 +1,129 @@
|
|||
////
|
||||
//// NodeList.swift
|
||||
//// MeshtasticClient
|
||||
////
|
||||
//// Created by Garth Vander Houwen on 8/7/21.
|
||||
////
|
||||
//
|
||||
// NodeList.swift
|
||||
// MeshtasticClient
|
||||
//// Abstract:
|
||||
//// A view showing a list of devices that have been seen on the mesh network from the perspective of the connected device.
|
||||
//
|
||||
// Created by Garth Vander Houwen on 8/7/21.
|
||||
//import SwiftUI
|
||||
//
|
||||
|
||||
// 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
|
||||
|
||||
struct NodeList: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)],
|
||||
animation: .default)
|
||||
|
||||
private var nodes: FetchedResults<NodeInfoEntity>
|
||||
|
||||
@State private var selection: String?
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationView {
|
||||
|
||||
List {
|
||||
|
||||
if nodes.count == 0 {
|
||||
|
||||
Text("Scan for Radios").font(.largeTitle)
|
||||
Text("No LoRa Mesh Nodes Found").font(.title2)
|
||||
Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your phone or tablet.")
|
||||
.font(.body)
|
||||
Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.")
|
||||
Text("Views with bluetooth functionality will show an indicator in the upper right hand corner show if bluetooth is on, and if a device is connected.")
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
} else {
|
||||
ForEach( nodes ) { node in
|
||||
|
||||
let index = nodes.firstIndex(where: { $0.id == node.id })
|
||||
|
||||
NavigationLink(destination: NodeDetail(node: node), tag: String(index!), selection: $selection) {
|
||||
|
||||
let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.name == node.bleName)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
HStack {
|
||||
|
||||
CircleText(text: node.user?.shortName ?? "???", color: Color.accentColor).offset(y: 1).padding(.trailing, 5)
|
||||
.offset(x: -15)
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad { Text(node.user?.longName ?? "Unknown").font(.headline)
|
||||
.offset(x: -15)
|
||||
} else {
|
||||
Text(node.user?.longName ?? "Unknown").font(.title).offset(x: -15)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
|
||||
if connected {
|
||||
HStack(alignment: .bottom) {
|
||||
|
||||
Image(systemName: "repeat.circle.fill").font(.title3)
|
||||
.foregroundColor(.accentColor).symbolRenderingMode(.hierarchical)
|
||||
Text("Currently Connected").font(.title3).foregroundColor(Color.accentColor)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack(alignment: .bottom) {
|
||||
|
||||
Image(systemName: "clock.badge.checkmark.fill").font(.title3).foregroundColor(.accentColor).symbolRenderingMode(.hierarchical)
|
||||
|
||||
if node.lastHeard != nil {
|
||||
Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.subheadline).foregroundColor(.gray)
|
||||
} else {
|
||||
Text("Last Heard: Unknown").font(.subheadline).foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.leading, .top, .bottom])
|
||||
}
|
||||
.swipeActions {
|
||||
|
||||
Button {
|
||||
|
||||
context.delete(node)
|
||||
|
||||
do {
|
||||
|
||||
try context.save()
|
||||
print("Successfully Deleted NodeInfoEntiy: \(node.num)")
|
||||
|
||||
} catch {
|
||||
|
||||
print("Failed to save context after deleting NodeInfoEntity Num: \(node.num)")
|
||||
}
|
||||
|
||||
} label: {
|
||||
|
||||
Label("Delete from app", systemImage: "trash")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("All Nodes")
|
||||
.onAppear {
|
||||
// self.nodes.returnsObjectsAsFaults = false
|
||||
self.bleManager.context = context
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
if nodes.count > 0 {
|
||||
selection = "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
.navigationViewStyle(DoubleColumnNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
//struct NodeList: View {
|
||||
//
|
||||
// @Environment(\.managedObjectContext) var context
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
//
|
||||
// @FetchRequest(
|
||||
// sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)],
|
||||
// animation: .default)
|
||||
//
|
||||
// private var nodes: FetchedResults<NodeInfoEntity>
|
||||
//
|
||||
// @State private var selection: String?
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// NavigationView {
|
||||
//
|
||||
// List {
|
||||
//
|
||||
// if nodes.count == 0 {
|
||||
//
|
||||
// Text("Scan for Radios").font(.largeTitle)
|
||||
// Text("No LoRa Mesh Nodes Found").font(.title2)
|
||||
// Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your phone or tablet.")
|
||||
// .font(.body)
|
||||
// Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.")
|
||||
// Text("Views with bluetooth functionality will show an indicator in the upper right hand corner show if bluetooth is on, and if a device is connected.")
|
||||
// .listRowSeparator(.visible)
|
||||
//
|
||||
// } else {
|
||||
// ForEach( nodes ) { node in
|
||||
//
|
||||
// let index = nodes.firstIndex(where: { $0.id == node.id })
|
||||
//
|
||||
// NavigationLink(destination: NodeDetail(node: node), tag: String(index!), selection: $selection) {
|
||||
//
|
||||
// let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.name == node.bleName)
|
||||
//
|
||||
// VStack(alignment: .leading) {
|
||||
//
|
||||
// HStack {
|
||||
//
|
||||
// CircleText(text: node.user?.shortName ?? "???", color: Color.accentColor).offset(y: 1).padding(.trailing, 5)
|
||||
// .offset(x: -15)
|
||||
//
|
||||
// if UIDevice.current.userInterfaceIdiom == .pad { Text(node.user?.longName ?? "Unknown").font(.headline)
|
||||
// .offset(x: -15)
|
||||
// } else {
|
||||
// Text(node.user?.longName ?? "Unknown").font(.title).offset(x: -15)
|
||||
// }
|
||||
// }
|
||||
// .padding(.bottom, 10)
|
||||
//
|
||||
// if connected {
|
||||
// HStack(alignment: .bottom) {
|
||||
//
|
||||
// Image(systemName: "repeat.circle.fill").font(.title3)
|
||||
// .foregroundColor(.accentColor).symbolRenderingMode(.hierarchical)
|
||||
// Text("Currently Connected").font(.title3).foregroundColor(Color.accentColor)
|
||||
// }
|
||||
// Spacer()
|
||||
// }
|
||||
//
|
||||
// HStack(alignment: .bottom) {
|
||||
//
|
||||
// Image(systemName: "clock.badge.checkmark.fill").font(.title3).foregroundColor(.accentColor).symbolRenderingMode(.hierarchical)
|
||||
//
|
||||
// if node.lastHeard != nil {
|
||||
// Text("Last Heard: \(node.lastHeard!, style: .relative) ago").font(.subheadline).foregroundColor(.gray)
|
||||
// } else {
|
||||
// Text("Last Heard: Unknown").font(.subheadline).foregroundColor(.gray)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .padding([.leading, .top, .bottom])
|
||||
// }
|
||||
// .swipeActions {
|
||||
//
|
||||
// Button {
|
||||
//
|
||||
// context.delete(node)
|
||||
//
|
||||
// do {
|
||||
//
|
||||
// try context.save()
|
||||
// print("Successfully Deleted NodeInfoEntiy: \(node.num)")
|
||||
//
|
||||
// } catch {
|
||||
//
|
||||
// print("Failed to save context after deleting NodeInfoEntity Num: \(node.num)")
|
||||
// }
|
||||
//
|
||||
// } label: {
|
||||
//
|
||||
// Label("Delete from app", systemImage: "trash")
|
||||
// }
|
||||
// .tint(.red)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .navigationTitle("All Nodes")
|
||||
// .onAppear {
|
||||
// // self.nodes.returnsObjectsAsFaults = false
|
||||
// self.bleManager.context = context
|
||||
//
|
||||
// if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
// if nodes.count > 0 {
|
||||
// selection = "0"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
// .navigationViewStyle(DoubleColumnNavigationViewStyle())
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue