mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #359 from meshtastic/offline_maps_updates
Crashfixes, weather radar overlays
This commit is contained in:
commit
3c38280328
21 changed files with 933 additions and 374 deletions
|
|
@ -103,7 +103,6 @@
|
|||
DDB75A142A0593E2006ED576 /* OfflineTileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A132A0593E2006ED576 /* OfflineTileManager.swift */; };
|
||||
DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A152A0594AD006ED576 /* TileOverlay.swift */; };
|
||||
DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */ = {isa = PBXBuildFile; fileRef = DDB75A192A05EB67006ED576 /* alpha.png */; };
|
||||
DDB75A1C2A076DFA006ED576 /* TilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A1B2A076DFA006ED576 /* TilesView.swift */; };
|
||||
DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */; };
|
||||
DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */; };
|
||||
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; };
|
||||
|
|
@ -293,8 +292,8 @@
|
|||
DDB75A132A0593E2006ED576 /* OfflineTileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineTileManager.swift; sourceTree = "<group>"; };
|
||||
DDB75A152A0594AD006ED576 /* TileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileOverlay.swift; sourceTree = "<group>"; };
|
||||
DDB75A192A05EB67006ED576 /* alpha.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = alpha.png; sourceTree = "<group>"; };
|
||||
DDB75A1B2A076DFA006ED576 /* TilesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TilesView.swift; sourceTree = "<group>"; };
|
||||
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrengthIndicator.swift; sourceTree = "<group>"; };
|
||||
DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV13.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV8.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDC2E15426CE248E0042C5E4 /* Meshtastic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meshtastic.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticApp.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -403,7 +402,6 @@
|
|||
DD964FC32974767D007C176F /* MapViewFitExtension.swift */,
|
||||
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */,
|
||||
DDDB443529F6287000EE2349 /* MapButtons.swift */,
|
||||
DDB75A1B2A076DFA006ED576 /* TilesView.swift */,
|
||||
);
|
||||
path = Custom;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1010,7 +1008,6 @@
|
|||
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */,
|
||||
DDDB444229F8A88700EE2349 /* Double.swift in Sources */,
|
||||
DD5E520F298EE33B00D21B61 /* cannedmessages.pb.swift in Sources */,
|
||||
DDB75A1C2A076DFA006ED576 /* TilesView.swift in Sources */,
|
||||
DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */,
|
||||
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */,
|
||||
DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */,
|
||||
|
|
@ -1301,7 +1298,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.1.9;
|
||||
MARKETING_VERSION = 2.1.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1335,7 +1332,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.1.9;
|
||||
MARKETING_VERSION = 2.1.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1454,7 +1451,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.1.9;
|
||||
MARKETING_VERSION = 2.1.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
|
@ -1485,7 +1482,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.1.9;
|
||||
MARKETING_VERSION = 2.1.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
|
@ -1582,6 +1579,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */,
|
||||
DDB759E12A04B264006ED576 /* MeshtasticDataModelV12.xcdatamodel */,
|
||||
DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */,
|
||||
DDC94FC329CED7280082EA6E /* MeshtasticDataModelV10.xcdatamodel */,
|
||||
|
|
@ -1595,7 +1593,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DDB759E12A04B264006ED576 /* MeshtasticDataModelV12.xcdatamodel */;
|
||||
currentVersion = DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ enum MapLayer: String, CaseIterable, Equatable {
|
|||
var localized: String { self.rawValue.localized }
|
||||
}
|
||||
|
||||
enum MapTileServerLinks: String, CaseIterable, Identifiable {
|
||||
enum MapTileServer: String, CaseIterable, Identifiable {
|
||||
|
||||
case openStreetMap
|
||||
case openStreetMapDE
|
||||
|
|
@ -254,11 +254,11 @@ enum MapTileServerLinks: String, CaseIterable, Identifiable {
|
|||
case .openStreetMapHot:
|
||||
return [Int](0...18)
|
||||
case .usgsTopo:
|
||||
return [Int](6...15)
|
||||
return [Int](6...16)
|
||||
case .usgsImageryTopo:
|
||||
return [Int](6...15)
|
||||
return [Int](6...16)
|
||||
case .usgsImageryOnly:
|
||||
return [Int](6...15)
|
||||
return [Int](6...16)
|
||||
case .terrain:
|
||||
return [Int](0...15)
|
||||
case .toner:
|
||||
|
|
@ -268,3 +268,87 @@ enum MapTileServerLinks: String, CaseIterable, Identifiable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MapOverlayServer: String, CaseIterable, Identifiable {
|
||||
|
||||
case baseReReflectivityCurrent
|
||||
case baseReReflectivityOneHourAgo
|
||||
case echoTopsEetCurrent
|
||||
case echoTopsEetOneHourAgo
|
||||
case q2OneHourPrecipitation
|
||||
case q2TwentyFourHourPrecipitation
|
||||
case q2FortyEightHourPrecipitation
|
||||
case q2SeventyTwoHourPrecipitation
|
||||
case mrmsHybridScanReflectivityComposite
|
||||
|
||||
var id: String { self.rawValue }
|
||||
var attribution: String {
|
||||
return "NEXRAD Weather tiles from Iowa State University Environmental Mesonet [OGC Web Services](https://mesonet.agron.iastate.edu/ogc/)."
|
||||
}
|
||||
var description: String {
|
||||
switch self {
|
||||
case .baseReReflectivityCurrent:
|
||||
return "Base Reflectivity current"
|
||||
case .baseReReflectivityOneHourAgo:
|
||||
return "Base Reflectivity one hour ago"
|
||||
case .echoTopsEetCurrent:
|
||||
return "Echo Tops EET current"
|
||||
case .echoTopsEetOneHourAgo:
|
||||
return "Echo Tops EET one hour ago"
|
||||
case .q2OneHourPrecipitation:
|
||||
return "Q2 1 Hour Precipitation"
|
||||
case .q2TwentyFourHourPrecipitation:
|
||||
return "Q2 24 Hour Precipitation"
|
||||
case .q2FortyEightHourPrecipitation:
|
||||
return "Q2 48 Hour Precipitation"
|
||||
case .q2SeventyTwoHourPrecipitation:
|
||||
return "Q2 72 Hour Precipitation"
|
||||
case .mrmsHybridScanReflectivityComposite:
|
||||
return "MRMS Hybrid-Scan Reflectivity Composite"
|
||||
}
|
||||
}
|
||||
var tileUrl: String {
|
||||
switch self {
|
||||
case .baseReReflectivityCurrent:
|
||||
return "https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/{z}/{x}/{y}"
|
||||
case .baseReReflectivityOneHourAgo:
|
||||
return "https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913-m55m/{z}/{x}/{y}"
|
||||
case .echoTopsEetCurrent:
|
||||
return "https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-eet-900913/{z}/{x}/{y}"
|
||||
case .echoTopsEetOneHourAgo:
|
||||
return "https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-eet-900913-m55m/{z}/{x}/{y}"
|
||||
case .q2OneHourPrecipitation:
|
||||
return "https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/q2-n1p-900913/{z}/{x}/{y}"
|
||||
case .q2TwentyFourHourPrecipitation:
|
||||
return "https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/q2-p24h-900913/{z}/{x}/{y}"
|
||||
case .q2FortyEightHourPrecipitation:
|
||||
return "https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/q2-p48h-900913/{z}/{x}/{y}"
|
||||
case .q2SeventyTwoHourPrecipitation:
|
||||
return "https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/q2-p72h-900913/{z}/{x}/{y}"
|
||||
case .mrmsHybridScanReflectivityComposite:
|
||||
return "https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/q2-hsr-900913/{z}/{x}/{y}"
|
||||
}
|
||||
}
|
||||
var zoomRange: [Int] {
|
||||
switch self {
|
||||
case .baseReReflectivityCurrent:
|
||||
return [Int](0...18)
|
||||
case .baseReReflectivityOneHourAgo:
|
||||
return [Int](0...18)
|
||||
case .echoTopsEetCurrent:
|
||||
return [Int](0...18)
|
||||
case .echoTopsEetOneHourAgo:
|
||||
return [Int](0...18)
|
||||
case .q2OneHourPrecipitation:
|
||||
return [Int](0...18)
|
||||
case .q2TwentyFourHourPrecipitation:
|
||||
return [Int](0...18)
|
||||
case .q2FortyEightHourPrecipitation:
|
||||
return [Int](0...18)
|
||||
case .q2SeventyTwoHourPrecipitation:
|
||||
return [Int](0...18)
|
||||
case .mrmsHybridScanReflectivityComposite:
|
||||
return [Int](0...18)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,26 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
|
|||
return "Short Range - Fast"
|
||||
}
|
||||
}
|
||||
func snrLimit() -> Float {
|
||||
switch self {
|
||||
case .longFast:
|
||||
return -17.5
|
||||
case .longSlow:
|
||||
return -7.5
|
||||
case .longModerate:
|
||||
return -17.5
|
||||
case .vLongSlow:
|
||||
return -20
|
||||
case .medSlow:
|
||||
return -15
|
||||
case .medFast:
|
||||
return -12.5
|
||||
case .shortSlow:
|
||||
return -10
|
||||
case .shortFast:
|
||||
return -7.5
|
||||
}
|
||||
}
|
||||
func protoEnumValue() -> Config.LoRaConfig.ModemPreset {
|
||||
switch self {
|
||||
case .longFast:
|
||||
|
|
|
|||
|
|
@ -38,7 +38,11 @@ extension String {
|
|||
return false
|
||||
} else {
|
||||
let characters = Array(self)
|
||||
return characters[0].isEmoji
|
||||
if characters.count <= 0 {
|
||||
return false
|
||||
} else {
|
||||
return characters[0].isEmoji
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,16 +115,35 @@ extension UserDefaults {
|
|||
}
|
||||
}
|
||||
|
||||
static var mapTileServer: MapTileServerLinks {
|
||||
static var mapTileServer: MapTileServer {
|
||||
get {
|
||||
|
||||
MapTileServerLinks(rawValue: UserDefaults.standard.string(forKey: "mapTileServer") ?? MapTileServerLinks.openStreetMap.rawValue) ?? MapTileServerLinks.openStreetMap
|
||||
MapTileServer(rawValue: UserDefaults.standard.string(forKey: "mapTileServer") ?? MapTileServer.openStreetMap.rawValue) ?? MapTileServer.openStreetMap
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: "mapTileServer")
|
||||
}
|
||||
}
|
||||
|
||||
static var enableOverlayServer: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableOverlayServer")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "enableOverlayServer")
|
||||
}
|
||||
}
|
||||
|
||||
static var mapOverlayServer: MapOverlayServer {
|
||||
get {
|
||||
|
||||
MapOverlayServer(rawValue: UserDefaults.standard.string(forKey: "mapOverlayServer") ?? MapOverlayServer.baseReReflectivityCurrent.rawValue) ?? MapOverlayServer.baseReReflectivityCurrent
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: "mapOverlayServer")
|
||||
}
|
||||
}
|
||||
|
||||
static var mapTilesAboveLabels: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "mapTilesAboveLabels")
|
||||
|
|
|
|||
|
|
@ -784,12 +784,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
positionPacket.timestamp = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(LocationHelper.currentAltitude)
|
||||
positionPacket.satsInView = UInt32(LocationHelper.satsInView)
|
||||
if LocationHelper.currentSpeed >= 0 {
|
||||
if !LocationHelper.currentSpeed.isNaN || !LocationHelper.currentSpeed.isInfinite {
|
||||
positionPacket.groundSpeed = UInt32(LocationHelper.currentSpeed * 3.6)
|
||||
}
|
||||
if LocationHelper.currentHeading >= 0 {
|
||||
if (!LocationHelper.currentHeading.isNaN || !LocationHelper.currentHeading.isInfinite) {
|
||||
positionPacket.groundTrack = UInt32(LocationHelper.currentHeading)
|
||||
}
|
||||
|
||||
var meshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(destNum)
|
||||
meshPacket.from = UInt32(fromNodeNum)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class OfflineTileManager: ObservableObject {
|
|||
}
|
||||
|
||||
// MARK: - Private properties
|
||||
private var overlay: MKTileOverlay { MKTileOverlay(urlTemplate: UserDefaults.mapTileServer.tileUrl.count > 1 ? UserDefaults.mapTileServer.tileUrl : MapTileServerLinks.openStreetMap.tileUrl) }
|
||||
private var overlay: MKTileOverlay { MKTileOverlay(urlTemplate: UserDefaults.mapTileServer.tileUrl.count > 1 ? UserDefaults.mapTileServer.tileUrl : MapTileServer.openStreetMap.tileUrl) }
|
||||
|
||||
private var documentsDirectory: URL { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! }
|
||||
|
||||
|
|
@ -48,6 +48,23 @@ class OfflineTileManager: ObservableObject {
|
|||
return Double(count) * size
|
||||
}
|
||||
|
||||
func getDownloadedSize(for mapTileLink: MapTileServer) -> Double {
|
||||
|
||||
var accumulatedSize: UInt64 = 0
|
||||
let mapTiles = try! fileManager.contentsOfDirectory(at: documentsDirectory.appendingPathComponent("tiles"), includingPropertiesForKeys: [])
|
||||
let matchingTiles = mapTiles.filter { fileName in
|
||||
let fileNameLower = fileName.absoluteString
|
||||
return fileNameLower.contains(mapTileLink.id)
|
||||
}
|
||||
print("Deleting \(matchingTiles.count) tiles for \(mapTileLink.id)")
|
||||
for tile in matchingTiles {
|
||||
let url = documentsDirectory.appendingPathComponent(tile.absoluteString)
|
||||
accumulatedSize += (try? url.regularFileAllocatedSize()) ?? 0
|
||||
}
|
||||
|
||||
return Double(accumulatedSize)
|
||||
}
|
||||
|
||||
func getDownloadedSize(for boundingBox: MKMapRect) -> Double {
|
||||
let paths = self.computeTileOverlayPaths(boundingBox: boundingBox)
|
||||
var accumulatedSize: UInt64 = 0
|
||||
|
|
@ -64,6 +81,19 @@ class OfflineTileManager: ObservableObject {
|
|||
createDirectoriesIfNecessary()
|
||||
}
|
||||
|
||||
func remove(for mapTileLink: MapTileServer) {
|
||||
|
||||
let mapTiles = try! fileManager.contentsOfDirectory(at: documentsDirectory.appendingPathComponent("tiles"), includingPropertiesForKeys: [])
|
||||
let matchingTiles = mapTiles.filter { fileName in
|
||||
let fileNameLower = fileName.absoluteString
|
||||
return fileNameLower.contains(mapTileLink.id)
|
||||
}
|
||||
print("Deleting \(matchingTiles.count) tiles for \(mapTileLink.id)")
|
||||
for tile in matchingTiles {
|
||||
try? fileManager.removeItem(at: tile.absoluteURL)
|
||||
}
|
||||
}
|
||||
|
||||
func remove(for boundingBox: MKMapRect) {
|
||||
let paths = self.computeTileOverlayPaths(boundingBox: boundingBox)
|
||||
for path in paths {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class NetworkManager {
|
|||
pathMonitor.pathUpdateHandler = {
|
||||
guard $0.status == .satisfied else {
|
||||
// No network available
|
||||
print("Network Not available")
|
||||
return pathMonitor.cancel()
|
||||
}
|
||||
pathMonitor.cancel()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV12.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV13.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,339 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22E261" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bitrate" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="errorCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" attributeType="String"/>
|
||||
<attribute name="hasGps" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="maxChannels" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messageTimeoutMsec" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="myNodeNum"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ntpServer" optional="YES" attributeType="String"/>
|
||||
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
|
||||
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
|
||||
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="macaddr" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -50,6 +50,7 @@ extension PositionEntity {
|
|||
|
||||
var annotaton: MKPointAnnotation {
|
||||
let pointAnn = MKPointAnnotation()
|
||||
|
||||
if nodeCoordinate != nil {
|
||||
pointAnn.coordinate = nodeCoordinate!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,6 +325,7 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
|
|||
newDeviceConfig.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
|
||||
newDeviceConfig.nodeInfoBroadcastSecs = Int32(config.nodeInfoBroadcastSecs)
|
||||
newDeviceConfig.doubleTapAsButtonPress = config.doubleTapAsButtonPress
|
||||
newDeviceConfig.isManaged = config.isManaged
|
||||
fetchedNode[0].deviceConfig = newDeviceConfig
|
||||
} else {
|
||||
fetchedNode[0].deviceConfig?.role = Int32(config.role.rawValue)
|
||||
|
|
@ -333,8 +334,9 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
|
|||
fetchedNode[0].deviceConfig?.buttonGpio = Int32(config.buttonGpio)
|
||||
fetchedNode[0].deviceConfig?.buzzerGpio = Int32(config.buzzerGpio)
|
||||
fetchedNode[0].deviceConfig?.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
|
||||
fetchedNode[0].deviceConfig?.doubleTapAsButtonPress = config.doubleTapAsButtonPress
|
||||
fetchedNode[0].deviceConfig?.nodeInfoBroadcastSecs = Int32(config.nodeInfoBroadcastSecs)
|
||||
fetchedNode[0].deviceConfig?.doubleTapAsButtonPress = config.doubleTapAsButtonPress
|
||||
fetchedNode[0].deviceConfig?.isManaged = config.isManaged
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
|
|
|
|||
|
|
@ -181,6 +181,11 @@ struct Config {
|
|||
/// Treat double tap interrupt on supported accelerometers as a button press if set to true
|
||||
var doubleTapAsButtonPress: Bool = false
|
||||
|
||||
///
|
||||
/// If true, device is considered to be "managed" by a mesh administrator
|
||||
/// Clients should then limit available configuration and administrative options inside the user interface
|
||||
var isManaged: Bool = false
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -1592,6 +1597,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
|
|||
6: .standard(proto: "rebroadcast_mode"),
|
||||
7: .standard(proto: "node_info_broadcast_secs"),
|
||||
8: .standard(proto: "double_tap_as_button_press"),
|
||||
9: .standard(proto: "is_managed"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -1608,6 +1614,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
|
|||
case 6: try { try decoder.decodeSingularEnumField(value: &self.rebroadcastMode) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.nodeInfoBroadcastSecs) }()
|
||||
case 8: try { try decoder.decodeSingularBoolField(value: &self.doubleTapAsButtonPress) }()
|
||||
case 9: try { try decoder.decodeSingularBoolField(value: &self.isManaged) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1638,6 +1645,9 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
|
|||
if self.doubleTapAsButtonPress != false {
|
||||
try visitor.visitSingularBoolField(value: self.doubleTapAsButtonPress, fieldNumber: 8)
|
||||
}
|
||||
if self.isManaged != false {
|
||||
try visitor.visitSingularBoolField(value: self.isManaged, fieldNumber: 9)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -1650,6 +1660,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
|
|||
if lhs.rebroadcastMode != rhs.rebroadcastMode {return false}
|
||||
if lhs.nodeInfoBroadcastSecs != rhs.nodeInfoBroadcastSecs {return false}
|
||||
if lhs.doubleTapAsButtonPress != rhs.doubleTapAsButtonPress {return false}
|
||||
if lhs.isManaged != rhs.isManaged {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
|
||||
let mapView = MKMapView()
|
||||
// Parameters
|
||||
var selectedMapLayer: MapLayer
|
||||
let selectedMapLayer: MapLayer
|
||||
let selectedWeatherLayer: MapOverlayServer = UserDefaults.mapOverlayServer
|
||||
let positions: [PositionEntity]
|
||||
let waypoints: [WaypointEntity]
|
||||
|
||||
|
|
@ -116,6 +117,17 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
default:
|
||||
mapView.mapType = .standard
|
||||
}
|
||||
// Weather radar
|
||||
if UserDefaults.enableOverlayServer {
|
||||
let locale = Locale.current
|
||||
if locale.region?.identifier ?? "no locale" == "US" {
|
||||
let overlay = MKTileOverlay(urlTemplate: selectedWeatherLayer.tileUrl)
|
||||
overlay.canReplaceMapContent = false
|
||||
overlay.minimumZ = selectedWeatherLayer.zoomRange.startIndex
|
||||
overlay.maximumZ = selectedWeatherLayer.zoomRange.endIndex
|
||||
mapView.addOverlay(overlay, level: .aboveLabels)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> MKMapView {
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
//
|
||||
// TilesView.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen on 5/6/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
|
||||
struct TilesView: View {
|
||||
|
||||
@ObservedObject var tileManager = OfflineTileManager.shared
|
||||
@State var totalDownloadedTileSize = ""
|
||||
|
||||
var body: some View {
|
||||
|
||||
Button(action: {
|
||||
tileManager.removeAll()
|
||||
totalDownloadedTileSize = tileManager.getAllDownloadedSize()
|
||||
print("delete all tiles")
|
||||
}) {
|
||||
|
||||
HStack {
|
||||
Image(systemName: "trash")
|
||||
.font(.callout)
|
||||
.foregroundColor(.red)
|
||||
Text("\("map.tiles.delete".localized) (\(totalDownloadedTileSize))")
|
||||
.font(.callout)
|
||||
.foregroundColor(.red)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.onAppear(perform: {
|
||||
totalDownloadedTileSize = tileManager.getAllDownloadedSize()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Previews
|
||||
struct TilesView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
TilesView()
|
||||
.previewLayout(.fixed(width: 300, height: 80))
|
||||
.environment(\.colorScheme, .light)
|
||||
}
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@ struct WaypointFormView: View {
|
|||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.controlSize(.regular)
|
||||
.disabled(bleManager.connectedPeripheral == nil)
|
||||
.padding(.bottom)
|
||||
|
||||
|
|
@ -178,7 +178,7 @@ struct WaypointFormView: View {
|
|||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.controlSize(.regular)
|
||||
.padding(.bottom)
|
||||
|
||||
if coordinate.waypointId > 0 {
|
||||
|
|
@ -230,7 +230,7 @@ struct WaypointFormView: View {
|
|||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.controlSize(.regular)
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,62 +11,64 @@ import CoreLocation
|
|||
import CoreData
|
||||
|
||||
struct NodeMap: View {
|
||||
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@ObservedObject var tileManager = OfflineTileManager.shared
|
||||
|
||||
|
||||
@State var selectedMapLayer: MapLayer = UserDefaults.mapLayer
|
||||
@State var enableMapRecentering: Bool = UserDefaults.enableMapRecentering
|
||||
@State var enableMapRouteLines: Bool = UserDefaults.enableMapRouteLines
|
||||
@State var enableMapNodeHistoryPins: Bool = UserDefaults.enableMapNodeHistoryPins
|
||||
@State var enableOfflineMaps: Bool = UserDefaults.enableOfflineMaps
|
||||
@State var selectedTileServer: MapTileServerLinks = UserDefaults.mapTileServer
|
||||
@State var selectedTileServer: MapTileServer = UserDefaults.mapTileServer
|
||||
@State var enableOfflineMapsMBTiles: Bool = UserDefaults.enableOfflineMapsMBTiles
|
||||
@State var enableOverlayServer: Bool = UserDefaults.enableOverlayServer
|
||||
@State var selectedOverlayServer: MapOverlayServer = UserDefaults.mapOverlayServer
|
||||
@State var mapTilesAboveLabels: Bool = UserDefaults.mapTilesAboveLabels
|
||||
|
||||
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)],
|
||||
predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none)
|
||||
private var positions: FetchedResults<PositionEntity>
|
||||
|
||||
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
|
||||
predicate: NSPredicate(
|
||||
format: "expire == nil || expire >= %@", Date() as NSDate
|
||||
), animation: .none)
|
||||
private var waypoints: FetchedResults<WaypointEntity>
|
||||
@State var waypointCoordinate: WaypointCoordinate?
|
||||
|
||||
|
||||
@State var selectedTracking: UserTrackingModes = .none
|
||||
|
||||
@State var isPresentingInfoSheet: Bool = false
|
||||
|
||||
@State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay(
|
||||
mapName: "offlinemap",
|
||||
tileType: "png",
|
||||
canReplaceMapContent: true
|
||||
)
|
||||
|
||||
mapName: "offlinemap",
|
||||
tileType: "png",
|
||||
canReplaceMapContent: true
|
||||
)
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
|
||||
|
||||
MapViewSwiftUI(
|
||||
onLongPress: { coord in
|
||||
onLongPress: { coord in
|
||||
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0)
|
||||
}, onWaypointEdit: { wpId in
|
||||
if wpId > 0 {
|
||||
waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
|
||||
}
|
||||
},
|
||||
selectedMapLayer: selectedMapLayer,
|
||||
positions: Array(positions),
|
||||
waypoints: Array(waypoints),
|
||||
userTrackingMode: selectedTracking.MKUserTrackingModeValue(),
|
||||
showNodeHistory: enableMapNodeHistoryPins,
|
||||
showRouteLines: enableMapRouteLines,
|
||||
customMapOverlay: self.customMapOverlay
|
||||
selectedMapLayer: selectedMapLayer,
|
||||
positions: Array(positions),
|
||||
waypoints: Array(waypoints),
|
||||
userTrackingMode: selectedTracking.MKUserTrackingModeValue(),
|
||||
showNodeHistory: enableMapNodeHistoryPins,
|
||||
showRouteLines: enableMapRouteLines,
|
||||
customMapOverlay: self.customMapOverlay
|
||||
)
|
||||
VStack(alignment: .trailing) {
|
||||
|
||||
|
|
@ -84,9 +86,9 @@ struct NodeMap: View {
|
|||
.ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
|
||||
.frame(maxHeight: .infinity)
|
||||
.sheet(item: $waypointCoordinate, content: { wpc in
|
||||
WaypointFormView(coordinate: wpc)
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
WaypointFormView(coordinate: wpc)
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
})
|
||||
.sheet(isPresented: $isPresentingInfoSheet) {
|
||||
VStack {
|
||||
|
|
@ -137,16 +139,47 @@ struct NodeMap: View {
|
|||
self.enableMapRouteLines.toggle()
|
||||
UserDefaults.enableMapRouteLines = self.enableMapRouteLines
|
||||
}
|
||||
|
||||
let locale = Locale.current
|
||||
if locale.region?.identifier ?? "no locale" == "US" {
|
||||
|
||||
Toggle(isOn: $enableOverlayServer) {
|
||||
|
||||
Label("Show Weather", systemImage: "cloud.fill")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.enableOverlayServer.toggle()
|
||||
UserDefaults.enableOverlayServer = self.enableOverlayServer
|
||||
}
|
||||
|
||||
if enableOverlayServer {
|
||||
Picker(selection: $selectedOverlayServer,
|
||||
label: Text("Radar")) {
|
||||
ForEach(MapOverlayServer.allCases, id: \.self) { mos in
|
||||
Text(mos.description)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.onChange(of: (selectedOverlayServer)) { newSelectedOverlayServer in
|
||||
UserDefaults.mapOverlayServer = newSelectedOverlayServer
|
||||
}
|
||||
Text(LocalizedStringKey(selectedOverlayServer.attribution))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
.padding(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
Section(header: Text("Offline Maps")) {
|
||||
Toggle(isOn: $enableOfflineMaps) {
|
||||
Text("Enable Offline Maps")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.enableOfflineMaps.toggle()
|
||||
UserDefaults.enableOfflineMaps = self.enableOfflineMaps
|
||||
if !self.enableOfflineMaps {
|
||||
.onChange(of: (enableOfflineMaps)) { newEnableOfflineMaps in
|
||||
UserDefaults.enableOfflineMaps = newEnableOfflineMaps
|
||||
if !newEnableOfflineMaps {
|
||||
if self.selectedMapLayer == .offline {
|
||||
self.selectedMapLayer = .standard
|
||||
}
|
||||
|
|
@ -159,15 +192,14 @@ struct NodeMap: View {
|
|||
|
||||
Picker(selection: $selectedTileServer,
|
||||
label: Text("Tile Server")) {
|
||||
ForEach(MapTileServerLinks.allCases, id: \.self) { tsl in
|
||||
ForEach(MapTileServer.allCases, id: \.self) { tsl in
|
||||
Text(tsl.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.onChange(of: (selectedTileServer)) { newSelectedTileServer in
|
||||
UserDefaults.mapTileServer = newSelectedTileServer
|
||||
selectedMapLayer = .standard
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.onChange(of: (selectedTileServer)) { newSelectedTileServer in
|
||||
UserDefaults.mapTileServer = newSelectedTileServer
|
||||
}
|
||||
Text("Attribution:")
|
||||
.fontWeight(.semibold)
|
||||
.font(.footnote)
|
||||
|
|
@ -214,7 +246,7 @@ struct NodeMap: View {
|
|||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([UserDefaults.enableOfflineMaps ? .large : .medium])
|
||||
.presentationDetents([UserDefaults.enableOfflineMaps || UserDefaults.enableOverlayServer ? .large : .medium])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,14 @@ struct AppSettings: View {
|
|||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@ObservedObject var tileManager = OfflineTileManager.shared
|
||||
@State var totalDownloadedTileSize = ""
|
||||
@StateObject var locationHelper = LocationHelper()
|
||||
@State var meshtasticUsername: String = UserDefaults.meshtasticUsername
|
||||
@State var provideLocation: Bool = UserDefaults.provideLocation
|
||||
@State var provideLocationInterval: Int = UserDefaults.provideLocationInterval
|
||||
@State private var isPresentingCoreDataResetConfirm = false
|
||||
@State private var isPresentingDeleteMapTilesConfirm = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
|
@ -40,8 +43,8 @@ struct AppSettings: View {
|
|||
.font(.footnote)
|
||||
}
|
||||
Label("Coordinate \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.latitude ?? 0)), \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.longitude ?? 0))", systemImage: "mappin")
|
||||
.font(.footnote)
|
||||
.textSelection(.enabled)
|
||||
.font(.footnote)
|
||||
.textSelection(.enabled)
|
||||
if locationHelper.locationManager.location?.verticalAccuracy ?? 0 > 0 {
|
||||
Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2")
|
||||
.font(.footnote)
|
||||
|
|
@ -55,56 +58,90 @@ struct AppSettings: View {
|
|||
.font(.footnote)
|
||||
}
|
||||
|
||||
}
|
||||
Section(header: Text("Location Settings")) {
|
||||
|
||||
Toggle(isOn: $provideLocation) {
|
||||
|
||||
Label("provide.location", systemImage: "location.circle.fill")
|
||||
.font(.footnote)
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
if UserDefaults.provideLocation {
|
||||
|
||||
Picker("update.interval", selection: $provideLocationInterval) {
|
||||
ForEach(LocationUpdateInterval.allCases) { lu in
|
||||
Text(lu.description)
|
||||
VStack {
|
||||
Picker("update.interval", selection: $provideLocationInterval) {
|
||||
ForEach(LocationUpdateInterval.allCases) { lu in
|
||||
Text(lu.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.onChange(of: (provideLocationInterval)) { newProvideLocationInterval in
|
||||
UserDefaults.provideLocationInterval = newProvideLocationInterval
|
||||
}
|
||||
Text("phone.gps.interval.description")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Section(header: Text("App Data")) {
|
||||
|
||||
Button {
|
||||
isPresentingCoreDataResetConfirm = true
|
||||
} label: {
|
||||
Label("clear.app.data", systemImage: "trash")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingCoreDataResetConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Erase all app data?", role: .destructive) {
|
||||
bleManager.disconnectPeripheral()
|
||||
clearCoreDataDatabase(context: context)
|
||||
UserDefaults.standard.reset()
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
}
|
||||
}
|
||||
if totalDownloadedTileSize != "0MB" {
|
||||
Section(header: Text("Map Tile Data")) {
|
||||
Button {
|
||||
isPresentingDeleteMapTilesConfirm = true
|
||||
} label: {
|
||||
Label("\("map.tiles.delete".localized) (\(totalDownloadedTileSize))", systemImage: "trash")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingDeleteMapTilesConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all map tiles?", role: .destructive) {
|
||||
tileManager.removeAll()
|
||||
totalDownloadedTileSize = tileManager.getAllDownloadedSize()
|
||||
print("delete all tiles")
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.onChange(of: (provideLocationInterval)) { newProvideLocationInterval in
|
||||
UserDefaults.provideLocationInterval = newProvideLocationInterval
|
||||
}
|
||||
|
||||
Text("phone.gps.interval.description")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
TilesView()
|
||||
}
|
||||
HStack {
|
||||
Button {
|
||||
isPresentingCoreDataResetConfirm = true
|
||||
} label: {
|
||||
Label("clear.app.data", systemImage: "trash")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingCoreDataResetConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Erase all app data?", role: .destructive) {
|
||||
bleManager.disconnectPeripheral()
|
||||
clearCoreDataDatabase(context: context)
|
||||
UserDefaults.standard.reset()
|
||||
UserDefaults.standard.synchronize()
|
||||
ForEach(MapTileServer.allCases, id: \.self) { tsl in
|
||||
|
||||
Button {
|
||||
tileManager.remove(for: tsl)
|
||||
totalDownloadedTileSize = tileManager.getAllDownloadedSize()
|
||||
} label: {
|
||||
Label("Delete \(tsl.description) Tiles", systemImage: "trash")
|
||||
.foregroundColor(.red)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear(perform: {
|
||||
totalDownloadedTileSize = tileManager.getAllDownloadedSize()
|
||||
})
|
||||
}
|
||||
.navigationTitle("app.settings")
|
||||
.navigationBarItems(trailing:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ struct DeviceConfig: View {
|
|||
@State var debugLogEnabled = false
|
||||
@State var rebroadcastMode = 0
|
||||
@State var doubleTapAsButtonPress = false
|
||||
@State var isManaged = false
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -88,6 +89,13 @@ struct DeviceConfig: View {
|
|||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Treat double tap on supported accelerometers as a user button press.")
|
||||
.font(.caption)
|
||||
|
||||
Toggle(isOn: $isManaged) {
|
||||
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.")
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
Section(header: Text("Debug")) {
|
||||
|
|
@ -217,6 +225,7 @@ struct DeviceConfig: View {
|
|||
dc.buzzerGpio = UInt32(buzzerGPIO)
|
||||
dc.rebroadcastMode = RebroadcastModes(rawValue: rebroadcastMode)?.protoEnumValue() ?? RebroadcastModes.all.protoEnumValue()
|
||||
dc.doubleTapAsButtonPress = doubleTapAsButtonPress
|
||||
dc.isManaged = isManaged
|
||||
|
||||
let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
|
|
@ -247,7 +256,7 @@ struct DeviceConfig: View {
|
|||
if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil {
|
||||
print("empty device config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
if node != nil && connectedNode != nil {
|
||||
if node != nil && connectedNode != nil && connectedNode?.user != nil {
|
||||
_ = bleManager.requestDeviceConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
}
|
||||
|
|
@ -295,12 +304,15 @@ struct DeviceConfig: View {
|
|||
}
|
||||
}
|
||||
.onChange(of: doubleTapAsButtonPress) { newDoubleTapAsButtonPress in
|
||||
|
||||
if node != nil && node!.deviceConfig != nil {
|
||||
|
||||
if newDoubleTapAsButtonPress != node!.deviceConfig!.doubleTapAsButtonPress { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: isManaged) { newIsManaged in
|
||||
if node != nil && node!.deviceConfig != nil {
|
||||
if newIsManaged != node!.deviceConfig!.isManaged { hasChanges = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
func setDeviceValues() {
|
||||
self.deviceRole = Int(node?.deviceConfig?.role ?? 0)
|
||||
|
|
@ -310,6 +322,7 @@ struct DeviceConfig: View {
|
|||
self.buzzerGPIO = Int(node?.deviceConfig?.buzzerGpio ?? 0)
|
||||
self.rebroadcastMode = Int(node?.deviceConfig?.rebroadcastMode ?? 0)
|
||||
self.doubleTapAsButtonPress = node?.deviceConfig?.doubleTapAsButtonPress ?? false
|
||||
self.isManaged = node?.deviceConfig?.isManaged ?? false
|
||||
self.hasChanges = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct Settings: View {
|
||||
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "user.longName", ascending: true)], animation: .default)
|
||||
|
|
@ -16,9 +16,9 @@ struct Settings: View {
|
|||
@State private var selectedNode: Int = 0
|
||||
@State private var connectedNodeNum: Int = 0
|
||||
@State private var initialLoad: Bool = true
|
||||
|
||||
|
||||
@State private var selection: SettingsSidebar = .about
|
||||
|
||||
|
||||
enum SettingsSidebar {
|
||||
case appSettings
|
||||
case shareChannels
|
||||
|
|
@ -41,7 +41,7 @@ struct Settings: View {
|
|||
case adminMessageLog
|
||||
case about
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
List {
|
||||
|
|
@ -50,7 +50,7 @@ struct Settings: View {
|
|||
} label: {
|
||||
Image(systemName: "questionmark.app")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
|
||||
Text("about.meshtastic")
|
||||
}
|
||||
.tag(SettingsSidebar.about)
|
||||
|
|
@ -63,227 +63,231 @@ struct Settings: View {
|
|||
}
|
||||
.tag(SettingsSidebar.appSettings)
|
||||
let node = nodes.first(where: { $0.num == connectedNodeNum })
|
||||
Section("Configure") {
|
||||
Picker("Configuring Node", selection: $selectedNode) {
|
||||
if selectedNode == 0 {
|
||||
Text("Connect to a Node").tag(0)
|
||||
}
|
||||
ForEach(nodes) { node in
|
||||
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Text("BLE Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
} else if node.metadata != nil {
|
||||
Text("Remote Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
} else {
|
||||
Text("Request Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
if !(node?.deviceConfig?.isManaged ?? false) {
|
||||
|
||||
Section("Configure") {
|
||||
Picker("Configuring Node", selection: $selectedNode) {
|
||||
if selectedNode == 0 {
|
||||
Text("Connect to a Node").tag(0)
|
||||
}
|
||||
ForEach(nodes) { node in
|
||||
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Text("BLE Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
} else if node.metadata != nil {
|
||||
Text("Remote Config: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
} else {
|
||||
Text("Request Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
.tag(Int(node.num))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(.automatic)
|
||||
.labelsHidden()
|
||||
.onChange(of: selectedNode) { newValue in
|
||||
if selectedNode > 0 {
|
||||
let node = nodes.first(where: { $0.num == newValue })
|
||||
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
|
||||
connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
|
||||
if node?.metadata == nil {
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context)
|
||||
|
||||
if adminMessageId > 0 {
|
||||
print("Sent node metadata request from node details")
|
||||
.pickerStyle(.automatic)
|
||||
.labelsHidden()
|
||||
.onChange(of: selectedNode) { newValue in
|
||||
if selectedNode > 0 {
|
||||
let node = nodes.first(where: { $0.num == newValue })
|
||||
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
|
||||
connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
|
||||
if connectedNode != nil && node?.metadata == nil {
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context)
|
||||
|
||||
if adminMessageId > 0 {
|
||||
print("Sent node metadata request from node details")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Section("radio.configuration") {
|
||||
|
||||
NavigationLink {
|
||||
ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum }))
|
||||
} label: {
|
||||
Image(systemName: "qrcode")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("share.channels")
|
||||
Section("radio.configuration") {
|
||||
|
||||
NavigationLink {
|
||||
ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum }))
|
||||
} label: {
|
||||
Image(systemName: "qrcode")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("share.channels")
|
||||
}
|
||||
.tag(SettingsSidebar.shareChannels)
|
||||
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
|
||||
|
||||
NavigationLink {
|
||||
UserConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "person.crop.rectangle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("user")
|
||||
}
|
||||
.tag(SettingsSidebar.userConfig)
|
||||
|
||||
NavigationLink {
|
||||
LoRaConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("lora")
|
||||
}
|
||||
.tag(SettingsSidebar.loraConfig)
|
||||
|
||||
NavigationLink {
|
||||
Channels(node: nodes.first(where: { $0.num == connectedNodeNum }))
|
||||
} label: {
|
||||
Image(systemName: "fibrechannel")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("channels")
|
||||
}
|
||||
.tag(SettingsSidebar.channelConfig)
|
||||
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
|
||||
|
||||
NavigationLink {
|
||||
BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("bluetooth")
|
||||
}
|
||||
.tag(SettingsSidebar.bluetoothConfig)
|
||||
|
||||
NavigationLink {
|
||||
DeviceConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "flipphone")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("device")
|
||||
}
|
||||
.tag(SettingsSidebar.deviceConfig)
|
||||
|
||||
NavigationLink {
|
||||
DisplayConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "display")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("display")
|
||||
}
|
||||
.tag(SettingsSidebar.displayConfig)
|
||||
|
||||
NavigationLink {
|
||||
NetworkConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "network")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("network")
|
||||
}
|
||||
.tag(SettingsSidebar.networkConfig)
|
||||
|
||||
NavigationLink {
|
||||
PositionConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "location")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("position")
|
||||
}
|
||||
.tag(SettingsSidebar.positionConfig)
|
||||
|
||||
}
|
||||
.tag(SettingsSidebar.shareChannels)
|
||||
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
|
||||
|
||||
NavigationLink {
|
||||
UserConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "person.crop.rectangle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("user")
|
||||
Section("module.configuration") {
|
||||
|
||||
NavigationLink {
|
||||
CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "list.bullet.rectangle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
Text("canned.messages")
|
||||
}
|
||||
.tag(SettingsSidebar.cannedMessagesConfig)
|
||||
|
||||
NavigationLink {
|
||||
ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "megaphone")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("external.notification")
|
||||
}
|
||||
.tag(SettingsSidebar.externalNotificationConfig)
|
||||
|
||||
NavigationLink {
|
||||
MQTTConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "dot.radiowaves.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("mqtt")
|
||||
}
|
||||
.tag(SettingsSidebar.mqttConfig)
|
||||
|
||||
NavigationLink {
|
||||
RangeTestConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "point.3.connected.trianglepath.dotted")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("range.test")
|
||||
}
|
||||
NavigationLink {
|
||||
RtttlConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "music.note.list")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("ringtone")
|
||||
}
|
||||
.tag(SettingsSidebar.ringtoneConfig)
|
||||
|
||||
NavigationLink {
|
||||
SerialConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "terminal")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("serial")
|
||||
}
|
||||
.tag(SettingsSidebar.serialConfig)
|
||||
|
||||
NavigationLink {
|
||||
TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "chart.xyaxis.line")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("telemetry")
|
||||
}
|
||||
.tag(SettingsSidebar.telemetryConfig)
|
||||
}
|
||||
.tag(SettingsSidebar.userConfig)
|
||||
|
||||
NavigationLink {
|
||||
LoRaConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("lora")
|
||||
|
||||
Section(header: Text("logging")) {
|
||||
NavigationLink {
|
||||
MeshLog()
|
||||
} label: {
|
||||
Image(systemName: "list.bullet.rectangle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("mesh.log")
|
||||
}
|
||||
.tag(SettingsSidebar.meshLog)
|
||||
|
||||
NavigationLink {
|
||||
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
|
||||
AdminMessageList(user: connectedNode?.user)
|
||||
} label: {
|
||||
Image(systemName: "building.columns")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("admin.log")
|
||||
}
|
||||
.tag(SettingsSidebar.adminMessageLog)
|
||||
}
|
||||
.tag(SettingsSidebar.loraConfig)
|
||||
|
||||
NavigationLink {
|
||||
Channels(node: nodes.first(where: { $0.num == connectedNodeNum }))
|
||||
} label: {
|
||||
Image(systemName: "fibrechannel")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("channels")
|
||||
Section(header: Text("Firmware")) {
|
||||
NavigationLink {
|
||||
Firmware(node: nodes.first(where: { $0.num == connectedNodeNum }))
|
||||
} label: {
|
||||
Image(systemName: "arrow.up.arrow.down.square")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
Text("Firmware Updates")
|
||||
}
|
||||
.tag(SettingsSidebar.about)
|
||||
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
|
||||
}
|
||||
.tag(SettingsSidebar.channelConfig)
|
||||
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
|
||||
|
||||
NavigationLink {
|
||||
BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("bluetooth")
|
||||
}
|
||||
.tag(SettingsSidebar.bluetoothConfig)
|
||||
|
||||
NavigationLink {
|
||||
DeviceConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "flipphone")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("device")
|
||||
}
|
||||
.tag(SettingsSidebar.deviceConfig)
|
||||
|
||||
NavigationLink {
|
||||
DisplayConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "display")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("display")
|
||||
}
|
||||
.tag(SettingsSidebar.displayConfig)
|
||||
|
||||
NavigationLink {
|
||||
NetworkConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "network")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("network")
|
||||
}
|
||||
.tag(SettingsSidebar.networkConfig)
|
||||
|
||||
NavigationLink {
|
||||
PositionConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "location")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("position")
|
||||
}
|
||||
.tag(SettingsSidebar.positionConfig)
|
||||
|
||||
}
|
||||
Section("module.configuration") {
|
||||
|
||||
NavigationLink {
|
||||
CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "list.bullet.rectangle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
Text("canned.messages")
|
||||
}
|
||||
.tag(SettingsSidebar.cannedMessagesConfig)
|
||||
|
||||
NavigationLink {
|
||||
ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "megaphone")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("external.notification")
|
||||
}
|
||||
.tag(SettingsSidebar.externalNotificationConfig)
|
||||
|
||||
NavigationLink {
|
||||
MQTTConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "dot.radiowaves.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("mqtt")
|
||||
}
|
||||
.tag(SettingsSidebar.mqttConfig)
|
||||
|
||||
NavigationLink {
|
||||
RangeTestConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "point.3.connected.trianglepath.dotted")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("range.test")
|
||||
}
|
||||
NavigationLink {
|
||||
RtttlConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "music.note.list")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("ringtone")
|
||||
}
|
||||
.tag(SettingsSidebar.ringtoneConfig)
|
||||
|
||||
NavigationLink {
|
||||
SerialConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "terminal")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("serial")
|
||||
}
|
||||
.tag(SettingsSidebar.serialConfig)
|
||||
|
||||
NavigationLink {
|
||||
TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "chart.xyaxis.line")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("telemetry")
|
||||
}
|
||||
.tag(SettingsSidebar.telemetryConfig)
|
||||
}
|
||||
Section(header: Text("logging")) {
|
||||
NavigationLink {
|
||||
MeshLog()
|
||||
} label: {
|
||||
Image(systemName: "list.bullet.rectangle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("mesh.log")
|
||||
}
|
||||
.tag(SettingsSidebar.meshLog)
|
||||
|
||||
NavigationLink {
|
||||
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
|
||||
AdminMessageList(user: connectedNode?.user)
|
||||
} label: {
|
||||
Image(systemName: "building.columns")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("admin.log")
|
||||
}
|
||||
.tag(SettingsSidebar.adminMessageLog)
|
||||
}
|
||||
Section(header: Text("Firmware")) {
|
||||
NavigationLink {
|
||||
Firmware(node: nodes.first(where: { $0.num == connectedNodeNum }))
|
||||
} label: {
|
||||
Image(systemName: "arrow.up.arrow.down.square")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
Text("Firmware Updates")
|
||||
}
|
||||
.tag(SettingsSidebar.about)
|
||||
.disabled(selectedNode > 0 && selectedNode != connectedNodeNum)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
|
@ -297,11 +301,11 @@ struct Settings: View {
|
|||
.listStyle(GroupedListStyle())
|
||||
.navigationTitle("settings")
|
||||
.navigationBarItems(leading:
|
||||
MeshtasticLogo()
|
||||
MeshtasticLogo()
|
||||
)
|
||||
}
|
||||
detail: {
|
||||
Text("select.menu.item")
|
||||
}
|
||||
detail: {
|
||||
Text("select.menu.item")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@
|
|||
"map"="Mesh Map";
|
||||
"map.type"="Default Type";
|
||||
"map.centering"="Centering Mode";
|
||||
"map.tiles.delete"="Delete Cached Map Tiles";
|
||||
"map.tiles.delete"="Delete All Map Tiles";
|
||||
"map.recentering"="Automatic Re-centering";
|
||||
"map.usertrackingmode"="User tracking mode";
|
||||
"map.usertrackingmode.follow"="Follow";
|
||||
|
|
@ -207,7 +207,7 @@
|
|||
"position"="Position";
|
||||
"position.config"="Position Config";
|
||||
"preferred.radio"="Preferred Radio";
|
||||
"provide.location"="Provide location to mesh";
|
||||
"provide.location"="Share location";
|
||||
"radio.configuration"="Radio Configuration";
|
||||
"range.test"="Range Test";
|
||||
"range.test.config"="Range Test Config";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue