2.7.3 Working Changes (#1404)
* Remove non functional module override button * Remove stale keys * Onboarding and lora config bug fixes * Add Annotations view and try and simplify online annimations to improve performance. * Bump version * Fix proto bug * Don't show ignored nodes on the mesh map * More node annotation animation improvements * Ham * Remove liquid glass form icon * Update MQTT config logic * Liquid glass chirpy and ham on the watch * Use Hops away value for DM's (#1409) * Set hopLimit for DM messages (DM's and Exchange position) to the hops away value for the node you are sending to. * Update Meshtastic/Accessory/Accessory Manager/AccessoryManager+ToRadio.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Icons * 🐰 * DataDog action logging (#1411) Co-authored-by: Jake-B <jake-b@users.noreply.github.com> * Update location usage details * Good doggo (#1414) * DataDog action logging * Filter version hash --------- Co-authored-by: Jake-B <jake-b@users.noreply.github.com> * Update Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Meshtastic/Helpers/LocationsHandler.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Meshtastic/Views/Nodes/Helpers/Map/MapContent/AnimatedNodePin.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: jake-b <1012393+jake-b@users.noreply.github.com> Co-authored-by: Jake-B <jake-b@users.noreply.github.com>
|
|
@ -1629,41 +1629,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"8" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "8"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "8"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "8"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "8"
|
||||
}
|
||||
},
|
||||
"zh-Hant-TW" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"12 Hour Clock" : {
|
||||
"localizations" : {
|
||||
"ja" : {
|
||||
|
|
@ -9242,34 +9207,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Currently showing modules that may not be supported by this node." : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mostra i moduli che potrebbero non essere supportati al momento da questo nodo."
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "現在、このノードでサポートされていない可能性のあるモジュールを表示しています。"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Тренутно су приказани модули који можда нису подржани од стране овог чвора."
|
||||
}
|
||||
},
|
||||
"zh-Hant-TW" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "目前顯示的模組可能不受此節點支援。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Currently the recommended way to update ESP32 devices is using the web flasher on a desktop computer from a chrome based browser. It does not work on mobile devices or over BLE." : {
|
||||
"localizations" : {
|
||||
"it" : {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
23D316932E5618D2002FA4FB /* AsyncGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D316922E5618D2002FA4FB /* AsyncGate.swift */; };
|
||||
23D9D9392E50DA97005D1C18 /* ResettableTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D9D9382E50DA97005D1C18 /* ResettableTimer.swift */; };
|
||||
23E23F922E392C2B00919073 /* LogRecord+StringRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23E23F912E392C2B00919073 /* LogRecord+StringRepresentation.swift */; };
|
||||
23F061B32E7B056600A1E2EA /* Logger+DataDog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23F061B22E7B056600A1E2EA /* Logger+DataDog.swift */; };
|
||||
23F488122E32980B002C776F /* AccessoryManager+Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23F488112E32980B002C776F /* AccessoryManager+Position.swift */; };
|
||||
23FF00B62E323C75001DF095 /* AccessoryManager+Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FF00B52E323C75001DF095 /* AccessoryManager+Connect.swift */; };
|
||||
251926852C3BA97800249DF5 /* FavoriteNodeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */; };
|
||||
|
|
@ -203,6 +204,9 @@
|
|||
DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC52975DBFD007C176F /* QueryCoreData.swift */; };
|
||||
DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; };
|
||||
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; };
|
||||
DD98EB212E7A31140016320A /* AppIcon_Ham.icon in Resources */ = {isa = PBXBuildFile; fileRef = DD98EB202E7A31140016320A /* AppIcon_Ham.icon */; };
|
||||
DD98EB292E7A42CC0016320A /* AppIcon_Chirpy.icon in Resources */ = {isa = PBXBuildFile; fileRef = DD98EB282E7A42CC0016320A /* AppIcon_Chirpy.icon */; };
|
||||
DD98EB2B2E7A838D0016320A /* AppIcon_MPowered.icon in Resources */ = {isa = PBXBuildFile; fileRef = DD98EB2A2E7A838D0016320A /* AppIcon_MPowered.icon */; };
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* IntervalEnums.swift */; };
|
||||
DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0B6B1294CDC55001356EC /* Channels.swift */; };
|
||||
DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; };
|
||||
|
|
@ -210,6 +214,7 @@
|
|||
DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA951592BC6624100CEA535 /* TelemetryWeather.swift */; };
|
||||
DDA9515C2BC6631200CEA535 /* TelemetryEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */; };
|
||||
DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */; };
|
||||
DDA9F5E82E77FAC100E70DEB /* AnimatedNodePin.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9F5E72E77FAA600E70DEB /* AnimatedNodePin.swift */; };
|
||||
DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580C2B0DAA9E00147258 /* Routes.swift */; };
|
||||
DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */; };
|
||||
DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */; };
|
||||
|
|
@ -366,6 +371,7 @@
|
|||
23D316922E5618D2002FA4FB /* AsyncGate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncGate.swift; sourceTree = "<group>"; };
|
||||
23D9D9382E50DA97005D1C18 /* ResettableTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResettableTimer.swift; sourceTree = "<group>"; };
|
||||
23E23F912E392C2B00919073 /* LogRecord+StringRepresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LogRecord+StringRepresentation.swift"; sourceTree = "<group>"; };
|
||||
23F061B22E7B056600A1E2EA /* Logger+DataDog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+DataDog.swift"; sourceTree = "<group>"; };
|
||||
23F488112E32980B002C776F /* AccessoryManager+Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessoryManager+Position.swift"; sourceTree = "<group>"; };
|
||||
23FF00B52E323C75001DF095 /* AccessoryManager+Connect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessoryManager+Connect.swift"; sourceTree = "<group>"; };
|
||||
251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteNodeButton.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -538,6 +544,9 @@
|
|||
DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV32.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = "<group>"; };
|
||||
DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
|
||||
DD98EB202E7A31140016320A /* AppIcon_Ham.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon_Ham.icon; sourceTree = "<group>"; };
|
||||
DD98EB282E7A42CC0016320A /* AppIcon_Chirpy.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon_Chirpy.icon; sourceTree = "<group>"; };
|
||||
DD98EB2A2E7A838D0016320A /* AppIcon_MPowered.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon_MPowered.icon; sourceTree = "<group>"; };
|
||||
DD994B68295F88B60013760A /* IntervalEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntervalEnums.swift; sourceTree = "<group>"; };
|
||||
DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 30.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -547,6 +556,7 @@
|
|||
DDA951592BC6624100CEA535 /* TelemetryWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = "<group>"; };
|
||||
DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryEnums.swift; sourceTree = "<group>"; };
|
||||
DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndoorAirQuality.swift; sourceTree = "<group>"; };
|
||||
DDA9F5E72E77FAA600E70DEB /* AnimatedNodePin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedNodePin.swift; sourceTree = "<group>"; };
|
||||
DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV20.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDAB580C2B0DAA9E00147258 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routes.swift; sourceTree = "<group>"; };
|
||||
DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEntityExtension.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -1196,8 +1206,11 @@
|
|||
DDC2E18926CE24F70042C5E4 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2339EA992E6C65DC0032C234 /* AppIconDebug.icon */,
|
||||
2339EA972E6C65570032C234 /* AppIcon.icon */,
|
||||
2339EA992E6C65DC0032C234 /* AppIconDebug.icon */,
|
||||
DD98EB282E7A42CC0016320A /* AppIcon_Chirpy.icon */,
|
||||
DD98EB202E7A31140016320A /* AppIcon_Ham.icon */,
|
||||
DD98EB2A2E7A838D0016320A /* AppIcon_MPowered.icon */,
|
||||
DDB75A192A05EB67006ED576 /* alpha.png */,
|
||||
DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */,
|
||||
DD0E21002B8A6BC500F2D100 /* DeviceHardware.json */,
|
||||
|
|
@ -1344,6 +1357,7 @@
|
|||
DDD5BB172C2F9C36007E03CA /* OSLogEntryLog.swift */,
|
||||
DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */,
|
||||
DDD5BB0C2C285F00007E03CA /* Logger.swift */,
|
||||
23F061B22E7B056600A1E2EA /* Logger+DataDog.swift */,
|
||||
DD6F65732C6CB80A0053C113 /* View.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
|
|
@ -1352,6 +1366,7 @@
|
|||
DDDC22362BA9232C002C44F1 /* MapContent */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDA9F5E72E77FAA600E70DEB /* AnimatedNodePin.swift */,
|
||||
DDDC22372BA92344002C44F1 /* MeshMapContent.swift */,
|
||||
DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */,
|
||||
);
|
||||
|
|
@ -1514,11 +1529,14 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */,
|
||||
DD98EB292E7A42CC0016320A /* AppIcon_Chirpy.icon in Resources */,
|
||||
25AECD4F2C2F723200862C8E /* Localizable.xcstrings in Resources */,
|
||||
DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */,
|
||||
2339EA9A2E6C65DC0032C234 /* AppIconDebug.icon in Resources */,
|
||||
DD98EB212E7A31140016320A /* AppIcon_Ham.icon in Resources */,
|
||||
2339EA982E6C65570032C234 /* AppIcon.icon in Resources */,
|
||||
DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */,
|
||||
DD98EB2B2E7A838D0016320A /* AppIcon_MPowered.icon in Resources */,
|
||||
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */,
|
||||
DD0E21012B8A6F1300F2D100 /* DeviceHardware.json in Resources */,
|
||||
DDDBC87B2BC62E4E001E8DF7 /* Settings.bundle in Resources */,
|
||||
|
|
@ -1655,6 +1673,7 @@
|
|||
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */,
|
||||
DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */,
|
||||
DD1BEF4C2E030D310090CE24 /* KeyBackupStatus.swift in Sources */,
|
||||
23F061B32E7B056600A1E2EA /* Logger+DataDog.swift in Sources */,
|
||||
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */,
|
||||
DD6F65722C6AB8EC0053C113 /* SecureInput.swift in Sources */,
|
||||
DD13AA492AB73BF400BA0C98 /* PositionPopover.swift in Sources */,
|
||||
|
|
@ -1763,6 +1782,7 @@
|
|||
DD4975A52B147BA90026544E /* AmbientLightingConfig.swift in Sources */,
|
||||
3D3417D22E2DC260006A988B /* MapDataManager.swift in Sources */,
|
||||
D93068D92B81509C0066FBC8 /* TapbackResponses.swift in Sources */,
|
||||
DDA9F5E82E77FAC100E70DEB /* AnimatedNodePin.swift in Sources */,
|
||||
DDF82CBD2D5BC69200DC25EC /* NavigateToButton.swift in Sources */,
|
||||
8D3F8A3F2D44BB02009EAAA4 /* PowerMetrics.swift in Sources */,
|
||||
2519268A2C3BB1B200249DF5 /* ExchangePositionsButton.swift in Sources */,
|
||||
|
|
@ -2052,7 +2072,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.7.2;
|
||||
MARKETING_VERSION = 2.7.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -2086,7 +2106,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.7.2;
|
||||
MARKETING_VERSION = 2.7.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -2117,7 +2137,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.7.2;
|
||||
MARKETING_VERSION = 2.7.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -2149,7 +2169,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.7.2;
|
||||
MARKETING_VERSION = 2.7.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
|
|||
|
|
@ -147,6 +147,27 @@ extension AccessoryManager {
|
|||
if transport.requiresPeriodicHeartbeat {
|
||||
await self.setupPeriodicHeartbeat()
|
||||
}
|
||||
|
||||
|
||||
|
||||
if let device = self.activeConnection?.device {
|
||||
var version: String?
|
||||
if let firmwareVersion = device.firmwareVersion {
|
||||
if let lastDotIndex = firmwareVersion.lastIndex(of: ".") {
|
||||
version = String(firmwareVersion[...(lastDotIndex)].dropLast())
|
||||
} else {
|
||||
version = firmwareVersion
|
||||
}
|
||||
}
|
||||
|
||||
let connectionAttributes: [String: any Encodable] = [
|
||||
"firmware_version": version,
|
||||
"transport_type": device.transportType.rawValue, // e.g., "websocket", "http/2", "quic"
|
||||
"hardware_model": device.hardwareModel,
|
||||
"nodes": self.expectedNodeDBSize
|
||||
]
|
||||
Logger.datadog.action(name: "connect", attributes: connectionAttributes )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ extension AccessoryManager {
|
|||
if let user = nodeInfo.user {
|
||||
updateDevice(deviceId: activeDevice.id, key: \.shortName, value: user.shortName ?? "?")
|
||||
updateDevice(deviceId: activeDevice.id, key: \.longName, value: user.longName ?? "Unknown".localized)
|
||||
updateDevice(deviceId: activeDevice.id, key: \.hardwareModel, value: user.hwModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ extension AccessoryManager {
|
|||
}
|
||||
}
|
||||
|
||||
public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) async throws {
|
||||
public func sendPosition(channel: Int32, destNum: Int64, hopsAway: Int32 = 0, wantResponse: Bool) async throws {
|
||||
guard let fromNodeNum = activeConnection?.device.num else {
|
||||
throw AccessoryError.ioFailed("Not connected to any device")
|
||||
}
|
||||
|
|
@ -41,6 +41,9 @@ extension AccessoryManager {
|
|||
meshPacket.to = UInt32(destNum)
|
||||
meshPacket.channel = UInt32(channel)
|
||||
meshPacket.from = UInt32(fromNodeNum)
|
||||
if hopsAway > 0 {
|
||||
meshPacket.hopLimit = UInt32(truncatingIfNeeded: hopsAway)
|
||||
}
|
||||
var dataMessage = DataMessage()
|
||||
if let serializedData: Data = try? positionPacket.serializedData() {
|
||||
dataMessage.payload = serializedData
|
||||
|
|
|
|||
|
|
@ -362,6 +362,10 @@ extension AccessoryManager {
|
|||
meshPacket.id = UInt32(newMessage.messageId)
|
||||
if toUserNum > 0 {
|
||||
meshPacket.to = UInt32(toUserNum)
|
||||
let hopsAway = newMessage.toUser?.userNode?.hopsAway ?? 0
|
||||
if hopsAway > Int32(truncatingIfNeeded: newMessage.toUser?.userNode?.loRaConfig?.hopLimit ?? 0) {
|
||||
meshPacket.hopLimit = UInt32(truncatingIfNeeded: hopsAway)
|
||||
}
|
||||
} else {
|
||||
meshPacket.to = Constants.maximumNodeNum
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ struct Device: Identifiable, Hashable {
|
|||
var shortName: String?
|
||||
var longName: String?
|
||||
var firmwareVersion: String?
|
||||
var hardwareModel: String?
|
||||
var rssi: Int?
|
||||
var lastUpdate: Date?
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "meshtastic_ham.jpg",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "logo-dark.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"filename" : "logo-dark 1.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 488 KiB |
BIN
Meshtastic/Assets.xcassets/AppIcon_Ham.appiconset/logo-dark.png
Normal file
|
After Width: | Height: | Size: 488 KiB |
|
After Width: | Height: | Size: 120 KiB |
21
Meshtastic/Assets.xcassets/AppIcon_Ham_Dark_Thumb.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "logo-dark.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/AppIcon_Ham_Dark_Thumb.imageset/logo-dark.png
vendored
Normal file
|
After Width: | Height: | Size: 488 KiB |
21
Meshtastic/Assets.xcassets/AppIcon_Ham_Thumb.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "meshtastic_ham.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/AppIcon_Ham_Thumb.imageset/meshtastic_ham.jpg
vendored
Normal file
|
After Width: | Height: | Size: 120 KiB |
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "MPowered.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "MPowered 1.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"filename" : "MPowered 2.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 136 KiB |
21
Meshtastic/Assets.xcassets/AppIcon_MPowered_Dark_Thumb.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "MPowered.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/AppIcon_MPowered_Dark_Thumb.imageset/MPowered.png
vendored
Normal file
|
After Width: | Height: | Size: 136 KiB |
21
Meshtastic/Assets.xcassets/AppIcon_MPowered_Thumb.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "MPowered.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/AppIcon_MPowered_Thumb.imageset/MPowered.png
vendored
Normal file
|
After Width: | Height: | Size: 136 KiB |
69
Meshtastic/Extensions/Logger+DataDog.swift
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// Logger+DataDog.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 9/17/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import DatadogRUM
|
||||
import DatadogLogs
|
||||
|
||||
struct DatadogLogger {
|
||||
private let osLogger: os.Logger
|
||||
private let ddLogger: any DatadogLogs.LoggerProtocol
|
||||
|
||||
// Initialize with a subsystem and category, similar to Logger
|
||||
fileprivate init(subsystem: String, category: String) {
|
||||
self.osLogger = Logger(subsystem: subsystem, category: category)
|
||||
self.ddLogger = DatadogLogs.Logger.create(
|
||||
with: Logger.Configuration(
|
||||
name: "gvh.Meshtastic",
|
||||
networkInfoEnabled: true,
|
||||
remoteLogThreshold: .debug,
|
||||
consoleLogFormat: .short
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ✨ os.Logger functions like debug, info, etc., are not normal functions.
|
||||
// They rely on compiler magic to parse the interpolated string, identify the
|
||||
// privacy modifiers (.public, .private), and handle the data securely without
|
||||
// ever creating a potentially sensitive string in your app's memory.
|
||||
// To do this, the compiler must see the string literal at the point of the call.
|
||||
// Since this is going to Datadog, care should be taken to only use these functions
|
||||
// with public debug data.
|
||||
func debug(_ message: String) {
|
||||
osLogger.debug("\(message, privacy: .public)")
|
||||
ddLogger.debug(message)
|
||||
}
|
||||
|
||||
func info(_ message: String) {
|
||||
osLogger.info("\(message, privacy: .public)")
|
||||
ddLogger.info(message)
|
||||
}
|
||||
|
||||
func warning(_ message: String) {
|
||||
osLogger.warning("\(message, privacy: .public)")
|
||||
ddLogger.warn(message)
|
||||
}
|
||||
|
||||
func error(_ message: String) {
|
||||
osLogger.error("\(message, privacy: .public)")
|
||||
ddLogger.error(message)
|
||||
}
|
||||
|
||||
// MARK: - Methods for RUM actions
|
||||
func action(name: String, attributes: [String: any Encodable]? = nil) {
|
||||
RUMMonitor.shared().addAction(
|
||||
type: .custom,
|
||||
name: name,
|
||||
attributes: attributes ?? [:]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension os.Logger {
|
||||
static let datadog = DatadogLogger(subsystem: "datadog", category: "🐶 DataDog")
|
||||
}
|
||||
|
|
@ -74,12 +74,12 @@ import OSLog
|
|||
// we'll resume the continuation with .notDetermined to prevent a leak.
|
||||
Task { @MainActor in // Ensure this task runs on the MainActor
|
||||
do {
|
||||
try await Task.sleep(for: .seconds(10)) // Wait for 10 seconds
|
||||
try await Task.sleep(for: .seconds(5)) // Wait for 5 seconds
|
||||
if let currentContinuation = self.permissionContinuation {
|
||||
// If the continuation hasn't been nilled out yet, it means
|
||||
// locationManagerDidChangeAuthorization hasn't been called.
|
||||
Logger.services.warning("📍 [App] Location permission request timed out. Resuming continuation with .notDetermined.")
|
||||
currentContinuation.resume(returning: .notDetermined)
|
||||
currentContinuation.resume(returning: .denied)
|
||||
self.permissionContinuation = nil // Clear the reference
|
||||
}
|
||||
} catch is CancellationError {
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>We use bluetooth to connect to nearby Meshtastic Devices</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>Bluetooth is used to connect an iPhone to a user's meshtastic device to allow text messaging and location data for the mesh network.</string>
|
||||
<string>Bluetooth is used to connect an iPhone to a user's meshtastic device to allow text messaging and location data for the mesh network.</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_meshtastic._tcp</string>
|
||||
|
|
@ -110,13 +110,13 @@
|
|||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>We use local networking to connect to network-based nodes.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>We use your location to display it on the mesh map as well as to have GPS coordinates to send to the connected device. Route Recording uses location in the background.</string>
|
||||
<string>We use your location to display it on the mesh map, show and filter by distance as well as to have GPS coordinates to send to the connected device. Route Recording uses location in the background.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>We use your location to display it on the mesh map as well as to have GPS coordinates to send to the connected device.</string>
|
||||
<key>NSLocationUsageDescription</key>
|
||||
<string>We use your location to display it on the mesh map as well as to have GPS coordinates to send to the connected device.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>We use your location to display it on the mesh map as well as to have GPS coordinates to send to the connected device.</string>
|
||||
<string>We use your location to display it on the mesh map, show and filter by distance as well as to have GPS coordinates to send to the connected device. Route Recording uses location in the background.</string>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
<key>Privacy – Bluetooth Always Usage Description</key>
|
||||
|
|
|
|||
|
|
@ -75,11 +75,6 @@ struct MeshtasticAppleApp: App {
|
|||
trackBackgroundEvents: true
|
||||
)
|
||||
)
|
||||
let attributes: [String: Encodable] = [
|
||||
"firmware_version": UserDefaults.firmwareVersion,
|
||||
"hardware_model": UserDefaults.hardwareModel
|
||||
]
|
||||
RUMMonitor.shared().addAttributes(attributes)
|
||||
#if DEBUG
|
||||
SessionReplay.enable(
|
||||
with: SessionReplay.Configuration(
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 14 KiB |
51
Meshtastic/Resources/AppIcon_Chirpy.icon/icon.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"fill" : {
|
||||
"automatic-gradient" : "extended-gray:1.00000,1.00000"
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"image-name" : "chirpy-2.svg",
|
||||
"name" : "chirpy-2",
|
||||
"position-specializations" : [
|
||||
{
|
||||
"idiom" : "square",
|
||||
"value" : {
|
||||
"scale" : 0.48,
|
||||
"translation-in-points" : [
|
||||
-18.390625,
|
||||
127.2421875
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"idiom" : "watchOS",
|
||||
"value" : {
|
||||
"scale" : 0.52,
|
||||
"translation-in-points" : [
|
||||
-7,
|
||||
145.9296875
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Resources/AppIcon_Ham.icon/Assets/ham.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
39
Meshtastic/Resources/AppIcon_Ham.icon/icon.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"fill" : {
|
||||
"automatic-gradient" : "display-p3:0.54507,0.90596,0.61177,1.00000"
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"blend-mode" : "normal",
|
||||
"glass" : false,
|
||||
"hidden" : false,
|
||||
"image-name" : "ham.png",
|
||||
"name" : "ham",
|
||||
"position" : {
|
||||
"scale" : 1.65,
|
||||
"translation-in-points" : [
|
||||
-2.0625,
|
||||
7.703125
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 999.988833921388505 965.773921479685669">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #2c2d3c;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2 {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #67ea94;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #fff;
|
||||
stroke: #2c2d3c;
|
||||
stroke-miterlimit: 10;
|
||||
stroke-width: 4px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<path class="cls-2" d="M94.025716228034071,0h812.407113278915858c51.63496446795466,0,93.556004414436757,41.921039946482125,93.556004414436757,93.556004414436757v492.878202627134669H.469711813595495V93.556004414438576C.469711813595495,41.921039946482935,42.39075176007843,0,94.025716228034071,0Z"/>
|
||||
<path class="cls-1" d="M0,586.434207041571426h999.988833921388505v285.722761271097625c0,51.668603000227677-41.948350166788941,93.616953167016618-93.616953167016618,93.616953167016618H93.616953167015708c-51.668603000227122,0-93.616953167015708-41.9483501667886-93.616953167015708-93.616953167015708v-285.722761271098534h0Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-1" d="M618.377708191917918,187.266821023605189c-5.37725830078125,7.611236572265625-10.867767333984375,15.312309265136719-16.2864990234375,23.063575744628906-63.564056396484375,90.924362182617188-127.117309570263387,181.856414794921875-190.674499511669637,272.785675048806297-2.57666015625,3.68614196776889-5.213653564453125,7.33122253417514-7.730712890625,11.05741882323764-2.1104736328125,3.124282836914062-4.4967041015625,3.45538330078125-7.599151611328125,1.3656005859375-14.092254638671875-9.4925537109375-28.304718017578125-18.8083190917705-42.325286865234375-28.405014038059562-5.188751220703125-3.551559448242188-5.215118408203125-5.176437377929688-1.075469970703125-11.1024169921875,21.893829345703125-31.340744018554688,43.82373046875-62.656356811523438,65.765899658203125-93.963272094726562,35.995880126953125-51.358840942382812,72.0172119140625-102.699783325195312,108.015289306591512-154.056999206542969,21.082672119140625-30.077842712402344,42.14501953125-60.169898986816406,63.208831787109375-90.26092529296875,14.48638916015625-20.694992065429688,42.915802001953125-19.505653381347656,54.99554443359375-2.132835388183594,12.1212158203125,17.432464599609375,24.80181884765625,34.474685668945312,37.0704345703125,51.806053161621094,68.286712646484375,96.465660095214844,136.523895263671875,192.966316223144531,204.7730712890625,289.458549499511719,4.833984375,6.834503173828125,4.7501220703125,7.293777465820312-2.44189453125,12.202743530273438-10.732177734375,7.325592041015625-21.492919921875,14.608978271458909-32.24285888671875,21.908569335911125-2.8941650390625,1.96527099609375-5.75006103515625,3.991241455078125-8.698974609375,5.870956420898438-4.70306396484375,2.997756958007812-5.7923583984375,2.8531494140625-9.11407470703125-1.866851806640625-9.059417724609375-12.873504638660961-17.994049072265625-25.834854125950187-26.993316650390625-38.750839233372062-61.423095703125-88.155807495117188-122.846649169921875-176.311248779296875-184.303070068359375-264.443733215332031-1.097625732421875-1.574066162109375-1.72393798828125-3.650642395019531-4.34326171875-4.5362548828125Z"/>
|
||||
<path class="cls-1" d="M158.503393275235794,497.819486734028033c-.949951171875-.600265502929688-2.020843505859375-1.235092163085938-3.04888916015625-1.93304443359375-13.63037109375-9.253097534179688-27.305328369140625-18.441741943333-40.856231689453125-27.80996704098925-5.657867431640625-3.911544799804688-5.68524169921875-4.707870483398438-1.499908447265625-10.693862915039062,44.36773681640625-63.456069946289062,88.761474609375-126.8939208984375,133.143768310546875-190.339736938476562,38.798583984375-55.4637451171875,77.5916748046875-110.931427001953125,116.388427734375-166.396453857421875.85858154296875-1.227447509765625,1.721649169921875-2.452171325683594,2.60211181640625-3.6639404296875,1.086273193359375-1.494873046875,2.374786376953125-1.591712951660156,3.846588134765625-.54876708984375,1.221954345703125.865997314453125,2.4818115234375,1.678573608398438,3.711273193359375,2.534156799316406,12.91644287109375,8.987823486328125,25.858612060546875,17.939002990722656,38.7315673828125,26.988716125488281,5.695037841796875,4.00360107421875,5.654296875,4.111610412597656,1.669097900390625,9.81170654296875-31.48590087890625,45.034263610839844-62.98114013671875,90.061981201171875-94.474273681640625,135.0911865234375-38.07806396484375,54.444259643554688-76.15557861328125,108.88897705078125-114.235931396484375,163.331588745117188-13.886627197265625,19.853256225585938-27.776275634765625,39.704452514648438-41.681121826171875,59.544937133762687-1.11419677734375,1.589950561523438-1.847442626953125,3.613037109375-4.296478271484375,4.083480834960938Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-3" d="M137.896376237244112,696.705250909642928c20.96875,0,37.06689453125,3.791015625,48.2978515625,11.3681640625,11.22802734375,7.5810546875,16.84326171875,19.9931640625,16.84326171875,37.234375,0,17.384765625-5.68212890625,29.9326171875-17.04638671875,37.650390625-11.3642578125,7.71875-27.5986328125,11.5771484375-48.70361328125,11.5771484375h-9.942831128431862c-.000291742523586,0-.000528246568138.000236504043642-.000528246568138.000528246568138v48.392498194363725c0,.000291742522677-.000236504044551.000528246568138-.000528246569047.000528246568138h-31.656658349400459c-.000291743193884,0-.000528247780494-.000236504587519-.000528247780494-.000528247779584l-.000000062215804-142.467784819122244c0-.00025341161745.000176929195732-.000472301364425.000425359509791-.000522299635122,6.899777119657301-1.388626539415782,14.205299478604502-2.363207315198451,21.916566890206013-2.91983650846305,7.71142578125-.5546875,14.474609375-.8349609375,20.29296875-.8349609375ZM139.925673112244112,724.448414972142928c-2.3017578125,0-4.56591796875.0712890625-6.79833984375.208984375-2.231739190286135.139636273994256-4.159820347772438.280248940371166-5.782747545557868.416956057564676-.000273155044852.000023009190045-.000466071027404.000250331106145-.000466070958282.000524453525941l.000010491383364,41.716756169787914c.000000000073669.000292127793728.000236816460529.00052894412147.000528944253347.00052894412147h9.94283043087944c10.95849609375,0,19.208984375-1.5283203125,24.7578125-4.5888671875,5.5458984375-3.056640625,8.3203125-8.7607421875,8.3203125-17.1044921875,0-4.03125-.71044921875-7.369140625-2.130859375-10.0126953125-1.42041015625-2.6396484375-3.44970703125-4.76171875-6.087890625-6.361328125-2.63818359375-1.59765625-5.853515625-2.7119140625-9.63916015625-3.337890625-3.78955078125-.6259765625-7.984375-.9384765625-12.58203125-.9384765625Z"/>
|
||||
<path class="cls-3" d="M321.549696549744112,787.860524347142928c0,8.6240234375-1.2177734375,16.5146484375-3.65283203125,23.6748046875-2.43505859375,7.1640625-5.955078125,13.2822265625-10.552734375,18.3564453125-4.6005859375,5.078125-10.11474609375,9.005859375-16.53857421875,11.78515625-6.42724609375,2.7802734375-13.63134765625,4.171875-21.6123046875,4.171875-7.84765625,0-14.9853515625-1.3916015625-21.40966796875-4.171875-6.4267578125-2.779296875-11.94091796875-6.70703125-16.53857421875-11.78515625-4.60107421875-5.07421875-8.18701171875-11.1923828125-10.75537109375-18.3564453125-2.57177734375-7.16015625-3.85595703125-15.05078125-3.85595703125-23.6748046875,0-8.62109375,1.3193359375-16.478515625,3.95703125-23.5712890625,2.63818359375-7.091796875,6.291015625-13.140625,10.95849609375-18.1474609375,4.66748046875-5.005859375,10.21337890625-8.8974609375,16.640625-11.6806640625,6.423828125-2.7802734375,13.42529296875-4.171875,21.00341796875-4.171875,7.71142578125,0,14.77880859375,1.3916015625,21.2060546875,4.171875,6.42431640625,2.783203125,11.9384765625,6.6748046875,16.5390625,11.6806640625,4.59765625,5.0068359375,8.18408203125,11.0556640625,10.75537109375,18.1474609375,2.568359375,7.0927734375,3.85595703125,14.9501953125,3.85595703125,23.5712890625ZM290.703993424744112,787.860524347142928c0-9.595703125-1.861328125-17.1376953125-5.58056640625-22.6328125-3.72265625-5.4912109375-9.03076171875-8.2392578125-15.93017578125-8.2392578125-6.89990234375,0-12.24560546875,2.748046875-16.03173828125,8.2392578125-3.7890625,5.4951171875-5.68212890625,13.037109375-5.68212890625,22.6328125,0,9.5947265625,1.89306640625,17.208984375,5.68212890625,22.8408203125,3.7861328125,5.6318359375,9.1318359375,8.4482421875,16.03173828125,8.4482421875,6.8994140625,0,12.20751953125-2.81640625,15.93017578125-8.4482421875,3.71923828125-5.6318359375,5.58056640625-13.24609375,5.58056640625-22.8408203125Z"/>
|
||||
<path class="cls-3" d="M407.898100107022401,779.93667285716765c-.000200849756766-.000784964015111-.00129470680713-.000783321500421-.001495793401773.000001581878678-2.707786189160288,10.569329734204075-5.580585301787323,21.066407772292223-8.624251513876516,31.495138970596599-3.043909791806072,10.429565792903304-6.258714032605894,20.930418984533389-9.639045575789169,31.49670189086828-.000069899304435.000218492124986-.000273616021332.000367926406398-.00050301680858.000367926524632l-23.539296095775171.000011983462173c-.000206836863072.000000000105501-.000391488199966-.00011904558778-.000471256027595-.000309882119836-2.44136929301294-5.840731350224814-5.089281628109347-12.792791855183168-7.936406711849486-20.859076606235249-2.8505859375-8.0634765625-5.802734375-16.8603515625-8.85302734375-26.38671875-3.05322265625-9.5234375-6.17333984375-19.607421875-9.36328125-30.24609375-3.189875277876126-10.63747479011181-6.274774439504654-21.380413957474957-9.258600766721429-32.226867289606162-.000092603000667-.000336619499649.00016096545005-.000670842697218.000510090074386-.00067084291004l31.859228971617995-.000019503109797c.000245485935011-.000000000149157.000456426913843.000164983857758.000508537503265.000404875147069,1.087370378786545,5.005713456179365,2.2767996094708,10.465564090736734,3.570228167525784,16.37461369797893,1.29345703125,5.912109375,2.65380859375,11.994140625,4.083984375,18.2509765625,1.43017578125,6.2578125,2.9267578125,12.5517578125,4.4931640625,18.8779296875,1.562988155857056,6.330077617163624,3.161132556077973,12.411131836779532,4.797363212593154,18.251951975455086,1.756347892862323-6.117186959725586,3.481445463590717-12.374999161931555,5.174804756156846-18.773436350455086,1.68994140625-6.39453125,3.3134765625-12.6884765625,4.8701171875-18.8779296875,1.5537109375-6.185546875,3.0439453125-12.166015625,4.46484375-17.9384765625,1.420377219847978-5.768420926531689,2.66937566906654-11.159906209401015,3.753810644462646-16.165620121939355.000052721623433-.000243361169851.00026652701581-.000411262781199.000515533499311-.000411262650232l21.916146113424475.000011551035641c.00024923982437.000000000130967.000463331872197.000168305405168.000515944337167.000411928922404,1.081028965874793,5.0057353488628,2.298773936253383,10.397204981329196,3.652253951776402,16.165607904631543,1.35107421875,5.7724609375,2.771484375,11.7529296875,4.26171875,17.9384765625,1.4873046875,6.189453125,3.0439453125,12.4833984375,4.66748046875,18.8779296875,1.62353149349201,6.398423064878443,3.313468937357356,12.656221764276779,5.07323045697467,18.773437458467015,1.632816527724572-5.840791592127061,3.266121161525007-11.921860031841788,4.89893751177533-18.251953083467015,1.6328125-6.326171875,3.16455078125-12.6201171875,4.5947265625-18.8779296875,1.4267578125-6.2568359375,2.787109375-12.3388671875,4.08056640625-18.2509765625,1.293951662453765-5.909208686422062,2.482921825785525-11.369194213808441,3.573734583316764-16.375018424441805-.000003979949724.000018416774765,31.427918994328138.000019352950403,31.454077442677772.000019353727112.000348228063558.000000000009095-.000075470190495.000333405139827-.000167919681189.000669136990837-2.986756061328379,10.846453909638512-6.071655379555523,21.589393647098404-9.261530825063346,32.226868996223857-3.18994140625,10.638671875-6.31005859375,20.72265625-9.3603515625,30.24609375-3.05322265625,9.5263671875-6.037109375,18.3232421875-8.9541015625,26.38671875-2.917436179105607,8.066283795618801-5.59659834274953,15.018343609099247-8.037967600018419,20.859075579299315-.000079963732787.000191305240151-.000265005328401.000310559629725-.000472350194286.000310559526042l-23.53929769284332-.000011841180822c-.000229907624998-.000000000114596-.000433906814578-.000149595760377-.000504015633851-.000368552991858-3.383261758685876-10.566283036894674-6.696698725164424-21.067135743112885-9.943730997560124-31.496701057152677-3.246772684080497-10.428731508565761-6.223575334949601-20.925809860984373-8.927963630221711-31.495140552475277Z"/>
|
||||
<path class="cls-3" d="M494.853407487244112,788.903493097142928c0-9.732421875,1.4521484375-18.251953125,4.36328125-25.552734375,2.9072265625-7.30078125,6.728515625-13.3828125,11.4658203125-18.251953125,4.7333984375-4.8662109375,10.177734375-8.5517578125,16.3359375-11.0556640625,6.154296875-2.5029296875,12.48046875-3.75390625,18.9736328125-3.75390625,15.150390625,0,27.123046875,4.7646484375,35.9189453125,14.2880859375,8.79296875,9.52734375,13.1904296875,23.5380859375,13.1904296875,42.03125,0,1.8095703125-.0693359375,3.791015625-.2021484375,5.9453125-.136707061361449,2.157042132481365-.273414122724716,4.06705619674176-.406215393553794,5.735866747245382-.000021832460334.000274351588814-.000250011164098.00047248683768-.000525230077983.000472486832223l-68.589703164481762-.000001380894901c-.000303570901451-.000000000005457-.000544143109437.000274783938949-.000512294425789.000576679538426.6748770204631,6.397200101144335,3.583058530197377,11.472190279755523,8.725471707539327,15.22695265477887,5.140625,3.75390625,12.0400390625,5.6318359375,20.69921875,5.6318359375,5.5458984375,0,10.990234375-.521484375,16.3359375-1.564453125,5.342483474791152-1.04291214605837,9.705581077088937-2.327009048351101,13.088260394941244-3.859139701526146.000320854016536-.000145325710037.00067184951331.000057817438574.000727782522517.000405579368817l4.05939714766464,25.239189084832105c.00003631123036.000225764073548-.000065345324401.000444938594228-.000268757921731.000549395461348-1.623953538706701.83393604718367-3.78987482676348,1.667872151112533-6.493819692206671,2.502784704363876-2.7080078125.833984375-5.716796875,1.564453125-9.0302734375,2.1904296875-3.3173828125.625-6.8681640625,1.146484375-10.654296875,1.564453125-3.7890625.4169921875-7.578125.625-11.3642578125.625-9.607421875,0-17.958984375-1.4599609375-25.0615234375-4.3798828125-7.103515625-2.9208984375-12.98828125-6.916015625-17.6552734375-11.994140625-4.66796875-5.0751953125-8.1171875-11.087890625-10.349609375-18.04296875-2.232421875-6.9521484375-3.3486328125-14.4619140625-3.3486328125-22.5283203125ZM565.879203275393593,777.013844477338353c.000307029940814,0,.000555216798602-.00026352228997.000539336708243-.000570141284697-.136750165998819-2.64042568615514-.57716985886691-5.214453794191286-1.319303874857724-7.717203113910728-.7451171875-2.50390625-1.8955078125-4.7265625-3.44921875-6.67578125-1.5576171875-1.9453125-3.5205078125-3.5458984375-5.8857421875-4.796875-2.3681640625-1.251953125-5.310546875-1.8779296875-8.8271484375-1.8779296875-3.3837890625,0-6.291015625.59375-8.7265625,1.7734375-2.4345703125,1.1826171875-4.4638671875,2.7470703125-6.087890625,4.693359375-1.623046875,1.94921875-2.8759765625,4.2080078125-3.75390625,6.779296875-.881767007294911,2.574017531815116-1.522360784938428,5.181232998073028-1.927635274651038,7.821646971486189-.00004962215462.000323294541886.000203938367122.000618462569037.000531018966285.000618462569037l39.976337543834234.0000000086402Z"/>
|
||||
<path class="cls-3" d="M683.173841621674001,760.742802301148913c-.000058786181398.000297246688206-.000339720678312.000487163006255-.000633223833574.000411881486798-2.70688320199406-.694296728632253-5.884462953697948-1.42374931095037-9.536597785596314-2.190307022992783-3.6533203125-.7626953125-7.578125-1.146484375-11.7705078125-1.146484375-1.8955078125,0-4.16015625.17578125-6.7978515625.521484375-2.638500547684089.348610175989961-4.635482847739695.7294427297129-5.985998350710361,1.14640687778774-.000221173195314.000068285993621-.000352009377821.000268293715635-.000352009315975.000499768464579l.000022234886274,83.853542405153348c.000000000078217.000291336309601-.00023617464467.000527511094333-.000527510954271.000527511094333h-30.236248386760053c-.00029204247403,0-.000528789692908-.000236747171584-.000528789749296-.000528789645614l-.000020339064577-103.877993113657794c-.000000000040018-.000208772313272.000125948654386-.000393847696614.000322391475493-.000464530767204,5.40906754117168-1.946263564073888,11.801565305589975-3.787055054202938,19.176455760192766-5.528240128429388,7.373046875-1.736328125,15.591796875-2.6064453125,24.65625-2.6064453125,1.6240234375,0,3.5830078125.103515625,5.8857421875.3125,2.298828125.208984375,4.59765625.4892578125,6.8994140625.833984375,2.298828125.3486328125,4.59765625.7666015625,6.8994140625,1.251953125,2.298644840624547.489218804228585,4.261405747351091,1.07900750726003,5.885349828440667,1.77229628893474.000233834378378.000099827790109.000357258635631.000348315996234.00030793087717.000597737078351l-5.074012687387949,25.656258927992894Z"/>
|
||||
<path class="cls-3" d="M697.785048112244112,788.903493097142928c0-9.732421875,1.4521484375-18.251953125,4.36328125-25.552734375,2.9072265625-7.30078125,6.728515625-13.3828125,11.4658203125-18.251953125,4.7333984375-4.8662109375,10.177734375-8.5517578125,16.3359375-11.0556640625,6.154296875-2.5029296875,12.48046875-3.75390625,18.9736328125-3.75390625,15.150390625,0,27.123046875,4.7646484375,35.9189453125,14.2880859375,8.79296875,9.52734375,13.1904296875,23.5380859375,13.1904296875,42.03125,0,1.8095703125-.0693359375,3.791015625-.2021484375,5.9453125-.136707061361449,2.157042132481365-.273414122724716,4.06705619674176-.406215393553794,5.735866747245382-.000021832460334.000274351588814-.000250011164098.00047248683768-.000525230077983.000472486832223l-68.589703164481762-.000001380894901c-.000303570901451-.000000000005457-.000544143109437.000274783938949-.000512294425789.000576679538426.6748770204631,6.397200101144335,3.583058530197377,11.472190279755523,8.725471707539327,15.22695265477887,5.140625,3.75390625,12.0400390625,5.6318359375,20.69921875,5.6318359375,5.5458984375,0,10.990234375-.521484375,16.3359375-1.564453125,5.342483474791152-1.04291214605837,9.705581077088937-2.327009048351101,13.088260394941244-3.859139701526146.000320854016536-.000145325710037.00067184951331.000057817438574.000727782522517.000405579368817l4.059397147662821,25.239189084830286c.000036311232179.000225764071729-.000065345322582.000444938592409-.000268757919912.000549395459529-1.623953538699425.833936047185489-3.789874826759842,1.667872151116171-6.493819692206671,2.502784704367514-2.7080078125.833984375-5.716796875,1.564453125-9.0302734375,2.1904296875-3.3173828125.625-6.8681640625,1.146484375-10.654296875,1.564453125-3.7890625.4169921875-7.578125.625-11.3642578125.625-9.607421875,0-17.958984375-1.4599609375-25.0615234375-4.3798828125-7.103515625-2.9208984375-12.98828125-6.916015625-17.6552734375-11.994140625-4.66796875-5.0751953125-8.1171875-11.087890625-10.349609375-18.04296875-2.232421875-6.9521484375-3.3486328125-14.4619140625-3.3486328125-22.5283203125ZM768.810843900393593,777.013844477338353c.000307029940814,0,.000555216798602-.00026352228997.000539336708243-.000570141284697-.136750165998819-2.64042568615514-.57716985886691-5.214453794191286-1.319303874857724-7.717203113910728-.7451171875-2.50390625-1.8955078125-4.7265625-3.44921875-6.67578125-1.5576171875-1.9453125-3.5205078125-3.5458984375-5.8857421875-4.796875-2.3681640625-1.251953125-5.310546875-1.8779296875-8.8271484375-1.8779296875-3.3837890625,0-6.291015625.59375-8.7265625,1.7734375-2.4345703125,1.1826171875-4.4638671875,2.7470703125-6.087890625,4.693359375-1.623046875,1.94921875-2.8759765625,4.2080078125-3.75390625,6.779296875-.881767007294911,2.574017531815116-1.522360784938428,5.181232998073028-1.927635274651038,7.821646971486189-.00004962215462.000323294541886.000203938365303.000618462569037.000531018966285.000618462569037l39.976337543834234.0000000086402Z"/>
|
||||
<path class="cls-3" d="M915.936423628601915,839.382585985831611c.000000000010914.000229665547522-.000142367920489.000430906506153-.000361853515642.000498525563671-2.706929463973211.833950018515679-5.818148114689393,1.635673815637347-9.334607412842161,2.398338273247646-3.51953125.7666015625-7.203125,1.4248046875-11.0595703125,1.9814453125-3.85546875.5546875-7.78125,1.0078125-11.76953125,1.3564453125-3.9921875.345703125-7.8134765625.521484375-11.4658203125.521484375-8.7958984375,0-16.640625-1.3232421875-23.5400390625-3.9638671875-6.8994140625-2.6396484375-12.71875-6.4296875-17.4521484375-11.3681640625-4.7373046875-4.9345703125-8.35546875-10.9150390625-10.857421875-17.9384765625-2.5048828125-7.0205078125-3.75390625-14.9150390625-3.75390625-23.67578125,0-8.8974609375,1.0810546875-16.9287109375,3.2470703125-24.091796875,2.162109375-7.1611328125,5.2763671875-13.24609375,9.3349609375-18.251953125,4.05859375-5.0068359375,9.0302734375-8.8291015625,14.9150390625-11.47265625,5.8857421875-2.6396484375,12.6142578125-3.962890625,20.1923828125-3.962890625,4.19140625,0,7.9453125.4169921875,11.2626953125,1.2509765625,3.313234435754566.833923432990559,6.660643563949634,2.017405148606485,10.044180458729898,3.545567004166514.000347211789631.000156816913659.000741290481528-.000091296607934.000741290481528-.000472278985399l.000000125786755-49.643279564157638c.000000000001819-.000258440384641.000186986879271-.000478936695799.000441955276983-.000521156931427l30.235271453839232-5.006660963241302c.000321908124533-.000053304791436.000614715901975.000195004166017.000614715918346.000521295818544l.000008328825061,158.32124323951939ZM847.548719987244112,787.234547784642928c0,9.595703125,2.095703125,17.3134765625,6.291015625,23.154296875,4.19140625,5.83984375,10.416015625,8.7607421875,18.669921875,8.7607421875,2.7041015625,0,5.2060546875-.1044921875,7.5078125-.3134765625,2.2986249090809-.207989424654443,4.192048029666694-.449176104264552,5.682205411103496-.729419462584701.000251654058957-.000047326799177.000417297229433-.000265219710855.000417297289459-.000521285312061l.000012965474525-56.527538922910026c.000000000038199-.00016391383906-.000078287173892-.000315375882565-.000215081212446-.000405681681514-1.896410170367744-1.251932019164087-4.3631601676625-2.294869711533465-7.407029967655035-3.128833397511698-3.0439453125-.8349609375-6.123046875-1.251953125-9.2333984375-1.251953125-14.341796875,0-21.5107421875,10.0126953125-21.5107421875,30.037109375Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 22 KiB |
79
Meshtastic/Resources/AppIcon_MPowered.icon/icon.json
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
"fill" : {
|
||||
"solid" : "display-p3:0.17335,0.17642,0.23072,1.00000"
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"glass" : false,
|
||||
"image-name" : "M-POWERED.svg",
|
||||
"name" : "M-POWERED",
|
||||
"position-specializations" : [
|
||||
{
|
||||
"value" : {
|
||||
"scale" : 1,
|
||||
"translation-in-points" : [
|
||||
1.6953125,
|
||||
12.1875
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"idiom" : "square",
|
||||
"value" : {
|
||||
"scale" : 1.05,
|
||||
"translation-in-points" : [
|
||||
-20.074908088235293,
|
||||
-22.122702205882355
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"idiom" : "watchOS",
|
||||
"value" : {
|
||||
"scale" : 1.05,
|
||||
"translation-in-points" : [
|
||||
-15.328125,
|
||||
-35.484375
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"position-specializations" : [
|
||||
{
|
||||
"value" : {
|
||||
"scale" : 1,
|
||||
"translation-in-points" : [
|
||||
15.359375,
|
||||
7.6484375
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"idiom" : "watchOS",
|
||||
"value" : {
|
||||
"scale" : 1.1,
|
||||
"translation-in-points" : [
|
||||
7.7109375,
|
||||
120.0546875
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
|
|
@ -10,12 +10,14 @@ struct ExchangePositionsButton: View {
|
|||
@State private var isPresentingPositionFailedAlert: Bool = false
|
||||
|
||||
var body: some View {
|
||||
let hopsAway = Int32(truncatingIfNeeded: node.hopsAway > node.loRaConfig?.hopLimit ?? 0 ? node.hopsAway : node.loRaConfig?.hopLimit ?? 0)
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
try await accessoryManager.sendPosition(
|
||||
channel: node.channel,
|
||||
destNum: node.num,
|
||||
hopsAway: hopsAway,
|
||||
wantResponse: true
|
||||
)
|
||||
Task { @MainActor in
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
import SwiftUI
|
||||
import MapKit
|
||||
import CoreLocation
|
||||
|
||||
struct AnimatedNodePin: View, Equatable {
|
||||
let nodeColor: UIColor
|
||||
let shortName: String?
|
||||
let hasDetectionSensorMetrics: Bool
|
||||
let isOnline: Bool
|
||||
let calculatedDelay: Double
|
||||
private let swiftUIColor: Color
|
||||
|
||||
init(nodeColor: UIColor, shortName: String?, hasDetectionSensorMetrics: Bool, isOnline: Bool, calculatedDelay: Double) {
|
||||
self.nodeColor = nodeColor
|
||||
self.shortName = shortName
|
||||
self.hasDetectionSensorMetrics = hasDetectionSensorMetrics
|
||||
self.isOnline = isOnline
|
||||
self.calculatedDelay = calculatedDelay
|
||||
self.swiftUIColor = Color(nodeColor)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Pass the calculatedDelay to the PulsingCircle view
|
||||
if isOnline {
|
||||
PulsingCircle(nodeColor: nodeColor, calculatedDelay: calculatedDelay)
|
||||
}
|
||||
|
||||
if hasDetectionSensorMetrics {
|
||||
Image(systemName: "sensor.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.symbolEffect(.variableColor)
|
||||
.padding()
|
||||
.foregroundStyle(.white)
|
||||
.background(swiftUIColor)
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
CircleText(text: shortName ?? "?", color: swiftUIColor, circleSize: 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func == (lhs: AnimatedNodePin, rhs: AnimatedNodePin) -> Bool {
|
||||
return lhs.nodeColor == rhs.nodeColor &&
|
||||
lhs.shortName == rhs.shortName &&
|
||||
lhs.hasDetectionSensorMetrics == rhs.hasDetectionSensorMetrics &&
|
||||
lhs.isOnline == rhs.isOnline &&
|
||||
lhs.calculatedDelay == rhs.calculatedDelay // Include calculatedDelay to ensure changes in animation timing trigger UI updates
|
||||
}
|
||||
}
|
||||
|
||||
struct PulsingCircle: View {
|
||||
let nodeColor: UIColor
|
||||
let calculatedDelay: Double
|
||||
@State private var isPulsing = false
|
||||
|
||||
var body: some View {
|
||||
Circle()
|
||||
.fill(Color(nodeColor.lighter()).opacity(0.4))
|
||||
.frame(width: 55, height: 55)
|
||||
.scaleEffect(isPulsing ? 1.2 : 0.8)
|
||||
.animation(
|
||||
.easeInOut(duration: 0.8).repeatForever(autoreverses: true).delay(calculatedDelay),
|
||||
value: isPulsing
|
||||
)
|
||||
.onAppear {
|
||||
isPulsing = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,12 +11,12 @@ import CoreLocation
|
|||
import OSLog
|
||||
|
||||
struct IdentifiableOverlay: Identifiable {
|
||||
let overlay: MKOverlay
|
||||
var id: ObjectIdentifier { ObjectIdentifier(overlay as AnyObject) }
|
||||
let overlay: MKOverlay
|
||||
var id: ObjectIdentifier { ObjectIdentifier(overlay as AnyObject) }
|
||||
}
|
||||
|
||||
struct MeshMapContent: MapContent {
|
||||
|
||||
|
||||
/// Parameters
|
||||
@Binding var showUserLocation: Bool
|
||||
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
|
||||
|
|
@ -30,141 +30,48 @@ struct MeshMapContent: MapContent {
|
|||
@Binding var selectedPosition: PositionEntity?
|
||||
@AppStorage("enableMapWaypoints") private var showWaypoints = true
|
||||
@Binding var selectedWaypoint: WaypointEntity?
|
||||
|
||||
// Map overlays
|
||||
@AppStorage("mapOverlaysEnabled") private var showMapOverlays = false
|
||||
@Binding var enabledOverlayConfigs: Set<UUID>
|
||||
|
||||
|
||||
@FetchRequest(fetchRequest: PositionEntity.allPositionsFetchRequest(), animation: .easeIn)
|
||||
var positions: FetchedResults<PositionEntity>
|
||||
|
||||
|
||||
@FetchRequest(fetchRequest: WaypointEntity.allWaypointssFetchRequest(), animation: .none)
|
||||
var waypoints: FetchedResults<WaypointEntity>
|
||||
|
||||
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)],
|
||||
predicate: NSPredicate(format: "enabled == true", ""), animation: .none)
|
||||
private var routes: FetchedResults<RouteEntity>
|
||||
|
||||
var delay: Double = 0
|
||||
@State private var scale: CGFloat = 0.5
|
||||
|
||||
|
||||
@MapContentBuilder
|
||||
var positionAnnotations: some MapContent {
|
||||
ForEach(positions, id: \.id) { position in
|
||||
if !showFavorites || (position.nodePosition?.favorite == true) {
|
||||
/// Node color from node.num
|
||||
if (!showFavorites || (position.nodePosition?.favorite == true)) && !(position.nodePosition?.ignored == true) {
|
||||
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
let positionName = position.nodePosition?.user?.longName ?? "?"
|
||||
/// Latest Position Anotations
|
||||
// Use a hash of the position ID to stagger animation delays for each node, preventing synchronized animations and improving visual distinction.
|
||||
let calculatedDelay = Double(position.id.hashValue % 100) / 100.0 * 0.5
|
||||
|
||||
Annotation(positionName, coordinate: position.coordinate) {
|
||||
LazyVStack {
|
||||
ZStack {
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
if position.nodePosition?.isOnline ?? false {
|
||||
Circle()
|
||||
.fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
|
||||
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.3))
|
||||
.scaleEffect(scale)
|
||||
.animation(
|
||||
Animation.easeInOut(duration: 0.6)
|
||||
.repeatForever().delay(delay), value: scale
|
||||
)
|
||||
.onAppear {
|
||||
self.scale = 1
|
||||
}
|
||||
.onChange(of: showFavorites) {
|
||||
|
||||
scale = 0.5 // Reset to initial state
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
scale = 1
|
||||
}
|
||||
}
|
||||
.frame(width: 60, height: 60)
|
||||
}
|
||||
if position.nodePosition?.hasDetectionSensorMetrics ?? false {
|
||||
Image(systemName: "sensor.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.symbolEffect(.variableColor)
|
||||
.padding()
|
||||
.foregroundStyle(.white)
|
||||
.background(Color(nodeColor))
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 40)
|
||||
}
|
||||
LazyVStack {
|
||||
AnimatedNodePin(
|
||||
nodeColor: nodeColor,
|
||||
shortName: position.nodePosition?.user?.shortName,
|
||||
hasDetectionSensorMetrics: position.nodePosition?.hasDetectionSensorMetrics ?? false,
|
||||
isOnline: position.nodePosition?.isOnline ?? false,
|
||||
calculatedDelay: calculatedDelay
|
||||
)
|
||||
}
|
||||
}
|
||||
.highPriorityGesture(TapGesture().onEnded { _ in
|
||||
selectedPosition = (selectedPosition == position ? nil : position)
|
||||
})
|
||||
}
|
||||
/// Node History and Route Lines for favorites
|
||||
if let nodePosition = position.nodePosition,
|
||||
nodePosition.favorite,
|
||||
let positions = nodePosition.positions,
|
||||
let nodePositions = Array(positions) as? [PositionEntity] {
|
||||
if showRouteLines {
|
||||
let routeCoords = nodePositions.compactMap({(pos) -> CLLocationCoordinate2D in
|
||||
return pos.nodeCoordinate ?? LocationsHandler.DefaultLocation
|
||||
.highPriorityGesture(TapGesture().onEnded { _ in
|
||||
selectedPosition = (selectedPosition == position ? nil : position)
|
||||
})
|
||||
let gradient = LinearGradient(
|
||||
colors: [Color(nodeColor.lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)],
|
||||
startPoint: .leading, endPoint: .trailing
|
||||
)
|
||||
let dashed = StrokeStyle(
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round, dash: [10, 10]
|
||||
)
|
||||
MapPolyline(coordinates: routeCoords)
|
||||
.stroke(gradient, style: dashed)
|
||||
}
|
||||
if showNodeHistory {
|
||||
ForEach(nodePositions, id: \.self) { (mappin: PositionEntity) in
|
||||
if mappin.latest == false && mappin.nodePosition?.favorite ?? false {
|
||||
let pf = PositionFlags(rawValue: Int(mappin.nodePosition?.metadata?.positionFlags ?? 771))
|
||||
let headingDegrees = Angle.degrees(Double(mappin.heading))
|
||||
Annotation("", coordinate: mappin.coordinate) {
|
||||
LazyVStack {
|
||||
if pf.contains(.Heading) {
|
||||
Image(systemName: "location.north.circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))).isLight() ? .black : .white)
|
||||
.background(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))))
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(headingDegrees)
|
||||
.frame(width: 16, height: 16)
|
||||
|
||||
} else {
|
||||
Circle()
|
||||
.fill(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))))
|
||||
.strokeBorder(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))).isLight() ? .black : .white, lineWidth: 2)
|
||||
.frame(width: 12, height: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
.annotationTitles(.hidden)
|
||||
.annotationSubtitles(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Reduced Precision Map Circles
|
||||
if 12...15 ~= position.precisionBits {
|
||||
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
|
||||
let radius: CLLocationDistance = pp?.precisionMeters ?? 0
|
||||
if radius > 0.0 {
|
||||
MapCircle(center: position.coordinate, radius: radius)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.25))
|
||||
.stroke(.white, lineWidth: 2)
|
||||
.tag(position.nodePosition?.num ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@MapContentBuilder
|
||||
var routeAnnotations: some MapContent {
|
||||
ForEach(routes) { route in
|
||||
|
|
@ -199,7 +106,7 @@ struct MeshMapContent: MapContent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@MapContentBuilder
|
||||
var waypointAnnotations: some MapContent {
|
||||
if waypoints.count > 0, showWaypoints, let waypoints = Array(waypoints) as? [WaypointEntity] {
|
||||
|
|
@ -217,7 +124,7 @@ struct MeshMapContent: MapContent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@MapContentBuilder
|
||||
var meshMap: some MapContent {
|
||||
let loraNodes = positions.filter { $0.nodePosition?.viaMqtt ?? true == false }
|
||||
|
|
@ -233,27 +140,27 @@ struct MeshMapContent: MapContent {
|
|||
.foregroundStyle(.indigo.opacity(0.4))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// GeoJSON Overlays with embedded styling
|
||||
if showMapOverlays {
|
||||
overlayContent
|
||||
}
|
||||
|
||||
|
||||
positionAnnotations
|
||||
routeAnnotations
|
||||
waypointAnnotations
|
||||
}
|
||||
|
||||
|
||||
var overlayContent: some MapContent {
|
||||
// Get all features but filter by enabled configs
|
||||
let allStyledFeatures = GeoJSONOverlayManager.shared.loadStyledFeaturesForConfigs(enabledOverlayConfigs)
|
||||
|
||||
|
||||
return Group {
|
||||
ForEach(0..<allStyledFeatures.count, id: \.self) { index in
|
||||
let styledFeature = allStyledFeatures[index]
|
||||
let feature = styledFeature.feature
|
||||
let geometryType = feature.geometry.type
|
||||
|
||||
|
||||
if geometryType == "Point" {
|
||||
if let coordinate = feature.geometry.coordinates.toCoordinate() {
|
||||
Annotation(feature.name, coordinate: coordinate) {
|
||||
|
|
@ -280,7 +187,7 @@ struct MeshMapContent: MapContent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@MapContentBuilder
|
||||
var body: some MapContent {
|
||||
meshMap
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import SwiftUI
|
|||
import MapKit
|
||||
|
||||
struct PositionPopover: View {
|
||||
|
||||
|
||||
@ObservedObject var locationsHandler = LocationsHandler.shared
|
||||
@Environment(\.managedObjectContext) var context
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
|
@ -17,230 +17,220 @@ struct PositionPopover: View {
|
|||
var position: PositionEntity
|
||||
var popover: Bool = true
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
var delay: Double = 0
|
||||
@State private var scale: CGFloat = 0.5
|
||||
|
||||
var body: some View {
|
||||
// Node Color from node.num
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
NavigationStack {
|
||||
VStack {
|
||||
HStack {
|
||||
ZStack {
|
||||
if position.nodePosition?.isOnline ?? false {
|
||||
Circle()
|
||||
.fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
|
||||
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.3))
|
||||
.scaleEffect(scale)
|
||||
.animation(
|
||||
Animation.easeInOut(duration: 0.6)
|
||||
.repeatForever().delay(delay), value: scale
|
||||
)
|
||||
.onAppear {
|
||||
self.scale = 1
|
||||
}
|
||||
.frame(width: 90, height: 90)
|
||||
}
|
||||
CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 65)
|
||||
}
|
||||
Text(position.nodePosition?.user?.longName ?? "Unknown")
|
||||
.font(.largeTitle)
|
||||
}
|
||||
Divider()
|
||||
HStack(alignment: .center) {
|
||||
VStack(alignment: .leading) {
|
||||
/// Time
|
||||
Label {
|
||||
if idiom != .phone {
|
||||
Text("Heard".localized + ":")
|
||||
VStack {
|
||||
HStack {
|
||||
ZStack {
|
||||
if position.nodePosition?.isOnline ?? false {
|
||||
Circle()
|
||||
.fill(Color(nodeColor.lighter()).opacity(0.4))
|
||||
.frame(width: 90, height: 90)
|
||||
}
|
||||
Text(position.time?.lastHeard ?? "unknown")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
} icon: {
|
||||
Image(systemName: position.nodePosition?.isOnline ?? false ? "checkmark.circle.fill" : "moon.circle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(position.nodePosition?.isOnline ?? false ? .green : .orange)
|
||||
.frame(width: 35)
|
||||
CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 65)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
/// Coordinate
|
||||
Label {
|
||||
Text("\(String(format: "%.6f", position.coordinate.latitude)), \(String(format: "%.6f", position.coordinate.longitude))")
|
||||
.textSelection(.enabled)
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
} icon: {
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
/// Hops Away
|
||||
if position.nodePosition?.hopsAway ?? 0 > 0 {
|
||||
Text(position.nodePosition?.user?.longName ?? "Unknown")
|
||||
.font(.largeTitle)
|
||||
}
|
||||
Divider()
|
||||
HStack(alignment: .center) {
|
||||
VStack(alignment: .leading) {
|
||||
/// Time
|
||||
Label {
|
||||
Text("Hops Away: \(position.nodePosition?.hopsAway ?? 0)")
|
||||
if idiom != .phone {
|
||||
Text("Heard".localized + ":")
|
||||
}
|
||||
Text(position.time?.lastHeard ?? "unknown")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
} icon: {
|
||||
Image(systemName: position.nodePosition?.isOnline ?? false ? "checkmark.circle.fill" : "moon.circle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(position.nodePosition?.isOnline ?? false ? .green : .orange)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
/// Coordinate
|
||||
Label {
|
||||
Text("\(String(format: "%.6f", position.coordinate.latitude)), \(String(format: "%.6f", position.coordinate.longitude))")
|
||||
.textSelection(.enabled)
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
} icon: {
|
||||
Image(systemName: "hare")
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
/// Altitude
|
||||
Label {
|
||||
let distanceInMeters = Measurement(value: Double(position.altitude), unit: UnitLength.meters)
|
||||
let distanceInFeet = distanceInMeters.converted(to: UnitLength.feet)
|
||||
if Locale.current.measurementSystem == .metric {
|
||||
Text(altitudeFormatter.string(from: distanceInMeters))
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} else {
|
||||
Text(altitudeFormatter.string(from: distanceInFeet))
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "mountain.2.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 3))
|
||||
/// Sats in view
|
||||
if pf.contains(.Satsinview) {
|
||||
Label {
|
||||
Text("Sats in view: \(String(position.satsInView))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "sparkles")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
/// Sequence Number
|
||||
if pf.contains(.SeqNo) {
|
||||
Label {
|
||||
Text("Sequence: \(String(position.seqNo))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "number")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
/// Heading
|
||||
let degrees = Angle.degrees(Double(position.heading))
|
||||
Label {
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
Text("Heading: \(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
} icon: {
|
||||
Image(systemName: "location.north")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
/// Distance
|
||||
if let lastLocation = locationsHandler.locationsArray.last {
|
||||
/// Distance
|
||||
if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
|
||||
let metersAway = position.coordinate.distance(from: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude))
|
||||
/// Hops Away
|
||||
if position.nodePosition?.hopsAway ?? 0 > 0 {
|
||||
Label {
|
||||
Text("Distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
|
||||
Text("Hops Away: \(position.nodePosition?.hopsAway ?? 0)")
|
||||
.textSelection(.enabled)
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
Image(systemName: "hare")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
}
|
||||
/// Speed
|
||||
let speed = Measurement(value: Double(position.speed), unit: UnitSpeed.kilometersPerHour)
|
||||
Label {
|
||||
Text("Speed: \(speed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "gauge.with.dots.needle.33percent")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
if position.nodePosition?.viaMqtt ?? false {
|
||||
/// Altitude
|
||||
Label {
|
||||
Text("MQTT")
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
let distanceInMeters = Measurement(value: Double(position.altitude), unit: UnitLength.meters)
|
||||
let distanceInFeet = distanceInMeters.converted(to: UnitLength.feet)
|
||||
if Locale.current.measurementSystem == .metric {
|
||||
Text(altitudeFormatter.string(from: distanceInMeters))
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} else {
|
||||
Text(altitudeFormatter.string(from: distanceInFeet))
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "network")
|
||||
Image(systemName: "mountain.2.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 3))
|
||||
/// Sats in view
|
||||
if pf.contains(.Satsinview) {
|
||||
Label {
|
||||
Text("Sats in view: \(String(position.satsInView))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "sparkles")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
/// Sequence Number
|
||||
if pf.contains(.SeqNo) {
|
||||
Label {
|
||||
Text("Sequence: \(String(position.seqNo))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "number")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
/// Heading
|
||||
let degrees = Angle.degrees(Double(position.heading))
|
||||
Label {
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
Text("Heading: \(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
} icon: {
|
||||
Image(systemName: "location.north")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
/// Distance
|
||||
if let lastLocation = locationsHandler.locationsArray.last {
|
||||
/// Distance
|
||||
if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
|
||||
let metersAway = position.coordinate.distance(from: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude))
|
||||
Label {
|
||||
Text("Distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Speed
|
||||
let speed = Measurement(value: Double(position.speed), unit: UnitSpeed.kilometersPerHour)
|
||||
Label {
|
||||
Text("Speed: \(speed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "gauge.with.dots.needle.33percent")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
if position.nodePosition?.viaMqtt ?? false {
|
||||
Label {
|
||||
Text("MQTT")
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "network")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
VStack(alignment: .center) {
|
||||
if position.nodePosition != nil {
|
||||
if position.nodePosition?.favorite ?? false {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom, 5)
|
||||
VStack(alignment: .center) {
|
||||
if position.nodePosition != nil {
|
||||
if position.nodePosition?.favorite ?? false {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
if position.nodePosition?.hasEnvironmentMetrics ?? false {
|
||||
Image(systemName: "cloud.sun.rain")
|
||||
.foregroundColor(.accentColor)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom)
|
||||
}
|
||||
if position.nodePosition?.hasDetectionSensorMetrics ?? false {
|
||||
Image(systemName: "sensor.fill")
|
||||
.symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3))
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom)
|
||||
}
|
||||
BatteryGauge(node: position.nodePosition!)
|
||||
}
|
||||
if position.nodePosition?.hasEnvironmentMetrics ?? false {
|
||||
Image(systemName: "cloud.sun.rain")
|
||||
.foregroundColor(.accentColor)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom)
|
||||
if position.nodePosition?.hopsAway ?? 0 == 0 && !(position.nodePosition?.viaMqtt ?? false) {
|
||||
LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast, compact: false)
|
||||
}
|
||||
if position.nodePosition?.hasDetectionSensorMetrics ?? false {
|
||||
Image(systemName: "sensor.fill")
|
||||
.symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3))
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom)
|
||||
}
|
||||
BatteryGauge(node: position.nodePosition!)
|
||||
Spacer()
|
||||
}
|
||||
if position.nodePosition?.hopsAway ?? 0 == 0 && !(position.nodePosition?.viaMqtt ?? false) {
|
||||
LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast, compact: false)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.top)
|
||||
if !popover {
|
||||
.padding(.top)
|
||||
if !popover {
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Spacer()
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("Close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
Spacer()
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("Close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.fraction(0.65), .large])
|
||||
.presentationContentInteraction(.scrolls)
|
||||
.presentationDragIndicator(.visible)
|
||||
|
|
|
|||
|
|
@ -375,8 +375,8 @@ struct DeviceOnboarding: View {
|
|||
fallthrough
|
||||
}
|
||||
case .location:
|
||||
let status = LocationsHandler.shared.manager.authorizationStatus
|
||||
if status != .notDetermined && status != .restricted && status != .denied {
|
||||
locationStatus = LocationsHandler.shared.manager.authorizationStatus
|
||||
if locationStatus != .notDetermined && locationStatus != .restricted {
|
||||
navigationPath.append(.localNetwork)
|
||||
}
|
||||
case .localNetwork:
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ struct AppIconPicker: View {
|
|||
@Binding var isPresenting: Bool
|
||||
@State private var didError = false
|
||||
@State private var errorDetails: String?
|
||||
var iconNames: [String?: String] = [nil: "Default", "AppIcon_Chirpy": "Chirpy"]
|
||||
var iconNames: [String?: String] = [nil: "Default", "AppIcon_MPowered": "Meshtastic Powered", "AppIcon_Chirpy": "Chirpy", "AppIcon_Ham": "Ham"]
|
||||
|
||||
// MARK: View
|
||||
var body: some View {
|
||||
List {
|
||||
Section(header: Text("Icons")) {
|
||||
ForEach(Array(iconNames.sorted(by: { $0.0 ?? "1" < $1.0 ?? "1"}).enumerated()), id: \.offset) { _, icon in
|
||||
ForEach(Array(iconNames.enumerated()), id: \.offset) { _, icon in
|
||||
AppIconButton(iconDescription: .constant(icon.value), iconName: .constant(icon.key), isPresenting: $isPresenting)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ struct LoRaConfig: View {
|
|||
Text(r.description)
|
||||
}
|
||||
}
|
||||
.fixedSize()
|
||||
Text("The region where you will be using your radios.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ struct MQTTConfig: View {
|
|||
@State var enabled = false
|
||||
@State var proxyToClientEnabled = false
|
||||
@State var address = ""
|
||||
@State var defaultServer = true
|
||||
@State var showTls = true
|
||||
@State var username = ""
|
||||
@State var password = ""
|
||||
@State var encryptionEnabled = true
|
||||
|
|
@ -196,7 +198,7 @@ struct MQTTConfig: View {
|
|||
.keyboardType(.default)
|
||||
}
|
||||
.autocorrectionDisabled()
|
||||
if address != "mqtt.meshtastic.org" {
|
||||
if !defaultServer {
|
||||
HStack {
|
||||
Label("Username", systemImage: "person.text.rectangle")
|
||||
TextField("Username", text: $username)
|
||||
|
|
@ -235,7 +237,7 @@ struct MQTTConfig: View {
|
|||
.keyboardType(.default)
|
||||
.listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
if !address.contains("mqtt.meshtastic.org") && !proxyToClientEnabled {
|
||||
if showTls {
|
||||
Toggle(isOn: $tlsEnabled) {
|
||||
Label("TLS Enabled", systemImage: "checkmark.shield.fill")
|
||||
Text("Your MQTT Server must support TLS.")
|
||||
|
|
@ -291,6 +293,13 @@ struct MQTTConfig: View {
|
|||
if address.lowercased() == "mqtt.meshtastic.org" {
|
||||
username = "meshdev"
|
||||
password = "large4cats"
|
||||
defaultServer = true
|
||||
if proxyToClientEnabled {
|
||||
showTls = false
|
||||
}
|
||||
} else {
|
||||
defaultServer = false
|
||||
showTls = true
|
||||
}
|
||||
if newAddress != node?.mqttConfig?.address ?? "" { hasChanges = true }
|
||||
}
|
||||
|
|
@ -316,7 +325,7 @@ struct MQTTConfig: View {
|
|||
if newJsonEnabled != node?.mqttConfig?.jsonEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: tlsEnabled) { _, newTlsEnabled in
|
||||
if address.lowercased() == "mqtt.meshtastic.org" {
|
||||
if defaultServer {
|
||||
tlsEnabled = false
|
||||
} else {
|
||||
if newTlsEnabled != node?.mqttConfig?.tlsEnabled { hasChanges = true }
|
||||
|
|
@ -426,6 +435,11 @@ struct MQTTConfig: View {
|
|||
self.enabled = node?.mqttConfig?.enabled ?? false
|
||||
self.proxyToClientEnabled = node?.mqttConfig?.proxyToClientEnabled ?? false
|
||||
self.address = node?.mqttConfig?.address ?? ""
|
||||
if address.lowercased().contains("mqtt.meshtastic.org") {
|
||||
defaultServer = true
|
||||
} else {
|
||||
defaultServer = false
|
||||
}
|
||||
self.username = node?.mqttConfig?.username ?? ""
|
||||
self.password = node?.mqttConfig?.password ?? ""
|
||||
self.root = node?.mqttConfig?.root ?? "msh"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ struct Settings: View {
|
|||
|
||||
@State private var selectedNode: Int = 0
|
||||
@State private var preferredNodeNum: Int = 0
|
||||
@State private var moduleOverride: Bool = false
|
||||
|
||||
@ObservedObject
|
||||
var router: Router
|
||||
|
|
@ -35,7 +34,7 @@ struct Settings: View {
|
|||
// MARK: Helper
|
||||
|
||||
private func isModuleSupported(_ module: ExcludedModules) -> Bool {
|
||||
return moduleOverride || Int(nodes.first(where: { $0.num == preferredNodeNum })?.metadata?.excludedModules ?? Int32.zero) & module.rawValue == 0
|
||||
return Int(nodes.first(where: { $0.num == preferredNodeNum })?.metadata?.excludedModules ?? Int32.zero) & module.rawValue == 0
|
||||
}
|
||||
|
||||
private func isAnySupported(_ modules: [ExcludedModules]) -> Bool {
|
||||
|
|
@ -288,10 +287,6 @@ struct Settings: View {
|
|||
}
|
||||
} header: {
|
||||
Text("Module Configuration")
|
||||
} footer: {
|
||||
if moduleOverride {
|
||||
Text("Currently showing modules that may not be supported by this node.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -554,8 +549,6 @@ struct Settings: View {
|
|||
.navigationTitle("Settings")
|
||||
.navigationBarItems(
|
||||
leading: MeshtasticLogo().onLongPressGesture(minimumDuration: 1.0) {
|
||||
self.moduleOverride.toggle()
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||