diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 8bcca6f1..cae899b7 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -59,6 +59,9 @@ DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FF298EE33B00D21B61 /* remote_hardware.pb.swift */; }; DD5E5212298EE33B00D21B61 /* apponly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5200298EE33B00D21B61 /* apponly.pb.swift */; }; DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */; }; + DD5E523A298EFA5300D21B61 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5239298EFA5300D21B61 /* TelemetryWeather.swift */; }; + DD5E523C298F02D400D21B61 /* LicensedUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523B298F02D400D21B61 /* LicensedUser.swift */; }; + DD5E523F298F5A9E00D21B61 /* AQICircleDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523E298F5A9E00D21B61 /* AQICircleDisplay.swift */; }; DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; @@ -189,6 +192,9 @@ DD5E51FF298EE33B00D21B61 /* remote_hardware.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = remote_hardware.pb.swift; sourceTree = ""; }; DD5E5200298EE33B00D21B61 /* apponly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = apponly.pb.swift; sourceTree = ""; }; DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = deviceonly.pb.swift; sourceTree = ""; }; + DD5E5239298EFA5300D21B61 /* TelemetryWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = ""; }; + DD5E523B298F02D400D21B61 /* LicensedUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensedUser.swift; sourceTree = ""; }; + DD5E523E298F5A9E00D21B61 /* AQICircleDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AQICircleDisplay.swift; sourceTree = ""; }; DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = ""; }; DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; @@ -335,6 +341,7 @@ DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, DDA0B6B1294CDC55001356EC /* Channels.swift */, + DD5E523B298F02D400D21B61 /* LicensedUser.swift */, DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DD3501882852FC3B000FC853 /* Settings.swift */, @@ -370,6 +377,14 @@ path = meshtastic; sourceTree = ""; }; + DD5E523D298F5A7D00D21B61 /* Weather */ = { + isa = PBXGroup; + children = ( + DD5E523E298F5A9E00D21B61 /* AQICircleDisplay.swift */, + ); + path = Weather; + sourceTree = ""; + }; DD61937A2863876A00E59241 /* Config */ = { isa = PBXGroup; children = ( @@ -423,6 +438,7 @@ DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */, DD1925B828CDA93900720036 /* SerialConfigEnums.swift */, DD994B68295F88B60013760A /* IntervalEnums.swift */, + DD5E5239298EFA5300D21B61 /* TelemetryWeather.swift */, ); path = Enums; sourceTree = ""; @@ -556,6 +572,7 @@ DDC2E18D26CE25CB0042C5E4 /* Helpers */ = { isa = PBXGroup; children = ( + DD5E523D298F5A7D00D21B61 /* Weather */, DD47E3D526F17ED900029299 /* CircleText.swift */, DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, DDC3B273283F411B00AC321C /* LastHeardText.swift */, @@ -764,6 +781,7 @@ DD457188293C7E63000C49FB /* SignalStrengthIndicator.swift in Sources */, DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */, DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */, + DD5E523F298F5A9E00D21B61 /* AQICircleDisplay.swift in Sources */, DD964FBF296E76EF007C176F /* WaypointFormView.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */, @@ -836,6 +854,7 @@ DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */, DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */, + DD5E523C298F02D400D21B61 /* LicensedUser.swift in Sources */, DDB6ABE428B13FFF00384BA1 /* DisplayEnums.swift in Sources */, DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */, DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, @@ -848,6 +867,7 @@ DD5E5206298EE33B00D21B61 /* localonly.pb.swift in Sources */, DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */, + DD5E523A298EFA5300D21B61 /* TelemetryWeather.swift in Sources */, C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, DD58C5F22919AD3C00D5BEFB /* ChannelEntityExtension.swift in Sources */, DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, diff --git a/Meshtastic/Enums/DeviceRoles.swift b/Meshtastic/Enums/DeviceRoles.swift index 1b6a62e6..f5652f0d 100644 --- a/Meshtastic/Enums/DeviceRoles.swift +++ b/Meshtastic/Enums/DeviceRoles.swift @@ -50,9 +50,9 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { case .routerClient: return NSLocalizedString("device.role.routerclient", comment: "Router Client - Hybrid of the Client and Router roles. Similar to Router, except the Router Client can be used as both a Router and an app connected Client. BLE/Wi-Fi and OLED screen will not be put to sleep.") case .repeater: - return "Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role." + return NSLocalizedString("device.role.repeater", comment: "Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role.") case .tracker: - return "Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off." + return NSLocalizedString("device.role.tracker", comment: "Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off.") } } } diff --git a/Meshtastic/Enums/TelemetryWeather.swift b/Meshtastic/Enums/TelemetryWeather.swift new file mode 100644 index 00000000..bab09f34 --- /dev/null +++ b/Meshtastic/Enums/TelemetryWeather.swift @@ -0,0 +1,75 @@ +// +// TelemetryWeather.swift +// Meshtastic +// +// Copyright Garth Vander Houwen 2/4/23. +// + +//https://developer.apple.com/documentation/weatherkit/weathercondition + +// clear * +// cloudy * +// foggy +// haze +// mostlyClear +// partlyCloudy +// smoky * +// breezy +// windy +// drizzle +// heavyRain +// isolatedThunderstorms +// rain * +// sunShowers +// scatteredThunderstorms +// strongStorms +// thunderstorms +// frigid * +// hail +// hot * +// flurries +// sleet +// snow * +// sunFlurries +// wintryMix +// blizzard +// blowingSnow +// freezingDrizzle +// freezingRain +// heavySnow +// hurricane +// tropicalStorm + +enum WeatherConditions: Int, CaseIterable, Identifiable { + + case clear = 0 + case cloudy = 1 + case frigid = 2 + case hot = 3 + case rain = 4 + case smoky = 5 + case snow = 6 + + var id: Int { self.rawValue } + var symbolName: String { + get { + switch self { + + case .clear: + return "sparkle" + case .cloudy: + return "cloud" + case .hot: + return "sun.max.trianglebadge.exclamationmark.fill" + case .rain: + return "cloud.rain" + case .frigid: + return "thermometer.snowflake" + case .smoky: + return "smoke" + case .snow: + return "cloud.snow" + } + } + } +} diff --git a/Meshtastic/Views/Helpers/Weather/AQICircleDisplay.swift b/Meshtastic/Views/Helpers/Weather/AQICircleDisplay.swift new file mode 100644 index 00000000..bf691666 --- /dev/null +++ b/Meshtastic/Views/Helpers/Weather/AQICircleDisplay.swift @@ -0,0 +1,68 @@ +// +// AQICircleDisplay.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 2/4/23. +// +import SwiftUI + +struct AirQualityIndexCompact: View { + var aqi: Int + + var body: some View { + + + HStack (spacing: 0.5) { + Text("AQI \(aqi)") + .foregroundColor(.gray) + .padding(.trailing, 0) + .font(.caption) + + if aqi > 0 && aqi < 51 { + // Good + Circle() + .fill(.green) + .frame(width: 10, height: 10) + } else if aqi > 50 && aqi < 101 { + // Satisfactory + Circle() + .fill(Color(red: 0, green: 0.9882, blue: 0.1804)) + .frame(width: 10, height: 10) + } else if aqi > 100 && aqi < 201 { + // Moderate + Circle() + .fill(.yellow) + .frame(width: 10, height: 10) + } else if aqi > 200 && aqi < 301 { + // Poor + Circle() + .fill(.orange) + .frame(width: 10, height: 10) + + } else if aqi > 300 && aqi < 401 { + // Very Poor + Circle() + .fill(.red) + .frame(width: 10, height: 10) + } else if aqi >= 401 { + // Very Poor + Circle() + .fill(Color(red: 0.8392, green: 0.0667, blue: 0)) + .frame(width: 10, height: 10) + } + } + } +} +struct AQICircleDisplay_Previews: PreviewProvider { + static var previews: some View { + + VStack { + AirQualityIndexCompact(aqi: 5) + AirQualityIndexCompact(aqi: 51) + AirQualityIndexCompact(aqi: 101) + AirQualityIndexCompact(aqi: 201) + AirQualityIndexCompact(aqi: 301) + AirQualityIndexCompact(aqi: 401) + } + } +} diff --git a/Meshtastic/Views/Settings/LicensedUser.swift b/Meshtastic/Views/Settings/LicensedUser.swift new file mode 100644 index 00000000..113c4c1e --- /dev/null +++ b/Meshtastic/Views/Settings/LicensedUser.swift @@ -0,0 +1,8 @@ +// +// LicensedUser.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 2/4/23. +// + +import Foundation diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index 4af3240e..dd2aecb0 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -19,12 +19,13 @@ struct UserConfig: View { @State var hasChanges = false @State var shortName = "" @State var longName = "" + @State var isLicensed = false var body: some View { VStack { Form { - Section(header: Text("USER DETAILS")) { + Section(header: Text("User Details")) { HStack { Label("Long Name", systemImage: "person.crop.rectangle.fill") TextField("Long Name", text: $longName) @@ -65,6 +66,13 @@ struct UserConfig: View { .disableAutocorrection(true) Text("The short name is used in maps and messaging and will be appended to the last 4 of the device MAC address to set the device's BLE Name. It can be up to 4 bytes long.") .font(.caption) + + Toggle(isOn: $isLicensed) { + Label("Licensed User", systemImage: "person.text.rectangle") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("Enable only if you are a licensed amateur radio user for your region.") + .font(.caption) } } .disabled(bleManager.connectedPeripheral == nil) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 65eca292..32add33f 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -66,6 +66,8 @@ "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."; +"device.role.repeater"="Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role."; +"device.role.tracker"="Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off."; "direct.messages"="Direktnachrichten"; "dismiss.keyboard"="Dismiss Keyboard"; "display"="Display (Device Screen)"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 4872c315..fc958b77 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -64,8 +64,10 @@ "device.metrics.log"="Device Metrics Log"; "device.role.client"="Client (default) - App connected client."; "device.role.clientmute"="Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh."; -"device.role.router"="Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep."; -"device.role.routerclient"="Router Client - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client."; +"device.role.router"="Router - Mesh packets will prefer to be routed over this node. Assumes device will operate in a standalone manner while placed in a location with a coverage advantage. WARNING: The BLE/Wi-Fi radios and the OLED screen will be put to sleep."; +"device.role.routerclient"="Router Client - Hybrid of the Client and Router roles. Similar to Router, except the Router Client can be used as both a Router and an app connected Client. BLE/Wi-Fi and OLED screen will not be put to sleep."; +"device.role.repeater"="Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role."; +"device.role.tracker"="Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off."; "direct.messages"="Direct Messages"; "dismiss.keyboard"="Dismiss Keyboard"; "display"="Display (Device Screen)"; diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index 48bb8a74..bf6c29d1 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -66,6 +66,8 @@ "device.role.clientmute"="静默模式 - 与标准模式类似,App 可以连接到电台进行收发操作,但不会转发 Mesh 网络中其他节点的消息。"; "device.role.router"="纯中继模式 - 自动转发 Mesh 网络中其他节点的消息,中继模式下屏幕会熄灭,Wi-Fi 和蓝牙将会进入睡眠模式,App 将无法连接到电台进行收发操作。"; "device.role.routerclient"="中继模式 - 优先转发 Mesh 网络中其他节点的消息,App 也可以连接到电台进行收发操作。"; +"device.role.repeater"="Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role."; +"device.role.tracker"="Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off."; "direct.messages"="直接收到的消息"; "dismiss.keyboard"="隐藏键盘"; "display"="屏幕(电台屏幕)";