diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 9989f2b7..e6c29271 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -140,6 +140,16 @@ }, "%@%%" : { + }, + "%@%% %@%%" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@%% %2$@%%" + } + } + } }, "%@°F" : { @@ -6712,6 +6722,9 @@ }, "Drag & Drop is the recommended way to update firmware for NRF devices. If your iPhone or iPad is USB-C it will work with your regular USB-C charging cable, for lightning devices you need the Apple Lightning to USB camera adaptor." : { + }, + "Dupe / Bad Packets: %d" : { + }, "echo" : { "localizations" : { @@ -16072,6 +16085,12 @@ }, "Override automatic OLED screen detection." : { + }, + "Packets Received: %d" : { + + }, + "Packets Sent: %d" : { + }, "password" : { "localizations" : { @@ -17387,6 +17406,9 @@ }, "Requires that there be an accelerometer on your device." : { + }, + "Reset App Settings" : { + }, "Reset NodeDB" : { @@ -22679,7 +22701,7 @@ "Your Firmware is up to date" : { }, - "Your MQTT Server must support TLS." : { + "Your MQTT Server must support TLS. Not available via the public mqtt server." : { }, "Your position has been sent with a request for a response with their position. You will receive a notification when a position is returned." : { diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index ba8e38fa..2effa9ae 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -22,9 +22,9 @@ extension NodeInfoEntity { return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 1")).lastObject as? TelemetryEntity } - var latestLocalStats: TelemetryEntity? { - return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 6")).lastObject as? TelemetryEntity - } +// var latestLocalStats: TelemetryEntity? { +// return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 6")).lastObject as? TelemetryEntity +// } var hasPositions: Bool { return positions?.count ?? 0 > 0 diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 58d8757b..a7c7d5b8 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -52,38 +52,80 @@ struct Connect: View { if #available(iOS 17.0, macOS 14.0, *) { TipView(BluetoothConnectionTip(), arrowEdge: .bottom) } - HStack { - VStack(alignment: .center) { - CircleText(text: node?.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 90) - } - .padding(.trailing) - VStack(alignment: .leading) { - if node != nil { - Text(connectedPeripheral.longName).font(.title2) + VStack(alignment: .leading) { + HStack { + VStack(alignment: .center) { + CircleText(text: node?.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 90) + .padding(.trailing, 5) + if node?.latestDeviceMetrics != nil { + BatteryCompact(batteryLevel: node?.latestDeviceMetrics?.batteryLevel ?? 0, font: .caption, iconFont: .callout, color: .accentColor) + .padding(.trailing, 5) + } } - Text("ble.name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name ?? "unknown".localized)") - .font(.callout).foregroundColor(Color.gray) - if node != nil { - Text("firmware.version").font(.callout)+Text(": \(node?.metadata?.firmwareVersion ?? "unknown".localized)") + .padding(.trailing) + VStack(alignment: .leading) { + if node != nil { + Text(connectedPeripheral.longName).font(.title2) + } + Text("ble.name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name ?? "unknown".localized)") .font(.callout).foregroundColor(Color.gray) - } - if bleManager.isSubscribed { - Text("subscribed").font(.callout) - .foregroundColor(.green) - } else { - - HStack { - if #available(iOS 17.0, macOS 14.0, *) { - Image(systemName: "square.stack.3d.down.forward") - .symbolRenderingMode(.multicolor) - .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) + if node != nil { + Text("firmware.version").font(.callout)+Text(": \(node?.metadata?.firmwareVersion ?? "unknown".localized)") + .font(.callout).foregroundColor(Color.gray) + } + if bleManager.isSubscribed { + Text("subscribed").font(.callout) + .foregroundColor(.green) + } else { + HStack { + if #available(iOS 17.0, macOS 14.0, *) { + Image(systemName: "square.stack.3d.down.forward") + .symbolRenderingMode(.multicolor) + .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) + .foregroundColor(.orange) + } + Text("communicating").font(.callout) .foregroundColor(.orange) } - Text("communicating").font(.callout) - .foregroundColor(.orange) } } } + VStack { + let localStats = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 6")).lastObject as? TelemetryEntity + if localStats != nil { + Divider() + if localStats?.numTotalNodes ?? 0 >= 100 { + Text("\(String(format: "Connected: %d nodes online", localStats?.numOnlineNodes ?? 0))") + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .fixedSize() + } else { + Text("\(String(format: "Connected: %d of %d nodes online", localStats?.numOnlineNodes ?? 0, localStats?.numTotalNodes ?? 0))") + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .fixedSize() + } + Text("\(String(format: "Ch. Util: %.2f", localStats?.channelUtilization ?? 0))% \(String(format: "Airtime: %.2f", localStats?.airUtilTx ?? 0))%") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + Text("Packets Sent: \(localStats?.numPacketsTx ?? 0)") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + Text("Packets Received: \(localStats?.numPacketsRx ?? 0)") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + Text("Dupe / Bad Packets: \(localStats?.numPacketsRxBad ?? 0)") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .fixedSize() + } + } } .font(.caption) .foregroundColor(Color.gray) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 91a922c9..36cc5592 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -98,9 +98,6 @@ struct ChannelMessageList: View { Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) .foregroundStyle(ackErrorVal?.color ?? Color.red) .font(.caption2) - } else { - let messageDate = message.timestamp - Text(" \(messageDate.formattedDate(format: MessageText.dateFormatString))").font(.caption2).foregroundColor(.gray) } } } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 02286655..0bb86bc1 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -76,9 +76,11 @@ struct AppSettings: View { } clearCoreDataDatabase(context: context, includeRoutes: true) context.refreshAllObjects() - UserDefaults.standard.reset() } } + Button("Reset App Settings", systemImage: "clipboard") { + UserDefaults.standard.reset() + } } if totalDownloadedTileSize != "0MB" { Section(header: Text("Map Tile Data")) { diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index ce48b658..3bb6ab38 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -98,7 +98,12 @@ struct Channels: View { preciseLocation = false positionsEnabled = false } else { - if positionPrecision == 32 { + if channelKey == "AQ==" { + preciseLocation = false + if positionPrecision < 10 || positionPrecision > 16 { + positionPrecision = 13 + } + } else if positionPrecision == 32 { preciseLocation = true positionsEnabled = true } else { @@ -250,7 +255,7 @@ struct Channels: View { channelKey = key positionsEnabled = false preciseLocation = false - positionPrecision = 0 + positionPrecision = 13 uplink = false downlink = false hasChanges = true diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 780a19b4..28fe1116 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -157,11 +157,20 @@ struct ChannelForm: View { if !preciseLocation { VStack(alignment: .leading) { Label("Approximate Location", systemImage: "location.slash.circle.fill") - Slider(value: $positionPrecision, in: 10...19, step: 1) { - } minimumValueLabel: { - Image(systemName: "minus") - } maximumValueLabel: { - Image(systemName: "plus") + if channelKeySize == -1 { + Slider(value: $positionPrecision, in: 10...16, step: 1) { + } minimumValueLabel: { + Image(systemName: "minus") + } maximumValueLabel: { + Image(systemName: "plus") + } + } else { + Slider(value: $positionPrecision, in: 10...19, step: 1) { + } minimumValueLabel: { + Image(systemName: "minus") + } maximumValueLabel: { + Image(systemName: "plus") + } } Text(PositionPrecision(rawValue: Int(positionPrecision))?.description ?? "") .foregroundColor(.gray) @@ -199,6 +208,11 @@ struct ChannelForm: View { .onChange(of: channelKey) { _ in hasChanges = true } + .onChange(of: channelKeySize) { _ in + if channelKeySize == -1 { + preciseLocation = false + } + } .onChange(of: channelRole) { _ in hasChanges = true } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index d6176526..8ef411ea 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -24,7 +24,7 @@ struct MQTTConfig: View { @State var password = "" @State var encryptionEnabled = true @State var jsonEnabled = false - @State var tlsEnabled = true + @State var tlsEnabled = false @State var root = "msh" @State var selectedTopic = "" @State var mqttConnected: Bool = false @@ -234,7 +234,7 @@ struct MQTTConfig: View { .listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/) Toggle(isOn: $tlsEnabled) { Label("TLS Enabled", systemImage: "checkmark.shield.fill") - Text("Your MQTT Server must support TLS.") + Text("Your MQTT Server must support TLS. Not available via the public mqtt server.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } @@ -288,9 +288,6 @@ struct MQTTConfig: View { jsonEnabled = false } if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true } - if newProxyToClientEnabled { - jsonEnabled = false - } } .onChange(of: address) { newAddress in if node != nil && node?.mqttConfig != nil { @@ -324,8 +321,12 @@ struct MQTTConfig: View { } if newJsonEnabled != node?.mqttConfig?.jsonEnabled { hasChanges = true } } - .onChange(of: tlsEnabled) { - if $0 != node?.mqttConfig?.tlsEnabled { hasChanges = true } + .onChange(of: tlsEnabled) { newTlsEnabled in + if address.lowercased() == "mqtt.meshtastic.org" { + tlsEnabled = false + } else { + if newTlsEnabled != node?.mqttConfig?.tlsEnabled { hasChanges = true } + } } .onChange(of: mqttConnected) { newMqttConnected in if newMqttConnected == false { diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 0235f566..14d9762f 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -23,7 +23,7 @@ struct WidgetsLiveActivity: Widget { nodesOnline: context.state.nodesOnline, totalNodes: context.state.totalNodes, timerRange: context.state.timerRange) - .widgetURL(URL(string: "meshtastic:///node/\(context.attributes.name)")) + .widgetURL(URL(string: "meshtastic:///bluetooth")) } dynamicIsland: { context in DynamicIsland { @@ -38,10 +38,17 @@ struct WidgetsLiveActivity: Widget { .fixedSize() Spacer() } - Text("\(context.state.nodesOnline) nodes online") - .font(.caption) - .foregroundStyle(.secondary) - .fixedSize() + if context.state.nodesOnline >= 100 { + Text("100+ online") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize() + } else { + Text("\(context.state.nodesOnline) of \(context.state.totalNodes) online") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize() + } Text("\(String(format: "Ch. Util: %.2f", context.state.channelUtilization))%") .font(.caption) .foregroundStyle(.secondary) @@ -74,7 +81,7 @@ struct WidgetsLiveActivity: Widget { .font(.caption) .foregroundStyle(.secondary) .fixedSize() - Text("Dupe / Bad: \(context.state.badReceivedPackets)") + Text("Dupe / Bad \(context.state.badReceivedPackets)") .font(.caption) .foregroundStyle(.secondary) .fixedSize() @@ -108,7 +115,7 @@ struct WidgetsLiveActivity: Widget { .contentMargins(.trailing, 32, for: .expanded) .contentMargins([.leading, .top, .bottom], 6, for: .compactLeading) .contentMargins(.all, 6, for: .minimal) - .widgetURL(URL(string: "meshtastic:///node/\(context.attributes.name)")) + .widgetURL(URL(string: "meshtastic:///bluetooth")) } } } @@ -214,12 +221,21 @@ struct NodeInfoView: View { .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() - Text("\(String(format: "Connected: %d nodes online", nodesOnline, totalNodes))") - .font(.caption) - .fontWeight(.medium) - .foregroundStyle(.secondary) - .opacity(isLuminanceReduced ? 0.8 : 1.0) - .fixedSize() + if totalNodes >= 100 { + Text("\(String(format: "Connected: %d nodes online", nodesOnline))") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.8 : 1.0) + .fixedSize() + } else { + Text("\(String(format: "Connected: %d of %d nodes online", nodesOnline, totalNodes))") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.8 : 1.0) + .fixedSize() + } let now = Date() Text("Last Heard: \(now.formatted())") .font(.caption)