From e43b673c059a5ca968df87b55cc022bf1cb46f80 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 14 Oct 2022 22:18:28 -0700 Subject: [PATCH] Message Cleanup, stop QR view crash, hook up messages to preferred node --- Meshtastic.xcodeproj/project.pbxproj | 8 +- Meshtastic/Helpers/BLEManager.swift | 13 +- Meshtastic/Helpers/LocationHelper.swift | 3 +- Meshtastic/Helpers/MeshPackets.swift | 9 +- .../MeshtasticDataModel.xcdatamodel/contents | 2 +- Meshtastic/Model/UserSettings.swift | 6 + Meshtastic/Views/Bluetooth/Connect.swift | 2 + Meshtastic/Views/ContentView.swift | 57 +++-- Meshtastic/Views/Messages/Contacts.swift | 188 ++++++++-------- ...serMessageList.swift => MessageList.swift} | 203 +++++++++--------- Meshtastic/Views/Nodes/NodeList.swift | 18 +- Meshtastic/Views/Settings/Settings.swift | 1 - Meshtastic/Views/Settings/ShareChannels.swift | 172 ++++++--------- 13 files changed, 331 insertions(+), 351 deletions(-) rename Meshtastic/Views/Messages/{UserMessageList.swift => MessageList.swift} (79%) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 93583250..6c4f3df1 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -17,7 +17,7 @@ DD17E5DE277D49D400010EC2 /* storeforward.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17E5DC277D49D400010EC2 /* storeforward.pb.swift */; }; DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */; }; DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B828CDA93900720036 /* SerialConfigEnums.swift */; }; - DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; + DD1BF2F92776FE2E008C8D2F /* MessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* MessageList.swift */; }; DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; }; DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; }; @@ -131,7 +131,7 @@ DD17E5DC277D49D400010EC2 /* storeforward.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = storeforward.pb.swift; sourceTree = ""; }; DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfigEnums.swift; sourceTree = ""; }; DD1925B828CDA93900720036 /* SerialConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfigEnums.swift; sourceTree = ""; }; - DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; + DD1BF2F82776FE2E008C8D2F /* MessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageList.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; @@ -497,7 +497,7 @@ isa = PBXGroup; children = ( DD882F5C2772E4640005BF05 /* Contacts.swift */, - DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */, + DD1BF2F82776FE2E008C8D2F /* MessageList.swift */, ); path = Messages; sourceTree = ""; @@ -731,7 +731,7 @@ DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */, DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */, DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */, - DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */, + DD1BF2F92776FE2E008C8D2F /* MessageList.swift in Sources */, DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */, DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */, DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 41c726ed..27344d12 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -45,7 +45,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph var timeoutTimer: Timer? var timeoutTimerCount = 0 var positionTimer: Timer? - let broadcastNodeNum: UInt32 = 4294967295 + let broadcastNodeNum: UInt32 = 0 /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! @@ -531,7 +531,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } else { - let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, meshLogging: meshLoggingEnabled, context: context!) + let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, meshLogging: meshLoggingEnabled, context: context!) + + self.userSettings?.preferredNodeNum = myInfo?.myNodeNum ?? 0 if myInfo != nil { @@ -675,11 +677,10 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Use a RunLoop to prevent the timer from running on the main UI thread if userSettings?.provideLocation ?? false { if self.positionTimer != nil { - + self.positionTimer!.invalidate() } - let context = ["name": "@\(peripheral.name ?? "Unknown")"] - self.positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((userSettings?.provideLocationInterval ?? 900)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) + positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((userSettings?.provideLocationInterval ?? 900)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) RunLoop.current.add(self.positionTimer!, forMode: .common) } @@ -922,12 +923,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - if meshLoggingEnabled { MeshLogger.log("📍 Sent a Position Packet from the Apple device GPS to node: \(fromNodeNum)") } if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true + MeshLogger.log("📍 Sent a Share Location Position Packet from the Apple device GPS to node: \(fromNodeNum)") } diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index c40b4d2a..929c3617 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -10,7 +10,6 @@ class LocationHelper: NSObject, ObservableObject { static let DefaultAltitude = CLLocationDistance(integerLiteral: 0) static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0) static let DefaultHeading = CLLocationDirection(integerLiteral: 0) - static let DefaultTime = Date.init(timeIntervalSince1970: 0) static var currentLocation: CLLocationCoordinate2D { @@ -47,7 +46,7 @@ class LocationHelper: NSObject, ObservableObject { static var currentTimestamp: Date { guard let timestamp = shared.locationManager.location?.timestamp else { - return DefaultTime + return Date.now } return timestamp } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index bf65b3ec..8114da96 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -743,7 +743,7 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj } } -func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> MyInfoEntity? { +func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, meshLogging: Bool, context: NSManagedObjectContext) -> MyInfoEntity? { let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum)) @@ -754,6 +754,7 @@ func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObje if fetchedMyInfo.isEmpty { let myInfoEntity = MyInfoEntity(context: context) + myInfoEntity.peripheralId = peripheralId myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum) myInfoEntity.hasGps = myInfo.hasGps_p myInfoEntity.hasWifi = myInfo.hasWifi_p @@ -784,6 +785,7 @@ func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObje } else { + fetchedMyInfo[0].peripheralId = peripheralId fetchedMyInfo[0].myNodeNum = Int64(myInfo.myNodeNum) fetchedMyInfo[0].hasGps = myInfo.hasGps_p fetchedMyInfo[0].bitrate = myInfo.bitrate @@ -1187,6 +1189,11 @@ func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb } else { print("💥 Empty POSITION_APP Packet") + + if let dataMessage = try? DataMessage(serializedData: packet.decoded.payload) { + print(dataMessage) + + } } } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents index c79ca23e..71ff5a04 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents @@ -113,6 +113,7 @@ + @@ -224,7 +225,6 @@ - diff --git a/Meshtastic/Model/UserSettings.swift b/Meshtastic/Model/UserSettings.swift index 9c0c2e9f..4317785a 100644 --- a/Meshtastic/Model/UserSettings.swift +++ b/Meshtastic/Model/UserSettings.swift @@ -18,6 +18,11 @@ class UserSettings: ObservableObject { UserDefaults.standard.set(preferredPeripheralId, forKey: "preferredPeripheralId") } } + @Published var preferredNodeNum: Int64 { + didSet { + UserDefaults.standard.set(preferredNodeNum, forKey: "preferredNodeNum") + } + } @Published var provideLocation: Bool { didSet { UserDefaults.standard.set(provideLocation, forKey: "provideLocation") @@ -48,6 +53,7 @@ class UserSettings: ObservableObject { self.meshtasticUsername = UserDefaults.standard.object(forKey: "meshtasticusername") as? String ?? "" self.preferredPeripheralId = UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" + self.preferredNodeNum = UserDefaults.standard.object(forKey: "preferredNodeNum") as? Int64 ?? 0 self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false self.provideLocationInterval = UserDefaults.standard.object(forKey: "provideLocationInterval") as? Int ?? 900 self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0 diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 575fad17..b0c80909 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -89,6 +89,7 @@ struct Connect: View { userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString + userSettings.preferredNodeNum = bleManager.connectedPeripheral!.num bleManager.preferredPeripheral = true isPreferredRadio = true @@ -100,6 +101,7 @@ struct Connect: View { if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { userSettings.preferredPeripheralId = "" + userSettings.preferredNodeNum = 0 bleManager.preferredPeripheral = false isPreferredRadio = false } diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 558c027d..bfcf5845 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -5,7 +5,10 @@ Copyright (c) Garth Vander Houwen 2021 import SwiftUI struct ContentView: View { - @State private var selection: Tab = .ble + + @EnvironmentObject var userSettings: UserSettings + + @State private var selection: Tab = .ble enum Tab { case contacts @@ -15,18 +18,24 @@ struct ContentView: View { case nodes case settings } + + var body: some View { TabView(selection: $selection) { - Contacts() + + if userSettings.preferredNodeNum > 0 { + + Contacts() .tabItem { Label("Messages", systemImage: "message") .symbolRenderingMode(.hierarchical) .symbolVariant(.none) - + } .tag(Tab.contacts) + } Connect() .tabItem { Label("Bluetooth", systemImage: "antenna.radiowaves.left.and.right") @@ -34,28 +43,32 @@ struct ContentView: View { .symbolVariant(.none) } .tag(Tab.ble) - NodeList() - .tabItem { - Label("Nodes", systemImage: "flipphone") + NodeList() + .tabItem { + Label("Nodes", systemImage: "flipphone") .symbolRenderingMode(.hierarchical) - .symbolVariant(.none) - } - .tag(Tab.nodes) - NodeMap() - .tabItem { - Label("Mesh Map", systemImage: "map") - .symbolRenderingMode(.hierarchical) - .symbolVariant(.none) - } - .tag(Tab.map) - Settings() - .tabItem { - Label("Settings", systemImage: "gear") - .symbolRenderingMode(.hierarchical) .symbolVariant(.none) - } - .tag(Tab.settings) + } + .tag(Tab.nodes) + NodeMap() + .tabItem { + Label("Mesh Map", systemImage: "map") + .symbolRenderingMode(.hierarchical) + .symbolVariant(.none) + } + .tag(Tab.map) + Settings() + .tabItem { + Label("Settings", systemImage: "gear") + .symbolRenderingMode(.hierarchical) + .symbolVariant(.none) + } + .tag(Tab.settings) + } + .onAppear( + + ) } } diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index c65a5254..713f607d 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -11,126 +11,120 @@ struct Contacts: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - - @State var onboarding = true @FetchRequest( sortDescriptors: [NSSortDescriptor(key: "longName", ascending: true)], animation: .default) private var users: FetchedResults + + @FetchRequest( + sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: true)], + animation: .default) + + private var nodes: FetchedResults var body: some View { NavigationView { - // Display Contact for Primary Channel - // Display Contacts for DM's on the Primary Channel - // Display Contacts for the rest of the non admin channels - List { - ForEach(users) { (user: UserEntity) in - let connectedNodeNum = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0 - - if user.num != connectedNodeNum { - - NavigationLink(destination: UserMessageList(user: user)) { - - if user.messageList.count > 0 { + Section(header: Text("Primary Channel")) { + + ForEach(users) { (user: UserEntity) in - let mostRecent = user.messageList.last - let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent!.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.userSettings?.preferredNodeNum ?? 0 { - HStack { - - VStack { - - CircleText(text: user.shortName ?? "???", color: Color.blue) - } - .padding([.leading, .trailing]) - - VStack { + NavigationLink(destination: MessageList(user: user)) { - 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) { + if user.messageList.count > 0 { + + let mostRecent = user.messageList.last + let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent!.messageTimestamp )))) + let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 + let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 + + HStack { - Text(mostRecent!.messagePayload ?? "Empty Message") - .frame(height: 60) - .truncationMode(.tail) - .foregroundColor(Color.gray) - .frame(maxWidth: .infinity, alignment: .leading) + 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(mostRecent!.messagePayload ?? "Empty Message") + .frame(height: 60) + .truncationMode(.tail) + .foregroundColor(Color.gray) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + .padding(.top) + } + } else { + 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() } } - .padding(.top) } - - } else { - - 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() } } + Section(header: Text("Private Channels")) { + // Display Contacts for the rest of the non admin channels + } - } + .hidden() } .navigationTitle("Contacts") .navigationBarTitleDisplayMode(.inline) diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/MessageList.swift similarity index 79% rename from Meshtastic/Views/Messages/UserMessageList.swift rename to Meshtastic/Views/Messages/MessageList.swift index bca629a2..1c7063f9 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/MessageList.swift @@ -8,7 +8,7 @@ import SwiftUI import CoreData -struct UserMessageList: View { +struct MessageList: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -43,49 +43,49 @@ struct UserMessageList: View { ScrollView { if user.messageList.count > 0 { - - ForEach( user.messageList ) { (message: MessageEntity) in - - 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 { - if message.replyID > 0 { + ForEach( user.messageList ) { (message: MessageEntity) in + if user.num != userSettings.preferredNodeNum { + 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) || userSettings.preferredNodeNum == message.fromUser?.num) { + + if message.replyID > 0 { + + let messageReply = user.messageList.first(where: { $0.messageId == message.replyID }) + + HStack { - let messageReply = user.messageList.first(where: { $0.messageId == message.replyID }) - - HStack { - - Text(messageReply?.messagePayload ?? "EMPTY MESSAGE").foregroundColor(.blue).font(.caption2) - .padding(10) - .overlay( - RoundedRectangle(cornerRadius: 18) - .stroke(Color.blue, lineWidth: 0.5) + Text(messageReply?.messagePayload ?? "EMPTY MESSAGE").foregroundColor(.blue).font(.caption2) + .padding(10) + .overlay( + RoundedRectangle(cornerRadius: 18) + .stroke(Color.blue, lineWidth: 0.5) ) - Image(systemName: "arrowshape.turn.up.left.fill") - .symbolRenderingMode(.hierarchical) - .imageScale(.large).foregroundColor(.blue) - .padding(.trailing) - } + Image(systemName: "arrowshape.turn.up.left.fill") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.blue) + .padding(.trailing) + } + } + + + HStack (alignment: .top) { + + if currentUser { Spacer(minLength:50) } + + if !currentUser { + + CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.darkGray), circleSize: 44, fontSize: 14) + .padding(.all, 5) + .offset(y: -5) } - - HStack (alignment: .top) { - - if currentUser { Spacer(minLength:50) } + VStack(alignment: currentUser ? .trailing : .leading) { - if !currentUser { - - CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.darkGray), circleSize: 44, fontSize: 14) - .padding(.all, 5) - .offset(y: -5) - } - - VStack(alignment: currentUser ? .trailing : .leading) { - - Text(message.messagePayload ?? "EMPTY MESSAGE") + Text(message.messagePayload ?? "EMPTY MESSAGE") .padding(10) - + .foregroundColor(.white) .background(currentUser ? Color.blue : Color(.darkGray)) .cornerRadius(15) @@ -150,7 +150,7 @@ struct UserMessageList: View { Image(uiImage: image!) } Button(action: { - + if bleManager.sendMessage(message: "‼️", toUserNum: user.num, isEmoji: true, replyID: message.messageId) { print("Sent ‼️ Tapback") @@ -178,7 +178,7 @@ struct UserMessageList: View { Image(uiImage: image!) } Button(action: { - + if bleManager.sendMessage(message: "💩", toUserNum: user.num, isEmoji: true, replyID: message.messageId) { print("Sent 💩 Tapback") @@ -195,7 +195,7 @@ struct UserMessageList: View { Button(action: { self.replyMessageId = message.messageId self.focusedField = .messageText - + print("I want to reply to \(message.messageId)") }) { Text("Reply") @@ -212,14 +212,14 @@ struct UserMessageList: View { VStack { let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp)) - + Text("Date \(messageDate, style: .date) \(messageDate.formattedDate(format: "h:mm:ss a"))").font(.caption2).foregroundColor(.gray) } if currentUser && message.receivedACK { VStack { - + Text("Received Ack \(message.receivedACK ? "✔️" : "")") } @@ -271,90 +271,91 @@ struct UserMessageList: View { Image(systemName: "trash") } } + + let tapbacks = message.value(forKey: "tapbacks") as! [MessageEntity] + + if tapbacks.count > 0 { - let tapbacks = message.value(forKey: "tapbacks") as! [MessageEntity] - - if tapbacks.count > 0 { + VStack (alignment: .trailing) { - VStack (alignment: .trailing) { - - HStack { + HStack { + + ForEach( tapbacks ) { (tapback: MessageEntity) in - ForEach( tapbacks ) { (tapback: MessageEntity) in - - VStack { - - let image = tapback.messagePayload!.image(fontSize: 20) - Image(uiImage: image!).font(.caption) - Text("\(tapback.fromUser?.shortName ?? "????")") - .font(.caption2) - .foregroundColor(.gray) - .fixedSize() - .padding(.bottom, 1) - } + VStack { + + let image = tapback.messagePayload!.image(fontSize: 20) + Image(uiImage: image!).font(.caption) + Text("\(tapback.fromUser?.shortName ?? "????")") + .font(.caption2) + .foregroundColor(.gray) + .fixedSize() + .padding(.bottom, 1) } } - .padding(10) - .overlay( - RoundedRectangle(cornerRadius: 18) - .stroke(Color.gray, lineWidth: 1) - ) - } - } - - HStack { - - if currentUser && message.receivedACK { - - // Ack Received - Text("Acknowledged").font(.caption2).foregroundColor(.gray) - - } else if currentUser && message.ackError == 0 { - - // Empty Error - Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange) - - } else if currentUser && message.ackError > 0 { - - let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) - Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true) - .font(.caption2).foregroundColor(.red) } + .padding(10) + .overlay( + RoundedRectangle(cornerRadius: 18) + .stroke(Color.gray, lineWidth: 1) + ) } } - .padding(.bottom) - .id(user.messageList.firstIndex(of: message)) - if !currentUser { + + HStack { - Spacer(minLength:50) + if currentUser && message.receivedACK { + + // Ack Received + Text("Acknowledged").font(.caption2).foregroundColor(.gray) + + } else if currentUser && message.ackError == 0 { + + // Empty Error + Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange) + + } else if currentUser && message.ackError > 0 { + + let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) + Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true) + .font(.caption2).foregroundColor(.red) + } } } - .padding([.leading, .trailing]) - .frame(maxWidth: .infinity) - .id(message.messageId) - .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")) { + .padding(.bottom) + .id(user.messageList.firstIndex(of: message)) + if !currentUser { + + Spacer(minLength:50) + } + } + .padding([.leading, .trailing]) + .frame(maxWidth: .infinity) + .id(message.messageId) + .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 = user.messageList.first(where: { $0.messageId == deleteMessageId }) - + context.delete(message!) do { try context.save() - + deleteMessageId = 0 - + } catch { - print("Failed to delete message \(deleteMessageId)") + print("Failed to delete message \(deleteMessageId)") } } }, - secondaryButton: .cancel() + secondaryButton: .cancel() ) } } + } } .listRowSeparator(.hidden) } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index dc74e3cd..9314ce27 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -139,16 +139,16 @@ struct NodeList: View { self.initialLoad = false } } - } detail: { + } detail: { + + if let node = selection { - if let node = selection { - - NodeDetail(node:node) - - } else { - - Text("Select a node") - } + NodeDetail(node:node) + + } else { + + Text("Select a node") } + } } } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 3ee00649..8a82e2c7 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -44,7 +44,6 @@ struct Settings: View { .symbolRenderingMode(.hierarchical) Text("Share Channels QR Code") } - .disabled(bleManager.connectedPeripheral == nil) NavigationLink { UserConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index b572e548..ead7b9f8 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -2,13 +2,12 @@ // ShareChannel.swift // MeshtasticApple // -// Created by Garth Vander Houwen on 4/8/22. +// Copyright(c) Garth Vander Houwen 4/8/22. // import SwiftUI import CoreData import CoreImage.CIFilterBuiltins - struct QrCodeImage { let context = CIContext() @@ -30,14 +29,10 @@ struct QrCodeImage { } } - struct ShareChannels: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @EnvironmentObject var userSettings: UserSettings - @State var initialLoad: Bool = true - @State var channelSet: ChannelSet = ChannelSet() @State var includeChannel0 = true @State var includeChannel1 = false @@ -47,30 +42,20 @@ struct ShareChannels: View { @State var includeChannel5 = false @State var includeChannel6 = false @State var includeChannel7 = false - @State var isPresentingHelp = false - var node: NodeInfoEntity? - @State private var channelsUrl = "https://www.meshtastic.org/e/#" - var qrCodeImage = QrCodeImage() var body: some View { VStack { - GeometryReader { bounds in - let smallest = min(bounds.size.width, bounds.size.height) - ScrollView { - VStack { - if node != nil { - + if node != nil && node?.myInfo != nil { Grid(alignment: .top, horizontalSpacing: 2) { - GridRow { Spacer() Text("Include") @@ -86,9 +71,7 @@ struct ShareChannels: View { .fontWeight(.bold) Spacer() } - ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in - GridRow { Spacer() if channel.index == 0 { @@ -155,47 +138,42 @@ struct ShareChannels: View { } } } - let qrImage = qrCodeImage.generateQRCode(from: channelsUrl) + let qrImage = qrCodeImage.generateQRCode(from: channelsUrl) VStack { - - ShareLink("Share QR Code & Link", - item: Image(uiImage: qrImage), - subject: Text("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you"), - message: Text(channelsUrl), - preview: SharePreview("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you", - image: Image(uiImage: qrImage)) - ) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - - Image(uiImage: qrImage) - .resizable() - .scaledToFit() - .frame( - minWidth: smallest * 0.65, - maxWidth: smallest * 0.65, - minHeight: smallest * 0.65, - maxHeight: smallest * 0.65, - alignment: .top + if node != nil { + ShareLink("Share QR Code & Link", + item: Image(uiImage: qrImage), + subject: Text("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you"), + message: Text(channelsUrl), + preview: SharePreview("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you", + image: Image(uiImage: qrImage)) ) - - Button { - - isPresentingHelp = true - - } label: { - - Label("Help Me!", systemImage: "lifepreserver") + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + Image(uiImage: qrImage) + .resizable() + .scaledToFit() + .frame( + minWidth: smallest * 0.65, + maxWidth: smallest * 0.65, + minHeight: smallest * 0.65, + maxHeight: smallest * 0.65, + alignment: .top + ) + Button { + isPresentingHelp = true + } label: { + Label("Help Me!", systemImage: "lifepreserver") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.small) } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.small) } } .sheet(isPresented: $isPresentingHelp) { - VStack { Text("Meshtastic Channels").font(.title) Text("A Meshtastic LoRa Mesh network can have up to 8 distinct channels.") @@ -225,42 +203,20 @@ struct ShareChannels: View { .navigationTitle("Generate QR Code") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") }) .onAppear { - - if self.initialLoad{ - - self.bleManager.context = context - - self.initialLoad = false - GenerateChannelSet() - } - } - .onChange(of: includeChannel1) { includeCh1 in - GenerateChannelSet() - } - .onChange(of: includeChannel2) { includeCh2 in - GenerateChannelSet() - } - .onChange(of: includeChannel3) { includeCh3 in - GenerateChannelSet() - } - .onChange(of: includeChannel4) { includeCh4 in - GenerateChannelSet() - } - .onChange(of: includeChannel5) { includeCh5 in - GenerateChannelSet() - } - .onChange(of: includeChannel6) { includeCh6 in - GenerateChannelSet() - } - .onChange(of: includeChannel7) { includeCh7 in + self.bleManager.context = context GenerateChannelSet() } + .onChange(of: includeChannel1) { includeCh1 in GenerateChannelSet() } + .onChange(of: includeChannel2) { includeCh2 in GenerateChannelSet() } + .onChange(of: includeChannel3) { includeCh3 in GenerateChannelSet() } + .onChange(of: includeChannel4) { includeCh4 in GenerateChannelSet() } + .onChange(of: includeChannel5) { includeCh5 in GenerateChannelSet() } + .onChange(of: includeChannel6) { includeCh6 in GenerateChannelSet() } + .onChange(of: includeChannel7) { includeCh7 in GenerateChannelSet() } } .navigationViewStyle(StackNavigationViewStyle()) } @@ -268,34 +224,36 @@ struct ShareChannels: View { func GenerateChannelSet() { channelSet = ChannelSet() var loRaConfig = Config.LoRaConfig() - loRaConfig.region = RegionCodes(rawValue: Int(node!.loRaConfig!.regionCode))!.protoEnumValue() - loRaConfig.modemPreset = ModemPresets(rawValue: Int(node!.loRaConfig!.modemPreset))!.protoEnumValue() - loRaConfig.bandwidth = UInt32(node!.loRaConfig!.bandwidth) - loRaConfig.spreadFactor = UInt32(node!.loRaConfig!.spreadFactor) - loRaConfig.codingRate = UInt32(node!.loRaConfig!.codingRate) - loRaConfig.frequencyOffset = node!.loRaConfig!.frequencyOffset - loRaConfig.hopLimit = UInt32(node!.loRaConfig!.hopLimit) - loRaConfig.txEnabled = node!.loRaConfig!.txEnabled - loRaConfig.txPower = node!.loRaConfig!.txPower - loRaConfig.channelNum = UInt32(node!.loRaConfig!.channelNum) + loRaConfig.region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))!.protoEnumValue() + loRaConfig.modemPreset = ModemPresets(rawValue: Int(node?.loRaConfig?.modemPreset ?? 0))!.protoEnumValue() + loRaConfig.bandwidth = UInt32(node?.loRaConfig?.bandwidth ?? 0) + loRaConfig.spreadFactor = UInt32(node?.loRaConfig?.spreadFactor ?? 0) + loRaConfig.codingRate = UInt32(node?.loRaConfig?.codingRate ?? 0) + loRaConfig.frequencyOffset = node?.loRaConfig?.frequencyOffset ?? 0 + loRaConfig.hopLimit = UInt32(node?.loRaConfig?.hopLimit ?? 3) + loRaConfig.txEnabled = node?.loRaConfig?.txEnabled ?? false + loRaConfig.txPower = node?.loRaConfig?.txPower ?? 0 + loRaConfig.channelNum = UInt32(node?.loRaConfig?.channelNum ?? 0) channelSet.loraConfig = loRaConfig - for ch in node!.myInfo!.channels!.array as! [ChannelEntity] { - if ch.role > 0 { - - if ch.index == 0 && includeChannel0 || ch.index == 1 && includeChannel1 || ch.index == 2 && includeChannel2 || ch.index == 3 && includeChannel3 || - ch.index == 4 && includeChannel4 || ch.index == 5 && includeChannel5 || ch.index == 6 && includeChannel6 || ch.index == 7 && includeChannel7 { + if node != nil { + for ch in node!.myInfo!.channels!.array as! [ChannelEntity] { + if ch.role > 0 { - var channelSettings = ChannelSettings() - channelSettings.name = ch.name! - channelSettings.psk = ch.psk! - channelSettings.id = UInt32(ch.id) - channelSettings.uplinkEnabled = ch.uplinkEnabled - channelSettings.downlinkEnabled = ch.downlinkEnabled - channelSet.settings.append(channelSettings) + if ch.index == 0 && includeChannel0 || ch.index == 1 && includeChannel1 || ch.index == 2 && includeChannel2 || ch.index == 3 && includeChannel3 || + ch.index == 4 && includeChannel4 || ch.index == 5 && includeChannel5 || ch.index == 6 && includeChannel6 || ch.index == 7 && includeChannel7 { + + var channelSettings = ChannelSettings() + channelSettings.name = ch.name! + channelSettings.psk = ch.psk! + channelSettings.id = UInt32(ch.id) + channelSettings.uplinkEnabled = ch.uplinkEnabled + channelSettings.downlinkEnabled = ch.downlinkEnabled + channelSet.settings.append(channelSettings) + } } } + let settingsString = try! channelSet.serializedData().base64EncodedString() + channelsUrl = ("https://meshtastic.org/e/#" + settingsString.base64ToBase64url()) } - let settingsString = try! channelSet.serializedData().base64EncodedString() - channelsUrl = ("https://meshtastic.org/e/#" + settingsString.base64ToBase64url()) } }