From 0892214bcd1fb91538626267cbdddc17ec782bbd Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 12 Dec 2022 20:35:38 -0800 Subject: [PATCH 01/28] Start of localization --- Meshtastic.xcodeproj/project.pbxproj | 20 +++++++++++++++++++ Meshtastic/Views/Bluetooth/Connect.swift | 4 ++-- Meshtastic/Views/Helpers/LastHeardText.swift | 12 ++++++++++- .../Views/Messages/ChannelMessageList.swift | 2 +- Meshtastic/Views/Messages/Contacts.swift | 12 +++++------ .../Views/Messages/UserMessageList.swift | 4 ++-- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 2 +- .../Views/Nodes/EnvironmentMetricsLog.swift | 2 +- Meshtastic/Views/Nodes/PositionLog.swift | 2 +- .../Settings/Config/BluetoothConfig.swift | 2 +- .../Views/Settings/Config/DeviceConfig.swift | 2 +- .../Views/Settings/Config/DisplayConfig.swift | 2 +- .../Views/Settings/Config/LoRaConfig.swift | 2 +- .../Config/Module/CannedMessagesConfig.swift | 2 +- .../Module/ExternalNotificationConfig.swift | 2 +- .../Settings/Config/Module/MQTTConfig.swift | 2 +- .../Config/Module/RangeTestConfig.swift | 4 ++-- .../Settings/Config/Module/SerialConfig.swift | 2 +- .../Config/Module/TelemetryConfig.swift | 2 +- .../Views/Settings/Config/NetworkConfig.swift | 2 +- .../Settings/Config/PositionConfig.swift | 2 +- .../Views/Settings/SaveChannelQRCode.swift | 4 ++-- Meshtastic/Views/Settings/UserConfig.swift | 2 +- de.lproj/Localizable.strings | 13 ++++++++++++ en.lproj/Localizable.strings | 17 ++++++++++++++++ 25 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 de.lproj/Localizable.strings create mode 100644 en.lproj/Localizable.strings diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index ce7109d7..14f1609a 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -98,6 +98,7 @@ DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; }; DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; }; DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; }; + DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DDCDC6CD29481FCC004C1DDA /* Localizable.strings */; }; DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */; }; DDCFF601285453A7005FA625 /* localonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCFF600285453A7005FA625 /* localonly.pb.swift */; }; DDD3BBD5292D763200D609B3 /* MeshtasticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */; }; @@ -222,6 +223,8 @@ DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = ""; }; DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; DDCDC69A29467643004C1DDA /* MeshtasticDataModelV3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV3.xcdatamodel; sourceTree = ""; }; + DDCDC6CC29481FCC004C1DDA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + DDCDC6CE294821AD004C1DDA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = ""; }; DDCFF600285453A7005FA625 /* localonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = localonly.pb.swift; sourceTree = ""; }; DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = ""; }; @@ -401,6 +404,7 @@ DDC2E14B26CE248E0042C5E4 = { isa = PBXGroup; children = ( + DDCDC6CD29481FCC004C1DDA /* Localizable.strings */, DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */, DDC2E15626CE248E0042C5E4 /* Meshtastic */, DDC2E16D26CE248F0042C5E4 /* MeshtasticTests */, @@ -641,6 +645,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + de, Base, ); mainGroup = DDC2E14B26CE248E0042C5E4; @@ -665,6 +670,7 @@ buildActionMask = 2147483647; files = ( DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */, + DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */, DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -835,11 +841,24 @@ }; /* End PBXTargetDependency section */ +/* Begin PBXVariantGroup section */ + DDCDC6CD29481FCC004C1DDA /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + DDCDC6CC29481FCC004C1DDA /* en */, + DDCDC6CE294821AD004C1DDA /* de */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ DDC2E17C26CE248F0042C5E4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -901,6 +920,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index f2875e5e..50757c31 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -28,7 +28,7 @@ struct Connect: View { VStack { List { if bleManager.isSwitchedOn { - Section(header: Text("Connected Radio").font(.title)) { + Section(header: Text("connected.radio").font(.title)) { if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected { HStack { Image(systemName: "antenna.radiowaves.left.and.right") @@ -160,7 +160,7 @@ struct Connect: View { .textCase(nil) if !self.bleManager.isConnected { - Section(header: Text("Available Radios").font(.title)) { + Section(header: Text("available.radios").font(.title)) { ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.name > $1.name })) { peripheral in HStack { Image(systemName: "circle.fill") diff --git a/Meshtastic/Views/Helpers/LastHeardText.swift b/Meshtastic/Views/Helpers/LastHeardText.swift index 36bfaa9d..eb68fd57 100644 --- a/Meshtastic/Views/Helpers/LastHeardText.swift +++ b/Meshtastic/Views/Helpers/LastHeardText.swift @@ -17,7 +17,17 @@ struct LastHeardText: View { } else { - Text("Unknown Age") + Text("unknown.age") } } } +struct LastHeardText_Previews: PreviewProvider { + static var previews: some View { + LastHeardText(lastHeard: Date()) + .previewLayout(.fixed(width: 300, height: 100)) + .environment(\.locale, .init(identifier: "en")) + LastHeardText(lastHeard: Date()) + .previewLayout(.fixed(width: 300, height: 100)) + .environment(\.locale, .init(identifier: "de")) + } +} diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 6b61a9da..4d8094ec 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -144,7 +144,7 @@ struct ChannelMessageList: View { self.deleteMessageId = message.messageId print(deleteMessageId) }) { - Text("Delete") + Text("delete") Image(systemName: "trash") } } diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index 54b00d9e..b6705dc5 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -28,7 +28,7 @@ struct Contacts: View { NavigationSplitView { List { - Section(header: Text("Channels (groups)")) { + Section(header: Text("channels")) { // Display Contacts for the rest of the non admin channels if node != nil && node!.myInfo != nil && node!.myInfo!.channels != nil { ForEach(node!.myInfo!.channels!.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in @@ -128,7 +128,7 @@ struct Contacts: View { deleteChannelMessages(channelIndex: channel.index, context: context) context.refreshAllObjects() } label: { - Text("Delete") + Text("delete") } } } @@ -137,7 +137,7 @@ struct Contacts: View { } } - Section(header: Text("Direct Messages")) { + Section(header: Text("direct.messages")) { ForEach(users) { (user: UserEntity) in if user.num != bleManager.userSettings?.preferredNodeNum ?? 0 { NavigationLink(destination: UserMessageList(user: user)) { @@ -214,7 +214,7 @@ struct Contacts: View { deleteUserMessages(user: user, context: context) } label: { - Text("Delete") + Text("delete") } } } @@ -227,7 +227,7 @@ struct Contacts: View { } } } - .navigationTitle("Contacts") + .navigationTitle("contacts") .navigationBarItems(leading: MeshtasticLogo() ) @@ -260,7 +260,7 @@ struct Contacts: View { UserMessageList(user:user) } else { - Text("Select a Contact") + Text("select.contact") } } } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index f2c4f98e..a8d87bad 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -127,7 +127,7 @@ struct UserMessageList: View { if ackDate >= sixMonthsAgo! { Text((ackDate.formattedDate(format: "h:mm:ss a"))).font(.caption2).foregroundColor(.gray) } else { - Text("Unknown Age").font(.caption2).foregroundColor(.gray) + Text("unknown.age").font(.caption2).foregroundColor(.gray) } } } @@ -145,7 +145,7 @@ struct UserMessageList: View { self.deleteMessageId = message.messageId print(deleteMessageId) }) { - Text("Delete") + Text("delete") Image(systemName: "trash") } } diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index e8e73d5e..70ea6137 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -148,7 +148,7 @@ struct DeviceMetricsLog: View { exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0) isExporting = true } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index a8d2c2b3..d9795950 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -156,7 +156,7 @@ struct EnvironmentMetricsLog: View { } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index b31e9b2a..b4de4ad2 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -137,7 +137,7 @@ struct PositionLog: View { } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 412282aa..ae2e1f93 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -87,7 +87,7 @@ struct BluetoothConfig: View { Button { isPresentingSaveConfirm = true } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges || shortPin) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 6800a117..c72a1b8a 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -143,7 +143,7 @@ struct DeviceConfig: View { } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 318bb111..69d14a44 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -97,7 +97,7 @@ struct DisplayConfig: View { } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index e5584f3d..306cac72 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -63,7 +63,7 @@ struct LoRaConfig: View { Button { isPresentingSaveConfirm = true } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 6d83625a..0ebe4ded 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -212,7 +212,7 @@ struct CannedMessagesConfig: View { isPresentingSaveConfirm = true } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || (!hasChanges && !hasMessagesChanges)) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 5bceaec3..f6d3fdaa 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -123,7 +123,7 @@ struct ExternalNotificationConfig: View { Button { isPresentingSaveConfirm = true } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index a8b135d9..c7ea369f 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -144,7 +144,7 @@ struct MQTTConfig: View { } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 56ea350f..1e5d7f12 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -76,7 +76,7 @@ struct RangeTestConfig: View { Text("This device will send out range test messages on the selected interval.") .font(.caption) Toggle(isOn: $save) { - Label("Save", systemImage: "square.and.arrow.down.fill") + Label("save", systemImage: "square.and.arrow.down.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) @@ -88,7 +88,7 @@ struct RangeTestConfig: View { Button { isPresentingSaveConfirm = true } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node?.myInfo?.hasWifi ?? false)) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 16eea485..f09e8500 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -113,7 +113,7 @@ struct SerialConfig: View { } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index c944d987..621db50c 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -125,7 +125,7 @@ struct TelemetryConfig: View { Button { isPresentingSaveConfirm = true } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges || node!.telemetryConfig == nil) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 3d3bafb9..383aac0a 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -96,7 +96,7 @@ struct NetworkConfig: View { Button { isPresentingSaveConfirm = true } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 361df32d..533cad90 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -193,7 +193,7 @@ struct PositionConfig: View { } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/SaveChannelQRCode.swift b/Meshtastic/Views/Settings/SaveChannelQRCode.swift index 7f375268..c556dcfa 100644 --- a/Meshtastic/Views/Settings/SaveChannelQRCode.swift +++ b/Meshtastic/Views/Settings/SaveChannelQRCode.swift @@ -32,7 +32,7 @@ struct SaveChannelQRCode: View { } } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) @@ -44,7 +44,7 @@ struct SaveChannelQRCode: View { Button { dismiss() } label: { - Label("Cancel", systemImage: "xmark") + Label("cancel", systemImage: "xmark") } .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index c8fe32b4..6380430b 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -72,7 +72,7 @@ struct UserConfig: View { Button { isPresentingSaveConfirm = true } label: { - Label("Save", systemImage: "square.and.arrow.down") + Label("save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) .buttonStyle(.bordered) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings new file mode 100644 index 00000000..58c5366a --- /dev/null +++ b/de.lproj/Localizable.strings @@ -0,0 +1,13 @@ +/* + Localizable.strings + Meshtastic + + Created by Garth Vander Houwen on 12/12/22. + +*/ +//"available.radios"="Verfügbare Funkgeräte"; +//"cancel"="Absagen"; +//"connected.radio"="Verbundenes Radio"; +//"delete"="Löschen"; +//"save"="Speichern"; +//"unknown.age"="Unbekanntes Alter"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings new file mode 100644 index 00000000..b12c646f --- /dev/null +++ b/en.lproj/Localizable.strings @@ -0,0 +1,17 @@ +/* + Localizable.strings + Meshtastic + + Copyright(c) Garth Vander Houwen on 12/12/22. + +*/ +"available.radios"="Available Radios"; +"cancel"="Cancel"; +"channels"="Channels (groups)"; +"connected.radio"="Connected Radio"; +"contacts"="Contacts"; +"delete"="Delete"; +"direct.messages"="Direct Messages"; +"save"="Save"; +"select.contact"="Select a Contact"; +"unknown.age"="Unknown Age"; From 770a3fbdb307de49532ecb1bbf8b0a581747d8fa Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 12 Dec 2022 21:19:22 -0800 Subject: [PATCH 02/28] Additional localized strings --- Meshtastic/Helpers/BLEManager.swift | 2 +- Meshtastic/Helpers/MeshPackets.swift | 10 +---- Meshtastic/Views/Bluetooth/Connect.swift | 25 ++++++------ Meshtastic/Views/Messages/Contacts.swift | 38 +++++++++---------- .../Settings/Config/PositionConfig.swift | 2 +- en.lproj/Localizable.strings | 9 +++++ 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 3057e4d7..d021e627 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -458,7 +458,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) { if decodedInfo.moduleConfig.cannedMessage.enabled { - self.getCannedMessageModuleMessages(destNum: self.connectedPeripheral.num, wantResponse: true) + _ = self.getCannedMessageModuleMessages(destNum: self.connectedPeripheral.num, wantResponse: true) } } } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 67eb547f..5eeea6af 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1038,16 +1038,8 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { - //print(packet.payloadVariant.debugDescription) - if let messages = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { - //let adminMessageId = bleManager.saveCannedMessageModuleMessages(messages: messages, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) - //if adminMessageId > 0 { - - //} - //print(messages) - } else { - //print(try! packet.decoded.jsonString()) + print(messages) } } diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 50757c31..9e409840 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -39,25 +39,26 @@ struct Connect: View { if node != nil { Text(bleManager.connectedPeripheral.longName).font(.title2) } - Text("BLE Name: ").font(.caption)+Text(bleManager.connectedPeripheral.peripheral.name ?? "Unknown") + Text("ble.name").font(.caption)+Text(": \(bleManager.connectedPeripheral.peripheral.name ?? "Unknown")") .font(.caption).foregroundColor(Color.gray) if node != nil { - Text("FW Version: ").font(.caption)+Text(node?.myInfo?.firmwareVersion ?? "Unknown") + Text("firmware.version").font(.caption)+Text(": \(node?.myInfo?.firmwareVersion ?? "Unknown")") .font(.caption).foregroundColor(Color.gray) } if bleManager.isSubscribed { - Text("Subscribed to mesh").font(.caption) + Text("subscribed").font(.caption) .foregroundColor(.green) } else { - Text("Communicating with device. . . ").font(.caption) + Text("communicating").font(.caption) .foregroundColor(.orange) } } Spacer() VStack(alignment: .center) { - Text("Preferred").font(.caption2) - Text("Radio").font(.caption2) - Toggle("Preferred Radio", isOn: $bleManager.preferredPeripheral) + Text("preferred.radio").font(.caption2) + .multilineTextAlignment(.center) + .frame(width: 75) + Toggle("preferred.radio", isOn: $bleManager.preferredPeripheral) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .labelsHidden() .onChange(of: bleManager.preferredPeripheral) { value in @@ -91,7 +92,7 @@ struct Connect: View { isPreferredRadio = false } } label: { - Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") + Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") } } .contextMenu{ @@ -112,7 +113,7 @@ struct Connect: View { NavigationLink { LoRaConfig(node: node) } label: { - Label("Set LoRa Region", systemImage: "globe.americas.fill") + Label("set.region", systemImage: "globe.americas.fill") .foregroundColor(.red) .font(.title) } @@ -127,7 +128,7 @@ struct Connect: View { .imageScale(.large).foregroundColor(.orange) .padding(.trailing) if bleManager.timeoutTimerCount == 0 { - Text("Connecting . . .") + Text("connecting") .font(.title3) .foregroundColor(.orange) } else { @@ -151,7 +152,7 @@ struct Connect: View { .symbolRenderingMode(.hierarchical) .imageScale(.large).foregroundColor(.red) .padding(.trailing) - Text("No device connected").font(.title3) + Text("not.connected").font(.title3) } .padding() } @@ -215,7 +216,7 @@ struct Connect: View { }) { - Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") + Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") } .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index b6705dc5..4e07be06 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -28,7 +28,7 @@ struct Contacts: View { NavigationSplitView { List { - Section(header: Text("channels")) { + Section(header: Text("Channels (groups)")) { // Display Contacts for the rest of the non admin channels if node != nil && node!.myInfo != nil && node!.myInfo!.channels != nil { ForEach(node!.myInfo!.channels!.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in @@ -92,24 +92,24 @@ struct Contacts: View { } .frame(maxWidth: .infinity, maxHeight: 80, alignment: .leading) .contextMenu { - if false { // Hide mute channel menu item until I can check it in notifications - Button { - channel.mute = !channel.mute - - do { - try context.save() - // Would rather not do this but the merge changes on - // A single object is only working on mac GVH - context.refreshAllObjects() - //context.refresh(channel, mergeChanges: true) - } catch { - context.rollback() - print("💥 Save Channel Mute Error") - } - } label: { - Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash") - } - } +// Hide mute channel menu item until I can check it in notifications +// Button { +// channel.mute = !channel.mute +// +// do { +// try context.save() +// // Would rather not do this but the merge changes on +// // A single object is only working on mac GVH +// context.refreshAllObjects() +// //context.refresh(channel, mergeChanges: true) +// } catch { +// context.rollback() +// print("💥 Save Channel Mute Error") +// } +// } label: { +// Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash") +// } + if channel.allPrivateMessages.count > 0 { Button(role: .destructive) { isPresentingDeleteChannelMessagesConfirm = true diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 533cad90..23779e06 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -208,7 +208,7 @@ struct PositionConfig: View { Button("Save Position Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { if fixedPosition { - let sendPosition = bleManager.sendPosition(destNum: bleManager.connectedPeripheral.num, wantAck: true) + _ = bleManager.sendPosition(destNum: bleManager.connectedPeripheral.num, wantAck: true) } var pc = Config.PositionConfig() diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index b12c646f..228a93e7 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -6,12 +6,21 @@ */ "available.radios"="Available Radios"; +"ble.name"="BLE Name"; "cancel"="Cancel"; "channels"="Channels (groups)"; "connected.radio"="Connected Radio"; +"communicating"="Communicating with device. ."; +"connecting"="Connecting . ."; "contacts"="Contacts"; "delete"="Delete"; "direct.messages"="Direct Messages"; +"disconnect"="Disconnect"; +"firmware.version"="Firmware Version"; +"not.connected"="No device connected"; +"preferred.radio"="Preferred Radio"; "save"="Save"; +"subscribed"="Subscribed to mesh"; "select.contact"="Select a Contact"; +"set.region"="Set LoRa Region"; "unknown.age"="Unknown Age"; From 9437fc7ecee4a617d5062868be4ca77b877a6764 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 12 Dec 2022 21:36:27 -0800 Subject: [PATCH 03/28] Add some german strings for testing --- de.lproj/Localizable.strings | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 58c5366a..c2bbe109 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -5,9 +5,9 @@ Created by Garth Vander Houwen on 12/12/22. */ -//"available.radios"="Verfügbare Funkgeräte"; -//"cancel"="Absagen"; -//"connected.radio"="Verbundenes Radio"; -//"delete"="Löschen"; -//"save"="Speichern"; -//"unknown.age"="Unbekanntes Alter"; +"available.radios"="Verfügbare Funkgeräte"; +"cancel"="Absagen"; +"connected.radio"="Verbundenes Radio"; +"delete"="Löschen"; +"save"="Speichern"; +"unknown.age"="Unbekanntes Alter"; From 75a84a352906778ee0ef1a8ddbabbe054157fcbf Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 12 Dec 2022 21:51:43 -0800 Subject: [PATCH 04/28] Add menu item strings --- Meshtastic/Views/Bluetooth/Connect.swift | 2 +- Meshtastic/Views/ContentView.swift | 10 +++++----- Meshtastic/Views/Helpers/DistanceText.swift | 2 +- en.lproj/Localizable.strings | 6 ++++++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 9e409840..cfa38d5e 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -229,7 +229,7 @@ struct Connect: View { } .padding(.bottom, 10) } - .navigationTitle("Bluetooth") + .navigationTitle("bluetooth") .navigationBarItems(leading: MeshtasticLogo(), trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index ba37fb99..a3f99164 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -29,28 +29,28 @@ struct ContentView: View { Contacts() .tabItem { - Label("Messages", systemImage: "message") + Label("messages", systemImage: "message") } .tag(Tab.contacts) } Connect() .tabItem { - Label("Bluetooth", systemImage: "antenna.radiowaves.left.and.right") + Label("bluetooth", systemImage: "antenna.radiowaves.left.and.right") } .tag(Tab.ble) NodeList() .tabItem { - Label("Nodes", systemImage: "flipphone") + Label("nodes", systemImage: "flipphone") } .tag(Tab.nodes) NodeMap() .tabItem { - Label("Mesh Map", systemImage: "map") + Label("map", systemImage: "map") } .tag(Tab.map) Settings() .tabItem { - Label("Settings", systemImage: "gear") + Label("settings", systemImage: "gear") } .tag(Tab.settings) } diff --git a/Meshtastic/Views/Helpers/DistanceText.swift b/Meshtastic/Views/Helpers/DistanceText.swift index 59d58bb8..6028c1e5 100644 --- a/Meshtastic/Views/Helpers/DistanceText.swift +++ b/Meshtastic/Views/Helpers/DistanceText.swift @@ -16,7 +16,7 @@ struct DistanceText: View { var body: some View { let distanceFormatter = MKDistanceFormatter() - Text("Distance: \(distanceFormatter.string(fromDistance: Double(meters)))") + Text("distance")+Text(": \(distanceFormatter.string(fromDistance: Double(meters)))") } } struct DistanceText_Previews: PreviewProvider { diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 228a93e7..6fded5ec 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -7,6 +7,7 @@ */ "available.radios"="Available Radios"; "ble.name"="BLE Name"; +"bluetooth"="Bluetooth"; "cancel"="Cancel"; "channels"="Channels (groups)"; "connected.radio"="Connected Radio"; @@ -15,11 +16,16 @@ "contacts"="Contacts"; "delete"="Delete"; "direct.messages"="Direct Messages"; +"distance"="Distance"; "disconnect"="Disconnect"; "firmware.version"="Firmware Version"; +"map"="Mesh Map"; +"messages"="Messages"; +"nodes"="Nodes"; "not.connected"="No device connected"; "preferred.radio"="Preferred Radio"; "save"="Save"; +"settings"="Settings"; "subscribed"="Subscribed to mesh"; "select.contact"="Select a Contact"; "set.region"="Set LoRa Region"; From fc0e1daa7d342d452cbb11ec8c302af80a2b2f2e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 12 Dec 2022 22:33:06 -0800 Subject: [PATCH 05/28] More strings --- Meshtastic/Views/Helpers/LastHeardText.swift | 7 +------ Meshtastic/Views/Nodes/NodeList.swift | 14 ++++---------- .../Config/Module/RangeTestConfig.swift | 2 +- .../Settings/Config/Module/SerialConfig.swift | 2 +- .../Config/Module/TelemetryConfig.swift | 2 +- Meshtastic/Views/Settings/Settings.swift | 19 +++++++++---------- de.lproj/Localizable.strings | 2 ++ en.lproj/Localizable.strings | 19 ++++++++++++++++++- 8 files changed, 37 insertions(+), 30 deletions(-) diff --git a/Meshtastic/Views/Helpers/LastHeardText.swift b/Meshtastic/Views/Helpers/LastHeardText.swift index eb68fd57..d4a877e9 100644 --- a/Meshtastic/Views/Helpers/LastHeardText.swift +++ b/Meshtastic/Views/Helpers/LastHeardText.swift @@ -7,16 +7,11 @@ import SwiftUI // struct LastHeardText: View { var lastHeard: Date? - let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date()) - var body: some View { if (lastHeard != nil && lastHeard! >= sixMonthsAgo!){ - - Text("Heard: \(lastHeard!, style: .relative) ago") - + Text("heard")+Text(": \(lastHeard!, style: .relative) ")+Text("ago") } else { - Text("unknown.age") } } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 2845bafe..aaefe178 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -30,13 +30,7 @@ struct NodeList: View { NavigationSplitView { List (nodes, id: \.self, selection: $selection) { node in if nodes.count == 0 { - Text("Scan for Radios").font(.largeTitle) - Text("No Meshtastic Nodes Found").font(.title2) - Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your iPhone, iPad or Mac.") - .font(.body) - Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.") - Text("Views with bluetooth functionality will show an indicator in the upper right hand corner showing if bluetooth is on, and if a device is connected.") - .listRowSeparator(.visible) + Text("no.nodes").font(.title) } else { NavigationLink(value: node) { let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == node.num) @@ -51,7 +45,7 @@ struct NodeList: View { Image(systemName: "repeat.circle.fill") .font(.title3) .symbolRenderingMode(.hierarchical) - Text("Currently Connected").font(.subheadline) + Text("connected").font(.subheadline) .foregroundColor(.green) } } @@ -85,7 +79,7 @@ struct NodeList: View { .padding([.top, .bottom]) } } - .navigationTitle("All Nodes") + .navigationTitle("nodes") .navigationBarItems(leading: MeshtasticLogo() ) @@ -97,7 +91,7 @@ struct NodeList: View { if let node = selection { NodeDetail(node:node) } else { - Text("Select a node") + Text("select.node") } } } diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 1e5d7f12..75993452 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -114,7 +114,7 @@ struct RangeTestConfig: View { } } } - .navigationTitle("Range Test Config") + .navigationTitle("range.test.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index f09e8500..77399586 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -148,7 +148,7 @@ struct SerialConfig: View { } } - .navigationTitle("Serial Config") + .navigationTitle("serial.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 621db50c..8a6726d6 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -154,7 +154,7 @@ struct TelemetryConfig: View { } } - .navigationTitle("Telemetry Config") + .navigationTitle("telemetry.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index da96b83d..c2590955 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -134,30 +134,30 @@ struct Settings: View { } label: { Image(systemName: "point.3.connected.trianglepath.dotted") .symbolRenderingMode(.hierarchical) - Text("Range Test (ESP32 Only)") + Text("range.test") } NavigationLink { SerialConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "terminal") .symbolRenderingMode(.hierarchical) - Text("Serial") + Text("serial") } NavigationLink { TelemetryConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "chart.xyaxis.line") .symbolRenderingMode(.hierarchical) - Text("Telemetry (Sensors)") + Text("telemetry") } } - Section(header: Text("Logging")) { + Section(header: Text("logging")) { NavigationLink { MeshLog() } label: { Image(systemName: "list.bullet.rectangle") .symbolRenderingMode(.hierarchical) - Text("Mesh Log") + Text("mesh.log") } NavigationLink { let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) @@ -165,11 +165,11 @@ struct Settings: View { } label: { Image(systemName: "building.columns") .symbolRenderingMode(.hierarchical) - Text("Admin Message Log") + Text("admin.log") } } - Section(header: Text("About")) { + Section(header: Text("about")) { NavigationLink { @@ -180,10 +180,9 @@ struct Settings: View { Image(systemName: "questionmark.app") .symbolRenderingMode(.hierarchical) - Text("About Meshtastic") + Text("about.meshtastic") } } - } .onAppear { @@ -192,7 +191,7 @@ struct Settings: View { } .listStyle(GroupedListStyle()) - .navigationTitle("Settings") + .navigationTitle("settings") .navigationBarItems(leading: MeshtasticLogo() ) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index c2bbe109..ab527870 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -5,9 +5,11 @@ Created by Garth Vander Houwen on 12/12/22. */ +"ago"="prije"; "available.radios"="Verfügbare Funkgeräte"; "cancel"="Absagen"; "connected.radio"="Verbundenes Radio"; "delete"="Löschen"; +"heard"="Čuo"; "save"="Speichern"; "unknown.age"="Unbekanntes Alter"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 6fded5ec..fd15778d 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -3,8 +3,13 @@ Meshtastic Copyright(c) Garth Vander Houwen on 12/12/22. - + */ +"about"="About"; +"about.meshtastic"="About Meshtastic"; +"admin"="Admin"; +"admin.log"="Admin Message Log"; +"ago"="ago"; "available.radios"="Available Radios"; "ble.name"="BLE Name"; "bluetooth"="Bluetooth"; @@ -12,6 +17,7 @@ "channels"="Channels (groups)"; "connected.radio"="Connected Radio"; "communicating"="Communicating with device. ."; +"connected"="Currently Connected"; "connecting"="Connecting . ."; "contacts"="Contacts"; "delete"="Delete"; @@ -19,14 +25,25 @@ "distance"="Distance"; "disconnect"="Disconnect"; "firmware.version"="Firmware Version"; +"heard"="Heard"; +"logging"="Logging"; "map"="Mesh Map"; +"mesh.log"="Mesh Log"; "messages"="Messages"; "nodes"="Nodes"; +"no.nodes"="No Meshtastic Nodes Found"; "not.connected"="No device connected"; "preferred.radio"="Preferred Radio"; +"range.test"="Range Test"; +"range.test.config"="Range Test Config"; "save"="Save"; +"serial"="Serial"; +"serial.config"="Serial Config"; "settings"="Settings"; "subscribed"="Subscribed to mesh"; "select.contact"="Select a Contact"; +"select.node"="Select a Node"; "set.region"="Set LoRa Region"; +"telemetry"="Telemetry (Sensors)"; +"telemetry.config"="Telemetry Config"; "unknown.age"="Unknown Age"; From 30a2898776b263515910852f1c51dbbd32448262 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 13 Dec 2022 07:49:46 -0800 Subject: [PATCH 06/28] More strings --- .../Views/Messages/ChannelMessageList.swift | 4 +-- .../Views/Messages/UserMessageList.swift | 4 +-- Meshtastic/Views/Settings/AppSettings.swift | 12 +++---- .../Settings/Config/BluetoothConfig.swift | 2 +- .../Views/Settings/Config/DeviceConfig.swift | 2 +- .../Views/Settings/Config/DisplayConfig.swift | 2 +- .../Views/Settings/Config/LoRaConfig.swift | 2 +- .../Config/Module/CannedMessagesConfig.swift | 2 +- .../Module/ExternalNotificationConfig.swift | 2 +- .../Settings/Config/Module/MQTTConfig.swift | 2 +- .../Views/Settings/Config/NetworkConfig.swift | 2 +- .../Settings/Config/PositionConfig.swift | 2 +- Meshtastic/Views/Settings/Settings.swift | 30 ++++++++--------- en.lproj/Localizable.strings | 32 ++++++++++++++++++- 14 files changed, 65 insertions(+), 35 deletions(-) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 4d8094ec..9d3f4600 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -243,7 +243,7 @@ struct ChannelMessageList: View { } } label: { - Text("Share Position") + Text("share.positon") Image(systemName: "mappin.and.ellipse") .symbolRenderingMode(.hierarchical) .imageScale(.large).foregroundColor(.accentColor) @@ -277,7 +277,7 @@ struct ChannelMessageList: View { .keyboardType(kbType!) .toolbar { ToolbarItemGroup(placement: .keyboard) { - Button("Dismiss Keyboard") { + Button("dismiss.keyboard") { focusedField = nil } .font(.subheadline) diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index a8d87bad..3e16389b 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -243,7 +243,7 @@ struct UserMessageList: View { } } label: { - Text("Share Position") + Text("share.postion") Image(systemName: "mappin.and.ellipse") .symbolRenderingMode(.hierarchical) .imageScale(.large).foregroundColor(.accentColor) @@ -277,7 +277,7 @@ struct UserMessageList: View { .keyboardType(kbType!) .toolbar { ToolbarItemGroup(placement: .keyboard) { - Button("Dismiss Keyboard") { + Button("dismiss.keyboard") { focusedField = nil } .font(.subheadline) diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 13a55da4..9fdd60ad 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -17,15 +17,15 @@ enum KeyboardType: Int, CaseIterable, Identifiable { get { switch self { case .defaultKeyboard: - return "Default" + return String(format: NSLocalizedString("default", comment: "Default Keyboard")) case .asciiCapable: - return "ASCII Capable" + return String(format: NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard")) case .twitter: - return "Twitter" + return String(format: NSLocalizedString("twitter", comment: "Twitter Keyboard")) case .emailAddress: - return "Email Address" + return String(format: NSLocalizedString("email.address", comment: "Email Address Keyboard")) case .numbersAndPunctuation: - return "Numbers and Punctuation" + return String(format: NSLocalizedString("numbers.punctuation", comment: "Numbers and Punctuation Keyboard")) } } } @@ -179,7 +179,7 @@ struct AppSettings: View { } } } - .navigationTitle("App Settings") + .navigationTitle("app.settings") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index ae2e1f93..7173a0f2 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -116,7 +116,7 @@ struct BluetoothConfig: View { Text("After bluetooth config saves the node will reboot.") } } - .navigationTitle("Bluetooth (BLE) Config") + .navigationTitle("bluetooth.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index c72a1b8a..31d02c42 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -182,7 +182,7 @@ struct DeviceConfig: View { Spacer() } - .navigationTitle("Device Config") + .navigationTitle("device.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 69d14a44..696c978e 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -131,7 +131,7 @@ struct DisplayConfig: View { } } } - .navigationTitle("Display Config") + .navigationTitle("display.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 306cac72..6f5164a3 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -94,7 +94,7 @@ struct LoRaConfig: View { Text("After LoRa config saves the node will reboot.") } } - .navigationTitle("LoRa Config") + .navigationTitle("lora.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 0ebe4ded..d36ee83e 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -265,7 +265,7 @@ struct CannedMessagesConfig: View { } } } - .navigationTitle("Canned Messages Config") + .navigationTitle("canned.messages.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index f6d3fdaa..19616a20 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -153,7 +153,7 @@ struct ExternalNotificationConfig: View { } } } - .navigationTitle("External Notification Config") + .navigationTitle("external.notification.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index c7ea369f..796741b0 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -174,7 +174,7 @@ struct MQTTConfig: View { } } } - .navigationTitle("MQTT Config") + .navigationTitle("mqtt.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 383aac0a..b1d31ea0 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -128,7 +128,7 @@ struct NetworkConfig: View { Text("After network config saves the node will reboot.") } } - .navigationTitle("Network Config") + .navigationTitle("network.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 23779e06..de98fe9b 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -240,7 +240,7 @@ struct PositionConfig: View { } } } - .navigationTitle("Position Config") + .navigationTitle("position.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index c2590955..7b6ad0d6 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -26,16 +26,16 @@ struct Settings: View { Image(systemName: "gearshape") .symbolRenderingMode(.hierarchical) - Text("App Settings") + Text("app.settings") } - Section("Radio Configuration") { + Section("radio.configuration") { NavigationLink { ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "qrcode") .symbolRenderingMode(.hierarchical) - Text("Share Channels QR Code") + Text("share.channels") } NavigationLink { @@ -45,7 +45,7 @@ struct Settings: View { Image(systemName: "person.crop.rectangle.fill") .symbolRenderingMode(.hierarchical) - Text("User") + Text("user") } NavigationLink() { @@ -56,7 +56,7 @@ struct Settings: View { Image(systemName: "dot.radiowaves.left.and.right") .symbolRenderingMode(.hierarchical) - Text("LoRa") + Text("lora") } NavigationLink() { @@ -65,7 +65,7 @@ struct Settings: View { } label: { Image(systemName: "antenna.radiowaves.left.and.right") .symbolRenderingMode(.hierarchical) - Text("Bluetooth (BLE)") + Text("bluetooth") } NavigationLink { @@ -73,7 +73,7 @@ struct Settings: View { } label: { Image(systemName: "flipphone") .symbolRenderingMode(.hierarchical) - Text("Device") + Text("device") } NavigationLink { @@ -81,7 +81,7 @@ struct Settings: View { } label: { Image(systemName: "display") .symbolRenderingMode(.hierarchical) - Text("Display (Device Screen)") + Text("display") } NavigationLink { @@ -90,7 +90,7 @@ struct Settings: View { Image(systemName: "network") .symbolRenderingMode(.hierarchical) - Text("Network") + Text("network") } NavigationLink { @@ -99,11 +99,11 @@ struct Settings: View { Image(systemName: "location") .symbolRenderingMode(.hierarchical) - Text("Position") + Text("position") } } - Section("Module Configuration") { + Section("module.configuration") { NavigationLink { CannedMessagesConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) @@ -112,7 +112,7 @@ struct Settings: View { Image(systemName: "list.bullet.rectangle.fill") .symbolRenderingMode(.hierarchical) - Text("Canned Messages") + Text("canned.messages") } NavigationLink { @@ -120,14 +120,14 @@ struct Settings: View { } label: { Image(systemName: "megaphone") .symbolRenderingMode(.hierarchical) - Text("External Notification") + Text("external.notification") } NavigationLink { MQTTConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "dot.radiowaves.right") .symbolRenderingMode(.hierarchical) - Text("MQTT") + Text("mqtt") } NavigationLink { RangeTestConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) @@ -197,7 +197,7 @@ struct Settings: View { ) } detail: { - Text("Select an item from the menu") + Text("select.menu.item") } } } diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index fd15778d..bb2a07ce 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -10,40 +10,70 @@ "admin"="Admin"; "admin.log"="Admin Message Log"; "ago"="ago"; +"app.settings"="App Settings"; +"ascii.capable"="ASCII Capable"; "available.radios"="Available Radios"; "ble.name"="BLE Name"; "bluetooth"="Bluetooth"; +"bluetooth.config"="Bluetooth Config"; "cancel"="Cancel"; +"canned.messages"="Canned Messages"; +"canned.messages.config"="Canned Messages Config"; "channels"="Channels (groups)"; "connected.radio"="Connected Radio"; "communicating"="Communicating with device. ."; "connected"="Currently Connected"; "connecting"="Connecting . ."; "contacts"="Contacts"; +"default"="Default"; "delete"="Delete"; +"device"="Device"; +"device.config"="Device Config"; "direct.messages"="Direct Messages"; +"dismiss.keyboard"="Dismiss Keyboard"; +"display"="Display (Device Screen)"; +"display.config"="Display Config"; "distance"="Distance"; "disconnect"="Disconnect"; +"email.address"="Email Address"; +"external.notification"="External Notification"; +"external.notification.config"="External Notification Config"; "firmware.version"="Firmware Version"; -"heard"="Heard"; +"heard"="Heard"; "logging"="Logging"; +"lora"="LoRa"; +"lora.config"="LoRa Config"; "map"="Mesh Map"; "mesh.log"="Mesh Log"; "messages"="Messages"; +"module.configuration"="Module Configuration"; +"mqtt"="MQTT"; +"mqtt.config"="MQTT Config"; +"network"="Network"; +"network.config"="Network Config"; "nodes"="Nodes"; "no.nodes"="No Meshtastic Nodes Found"; "not.connected"="No device connected"; +"numbers.punctuation"="Numbers and Punctuation"; +"position"="Position"; +"position.config"="Position Config"; "preferred.radio"="Preferred Radio"; +"radio.configuration"="Radio Configuration"; "range.test"="Range Test"; "range.test.config"="Range Test Config"; "save"="Save"; "serial"="Serial"; "serial.config"="Serial Config"; "settings"="Settings"; +"share.channels"="Share Channels QR Code"; +"share.position"="Share Position"; "subscribed"="Subscribed to mesh"; "select.contact"="Select a Contact"; "select.node"="Select a Node"; +"select.menu.item"="Select an item from the menu"; "set.region"="Set LoRa Region"; "telemetry"="Telemetry (Sensors)"; "telemetry.config"="Telemetry Config"; +"twitter"="Twitter"; "unknown.age"="Unknown Age"; +"user"="User"; From 5da522b9119c9b15c200cf954c8a2705a4396f99 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 13 Dec 2022 08:47:14 -0800 Subject: [PATCH 07/28] More strings --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 2 +- .../Views/Nodes/EnvironmentMetricsLog.swift | 2 +- Meshtastic/Views/Nodes/NodeDetail.swift | 4 +- Meshtastic/Views/Nodes/PositionLog.swift | 2 +- Meshtastic/Views/Settings/About.swift | 2 +- Meshtastic/Views/Settings/AppSettings.swift | 52 +++++++++---------- .../Settings/Config/BluetoothConfig.swift | 4 +- .../Views/Settings/Config/DeviceConfig.swift | 4 +- .../Views/Settings/Config/DisplayConfig.swift | 2 +- .../Config/Module/CannedMessagesConfig.swift | 6 +-- .../Module/ExternalNotificationConfig.swift | 6 +-- .../Settings/Config/Module/MQTTConfig.swift | 6 +-- .../Config/Module/RangeTestConfig.swift | 6 +-- .../Settings/Config/Module/SerialConfig.swift | 6 +-- .../Config/Module/TelemetryConfig.swift | 4 +- .../Views/Settings/Config/NetworkConfig.swift | 4 +- .../Settings/Config/PositionConfig.swift | 2 +- en.lproj/Localizable.strings | 24 ++++++++- 18 files changed, 80 insertions(+), 58 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 70ea6137..198f1f72 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -132,7 +132,7 @@ struct DeviceMetricsLog: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingClearLogConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index d9795950..9b8c803a 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -135,7 +135,7 @@ struct EnvironmentMetricsLog: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingClearLogConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 6ab47349..b5efc6df 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -358,7 +358,7 @@ struct NodeDetail: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $showingShutdownConfirm ) { Button("Shutdown Node?", role: .destructive) { @@ -385,7 +385,7 @@ struct NodeDetail: View { .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $showingRebootConfirm ) { diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index b4de4ad2..46333fe1 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -114,7 +114,7 @@ struct PositionLog: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingClearLogConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Settings/About.swift b/Meshtastic/Views/Settings/About.swift index 8cc59d3c..32745f11 100644 --- a/Meshtastic/Views/Settings/About.swift +++ b/Meshtastic/Views/Settings/About.swift @@ -50,7 +50,7 @@ struct AboutMeshtastic: View { .font(.caption) } } - .navigationTitle("About") + .navigationTitle("about") .navigationBarTitleDisplayMode(.inline) } } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 9fdd60ad..7a1cd178 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -17,15 +17,15 @@ enum KeyboardType: Int, CaseIterable, Identifiable { get { switch self { case .defaultKeyboard: - return String(format: NSLocalizedString("default", comment: "Default Keyboard")) + return NSLocalizedString("default", comment: "Default Keyboard") case .asciiCapable: - return String(format: NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard")) + return NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard") case .twitter: - return String(format: NSLocalizedString("twitter", comment: "Twitter Keyboard")) + return NSLocalizedString("twitter", comment: "Twitter Keyboard") case .emailAddress: - return String(format: NSLocalizedString("email.address", comment: "Email Address Keyboard")) + return NSLocalizedString("email.address", comment: "Email Address Keyboard") case .numbersAndPunctuation: - return String(format: NSLocalizedString("numbers.punctuation", comment: "Numbers and Punctuation Keyboard")) + return NSLocalizedString("numbers.punctuation", comment: "Numbers and Punctuation Keyboard") } } } @@ -43,11 +43,11 @@ enum MeshMapType: String, CaseIterable, Identifiable { get { switch self { case .satellite: - return "Satellite" + return NSLocalizedString("satellite", comment: "Satellite Map Type") case .standard: - return "Standard" + return NSLocalizedString("standard", comment: "Standard Map Type") case .hybrid: - return "Hybrid" + return NSLocalizedString("hybrid", comment: "Hybrid Map Type") } } } @@ -69,21 +69,21 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable { get { switch self { case .fiveSeconds: - return "Five Seconds" + return NSLocalizedString("interval.five.seconds", comment: "Five Seconds") case .tenSeconds: - return "Ten Seconds" + return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds") case .fifteenSeconds: - return "Fifteen Seconds" + return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") case .thirtySeconds: - return "Thirty Seconds" + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") case .oneMinute: - return "One Minute" + return NSLocalizedString("interval.one.minute", comment: "One Minute") case .fiveMinutes: - return "Five Minutes" + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") case .tenMinutes: - return "Ten Minutes" + return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") case .fifteenMinutes: - return "Fifteen Minutes" + return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") } } } @@ -105,7 +105,7 @@ struct AppSettings: View { var body: some View { VStack { Form { - Section(header: Text("USER DETAILS")) { + Section(header: Text("user.details")) { HStack { Label("Name", systemImage: "person.crop.rectangle.fill") @@ -116,16 +116,16 @@ struct AppSettings: View { .disableAutocorrection(true) .listRowSeparator(.visible) } - Section(header: Text("Options")) { + Section(header: Text("options")) { - Picker("Keyboard Type", selection: $userSettings.keyboardType) { + Picker("keyboard.type", selection: $userSettings.keyboardType) { ForEach(KeyboardType.allCases) { kb in Text(kb.description) } } .pickerStyle(DefaultPickerStyle()) - Picker("Map Type", selection: $userSettings.meshMapType) { + Picker("map.type", selection: $userSettings.meshMapType) { ForEach(MeshMapType.allCases) { map in Text(map.description) } @@ -134,23 +134,23 @@ struct AppSettings: View { } - Section(header: Text("Phone GPS")) { + Section(header: Text("phone.gps")) { Toggle(isOn: $userSettings.provideLocation) { - Label("Provide location to mesh", systemImage: "location.circle.fill") + Label("provide.location", systemImage: "location.circle.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) if userSettings.provideLocation { - Picker(" Update Interval", selection: $userSettings.provideLocationInterval) { + Picker("update.interval", selection: $userSettings.provideLocationInterval) { ForEach(LocationUpdateInterval.allCases) { lu in Text(lu.description) } } .pickerStyle(DefaultPickerStyle()) - Text("How frequently your phone will send your location to the device, location updates to the mesh are managed by the device.") + Text("phone.gps.interval.description") .font(.caption) .listRowSeparator(.visible) } @@ -160,7 +160,7 @@ struct AppSettings: View { Button { isPresentingCoreDataResetConfirm = true } label: { - Label("Clear App Data", systemImage: "trash") + Label("clear.app.data", systemImage: "trash") .foregroundColor(.red) } .buttonStyle(.bordered) @@ -168,7 +168,7 @@ struct AppSettings: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingCoreDataResetConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 7173a0f2..1516a5a9 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -37,11 +37,11 @@ struct BluetoothConfig: View { Form { - Section(header: Text("Options")) { + Section(header: Text("options")) { Toggle(isOn: $enabled) { - Label("Enabled", systemImage: "antenna.radiowaves.left.and.right") + Label("enabled", systemImage: "antenna.radiowaves.left.and.right") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 31d02c42..1abc2cdf 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -31,7 +31,7 @@ struct DeviceConfig: View { Form { - Section(header: Text("Options")) { + Section(header: Text("options")) { Picker("Device Role", selection: $deviceRole ) { ForEach(DeviceRoles.allCases) { dr in @@ -96,7 +96,7 @@ struct DeviceConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingNodeDBResetConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 696c978e..13b12138 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -106,7 +106,7 @@ struct DisplayConfig: View { .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingSaveConfirm ) { Button("Save Display Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index d36ee83e..a7b16b39 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -47,11 +47,11 @@ struct CannedMessagesConfig: View { Form { - Section(header: Text("Options")) { + Section(header: Text("options")) { Toggle(isOn: $enabled) { - Label("Enabled", systemImage: "list.bullet.rectangle.fill") + Label("enabled", systemImage: "list.bullet.rectangle.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -220,7 +220,7 @@ struct CannedMessagesConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 19616a20..69896e77 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -68,9 +68,9 @@ struct ExternalNotificationConfig: View { VStack { Form { - Section(header: Text("Options")) { + Section(header: Text("options")) { Toggle(isOn: $enabled) { - Label("Enabled", systemImage: "megaphone") + Label("enabled", systemImage: "megaphone") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Toggle(isOn: $alertBell) { @@ -131,7 +131,7 @@ struct ExternalNotificationConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 796741b0..27b1e11f 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -26,11 +26,11 @@ struct MQTTConfig: View { VStack { Form { - Section(header: Text("Options")) { + Section(header: Text("options")) { Toggle(isOn: $enabled) { - Label("Enabled", systemImage: "dot.radiowaves.right") + Label("enabled", systemImage: "dot.radiowaves.right") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -152,7 +152,7 @@ struct MQTTConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 75993452..38ecae08 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -61,10 +61,10 @@ struct RangeTestConfig: View { var body: some View { VStack { Form { - Section(header: Text("Options")) { + Section(header: Text("options")) { Toggle(isOn: $enabled) { - Label("Enabled", systemImage: "figure.walk") + Label("enabled", systemImage: "figure.walk") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Picker("Sender Interval", selection: $sender ) { @@ -96,7 +96,7 @@ struct RangeTestConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 77399586..6e3f33a9 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -31,11 +31,11 @@ struct SerialConfig: View { Form { - Section(header: Text("Options")) { + Section(header: Text("options")) { Toggle(isOn: $enabled) { - Label("Enabled", systemImage: "terminal") + Label("enabled", systemImage: "terminal") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -122,7 +122,7 @@ struct SerialConfig: View { .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 8a6726d6..91d5d78c 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -108,7 +108,7 @@ struct TelemetryConfig: View { Text("Supported I2C Connected sensors will be detected automatically, sensors are BMP280, BME280, BME680, MCP9808, INA219, INA260, LPS22 and SHTC3.") .font(.caption) Toggle(isOn: $environmentMeasurementEnabled) { - Label("Enabled", systemImage: "chart.xyaxis.line") + Label("enabled", systemImage: "chart.xyaxis.line") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Toggle(isOn: $environmentScreenEnabled) { @@ -133,7 +133,7 @@ struct TelemetryConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index b1d31ea0..20d354d0 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -33,7 +33,7 @@ struct NetworkConfig: View { Section(header: Text("WiFi Options (ESP32 Only)")) { Toggle(isOn: $wifiEnabled) { - Label("Enabled", systemImage: "wifi") + Label("enabled", systemImage: "wifi") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) HStack { @@ -84,7 +84,7 @@ struct NetworkConfig: View { .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) Section(header: Text("Ethernet Options")) { Toggle(isOn: $ethEnabled) { - Label("Enabled", systemImage: "network") + Label("enabled", systemImage: "network") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Text("Enabling Ethernet will disable the bluetooth connection to the app.") diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index de98fe9b..4aa567f8 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -201,7 +201,7 @@ struct PositionConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index bb2a07ce..cdcc3a07 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -11,6 +11,7 @@ "admin.log"="Admin Message Log"; "ago"="ago"; "app.settings"="App Settings"; +"are.you.sure"="Are you sure?"; "ascii.capable"="ASCII Capable"; "available.radios"="Available Radios"; "ble.name"="BLE Name"; @@ -20,6 +21,7 @@ "canned.messages"="Canned Messages"; "canned.messages.config"="Canned Messages Config"; "channels"="Channels (groups)"; +"clear.app.data"="Clear App Data"; "connected.radio"="Connected Radio"; "communicating"="Communicating with device. ."; "connected"="Currently Connected"; @@ -36,14 +38,26 @@ "distance"="Distance"; "disconnect"="Disconnect"; "email.address"="Email Address"; +"enabled"="Enabled"; "external.notification"="External Notification"; "external.notification.config"="External Notification Config"; "firmware.version"="Firmware Version"; -"heard"="Heard"; +"heard"="Heard"; +"hybrid"="Hybrid"; +"interval.five.seconds"="Five Seconds"; +"interval.ten.seconds"="Ten Seconds"; +"interval.fifteen.seconds"="Fifteen Seconds"; +"interval.thirty.seconds"="Thirty Seconds"; +"interval.one.minute"="One Minute"; +"interval.five.minutes"="Five Minutes"; +"interval.ten.minutes"="Ten Minutes"; +"interval.fifteen.minutes"="Fifteen Minutes"; +"keyboard.type"="Keyboard Type"; "logging"="Logging"; "lora"="LoRa"; "lora.config"="LoRa Config"; "map"="Mesh Map"; +"map.type"="Map Type"; "mesh.log"="Mesh Log"; "messages"="Messages"; "module.configuration"="Module Configuration"; @@ -55,12 +69,17 @@ "no.nodes"="No Meshtastic Nodes Found"; "not.connected"="No device connected"; "numbers.punctuation"="Numbers and Punctuation"; +"options"="Options"; +"phone.gps"="Phone GPS"; +"phone.gps.interval.description"="How frequently your phone will send your location to the device, location updates to the mesh are managed by the device."; "position"="Position"; "position.config"="Position Config"; "preferred.radio"="Preferred Radio"; +"provide.location"="Provide location to mesh"; "radio.configuration"="Radio Configuration"; "range.test"="Range Test"; "range.test.config"="Range Test Config"; +"satellite"="Satellite"; "save"="Save"; "serial"="Serial"; "serial.config"="Serial Config"; @@ -72,8 +91,11 @@ "select.node"="Select a Node"; "select.menu.item"="Select an item from the menu"; "set.region"="Set LoRa Region"; +"standard"="Standard"; "telemetry"="Telemetry (Sensors)"; "telemetry.config"="Telemetry Config"; "twitter"="Twitter"; "unknown.age"="Unknown Age"; +"update.interval"="Update Interval"; "user"="User"; +"user.details"="User Details"; From eedbef57fbe09de270bba99baab74712ec642d21 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 13 Dec 2022 17:47:23 -0800 Subject: [PATCH 08/28] * More strings * Add duty cycle error * Don't log local telemetry in the mesh log --- Meshtastic/Enums/BluetoothModes.swift | 7 +- .../Enums/CannedMessagesConfigEnums.swift | 23 ++--- Meshtastic/Enums/ChannelRoles.swift | 7 +- Meshtastic/Enums/DeviceRoles.swift | 8 +- Meshtastic/Enums/DisplayEnums.swift | 29 +++--- Meshtastic/Enums/MessagingEnums.swift | 15 +-- Meshtastic/Enums/PositionConfigEnums.swift | 98 +++++++++---------- Meshtastic/Enums/RoutingError.swift | 58 ++++------- Meshtastic/Enums/SerialConfigEnums.swift | 28 +++--- Meshtastic/Export/WriteCsvFile.swift | 6 +- Meshtastic/Helpers/BLEManager.swift | 2 +- Meshtastic/Helpers/MeshPackets.swift | 7 +- Meshtastic/Views/Helpers/DateTimeText.swift | 2 +- .../Views/Messages/ChannelMessageList.swift | 24 ++--- .../Views/Messages/UserMessageList.swift | 26 ++--- Meshtastic/Views/Nodes/NodeDetail.swift | 4 +- .../Settings/Config/Module/MQTTConfig.swift | 8 +- .../Settings/Config/Module/SerialConfig.swift | 8 +- .../Config/Module/TelemetryConfig.swift | 2 +- .../Views/Settings/Config/NetworkConfig.swift | 8 +- en.lproj/Localizable.strings | 81 +++++++++++++++ 21 files changed, 256 insertions(+), 195 deletions(-) diff --git a/Meshtastic/Enums/BluetoothModes.swift b/Meshtastic/Enums/BluetoothModes.swift index c631defa..69f926e6 100644 --- a/Meshtastic/Enums/BluetoothModes.swift +++ b/Meshtastic/Enums/BluetoothModes.swift @@ -4,6 +4,7 @@ // // Copyright(c) Garth Vander Houwen 8/19/22. // +import Foundation enum BluetoothModes: Int, CaseIterable, Identifiable { @@ -16,11 +17,11 @@ enum BluetoothModes: Int, CaseIterable, Identifiable { get { switch self { case .randomPin: - return "Random PIN" + return NSLocalizedString("bluetooth.mode.randompin", comment: "Random PIN") case .fixedPin: - return "Fixed PIN" + return NSLocalizedString("bluetooth.mode.fixedpin", comment: "Fixed PIN") case .noPin: - return "No PIN (Just Works)" + return NSLocalizedString("bluetooth.mode.nopin", comment: "No PIN (Just Works)") } } } diff --git a/Meshtastic/Enums/CannedMessagesConfigEnums.swift b/Meshtastic/Enums/CannedMessagesConfigEnums.swift index 217bcd60..565e375d 100644 --- a/Meshtastic/Enums/CannedMessagesConfigEnums.swift +++ b/Meshtastic/Enums/CannedMessagesConfigEnums.swift @@ -4,6 +4,7 @@ // // Copyright(c) Garth Vander Houwen 9/10/22. // +import Foundation // Default of 0 is unset enum ConfigPresets : Int, CaseIterable, Identifiable { @@ -18,11 +19,11 @@ enum ConfigPresets : Int, CaseIterable, Identifiable { switch self { case .unset: - return "Manual Configuration" + return NSLocalizedString("canned.messages.preset.manual", comment: "Manual Configuration") case .rakRotaryEncoder: - return "RAK Rotary Encoder Module" + return NSLocalizedString("canned.messages.preset.rakrotary", comment: "RAK Rotary Encoder Module") case .cardKB: - return "M5 Stack Card KB / RAK Keypad" + return NSLocalizedString("canned.messages.preset.cardkb", comment: "M5 Stack Card KB / RAK Keypad") } } } @@ -46,21 +47,21 @@ enum InputEventChars: Int, CaseIterable, Identifiable { switch self { case .none: - return "None" + return NSLocalizedString("inputevent.none", comment: "None") case .up: - return "Up" + return NSLocalizedString("inputevent.up", comment: "Up") case .down: - return "Down" + return NSLocalizedString("inputevent.down", comment: "Down") case .left: - return "Left" + return NSLocalizedString("inputevent.left", comment: "Left") case .right: - return "Right" + return NSLocalizedString("inputevent.right", comment: "Right") case .select: - return "Select" + return NSLocalizedString("inputevent.select", comment: "Select") case .back: - return "Back" + return NSLocalizedString("inputevent.back", comment: "Back") case .cancel: - return "Cancel" + return NSLocalizedString("inputevent.cancel", comment: "Cancel") } } } diff --git a/Meshtastic/Enums/ChannelRoles.swift b/Meshtastic/Enums/ChannelRoles.swift index ac5ad574..83bb30a7 100644 --- a/Meshtastic/Enums/ChannelRoles.swift +++ b/Meshtastic/Enums/ChannelRoles.swift @@ -4,6 +4,7 @@ // // Copyright(c) Garth Vander Houwen 9/21/22. // +import Foundation // Default of 0 is Client enum ChannelRoles: Int, CaseIterable, Identifiable { @@ -18,11 +19,11 @@ enum ChannelRoles: Int, CaseIterable, Identifiable { switch self { case .disabled: - return "Disabled" + return NSLocalizedString("channel.role.disabled", comment: "Disabled") case .primary: - return "Primary" + return NSLocalizedString("channel.role.primary", comment: "Primary") case .secondary: - return "Secondary" + return NSLocalizedString("channel.role.secondary", comment: "Secondary") } } } diff --git a/Meshtastic/Enums/DeviceRoles.swift b/Meshtastic/Enums/DeviceRoles.swift index ba793bd3..c1e8d18c 100644 --- a/Meshtastic/Enums/DeviceRoles.swift +++ b/Meshtastic/Enums/DeviceRoles.swift @@ -21,13 +21,13 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { switch self { case .client: - return "Client (default) - App connected client." + return NSLocalizedString("device.role.client", comment: "Client (default) - App connected client.") case .clientMute: - return "Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh." + return NSLocalizedString("device.role.clientmute", comment: "Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh.") case .router: - return "Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep." + return NSLocalizedString("device.role.router", comment: "Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep.") case .routerClient: - return "Router Client - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client." + return NSLocalizedString("device.role.routerclient", comment: "Router Client - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client.") } } } diff --git a/Meshtastic/Enums/DisplayEnums.swift b/Meshtastic/Enums/DisplayEnums.swift index a1a6103b..46110532 100644 --- a/Meshtastic/Enums/DisplayEnums.swift +++ b/Meshtastic/Enums/DisplayEnums.swift @@ -49,23 +49,24 @@ enum ScreenOnIntervals: Int, CaseIterable, Identifiable { get { switch self { case .oneMinute: - return "One Minute" + return NSLocalizedString("interval.one.minute", comment: "One Minute") case .fiveMinutes: - return "Five Minutes" + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") case .tenMinutes: - return "Ten Minutes" + return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") case .fifteenMinutes: - return "Fifteen Minutes" + return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") case .thirtyMinutes: - return "Thirty Minutes" + return NSLocalizedString("interval.thirty.minutes", comment: "Thirty Minutes") case .oneHour: - return "One Hour" + return NSLocalizedString("interval.one.hour", comment: "One Hour") case .max: - return "Always On" + return NSLocalizedString("always.on", comment: "Always On") } } } } + // Default of 0 is off enum ScreenCarouselIntervals: Int, CaseIterable, Identifiable { @@ -81,17 +82,17 @@ enum ScreenCarouselIntervals: Int, CaseIterable, Identifiable { get { switch self { case .off: - return "Off" + return NSLocalizedString("off", comment: "Off") case .thirtySeconds: - return "Thirty Seconds" + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") case .oneMinute: - return "One Minute" + return NSLocalizedString("interval.one.minute", comment: "One Minute") case .fiveMinutes: - return "Five Minutes" + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") case .tenMinutes: - return "Ten Minutes" + return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") case .fifteenMinutes: - return "Fifteen Minutes" + return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") } } } @@ -109,7 +110,7 @@ enum OledTypes: Int, CaseIterable, Identifiable { get { switch self { case .auto: - return "Automatic Detection" + return NSLocalizedString("automatic.detection", comment: "Automatic Detection") case .ssd1306: return "SSD 1306" case .sh1106: diff --git a/Meshtastic/Enums/MessagingEnums.swift b/Meshtastic/Enums/MessagingEnums.swift index 251fbb6f..9b9f145d 100644 --- a/Meshtastic/Enums/MessagingEnums.swift +++ b/Meshtastic/Enums/MessagingEnums.swift @@ -4,6 +4,7 @@ // // Copyright(c) Garth Vander Houwen 9/30/22. // +import Foundation enum BubblePosition { case left @@ -45,19 +46,19 @@ enum Tapbacks: Int, CaseIterable, Identifiable { get { switch self { case .heart: - return "Heart" + return NSLocalizedString("tapback.heart", comment: "Heart") case .thumbsUp: - return "Thumbs Up" + return NSLocalizedString("tapback.thumbsup", comment: "Thumbs Up") case .thumbsDown: - return "Thumbs Down" + return NSLocalizedString("tapback.thumbsdown", comment: "Thumbs Down") case .haHa: - return "HaHa" + return NSLocalizedString("tapback.haha", comment: "HaHa") case .exclamation: - return "Exclamation Mark" + return NSLocalizedString("tapback.exclamation", comment: "Exclamation Mark") case .question: - return "Question Mark" + return NSLocalizedString("tapback.question", comment: "Question Mark") case .poop: - return "Poop" + return NSLocalizedString("tapback.poop", comment: "Poop") } } } diff --git a/Meshtastic/Enums/PositionConfigEnums.swift b/Meshtastic/Enums/PositionConfigEnums.swift index b70ed62a..081420f0 100644 --- a/Meshtastic/Enums/PositionConfigEnums.swift +++ b/Meshtastic/Enums/PositionConfigEnums.swift @@ -27,27 +27,27 @@ enum PositionBroadcastIntervals: Int, CaseIterable, Identifiable { switch self { case .fifteenSeconds: - return "Fifteen Seconds" + return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") case .thirtySeconds: - return "Thirty Seconds" + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") case .oneMinute: - return "One Minute" + return NSLocalizedString("interval.one.minute", comment: "One Minute") case .fiveMinutes: - return "Five Minutes" + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") case .tenMinutes: - return "Ten Minutes" + return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") case .fifteenMinutes: - return "Fifteen Minutes" + return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") case .thirtyMinutes: - return "Thirty Minutes" + return NSLocalizedString("interval.thirty.minutes", comment: "Thirty Minutes") case .oneHour: - return "One Hour" + return NSLocalizedString("interval.one.hour", comment: "One Hour") case .sixHours: - return "Six Hours" + return NSLocalizedString("interval.six.hours", comment: "Six Hours") case .twelveHours: - return "Twelve Hours" + return NSLocalizedString("interval.twelve.hours", comment: "Twelve Hours") case .twentyFourHours: - return "Twenty Four Hours" + return NSLocalizedString("interval.twentyfour.hours", comment: "Twenty Four Hours") } } } @@ -67,17 +67,17 @@ enum GpsFormats: Int, CaseIterable, Identifiable { get { switch self { case .gpsFormatDec: - return "Decimal Degrees Format" + return NSLocalizedString("gpsformat.dec", comment: "Decimal Degrees Format") case .gpsFormatDms: - return "Degrees Minutes Seconds" + return NSLocalizedString("gpsformat.dms", comment: "Degrees Minutes Seconds") case .gpsFormatUtm: - return "Universal Transverse Mercator" + return NSLocalizedString("gpsformat.utm", comment: "Universal Transverse Mercator") case .gpsFormatMgrs: - return "Military Grid Reference System" + return NSLocalizedString("gpsformat.mgrs", comment: "Military Grid Reference System") case .gpsFormatOlc: - return "Open Location Code (aka Plus Codes)" + return NSLocalizedString("gpsformat.olc", comment: "Open Location Code (aka Plus Codes)") case .gpsFormatOsgr: - return "Ordnance Survey Grid Reference" + return NSLocalizedString("gpsformat.osgr", comment: "Ordnance Survey Grid Reference") } } } @@ -128,39 +128,39 @@ enum GpsUpdateIntervals: Int, CaseIterable, Identifiable { switch self { case .fiveSeconds: - return "Five Seconds" + return NSLocalizedString("interval.five.seconds", comment: "Five Seconds") case .tenSeconds: - return "Ten Seconds" + return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds") case .fifteenSeconds: - return "Fifteen Seconds" + return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") case .twentySeconds: - return "Twenty Seconds" + return NSLocalizedString("interval.twenty.seconds", comment: "Twenty Seconds") case .twentyFiveSeconds: - return "Twenty Five Seconds" + return NSLocalizedString("interval.twentyfive.seconds", comment: "Twenty Five Seconds") case .thirtySeconds: - return "Thirty Seconds" + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") case .oneMinute: - return "One Minute" + return NSLocalizedString("interval.one.minute", comment: "One Minute") case .twoMinutes: - return "Two Minutes" + return NSLocalizedString("interval.two.minutes", comment: "Two Minutes") case .fiveMinutes: - return "Five Minutes" + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") case .tenMinutes: - return "Ten Minutes" + return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") case .fifteenMinutes: - return "Fifteen Minutes" + return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") case .thirtyMinutes: - return "Thirty Minutes" + return NSLocalizedString("interval.thirty.minutes", comment: "Thirty Minutes") case .oneHour: - return "One Hour" + return NSLocalizedString("interval.one.hour", comment: "One Hour") case .sixHours: - return "Six Hours" + return NSLocalizedString("interval.six.hours", comment: "Six Hours") case .twelveHours: - return "Twelve Hours" + return NSLocalizedString("interval.twelve.hours", comment: "Twelve Hours") case .twentyFourHours: - return "Twenty Four Hours" + return NSLocalizedString("interval.twentyfour.hours", comment: "Twenty Four Hours") case .maxInt32: - return "On Boot Only" + return NSLocalizedString("on.boot", comment: "On Boot Only") } } } @@ -168,10 +168,7 @@ enum GpsUpdateIntervals: Int, CaseIterable, Identifiable { enum GpsAttemptTimes: Int, CaseIterable, Identifiable { - case oneSecond = 1 case twoSeconds = 2 - case threeSeconds = 3 - case fourSeconds = 4 case fiveSeconds = 5 case tenSeconds = 10 case fifteenSeconds = 15 @@ -179,6 +176,7 @@ enum GpsAttemptTimes: Int, CaseIterable, Identifiable { case twentyFiveSeconds = 25 case thirtySeconds = 30 case oneMinute = 60 + case twoMinutes = 120 case fiveMinutes = 300 var id: Int { self.rawValue } @@ -186,30 +184,26 @@ enum GpsAttemptTimes: Int, CaseIterable, Identifiable { get { switch self { - case .oneSecond: - return "One Seconds" case .twoSeconds: - return "Two Seconds" - case .threeSeconds: - return "Three Seconds" - case .fourSeconds: - return "Four Seconds" + return NSLocalizedString("interval.two.seconds", comment: "Two Seconds") case .fiveSeconds: - return "Five Seconds" + return NSLocalizedString("interval.five.seconds", comment: "Five Seconds") case .tenSeconds: - return "Ten Seconds" + return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds") case .fifteenSeconds: - return "Fifteen Seconds" + return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") case .twentySeconds: - return "Twenty Seconds" + return NSLocalizedString("interval.twenty.seconds", comment: "Twenty Seconds") case .twentyFiveSeconds: - return "Twenty Five Seconds" + return NSLocalizedString("interval.twentyfive.seconds", comment: "Twenty Five Seconds") case .thirtySeconds: - return "Thirty Seconds" + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") case .oneMinute: - return "One Minute" + return NSLocalizedString("interval.one.minute", comment: "One Minute") + case .twoMinutes: + return NSLocalizedString("interval.two.minutes", comment: "Two Minutes") case .fiveMinutes: - return "Five Minutes" + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") } } } diff --git a/Meshtastic/Enums/RoutingError.swift b/Meshtastic/Enums/RoutingError.swift index b242f45f..390ad5a6 100644 --- a/Meshtastic/Enums/RoutingError.swift +++ b/Meshtastic/Enums/RoutingError.swift @@ -4,6 +4,7 @@ // // Copyright(c) Garth Vander Houwen 8/4/22. // +import Foundation enum RoutingError: Int, CaseIterable, Identifiable { @@ -16,6 +17,7 @@ enum RoutingError: Int, CaseIterable, Identifiable { case noChannel = 6 case tooLarge = 7 case noResponse = 8 + case dutyCycleLimit = 9 case badRequest = 32 case notAuthorized = 33 @@ -25,56 +27,29 @@ enum RoutingError: Int, CaseIterable, Identifiable { switch self { case .none: - return "No Error." + return NSLocalizedString("routing.acknowledged", comment: "Acknowledged") case .noRoute: - return "No Route" + return NSLocalizedString("routing.noroute", comment: "No Route") case .gotNak: - return "Received a nak" + return NSLocalizedString("routing.gotnak", comment: "Received a negative acknowledgment") case .timeout: - return "Timeout" + return NSLocalizedString("routing.timeout", comment: "Timeout") case .noInterface: - return "No Interface" + return NSLocalizedString("routing.nointerface", comment: "No Interface") case .maxRetransmit: - return "Max Retransmission Reached" + return NSLocalizedString("routing.maxretransmit", comment: "Max Retransmission Reached") case .noChannel: - return "No Channel" + return NSLocalizedString("routing.nochannel", comment: "No Channel") case .tooLarge: - return "The packet is too large" + return NSLocalizedString("routing.toolarge", comment: "The packet is too large") case .noResponse: - return "No Response" + return NSLocalizedString("routing.noresponse", comment: "No Response") + case .dutyCycleLimit: + return NSLocalizedString("routing.dutycyclelimit", comment: "Regional Duty Cycle Limit Reached") case .badRequest: - return "Bad Request" + return NSLocalizedString("routing.badRequest", comment: "Bad Request") case .notAuthorized: - return "Not Authorized" - } - } - } - var description: String { - get { - switch self { - - case .none: - return "This message is not a failure." - case .noRoute: - return "Our node doesn't have a route to the requested destination anymore." - case .gotNak: - return "We received a nak while trying to forward on your behalf." - case .timeout: - return "We timed out while attempting to route this packet." - case .noInterface: - return "No suitable interface could be found for delivering this packet." - case .maxRetransmit: - return "We reached the max retransmission count (Hop Limit) and have received no responses." - case .noChannel: - return "No suitable channel was found for sending this packet (i.e. was requested channel index disabled?)." - case .tooLarge: - return "The packet was too big for sending (exceeds interface MTU after encoding)." - case .noResponse: - return "The request had want_response set, the request reached the destination node, but no service on that node wants to send a response (possibly due to bad channel permissions)." - case .badRequest: - return "The application layer service on the remote node received your request, but considered your request somehow invalid." - case .notAuthorized: - return "The application layer service on the remote node received your request, but considered your request not authorized (i.e you did not send the request on the required bound channel)." + return NSLocalizedString("routing.notauthorized", comment: "Not Authorized") } } } @@ -100,10 +75,13 @@ enum RoutingError: Int, CaseIterable, Identifiable { return Routing.Error.tooLarge case .noResponse: return Routing.Error.noResponse + case .dutyCycleLimit: + return Routing.Error.dutyCycleLimit case .badRequest: return Routing.Error.badRequest case .notAuthorized: return Routing.Error.notAuthorized + } } } diff --git a/Meshtastic/Enums/SerialConfigEnums.swift b/Meshtastic/Enums/SerialConfigEnums.swift index 81e4d5b6..f6c4dded 100644 --- a/Meshtastic/Enums/SerialConfigEnums.swift +++ b/Meshtastic/Enums/SerialConfigEnums.swift @@ -4,6 +4,7 @@ // // Copyright(c) Garth Vander Houwen 9/10/22. // +import Foundation enum SerialBaudRates: Int, CaseIterable, Identifiable { @@ -30,7 +31,7 @@ enum SerialBaudRates: Int, CaseIterable, Identifiable { switch self { case .baudDefault: - return "Baud Default" + return NSLocalizedString("default", comment: "Default") case .baud110: return "110 Baud" case .baud300: @@ -118,15 +119,15 @@ enum SerialModeTypes: Int, CaseIterable, Identifiable { get { switch self { case .default: - return "Default" + return NSLocalizedString("serial.mode.default", comment: "Default") case .simple: - return "Simple" + return NSLocalizedString("serial.mode.simple", comment: "Simple") case .proto: - return "Protobufs" + return NSLocalizedString("serial.mode.proto", comment: "Protobufs") case .txtmsg: - return "Text Message" + return NSLocalizedString("serial.mode.txtmsg", comment: "Text Message") case .nmea: - return "NMEA Positions" + return NSLocalizedString("serial.mode.nmea", comment: "NMEA Positions") } } } @@ -166,20 +167,19 @@ enum SerialTimeoutIntervals: Int, CaseIterable, Identifiable { case .unset: return "Unset" case .oneSecond: - return "One Second" + return NSLocalizedString("interval.one.second", comment: "One Second") case .fiveSeconds: - return "Five Seconds" + return NSLocalizedString("interval.five.seconds", comment: "Five Seconds") case .tenSeconds: - return "Ten Seconds" + return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds") case .fifteenSeconds: - return "Fifteen Seconds" + return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") case .thirtySeconds: - return "Thirty Seconds" + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") case .oneMinute: - return "One Minute" + return NSLocalizedString("interval.one.minute", comment: "One Minute") case .fiveMinutes: - return "Five Minutes" - + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") } } } diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index 9edc9929..b0de58cc 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -23,7 +23,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin csvString += ", " csvString += String(dm.airUtilTx) csvString += ", " - csvString += dm.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? "Unknown Age" + csvString += dm.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") } } } else if metricsType == 1 { @@ -44,7 +44,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin csvString += ", " csvString += String(dm.current) csvString += ", " - csvString += dm.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? "Unknown Age" + csvString += dm.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") } } } @@ -73,7 +73,7 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { csvString += ", " csvString += String(pos.snr) csvString += ", " - csvString += pos.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? "Unknown Age" + csvString += pos.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") } return csvString } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index d021e627..80cce787 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -490,7 +490,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { case .rangeTestApp: MeshLogger.log("ℹ️ MESH PACKET received for Range Test App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .telemetryApp: - if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, context: context!) } + if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } case .textMessageCompressedApp: MeshLogger.log("ℹ️ MESH PACKET received for Text Message Compressed App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .zpsApp: diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 5eeea6af..b445db5e 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1189,7 +1189,7 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana } } -func telemetryPacket(packet: MeshPacket, context: NSManagedObjectContext) { +func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) { if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) { @@ -1235,7 +1235,10 @@ func telemetryPacket(packet: MeshPacket, context: NSManagedObjectContext) { } try context.save() - MeshLogger.log("💾 Telemetry Saved for Node: \(packet.from)") + // Only log telemetery from the mesh not the connected device + if connectedNode != Int64(packet.from) { + MeshLogger.log("💾 Telemetry Saved for Node: \(packet.from)") + } } catch { context.rollback() diff --git a/Meshtastic/Views/Helpers/DateTimeText.swift b/Meshtastic/Views/Helpers/DateTimeText.swift index e88a46e6..068664fe 100644 --- a/Meshtastic/Views/Helpers/DateTimeText.swift +++ b/Meshtastic/Views/Helpers/DateTimeText.swift @@ -24,7 +24,7 @@ struct DateTimeText: View { } else { - Text("Unknown Age") + Text("unknown.age") } } } diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 9d3f4600..afe52cb9 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -67,9 +67,9 @@ struct ChannelMessageList: View { .cornerRadius(15) .contextMenu { VStack{ - Text("Channel: \(message.channel)") + Text("channel")+Text(": \(message.channel)") } - Menu("Tapback response") { + Menu("tapback") { ForEach(Tapbacks.allCases) { tb in Button(action: { if bleManager.sendMessage(message: tb.emojiString, toUserNum: 0, channel: channel.index, isEmoji: true, replyID: message.messageId) { @@ -89,16 +89,16 @@ struct ChannelMessageList: View { self.focusedField = .messageText print("I want to reply to \(message.messageId)") }) { - Text("Reply") + Text("reply") Image(systemName: "arrowshape.turn.up.left.2.fill") } Button(action: { UIPasteboard.general.string = message.messagePayload }) { - Text("Copy") + Text("copy") Image(systemName: "doc.on.doc") } - Menu("Message Details") { + Menu("message.details") { VStack { let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp)) Text("Date \(messageDate, style: .date) \(messageDate.formattedDate(format: "h:mm:ss a"))").font(.caption2).foregroundColor(.gray) @@ -110,11 +110,11 @@ struct ChannelMessageList: View { } if currentUser && message.receivedACK { VStack { - Text("Received Ack \(message.receivedACK ? "✔️" : "")") + Text("received.ack")+Text(" \(message.receivedACK ? "✔️" : "")") } } else if currentUser && message.ackError == 0 { // Empty Error - Text("Waiting. . .") + Text("waiting") } else if currentUser && message.ackError > 0 { let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true) @@ -126,7 +126,7 @@ struct ChannelMessageList: View { if ackDate >= sixMonthsAgo! { Text((ackDate.formattedDate(format: "h:mm:ss a"))).font(.caption2).foregroundColor(.gray) } else { - Text("Unknown Age").font(.caption2).foregroundColor(.gray) + Text("unknown.age").font(.caption2).foregroundColor(.gray) } } } @@ -243,12 +243,12 @@ struct ChannelMessageList: View { } } label: { - Text("share.positon") + Text("share.position") Image(systemName: "mappin.and.ellipse") .symbolRenderingMode(.hierarchical) .imageScale(.large).foregroundColor(.accentColor) } - ProgressView("Bytes: \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) + ProgressView("\(NSLocalizedString("bytes", comment: "")): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) .frame(width: 130) .padding(5) .font(.subheadline) @@ -260,7 +260,7 @@ struct ChannelMessageList: View { ZStack { let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0) - TextField("Message", text: $typingMessage, axis: .vertical) + TextField("message", text: $typingMessage, axis: .vertical) .onChange(of: typingMessage, perform: { value in totalBytes = value.utf8.count // Only mess with the value if it is too big @@ -300,7 +300,7 @@ struct ChannelMessageList: View { .imageScale(.large).foregroundColor(.accentColor) } - ProgressView("Bytes: \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) + ProgressView("\(NSLocalizedString("bytes", comment: "")): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) .frame(width: 130) .padding(5) .font(.subheadline) diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 3e16389b..2cb9b642 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -68,9 +68,9 @@ struct UserMessageList: View { .cornerRadius(15) .contextMenu { VStack{ - Text("Channel: \(message.channel)") + Text("channel")+Text(": \(message.channel)") } - Menu("Tapback response") { + Menu("tapback") { ForEach(Tapbacks.allCases) { tb in Button(action: { if bleManager.sendMessage(message: tb.emojiString, toUserNum: user.num, channel: 0, isEmoji: true, replyID: message.messageId) { @@ -90,16 +90,16 @@ struct UserMessageList: View { self.focusedField = .messageText print("I want to reply to \(message.messageId)") }) { - Text("Reply") + Text("reply") Image(systemName: "arrowshape.turn.up.left.2.fill") } Button(action: { UIPasteboard.general.string = message.messagePayload }) { - Text("Copy") + Text("copy") Image(systemName: "doc.on.doc") } - Menu("Message Details") { + Menu("message.details") { VStack { let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp)) Text("Date \(messageDate, style: .date) \(messageDate.formattedDate(format: "h:mm:ss a"))").font(.caption2).foregroundColor(.gray) @@ -111,11 +111,11 @@ struct UserMessageList: View { } if currentUser && message.receivedACK { VStack { - Text("Received Ack \(message.receivedACK ? "✔️" : "")") + Text("received.ack")+Text(" \(message.receivedACK ? "✔️" : "")") } } else if currentUser && message.ackError == 0 { // Empty Error - Text("Waiting. . .") + Text("waiting") } else if currentUser && message.ackError > 0 { let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true) @@ -174,14 +174,14 @@ struct UserMessageList: View { } } HStack { + let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) if currentUser && message.receivedACK { // Ack Received - Text("Acknowledged").font(.caption2).foregroundColor(.gray) + Text("\(ackErrorVal?.display ?? "No Error" )").font(.caption2).foregroundColor(.gray) } else if currentUser && message.ackError == 0 { // Empty Error Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange) } else if currentUser && message.ackError > 0 { - let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true) .font(.caption2).foregroundColor(.red) } @@ -243,12 +243,12 @@ struct UserMessageList: View { } } label: { - Text("share.postion") + Text("share.position") Image(systemName: "mappin.and.ellipse") .symbolRenderingMode(.hierarchical) .imageScale(.large).foregroundColor(.accentColor) } - ProgressView("Bytes: \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) + ProgressView("\(NSLocalizedString("bytes", comment: "")): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) .frame(width: 130) .padding(5) .font(.subheadline) @@ -260,7 +260,7 @@ struct UserMessageList: View { HStack(alignment: .top) { ZStack { let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0) - TextField("Message", text: $typingMessage, axis: .vertical) + TextField("message", text: $typingMessage, axis: .vertical) .onChange(of: typingMessage, perform: { value in totalBytes = value.utf8.count // Only mess with the value if it is too big @@ -297,7 +297,7 @@ struct UserMessageList: View { .symbolRenderingMode(.hierarchical) .imageScale(.large).foregroundColor(.accentColor) } - ProgressView("Bytes: \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) + ProgressView("\(NSLocalizedString("bytes", comment: "")): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes)) .frame(width: 130) .padding(5) .font(.subheadline) diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index b5efc6df..ee0da7d0 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -138,7 +138,7 @@ struct NodeDetail: View { .font(.title) .foregroundColor(.accentColor) .symbolRenderingMode(.hierarchical) - Text("User Id:").font(.title) + Text("user").font(.title)+Text(":").font(.title) } //Text(node.user?.userId ?? "??????").font(.title).foregroundColor(.gray) Text("!\(String(format:"%02x", node.num))") @@ -176,7 +176,7 @@ struct NodeDetail: View { .font(.title) .foregroundColor(.accentColor) .symbolRenderingMode(.hierarchical) - Text("Last Heard: ").font(.title) + Text("heard.last").font(.title)+Text(":").font(.title) } DateTimeText(dateTime: node.lastHeard) diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 27b1e11f..89f472dc 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -78,8 +78,8 @@ struct MQTTConfig: View { .autocorrectionDisabled() HStack { - Label("Username", systemImage: "person.text.rectangle") - TextField("Server Username", text: $username) + Label("mqtt.username", systemImage: "person.text.rectangle") + TextField("mqtt.username", text: $username) .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) @@ -105,8 +105,8 @@ struct MQTTConfig: View { .keyboardType(.default) .scrollDismissesKeyboard(.interactively) HStack { - Label("Password", systemImage: "wallet.pass") - TextField("Server Password", text: $password) + Label("password", systemImage: "wallet.pass") + TextField("password", text: $password) .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 6e3f33a9..349373bd 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -41,20 +41,20 @@ struct SerialConfig: View { Toggle(isOn: $echo) { - Label("Echo", systemImage: "repeat") + Label("echo", systemImage: "repeat") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Text("If set, any packets you send will be echoed back to your device.") .font(.caption) - Picker("Baud Rate", selection: $baudRate ) { + Picker("Baud", selection: $baudRate ) { ForEach(SerialBaudRates.allCases) { sbr in Text(sbr.description) } } .pickerStyle(DefaultPickerStyle()) - Picker("Timeout", selection: $timeout ) { + Picker("timeout", selection: $timeout ) { ForEach(SerialTimeoutIntervals.allCases) { sti in Text(sti.description) } @@ -63,7 +63,7 @@ struct SerialConfig: View { Text("The amount of time to wait before we consider your packet as done.") .font(.caption) - Picker("Mode", selection: $mode ) { + Picker("mode", selection: $mode ) { ForEach(SerialModeTypes.allCases) { smt in Text(smt.description) } diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 91d5d78c..0eeb1902 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -86,7 +86,7 @@ struct TelemetryConfig: View { VStack { Form { - Section(header: Text("Update Intervals")) { + Section(header: Text("update.interval")) { Picker("Device Metrics", selection: $deviceUpdateInterval ) { ForEach(UpdateIntervals.allCases) { ui in Text(ui.description) diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 20d354d0..c71bfaa0 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -37,8 +37,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) @@ -58,8 +58,8 @@ struct NetworkConfig: View { } .keyboardType(.default) HStack { - Label("Password", systemImage: "wallet.pass") - TextField("Password", text: $wifiPsk) + Label("password", systemImage: "wallet.pass") + TextField("password", text: $wifiPsk) .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index cdcc3a07..3c83e242 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -10,16 +10,29 @@ "admin"="Admin"; "admin.log"="Admin Message Log"; "ago"="ago"; +"always.on"="Always On"; "app.settings"="App Settings"; "are.you.sure"="Are you sure?"; "ascii.capable"="ASCII Capable"; "available.radios"="Available Radios"; +"automatic.detection"="Automatic Detection"; "ble.name"="BLE Name"; "bluetooth"="Bluetooth"; "bluetooth.config"="Bluetooth Config"; +"bluetooth.mode.randompin"="Random PIN"; +"bluetooth.mode.fixedpin"="Fixed PIN"; +"bluetooth.mode.nopin"="No PIN (Just Works)"; +"bytes"="Bytes"; "cancel"="Cancel"; "canned.messages"="Canned Messages"; "canned.messages.config"="Canned Messages Config"; +"canned.messages.preset.manual"="Manual Configuration"; +"canned.messages.preset.rakrotary"="RAK Rotary Encoder Module"; +"canned.messages.preset.cardkb"="M5 Stack Card KB / RAK Keypad"; +"channel"="Channel"; +"channel.role.disabled"="Disabled"; +"channel.role.primary"="Primary"; +"channel.role.secondary"="Secondary"; "channels"="Channels (groups)"; "clear.app.data"="Clear App Data"; "connected.radio"="Connected Radio"; @@ -27,31 +40,62 @@ "connected"="Currently Connected"; "connecting"="Connecting . ."; "contacts"="Contacts"; +"copy"="Copy"; "default"="Default"; "delete"="Delete"; "device"="Device"; "device.config"="Device Config"; +"device.role.client"="Client (default) - App connected client."; +"device.role.clientmute"="Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh."; +"device.role.router"="Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep."; +"device.role.routerclient"="Router Client - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client."; "direct.messages"="Direct Messages"; "dismiss.keyboard"="Dismiss Keyboard"; "display"="Display (Device Screen)"; "display.config"="Display Config"; "distance"="Distance"; "disconnect"="Disconnect"; +"echo"="Echo"; "email.address"="Email Address"; "enabled"="Enabled"; "external.notification"="External Notification"; "external.notification.config"="External Notification Config"; "firmware.version"="Firmware Version"; +"gpsformat.dec"="Decimal Degrees Format"; +"gpsformat.dms"="Degrees Minutes Seconds"; +"gpsformat.utm"="Universal Transverse Mercator"; +"gpsformat.mgrs"="Military Grid Reference System"; +"gpsformat.olc"="Open Location Code (aka Plus Codes)"; +"gpsformat.osgr"="Ordnance Survey Grid Reference"; "heard"="Heard"; +"heard.last"="Last Heard"; "hybrid"="Hybrid"; +"inputevent.none"="None"; +"inputevent.up"="Up"; +"inputevent.down"="Down"; +"inputevent.left"="Left"; +"inputevent.right"="Right"; +"inputevent.select"="Select"; +"inputevent.back"="Back"; +"inputevent.cancel"="Cancel"; +"interval.one.second"="One Second"; +"interval.two.seconds"="Two Seconds"; "interval.five.seconds"="Five Seconds"; "interval.ten.seconds"="Ten Seconds"; "interval.fifteen.seconds"="Fifteen Seconds"; +"interval.twenty.seconds"="Twenty Seconds"; +"interval.twentyfive.seconds"="Twenty Five Seconds"; "interval.thirty.seconds"="Thirty Seconds"; "interval.one.minute"="One Minute"; +"interval.two.minutes"="Two Minutes"; "interval.five.minutes"="Five Minutes"; "interval.ten.minutes"="Ten Minutes"; "interval.fifteen.minutes"="Fifteen Minutes"; +"interval.thirty.minutes"="Thirty Minutes"; +"interval.one.hour"="One Hour"; +"interval.six.hours"="Six Hours"; +"interval.twelve.hours"="Twelve Hours"; +"interval.twentyfour.hours"="Twenty Four Hours"; "keyboard.type"="Keyboard Type"; "logging"="Logging"; "lora"="LoRa"; @@ -59,17 +103,24 @@ "map"="Mesh Map"; "map.type"="Map Type"; "mesh.log"="Mesh Log"; +"message"="Message"; +"message.details"="Message Details"; "messages"="Messages"; +"mode"="Mode"; "module.configuration"="Module Configuration"; "mqtt"="MQTT"; "mqtt.config"="MQTT Config"; +"mqtt.username"="Username"; "network"="Network"; "network.config"="Network Config"; "nodes"="Nodes"; "no.nodes"="No Meshtastic Nodes Found"; "not.connected"="No device connected"; "numbers.punctuation"="Numbers and Punctuation"; +"off"="Off"; +"on.boot"="On Boot Only"; "options"="Options"; +"password"="Password"; "phone.gps"="Phone GPS"; "phone.gps.interval.description"="How frequently your phone will send your location to the device, location updates to the mesh are managed by the device."; "position"="Position"; @@ -79,10 +130,29 @@ "radio.configuration"="Radio Configuration"; "range.test"="Range Test"; "range.test.config"="Range Test Config"; +"reply"="Reply"; +"received.ack"="Received Ack"; +"routing.acknowledged"="Acknowledged"; +"routing.noroute"="No Route"; +"routing.gotnak"="Received a negative acknowledgment"; +"routing.timeout"="Timeout"; +"routing.nointerface"="No Interface"; +"routing.maxretransmit"="Max Retransmission Reached"; +"routing.nochannel"="No Channel"; +"routing.toolarge"="The packet is too large"; +"routing.noresponse"="No Response"; +"routing.dutycyclelimit"="Regional Duty Cycle Limit Reached"; +"routing.badRequest"="Bad Request"; +"routing.notauthorized"="Not Authorized"; "satellite"="Satellite"; "save"="Save"; "serial"="Serial"; "serial.config"="Serial Config"; +"serial.mode.default"="Default"; +"serial.mode.simple"="Simple"; +"serial.mode.proto"="Protobufs"; +"serial.mode.txtmsg"="Text Message"; +"serial.mode.nmea"="NMEA Positions"; "settings"="Settings"; "share.channels"="Share Channels QR Code"; "share.position"="Share Position"; @@ -92,10 +162,21 @@ "select.menu.item"="Select an item from the menu"; "set.region"="Set LoRa Region"; "standard"="Standard"; +"ssid"="SSID"; +"tapback"="Tapback Response"; +"tapback.heart"="Heart"; +"tapback.thumbsup"="Thumbs Up"; +"tapback.thumbsdown"="Thumbs Down"; +"tapback.haha"="HaHa"; +"tapback.exclamation"="Exclamation Mark"; +"tapback.question"="Question Mark"; +"tapback.poop"="Poop"; "telemetry"="Telemetry (Sensors)"; "telemetry.config"="Telemetry Config"; +"timeout"="timeout" "twitter"="Twitter"; "unknown.age"="Unknown Age"; "update.interval"="Update Interval"; "user"="User"; "user.details"="User Details"; +"waiting"="Waiting. . ."; From 7f57ea8ad0cfda6867b4ab2eb1354f0558780517 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 13 Dec 2022 19:39:12 -0800 Subject: [PATCH 09/28] Remote test german strings --- de.lproj/Localizable.strings | 8 -------- 1 file changed, 8 deletions(-) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index ab527870..3d5b44e6 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -5,11 +5,3 @@ Created by Garth Vander Houwen on 12/12/22. */ -"ago"="prije"; -"available.radios"="Verfügbare Funkgeräte"; -"cancel"="Absagen"; -"connected.radio"="Verbundenes Radio"; -"delete"="Löschen"; -"heard"="Čuo"; -"save"="Speichern"; -"unknown.age"="Unbekanntes Alter"; From a26127b8dc13d5cd7e2905667b8ad3969d8cd22b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 13 Dec 2022 23:49:54 -0800 Subject: [PATCH 10/28] Fix missing ; --- en.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 3c83e242..b97f5f37 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* +/* Localizable.strings Meshtastic @@ -173,7 +173,7 @@ "tapback.poop"="Poop"; "telemetry"="Telemetry (Sensors)"; "telemetry.config"="Telemetry Config"; -"timeout"="timeout" +"timeout"="timeout"; "twitter"="Twitter"; "unknown.age"="Unknown Age"; "update.interval"="Update Interval"; From 72d6c03d3207afc8193cd5263f5e7f5c5cdff229 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 14 Dec 2022 00:00:54 -0800 Subject: [PATCH 11/28] Bump version --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 14f1609a..1818b5e5 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -996,7 +996,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.7; + MARKETING_VERSION = 2.0.8; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1029,7 +1029,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.7; + MARKETING_VERSION = 2.0.8; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; From ef97027a9fb428d20d19a723ab9d70728a8e7e9e Mon Sep 17 00:00:00 2001 From: formtapez Date: Wed, 14 Dec 2022 11:58:26 +0100 Subject: [PATCH 12/28] german translation (incomplete) --- de.lproj/Localizable.strings | 175 +++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 3d5b44e6..76695d37 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -5,3 +5,178 @@ Created by Garth Vander Houwen on 12/12/22. */ +"about"="Über"; +"about.meshtastic"="Über Meshtastic"; +"admin"="admin"; +"admin.log"="Admin Message Log"; +"ago"="ago"; +"always.on"="Immer an"; +"app.settings"="App Einstellungen"; +"are.you.sure"="Bist Du sicher?"; +"ascii.capable"="ASCII fähig"; +"available.radios"="Geräte in der Nähe"; +"automatic.detection"="Automatische erkennung"; +"ble.name"="BLE Name"; +"bluetooth"="Bluetooth"; +"bluetooth.config"="Bluetooth Konfiguration"; +"bluetooth.mode.randompin"="Zufällige PIN"; +"bluetooth.mode.fixedpin"="Feste PIN"; +"bluetooth.mode.nopin"="Keine PIN (geht einfach)"; +"bytes"="Bytes"; +"cancel"="Abbrechen"; +"canned.messages"="Canned Messages"; +"canned.messages.config"="Canned Messages Config"; +"canned.messages.preset.manual"="Manualle Konfiguration"; +"canned.messages.preset.rakrotary"="RAK Drehimpulsgeber Modul"; +"canned.messages.preset.cardkb"="M5 Stack Card KB / RAK Tastenfeld"; +"channel"="Kanal"; +"channel.role.disabled"="Deaktiviert"; +"channel.role.primary"="Primär"; +"channel.role.secondary"="Sekundär"; +"channels"="Kanäle (Gruppen)"; +"clear.app.data"="App Daten löschen"; +"connected.radio"="Verbundenes Gerät"; +"communicating"="Verbinde mit Gerät..."; +"connected"="Derzeit verbunden"; +"connecting"="Verbinde..."; +"contacts"="Kontakte"; +"copy"="Kopieren"; +"default"="Standard"; +"delete"="Löschen"; +"device"="Gerät"; +"device.config"="Gerätekonfiguration"; +"device.role.client"="Client (default) - App connected client."; +"device.role.clientmute"="Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh."; +"device.role.router"="Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep."; +"device.role.routerclient"="Router Client - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client."; +"direct.messages"="Direct Messages"; +"dismiss.keyboard"="Dismiss Keyboard"; +"display"="Display (Device Screen)"; +"display.config"="Display Config"; +"distance"="Distance"; +"disconnect"="Disconnect"; +"echo"="Echo"; +"email.address"="Email Address"; +"enabled"="Enabled"; +"external.notification"="External Notification"; +"external.notification.config"="External Notification Config"; +"firmware.version"="Firmware Version"; +"gpsformat.dec"="Decimal Degrees Format"; +"gpsformat.dms"="Degrees Minutes Seconds"; +"gpsformat.utm"="Universal Transverse Mercator"; +"gpsformat.mgrs"="Military Grid Reference System"; +"gpsformat.olc"="Open Location Code (aka Plus Codes)"; +"gpsformat.osgr"="Ordnance Survey Grid Reference"; +"heard"="Heard"; +"heard.last"="Last Heard"; +"hybrid"="Hybrid"; +"inputevent.none"="None"; +"inputevent.up"="Up"; +"inputevent.down"="Down"; +"inputevent.left"="Left"; +"inputevent.right"="Right"; +"inputevent.select"="Select"; +"inputevent.back"="Back"; +"inputevent.cancel"="Cancel"; +"interval.one.second"="One Second"; +"interval.two.seconds"="Two Seconds"; +"interval.five.seconds"="Five Seconds"; +"interval.ten.seconds"="Ten Seconds"; +"interval.fifteen.seconds"="Fifteen Seconds"; +"interval.twenty.seconds"="Twenty Seconds"; +"interval.twentyfive.seconds"="Twenty Five Seconds"; +"interval.thirty.seconds"="Thirty Seconds"; +"interval.one.minute"="One Minute"; +"interval.two.minutes"="Two Minutes"; +"interval.five.minutes"="Five Minutes"; +"interval.ten.minutes"="Ten Minutes"; +"interval.fifteen.minutes"="Fifteen Minutes"; +"interval.thirty.minutes"="Thirty Minutes"; +"interval.one.hour"="One Hour"; +"interval.six.hours"="Six Hours"; +"interval.twelve.hours"="Twelve Hours"; +"interval.twentyfour.hours"="Twenty Four Hours"; +"keyboard.type"="Keyboard Type"; +"logging"="Logging"; +"lora"="LoRa"; +"lora.config"="LoRa Config"; +"map"="Mesh Map"; +"map.type"="Map Type"; +"mesh.log"="Mesh Log"; +"message"="Message"; +"message.details"="Message Details"; +"messages"="Messages"; +"mode"="Mode"; +"module.configuration"="Module Configuration"; +"mqtt"="MQTT"; +"mqtt.config"="MQTT Config"; +"mqtt.username"="Username"; +"network"="Network"; +"network.config"="Network Config"; +"nodes"="Nodes"; +"no.nodes"="No Meshtastic Nodes Found"; +"not.connected"="No device connected"; +"numbers.punctuation"="Numbers and Punctuation"; +"off"="Off"; +"on.boot"="On Boot Only"; +"options"="Options"; +"password"="Password"; +"phone.gps"="Phone GPS"; +"phone.gps.interval.description"="How frequently your phone will send your location to the device, location updates to the mesh are managed by the device."; +"position"="Position"; +"position.config"="Position Config"; +"preferred.radio"="Preferred Radio"; +"provide.location"="Provide location to mesh"; +"radio.configuration"="Radio Configuration"; +"range.test"="Range Test"; +"range.test.config"="Range Test Config"; +"reply"="Reply"; +"received.ack"="Received Ack"; +"routing.acknowledged"="Acknowledged"; +"routing.noroute"="No Route"; +"routing.gotnak"="Received a negative acknowledgment"; +"routing.timeout"="Timeout"; +"routing.nointerface"="No Interface"; +"routing.maxretransmit"="Max Retransmission Reached"; +"routing.nochannel"="No Channel"; +"routing.toolarge"="The packet is too large"; +"routing.noresponse"="No Response"; +"routing.dutycyclelimit"="Regional Duty Cycle Limit Reached"; +"routing.badRequest"="Bad Request"; +"routing.notauthorized"="Not Authorized"; +"satellite"="Satellite"; +"save"="Save"; +"serial"="Serial"; +"serial.config"="Serial Config"; +"serial.mode.default"="Default"; +"serial.mode.simple"="Simple"; +"serial.mode.proto"="Protobufs"; +"serial.mode.txtmsg"="Text Message"; +"serial.mode.nmea"="NMEA Positions"; +"settings"="Settings"; +"share.channels"="Share Channels QR Code"; +"share.position"="Share Position"; +"subscribed"="Subscribed to mesh"; +"select.contact"="Select a Contact"; +"select.node"="Select a Node"; +"select.menu.item"="Select an item from the menu"; +"set.region"="Set LoRa Region"; +"standard"="Standard"; +"ssid"="SSID"; +"tapback"="Tapback Response"; +"tapback.heart"="Heart"; +"tapback.thumbsup"="Thumbs Up"; +"tapback.thumbsdown"="Thumbs Down"; +"tapback.haha"="HaHa"; +"tapback.exclamation"="Exclamation Mark"; +"tapback.question"="Question Mark"; +"tapback.poop"="Poop"; +"telemetry"="Telemetry (Sensors)"; +"telemetry.config"="Telemetry Config"; +"timeout"="timeout"; +"twitter"="Twitter"; +"unknown.age"="Unknown Age"; +"update.interval"="Update Interval"; +"user"="User"; +"user.details"="User Details"; +"waiting"="Waiting. . ."; From a162091388afacdfb8459200c92789c63da29688 Mon Sep 17 00:00:00 2001 From: formtapez Date: Wed, 14 Dec 2022 12:15:47 +0100 Subject: [PATCH 13/28] german translation --- de.lproj/Localizable.strings | 212 +++++++++++++++++------------------ 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 76695d37..2c9d9c6f 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -9,7 +9,7 @@ "about.meshtastic"="Über Meshtastic"; "admin"="admin"; "admin.log"="Admin Message Log"; -"ago"="ago"; +"ago"="her"; "always.on"="Immer an"; "app.settings"="App Einstellungen"; "are.you.sure"="Bist Du sicher?"; @@ -49,134 +49,134 @@ "device.role.clientmute"="Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh."; "device.role.router"="Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep."; "device.role.routerclient"="Router Client - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client."; -"direct.messages"="Direct Messages"; +"direct.messages"="Direktnachrichten"; "dismiss.keyboard"="Dismiss Keyboard"; "display"="Display (Device Screen)"; "display.config"="Display Config"; -"distance"="Distance"; -"disconnect"="Disconnect"; +"distance"="Entfernung"; +"disconnect"="Trennen"; "echo"="Echo"; -"email.address"="Email Address"; -"enabled"="Enabled"; -"external.notification"="External Notification"; -"external.notification.config"="External Notification Config"; +"email.address"="Email Adresse"; +"enabled"="Aktiviert"; +"external.notification"="Externe Benachrichtigung"; +"external.notification.config"="Einstellungen der externen Benachrichtigung"; "firmware.version"="Firmware Version"; -"gpsformat.dec"="Decimal Degrees Format"; -"gpsformat.dms"="Degrees Minutes Seconds"; +"gpsformat.dec"="Dezimalgrad Format"; +"gpsformat.dms"="Grad Minuten Sekunden"; "gpsformat.utm"="Universal Transverse Mercator"; "gpsformat.mgrs"="Military Grid Reference System"; "gpsformat.olc"="Open Location Code (aka Plus Codes)"; "gpsformat.osgr"="Ordnance Survey Grid Reference"; -"heard"="Heard"; -"heard.last"="Last Heard"; +"heard"="Gehört"; +"heard.last"="Zuletzte gehört"; "hybrid"="Hybrid"; -"inputevent.none"="None"; -"inputevent.up"="Up"; -"inputevent.down"="Down"; -"inputevent.left"="Left"; -"inputevent.right"="Right"; -"inputevent.select"="Select"; -"inputevent.back"="Back"; -"inputevent.cancel"="Cancel"; -"interval.one.second"="One Second"; -"interval.two.seconds"="Two Seconds"; -"interval.five.seconds"="Five Seconds"; -"interval.ten.seconds"="Ten Seconds"; -"interval.fifteen.seconds"="Fifteen Seconds"; -"interval.twenty.seconds"="Twenty Seconds"; -"interval.twentyfive.seconds"="Twenty Five Seconds"; -"interval.thirty.seconds"="Thirty Seconds"; -"interval.one.minute"="One Minute"; -"interval.two.minutes"="Two Minutes"; -"interval.five.minutes"="Five Minutes"; -"interval.ten.minutes"="Ten Minutes"; -"interval.fifteen.minutes"="Fifteen Minutes"; -"interval.thirty.minutes"="Thirty Minutes"; -"interval.one.hour"="One Hour"; -"interval.six.hours"="Six Hours"; -"interval.twelve.hours"="Twelve Hours"; -"interval.twentyfour.hours"="Twenty Four Hours"; -"keyboard.type"="Keyboard Type"; +"inputevent.none"="Keins"; +"inputevent.up"="Hoch"; +"inputevent.down"="Runter"; +"inputevent.left"="Links"; +"inputevent.right"="Rechts"; +"inputevent.select"="Auswählen"; +"inputevent.back"="Zurück"; +"inputevent.cancel"="Abbrechen"; +"interval.one.second"="Eine Sekunde"; +"interval.two.seconds"="Zwei Sekunden"; +"interval.five.seconds"="Fünf Sekunden"; +"interval.ten.seconds"="Zehn Sekunden"; +"interval.fifteen.seconds"="Fünfzehn Sekunden"; +"interval.twenty.seconds"="Zwanzig Sekunden"; +"interval.twentyfive.seconds"="Fünfundzwanzig Sekunden"; +"interval.thirty.seconds"="Dreißig Sekunden"; +"interval.one.minute"="Eine Minute"; +"interval.two.minutes"="Zwei Minutes"; +"interval.five.minutes"="Fünf Minutes"; +"interval.ten.minutes"="Zehn Minutes"; +"interval.fifteen.minutes"="Fünfzehn Minutes"; +"interval.thirty.minutes"="Dreißig Minutes"; +"interval.one.hour"="Eine Stunde"; +"interval.six.hours"="Sechs Stunden"; +"interval.twelve.hours"="Zwölf Stunden"; +"interval.twentyfour.hours"="Vierundzwanzig Stunden"; +"keyboard.type"="Keyboard Typ"; "logging"="Logging"; "lora"="LoRa"; -"lora.config"="LoRa Config"; -"map"="Mesh Map"; -"map.type"="Map Type"; +"lora.config"="LoRa Einstellungen"; +"map"="Mesh Karte"; +"map.type"="kartentyp"; "mesh.log"="Mesh Log"; -"message"="Message"; -"message.details"="Message Details"; -"messages"="Messages"; -"mode"="Mode"; -"module.configuration"="Module Configuration"; +"message"="Nachricht"; +"message.details"="Nachrichtendetails"; +"messages"="Nachrichten"; +"mode"="Modus"; +"module.configuration"="Modul Konfiguration"; "mqtt"="MQTT"; "mqtt.config"="MQTT Config"; -"mqtt.username"="Username"; -"network"="Network"; -"network.config"="Network Config"; +"mqtt.username"="Benutzername"; +"network"="Netzwerk"; +"network.config"="Netzwerkeinstellungen"; "nodes"="Nodes"; -"no.nodes"="No Meshtastic Nodes Found"; -"not.connected"="No device connected"; -"numbers.punctuation"="Numbers and Punctuation"; -"off"="Off"; -"on.boot"="On Boot Only"; -"options"="Options"; -"password"="Password"; -"phone.gps"="Phone GPS"; -"phone.gps.interval.description"="How frequently your phone will send your location to the device, location updates to the mesh are managed by the device."; +"no.nodes"="Keine Meshtastic Nodes gefunden"; +"not.connected"="Kein Gerät verbunden"; +"numbers.punctuation"="Ziffern und Interpunktion"; +"off"="Aus"; +"on.boot"="Nur beim Starten"; +"options"="Optionen"; +"password"="Passwort"; +"phone.gps"="Telefon GPS"; +"phone.gps.interval.description"="Wie häufig das Telefon den Standort an das Gerät sendet. Standortaktualisierungen an das Mesh werden vom Gerät verwaltet."; "position"="Position"; -"position.config"="Position Config"; -"preferred.radio"="Preferred Radio"; -"provide.location"="Provide location to mesh"; -"radio.configuration"="Radio Configuration"; -"range.test"="Range Test"; -"range.test.config"="Range Test Config"; -"reply"="Reply"; -"received.ack"="Received Ack"; -"routing.acknowledged"="Acknowledged"; -"routing.noroute"="No Route"; -"routing.gotnak"="Received a negative acknowledgment"; -"routing.timeout"="Timeout"; -"routing.nointerface"="No Interface"; -"routing.maxretransmit"="Max Retransmission Reached"; -"routing.nochannel"="No Channel"; -"routing.toolarge"="The packet is too large"; -"routing.noresponse"="No Response"; -"routing.dutycyclelimit"="Regional Duty Cycle Limit Reached"; +"position.config"="Positionseinstellungen"; +"preferred.radio"="Bevorzugtes Gerät"; +"provide.location"="Standort im Mesh veröffentlichen"; +"radio.configuration"="Geräteeinstellungen"; +"range.test"="Entfernungstest"; +"range.test.config"="Entfernungstest Konfiguration"; +"reply"="Antworten"; +"received.ack"="Empfangsbestätigung"; +"routing.acknowledged"="Bestätigt"; +"routing.noroute"="Keine Route"; +"routing.gotnak"="Negative Empfangsbestätigung empfangen"; +"routing.timeout"="Zeitlimit erreicht"; +"routing.nointerface"="Kein Interface"; +"routing.maxretransmit"="Maximale Wiederholungen erreicht"; +"routing.nochannel"="Kein Kanal"; +"routing.toolarge"="Das Paket ist zu groß"; +"routing.noresponse"="Keine Antwort"; +"routing.dutycyclelimit"="Regionale Einschaltdauergrenze erreicht"; "routing.badRequest"="Bad Request"; -"routing.notauthorized"="Not Authorized"; -"satellite"="Satellite"; -"save"="Save"; +"routing.notauthorized"="Nicht authorisiert"; +"satellite"="Satellit"; +"save"="Speichern"; "serial"="Serial"; -"serial.config"="Serial Config"; -"serial.mode.default"="Default"; -"serial.mode.simple"="Simple"; +"serial.config"="Serial Konfiguration"; +"serial.mode.default"="Standard"; +"serial.mode.simple"="Einfach"; "serial.mode.proto"="Protobufs"; -"serial.mode.txtmsg"="Text Message"; -"serial.mode.nmea"="NMEA Positions"; -"settings"="Settings"; -"share.channels"="Share Channels QR Code"; -"share.position"="Share Position"; +"serial.mode.txtmsg"="Text Nachricht"; +"serial.mode.nmea"="NMEA Positionen"; +"settings"="Einstellungen"; +"share.channels"="Kanal QR Code teilen"; +"share.position"="Position teilen"; "subscribed"="Subscribed to mesh"; -"select.contact"="Select a Contact"; -"select.node"="Select a Node"; -"select.menu.item"="Select an item from the menu"; -"set.region"="Set LoRa Region"; +"select.contact"="Kontakt wählen"; +"select.node"="Node auswählen"; +"select.menu.item"="Wähle einen Menüeintrag aus"; +"set.region"="Setze LoRa Region"; "standard"="Standard"; "ssid"="SSID"; "tapback"="Tapback Response"; -"tapback.heart"="Heart"; -"tapback.thumbsup"="Thumbs Up"; -"tapback.thumbsdown"="Thumbs Down"; +"tapback.heart"="Gehört"; +"tapback.thumbsup"="Daumen hoch"; +"tapback.thumbsdown"="Daumen runter"; "tapback.haha"="HaHa"; -"tapback.exclamation"="Exclamation Mark"; -"tapback.question"="Question Mark"; -"tapback.poop"="Poop"; -"telemetry"="Telemetry (Sensors)"; -"telemetry.config"="Telemetry Config"; -"timeout"="timeout"; +"tapback.exclamation"="Ausrufezeichen"; +"tapback.question"="Fragezeichen"; +"tapback.poop"="Kacke"; +"telemetry"="Telemetrie (Sensoren)"; +"telemetry.config"="Telemetrie Einstellungen"; +"timeout"="Zeitlimit erreicht"; "twitter"="Twitter"; -"unknown.age"="Unknown Age"; -"update.interval"="Update Interval"; -"user"="User"; -"user.details"="User Details"; -"waiting"="Waiting. . ."; +"unknown.age"="Unbekanntes alter"; +"update.interval"="Update intervall"; +"user"="Benutzer"; +"user.details"="Benutzer Details"; +"waiting"="Warte..."; From e18739eb07a38b8179c71b925dc47593060457ac Mon Sep 17 00:00:00 2001 From: formtapez Date: Wed, 14 Dec 2022 12:32:04 +0100 Subject: [PATCH 14/28] german translation --- de.lproj/Localizable.strings | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 2c9d9c6f..7ec05091 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -45,10 +45,10 @@ "delete"="Löschen"; "device"="Gerät"; "device.config"="Gerätekonfiguration"; -"device.role.client"="Client (default) - App connected client."; -"device.role.clientmute"="Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh."; -"device.role.router"="Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep."; -"device.role.routerclient"="Router Client - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client."; +"device.role.client"="Client (Standard) - Mit App verbundener Client."; +"device.role.clientmute"="Client Leise - Das selbe wie Client, außer das die Pakete nicht über diesen Node weitergeleitet werden. Nimmt nicht am Mesh-Routing teil."; +"device.role.router"="Router - Mesh Pakete werden bevorzugt über diesen Node gerouted. Dieser Node wird nicht von einer Client App benutzt. WLAN, Bluetooth und Display sind aus."; +"device.role.routerclient"="Router Client - Mesh Pakete werden bevorzugt über diesen Node gerouted. Der Router Client kann parallel auch von einer Client-App genutzt werden."; "direct.messages"="Direktnachrichten"; "dismiss.keyboard"="Dismiss Keyboard"; "display"="Display (Device Screen)"; @@ -63,12 +63,12 @@ "firmware.version"="Firmware Version"; "gpsformat.dec"="Dezimalgrad Format"; "gpsformat.dms"="Grad Minuten Sekunden"; -"gpsformat.utm"="Universal Transverse Mercator"; -"gpsformat.mgrs"="Military Grid Reference System"; +"gpsformat.utm"="Universal Transversal Mercator"; +"gpsformat.mgrs"="Militärisches Gitternetz-Referenzsystem"; "gpsformat.olc"="Open Location Code (aka Plus Codes)"; -"gpsformat.osgr"="Ordnance Survey Grid Reference"; +"gpsformat.osgr"="Ordnance Survey Gitterreferenz"; "heard"="Gehört"; -"heard.last"="Zuletzte gehört"; +"heard.last"="Zuletzt gehört"; "hybrid"="Hybrid"; "inputevent.none"="Keins"; "inputevent.up"="Hoch"; From 44a388f0c82be5adbd93e038f5c100ebb5f34f42 Mon Sep 17 00:00:00 2001 From: Joshua Pirihi Date: Thu, 15 Dec 2022 06:16:07 +1300 Subject: [PATCH 15/28] Add security call when importing mbtiles file --- Meshtastic/MeshtasticApp.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 98958a3e..84ae1e6a 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -66,6 +66,12 @@ struct MeshtasticAppleApp: App { let destination = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false) if !self.saveChannels { + + //tell the system we want the file please + guard url.startAccessingSecurityScopedResource() else { + return + } + //do we need to delete an old one? if (fileManager.fileExists(atPath: destination.path)) { print("ℹ️ Found an old map file. Deleting it") From af77373a6b1d6541224631571644207ba5b079c0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 14 Dec 2022 21:17:55 -0800 Subject: [PATCH 16/28] Save device buzzer and button GPIO values when they arrive --- Meshtastic/Helpers/MeshPackets.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index b445db5e..dcd2e64b 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -78,11 +78,15 @@ func localConfig (config: Config, context:NSManagedObjectContext, nodeNum: Int64 newDeviceConfig.role = Int32(config.device.role.rawValue) newDeviceConfig.serialEnabled = config.device.serialEnabled newDeviceConfig.debugLogEnabled = config.device.debugLogEnabled + newDeviceConfig.buttonGpio = Int32(config.device.buttonGpio) + newDeviceConfig.buzzerGpio = Int32(config.device.buzzerGpio) fetchedNode[0].deviceConfig = newDeviceConfig } else { fetchedNode[0].deviceConfig?.role = Int32(config.device.role.rawValue) fetchedNode[0].deviceConfig?.serialEnabled = config.device.serialEnabled fetchedNode[0].deviceConfig?.debugLogEnabled = config.device.debugLogEnabled + fetchedNode[0].deviceConfig?.buttonGpio = Int32(config.device.buttonGpio) + fetchedNode[0].deviceConfig?.buzzerGpio = Int32(config.device.buzzerGpio) } do { From 464b74a118e61354982fb23ec77f78aa6277bdb7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 17 Dec 2022 19:14:53 -0800 Subject: [PATCH 17/28] Channel Grid --- Meshtastic.xcodeproj/project.pbxproj | 16 ++- Meshtastic/Views/Messages/Contacts.swift | 2 +- Meshtastic/Views/Settings/Channels.swift | 106 ++++++++++++++++++ Meshtastic/Views/Settings/Settings.swift | 11 ++ Meshtastic/Views/Settings/ShareChannels.swift | 2 +- de.lproj/Localizable.strings | 3 +- en.lproj/Localizable.strings | 3 +- 7 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 Meshtastic/Views/Settings/Channels.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 1818b5e5..0d6c3898 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; }; DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; }; + DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0B6B1294CDC55001356EC /* Channels.swift */; }; DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; }; DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; }; DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */; }; @@ -187,6 +188,7 @@ DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = ""; }; DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = ""; }; + DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = ""; }; DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAnnotation.swift; sourceTree = ""; }; @@ -305,13 +307,14 @@ isa = PBXGroup; children = ( DD97E96728EFE9A00056DDA4 /* About.swift */, - DD3501882852FC3B000FC853 /* Settings.swift */, - DD4A911D2708C65400501B7E /* AppSettings.swift */, - DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, - DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, - DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, - DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, + DD4A911D2708C65400501B7E /* AppSettings.swift */, + DDA0B6B1294CDC55001356EC /* Channels.swift */, + DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, + DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, + DD3501882852FC3B000FC853 /* Settings.swift */, + DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, + DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD61937A2863876A00E59241 /* Config */, ); path = Settings; @@ -737,6 +740,7 @@ DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */, DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, DD6193792863875F00E59241 /* SerialConfig.swift in Sources */, + DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */, DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */, DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */, DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */, diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index 4e07be06..7d873cee 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -28,7 +28,7 @@ struct Contacts: View { NavigationSplitView { List { - Section(header: Text("Channels (groups)")) { + Section(header: Text("channels")) { // Display Contacts for the rest of the non admin channels if node != nil && node!.myInfo != nil && node!.myInfo!.channels != nil { ForEach(node!.myInfo!.channels!.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift new file mode 100644 index 00000000..cda0b2f1 --- /dev/null +++ b/Meshtastic/Views/Settings/Channels.swift @@ -0,0 +1,106 @@ +// +// ShareChannel.swift +// MeshtasticApple +// +// Copyright(c) Garth Vander Houwen 4/8/22. +// +import SwiftUI +import CoreData + +func generateChannelKey(size: Int) -> String { + var keyData = Data(count: size) + let result = keyData.withUnsafeMutableBytes { + SecRandomCopyBytes(kSecRandomDefault, size, $0.baseAddress!) + } + return keyData.base64EncodedString() +} + +struct Channels: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @Environment(\.dismiss) private var dismiss + @Environment(\.sizeCategory) var sizeCategory + + var node: NodeInfoEntity? + + @State private var isPresentingEditView = false + + var body: some View { + + ScrollView { + if node != nil && node?.myInfo != nil { + Grid() { + GridRow { + Text("Index") + .font(.caption2) + Text("name") + .font(.caption2) + if sizeCategory <= ContentSizeCategory.extraExtraLarge { + Text("Up/down link") + .font(.caption2) + } + Text("Edit") + .font(.caption2) + Text("Delete") + .font(.caption2) + } + ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in + GridRow { + CircleText(text: String(channel.index), color: .accentColor, circleSize: 32) + Text(((channel.name!.isEmpty ? "Primary" : channel.name) ?? "Primary").camelCaseToWords()) + if sizeCategory <= ContentSizeCategory.extraExtraLarge { + HStack { + if channel.uplinkEnabled { + Image(systemName: "checkmark.square") + } else { + Image(systemName: "square") + } + if channel.downlinkEnabled { + Image(systemName: "checkmark.square") + } else { + Image(systemName: "square") + } + } + } + Button { + print("Edit Channel") + + } label: { + Label("", systemImage: "square.and.pencil") + } + Button(role: .destructive) { + print("Delete Channel") + + } label: { + Label("", systemImage: "trash") + } + .disabled(channel.role == 1) + } + } + } + if node!.myInfo!.channels?.array.count ?? 0 < 8 { + + Button { + print("Add Channel") + + } label: { + Label("Add Channel", systemImage: "plus.square") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + } + } + } + .navigationTitle("Channels") + .navigationBarItems(trailing: + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + bleManager.context = context + } + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 7b6ad0d6..528aacc5 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -59,6 +59,17 @@ struct Settings: View { Text("lora") } + NavigationLink() { + + Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) + } label: { + + Image(systemName: "fibrechannel") + .symbolRenderingMode(.hierarchical) + + Text("channels") + } + NavigationLink() { BluetoothConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 75240770..5810b5cf 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -76,7 +76,7 @@ struct ShareChannels: View { .toggleStyle(.switch) .labelsHidden() .disabled(channel.role == 1) - Text(((channel.name!.isEmpty ? "Primary" : channel.name) ?? "Primary").camelCaseToWords()).fixedSize() + Text(((channel.name!.isEmpty ? "Primary" : channel.name) ?? "Primary").camelCaseToWords()) if channel.psk?.hexDescription.count ?? 0 < 3 { Image(systemName: "lock.slash") .foregroundColor(.red) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 7ec05091..bde184d0 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -33,7 +33,7 @@ "channel.role.disabled"="Deaktiviert"; "channel.role.primary"="Primär"; "channel.role.secondary"="Sekundär"; -"channels"="Kanäle (Gruppen)"; +"channels"="Kanäle"; "clear.app.data"="App Daten löschen"; "connected.radio"="Verbundenes Gerät"; "communicating"="Verbinde mit Gerät..."; @@ -111,6 +111,7 @@ "mqtt"="MQTT"; "mqtt.config"="MQTT Config"; "mqtt.username"="Benutzername"; +"name"="Name"; "network"="Netzwerk"; "network.config"="Netzwerkeinstellungen"; "nodes"="Nodes"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index b97f5f37..dbe62ed9 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -33,7 +33,7 @@ "channel.role.disabled"="Disabled"; "channel.role.primary"="Primary"; "channel.role.secondary"="Secondary"; -"channels"="Channels (groups)"; +"channels"="Channels"; "clear.app.data"="Clear App Data"; "connected.radio"="Connected Radio"; "communicating"="Communicating with device. ."; @@ -111,6 +111,7 @@ "mqtt"="MQTT"; "mqtt.config"="MQTT Config"; "mqtt.username"="Username"; +"name"="Name"; "network"="Network"; "network.config"="Network Config"; "nodes"="Nodes"; From ca0b9e66d390f36a3a031691aaf782d5ec2e5dc2 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 17 Dec 2022 23:53:06 -0800 Subject: [PATCH 18/28] Enter for submit on mac --- Meshtastic/Helpers/BLEManager.swift | 1 - Meshtastic/Helpers/MeshPackets.swift | 1 + Meshtastic/Persistence/UpdateCoreData.swift | 5 +- .../Views/Messages/ChannelMessageList.swift | 17 ++- Meshtastic/Views/Messages/Contacts.swift | 128 +++++++++--------- .../Views/Messages/UserMessageList.swift | 15 ++ Meshtastic/Views/Settings/Channels.swift | 92 +++++-------- 7 files changed, 133 insertions(+), 126 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 80cce787..0c1790fa 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -957,7 +957,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { fetchedMyInfo[0].channels = mutableChannels do { try context!.save() - } catch { print("Failed to clear existing channels from local app database") } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index dcd2e64b..03b1d0de 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -756,6 +756,7 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo if fetchedMyInfo.count == 1 { let newChannel = ChannelEntity(context: context) + newChannel.id = Int32(channel.index) newChannel.index = Int32(channel.index) newChannel.uplinkEnabled = channel.settings.uplinkEnabled newChannel.downlinkEnabled = channel.settings.downlinkEnabled diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index a189e58e..faa46e9b 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -60,9 +60,9 @@ public func clearTelemetry(destNum: Int64, metricsType: Int32, context: NSManage } } -public func deleteChannelMessages(channelIndex: Int32, context: NSManagedObjectContext) { +public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObjectContext) { let fetchChannelMessagesRequest = NSFetchRequest(entityName: "MessageEntity") - fetchChannelMessagesRequest.predicate = NSPredicate(format: "channel == %i AND toUser == nil AND admin == false", Int32(channelIndex)) + fetchChannelMessagesRequest.predicate = NSPredicate(format: "channel == %i AND toUser == nil AND admin == false", Int32(channel.id)) fetchChannelMessagesRequest.includesPropertyValues = false do { let objects = try context.fetch(fetchChannelMessagesRequest) as! [NSManagedObject] @@ -70,7 +70,6 @@ public func deleteChannelMessages(channelIndex: Int32, context: NSManagedObjectC context.delete(object) } try context.save() - context.refreshAllObjects() } catch let error as NSError { print("Error: \(error.localizedDescription)") } diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index afe52cb9..b59c3195 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -311,9 +311,22 @@ struct ChannelMessageList: View { .focused($focusedField, equals: .messageText) .multilineTextAlignment(.leading) .frame(minHeight: 50) - + .keyboardShortcut(.defaultAction) + .onSubmit { + #if targetEnvironment(macCatalyst) + if bleManager.sendMessage(message: typingMessage, toUserNum: 0, channel: channel.index, isEmoji: false, replyID: replyMessageId) { + typingMessage = "" + focusedField = nil + replyMessageId = 0 + if sendPositionWithMessage { + if bleManager.sendPosition(destNum: Int64(channel.index), wantAck: true) { + print("Location Sent") + } + } + } + #endif + } Text(typingMessage).opacity(0).padding(.all, 0) - } .overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1)) .padding(.bottom, 15) diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index 7d873cee..76ce62ed 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -33,82 +33,79 @@ struct Contacts: View { if node != nil && node!.myInfo != nil && node!.myInfo!.channels != nil { ForEach(node!.myInfo!.channels!.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in if channel.name?.lowercased() ?? "" != "admin" && channel.name?.lowercased() ?? "" != "gpio" && channel.name?.lowercased() ?? "" != "serial" { - VStack { - NavigationLink(destination: ChannelMessageList(channel: channel)) { - - let mostRecent = channel.allPrivateMessages.last(where: { $0.channel == channel.index }) - let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) - let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 - let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 - VStack(alignment: .leading) { - HStack { - CircleText(text: String(channel.index), color: .accentColor, circleSize: 52, fontSize: 40, brightness: 0.1) - .padding(.trailing, 5) - VStack { - HStack { - if channel.name?.isEmpty ?? false { - if channel.role == 1 { - Text(String("PrimaryChannel").camelCaseToWords()).font(.headline) - } else { - Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline) - } + NavigationLink(destination: ChannelMessageList(channel: channel)) { + + let mostRecent = channel.allPrivateMessages.last(where: { $0.channel == channel.index }) + let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) + let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 + let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 + VStack(alignment: .leading) { + HStack { + CircleText(text: String(channel.index), color: .accentColor, circleSize: 52, fontSize: 40, brightness: 0.1) + .padding(.trailing, 5) + VStack { + HStack { + if channel.name?.isEmpty ?? false { + if channel.role == 1 { + Text(String("PrimaryChannel").camelCaseToWords()).font(.headline) } else { - Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline) - } - Spacer() - if channel.allPrivateMessages.count > 0 { - VStack (alignment: .trailing) { - if lastMessageDay == currentDay { - Text(lastMessageTime, style: .time ) - .font(.subheadline) - } else if lastMessageDay == (currentDay - 1) { - Text("Yesterday") - .font(.subheadline) - } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { - Text(lastMessageTime.formattedDate(format: "MM/dd/yy")) - .font(.subheadline) - } else if lastMessageDay < (currentDay - 1800) { - Text(lastMessageTime.formattedDate(format: "MM/dd/yy")) - .font(.subheadline) - } - } - .brightness(-0.20) + Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline) } + } else { + Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline) } + Spacer() if channel.allPrivateMessages.count > 0 { - HStack(alignment: .top) { - Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") - .truncationMode(.tail) - .frame(maxWidth: .infinity, alignment: .leading) - .brightness(-0.20) - .font(.body) + VStack (alignment: .trailing) { + if lastMessageDay == currentDay { + Text(lastMessageTime, style: .time ) + .font(.subheadline) + } else if lastMessageDay == (currentDay - 1) { + Text("Yesterday") + .font(.subheadline) + } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { + Text(lastMessageTime.formattedDate(format: "MM/dd/yy")) + .font(.subheadline) + } else if lastMessageDay < (currentDay - 1800) { + Text(lastMessageTime.formattedDate(format: "MM/dd/yy")) + .font(.subheadline) + } } + .brightness(-0.20) + } + } + if channel.allPrivateMessages.count > 0 { + HStack(alignment: .top) { + Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") + .truncationMode(.tail) + .frame(maxWidth: .infinity, alignment: .leading) + .brightness(-0.20) + .font(.body) } } - .frame(maxWidth: .infinity, alignment: .leading) } + .frame(maxWidth: .infinity, alignment: .leading) } } } .frame(maxWidth: .infinity, maxHeight: 80, alignment: .leading) .contextMenu { -// Hide mute channel menu item until I can check it in notifications -// Button { -// channel.mute = !channel.mute -// -// do { -// try context.save() -// // Would rather not do this but the merge changes on -// // A single object is only working on mac GVH -// context.refreshAllObjects() -// //context.refresh(channel, mergeChanges: true) -// } catch { -// context.rollback() -// print("💥 Save Channel Mute Error") -// } -// } label: { -// Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash") -// } + Button { + channel.mute = !channel.mute + + do { + try context.save() + // Would rather not do this but the merge changes on + // A single object is only working on mac GVH + context.refreshAllObjects() + //context.refresh(channel, mergeChanges: true) + } catch { + context.rollback() + print("💥 Save Channel Mute Error") + } + } label: { + Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash") + } if channel.allPrivateMessages.count > 0 { Button(role: .destructive) { @@ -121,12 +118,13 @@ struct Contacts: View { .confirmationDialog( "This conversation will be deleted.", isPresented: $isPresentingDeleteChannelMessagesConfirm, + titleVisibility: .visible ) { Button(role: .destructive) { - deleteChannelMessages(channelIndex: channel.index, context: context) - context.refreshAllObjects() + deleteChannelMessages(channel: channel, context: context) + } label: { Text("delete") } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 2cb9b642..914f2f91 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -308,6 +308,21 @@ struct UserMessageList: View { .focused($focusedField, equals: .messageText) .multilineTextAlignment(.leading) .frame(minHeight: 50) + .keyboardShortcut(.defaultAction) + .onSubmit { + #if targetEnvironment(macCatalyst) + if bleManager.sendMessage(message: typingMessage, toUserNum: user.num, channel: 0, isEmoji: false, replyID: replyMessageId) { + typingMessage = "" + focusedField = nil + replyMessageId = 0 + if sendPositionWithMessage { + if bleManager.sendPosition(destNum: user.num, wantAck: true) { + print("Location Sent") + } + } + } + #endif + } Text(typingMessage).opacity(0).padding(.all, 0) } .overlay(RoundedRectangle(cornerRadius: 20).stroke(.tertiary, lineWidth: 1)) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index cda0b2f1..48f16093 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -9,7 +9,7 @@ import CoreData func generateChannelKey(size: Int) -> String { var keyData = Data(count: size) - let result = keyData.withUnsafeMutableBytes { + _ = keyData.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, size, $0.baseAddress!) } return keyData.base64EncodedString() @@ -25,73 +25,55 @@ struct Channels: View { var node: NodeInfoEntity? @State private var isPresentingEditView = false + @State private var selectedIndex: Int32 = -1 var body: some View { - ScrollView { - if node != nil && node?.myInfo != nil { - Grid() { - GridRow { - Text("Index") - .font(.caption2) - Text("name") - .font(.caption2) - if sizeCategory <= ContentSizeCategory.extraExtraLarge { - Text("Up/down link") - .font(.caption2) - } - Text("Edit") - .font(.caption2) - Text("Delete") - .font(.caption2) - } + NavigationStack { + List { + if node != nil && node?.myInfo != nil { ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in - GridRow { - CircleText(text: String(channel.index), color: .accentColor, circleSize: 32) - Text(((channel.name!.isEmpty ? "Primary" : channel.name) ?? "Primary").camelCaseToWords()) - if sizeCategory <= ContentSizeCategory.extraExtraLarge { + Button(action: { + selectedIndex = channel.index + isPresentingEditView = true + print("Tapity tap") + }) { + VStack(alignment: .leading) { HStack { - if channel.uplinkEnabled { - Image(systemName: "checkmark.square") - } else { - Image(systemName: "square") - } - if channel.downlinkEnabled { - Image(systemName: "checkmark.square") - } else { - Image(systemName: "square") + CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 36, brightness: 0.1) + .padding(.trailing, 5) + VStack { + HStack { + if channel.name?.isEmpty ?? false { + if channel.role == 1 { + Text(String("PrimaryChannel").camelCaseToWords()).font(.headline) + } else { + Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline) + } + } else { + Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline) + } + } } } } - Button { - print("Edit Channel") - - } label: { - Label("", systemImage: "square.and.pencil") - } - Button(role: .destructive) { - print("Delete Channel") - - } label: { - Label("", systemImage: "trash") - } - .disabled(channel.role == 1) } } } - if node!.myInfo!.channels?.array.count ?? 0 < 8 { + } + if node?.myInfo?.channels?.array.count ?? 0 < 8 { + + Button { + let key = generateChannelKey(size: 32) + print("Add Channel Key \(key) ") - Button { - print("Add Channel") - - } label: { - Label("Add Channel", systemImage: "plus.square") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() + } label: { + Label("Add Channel", systemImage: "plus.square") } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() } } .navigationTitle("Channels") From 32916e2c4dea03a13670a9fdf528ca4f606cb7b0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 18 Dec 2022 00:09:08 -0800 Subject: [PATCH 19/28] Mute channel notifications --- Meshtastic/Helpers/MeshPackets.swift | 37 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 03b1d0de..f947ecbf 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1314,19 +1314,30 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM MeshLogger.log("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown")") } else if newMessage.fromUser != nil && newMessage.toUser == nil { - - - // Create an iOS Notification for the received private channel message and schedule it immediately - let manager = LocalNotificationManager() - manager.notifications = [ - Notification( - id: ("notification.id.\(newMessage.messageId)"), - title: "\(newMessage.fromUser?.longName ?? "Unknown")", - subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")", - content: messageText) - ] - manager.schedule() - MeshLogger.log("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown")") + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedNode)) + + do { + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity] + for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { + if channel.index == newMessage.channel && !channel.mute { + // Create an iOS Notification for the received private channel message and schedule it immediately + let manager = LocalNotificationManager() + manager.notifications = [ + Notification( + id: ("notification.id.\(newMessage.messageId)"), + title: "\(newMessage.fromUser?.longName ?? "Unknown")", + subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")", + content: messageText) + ] + manager.schedule() + MeshLogger.log("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown")") + } + } + } catch { + + + } } } } catch { From 197cc90a72a156981e58c52d3876607a60197026 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 18 Dec 2022 00:21:26 -0800 Subject: [PATCH 20/28] Put refresh on contacts page when deleting messages --- Meshtastic/Persistence/UpdateCoreData.swift | 1 - Meshtastic/Views/Messages/Contacts.swift | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index faa46e9b..bd752922 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -86,7 +86,6 @@ public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext context.delete(object) } try context.save() - context.refresh(user, mergeChanges: true) } catch let error as NSError { print("Error: \(error.localizedDescription)") } diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index 76ce62ed..308e4188 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -124,7 +124,7 @@ struct Contacts: View { Button(role: .destructive) { deleteChannelMessages(channel: channel, context: context) - + context.refresh(node!.myInfo!, mergeChanges: true) } label: { Text("delete") } @@ -210,7 +210,7 @@ struct Contacts: View { Button(role: .destructive) { deleteUserMessages(user: user, context: context) - + context.refresh(node!.user!, mergeChanges: true) } label: { Text("delete") } From 7e523108d47ecbce05027b202e34989acda97323 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 20 Dec 2022 08:29:34 -0800 Subject: [PATCH 21/28] Additional channel editor ui mockup --- Meshtastic/Views/Settings/Channels.swift | 150 ++++++++++++++++++++++- 1 file changed, 148 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 48f16093..0d910b86 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -24,9 +24,19 @@ struct Channels: View { var node: NodeInfoEntity? + @State private var isPresentingSaveConfirm: Bool = false + @State var hasChanges = false @State private var isPresentingEditView = false @State private var selectedIndex: Int32 = -1 + @State private var channelIndex: Int32 = 0 + @State private var channelName = "Channel" + @State private var channelKeySize = 32 + @State private var channelKey = "AQ==" + @State private var channelRole = 2 + @State private var uplink = false + @State private var downlink = false + var body: some View { NavigationStack { @@ -35,8 +45,13 @@ struct Channels: View { ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in Button(action: { selectedIndex = channel.index + channelIndex = channel.index + channelRole = Int(channel.role) + channelKey = channel.psk?.hexDescription ?? "" isPresentingEditView = true - print("Tapity tap") + channelName = channel.name ?? "Channel\(channelIndex)" + uplink = channel.uplinkEnabled + downlink = channel.downlinkEnabled }) { VStack(alignment: .leading) { HStack { @@ -66,6 +81,13 @@ struct Channels: View { Button { let key = generateChannelKey(size: 32) print("Add Channel Key \(key) ") + isPresentingEditView = true + channelIndex = Int32(node!.myInfo!.channels!.array.count) + channelRole = 2 + channelName = "Channel\(channelIndex)" + channelKey = key + uplink = false + downlink = false } label: { Label("Add Channel", systemImage: "plus.square") @@ -74,9 +96,133 @@ struct Channels: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding() + .sheet(isPresented: $isPresentingEditView) { + + #if targetEnvironment(macCatalyst) + Text("edit.channel") + .font(.largeTitle) + .padding() + #endif + Form { + Section("Edit Channel \(channelIndex)") { + HStack { + Text("name") + Spacer() + TextField( + "Channel Name", + text: $channelName + ) + .foregroundColor(Color.gray) + } + HStack { + Picker("Key Size", selection: $channelKeySize) { + Text("Empty").tag(0) + Text("Default").tag(-1) + Text("1 Bit").tag(1) + Text("128 Bit").tag(16) + Text("256 Bit").tag(32) + } + .pickerStyle(DefaultPickerStyle()) + Spacer() + Button { + if channelKeySize == -1 { + channelKey = "AQ==" + } else { + let key = generateChannelKey(size: channelKeySize) + channelKey = key.base64ToBase64url() + } + } label: { + Image(systemName: "lock.rotation") + .font(.title) + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.small) + } + HStack (alignment: .top) { + Text("Key") + Spacer() + TextField ( + "", + text: $channelKey, + axis: .vertical + ) + .foregroundColor(Color.gray) + + .disabled(true) + } + Picker("Channel Role", selection: $channelRole) { + if channelRole == 1 { + Text("Primary").tag(1) + } else{ + Text("Disabled").tag(0) + Text("Secondary").tag(2) + } + } + .pickerStyle(DefaultPickerStyle()) + Toggle("Uplink Enabled", isOn: $uplink) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Toggle("Downlink Enabled", isOn: $downlink) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + } + .onSubmit { + //validate(name: channelName) + } + .onChange(of: channelKeySize) { newKeySize in + if channelKeySize == -1 { + channelKey = "AQ==" + } else { + let key = generateChannelKey(size: channelKeySize) + channelKey = key.base64ToBase64url() + } + hasChanges = true + } + .onChange(of: channelKey) { newKey in + hasChanges = true + } + } + Button { + isPresentingSaveConfirm = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible + ) { + Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var channel = Channel() + channel.index = channelIndex + channel.settings.name = channelName + channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary + channel.settings.uplinkEnabled = uplink + channel.settings.downlinkEnabled = downlink + + +// let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: node!.user!, toUser: node!.user!) +// +// if adminMessageId > 0 { +// // Should show a saved successfully alert once I know that to be true +// // for now just disable the button after a successful save +// hasChanges = false +// goBack() +// } + } + } + .presentationDetents([.medium, .large]) + } } } - .navigationTitle("Channels") + .navigationTitle("channels") + .navigationSplitViewStyle(.balanced) .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") From f031e54b835ea4b5d87bc7f065a3914b8444e819 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 20 Dec 2022 08:42:14 -0800 Subject: [PATCH 22/28] Remove extraneous base64 conversion --- Meshtastic/Views/Settings/Channels.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 0d910b86..57558113 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -129,7 +129,7 @@ struct Channels: View { channelKey = "AQ==" } else { let key = generateChannelKey(size: channelKeySize) - channelKey = key.base64ToBase64url() + channelKey = key } } label: { Image(systemName: "lock.rotation") @@ -174,7 +174,7 @@ struct Channels: View { channelKey = "AQ==" } else { let key = generateChannelKey(size: channelKeySize) - channelKey = key.base64ToBase64url() + channelKey = key } hasChanges = true } From 3dbd47dda84ff6a2108725734e9a81cf3c186a1b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 20 Dec 2022 20:34:01 -0800 Subject: [PATCH 23/28] More complete channel mockup --- Meshtastic/Views/Settings/Channels.swift | 117 +++++++++++++++-------- 1 file changed, 75 insertions(+), 42 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 57558113..40fe3ad8 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -24,9 +24,10 @@ struct Channels: View { var node: NodeInfoEntity? - @State private var isPresentingSaveConfirm: Bool = false + @State var hasChanges = false @State private var isPresentingEditView = false + @State private var isPresentingSaveConfirm: Bool = false @State private var selectedIndex: Int32 = -1 @State private var channelIndex: Int32 = 0 @@ -47,11 +48,23 @@ struct Channels: View { selectedIndex = channel.index channelIndex = channel.index channelRole = Int(channel.role) - channelKey = channel.psk?.hexDescription ?? "" + channelKey = channel.psk?.base64EncodedString() ?? "" + if channelKey.count == 0 { + channelKeySize = 0 + } else if channelKey == "AQ==" { + channelKeySize = -1 + } else if channelKey.count == 24 { + channelKeySize = 16 + } else if channelKey.count == 32 { + channelKeySize = 24 + } else if channelKey.count == 44 { + channelKeySize = 32 + } isPresentingEditView = true channelName = channel.name ?? "Channel\(channelIndex)" uplink = channel.uplinkEnabled downlink = channel.downlinkEnabled + hasChanges = false }) { VStack(alignment: .leading) { HStack { @@ -88,6 +101,7 @@ struct Channels: View { channelKey = key uplink = false downlink = false + hasChanges = false } label: { Label("Add Channel", systemImage: "plus.square") @@ -99,7 +113,7 @@ struct Channels: View { .sheet(isPresented: $isPresentingEditView) { #if targetEnvironment(macCatalyst) - Text("edit.channel") + Text("channel") .font(.largeTitle) .padding() #endif @@ -116,13 +130,15 @@ struct Channels: View { } HStack { Picker("Key Size", selection: $channelKeySize) { - Text("Empty").tag(0) - Text("Default").tag(-1) - Text("1 Bit").tag(1) - Text("128 Bit").tag(16) - Text("256 Bit").tag(32) + Text("Empty (0 bytes)").tag(0) + Text("Default (1 byte)").tag(-1) + Text("1 Bit (1 byte)").tag(1) + Text("AES-128 (16 bytes)").tag(16) + Text("AES-192 (24 bytes)").tag(24) + Text("AES-256 (32 bytes)").tag(32) } .pickerStyle(DefaultPickerStyle()) + .fixedSize() Spacer() Button { if channelKeySize == -1 { @@ -160,6 +176,7 @@ struct Channels: View { } } .pickerStyle(DefaultPickerStyle()) + .disabled(channelRole == 1) Toggle("Uplink Enabled", isOn: $uplink) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Toggle("Downlink Enabled", isOn: $downlink) @@ -169,6 +186,9 @@ struct Channels: View { .onSubmit { //validate(name: channelName) } + .onChange(of: channelName) { newName in + hasChanges = true + } .onChange(of: channelKeySize) { newKeySize in if channelKeySize == -1 { channelKey = "AQ==" @@ -181,41 +201,54 @@ struct Channels: View { .onChange(of: channelKey) { newKey in hasChanges = true } - } - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding(.bottom) - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - - var channel = Channel() - channel.index = channelIndex - channel.settings.name = channelName - channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary - channel.settings.uplinkEnabled = uplink - channel.settings.downlinkEnabled = downlink - - -// let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: node!.user!, toUser: node!.user!) -// -// if adminMessageId > 0 { -// // Should show a saved successfully alert once I know that to be true -// // for now just disable the button after a successful save -// hasChanges = false -// goBack() -// } + .onChange(of: channelRole) { newRole in + hasChanges = true } + .onChange(of: uplink) { newUplink in + hasChanges = true + } + .onChange(of: downlink) { newDownlink in + hasChanges = true + } + } + HStack { + Button { + isPresentingSaveConfirm = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible + ) { + Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var channel = Channel() + channel.index = channelIndex + channel.settings.name = channelName + channel.settings.psk = Data(base64Encoded: channelKey, options: .ignoreUnknownCharacters) ?? Data() + channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary + channel.settings.uplinkEnabled = uplink + channel.settings.downlinkEnabled = downlink + } + } + #if targetEnvironment(macCatalyst) + Button { + isPresentingEditView = false + } label: { + Label("Close", systemImage: "xmark") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) + #endif } .presentationDetents([.medium, .large]) } From ab04dd1e324a4bcf0895c52da29cb61d38481c49 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 20 Dec 2022 21:33:50 -0800 Subject: [PATCH 24/28] More fixes for validation --- Meshtastic/Views/Settings/Channels.swift | 32 +++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 40fe3ad8..f9376439 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -24,7 +24,6 @@ struct Channels: View { var node: NodeInfoEntity? - @State var hasChanges = false @State private var isPresentingEditView = false @State private var isPresentingSaveConfirm: Bool = false @@ -126,19 +125,33 @@ struct Channels: View { "Channel Name", text: $channelName ) + .disableAutocorrection(true) + .keyboardType(.alphabet) .foregroundColor(Color.gray) + .onChange(of: channelName, perform: { value in + channelName = channelName.replacing(" ", with: "") + let totalBytes = channelName.utf8.count + // Only mess with the value if it is too big + if totalBytes > 11 { + let firstNBytes = Data(channelName.utf8.prefix(11)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the channelName back to the last place where it was the right size + channelName = maxBytesString + } + } + hasChanges = true + }) } HStack { Picker("Key Size", selection: $channelKeySize) { - Text("Empty (0 bytes)").tag(0) - Text("Default (1 byte)").tag(-1) - Text("1 Bit (1 byte)").tag(1) - Text("AES-128 (16 bytes)").tag(16) - Text("AES-192 (24 bytes)").tag(24) - Text("AES-256 (32 bytes)").tag(32) + Text("Empty").tag(0) + Text("Default").tag(-1) + Text("1 bit").tag(1) + Text("128 bit").tag(16) + Text("192 bit").tag(24) + Text("256 bit").tag(32) } .pickerStyle(DefaultPickerStyle()) - .fixedSize() Spacer() Button { if channelKeySize == -1 { @@ -164,9 +177,10 @@ struct Channels: View { axis: .vertical ) .foregroundColor(Color.gray) - .disabled(true) + } + .textSelection(.enabled) Picker("Channel Role", selection: $channelRole) { if channelRole == 1 { Text("Primary").tag(1) From 6451fbf9949c37020b8cc0e599647764bdc79c76 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 20 Dec 2022 22:32:04 -0800 Subject: [PATCH 25/28] A few other channel validation details --- Meshtastic/Views/Settings/Channels.swift | 200 +++++++++++------------ 1 file changed, 99 insertions(+), 101 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index f9376439..9f3ea027 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -30,7 +30,7 @@ struct Channels: View { @State private var selectedIndex: Int32 = -1 @State private var channelIndex: Int32 = 0 - @State private var channelName = "Channel" + @State private var channelName = "" @State private var channelKeySize = 32 @State private var channelKey = "AQ==" @State private var channelRole = 2 @@ -60,7 +60,8 @@ struct Channels: View { channelKeySize = 32 } isPresentingEditView = true - channelName = channel.name ?? "Channel\(channelIndex)" + + channelName = channel.name ?? "" uplink = channel.uplinkEnabled downlink = channel.downlinkEnabled hasChanges = false @@ -96,7 +97,6 @@ struct Channels: View { isPresentingEditView = true channelIndex = Int32(node!.myInfo!.channels!.array.count) channelRole = 2 - channelName = "Channel\(channelIndex)" channelKey = key uplink = false downlink = false @@ -117,113 +117,111 @@ struct Channels: View { .padding() #endif Form { - Section("Edit Channel \(channelIndex)") { - HStack { - Text("name") - Spacer() - TextField( - "Channel Name", - text: $channelName - ) - .disableAutocorrection(true) - .keyboardType(.alphabet) - .foregroundColor(Color.gray) - .onChange(of: channelName, perform: { value in - channelName = channelName.replacing(" ", with: "") - let totalBytes = channelName.utf8.count - // Only mess with the value if it is too big - if totalBytes > 11 { - let firstNBytes = Data(channelName.utf8.prefix(11)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the channelName back to the last place where it was the right size - channelName = maxBytesString - } + HStack { + Text("name") + Spacer() + TextField( + "Channel Name", + text: $channelName + ) + .disableAutocorrection(true) + .keyboardType(.alphabet) + .foregroundColor(Color.gray) + .disabled(channelRole == 1 && channelName.count > 0) + .onChange(of: channelName, perform: { value in + channelName = channelName.replacing(" ", with: "") + let totalBytes = channelName.utf8.count + // Only mess with the value if it is too big + if totalBytes > 11 { + let firstNBytes = Data(channelName.utf8.prefix(11)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the channelName back to the last place where it was the right size + channelName = maxBytesString } - hasChanges = true - }) - } - HStack { - Picker("Key Size", selection: $channelKeySize) { - Text("Empty").tag(0) - Text("Default").tag(-1) - Text("1 bit").tag(1) - Text("128 bit").tag(16) - Text("192 bit").tag(24) - Text("256 bit").tag(32) - } - .pickerStyle(DefaultPickerStyle()) - Spacer() - Button { - if channelKeySize == -1 { - channelKey = "AQ==" - } else { - let key = generateChannelKey(size: channelKeySize) - channelKey = key - } - } label: { - Image(systemName: "lock.rotation") - .font(.title) - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.small) - } - HStack (alignment: .top) { - Text("Key") - Spacer() - TextField ( - "", - text: $channelKey, - axis: .vertical - ) - .foregroundColor(Color.gray) - .disabled(true) - - } - .textSelection(.enabled) - Picker("Channel Role", selection: $channelRole) { - if channelRole == 1 { - Text("Primary").tag(1) - } else{ - Text("Disabled").tag(0) - Text("Secondary").tag(2) } + hasChanges = true + }) + } + HStack { + Picker("Key Size", selection: $channelKeySize) { + Text("Empty").tag(0) + Text("Default").tag(-1) + Text("1 bit").tag(1) + Text("128 bit").tag(16) + Text("192 bit").tag(24) + Text("256 bit").tag(32) } .pickerStyle(DefaultPickerStyle()) - .disabled(channelRole == 1) - Toggle("Uplink Enabled", isOn: $uplink) - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Toggle("Downlink Enabled", isOn: $downlink) - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - - } - .onSubmit { - //validate(name: channelName) - } - .onChange(of: channelName) { newName in - hasChanges = true - } - .onChange(of: channelKeySize) { newKeySize in - if channelKeySize == -1 { - channelKey = "AQ==" - } else { - let key = generateChannelKey(size: channelKeySize) - channelKey = key + Spacer() + Button { + if channelKeySize == -1 { + channelKey = "AQ==" + } else { + let key = generateChannelKey(size: channelKeySize) + channelKey = key + } + } label: { + Image(systemName: "lock.rotation") + .font(.title) } - hasChanges = true + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.small) } - .onChange(of: channelKey) { newKey in - hasChanges = true + HStack (alignment: .top) { + Text("Key") + Spacer() + TextField ( + "", + text: $channelKey, + axis: .vertical + ) + .foregroundColor(Color.gray) + .disabled(true) + } - .onChange(of: channelRole) { newRole in - hasChanges = true + .textSelection(.enabled) + Picker("Channel Role", selection: $channelRole) { + if channelRole == 1 { + Text("Primary").tag(1) + } else{ + Text("Disabled").tag(0) + Text("Secondary").tag(2) + } } - .onChange(of: uplink) { newUplink in - hasChanges = true - } - .onChange(of: downlink) { newDownlink in - hasChanges = true + .pickerStyle(DefaultPickerStyle()) + .disabled(channelRole == 1) + Toggle("Uplink Enabled", isOn: $uplink) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Toggle("Downlink Enabled", isOn: $downlink) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + .onSubmit { + //validate(name: channelName) + } + .onChange(of: channelName) { newName in + hasChanges = true + } + .onChange(of: channelKeySize) { newKeySize in + if channelKeySize == -1 { + channelKey = "AQ==" + } else { + let key = generateChannelKey(size: channelKeySize) + channelKey = key } + hasChanges = true + } + .onChange(of: channelKey) { newKey in + hasChanges = true + } + .onChange(of: channelRole) { newRole in + hasChanges = true + } + .onChange(of: uplink) { newUplink in + hasChanges = true + } + .onChange(of: downlink) { newDownlink in + hasChanges = true } HStack { Button { From 1dad62d1230fd40ede140381ece04caca51808f7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 21 Dec 2022 11:47:02 -0800 Subject: [PATCH 26/28] Save channel --- Meshtastic/Helpers/BLEManager.swift | 27 +++++++++++++++++++++++ Meshtastic/Views/Settings/Channels.swift | 28 +++++++++++++++--------- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 0c1790fa..e30afb07 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -940,6 +940,33 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { return success } + public func saveChannel(channel: Channel, fromUser: UserEntity, toUser: UserEntity) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setChannel = channel + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = 0 //UInt32(fromUser.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { if isConnected { diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 9f3ea027..864c1ac2 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -19,21 +19,20 @@ struct Channels: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @Environment(\.dismiss) private var dismiss + @Environment(\.dismiss) private var goBack @Environment(\.sizeCategory) var sizeCategory + var node: NodeInfoEntity? @State var hasChanges = false @State private var isPresentingEditView = false @State private var isPresentingSaveConfirm: Bool = false - @State private var selectedIndex: Int32 = -1 - @State private var channelIndex: Int32 = 0 @State private var channelName = "" @State private var channelKeySize = 32 @State private var channelKey = "AQ==" - @State private var channelRole = 2 + @State private var channelRole = 0 @State private var uplink = false @State private var downlink = false @@ -44,7 +43,6 @@ struct Channels: View { if node != nil && node?.myInfo != nil { ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in Button(action: { - selectedIndex = channel.index channelIndex = channel.index channelRole = Int(channel.role) channelKey = channel.psk?.base64EncodedString() ?? "" @@ -59,11 +57,10 @@ struct Channels: View { } else if channelKey.count == 44 { channelKeySize = 32 } - isPresentingEditView = true - channelName = channel.name ?? "" uplink = channel.uplinkEnabled downlink = channel.downlinkEnabled + isPresentingEditView = true hasChanges = false }) { VStack(alignment: .leading) { @@ -93,14 +90,14 @@ struct Channels: View { Button { let key = generateChannelKey(size: 32) - print("Add Channel Key \(key) ") - isPresentingEditView = true + channelName = "" channelIndex = Int32(node!.myInfo!.channels!.array.count) channelRole = 2 channelKey = key uplink = false downlink = false hasChanges = false + isPresentingEditView = true } label: { Label("Add Channel", systemImage: "plus.square") @@ -244,10 +241,21 @@ struct Channels: View { var channel = Channel() channel.index = channelIndex channel.settings.name = channelName - channel.settings.psk = Data(base64Encoded: channelKey, options: .ignoreUnknownCharacters) ?? Data() + channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary channel.settings.uplinkEnabled = uplink channel.settings.downlinkEnabled = downlink + + let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) + + if adminMessageId > 0 { + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + channelName = "" + hasChanges = false + isPresentingEditView = false + bleManager.disconnectPeripheral() + } } } #if targetEnvironment(macCatalyst) From 43f515b7bb01ee614b5c9f241b8bd5acf450771c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 21 Dec 2022 12:50:27 -0800 Subject: [PATCH 27/28] Move position timer so it invalidates fewer times, update sendPosition so that it always wants and ack and want response can be adjusted by the use case. Currently only positions sent from a dm will want response. --- Meshtastic/Helpers/BLEManager.swift | 37 +++++++++---------- .../Views/Messages/ChannelMessageList.swift | 2 +- .../Views/Messages/UserMessageList.swift | 2 +- .../Settings/Config/PositionConfig.swift | 2 +- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index e30afb07..acd11c67 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -527,19 +527,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { MeshLogger.log("💥 Error Deleting the All - Broadcast User") } - // MARK: Share Location Position Update Timer - // Use context to pass the radio name with the timer - // Use a RunLoop to prevent the timer from running on the main UI thread - if userSettings?.provideLocation ?? false { - if positionTimer != nil { - positionTimer!.invalidate() - } - positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((userSettings?.provideLocationInterval ?? 900)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) - if positionTimer != nil { - RunLoop.current.add(positionTimer!, forMode: .common) - } - } - if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce { invalidVersion = false lastConnectionError = "" @@ -548,6 +535,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { MeshLogger.log("🤜 BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)") peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) // Config conplete returns so we don't read the characteristic again + // MARK: Share Location Position Update Timer + // Use context to pass the radio name with the timer + // Use a RunLoop to prevent the timer from running on the main UI thread + if userSettings?.provideLocation ?? false { + if positionTimer != nil { + positionTimer!.invalidate() + } + positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((userSettings?.provideLocationInterval ?? 900)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) + if positionTimer != nil { + RunLoop.current.add(positionTimer!, forMode: .common) + } + } + return } @@ -711,7 +711,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { return success } - public func sendPosition(destNum: Int64, wantAck: Bool) -> Bool { + public func sendPosition(destNum: Int64, wantResponse: Bool) -> Bool { var success = false let fromNodeNum = connectedPeripheral.num @@ -734,14 +734,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) meshPacket.from = 0 // Send 0 as from from phone to device to avoid warning about client trying to set node num - meshPacket.wantAck = wantAck var dataMessage = DataMessage() dataMessage.payload = try! positionPacket.serializedData() dataMessage.portnum = PortNum.positionApp - if destNum != emptyNodeNum { - dataMessage.wantResponse = true - } + //if destNum != emptyNodeNum { + dataMessage.wantResponse = wantResponse + //} meshPacket.decoded = dataMessage var toRadio: ToRadio! @@ -765,7 +764,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { // Send a position out to the mesh if "share location with the mesh" is enabled in settings if userSettings!.provideLocation { - let success = sendPosition(destNum: connectedPeripheral.num, wantAck: false) + let success = sendPosition(destNum: connectedPeripheral.num, wantResponse: false) if !success { print("Failed to send positon to device") diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index b59c3195..68f407e3 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -336,7 +336,7 @@ struct ChannelMessageList: View { focusedField = nil replyMessageId = 0 if sendPositionWithMessage { - if bleManager.sendPosition(destNum: Int64(channel.index), wantAck: true) { + if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false) { print("Location Sent") } } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 914f2f91..6dda3220 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -333,7 +333,7 @@ struct UserMessageList: View { focusedField = nil replyMessageId = 0 if sendPositionWithMessage { - if bleManager.sendPosition(destNum: user.num, wantAck: true) { + if bleManager.sendPosition(destNum: user.num, wantResponse: true) { print("Location Sent") } } diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 4aa567f8..e800a64f 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -208,7 +208,7 @@ struct PositionConfig: View { Button("Save Position Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { if fixedPosition { - _ = bleManager.sendPosition(destNum: bleManager.connectedPeripheral.num, wantAck: true) + _ = bleManager.sendPosition(destNum: bleManager.connectedPeripheral.num, wantResponse: false) } var pc = Config.PositionConfig() From 339a3c8db0d26ef8f7ab98f4e49815ad61e2a084 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 21 Dec 2022 13:39:40 -0800 Subject: [PATCH 28/28] Comment out channel editor for 2.0.8 app store release --- Meshtastic/Views/Settings/Channels.swift | 563 ++++++++++++----------- Meshtastic/Views/Settings/Settings.swift | 20 +- 2 files changed, 292 insertions(+), 291 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 864c1ac2..7f73dc12 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -4,284 +4,285 @@ // // Copyright(c) Garth Vander Houwen 4/8/22. // -import SwiftUI -import CoreData - -func generateChannelKey(size: Int) -> String { - var keyData = Data(count: size) - _ = keyData.withUnsafeMutableBytes { - SecRandomCopyBytes(kSecRandomDefault, size, $0.baseAddress!) - } - return keyData.base64EncodedString() -} - -struct Channels: View { - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - @Environment(\.dismiss) private var goBack - @Environment(\.sizeCategory) var sizeCategory - - - var node: NodeInfoEntity? - - @State var hasChanges = false - @State private var isPresentingEditView = false - @State private var isPresentingSaveConfirm: Bool = false - @State private var channelIndex: Int32 = 0 - @State private var channelName = "" - @State private var channelKeySize = 32 - @State private var channelKey = "AQ==" - @State private var channelRole = 0 - @State private var uplink = false - @State private var downlink = false - - var body: some View { - - NavigationStack { - List { - if node != nil && node?.myInfo != nil { - ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in - Button(action: { - channelIndex = channel.index - channelRole = Int(channel.role) - channelKey = channel.psk?.base64EncodedString() ?? "" - if channelKey.count == 0 { - channelKeySize = 0 - } else if channelKey == "AQ==" { - channelKeySize = -1 - } else if channelKey.count == 24 { - channelKeySize = 16 - } else if channelKey.count == 32 { - channelKeySize = 24 - } else if channelKey.count == 44 { - channelKeySize = 32 - } - channelName = channel.name ?? "" - uplink = channel.uplinkEnabled - downlink = channel.downlinkEnabled - isPresentingEditView = true - hasChanges = false - }) { - VStack(alignment: .leading) { - HStack { - CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 36, brightness: 0.1) - .padding(.trailing, 5) - VStack { - HStack { - if channel.name?.isEmpty ?? false { - if channel.role == 1 { - Text(String("PrimaryChannel").camelCaseToWords()).font(.headline) - } else { - Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline) - } - } else { - Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline) - } - } - } - } - } - } - } - } - } - if node?.myInfo?.channels?.array.count ?? 0 < 8 { - - Button { - let key = generateChannelKey(size: 32) - channelName = "" - channelIndex = Int32(node!.myInfo!.channels!.array.count) - channelRole = 2 - channelKey = key - uplink = false - downlink = false - hasChanges = false - isPresentingEditView = true - - } label: { - Label("Add Channel", systemImage: "plus.square") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .sheet(isPresented: $isPresentingEditView) { - - #if targetEnvironment(macCatalyst) - Text("channel") - .font(.largeTitle) - .padding() - #endif - Form { - HStack { - Text("name") - Spacer() - TextField( - "Channel Name", - text: $channelName - ) - .disableAutocorrection(true) - .keyboardType(.alphabet) - .foregroundColor(Color.gray) - .disabled(channelRole == 1 && channelName.count > 0) - .onChange(of: channelName, perform: { value in - channelName = channelName.replacing(" ", with: "") - let totalBytes = channelName.utf8.count - // Only mess with the value if it is too big - if totalBytes > 11 { - let firstNBytes = Data(channelName.utf8.prefix(11)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the channelName back to the last place where it was the right size - channelName = maxBytesString - } - } - hasChanges = true - }) - } - HStack { - Picker("Key Size", selection: $channelKeySize) { - Text("Empty").tag(0) - Text("Default").tag(-1) - Text("1 bit").tag(1) - Text("128 bit").tag(16) - Text("192 bit").tag(24) - Text("256 bit").tag(32) - } - .pickerStyle(DefaultPickerStyle()) - Spacer() - Button { - if channelKeySize == -1 { - channelKey = "AQ==" - } else { - let key = generateChannelKey(size: channelKeySize) - channelKey = key - } - } label: { - Image(systemName: "lock.rotation") - .font(.title) - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.small) - } - HStack (alignment: .top) { - Text("Key") - Spacer() - TextField ( - "", - text: $channelKey, - axis: .vertical - ) - .foregroundColor(Color.gray) - .disabled(true) - - } - .textSelection(.enabled) - Picker("Channel Role", selection: $channelRole) { - if channelRole == 1 { - Text("Primary").tag(1) - } else{ - Text("Disabled").tag(0) - Text("Secondary").tag(2) - } - } - .pickerStyle(DefaultPickerStyle()) - .disabled(channelRole == 1) - Toggle("Uplink Enabled", isOn: $uplink) - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Toggle("Downlink Enabled", isOn: $downlink) - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - } - .onSubmit { - //validate(name: channelName) - } - .onChange(of: channelName) { newName in - hasChanges = true - } - .onChange(of: channelKeySize) { newKeySize in - if channelKeySize == -1 { - channelKey = "AQ==" - } else { - let key = generateChannelKey(size: channelKeySize) - channelKey = key - } - hasChanges = true - } - .onChange(of: channelKey) { newKey in - hasChanges = true - } - .onChange(of: channelRole) { newRole in - hasChanges = true - } - .onChange(of: uplink) { newUplink in - hasChanges = true - } - .onChange(of: downlink) { newDownlink in - hasChanges = true - } - HStack { - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding(.bottom) - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - - var channel = Channel() - channel.index = channelIndex - channel.settings.name = channelName - channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() - channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary - channel.settings.uplinkEnabled = uplink - channel.settings.downlinkEnabled = downlink - - let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) - - if adminMessageId > 0 { - // Should show a saved successfully alert once I know that to be true - // for now just disable the button after a successful save - channelName = "" - hasChanges = false - isPresentingEditView = false - bleManager.disconnectPeripheral() - } - } - } - #if targetEnvironment(macCatalyst) - Button { - isPresentingEditView = false - } label: { - Label("Close", systemImage: "xmark") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding(.bottom) - #endif - } - .presentationDetents([.medium, .large]) - } - } - } - .navigationTitle("channels") - .navigationSplitViewStyle(.balanced) - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - }) - .onAppear { - bleManager.context = context - } - } -} +//import SwiftUI +//import CoreData +// +//func generateChannelKey(size: Int) -> String { +// var keyData = Data(count: size) +// _ = keyData.withUnsafeMutableBytes { +// SecRandomCopyBytes(kSecRandomDefault, size, $0.baseAddress!) +// } +// return keyData.base64EncodedString() +//} +// +//struct Channels: View { +// +// @Environment(\.managedObjectContext) var context +// @EnvironmentObject var bleManager: BLEManager +// @Environment(\.dismiss) private var goBack +// @Environment(\.sizeCategory) var sizeCategory +// +// +// var node: NodeInfoEntity? +// +// @State var hasChanges = false +// @State private var isPresentingEditView = false +// @State private var isPresentingSaveConfirm: Bool = false +// @State private var channelIndex: Int32 = 0 +// @State private var channelName = "" +// @State private var channelKeySize = 32 +// @State private var channelKey = "AQ==" +// @State private var channelRole = 0 +// @State private var uplink = false +// @State private var downlink = false +// +// var body: some View { +// +// NavigationStack { +// List { +// if node != nil && node?.myInfo != nil { +// ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in +// Button(action: { +// channelIndex = channel.index +// channelRole = Int(channel.role) +// channelKey = channel.psk?.base64EncodedString() ?? "" +// if channelKey.count == 0 { +// channelKeySize = 0 +// } else if channelKey == "AQ==" { +// channelKeySize = -1 +// } else if channelKey.count == 24 { +// channelKeySize = 16 +// } else if channelKey.count == 32 { +// channelKeySize = 24 +// } else if channelKey.count == 44 { +// channelKeySize = 32 +// } +// channelName = channel.name ?? "" +// uplink = channel.uplinkEnabled +// downlink = channel.downlinkEnabled +// isPresentingEditView = true +// hasChanges = false +// }) { +// VStack(alignment: .leading) { +// HStack { +// CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 36, brightness: 0.1) +// .padding(.trailing, 5) +// VStack { +// HStack { +// if channel.name?.isEmpty ?? false { +// if channel.role == 1 { +// Text(String("PrimaryChannel").camelCaseToWords()).font(.headline) +// } else { +// Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline) +// } +// } else { +// Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline) +// } +// } +// } +// } +// } +// } +// } +// } +// } +// if node?.myInfo?.channels?.array.count ?? 0 < 8 { +// +// Button { +// let key = generateChannelKey(size: 32) +// channelName = "" +// channelIndex = Int32(node!.myInfo!.channels!.array.count) +// channelRole = 2 +// channelKey = key +// uplink = false +// downlink = false +// hasChanges = false +// isPresentingEditView = true +// +// } label: { +// Label("Add Channel", systemImage: "plus.square") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding() +// .sheet(isPresented: $isPresentingEditView) { +// +// #if targetEnvironment(macCatalyst) +// Text("channel") +// .font(.largeTitle) +// .padding() +// #endif +// Form { +// HStack { +// Text("name") +// Spacer() +// TextField( +// "Channel Name", +// text: $channelName +// ) +// .disableAutocorrection(true) +// .keyboardType(.alphabet) +// .foregroundColor(Color.gray) +// .disabled(channelRole == 1 && channelName.count > 0) +// .onChange(of: channelName, perform: { value in +// channelName = channelName.replacing(" ", with: "") +// let totalBytes = channelName.utf8.count +// // Only mess with the value if it is too big +// if totalBytes > 11 { +// let firstNBytes = Data(channelName.utf8.prefix(11)) +// if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { +// // Set the channelName back to the last place where it was the right size +// channelName = maxBytesString +// } +// } +// hasChanges = true +// }) +// } +// HStack { +// Picker("Key Size", selection: $channelKeySize) { +// Text("Empty").tag(0) +// Text("Default").tag(-1) +// Text("1 bit").tag(1) +// Text("128 bit").tag(16) +// Text("192 bit").tag(24) +// Text("256 bit").tag(32) +// } +// .pickerStyle(DefaultPickerStyle()) +// Spacer() +// Button { +// if channelKeySize == -1 { +// channelKey = "AQ==" +// } else { +// let key = generateChannelKey(size: channelKeySize) +// channelKey = key +// } +// } label: { +// Image(systemName: "lock.rotation") +// .font(.title) +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.small) +// } +// HStack (alignment: .top) { +// Text("Key") +// Spacer() +// TextField ( +// "", +// text: $channelKey, +// axis: .vertical +// ) +// .foregroundColor(Color.gray) +// .disabled(true) +// +// } +// .textSelection(.enabled) +// Picker("Channel Role", selection: $channelRole) { +// if channelRole == 1 { +// Text("Primary").tag(1) +// } else{ +// Text("Disabled").tag(0) +// Text("Secondary").tag(2) +// } +// } +// .pickerStyle(DefaultPickerStyle()) +// .disabled(channelRole == 1) +// Toggle("Uplink Enabled", isOn: $uplink) +// .toggleStyle(SwitchToggleStyle(tint: .accentColor)) +// Toggle("Downlink Enabled", isOn: $downlink) +// .toggleStyle(SwitchToggleStyle(tint: .accentColor)) +// } +// .onSubmit { +// //validate(name: channelName) +// } +// .onChange(of: channelName) { newName in +// hasChanges = true +// } +// .onChange(of: channelKeySize) { newKeySize in +// if channelKeySize == -1 { +// channelKey = "AQ==" +// } else { +// let key = generateChannelKey(size: channelKeySize) +// channelKey = key +// } +// hasChanges = true +// } +// .onChange(of: channelKey) { newKey in +// hasChanges = true +// } +// .onChange(of: channelRole) { newRole in +// hasChanges = true +// } +// .onChange(of: uplink) { newUplink in +// hasChanges = true +// } +// .onChange(of: downlink) { newDownlink in +// hasChanges = true +// } +// HStack { +// Button { +// isPresentingSaveConfirm = true +// } label: { +// Label("save", systemImage: "square.and.arrow.down") +// } +// .disabled(bleManager.connectedPeripheral == nil || !hasChanges) +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding(.bottom) +// .confirmationDialog( +// "are.you.sure", +// isPresented: $isPresentingSaveConfirm, +// titleVisibility: .visible +// ) { +// Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { +// +// var channel = Channel() +// channel.index = channelIndex +// channel.settings.id = UInt32(channelIndex) +// channel.settings.name = channelName +// channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() +// channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary +// channel.settings.uplinkEnabled = uplink +// channel.settings.downlinkEnabled = downlink +// +// let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) +// +// if adminMessageId > 0 { +// // Should show a saved successfully alert once I know that to be true +// // for now just disable the button after a successful save +// channelName = "" +// hasChanges = false +// isPresentingEditView = false +// bleManager.disconnectPeripheral() +// } +// } +// } +// #if targetEnvironment(macCatalyst) +// Button { +// isPresentingEditView = false +// } label: { +// Label("Close", systemImage: "xmark") +// } +// .buttonStyle(.bordered) +// .buttonBorderShape(.capsule) +// .controlSize(.large) +// .padding(.bottom) +// #endif +// } +// .presentationDetents([.medium, .large]) +// } +// } +// } +// .navigationTitle("channels") +// .navigationSplitViewStyle(.automatic) +// .navigationBarItems(trailing: +// ZStack { +// ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") +// }) +// .onAppear { +// bleManager.context = context +// } +// } +//} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 528aacc5..9bb805f7 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -59,16 +59,16 @@ struct Settings: View { Text("lora") } - NavigationLink() { - - Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) - } label: { - - Image(systemName: "fibrechannel") - .symbolRenderingMode(.hierarchical) - - Text("channels") - } +// NavigationLink() { +// +// Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) +// } label: { +// +// Image(systemName: "fibrechannel") +// .symbolRenderingMode(.hierarchical) +// +// Text("channels") +// } NavigationLink() {