mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #263 from meshtastic/2.0.8_Working_Changes
Working Changes for 2.0.8
This commit is contained in:
commit
55a8329069
49 changed files with 1215 additions and 484 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ struct DateTimeText: View {
|
|||
|
||||
} else {
|
||||
|
||||
Text("Unknown Age")
|
||||
Text("unknown.age")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ struct AboutMeshtastic: View {
|
|||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.navigationTitle("About")
|
||||
.navigationTitle("about")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
288
Meshtastic/Views/Settings/Channels.swift
Normal file
288
Meshtastic/Views/Settings/Channels.swift
Normal 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
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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 : "????")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
183
de.lproj/Localizable.strings
Normal file
183
de.lproj/Localizable.strings
Normal 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...";
|
||||
183
en.lproj/Localizable.strings
Normal file
183
en.lproj/Localizable.strings
Normal 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. . .";
|
||||
Loading…
Add table
Add a link
Reference in a new issue