mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge branch '2.6.2'
#Conflicts: # Localizable.xcstrings
This commit is contained in:
commit
a264a09d24
33 changed files with 3101 additions and 3788 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -149,13 +149,13 @@ enum DisplayModes: Int, CaseIterable, Identifiable {
|
|||
var description: String {
|
||||
switch self {
|
||||
case .defaultMode:
|
||||
return "default.128x64.screen.layout".localized
|
||||
return "Default 128x64 screen layout".localized
|
||||
case .twoColor:
|
||||
return "optimized.for.2.color.displays".localized
|
||||
return "Optimized for 2 color displays".localized
|
||||
case .inverted:
|
||||
return "inverted.top.bar.for.2.color.display".localized
|
||||
return "Inverted top bar for 2 Color display".localized
|
||||
case .color:
|
||||
return "tft.full.color.displays".localized
|
||||
return "TFT Full Color Displays".localized
|
||||
}
|
||||
}
|
||||
func protoEnumValue() -> Config.DisplayConfig.DisplayMode {
|
||||
|
|
|
|||
|
|
@ -484,7 +484,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
Logger.data.error("Error Updating Core Data BluetoothConfigEntity: \(nsError, privacy: .public)")
|
||||
}
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.sent %@".localized, destNum.toHex())
|
||||
let logString = String.localizedStringWithFormat("Sent a Trace Route Request to node: %@".localized, destNum.toHex())
|
||||
Logger.mesh.info("🪧 \(logString, privacy: .public)")
|
||||
|
||||
} catch {
|
||||
|
|
@ -498,13 +498,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return }
|
||||
|
||||
if FROMRADIO_characteristic == nil {
|
||||
Logger.mesh.error("🚨 \("firmware.version.unsupported".localized, privacy: .public)")
|
||||
Logger.mesh.error("🚨 \("Unsupported Firmware Version Detected, unable to connect to device.".localized, privacy: .public)")
|
||||
invalidVersion = true
|
||||
return
|
||||
} else {
|
||||
|
||||
let nodeName = connectedPeripheral?.peripheral.name ?? "Unknown".localized
|
||||
let logString = String.localizedStringWithFormat("mesh.log.wantconfig %@".localized, nodeName)
|
||||
let logString = String.localizedStringWithFormat("Issuing Want Config to %@".localized, nodeName)
|
||||
Logger.mesh.info("🛎️ \(logString, privacy: .public)")
|
||||
// BLE Characteristics discovered, issue wantConfig
|
||||
var toRadio: ToRadio = ToRadio()
|
||||
|
|
@ -954,7 +954,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
let nsError = error as NSError
|
||||
Logger.data.error("Error Updating Core Data TraceRouteHop: \(nsError, privacy: .public)")
|
||||
}
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.route %@".localized, routeString)
|
||||
let logString = String.localizedStringWithFormat("Trace Route request returned: %@".localized, routeString)
|
||||
Logger.mesh.info("🪧 \(logString, privacy: .public)")
|
||||
}
|
||||
case .neighborinfoApp:
|
||||
|
|
@ -1061,7 +1061,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
connectTo(peripheral: preferredPeripheral!.peripheral)
|
||||
}
|
||||
let nodeName = connectedPeripheral?.peripheral.name ?? "Unknown".localized
|
||||
let logString = String.localizedStringWithFormat("mesh.log.textmessage.send.failed %@".localized, nodeName)
|
||||
let logString = String.localizedStringWithFormat("Message Send Failed, not properly connected to %@".localized, nodeName)
|
||||
Logger.mesh.info("🚫 \(logString, privacy: .public)")
|
||||
|
||||
success = false
|
||||
|
|
@ -1147,7 +1147,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.textmessage.sent %@ %@ %@".localized, String(newMessage.messageId), fromUserNum.toHex(), toUserNum.toHex())
|
||||
let logString = String.localizedStringWithFormat("Sent message %@ from %@ to %@".localized, String(newMessage.messageId), fromUserNum.toHex(), toUserNum.toHex())
|
||||
|
||||
Logger.mesh.info("💬 \(logString, privacy: .public)")
|
||||
do {
|
||||
|
|
@ -1195,7 +1195,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
guard let binaryData: Data = try? toRadio.serializedData() else {
|
||||
return false
|
||||
}
|
||||
let logString = String.localizedStringWithFormat("mesh.log.waypoint.sent %@".localized, String(fromNodeNum))
|
||||
let logString = String.localizedStringWithFormat("Sent a Waypoint Packet from: %@".localized, String(fromNodeNum))
|
||||
Logger.mesh.info("📍 \(logString, privacy: .public)")
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
|
|
@ -1359,7 +1359,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.sharelocation %@".localized, String(fromNodeNum))
|
||||
let logString = String.localizedStringWithFormat("Sent a Position Packet from the Apple device GPS to node: %@".localized, String(fromNodeNum))
|
||||
Logger.services.debug("📍 \(logString, privacy: .public)")
|
||||
return true
|
||||
} else {
|
||||
|
|
@ -1714,7 +1714,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.channel.sent %@ %d".localized, String(connectedPeripheral.num), chan.index)
|
||||
let logString = String.localizedStringWithFormat("Sent a Channel for: %@ Channel Index %d".localized, String(connectedPeripheral.num), chan.index)
|
||||
Logger.mesh.info("🎛️ \(logString, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
|
@ -1743,7 +1743,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.lora.config.sent %@".localized, String(connectedPeripheral.num))
|
||||
let logString = String.localizedStringWithFormat("Sent a LoRa.Config for: %@".localized, String(connectedPeripheral.num))
|
||||
Logger.mesh.info("📻 \(logString, privacy: .public)")
|
||||
}
|
||||
|
||||
|
|
@ -2663,7 +2663,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.cannedmessages.messages.get %@".localized, String(connectedPeripheral.num))
|
||||
let logString = String.localizedStringWithFormat("Requested Canned Messages Module Messages for node: %@".localized, String(connectedPeripheral.num))
|
||||
Logger.mesh.info("🥫 \(logString, privacy: .public)")
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNu
|
|||
|
||||
func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedObjectContext) -> MyInfoEntity? {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.myinfo %@".localized, String(myInfo.myNodeNum))
|
||||
let logString = String.localizedStringWithFormat("MyInfo received: %@".localized, String(myInfo.myNodeNum))
|
||||
Logger.mesh.info("ℹ️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchMyInfoRequest = MyInfoEntity.fetchRequest()
|
||||
|
|
@ -209,7 +209,7 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
|
|||
func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
if metadata.isInitialized {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.device.metadata.received %@".localized, fromNum.toHex())
|
||||
let logString = String.localizedStringWithFormat("Device Metadata received from: %@".localized, fromNum.toHex())
|
||||
Logger.mesh.info("🏷️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchedNodeRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -261,7 +261,7 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, sessionPass
|
|||
|
||||
func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObjectContext) -> NodeInfoEntity? {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, String(nodeInfo.num))
|
||||
let logString = String.localizedStringWithFormat("Node info received for: %@".localized, String(nodeInfo.num))
|
||||
Logger.mesh.info("📟 \(logString, privacy: .public)")
|
||||
|
||||
guard nodeInfo.num > 0 else { return nil }
|
||||
|
|
@ -472,7 +472,7 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
|
||||
if !cmmc.messages.isEmpty {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.cannedmessages.messages.received %@".localized, packet.from.toHex())
|
||||
let logString = String.localizedStringWithFormat("Canned Messages Messages Received For: %@".localized, packet.from.toHex())
|
||||
Logger.mesh.info("🥫 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -582,7 +582,7 @@ func adminResponseAck (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
}
|
||||
func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.paxcounter %@".localized, String(packet.from))
|
||||
let logString = String.localizedStringWithFormat("PAX Counter message received from: %@".localized, String(packet.from))
|
||||
Logger.mesh.info("🧑🤝🧑 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -626,7 +626,7 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
|
|||
let routingError = RoutingError(rawValue: routingMessage.errorReason.rawValue)
|
||||
|
||||
let routingErrorString = routingError?.display ?? "Unknown".localized
|
||||
let logString = String.localizedStringWithFormat("mesh.log.routing.message %@ %@".localized, String(packet.decoded.requestID), routingErrorString)
|
||||
let logString = String.localizedStringWithFormat("Routing received for RequestID: %@ Ack Status: %@".localized, String(packet.decoded.requestID), routingErrorString)
|
||||
Logger.mesh.info("🕸️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchMessageRequest = MessageEntity.fetchRequest()
|
||||
|
|
@ -686,7 +686,7 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
|
|||
func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) {
|
||||
|
||||
if let telemetryMessage = try? Telemetry(serializedBytes: packet.decoded.payload) {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.telemetry.received %@".localized, String(packet.from))
|
||||
let logString = String.localizedStringWithFormat("Telemetry received for: %@".localized, String(packet.from))
|
||||
Logger.mesh.info("📈 \(logString, privacy: .public)")
|
||||
if telemetryMessage.variant != Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.localStats(telemetryMessage.localStats) && telemetryMessage.variant != Telemetry.OneOf_Variant.powerMetrics(telemetryMessage.powerMetrics) {
|
||||
/// Other unhandled telemetry packets
|
||||
|
|
@ -875,7 +875,7 @@ func textMessageAppPacket(
|
|||
}
|
||||
|
||||
if messageText?.count ?? 0 > 0 {
|
||||
Logger.mesh.info("💬 \("mesh.log.textmessage.received".localized, privacy: .public)")
|
||||
Logger.mesh.info("💬 \("Message received from the text message app.".localized, privacy: .public)")
|
||||
let messageUsers = UserEntity.fetchRequest()
|
||||
messageUsers.predicate = NSPredicate(format: "num IN %@", [packet.to, packet.from])
|
||||
do {
|
||||
|
|
@ -1037,7 +1037,7 @@ func textMessageAppPacket(
|
|||
|
||||
func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.waypoint.received %@".localized, String(packet.from))
|
||||
let logString = String.localizedStringWithFormat("Waypoint Packet received from node: %@".localized, String(packet.from))
|
||||
Logger.mesh.info("📍 \(logString, privacy: .public)")
|
||||
|
||||
let fetchWaypointRequest = WaypointEntity.fetchRequest()
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes
|
|||
|
||||
func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, packet.from.toHex())
|
||||
let logString = String.localizedStringWithFormat("Node info received for: %@".localized, packet.from.toHex())
|
||||
Logger.mesh.info("📟 \(logString, privacy: .public)")
|
||||
|
||||
guard packet.from > 0 else { return }
|
||||
|
|
@ -312,7 +312,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
|
||||
func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.position.received %@".localized, String(packet.from))
|
||||
let logString = String.localizedStringWithFormat("Position Packet received from node: %@".localized, String(packet.from))
|
||||
Logger.mesh.info("📍 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodePositionRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -505,7 +505,7 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessi
|
|||
|
||||
func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.display.config %@".localized, nodeNum.toHex())
|
||||
let logString = String.localizedStringWithFormat("Display config received: %@".localized, nodeNum.toHex())
|
||||
Logger.data.info("🖥️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -567,7 +567,7 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses
|
|||
|
||||
func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.lora.config %@".localized, nodeNum.toHex())
|
||||
let logString = String.localizedStringWithFormat("LoRa config received: %@".localized, nodeNum.toHex())
|
||||
Logger.data.info("📻 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -638,7 +638,7 @@ func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPa
|
|||
|
||||
func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.network.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("Network config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("🌐 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -687,7 +687,7 @@ func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, ses
|
|||
|
||||
func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.position.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("Positon config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("🗺️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -750,7 +750,7 @@ func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, s
|
|||
}
|
||||
|
||||
func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.power.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("Power config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("🗺️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -916,7 +916,7 @@ func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightin
|
|||
|
||||
func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.cannedmessage.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("Canned Message module config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("🥫 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -975,7 +975,7 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo
|
|||
|
||||
func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSensorConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("Detection Sensor module config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("🕵️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -1032,7 +1032,7 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso
|
|||
|
||||
func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("External Notification module config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("📣 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -1143,7 +1143,7 @@ func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, n
|
|||
|
||||
func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.ringtone.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("RTTTL Ringtone config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("⛰️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -1183,7 +1183,7 @@ func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, sessionPasskey: D
|
|||
|
||||
func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.mqtt.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("MQTT module config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("🌉 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -1245,7 +1245,7 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6
|
|||
|
||||
func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.rangetest.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("Range Test module config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("⛰️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -1289,7 +1289,7 @@ func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nod
|
|||
|
||||
func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.serial.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("Serial module config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("🤖 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
@ -1344,7 +1344,7 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum:
|
|||
|
||||
func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum))
|
||||
let logString = String.localizedStringWithFormat("Store & Forward module config received: %@".localized, String(nodeNum))
|
||||
Logger.data.info("📬 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ struct Connect: View {
|
|||
#endif
|
||||
}
|
||||
} label: {
|
||||
Label("mesh.live.activity", systemImage: liveActivityStarted ? "stop" : "play")
|
||||
Label("Mesh Live Activity", systemImage: liveActivityStarted ? "stop" : "play")
|
||||
}
|
||||
#endif
|
||||
Text("Num: \(String(node!.num))")
|
||||
|
|
@ -175,7 +175,7 @@ struct Connect: View {
|
|||
.foregroundColor(.red)
|
||||
.frame(width: 60, height: 60)
|
||||
.padding(.trailing)
|
||||
Text("not.connected").font(.title3)
|
||||
Text("No device connected").font(.title3)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,13 +40,13 @@ struct ContentView: View {
|
|||
router: appState.router
|
||||
)
|
||||
.tabItem {
|
||||
Label("nodes", systemImage: "flipphone")
|
||||
Label("Nodes", systemImage: "flipphone")
|
||||
}
|
||||
.tag(NavigationState.Tab.nodes)
|
||||
|
||||
MeshMap(router: appState.router)
|
||||
.tabItem {
|
||||
Label("map", systemImage: "map")
|
||||
Label("Mesh Map", systemImage: "map")
|
||||
}
|
||||
.tag(NavigationState.Tab.map)
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ struct MessageContextMenuItems: View {
|
|||
Image(systemName: "doc.on.doc")
|
||||
}
|
||||
|
||||
Menu("message.details") {
|
||||
Menu("Message Details") {
|
||||
VStack {
|
||||
let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp))
|
||||
Text("\(messageDate.formattedDate(format: MessageText.dateFormatString))").foregroundColor(.gray)
|
||||
|
|
@ -69,8 +69,8 @@ struct MessageContextMenuItems: View {
|
|||
}
|
||||
if isCurrentUser && message.receivedACK {
|
||||
VStack {
|
||||
Text("received.ack") + Text(": \(message.receivedACK ? "✔️" : "")")
|
||||
Text("received.ack.real") + Text(": \(message.realACK ? "✔️" : "")")
|
||||
Text("Received Ack") + Text(": \(message.receivedACK ? "✔️" : "")")
|
||||
Text("Recipient Ack") + Text(": \(message.realACK ? "✔️" : "")")
|
||||
}
|
||||
} else if isCurrentUser && message.ackError == 0 {
|
||||
// Empty Error
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ struct Messages: View {
|
|||
}
|
||||
NavigationLink(value: MessagesNavigationState.directMessages()) {
|
||||
Label {
|
||||
Text("direct.messages")
|
||||
Text("Direct Messages")
|
||||
.badge(unreadDirectMessages)
|
||||
.font(.title2)
|
||||
.padding()
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ struct DetectionSensorLog: View {
|
|||
.padding(.bottom)
|
||||
.padding(.trailing)
|
||||
}
|
||||
.navigationTitle("detection.sensor.log")
|
||||
.navigationTitle("Detection Sensor Log")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
@ -128,7 +128,7 @@ struct DetectionSensorLog: View {
|
|||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
contentType: .commaSeparatedText,
|
||||
defaultFilename: String("\(node.user?.longName ?? "Node") \("detection.sensor.log".localized)"),
|
||||
defaultFilename: String("\(node.user?.longName ?? "Node") \("Detection Sensor Log".localized)"),
|
||||
onCompletion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ struct DeviceMetricsLog: View {
|
|||
ContentUnavailableView("No Device Metrics", systemImage: "slash.circle")
|
||||
}
|
||||
}
|
||||
.navigationTitle("device.metrics.log")
|
||||
.navigationTitle("Device Metrics Log")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
@ -250,7 +250,7 @@ struct DeviceMetricsLog: View {
|
|||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
contentType: .commaSeparatedText,
|
||||
defaultFilename: String("\(node.user?.longName ?? "Node") \("device.metrics.log".localized)"),
|
||||
defaultFilename: String("\(node.user?.longName ?? "Node") \("Device Metrics Log".localized)"),
|
||||
onCompletion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ struct NodeList: View {
|
|||
.searchable(text: $searchText, placement: .automatic, prompt: "Find a node")
|
||||
.disableAutocorrection(true)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count)))
|
||||
.navigationTitle(String.localizedStringWithFormat("Nodes (%@)".localized, String(nodes.count)))
|
||||
.listStyle(.plain)
|
||||
.alert(
|
||||
"Position Exchange Requested",
|
||||
|
|
@ -272,7 +272,7 @@ struct NodeList: View {
|
|||
)
|
||||
}
|
||||
} else {
|
||||
ContentUnavailableView("select.node", systemImage: "flipphone")
|
||||
ContentUnavailableView("Select Node", systemImage: "flipphone")
|
||||
}
|
||||
} detail: {
|
||||
ContentUnavailableView("", systemImage: "line.3.horizontal")
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ struct PaxCounterLog: View {
|
|||
isPresented: $isPresentingClearLogConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("paxcounter.delete", role: .destructive) {
|
||||
Button("Delete all pax data?", role: .destructive) {
|
||||
if clearPax(destNum: node.num, context: context) {
|
||||
Logger.services.info("Cleared Pax Counter for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
|
|
@ -196,10 +196,10 @@ struct PaxCounterLog: View {
|
|||
.padding(.trailing)
|
||||
}
|
||||
} else {
|
||||
ContentUnavailableView("paxcounter.content.unavailable", systemImage: "slash.circle")
|
||||
ContentUnavailableView("No PAX Counter Logs", systemImage: "slash.circle")
|
||||
}
|
||||
}
|
||||
.navigationTitle("paxcounter.log")
|
||||
.navigationTitle("PAX Counter Log")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ struct PowerMetricsLog: View {
|
|||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
contentType: .commaSeparatedText,
|
||||
defaultFilename: String("\(node.user?.longName ?? "Node") \("power.metrics.log".localized)"),
|
||||
defaultFilename: String("\(node.user?.longName ?? "Node") \("Power Metrics Log".localized)"),
|
||||
onCompletion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ struct AboutMeshtastic: View {
|
|||
}
|
||||
.font(.title2)
|
||||
|
||||
Text("Version: \(Bundle.main.appVersionLong) (\(Bundle.main.appBuild)) ")
|
||||
Text("Version: \(Bundle.main.appVersionLong) (\(Bundle.main.appBuild))")
|
||||
}
|
||||
|
||||
Section(header: Text("Project information")) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ struct AppData: View {
|
|||
|
||||
VStack {
|
||||
|
||||
Section(header: Text("phone.gps")) {
|
||||
Section(header: Text("Phone GPS")) {
|
||||
GPSStatus()
|
||||
}
|
||||
Divider()
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ struct DeviceConfig: View {
|
|||
}
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle("device.config")
|
||||
.navigationTitle("Device Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ struct LoRaConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("lora.config")
|
||||
.navigationTitle("LoRa Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ struct DetectionSensorConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("detection.sensor.config")
|
||||
.navigationTitle("Detection Sensor Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ struct ExternalNotificationConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("external.notification.config")
|
||||
.navigationTitle("External Notification Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ struct MQTTConfig: View {
|
|||
|
||||
Toggle(isOn: $proxyToClientEnabled) {
|
||||
|
||||
Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right")
|
||||
Label("MQTT Client Proxy", systemImage: "iphone.radiowaves.left.and.right")
|
||||
Text("Utilizes the network connection on your phone to connect to MQTT.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
|
@ -340,7 +340,7 @@ struct MQTTConfig: View {
|
|||
if newMapPublishIntervalSecs != node?.mqttConfig?.mapPublishIntervalSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.navigationTitle("mqtt.config")
|
||||
.navigationTitle("MQTT Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ struct PaxCounterConfig: View {
|
|||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.listRowSeparator(.hidden)
|
||||
Text("config.module.paxcounter.updateinterval.description")
|
||||
Text("How often we can send a message to the mesh when people are detected.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ struct PaxCounterConfig: View {
|
|||
}
|
||||
}
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.powerConfig == nil)
|
||||
.navigationTitle("config.module.paxcounter.title")
|
||||
.navigationTitle("PAX Counter Config")
|
||||
.navigationBarItems(trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ struct RangeTestConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("range.test.config")
|
||||
.navigationTitle("Range Test Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ struct RtttlConfig: View {
|
|||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
ConfigHeader(title: "ringtone", config: \.rtttlConfig, node: node, onAppear: setRtttLConfigValue)
|
||||
ConfigHeader(title: "Ringtone", config: \.rtttlConfig, node: node, onAppear: setRtttLConfigValue)
|
||||
|
||||
Section(header: Text("Options")) {
|
||||
HStack {
|
||||
Label("ringtone", systemImage: "music.quarternote.3")
|
||||
TextField("config.ringtone.label", text: $ringtone, axis: .vertical)
|
||||
Label("Ringtone", systemImage: "music.quarternote.3")
|
||||
TextField("Ringtone Transfer Language", text: $ringtone, axis: .vertical)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
|
|
@ -43,7 +43,7 @@ struct RtttlConfig: View {
|
|||
}
|
||||
.keyboardType(.default)
|
||||
.listRowSeparator(.hidden)
|
||||
Text("config.ringtone.description")
|
||||
Text("Ringtone Transfer Language(RTTTL) Ringtone String used by supported buzzers in external notifications.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ struct RtttlConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("config.ringtone.title")
|
||||
.navigationTitle("Ringtone Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ struct SerialConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("serial.config")
|
||||
.navigationTitle("Serial Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ struct StoreForwardConfig: View {
|
|||
if enabled {
|
||||
Section(header: Text("Settings")) {
|
||||
Toggle(isOn: $heartbeat) {
|
||||
Label("storeforward.heartbeat", systemImage: "waveform.path.ecg")
|
||||
Label("Send Heartbeat", systemImage: "waveform.path.ecg")
|
||||
Text("Send a heartbeat to advertise the server's presence.")
|
||||
}
|
||||
Picker("Number of records", selection: $records) {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ struct NetworkConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("network.config")
|
||||
.navigationTitle("Network Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ struct PositionConfig: View {
|
|||
}
|
||||
saveButton
|
||||
}
|
||||
.navigationTitle("position.config")
|
||||
.navigationTitle("Position Config")
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: bleManager.connectedPeripheral?.shortName ?? "?")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ struct PowerConfig: View {
|
|||
|
||||
var body: some View {
|
||||
Form {
|
||||
ConfigHeader(title: "config.power.title", config: \.powerConfig, node: node, onAppear: setPowerValues)
|
||||
ConfigHeader(title: "Power Config", config: \.powerConfig, node: node, onAppear: setPowerValues)
|
||||
|
||||
Section {
|
||||
if (currentDevice?.architecture == .esp32 || currentDevice?.architecture == .esp32S3) || (currentDevice?.architecture == .nrf52840 && (node?.deviceConfig?.role ?? 0 == 5 || node?.deviceConfig?.role ?? 0 == 6)) {
|
||||
|
|
@ -38,7 +38,7 @@ struct PowerConfig: View {
|
|||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
Toggle(isOn: $shutdownOnPowerLoss) {
|
||||
Label("config.power.shutdown.on.power.loss", systemImage: "power")
|
||||
Label("Shutdown on Power Loss", systemImage: "power")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
if shutdownOnPowerLoss {
|
||||
|
|
@ -101,7 +101,7 @@ struct PowerConfig: View {
|
|||
}
|
||||
}
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.powerConfig == nil)
|
||||
.navigationTitle("config.power.title")
|
||||
.navigationTitle("Power Config")
|
||||
.navigationBarItems(trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ struct SaveConfigButton: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
let nodeName = node?.user?.longName ?? "Unknown".localized
|
||||
let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName)
|
||||
let buttonText = String.localizedStringWithFormat("Save Config for %@".localized, nodeName)
|
||||
Button(buttonText) {
|
||||
onConfirmation()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ struct RouteRecorder: View {
|
|||
Logger.data.error("Error Saving RouteEntity from the Route Recorder \(nsError, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label("start", systemImage: "play")
|
||||
Label("Start", systemImage: "play")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ struct Settings: View {
|
|||
// MARK: Views
|
||||
|
||||
var radioConfigurationSection: some View {
|
||||
Section("radio.configuration") {
|
||||
Section("Radio Configuration") {
|
||||
let node = nodes.first(where: { $0.num == preferredNodeNum })
|
||||
if let node,
|
||||
let loRaConfig = node.loRaConfig,
|
||||
|
|
@ -69,7 +69,7 @@ struct Settings: View {
|
|||
|
||||
NavigationLink(value: SettingsNavigationState.lora) {
|
||||
Label {
|
||||
Text("lora")
|
||||
Text("LoRa")
|
||||
} icon: {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.rotationEffect(.degrees(-90))
|
||||
|
|
@ -95,7 +95,7 @@ struct Settings: View {
|
|||
|
||||
NavigationLink(value: SettingsNavigationState.shareQRCode) {
|
||||
Label {
|
||||
Text("share.channels")
|
||||
Text("Share QR Code")
|
||||
} icon: {
|
||||
Image(systemName: "qrcode")
|
||||
}
|
||||
|
|
@ -189,7 +189,7 @@ struct Settings: View {
|
|||
if isModuleSupported(.detectionsensorConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.detectionSensor) {
|
||||
Label {
|
||||
Text("detection.sensor")
|
||||
Text("Detection Sensor")
|
||||
} icon: {
|
||||
Image(systemName: "sensor")
|
||||
}
|
||||
|
|
@ -199,7 +199,7 @@ struct Settings: View {
|
|||
if isModuleSupported(.extnotifConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.externalNotification) {
|
||||
Label {
|
||||
Text("external.notification")
|
||||
Text("External Notification")
|
||||
} icon: {
|
||||
Image(systemName: "megaphone")
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@ struct Settings: View {
|
|||
if isModuleSupported(.rangetestConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.rangeTest) {
|
||||
Label {
|
||||
Text("range.test")
|
||||
Text("Range Test")
|
||||
} icon: {
|
||||
Image(systemName: "point.3.connected.trianglepath.dotted")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ struct ShareChannels: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("generate.qr.code")
|
||||
.navigationTitle("Generate QR Code")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue