Merge pull request #263 from meshtastic/2.0.8_Working_Changes

Working Changes for 2.0.8
This commit is contained in:
Garth Vander Houwen 2022-12-21 13:41:53 -08:00 committed by GitHub
commit 55a8329069
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1215 additions and 484 deletions

View file

@ -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 */; };
@ -98,6 +99,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 */; };
@ -186,6 +188,7 @@
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = "<group>"; };
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = "<group>"; };
DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = "<group>"; };
DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = "<group>"; };
DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = "<group>"; };
DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAnnotation.swift; sourceTree = "<group>"; };
@ -222,6 +225,8 @@
DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = "<group>"; };
DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
DDCDC69A29467643004C1DDA /* MeshtasticDataModelV3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV3.xcdatamodel; sourceTree = "<group>"; };
DDCDC6CC29481FCC004C1DDA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
DDCDC6CE294821AD004C1DDA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = "<group>"; };
DDCFF600285453A7005FA625 /* localonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = localonly.pb.swift; sourceTree = "<group>"; };
DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = "<group>"; };
@ -302,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;
@ -401,6 +407,7 @@
DDC2E14B26CE248E0042C5E4 = {
isa = PBXGroup;
children = (
DDCDC6CD29481FCC004C1DDA /* Localizable.strings */,
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */,
DDC2E15626CE248E0042C5E4 /* Meshtastic */,
DDC2E16D26CE248F0042C5E4 /* MeshtasticTests */,
@ -641,6 +648,7 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
de,
Base,
);
mainGroup = DDC2E14B26CE248E0042C5E4;
@ -665,6 +673,7 @@
buildActionMask = 2147483647;
files = (
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */,
DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */,
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -731,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 */,
@ -835,11 +845,24 @@
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
DDCDC6CD29481FCC004C1DDA /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
DDCDC6CC29481FCC004C1DDA /* en */,
DDCDC6CE294821AD004C1DDA /* de */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* 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 +924,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";
@ -976,7 +1000,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;
@ -1009,7 +1033,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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)
}
}
}
@ -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:
@ -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")
@ -940,6 +939,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)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = true
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
let messageDescription = "Saved Channel \(channel.index) for \(toUser.longName ?? "Unknown")"
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
return Int64(meshPacket.id)
}
return 0
}
public func saveChannelSet(base64UrlString: String) -> Bool {
if isConnected {
@ -957,7 +983,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")
}

View file

@ -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 {
@ -752,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
@ -1038,16 +1043,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)
}
}
@ -1197,7 +1194,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) {
@ -1243,7 +1240,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()
@ -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<NSFetchRequestResult> = 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 {

View file

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

View file

@ -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<NSFetchRequestResult>(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)")
}
@ -87,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)")
}

View file

@ -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")
@ -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()
}
@ -160,7 +161,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")
@ -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)
@ -228,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 : "????")

View file

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

View file

@ -24,7 +24,7 @@ struct DateTimeText: View {
} else {
Text("Unknown Age")
Text("unknown.age")
}
}
}

View file

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

View file

@ -7,17 +7,22 @@ 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")
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"))
}
}

View file

@ -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)
}
}
}
@ -144,7 +144,7 @@ struct ChannelMessageList: View {
self.deleteMessageId = message.messageId
print(deleteMessageId)
}) {
Text("Delete")
Text("delete")
Image(systemName: "trash")
}
}
@ -243,12 +243,12 @@ struct ChannelMessageList: View {
}
} label: {
Text("Share Position")
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
@ -277,7 +277,7 @@ struct ChannelMessageList: View {
.keyboardType(kbType!)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button("Dismiss Keyboard") {
Button("dismiss.keyboard") {
focusedField = nil
}
.font(.subheadline)
@ -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)
@ -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)
@ -323,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")
}
}

View file

@ -28,88 +28,85 @@ 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
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 {
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")
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
@ -121,14 +118,15 @@ 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)
context.refresh(node!.myInfo!, mergeChanges: true)
} label: {
Text("Delete")
Text("delete")
}
}
}
@ -137,7 +135,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)) {
@ -212,9 +210,9 @@ struct Contacts: View {
Button(role: .destructive) {
deleteUserMessages(user: user, context: context)
context.refresh(node!.user!, mergeChanges: true)
} label: {
Text("Delete")
Text("delete")
}
}
}
@ -227,7 +225,7 @@ struct Contacts: View {
}
}
}
.navigationTitle("Contacts")
.navigationTitle("contacts")
.navigationBarItems(leading:
MeshtasticLogo()
)
@ -260,7 +258,7 @@ struct Contacts: View {
UserMessageList(user:user)
} else {
Text("Select a Contact")
Text("select.contact")
}
}
}

View file

@ -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)
@ -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")
}
}
@ -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 Position")
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
@ -277,7 +277,7 @@ struct UserMessageList: View {
.keyboardType(kbType!)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button("Dismiss Keyboard") {
Button("dismiss.keyboard") {
focusedField = nil
}
.font(.subheadline)
@ -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)
@ -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))
@ -318,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")
}
}

View file

@ -132,7 +132,7 @@ struct DeviceMetricsLog: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingClearLogConfirm,
titleVisibility: .visible
) {
@ -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)

View file

@ -135,7 +135,7 @@ struct EnvironmentMetricsLog: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingClearLogConfirm,
titleVisibility: .visible
) {
@ -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)

View file

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

View file

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

View file

@ -114,7 +114,7 @@ struct PositionLog: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingClearLogConfirm,
titleVisibility: .visible
) {
@ -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)

View file

@ -50,7 +50,7 @@ struct AboutMeshtastic: View {
.font(.caption)
}
}
.navigationTitle("About")
.navigationTitle("about")
.navigationBarTitleDisplayMode(.inline)
}
}

View file

@ -17,15 +17,15 @@ enum KeyboardType: Int, CaseIterable, Identifiable {
get {
switch self {
case .defaultKeyboard:
return "Default"
return NSLocalizedString("default", comment: "Default Keyboard")
case .asciiCapable:
return "ASCII Capable"
return NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard")
case .twitter:
return "Twitter"
return NSLocalizedString("twitter", comment: "Twitter Keyboard")
case .emailAddress:
return "Email Address"
return NSLocalizedString("email.address", comment: "Email Address Keyboard")
case .numbersAndPunctuation:
return "Numbers and Punctuation"
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
) {
@ -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 : "????")

View file

@ -0,0 +1,288 @@
//
// 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)
// _ = 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
// }
// }
//}

View file

@ -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))
@ -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)
@ -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 : "????")

View file

@ -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
) {
@ -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)
@ -182,7 +182,7 @@ struct DeviceConfig: View {
Spacer()
}
.navigationTitle("Device Config")
.navigationTitle("device.config")
.navigationBarItems(trailing:
ZStack {

View file

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

View file

@ -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)
@ -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 : "????")

View file

@ -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))
@ -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)
@ -220,7 +220,7 @@ struct CannedMessagesConfig: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
) {
@ -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 : "????")

View file

@ -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) {
@ -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)
@ -131,7 +131,7 @@ struct ExternalNotificationConfig: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
) {
@ -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 : "????")

View file

@ -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))
@ -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)
@ -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)
@ -152,7 +152,7 @@ struct MQTTConfig: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
) {
@ -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 : "????")

View file

@ -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 ) {
@ -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)
@ -96,7 +96,7 @@ struct RangeTestConfig: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
) {
@ -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 : "????")

View file

@ -31,30 +31,30 @@ 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))
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)
}
@ -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)
@ -122,7 +122,7 @@ struct SerialConfig: View {
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
) {
@ -148,7 +148,7 @@ struct SerialConfig: View {
}
}
.navigationTitle("Serial Config")
.navigationTitle("serial.config")
.navigationBarItems(trailing:
ZStack {

View file

@ -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)
@ -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) {
@ -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)
@ -133,7 +133,7 @@ struct TelemetryConfig: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
) {
@ -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 : "????")

View file

@ -33,12 +33,12 @@ 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 {
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)
@ -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.")
@ -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)
@ -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 : "????")

View file

@ -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)
@ -201,14 +201,14 @@ struct PositionConfig: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
"are.you.sure",
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
) {
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, wantResponse: false)
}
var pc = Config.PositionConfig()
@ -240,7 +240,7 @@ struct PositionConfig: View {
}
}
}
.navigationTitle("Position Config")
.navigationTitle("position.config")
.navigationBarItems(trailing:
ZStack {

View file

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

View file

@ -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,16 +56,27 @@ struct Settings: View {
Image(systemName: "dot.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
Text("LoRa")
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 }))
} label: {
Image(systemName: "antenna.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
Text("Bluetooth (BLE)")
Text("bluetooth")
}
NavigationLink {
@ -73,7 +84,7 @@ struct Settings: View {
} label: {
Image(systemName: "flipphone")
.symbolRenderingMode(.hierarchical)
Text("Device")
Text("device")
}
NavigationLink {
@ -81,7 +92,7 @@ struct Settings: View {
} label: {
Image(systemName: "display")
.symbolRenderingMode(.hierarchical)
Text("Display (Device Screen)")
Text("display")
}
NavigationLink {
@ -90,7 +101,7 @@ struct Settings: View {
Image(systemName: "network")
.symbolRenderingMode(.hierarchical)
Text("Network")
Text("network")
}
NavigationLink {
@ -99,11 +110,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 +123,7 @@ struct Settings: View {
Image(systemName: "list.bullet.rectangle.fill")
.symbolRenderingMode(.hierarchical)
Text("Canned Messages")
Text("canned.messages")
}
NavigationLink {
@ -120,44 +131,44 @@ 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 }))
} 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 +176,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 +191,9 @@ struct Settings: View {
Image(systemName: "questionmark.app")
.symbolRenderingMode(.hierarchical)
Text("About Meshtastic")
Text("about.meshtastic")
}
}
}
.onAppear {
@ -192,13 +202,13 @@ struct Settings: View {
}
.listStyle(GroupedListStyle())
.navigationTitle("Settings")
.navigationTitle("settings")
.navigationBarItems(leading:
MeshtasticLogo()
)
}
detail: {
Text("Select an item from the menu")
Text("select.menu.item")
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,183 @@
/*
Localizable.strings
Meshtastic
Created by Garth Vander Houwen on 12/12/22.
*/
"about"="Über";
"about.meshtastic"="Über Meshtastic";
"admin"="admin";
"admin.log"="Admin Message Log";
"ago"="her";
"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";
"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 (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)";
"display.config"="Display Config";
"distance"="Entfernung";
"disconnect"="Trennen";
"echo"="Echo";
"email.address"="Email Adresse";
"enabled"="Aktiviert";
"external.notification"="Externe Benachrichtigung";
"external.notification.config"="Einstellungen der externen Benachrichtigung";
"firmware.version"="Firmware Version";
"gpsformat.dec"="Dezimalgrad Format";
"gpsformat.dms"="Grad Minuten Sekunden";
"gpsformat.utm"="Universal Transversal Mercator";
"gpsformat.mgrs"="Militärisches Gitternetz-Referenzsystem";
"gpsformat.olc"="Open Location Code (aka Plus Codes)";
"gpsformat.osgr"="Ordnance Survey Gitterreferenz";
"heard"="Gehört";
"heard.last"="Zuletzt gehört";
"hybrid"="Hybrid";
"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 Einstellungen";
"map"="Mesh Karte";
"map.type"="kartentyp";
"mesh.log"="Mesh Log";
"message"="Nachricht";
"message.details"="Nachrichtendetails";
"messages"="Nachrichten";
"mode"="Modus";
"module.configuration"="Modul Konfiguration";
"mqtt"="MQTT";
"mqtt.config"="MQTT Config";
"mqtt.username"="Benutzername";
"name"="Name";
"network"="Netzwerk";
"network.config"="Netzwerkeinstellungen";
"nodes"="Nodes";
"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"="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"="Nicht authorisiert";
"satellite"="Satellit";
"save"="Speichern";
"serial"="Serial";
"serial.config"="Serial Konfiguration";
"serial.mode.default"="Standard";
"serial.mode.simple"="Einfach";
"serial.mode.proto"="Protobufs";
"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"="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"="Gehört";
"tapback.thumbsup"="Daumen hoch";
"tapback.thumbsdown"="Daumen runter";
"tapback.haha"="HaHa";
"tapback.exclamation"="Ausrufezeichen";
"tapback.question"="Fragezeichen";
"tapback.poop"="Kacke";
"telemetry"="Telemetrie (Sensoren)";
"telemetry.config"="Telemetrie Einstellungen";
"timeout"="Zeitlimit erreicht";
"twitter"="Twitter";
"unknown.age"="Unbekanntes alter";
"update.interval"="Update intervall";
"user"="Benutzer";
"user.details"="Benutzer Details";
"waiting"="Warte...";

View file

@ -0,0 +1,183 @@
/*
Localizable.strings
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";
"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";
"clear.app.data"="Clear App Data";
"connected.radio"="Connected Radio";
"communicating"="Communicating with device. .";
"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";
"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";
"name"="Name";
"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. . .";