Merge pull request #206 from meshtastic/messages_cleanup

Messages cleanup
This commit is contained in:
Garth Vander Houwen 2022-10-14 22:22:53 -07:00 committed by GitHub
commit a44ad6eceb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 341 additions and 351 deletions

View file

@ -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 = "<group>"; };
DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfigEnums.swift; sourceTree = "<group>"; };
DD1925B828CDA93900720036 /* SerialConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfigEnums.swift; sourceTree = "<group>"; };
DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = "<group>"; };
DD1BF2F82776FE2E008C8D2F /* MessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageList.swift; sourceTree = "<group>"; };
DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = "<group>"; };
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = "<group>"; };
DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = "<group>"; };
@ -497,7 +497,7 @@
isa = PBXGroup;
children = (
DD882F5C2772E4640005BF05 /* Contacts.swift */,
DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */,
DD1BF2F82776FE2E008C8D2F /* MessageList.swift */,
);
path = Messages;
sourceTree = "<group>";
@ -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 */,

View file

@ -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)")
}

View file

@ -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
}

View file

@ -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<NSFetchRequestResult> = 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)
}
}
}

View file

@ -113,6 +113,7 @@
<attribute name="messageTimeoutMsec" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="peripheralId" optional="YES" attributeType="String"/>
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
@ -224,7 +225,6 @@
<attribute name="macaddr" optional="YES" attributeType="Binary"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="shortName" attributeType="String"/>
<attribute name="team" optional="YES" attributeType="String"/>
<attribute name="userId" attributeType="String"/>
<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"/>

View file

@ -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

View file

@ -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
}

View file

@ -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(
)
}
}

View file

@ -6,7 +6,9 @@
//
import SwiftUI
#if canImport(Charts)
import Charts
#endif
struct BatteryGauge: View {
@ -18,6 +20,7 @@ struct BatteryGauge: View {
var body: some View {
VStack {
#if !targetEnvironment(macCatalyst)
if batteryLevel == 0.0 {
// Plugged in
Image(systemName: "powerplug")
@ -47,6 +50,7 @@ struct BatteryGauge: View {
.tint(gradient)
.gaugeStyle(.accessoryCircular)
}
#endif
}
}
}

View file

@ -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<UserEntity>
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: true)],
animation: .default)
private var nodes: FetchedResults<NodeInfoEntity>
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)

View file

@ -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)
}

View file

@ -5,7 +5,9 @@
// Copyright(c) Garth Vander Houwen 7/7/22.
//
import SwiftUI
#if canImport(Charts)
import Charts
#endif
struct DeviceMetricsLog: View {
@ -35,6 +37,8 @@ struct DeviceMetricsLog: View {
if data.count > 0 {
#if canImport(Charts)
GroupBox(label: Label("Battery Level Trend", systemImage: "battery.100")) {
Chart(data.array as! [TelemetryEntity], id: \.self) {
@ -49,6 +53,8 @@ struct DeviceMetricsLog: View {
}
.frame(height: 150)
}
#endif
}
ScrollView {

View file

@ -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")
}
}
}
}

View file

@ -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 }))

View file

@ -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())
}
}