mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #83 from meshtastic/feature/new_config
Scaffolding for new settings and config
This commit is contained in:
commit
be0654df0d
16 changed files with 980 additions and 275 deletions
|
|
@ -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 */,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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/>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// Copyright (C) 2022 Garth Vander Houwen
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
//
|
||||
// DeviceSettings.swift
|
||||
// MeshtasticApple
|
||||
//
|
||||
// Created by Garth Vander Houwen on 6/7/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
171
MeshtasticApple/Views/Settings/DisplayConfig.swift
Normal file
171
MeshtasticApple/Views/Settings/DisplayConfig.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
150
MeshtasticApple/Views/Settings/LoRaConfig.swift
Normal file
150
MeshtasticApple/Views/Settings/LoRaConfig.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
217
MeshtasticApple/Views/Settings/PositionConfig.swift
Normal file
217
MeshtasticApple/Views/Settings/PositionConfig.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue