mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Clean up descriptive text to be the same font and color on all the config views
This commit is contained in:
parent
07768d98cd
commit
a5d4f62ca4
9 changed files with 364 additions and 277 deletions
|
|
@ -26,7 +26,7 @@ struct AppSettings: View {
|
|||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
if provideLocation {
|
||||
Toggle(isOn: $enableSmartPosition) {
|
||||
Label("appsettings.smartposition", systemImage: "brain.fill")
|
||||
Label("appsettings.smartposition", systemImage: "brain")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
VStack {
|
||||
|
|
|
|||
|
|
@ -33,26 +33,28 @@ struct DeviceConfig: View {
|
|||
ConfigHeader(title: "Device", config: \.deviceConfig, node: node, onAppear: setDeviceValues)
|
||||
|
||||
Section(header: Text("options")) {
|
||||
Picker("Device Role", selection: $deviceRole ) {
|
||||
ForEach(DeviceRoles.allCases) { dr in
|
||||
Text(dr.name)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Device Role", selection: $deviceRole ) {
|
||||
ForEach(DeviceRoles.allCases) { dr in
|
||||
Text(dr.name)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text(DeviceRoles(rawValue: deviceRole)?.description ?? "")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.padding(.top, 10)
|
||||
Text(DeviceRoles(rawValue: deviceRole)?.description ?? "")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
Picker("Rebroadcast Mode", selection: $rebroadcastMode ) {
|
||||
ForEach(RebroadcastModes.allCases) { rm in
|
||||
Text(rm.name)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Rebroadcast Mode", selection: $rebroadcastMode ) {
|
||||
ForEach(RebroadcastModes.allCases) { rm in
|
||||
Text(rm.name)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text(RebroadcastModes(rawValue: rebroadcastMode)?.description ?? "")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.padding(.top, 10)
|
||||
Text(RebroadcastModes(rawValue: rebroadcastMode)?.description ?? "")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
Picker("Node Info Broadcast Interval", selection: $nodeInfoBroadcastSecs ) {
|
||||
ForEach(UpdateIntervals.allCases) { ui in
|
||||
if ui.rawValue >= 3600 {
|
||||
|
|
@ -61,20 +63,24 @@ struct DeviceConfig: View {
|
|||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.padding(.top, 10)
|
||||
Toggle(isOn: $doubleTapAsButtonPress) {
|
||||
Label("Double Tap as Button", systemImage: "hand.tap")
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $doubleTapAsButtonPress) {
|
||||
Label("Double Tap as Button", systemImage: "hand.tap")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Treat double tap on supported accelerometers as a user button press.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Treat double tap on supported accelerometers as a user button press.")
|
||||
.font(.caption)
|
||||
|
||||
Toggle(isOn: $isManaged) {
|
||||
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $isManaged) {
|
||||
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.")
|
||||
.font(.caption)
|
||||
}
|
||||
Section(header: Text("Debug")) {
|
||||
Toggle(isOn: $serialEnabled) {
|
||||
|
|
|
|||
|
|
@ -31,83 +31,108 @@ struct DisplayConfig: View {
|
|||
ConfigHeader(title: "Display", config: \.displayConfig, node: node, onAppear: setDisplayValues)
|
||||
|
||||
Section(header: Text("Device Screen")) {
|
||||
Picker("Display Mode", selection: $displayMode ) {
|
||||
ForEach(DisplayModes.allCases) { dm in
|
||||
Text(dm.description)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Display Mode", selection: $displayMode ) {
|
||||
ForEach(DisplayModes.allCases) { dm in
|
||||
Text(dm.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Override automatic OLED screen detection.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Override automatic OLED screen detection.")
|
||||
.font(.caption)
|
||||
|
||||
Toggle(isOn: $compassNorthTop) {
|
||||
|
||||
Label("Always point north", systemImage: "location.north.circle")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("The compass heading on the screen outside of the circle will always point north.")
|
||||
.font(.caption)
|
||||
Toggle(isOn: $wakeOnTapOrMotion) {
|
||||
Label("Wake Screen on tap or motion", systemImage: "gyroscope")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Requires that there be an accelerometer on your device.")
|
||||
.font(.caption)
|
||||
Toggle(isOn: $flipScreen) {
|
||||
|
||||
Label("Flip Screen", systemImage: "pip.swap")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Flip screen vertically")
|
||||
.font(.caption)
|
||||
|
||||
Picker("OLED Type", selection: $oledType ) {
|
||||
ForEach(OledTypes.allCases) { ot in
|
||||
Text(ot.description)
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
Toggle(isOn: $compassNorthTop) {
|
||||
|
||||
Label("Always point north", systemImage: "location.north.circle")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("The compass heading on the screen outside of the circle will always point north.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $wakeOnTapOrMotion) {
|
||||
Label("Wake Screen on tap or motion", systemImage: "gyroscope")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Requires that there be an accelerometer on your device.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $flipScreen) {
|
||||
|
||||
Label("Flip Screen", systemImage: "pip.swap")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Flip screen vertically")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Picker("OLED Type", selection: $oledType ) {
|
||||
ForEach(OledTypes.allCases) { ot in
|
||||
Text(ot.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Override automatic OLED screen detection.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Override automatic OLED screen detection.")
|
||||
.font(.caption)
|
||||
}
|
||||
Section(header: Text("Timing & Format")) {
|
||||
Picker("Screen on for", selection: $screenOnSeconds ) {
|
||||
ForEach(ScreenOnIntervals.allCases) { soi in
|
||||
Text(soi.description)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Screen on for", selection: $screenOnSeconds ) {
|
||||
ForEach(ScreenOnIntervals.allCases) { soi in
|
||||
Text(soi.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("How long the screen remains on after the user button is pressed or messages are received.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("How long the screen remains on after the user button is pressed or messages are received.")
|
||||
.font(.caption)
|
||||
|
||||
Picker("Carousel Interval", selection: $screenCarouselInterval ) {
|
||||
ForEach(ScreenCarouselIntervals.allCases) { sci in
|
||||
Text(sci.description)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Carousel Interval", selection: $screenCarouselInterval ) {
|
||||
ForEach(ScreenCarouselIntervals.allCases) { sci in
|
||||
Text(sci.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.")
|
||||
.font(.caption)
|
||||
|
||||
Picker("GPS Format", selection: $gpsFormat ) {
|
||||
ForEach(GpsFormats.allCases) { lu in
|
||||
Text(lu.description)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("GPS Format", selection: $gpsFormat ) {
|
||||
ForEach(GpsFormats.allCases) { lu in
|
||||
Text(lu.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Text("The format used to display GPS coordinates on the device screen.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Text("The format used to display GPS coordinates on the device screen.")
|
||||
.font(.caption)
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
Picker("Display Units", selection: $units ) {
|
||||
ForEach(Units.allCases) { un in
|
||||
Text(un.description)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Display Units", selection: $units ) {
|
||||
ForEach(Units.allCases) { un in
|
||||
Text(un.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Units displayed on the device screen")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Units displayed on the device screen")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.displayConfig == nil)
|
||||
|
|
|
|||
|
|
@ -57,32 +57,37 @@ struct LoRaConfig: View {
|
|||
|
||||
Section(header: Text("Options")) {
|
||||
|
||||
Picker("Region", selection: $region ) {
|
||||
ForEach(RegionCodes.allCases) { r in
|
||||
Text(r.description)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Region", selection: $region ) {
|
||||
ForEach(RegionCodes.allCases) { r in
|
||||
Text(r.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.fixedSize()
|
||||
|
||||
Text("The region where you will be using your radios.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.fixedSize()
|
||||
|
||||
Text("The region where you will be using your radios.")
|
||||
.font(.caption)
|
||||
|
||||
Toggle(isOn: $usePreset) {
|
||||
Label("Use Preset", systemImage: "list.bullet.rectangle")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
if usePreset {
|
||||
Picker("Presets", selection: $modemPreset ) {
|
||||
ForEach(ModemPresets.allCases) { m in
|
||||
Text(m.description)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Presets", selection: $modemPreset ) {
|
||||
ForEach(ModemPresets.allCases) { m in
|
||||
Text(m.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.fixedSize()
|
||||
Text("Available modem presets, default is Long Fast.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.fixedSize()
|
||||
Text("Available modem presets, default is Long Fast.")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
Section(header: Text("Advanced")) {
|
||||
|
|
@ -123,36 +128,40 @@ struct LoRaConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Picker("Number of hops", selection: $hopLimit) {
|
||||
ForEach(1..<8) {
|
||||
Text("\($0)")
|
||||
.tag($0 == 0 ? 3 : $0)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully.")
|
||||
.font(.caption)
|
||||
|
||||
HStack {
|
||||
Text("Frequency Slot")
|
||||
.fixedSize()
|
||||
TextField("Frequency Slot", value: $channelNum, formatter: formatter)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .keyboard) {
|
||||
Button("dismiss.keyboard") {
|
||||
focusedField = nil
|
||||
}
|
||||
.font(.subheadline)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Number of hops", selection: $hopLimit) {
|
||||
ForEach(1..<8) {
|
||||
Text("\($0)")
|
||||
.tag($0 == 0 ? 3 : $0)
|
||||
}
|
||||
.keyboardType(.decimalPad)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.focused($focusedField, equals: .channelNum)
|
||||
.disabled(overrideFrequency > 0.0)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("Frequency Slot")
|
||||
.fixedSize()
|
||||
TextField("Frequency Slot", value: $channelNum, formatter: formatter)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .keyboard) {
|
||||
Button("dismiss.keyboard") {
|
||||
focusedField = nil
|
||||
}
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
.keyboardType(.decimalPad)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.focused($focusedField, equals: .channelNum)
|
||||
.disabled(overrideFrequency > 0.0)
|
||||
}
|
||||
Text("This determines the actual frequency you are transmitting on in the band. If set to 0 this value will be calculated automatically based on the primary channel name.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
Text("This determines the actual frequency you are transmitting on in the band. If set to 0 this value will be calculated automatically based on the primary channel name.")
|
||||
.font(.caption)
|
||||
Toggle(isOn: $rxBoostedGain) {
|
||||
Label("RX Boosted Gain", systemImage: "waveform.badge.plus")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,15 @@ struct NetworkConfig: View {
|
|||
|
||||
if (node != nil && node?.metadata?.hasWifi ?? false) {
|
||||
Section(header: Text("WiFi Options")) {
|
||||
Toggle(isOn: $wifiEnabled) {
|
||||
Label("enabled", systemImage: "wifi")
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $wifiEnabled) {
|
||||
Label("enabled", systemImage: "wifi")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Enabling WiFi will disable the bluetooth connection to the app.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
HStack {
|
||||
Label("ssid", systemImage: "network")
|
||||
TextField("ssid", text: $wifiSsid)
|
||||
|
|
@ -77,18 +82,20 @@ struct NetworkConfig: View {
|
|||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
Text("Enabling WiFi will disable the bluetooth connection to the app.")
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
if (node != nil && node?.metadata?.hasEthernet ?? false) {
|
||||
Section(header: Text("Ethernet Options")) {
|
||||
Toggle(isOn: $ethEnabled) {
|
||||
Label("enabled", systemImage: "network")
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $ethEnabled) {
|
||||
Label("enabled", systemImage: "network")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Enabling Ethernet will disable the bluetooth connection to the app.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Enabling Ethernet will disable the bluetooth connection to the app.")
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,44 +79,55 @@ struct PositionConfig: View {
|
|||
|
||||
Section(header: Text("Position Packet")) {
|
||||
|
||||
Picker("Interval", selection: $positionBroadcastSeconds) {
|
||||
ForEach(UpdateIntervals.allCases) { at in
|
||||
if at.rawValue >= 900 {
|
||||
Text(at.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("The maximum interval that can elapse without a node broadcasting a position")
|
||||
.font(.caption)
|
||||
Toggle(isOn: $smartPositionEnabled) {
|
||||
Label("Smart Position", systemImage: "location.fill.viewfinder")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
if smartPositionEnabled {
|
||||
Picker("Minimum Broadcast Interval", selection: $broadcastSmartMinimumIntervalSecs) {
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Broadcast Interval", selection: $positionBroadcastSeconds) {
|
||||
ForEach(UpdateIntervals.allCases) { at in
|
||||
Text(at.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("The fastest that position updates will be sent if the minimum distance has been satisfied")
|
||||
.font(.caption)
|
||||
Picker("Minimum Distance", selection: $broadcastSmartMinimumDistance) {
|
||||
ForEach(10..<151) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
if $0.isMultiple(of: 5) {
|
||||
Text("\($0)")
|
||||
.tag($0)
|
||||
}
|
||||
if at.rawValue >= 900 {
|
||||
Text(at.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("The minimum distance change in meters to be considered for a smart position broadcast.")
|
||||
.font(.caption)
|
||||
Text("The maximum interval that can elapse without a node broadcasting a position")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
Toggle(isOn: $smartPositionEnabled) {
|
||||
Label("Smart Position", systemImage: "brain")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
if smartPositionEnabled {
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Minimum Interval", selection: $broadcastSmartMinimumIntervalSecs) {
|
||||
ForEach(UpdateIntervals.allCases) { at in
|
||||
Text(at.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("The fastest that position updates will be sent if the minimum distance has been satisfied")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Minimum Distance", selection: $broadcastSmartMinimumDistance) {
|
||||
ForEach(10..<151) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
if $0.isMultiple(of: 5) {
|
||||
Text("\($0)")
|
||||
.tag($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("The minimum distance change in meters to be considered for a smart position broadcast.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
Section(header: Text("Device GPS")) {
|
||||
|
|
@ -131,27 +142,33 @@ struct PositionConfig: View {
|
|||
.padding(.bottom, 5)
|
||||
|
||||
if gpsMode == 1 {
|
||||
Picker("Update Interval", selection: $gpsUpdateInterval) {
|
||||
ForEach(GpsUpdateIntervals.allCases) { ui in
|
||||
Text(ui.description)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Update Interval", selection: $gpsUpdateInterval) {
|
||||
ForEach(GpsUpdateIntervals.allCases) { ui in
|
||||
Text(ui.description)
|
||||
}
|
||||
}
|
||||
Text("How often should we try to get a GPS position.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
Text("How often should we try to get a GPS position.")
|
||||
.font(.caption)
|
||||
} else {
|
||||
Toggle(isOn: $fixedPosition) {
|
||||
Label("Fixed Position", systemImage: "location.square.fill")
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $fixedPosition) {
|
||||
Label("Fixed Position", systemImage: "location.square.fill")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("If enabled your current phone location will be sent to the device and will broadcast over the mesh on the position interval. Fixed position will always use the most recent position the device has.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("If enabled your current phone location will be sent to the device and will broadcast over the mesh on the position interval. Fixed position will always use the most recent position the device has.")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
Section(header: Text("Position Flags")) {
|
||||
|
||||
Text("Optional fields to include when assembling position messages. the more fields are included, the larger the message will be - leading to longer airtime and a higher risk of packet loss")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
Toggle(isOn: $includeAltitude) {
|
||||
Label("Altitude", systemImage: "arrow.up")
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ struct PowerConfig: View {
|
|||
@State private var waitBluetoothSecs = 60
|
||||
@State private var lsSecs = 300
|
||||
@State private var minWakeSecs = 10
|
||||
|
||||
|
||||
@State private var currentDevice: DeviceHardware?
|
||||
|
||||
@State private var hasChanges: Bool = false
|
||||
@FocusState private var isFocused: Bool
|
||||
|
||||
|
|
@ -25,14 +27,16 @@ struct PowerConfig: View {
|
|||
Form {
|
||||
ConfigHeader(title: "Power", config: \.powerConfig, node: node, onAppear: setPowerValues)
|
||||
|
||||
Section(header: Text("power")) {
|
||||
Section {
|
||||
|
||||
Toggle(isOn: $isPowerSaving) {
|
||||
Text("power.save")
|
||||
Text("power.solar")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
Text("For use when powered from a low-current source in addition to the battery, minimizes power consumption as much as possible even if the deviced appears to be powered.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
|
||||
Section {
|
||||
Toggle(isOn: $shutdownOnPowerLoss) {
|
||||
Text("power.shutdown.on.power.loss")
|
||||
}
|
||||
|
|
@ -47,53 +51,56 @@ struct PowerConfig: View {
|
|||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
} header: {
|
||||
Text("Shutdown")
|
||||
Text("power")
|
||||
}
|
||||
if currentDevice?.architecture == .esp32 || currentDevice?.architecture == .esp32S3 {
|
||||
|
||||
Section {
|
||||
Toggle(isOn: $adcOverride) {
|
||||
Text("power.adc.override")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Section {
|
||||
Toggle(isOn: $adcOverride) {
|
||||
Text("power.adc.override")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
if adcOverride {
|
||||
HStack {
|
||||
Text("power.adc.multiplier")
|
||||
Spacer()
|
||||
FloatField(title: "power.adc.multiplier", number: $adcMultiplier) {
|
||||
(2.0 ... 6.0).contains($0)
|
||||
if adcOverride {
|
||||
HStack {
|
||||
Text("power.adc.multiplier")
|
||||
Spacer()
|
||||
FloatField(title: "power.adc.multiplier", number: $adcMultiplier) {
|
||||
(2.0 ... 6.0).contains($0)
|
||||
}
|
||||
.focused($isFocused)
|
||||
Spacer()
|
||||
}
|
||||
.focused($isFocused)
|
||||
Spacer()
|
||||
}
|
||||
} header: {
|
||||
Text("Battery")
|
||||
}
|
||||
} header: {
|
||||
Text("Battery")
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("power.wait.bluetooth.secs", selection: $waitBluetoothSecs) {
|
||||
ForEach(PowerIntervals.allCases) {
|
||||
Text($0.description)
|
||||
|
||||
Section {
|
||||
Picker("power.wait.bluetooth.secs", selection: $waitBluetoothSecs) {
|
||||
ForEach(PowerIntervals.allCases) {
|
||||
Text($0.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Picker("power.ls.secs", selection: $lsSecs) {
|
||||
ForEach(PowerIntervals.allCases) {
|
||||
Text($0.description)
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Picker("power.ls.secs", selection: $lsSecs) {
|
||||
ForEach(PowerIntervals.allCases) {
|
||||
Text($0.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Picker("power.min.wake.secs", selection: $minWakeSecs) {
|
||||
ForEach(PowerIntervals.allCases) {
|
||||
Text($0.description)
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Picker("power.min.wake.secs", selection: $minWakeSecs) {
|
||||
ForEach(PowerIntervals.allCases) {
|
||||
Text($0.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
} header: {
|
||||
Text("Sleep")
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
} header: {
|
||||
Text("Sleep")
|
||||
}
|
||||
}
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.powerConfig == nil)
|
||||
|
|
@ -118,6 +125,16 @@ struct PowerConfig: View {
|
|||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
|
||||
Api().loadDeviceHardwareData { (hw) in
|
||||
for device in hw {
|
||||
let currentHardware = node?.user?.hwModel ?? "UNSET"
|
||||
let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "")
|
||||
if deviceString == currentHardware {
|
||||
currentDevice = device
|
||||
}
|
||||
}
|
||||
}
|
||||
setPowerValues()
|
||||
|
||||
// Need to request a Power config from the remote node before allowing changes
|
||||
|
|
|
|||
|
|
@ -42,53 +42,59 @@ struct UserConfig: View {
|
|||
VStack {
|
||||
Form {
|
||||
Section(header: Text("User Details")) {
|
||||
HStack {
|
||||
Label(isLicensed ? "Call Sign" : "Long Name", systemImage: "person.crop.rectangle.fill")
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Label(isLicensed ? "Call Sign" : "Long Name", systemImage: "person.crop.rectangle.fill")
|
||||
|
||||
TextField("Long Name", text: $longName)
|
||||
.onChange(of: longName, perform: { _ in
|
||||
let totalBytes = longName.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > (isLicensed ? 6 : 36) {
|
||||
let firstNBytes = Data(longName.utf8.prefix(isLicensed ? 6 : 36))
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
// Set the longName back to the last place where it was the right size
|
||||
longName = maxBytesString
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
.keyboardType(.default)
|
||||
.disableAutocorrection(true)
|
||||
if longName.isEmpty && isLicensed {
|
||||
Label("Call Sign must not be empty", systemImage: "exclamationmark.square")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to \(isLicensed ? "8" : "36") bytes long.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
|
||||
TextField("Long Name", text: $longName)
|
||||
.onChange(of: longName, perform: { _ in
|
||||
let totalBytes = longName.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > (isLicensed ? 6 : 36) {
|
||||
let firstNBytes = Data(longName.utf8.prefix(isLicensed ? 6 : 36))
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
// Set the longName back to the last place where it was the right size
|
||||
longName = maxBytesString
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Label("Short Name", systemImage: "circlebadge.fill")
|
||||
TextField("Short Name", text: $shortName)
|
||||
.foregroundColor(.gray)
|
||||
.onChange(of: shortName, perform: { _ in
|
||||
let totalBytes = shortName.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 4 {
|
||||
let firstNBytes = Data(shortName.utf8.prefix(4))
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
// Set the shortName back to the last place where it was the right size
|
||||
shortName = maxBytesString
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
.keyboardType(.default)
|
||||
.disableAutocorrection(true)
|
||||
if longName.isEmpty && isLicensed {
|
||||
Label("Call Sign must not be empty", systemImage: "exclamationmark.square")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to \(isLicensed ? "8" : "36") bytes long.")
|
||||
.font(.caption2)
|
||||
|
||||
HStack {
|
||||
Label("Short Name", systemImage: "circlebadge.fill")
|
||||
TextField("Short Name", text: $shortName)
|
||||
.foregroundColor(.gray)
|
||||
.onChange(of: shortName, perform: { _ in
|
||||
let totalBytes = shortName.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 4 {
|
||||
let firstNBytes = Data(shortName.utf8.prefix(4))
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
// Set the shortName back to the last place where it was the right size
|
||||
shortName = maxBytesString
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
.disableAutocorrection(true)
|
||||
Text("The last 4 of the device MAC address will be appended to the short name to set the device's BLE Name. Short name can be up to 4 bytes long.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
.disableAutocorrection(true)
|
||||
Text("The last 4 of the device MAC address will be appended to the short name to set the device's BLE Name. Short name can be up to 4 bytes long.")
|
||||
.font(.caption2)
|
||||
|
||||
// Only manage ham mode for the locally connected node
|
||||
if node?.num ?? 0 > 0 && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Toggle(isOn: $isLicensed) {
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@
|
|||
"power.config"="Power Config";
|
||||
"power.ls.secs"="Light Sleep Interval";
|
||||
"power.min.wake.secs"="Minimum Wake Interval";
|
||||
"power.save"="Power Save";
|
||||
"power.solar"="Solar Powered";
|
||||
"power.shutdown.on.power.loss"="Shutdown on Power Loss";
|
||||
"power.shutdown.after.secs"="After";
|
||||
"power.wait.bluetooth.secs"="Bluetooth Off After";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue