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>
This commit is contained in:
Garth Vander Houwen 2025-09-18 13:19:45 -07:00 committed by GitHub
parent 438efc3beb
commit 1b98ae97cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 802 additions and 404 deletions

View file

@ -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" : {

View file

@ -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 = "";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

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

View file

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

View file

@ -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&apos;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>

View file

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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

View 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"
}
}

View file

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

View 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"
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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