Merge pull request #83 from meshtastic/feature/new_config

Scaffolding for new settings and config
This commit is contained in:
Garth Vander Houwen 2022-06-12 10:46:28 -07:00 committed by GitHub
commit be0654df0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 980 additions and 275 deletions

View file

@ -16,6 +16,8 @@
DD17E5DE277D49D400010EC2 /* storeforward.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17E5DC277D49D400010EC2 /* storeforward.pb.swift */; };
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; };
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; };
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; };
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; };
DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E65252767A01F00E45FC5 /* NodeDetail.swift */; };
DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; };
DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35018A2852FC79000FC853 /* UserSettings.swift */; };
@ -35,7 +37,7 @@
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FE272476C700F4AB02 /* LogDocument.swift */; };
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AE626F6B38600ABCC23 /* Connect.swift */; };
DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD882F5C2772E4640005BF05 /* Contacts.swift */; };
DD8EBF43285058FA00426DCA /* DeviceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8EBF42285058FA00426DCA /* DeviceSettings.swift */; };
DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8EBF42285058FA00426DCA /* DisplayConfig.swift */; };
DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860B26F684AF00DC5189 /* BatteryIcon.swift */; };
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; };
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; };
@ -94,6 +96,8 @@
DD17E5DC277D49D400010EC2 /* storeforward.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = storeforward.pb.swift; sourceTree = "<group>"; };
DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.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>"; };
DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = "<group>"; };
DD2E65252767A01F00E45FC5 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = "<group>"; };
DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
DD35018A2852FC79000FC853 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = "<group>"; };
@ -113,7 +117,7 @@
DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = "<group>"; };
DD836AE626F6B38600ABCC23 /* Connect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connect.swift; sourceTree = "<group>"; };
DD882F5C2772E4640005BF05 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = "<group>"; };
DD8EBF42285058FA00426DCA /* DeviceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceSettings.swift; sourceTree = "<group>"; };
DD8EBF42285058FA00426DCA /* DisplayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayConfig.swift; sourceTree = "<group>"; };
DD90860A26F645B700DC5189 /* MeshtasticApple.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MeshtasticApple.entitlements; sourceTree = "<group>"; };
DD90860B26F684AF00DC5189 /* BatteryIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryIcon.swift; sourceTree = "<group>"; };
DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = "<group>"; };
@ -221,12 +225,14 @@
DD4A911C2708C57100501B7E /* Settings */ = {
isa = PBXGroup;
children = (
DD3501882852FC3B000FC853 /* Settings.swift */,
DD4A911D2708C65400501B7E /* AppSettings.swift */,
DD8EBF42285058FA00426DCA /* DeviceSettings.swift */,
DD6B85A728009258000ACD6B /* ShareChannel.swift */,
DD8EBF42285058FA00426DCA /* DisplayConfig.swift */,
DD2553562855B02500E55709 /* LoRaConfig.swift */,
DD2553582855B52700E55709 /* PositionConfig.swift */,
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */,
DD8169FE272476C700F4AB02 /* LogDocument.swift */,
DD6B85A728009258000ACD6B /* ShareChannel.swift */,
DD3501882852FC3B000FC853 /* Settings.swift */,
);
path = Settings;
sourceTree = "<group>";
@ -234,7 +240,6 @@
DD8EDE9226F97A2B00A5A10B /* Frameworks */ = {
isa = PBXGroup;
children = (
DDCA31312826009C00207175 /* PassKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -593,12 +598,14 @@
C9A7BC1027759A9600760B50 /* PositionAnnotationView.swift in Sources */,
DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */,
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */,
DD8EBF43285058FA00426DCA /* DeviceSettings.swift in Sources */,
DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */,
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */,
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
DD17E5DE277D49D400010EC2 /* storeforward.pb.swift in Sources */,
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */,
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */,
C9A88B55278B503C00BD810A /* MapViewModule.swift in Sources */,
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */,
DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */,
C9483F6D2773017500998F6B /* MapView.swift in Sources */,
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */,

View file

@ -444,7 +444,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
case .routingApp:
routingPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!)
case .adminApp:
if meshLoggingEnabled { MeshLogger.log(" MESH PACKET received for Admin App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
adminAppPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!)
case .replyApp:
if meshLoggingEnabled { MeshLogger.log(" MESH PACKET received for Reply App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
case .ipTunnelApp:
@ -799,13 +799,42 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
return false
}
public func getConfig(destNum: Int64, wantResponse: Bool) -> Bool {
var newConfig = Config.LoRaConfig()
var channel = ChannelSettings()
// var newPrefs = (value.loraConfig).toBuilder()
// newConfig.
var adminPacket = AdminMessage()
adminPacket.getConfigRequest = AdminMessage.ConfigType.deviceConfig
adminPacket.variant = AdminMessage.OneOf_Variant.getConfigRequest(AdminMessage.ConfigType.loraConfig)
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(connectedPeripheral.num)
meshPacket.from = UInt32(connectedPeripheral.num)
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = false
meshPacket.hopLimit = 0
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
dataMessage.wantResponse = true
meshPacket.decoded = dataMessage
var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket
let binaryData: Data = try! toRadio.serializedData()
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
return true
}
return false
}
}

View file

@ -7,6 +7,8 @@
import Foundation
import CoreData
import SwiftUI
func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> MyInfoEntity? {
@ -322,6 +324,49 @@ func nodeInfoAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManage
}
}
func adminAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) {
if let deviceConfig = try? MeshtasticApple.Config.DeviceConfig(serializedData: packet.decoded.payload) {
print(try! deviceConfig.jsonString())
} else if let displayConfig = try? MeshtasticApple.Config.DisplayConfig(serializedData: packet.decoded.payload) {
print(try! displayConfig.jsonUTF8Data())
print(displayConfig.gpsFormat)
} else if let loraConfig = try? MeshtasticApple.Config.LoRaConfig(serializedData: packet.decoded.payload) {
print(try! loraConfig.jsonUTF8Data())
print(loraConfig.region)
} else if let positionConfig = try? MeshtasticApple.Config.PositionConfig(serializedData: packet.decoded.payload) {
print(try! positionConfig.jsonUTF8Data())
print(positionConfig.positionBroadcastSecs)
} else if let powerConfig = try? MeshtasticApple.Config.PowerConfig(serializedData: packet.decoded.payload) {
print(try! powerConfig.jsonUTF8Data())
print(powerConfig.meshSdsTimeoutSecs)
}
if meshLogging { MeshLogger.log(" MESH PACKET received for Admin App UNHANDLED \(try! packet.jsonString())") }
//PowerConfig
//WiFiConfig
//if let loraConfig = try? MeshtasticApple.Config.LoRaConfig(serializedData: packet.serializedData) {
// print(loraConfig)
//}
}
func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) {
let fetchNodePositionRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")

View file

@ -4,7 +4,7 @@
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:meshtastic.org</string>
<string>applinks:meshtastic.org/e/</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>

View file

@ -1,3 +1,5 @@
// Copyright (C) 2022 Garth Vander Houwen
import SwiftUI
import CoreData

View file

@ -416,13 +416,6 @@ struct Config {
/// 0 for default of 1 minute
var waitBluetoothSecs: UInt32 = 0
///
/// Power management state machine option.
/// See [power management](/docs/software/other/power) for details.
/// 0 for default of 15 minutes
/// IMPORTANT NOTE FOR DEVICE CLIENTS: YOU MUST SEND SOME SORT OF PACKET TO THE PHONE AT LEAST THIS OFTEN OR THE DEVICE WILL DECIDE YOU ARE GONE!
var phoneTimeoutSecs: UInt32 = 0
///
/// Power management state machine option.
/// See [power management](/docs/software/other/power) for details.
@ -1363,7 +1356,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
5: .standard(proto: "is_power_saving"),
6: .standard(proto: "adc_multiplier_override"),
7: .standard(proto: "wait_bluetooth_secs"),
8: .standard(proto: "phone_timeout_secs"),
9: .standard(proto: "mesh_sds_timeout_secs"),
10: .standard(proto: "sds_secs"),
11: .standard(proto: "ls_secs"),
@ -1383,7 +1375,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
case 5: try { try decoder.decodeSingularBoolField(value: &self.isPowerSaving) }()
case 6: try { try decoder.decodeSingularFloatField(value: &self.adcMultiplierOverride) }()
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.waitBluetoothSecs) }()
case 8: try { try decoder.decodeSingularUInt32Field(value: &self.phoneTimeoutSecs) }()
case 9: try { try decoder.decodeSingularUInt32Field(value: &self.meshSdsTimeoutSecs) }()
case 10: try { try decoder.decodeSingularUInt32Field(value: &self.sdsSecs) }()
case 11: try { try decoder.decodeSingularUInt32Field(value: &self.lsSecs) }()
@ -1415,9 +1406,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
if self.waitBluetoothSecs != 0 {
try visitor.visitSingularUInt32Field(value: self.waitBluetoothSecs, fieldNumber: 7)
}
if self.phoneTimeoutSecs != 0 {
try visitor.visitSingularUInt32Field(value: self.phoneTimeoutSecs, fieldNumber: 8)
}
if self.meshSdsTimeoutSecs != 0 {
try visitor.visitSingularUInt32Field(value: self.meshSdsTimeoutSecs, fieldNumber: 9)
}
@ -1441,7 +1429,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
if lhs.isPowerSaving != rhs.isPowerSaving {return false}
if lhs.adcMultiplierOverride != rhs.adcMultiplierOverride {return false}
if lhs.waitBluetoothSecs != rhs.waitBluetoothSecs {return false}
if lhs.phoneTimeoutSecs != rhs.phoneTimeoutSecs {return false}
if lhs.meshSdsTimeoutSecs != rhs.meshSdsTimeoutSecs {return false}
if lhs.sdsSecs != rhs.sdsSecs {return false}
if lhs.lsSecs != rhs.lsSecs {return false}

View file

@ -26,44 +26,52 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
typealias RawValue = Int
///
/// No external telemetry sensor
/// No external telemetry sensor explicitly set
case notSet // = 0
///
/// TODO: REPLACE
/// Moderate accuracy temperature
case dht11 // = 1
///
/// TODO: REPLACE
/// High accuracy temperature
case ds18B20 // = 2
///
/// TODO: REPLACE
/// Moderate accuracy temperature and humidity
case dht12 // = 3
///
/// TODO: REPLACE
/// Moderate accuracy temperature and humidity
case dht21 // = 4
///
/// TODO: REPLACE
/// Moderate accuracy temperature and humidity
case dht22 // = 5
///
/// TODO: REPLACE
/// High accuracy temperature, pressure, humidity
case bme280 // = 6
///
/// TODO: REPLACE
/// High accuracy temperature, pressure, humidity, and air resistance
case bme680 // = 7
///
/// TODO: REPLACE
/// Very high accuracy temperature
case mcp9808 // = 8
///
/// TODO: REPLACE
/// Moderate accuracy temperature and humidity
case shtc3 // = 9
///
/// Moderate accuracy current and voltage
case ina260 // = 10
///
/// Moderate accuracy current and voltage
case ina219 // = 11
case UNRECOGNIZED(Int)
init() {
@ -82,6 +90,8 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
case 7: self = .bme680
case 8: self = .mcp9808
case 9: self = .shtc3
case 10: self = .ina260
case 11: self = .ina219
default: self = .UNRECOGNIZED(rawValue)
}
}
@ -98,6 +108,8 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
case .bme680: return 7
case .mcp9808: return 8
case .shtc3: return 9
case .ina260: return 10
case .ina219: return 11
case .UNRECOGNIZED(let i): return i
}
}
@ -119,6 +131,8 @@ extension TelemetrySensorType: CaseIterable {
.bme680,
.mcp9808,
.shtc3,
.ina260,
.ina219,
]
}
@ -280,6 +294,8 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
7: .same(proto: "BME680"),
8: .same(proto: "MCP9808"),
9: .same(proto: "SHTC3"),
10: .same(proto: "INA260"),
11: .same(proto: "INA219"),
]
}

View file

@ -1,5 +1,5 @@
/*
Abstract: Default App View
Copyright (c) Garth Vander Houwen 2021
*/
import SwiftUI
@ -36,7 +36,7 @@ struct ContentView: View {
// .tag(Tab.messages)
Connect()
.tabItem {
Label("Bluetooth", systemImage: "dot.radiowaves.left.and.right")
Label("Bluetooth", systemImage: "antenna.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
.symbolVariant(.none)
}
@ -55,7 +55,7 @@ struct ContentView: View {
.symbolVariant(.none)
}
.tag(Tab.map)
AppSettings()
Settings()
.tabItem {
Label("Settings", systemImage: "gear")
.symbolRenderingMode(.hierarchical)

View file

@ -36,10 +36,6 @@ struct UserMessageList: View {
var body: some View {
let firmwareVersion = bleManager.lastConnnectionVersion
let minimumVersion = "1.2.52"
let hasTapbackSupport = minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedSame
VStack {
ScrollViewReader { scrollView in
@ -78,7 +74,7 @@ struct UserMessageList: View {
if currentUser { Spacer(minLength:50) }
if !currentUser {
CircleText(text: message.fromUser?.shortName ?? "???", color: currentUser ? .accentColor : Color(.darkGray), circleSize: 36, fontSize: 16).padding(.all, 5)
CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.darkGray), circleSize: 36, fontSize: 16).padding(.all, 5)
}
VStack(alignment: currentUser ? .trailing : .leading) {
@ -90,118 +86,115 @@ struct UserMessageList: View {
.cornerRadius(15)
.contextMenu {
if hasTapbackSupport {
Menu("Tapback response") {
Menu("Tapback response") {
Button(action: {
Button(action: {
if bleManager.sendMessage(message: "❤️", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
if bleManager.sendMessage(message: "❤️", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent ❤️ Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("❤️ Tapback Failed") }
print("Sent ❤️ Tapback")
self.context.refresh(user, mergeChanges: true)
}) {
Text("Heart")
let image = "❤️".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "👍", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent 👍 Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("👍 Tapback Failed")}
}) {
Text("Thumbs Up")
let image = "👍".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "👎", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent 👎 Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("👎 Tapback Failed") }
}) {
Text("Thumbs Down")
let image = "👎".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "🤣", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent 🤣 Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("🤣 Tapback Failed") }
}) {
Text("HaHa")
let image = "🤣".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "‼️", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent ‼️ Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("‼️ Tapback Failed") }
}) {
Text("Exclamation Mark")
let image = "‼️".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent ❓ Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("❓ Tapback Failed") }
}) {
Text("Question Mark")
let image = "".image()
Image(uiImage: image!)
}
Button(action: {
} else { print("❤️ Tapback Failed") }
if bleManager.sendMessage(message: "💩", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent 💩 Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("💩 Tapback Failed") }
}) {
Text("Poop")
let image = "💩".image()
Image(uiImage: image!)
}
}) {
Text("Heart")
let image = "❤️".image()
Image(uiImage: image!)
}
Button(action: {
self.replyMessageId = message.messageId
self.focusedField = .messageText
print("I want to reply to \(message.messageId)")
if bleManager.sendMessage(message: "👍", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent 👍 Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("👍 Tapback Failed")}
}) {
Text("Reply")
Image(systemName: "arrowshape.turn.up.left.2.fill")
Text("Thumbs Up")
let image = "👍".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "👎", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent 👎 Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("👎 Tapback Failed") }
}) {
Text("Thumbs Down")
let image = "👎".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "🤣", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent 🤣 Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("🤣 Tapback Failed") }
}) {
Text("HaHa")
let image = "🤣".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "‼️", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent ‼️ Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("‼️ Tapback Failed") }
}) {
Text("Exclamation Mark")
let image = "‼️".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent ❓ Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("❓ Tapback Failed") }
}) {
Text("Question Mark")
let image = "".image()
Image(uiImage: image!)
}
Button(action: {
if bleManager.sendMessage(message: "💩", toUserNum: user.num, isEmoji: true, replyID: message.messageId) {
print("Sent 💩 Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("💩 Tapback Failed") }
}) {
Text("Poop")
let image = "💩".image()
Image(uiImage: image!)
}
}
Button(action: {
self.replyMessageId = message.messageId
self.focusedField = .messageText
print("I want to reply to \(message.messageId)")
}) {
Text("Reply")
Image(systemName: "arrowshape.turn.up.left.2.fill")
}
Button(action: {
UIPasteboard.general.string = message.messagePayload
@ -250,37 +243,33 @@ struct UserMessageList: View {
}
}
if hasTapbackSupport {
let tapbacks = message.value(forKey: "tapbacks") as! [MessageEntity]
let tapbacks = message.value(forKey: "tapbacks") as! [MessageEntity]
if tapbacks.count > 0 {
if tapbacks.count > 0 {
VStack (alignment: .trailing) {
VStack (alignment: .trailing) {
HStack {
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)
}
HStack {
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)
}
}
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 18)
.stroke(Color.gray, lineWidth: 1)
)
}
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 18)
.stroke(Color.gray, lineWidth: 1)
)
}
}
@ -291,7 +280,6 @@ struct UserMessageList: View {
Text("Acknowledged").font(.caption2).foregroundColor(.gray)
}
}
}
.padding(.bottom)
.id(user.messageList.firstIndex(of: message))

View file

@ -91,110 +91,97 @@ struct AppSettings: View {
var body: some View {
NavigationView {
VStack {
GeometryReader { _ in
Form {
Section(header: Text("USER DETAILS")) {
Form {
Section(header: Text("USER DETAILS")) {
HStack {
Label("Name", systemImage: "person.crop.rectangle.fill")
TextField("Username", text: $userSettings.meshtasticUsername)
.foregroundColor(.gray)
}
.keyboardType(.asciiCapable)
.disableAutocorrection(true)
.listRowSeparator(.visible)
HStack {
Label("Radio", systemImage: "flipphone")
Text(userSettings.preferredPeripheralName)
.foregroundColor(.gray)
}
Text("This option is set via the preferred radio toggle for the connected device on the bluetooth tab.")
.font(.caption)
.listRowSeparator(.hidden)
Text("The preferred radio will automatically reconnect if it becomes disconnected and is still within range.")
.font(.caption2)
HStack {
Label("Name", systemImage: "person.crop.rectangle.fill")
TextField("Username", text: $userSettings.meshtasticUsername)
.foregroundColor(.gray)
}
Section(header: Text("LOCATION OPTIONS")) {
.keyboardType(.asciiCapable)
.disableAutocorrection(true)
.listRowSeparator(.visible)
HStack {
Label("Radio", systemImage: "flipphone")
Text(userSettings.preferredPeripheralName)
.foregroundColor(.gray)
Toggle(isOn: $userSettings.provideLocation) {
Label("Provide location to mesh", systemImage: "location.circle.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if userSettings.provideLocation {
Picker(" Update Interval", selection: $userSettings.provideLocationInterval) {
ForEach(LocationUpdateInterval.allCases) { lu in
Text(lu.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("How frequently your phone will send your location to the device, location updates to the mesh are managed by the device.")
.font(.caption)
.listRowSeparator(.visible)
}
}
Section(header: Text("MESH OPTIONS")) {
NavigationLink(destination: ShareChannel()) {
Text("Share Your Channel vis QR Code")
}
}
Section(header: Text("MESSAGING OPTIONS")) {
Text("This option is set via the preferred radio toggle for the connected device on the bluetooth tab.")
.font(.caption)
.listRowSeparator(.hidden)
Text("The preferred radio will automatically reconnect if it becomes disconnected and is still within range.")
.font(.caption2)
.foregroundColor(.gray)
Picker("Keyboard Type", selection: $userSettings.keyboardType) {
ForEach(KeyboardType.allCases) { kb in
Text(kb.description)
}
Section(header: Text("OPTIONS")) {
Toggle(isOn: $userSettings.provideLocation) {
Label("Provide location to mesh", systemImage: "location.circle.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if userSettings.provideLocation {
Picker(" Update Interval", selection: $userSettings.provideLocationInterval) {
ForEach(LocationUpdateInterval.allCases) { lu in
Text(lu.description)
}
}
.pickerStyle(DefaultPickerStyle())
}
Section(header: Text("MAP OPTIONS")) {
Picker("Map Type", selection: $userSettings.meshMapType) {
ForEach(MeshMapType.allCases) { map in
Text(map.description)
}
}
.pickerStyle(DefaultPickerStyle())
// TextField("Custom Tile Server", text: $userSettings.meshMapCustomTileServer)
}
Section(header: Text("DEBUG OPTIONS")) {
Toggle(isOn: $userSettings.meshActivityLog) {
Label("Log all Mesh activity", systemImage: "network")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if userSettings.meshActivityLog {
NavigationLink(destination: MeshLog()) {
Text("View Mesh Log")
}
Text("How frequently your phone will send your location to the device, location updates to the mesh are managed by the device.")
.font(.caption)
.listRowSeparator(.visible)
}
Picker("Keyboard Type", selection: $userSettings.keyboardType) {
ForEach(KeyboardType.allCases) { kb in
Text(kb.description)
}
}
.pickerStyle(DefaultPickerStyle())
Picker("Map Type", selection: $userSettings.meshMapType) {
ForEach(MeshMapType.allCases) { map in
Text(map.description)
}
}
.pickerStyle(DefaultPickerStyle())
}
Section(header: Text("DEBUG")) {
Toggle(isOn: $userSettings.meshActivityLog) {
Label("Log all Mesh activity", systemImage: "network")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if userSettings.meshActivityLog {
NavigationLink(destination: MeshLog()) {
Text("View Mesh Log")
}
.listRowSeparator(.visible)
}
}
}
.navigationTitle("App Settings")
.navigationBarItems(trailing:
}
.navigationTitle("App Settings")
.navigationBarItems(trailing:
ZStack {
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
})
.onAppear {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
})
.onAppear {
self.bleManager.context = context
}
}
self.bleManager.context = context
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -1,8 +0,0 @@
//
// DeviceSettings.swift
// MeshtasticApple
//
// Created by Garth Vander Houwen on 6/7/22.
//
import Foundation

View file

@ -0,0 +1,171 @@
//
// DeviceSettings.swift
// Meshtastic Apple
//
// Copyright (c) Garth Vander Houwen 6/7/22.
//
import SwiftUI
enum GpsFormat: Int, CaseIterable, Identifiable {
case gpsFormatDec = 0
case gpsFormatDms = 1
case gpsFormatUtm = 2
case gpsFormatMgrs = 3
case gpsFormatOlc = 4
case gpsFormatOsgr = 5
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .gpsFormatDec:
return "Decimal Degrees Format"
case .gpsFormatDms:
return "Degrees Minutes Seconds"
case .gpsFormatUtm:
return "Universal Transverse Mercator"
case .gpsFormatMgrs:
return "Military Grid Reference System"
case .gpsFormatOlc:
return "Open Location Code (aka Plus Codes)"
case .gpsFormatOsgr:
return "Ordnance Survey Grid Reference"
}
}
}
}
// Default of 0 is One Minute
enum ScreenOnSeconds: Int, CaseIterable, Identifiable {
case fifteenSeconds = 15
case thirtySeconds = 30
case oneMinute = 0
case fiveMinutes = 300
case tenMinutes = 600
case fifteenMinutes = 900
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .fifteenSeconds:
return "Fifteen Seconds"
case .thirtySeconds:
return "Thirty Seconds"
case .oneMinute:
return "One Minute"
case .fiveMinutes:
return "Five Minutes"
case .tenMinutes:
return "Ten Minutes"
case .fifteenMinutes:
return "Fifteen Minutes"
}
}
}
}
// Default of 0 is off
enum ScreenCarouselSeconds: Int, CaseIterable, Identifiable {
case off = 0
case fifteenSeconds = 15
case thirtySeconds = 30
case oneMinute = 60
case fiveMinutes = 300
case tenMinutes = 600
case fifteenMinutes = 900
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .off:
return "Off"
case .fifteenSeconds:
return "Fifteen Seconds"
case .thirtySeconds:
return "Thirty Seconds"
case .oneMinute:
return "One Minute"
case .fiveMinutes:
return "Five Minutes"
case .tenMinutes:
return "Ten Minutes"
case .fifteenMinutes:
return "Fifteen Minutes"
}
}
}
}
struct DisplayConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State var isConnected: Bool = false
@State var gpsFormat: Config.DisplayConfig.GpsCoordinateFormat = .gpsFormatDec
var body: some View {
VStack {
Form {
Section(header: Text("Timing")) {
Picker("Screen on for", selection: $gpsFormat ) {
ForEach(ScreenOnSeconds.allCases) { lu in
Text(lu.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("The number of seconds the screen remains on after the user button is pressed or messages are received.")
.font(.caption)
.listRowSeparator(.visible)
Picker("Carousel Interval", selection: $gpsFormat ) {
ForEach(ScreenCarouselSeconds.allCases) { lu in
Text(lu.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.")
.font(.caption)
.listRowSeparator(.visible)
}
Section(header: Text("Format")) {
Picker("GPS Format", selection: $gpsFormat ) {
ForEach(GpsFormat.allCases) { lu in
Text(lu.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("The format used to display GPS coordinates on the screen.")
.font(.caption)
.listRowSeparator(.visible)
}
}
}
.navigationTitle("Display Config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
})
.onAppear {
self.bleManager.context = context
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -0,0 +1,150 @@
//
// LoRaConfig.swift
// Meshtastic Apple
//
// Copyright (c) by Garth Vander Houwen 6/11/22.
//
import SwiftUI
enum RegionCodes : Int, CaseIterable, Identifiable {
case unset = 0
case us = 1
case eu433 = 2
case eu868 = 3
case cn = 4
case jp = 5
case anz = 6
case kr = 7
case tw = 8
case ru = 9
//case in = 10
case nz865
case th
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .unset:
return "UNSET - Please set a Region"
case .us:
return "United States"
case .eu433:
return "European Union 433mhz"
case .eu868:
return "European Union 868mhz"
case .cn:
return "China"
case .jp:
return "Japan"
case .anz:
return "Australia / New Zealand"
case .kr:
return "Korea"
case .tw:
return "Taiwan"
case .ru:
return "Russia"
case .nz865:
return "New Zealand 865mhz"
case .th:
return "TH"
}
}
}
}
enum ModemPresets : Int, CaseIterable, Identifiable {
case LongFast = 0
case LongSlow = 1
case VLongSlow = 2
case MidSlow = 3
case MidFast = 4
case ShortSlow = 5
case ShortFast = 6
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .LongFast:
return "Long Fast"
case .LongSlow:
return "Long Slow"
case .VLongSlow:
return "Very Long Slow"
case .MidSlow:
return "Mid Slow"
case .MidFast:
return "Mid Fast"
case .ShortSlow:
return "Short Slow"
case .ShortFast:
return "Short Fast"
}
}
}
}
struct LoRaConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State var region: Config.LoRaConfig.RegionCode = .us
@State var modemPreset: Config.LoRaConfig.ModemPreset = .longFast
var body: some View {
VStack {
Form {
Section(header: Text("Region")) {
Picker("Region", selection: $region ) {
ForEach(RegionCodes.allCases) { r in
Text(r.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("The region where you will be using your Meshtastic LoRa radios.")
.font(.caption)
.listRowSeparator(.visible)
.listRowSeparator(.visible)
}
Section(header: Text("Modem")) {
Picker("Presets", selection: $region ) {
ForEach(ModemPresets.allCases) { m in
Text(m.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("Available modem presets.")
.font(.caption)
.listRowSeparator(.visible)
.listRowSeparator(.visible)
}
}
}
.navigationTitle("LoRa Config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
})
.onAppear {
self.bleManager.context = context
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -0,0 +1,217 @@
//
// PositionConfig.swift
// Meshtastic Apple
//
// Copyright (c) Garth Vander Houwen 6/11/22.
//
import SwiftUI
enum GpsUpdateIntervals: Int, CaseIterable, Identifiable {
case thirtySeconds = 0
case oneMinute = 60
case fiveMinutes = 300
case tenMinutes = 600
case fifteenMinutes = 900
case thirtyMinutes = 1800
case oneHour = 3600
case maxInt32 = 2147483647
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .thirtySeconds:
return "Thirty Seconds"
case .oneMinute:
return "One Minute"
case .fiveMinutes:
return "Five Minutes"
case .tenMinutes:
return "Ten Minutes"
case .fifteenMinutes:
return "Fifteen Minutes"
case .thirtyMinutes:
return "Thirty Minutes"
case .oneHour:
return "One Hour"
case .maxInt32:
return "On Boot Only"
}
}
}
}
enum GpsAttemptTimes: Int, CaseIterable, Identifiable {
case thirtySeconds = 0
case oneMinute = 60
case fiveMinutes = 300
case tenMinutes = 600
case fifteenMinutes = 900
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .thirtySeconds:
return "Thirty Seconds"
case .oneMinute:
return "One Minute"
case .fiveMinutes:
return "Five Minutes"
case .tenMinutes:
return "Ten Minutes"
case .fifteenMinutes:
return "Fifteen Minutes"
}
}
}
}
enum PositionBroadcastIntervals: Int, CaseIterable, Identifiable {
case thirtySeconds = 30
case oneMinute = 60
case fiveMinutes = 300
case tenMinutes = 600
case fifteenMinutes = 0
case thirtyMinutes = 1800
case oneHour = 3600
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .thirtySeconds:
return "Thirty Seconds"
case .oneMinute:
return "One Minute"
case .fiveMinutes:
return "Five Minutes"
case .tenMinutes:
return "Ten Minutes"
case .fifteenMinutes:
return "Fifteen Minutes"
case .thirtyMinutes:
return "Thirty Minutes"
case .oneHour:
return "One Hour"
}
}
}
}
struct PositionConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State var smartPositionEnabled = true
@State var deviceGpsEnabled = true
@State var fixedPosition = false
@State var gpsUpdateInterval: Int32 = 0
@State var gpsAttemptTime: Int32 = 0
@State var positionBroadcastSeconds: Int32 = 0
var body: some View {
VStack {
Form {
Section(header: Text("Device GPS Options")) {
Toggle(isOn: $deviceGpsEnabled) {
Label("Device GPS Enabled", systemImage: "location")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if deviceGpsEnabled {
Picker("GPS Update Interval", selection: $gpsUpdateInterval) {
ForEach(GpsUpdateIntervals.allCases) { ui in
Text(ui.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("How often should we try to get a GPS position.")
.font(.caption)
.listRowSeparator(.visible)
Picker("GPS Attempt Time", selection: $gpsAttemptTime) {
ForEach(GpsAttemptTimes.allCases) { at in
Text(at.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("How long should we try to get our position during each GPS Update Interval attempt?")
.font(.caption)
.listRowSeparator(.visible)
} else {
Toggle(isOn: $fixedPosition) {
Label("Fixed Position", systemImage: "location.square.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if fixedPosition {
Text("Set to current location here")
.font(.caption)
.listRowSeparator(.visible)
}
}
}
Section(header: Text("Position Packet Options")) {
Toggle(isOn: $smartPositionEnabled) {
Label("Smart Position Broadcast", systemImage: "location.fill.viewfinder")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if !smartPositionEnabled {
Picker("Position Broadcast Interval", selection: $positionBroadcastSeconds) {
ForEach(PositionBroadcastIntervals.allCases) { at in
Text(at.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("We should send our position this often (but only if it has changed significantly)")
.font(.caption)
.listRowSeparator(.visible)
}
}
Section(header: Text("Position Flags")) {
Text("TODO")
.font(.caption)
.listRowSeparator(.visible)
}
}
}
.navigationTitle("Position Config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????")
})
.onAppear {
self.bleManager.context = context
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -2,7 +2,123 @@
// Settings.swift
// MeshtasticApple
//
// Created by Garth Vander Houwen on 6/9/22.
// Copyright (c) Garth Vander Houwen 6/9/22.
//
import Foundation
import SwiftUI
struct Settings: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
var body: some View {
NavigationView {
List {
Section("General") {
NavigationLink {
AppSettings()
} label: {
Image(systemName: "gearshape")
.symbolRenderingMode(.hierarchical)
Text("App Settings")
}
NavigationLink {
ShareChannel()
} label: {
Image(systemName: "qrcode")
.symbolRenderingMode(.hierarchical)
Text("Share Channel QR Code")
}
}
Section("Radio Configuration (Non-Functional Interaction Previews)") {
NavigationLink {
DisplayConfig()
} label: {
Image(systemName: "display")
.symbolRenderingMode(.hierarchical)
Text("Display (Device Screen)")
}
NavigationLink {
LoRaConfig()
} label: {
Image(systemName: "dot.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
Text("LoRa")
}
NavigationLink {
PositionConfig()
} label: {
Image(systemName: "location")
.symbolRenderingMode(.hierarchical)
Text("Position")
}
NavigationLink {
PositionConfig()
} label: {
Image(systemName: "bolt")
.symbolRenderingMode(.hierarchical)
Text("Power")
}
.disabled(true)
}
Section("Module Configuration") {
NavigationLink {
DisplayConfig()
} label: {
Image(systemName: "list.bullet.rectangle.fill")
.symbolRenderingMode(.hierarchical)
Text("Canned Messages")
}
.disabled(true)
NavigationLink {
DisplayConfig()
} label: {
Image(systemName: "point.3.connected.trianglepath.dotted")
.symbolRenderingMode(.hierarchical)
Text("Range Test")
}
.disabled(true)
NavigationLink {
DisplayConfig()
} label: {
Image(systemName: "chart.xyaxis.line")
.symbolRenderingMode(.hierarchical)
Text("Telemetry (Sensors)")
}
.disabled(true)
}
// Not Implemented:
// Device Config - No interesting settings for end users
// Power Config - All confusion, should delete most and have sensible defaults
// External Notifications - Not Working
// Serial Config - Not sure what the point is
// Store Forward Config - Not Working
// WiFi Config - Would break connection to device
// MQTT Config - Part of WiFi
}
.listStyle(GroupedListStyle())
.navigationTitle("Settings")
}
}
}

View file

@ -38,7 +38,7 @@ struct ShareChannel: View {
let channelSet = ChannelSet()
@State private var text = "https://meshtastic.org/e/#"
@State private var text = "https://meshtastic.org/e/#test"
var qrCodeImage = QrCodeImage()
var body: some View {
@ -52,11 +52,9 @@ struct ShareChannel: View {
ScrollView {
VStack {
Text("Scan the QR code below with the Apple or Android device you would like to share with your channel settings with.")
Text("Scan the QR code below with the Apple or Android device you would like to share your channel settings with.")
.fixedSize(horizontal: false, vertical: true)
.font(.callout)
.padding()
Spacer()
let image = qrCodeImage.generateQRCode(from: text)
Image(uiImage: image)