Merge branch '2.6.2'

#Conflicts:
#	Localizable.xcstrings
This commit is contained in:
Garth Vander Houwen 2025-05-08 14:53:04 -07:00
commit febe290ca8
28 changed files with 3153 additions and 4050 deletions

File diff suppressed because it is too large Load diff

View file

@ -80,9 +80,9 @@ enum UserTrackingModes: Int, CaseIterable, Identifiable {
case .none:
return "map.usertrackingmode.none".localized
case .follow:
return "map.usertrackingmode.follow".localized
return "Follow".localized
case .followWithHeading:
return "map.usertrackingmode.followwithheading".localized
return "Follow with heading".localized
}
}
var icon: String {

View file

@ -21,7 +21,6 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
case takTracker = 10
case repeater = 4
case router = 2
case routerClient = 3
case routerLate = 11
var id: Int { self.rawValue }
@ -30,56 +29,52 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
case .client:
return "Client".localized
case .clientMute:
return "device.role.name.clientMute".localized
return "Client Mute".localized
case .router:
return "device.role.name.router".localized
case .routerClient:
return "device.role.name.routerClient".localized
return "Router".localized
case .repeater:
return "device.role.name.repeater".localized
return "Repeater".localized
case .tracker:
return "device.role.name.tracker".localized
return "Tracker".localized
case .sensor:
return "device.role.name.sensor".localized
return "Sensor".localized
case .tak:
return "device.role.name.tak".localized
return "TAK".localized
case .takTracker:
return "device.role.name.takTracker".localized
return "TAK Tracker".localized
case .clientHidden:
return "device.role.name.clientHidden".localized
return "Client Hidden".localized
case .lostAndFound:
return "device.role.name.lostAndFound".localized
return "Lost and Found".localized
case .routerLate:
return "device.role.name.routerlate".localized
return "Router Late".localized
}
}
var description: String {
switch self {
case .client:
return "device.role.client".localized
return "App connected or stand alone messaging device.".localized
case .clientMute:
return "device.role.clientmute".localized
return "Device that does not forward packets from other devices.".localized
case .router:
return "device.role.router".localized
case .routerClient:
return "device.role.routerclient".localized
return "Infrastructure node on a tower or mountain top only. Not to be used for roofs or mobile nodes. Needs exceptional coverage. Visible in Nodes list.".localized
case .repeater:
return "device.role.repeater".localized
return "Infrastructure node on a tower or mountain top only. Not to be used for roofs or mobile nodes. Relays messages with minimal overhead. Not visible in Nodes list.".localized
case .tracker:
return "device.role.tracker".localized
return "Broadcasts GPS position packets as priority.".localized
case .sensor:
return "device.role.sensor".localized
return "Broadcasts telemetry packets as priority.".localized
case .tak:
return "device.role.tak".localized
return "Optimized for ATAK system communication, reduces routine broadcasts.".localized
case .takTracker:
return "device.role.taktracker".localized
return "Enables automatic TAK PLI broadcasts and reduces routine broadcasts.".localized
case .clientHidden:
return "device.role.clienthidden".localized
return "Device that only broadcasts as needed for stealth or power savings.".localized
case .lostAndFound:
return "device.role.lostandfound".localized
return "Broadcasts location as message to default channel regularly for to assist with device recovery.".localized
case .routerLate:
return "device.role.routerlate".localized
return "Infrastructure node that always rebroadcasts packets once but only after all other modes, ensuring additional coverage for local clusters. Visible in Nodes list.".localized
}
}
@ -89,7 +84,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
return "apps.iphone"
case .clientMute:
return "speaker.slash"
case .router, .routerClient, .routerLate:
case .router, .routerLate:
return "wifi.router"
case .repeater:
return "repeat"
@ -116,8 +111,6 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
return Config.DeviceConfig.Role.clientMute
case .router:
return Config.DeviceConfig.Role.router
case .routerClient:
return Config.DeviceConfig.Role.routerClient
case .repeater:
return Config.DeviceConfig.Role.repeater
case .tracker:

View file

@ -105,27 +105,27 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
case .in:
return "India".localized
case .nz865:
return "New Zealand 865mhz".localized
return "New Zealand 865MHz".localized
case .th:
return "Thailand".localized
case .ua433:
return "Ukraine 433mhz".localized
return "Ukraine 433MHz".localized
case .ua868:
return "Ukraine 868mhz".localized
return "Ukraine 868MHz".localized
case .lora24:
return "2.4 Ghz".localized
case .my433:
return "Malaysia 433mhz".localized
return "Malaysia 433MHz".localized
case .my919:
return "Malaysia 919mhz".localized
return "Malaysia 919MHz".localized
case .sg923:
return "Singapore 923mhz".localized
return "Singapore 923MHz".localized
case .ph433:
return "Philippines 433mhz".localized
return "Philippines 433MHz".localized
case .ph868:
return "Philippines 868mhz".localized
return "Philippines 868MHz".localized
case .ph915:
return "Philippines 915mhz".localized
return "Philippines 915MHz".localized
}
}
var dutyCycle: Int {
@ -280,7 +280,6 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
case longFast = 0
case longSlow = 1
case longModerate = 7
case vLongSlow = 2
case medSlow = 3
case medFast = 4
case shortSlow = 5
@ -295,19 +294,17 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
case .longSlow:
return "Long Range - Slow".localized
case .longModerate:
return "long.range.moderate".localized
case .vLongSlow:
return "very.long.range.slow".localized
return "Long Range - Moderate".localized
case .medSlow:
return "medium.range.slow".localized
return "Medium Range - Slow".localized
case .medFast:
return "medium.range.fast".localized
return "Medium Range - Fast".localized
case .shortSlow:
return "short.range.slow".localized
return "Short Range - Slow".localized
case .shortFast:
return "short.range.fast".localized
return "Short Range - Fast".localized
case .shortTurbo:
return "short.range.turbo".localized
return "Short Range - Turbo".localized
}
}
var name: String {
@ -318,8 +315,6 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
return "LongSlow"
case .longModerate:
return "LongModerate"
case .vLongSlow:
return "VLongFast"
case .medSlow:
return "MediumSlow"
case .medFast:
@ -340,8 +335,6 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
return -7.5
case .longModerate:
return -17.5
case .vLongSlow:
return -20
case .medSlow:
return -15
case .medFast:
@ -362,8 +355,6 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
return Config.LoRaConfig.ModemPreset.longSlow
case .longModerate:
return Config.LoRaConfig.ModemPreset.longModerate
case .vLongSlow:
return Config.LoRaConfig.ModemPreset.veryLongSlow
case .medSlow:
return Config.LoRaConfig.ModemPreset.mediumSlow
case .medFast:

View file

@ -21,17 +21,17 @@ enum GpsFormats: Int, CaseIterable, Identifiable {
var description: String {
switch self {
case .gpsFormatDec:
return "gpsformat.dec".localized
return "Decimal Degrees Format".localized
case .gpsFormatDms:
return "gpsformat.dms".localized
return "Degrees Minutes Seconds".localized
case .gpsFormatUtm:
return "gpsformat.utm".localized
return "Universal Transverse Mercator".localized
case .gpsFormatMgrs:
return "gpsformat.mgrs".localized
return "Military Grid Reference System".localized
case .gpsFormatOlc:
return "gpsformat.olc".localized
return "Open Location Code (aka Plus Codes)".localized
case .gpsFormatOsgr:
return "gpsformat.osgr".localized
return "Ordnance Survey Grid Reference".localized
}
}
func protoEnumValue() -> Config.DisplayConfig.GpsCoordinateFormat {

View file

@ -20,34 +20,34 @@ enum ActivityType: Int, CaseIterable, Identifiable {
var description: String {
switch self {
case .walking:
return "routes.activitytype.walking".localized
return "Walking".localized
case .hiking:
return "routes.activitytype.hiking".localized
return "Hiking".localized
case .biking:
return "routes.activitytype.biking".localized
return "Biking".localized
case .driving:
return "routes.activitytype.driving".localized
return "Driving".localized
case .overlanding:
return "routes.activitytype.overlanding".localized
return "Overlanding".localized
case .skiing:
return "routes.activitytype.skiing".localized
return "Skiing".localized
}
}
var fileNameString: String {
switch self {
case .walking:
return "routes.activitytype.filename.walking".localized
return "walk".localized
case .hiking:
return "routes.activitytype.filename.hiking".localized
return "hiking".localized
case .biking:
return "routes.activitytype.filename.biking".localized
return "biking".localized
case .driving:
return "routes.activitytype.filename.driving".localized
return "driving".localized
case .overlanding:
return "routes.activitytype.filename.overlanding".localized
return "overlanding".localized
case .skiing:
return "routes.activitytype.filename.skiing".localized
return "skiing".localized
}
}
}

View file

@ -120,15 +120,15 @@ enum SerialModeTypes: Int, CaseIterable, Identifiable {
case .default:
return "Default".localized
case .simple:
return "serial.mode.simple".localized
return "Simple".localized
case .proto:
return "serial.mode.proto".localized
return "Protobufs".localized
case .txtmsg:
return "serial.mode.txtmsg".localized
return "Text Message".localized
case .nmea:
return "serial.mode.nmea".localized
return "NMEA Positions".localized
case .caltopo:
return "serial.mode.caltopo".localized
return "CALTOPO".localized
}
}
func protoEnumValue() -> ModuleConfig.SerialConfig.Serial_Mode {

View file

@ -30,11 +30,11 @@ extension Date {
let hour = Calendar.current.component(.hour, from: self)
switch hour {
case 6..<12: return "relativetimeofday.morning".localized
case 12: return "relativetimeofday.midday".localized
case 13..<17: return "relativetimeofday.afternoon".localized
case 17..<22: return "relativetimeofday.evening".localized
default: return "relativetimeofday.nighttime".localized
case 6..<12: return "Morning".localized
case 12: return "Midday".localized
case 13..<17: return "Afternoon".localized
case 17..<22: return "Evening".localized
default: return "Nighttime".localized
}
}
}

View file

@ -739,7 +739,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
let supportedVersion = connectedVersion == "0.0.0" || self.minimumVersion.compare(connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(connectedVersion, options: .numeric) == .orderedSame
if !supportedVersion {
invalidVersion = true
lastConnectionError = "🚨" + "update.firmware".localized
lastConnectionError = "🚨" + "Update Your Firmware".localized
return
}
}

View file

@ -863,7 +863,7 @@ func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, s
func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat("mesh.log.ambientlighting.config %@".localized, String(nodeNum))
let logString = String.localizedStringWithFormat("Ambient Lighting module config received: %@".localized, String(nodeNum))
Logger.data.info("🏮 \(logString, privacy: .public)")
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()

View file

@ -13,10 +13,10 @@
return "tip.channels.share"
}
var title: Text {
Text("tip.channels.share.title")
Text("Sharing Meshtastic Channels")
}
var message: Text? {
Text("tip.channels.share.message")
Text("A Meshtastic QR code contains the LoRa config and channel values needed for radios to communicate. You can share a complete channel configuration using the Replace Channels option, if you choose Add Channels your shared channels will be added to the channels on the receiving radio.")
}
var image: Image? {
Image(systemName: "qrcode")
@ -29,10 +29,10 @@ struct CreateChannelsTip: Tip {
return "tip.channels.create"
}
var title: Text {
Text("tip.channels.create.title")
Text("Manage Channels")
}
var message: Text? {
Text("tip.channels.create.message")
Text("Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)")
}
var image: Image? {
Image(systemName: "fibrechannel")

View file

@ -16,7 +16,7 @@ struct MessagesTip: Tip {
Text("Messages")
}
var message: Text? {
Text("tip.messages.message")
Text("You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details.")
}
var image: Image? {
Image(systemName: "bubble.left.and.bubble.right")

View file

@ -32,7 +32,7 @@ struct Connect: View {
VStack {
List {
if bleManager.isSwitchedOn {
Section(header: Text("connected.radio").font(.title)) {
Section(header: Text("Connected Radio").font(.title)) {
if let connectedPeripheral = bleManager.connectedPeripheral, connectedPeripheral.peripheral.state == .connected {
TipView(BluetoothConnectionTip(), arrowEdge: .bottom)
VStack(alignment: .leading) {
@ -125,7 +125,7 @@ struct Connect: View {
NavigationLink {
LoRaConfig(node: node)
} label: {
Label("set.region", systemImage: "globe.americas.fill")
Label("Set LoRa Region", systemImage: "globe.americas.fill")
.foregroundColor(.red)
.font(.title)
}
@ -142,7 +142,7 @@ struct Connect: View {
.frame(width: 60, height: 60)
.padding(.trailing)
if bleManager.timeoutTimerCount == 0 {
Text("connecting")
Text("Connecting . .")
.font(.title2)
.foregroundColor(.orange)
} else {

View file

@ -17,7 +17,7 @@ struct InvalidVersion: View {
VStack {
Text("update.firmware")
Text("Update Your Firmware")
.font(.largeTitle)
.foregroundColor(.orange)

View file

@ -194,7 +194,7 @@ struct UserList: View {
}
}
.listStyle(.plain)
.navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count)))
.navigationTitle(String.localizedStringWithFormat("Contacts (%@)".localized, String(users.count == 0 ? 0 : users.count)))
.sheet(isPresented: $editingFilters) {
NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isIgnored: $isIgnored, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles)
}

View file

@ -209,7 +209,7 @@ struct DeviceMetricsLog: View {
isPresented: $isPresentingClearLogConfirm,
titleVisibility: .visible
) {
Button("device.metrics.delete", role: .destructive) {
Button("Delete all device metrics?", role: .destructive) {
if clearTelemetry(destNum: node.num, metricsType: 0, context: context) {
Logger.data.notice("Cleared Device Metrics for \(node.num, privacy: .public)")
} else {

View file

@ -75,7 +75,7 @@ struct NodeListItem: View {
if connected {
IconAndText(systemName: "antenna.radiowaves.left.and.right.circle.fill",
imageColor: .green,
text: "connected".localized)
text: "Connected".localized)
}
if node.lastHeard?.timeIntervalSince1970 ?? 0 > 0 && node.lastHeard! < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
IconAndText(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill",

View file

@ -44,7 +44,7 @@ struct PaxCounterLog: View {
y: .value("y", (point.wifi + point.ble))
)
}
.accessibilityLabel("paxcounter.total")
.accessibilityLabel("Total PAX")
.accessibilityValue("X: \(point.time!), Y: \(point.wifi + point.ble)")
.foregroundStyle(paxChartColor)
.interpolationMethod(.cardinal)
@ -55,7 +55,7 @@ struct PaxCounterLog: View {
y: .value("y", point.wifi)
)
}
.accessibilityLabel("paxcounter.wifi")
.accessibilityLabel("WiFi")
.accessibilityValue("X: \(point.time!), Y: \(point.wifi)")
.foregroundStyle(wifiChartColor)
@ -65,7 +65,7 @@ struct PaxCounterLog: View {
y: .value("y", point.ble)
)
}
.accessibilityLabel("paxcounter.ble")
.accessibilityLabel("BLE")
.accessibilityValue("X: \(point.time!), Y: \(point.ble)")
.foregroundStyle(bleChartColor)
}
@ -76,9 +76,9 @@ struct PaxCounterLog: View {
.chartXAxis(.automatic)
.chartYScale(domain: 0...maxValue)
.chartForegroundStyleScale([
"paxcounter.ble".localized: .blue,
"paxcounter.wifi".localized: .orange,
"paxcounter.total".localized: .green
"BLE".localized: .blue,
"WiFi".localized: .orange,
"Total PAX".localized: .green
])
.chartLegend(position: .automatic, alignment: .bottom)
}
@ -89,13 +89,13 @@ struct PaxCounterLog: View {
if UIScreen.main.bounds.size.width > 768 && (UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac) {
// Add a table for mac and ipad
Table(pax) {
TableColumn("paxcounter.ble") { pc in
TableColumn("BLE") { pc in
Text("\(pc.ble)")
}
TableColumn("paxcounter.wifi") { pc in
TableColumn("WiFi") { pc in
Text("\(pc.wifi)")
}
TableColumn("paxcounter.total") { pc in
TableColumn("Total PAX") { pc in
Text("\(pc.wifi + pc.ble)")
}
TableColumn("Uptime") { pc in
@ -120,10 +120,10 @@ struct PaxCounterLog: View {
]
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
GridRow {
Text("paxcounter.ble")
Text("BLE")
.font(.caption)
.fontWeight(.bold)
Text("paxcounter.wifi")
Text("WiFi")
.font(.caption)
.fontWeight(.bold)
Text("Total")

View file

@ -379,6 +379,6 @@ enum PositionPrecision: Int, CaseIterable, Identifiable {
var description: String {
let distanceFormatter = MKDistanceFormatter()
return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))
return String.localizedStringWithFormat("Within %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))
}
}

View file

@ -310,6 +310,9 @@ struct DeviceConfig: View {
}
}
func setDeviceValues() {
if node?.deviceConfig?.role ?? 0 == 3 {
node?.deviceConfig?.role = 1
}
self.deviceRole = Int(node?.deviceConfig?.role ?? 0)
self.buttonGPIO = Int(node?.deviceConfig?.buttonGpio ?? 0)
self.buzzerGPIO = Int(node?.deviceConfig?.buzzerGpio ?? 0)

View file

@ -304,6 +304,9 @@ struct LoRaConfig: View {
}
}
func setLoRaValues() {
if node?.loRaConfig?.modemPreset ?? 0 == 2 {
node?.loRaConfig?.modemPreset = 0
}
self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 3)
self.region = Int(node?.loRaConfig?.regionCode ?? 0)
self.usePreset = node?.loRaConfig?.usePreset ?? true

View file

@ -130,7 +130,7 @@ struct DetectionSensorConfig: View {
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Section(header: Text("update.interval")) {
Section(header: Text("Update Interval")) {
Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) {
ForEach(UpdateIntervals.allCases) { ui in
Text(ui.description).tag(ui.rawValue)

View file

@ -32,7 +32,7 @@ struct TelemetryConfig: View {
Form {
ConfigHeader(title: "Telemetry", config: \.telemetryConfig, node: node, onAppear: setTelemetryValues)
Section(header: Text("update.interval")) {
Section(header: Text("Update Interval")) {
Picker("Device Metrics", selection: $deviceUpdateInterval ) {
ForEach(UpdateIntervals.allCases) { ui in
if ui.rawValue >= 900 {

View file

@ -42,8 +42,8 @@ struct NetworkConfig: View {
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
HStack {
Label("ssid", systemImage: "network")
TextField("ssid", text: $wifiSsid)
Label("SSID", systemImage: "network")
TextField("SSID", text: $wifiSsid)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)

View file

@ -32,8 +32,8 @@ struct PowerConfig: View {
Section {
if (currentDevice?.architecture == .esp32 || currentDevice?.architecture == .esp32S3) || (currentDevice?.architecture == .nrf52840 && (node?.deviceConfig?.role ?? 0 == 5 || node?.deviceConfig?.role ?? 0 == 6)) {
Toggle(isOn: $isPowerSaving) {
Label("config.power.saving", systemImage: "bolt")
Text("config.power.saving.description")
Label("Power Saving", systemImage: "bolt")
Text("Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don't use this setting if you want to use your device with the phone apps or are using a device without a user button.")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
@ -55,15 +55,15 @@ struct PowerConfig: View {
if currentDevice?.architecture == .esp32 || currentDevice?.architecture == .esp32S3 {
Section {
Toggle(isOn: $adcOverride) {
Text("config.power.adc.override")
Text("ADC Override")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if adcOverride {
HStack {
Text("config.power.adc.multiplier")
Text("Multiplier")
Spacer()
FloatField(title: "config.power.adc.multiplier", number: $adcMultiplier) {
FloatField(title: "Multiplier", number: $adcMultiplier) {
(2.0 ... 6.0).contains($0)
}
.focused($isFocused)
@ -71,7 +71,7 @@ struct PowerConfig: View {
}
}
} header: {
Text("config.power.section.battery")
Text("Battery")
}
// Section {
// Picker("config.power.wait.bluetooth.secs", selection: $waitBluetoothSecs) {

View file

@ -30,7 +30,7 @@ struct SaveConfigButton: View {
onConfirmation()
}
} message: {
Text("config.save.confirm")
Text("After config values save the node will reboot.")
}
}
}

View file

@ -204,7 +204,7 @@ struct RouteRecorder: View {
locationsHandler.isRecording = false
locationsHandler.isRecordingPaused = true
} label: {
Label("pause", systemImage: "pause")
Label("Pause", systemImage: "pause")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
@ -216,7 +216,7 @@ struct RouteRecorder: View {
locationsHandler.isRecording = true
locationsHandler.isRecordingPaused = false
} label: {
Label("resume", systemImage: "playpause")
Label("Resume", systemImage: "playpause")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -186,7 +186,7 @@ struct UserConfig: View {
}
}
} message: {
Text("config.save.confirm")
Text("After config values save the node will reboot.")
}
}
Spacer()